From f0d01f9621cdf6b449d21ffe6a97e38f4e06cb72 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 6 Dec 2022 21:48:38 +0100 Subject: [PATCH 001/345] add options to set soft jumpers, add option to set MAC suffix from ID --- src/tools/ebuspicloader.cpp | 104 +++++++++++++++++++++++++++++++++--- 1 file changed, 97 insertions(+), 7 deletions(-) diff --git a/src/tools/ebuspicloader.cpp b/src/tools/ebuspicloader.cpp index a4d195f0b..9cb684e6b 100644 --- a/src/tools/ebuspicloader.cpp +++ b/src/tools/ebuspicloader.cpp @@ -55,15 +55,21 @@ static const char argpargsdoc[] = "PORT"; /** the definition of the known program arguments. */ static const struct argp_option argpoptions[] = { {"verbose", 'v', nullptr, 0, "enable verbose output", 0 }, - {"dhcp", 'd', nullptr, 0, "set dynamic IP address via DHCP", 0 }, + {"dhcp", 'd', nullptr, 0, "set dynamic IP address via DHCP (default)", 0 }, {"ip", 'i', "IP", 0, "set fix IP address (e.g. 192.168.0.10)", 0 }, {"mask", 'm', "MASK", 0, "set fix IP mask (e.g. 24)", 0 }, {"gateway", 'g', "GW", 0, "set fix IP gateway to GW (if necessary and other than net address + 1)", 0 }, {"macip", 'M', nullptr, 0, "set the MAC address suffix from the IP address", 0 }, + {"macid", 'I', nullptr, 0, "set the MAC address suffix from internal ID (default)", 0 }, {"arbdel", 'a', "US", 0, "set arbitration delay to US microseconds (0-620 in steps of 10, default 200" ", since firmware 20211128)", 0 }, - {"pingon", 'p', nullptr, 0, "enable visual ping", 0 }, + {"pingon", 'p', nullptr, 0, "enable visual ping (default)", 0 }, {"pingoff", 'o', nullptr, 0, "disable visual ping", 0 }, + {"softvar", -3, "VARIANT", 0, "set the soft jumpers VARIANT to U=USB/RPI (default), W=WIFI, E=Ethernet," + " N=non-enhanced USB/RPI/WIFI, F=non-enhanced Ethernet" + " (prefer hard jumpers in lowercase, ignore hard jumpers in uppercase" + ", since firmware 20221206)", 0 }, + {"hardvar", -4, nullptr, 0, "set the variant from hard jumpers only (ignore soft jumpers)", 0 }, {"flash", 'f', "FILE", 0, "flash the FILE to the device", 0 }, {"reset", 'r', nullptr, 0, "reset the device at the end on success", 0 }, {"slow", 's', nullptr, 0, "use low speed for transfer", 0 }, @@ -75,6 +81,7 @@ static bool setDhcp = false; static bool setIp = false; static uint8_t setIpAddress[] = {0, 0, 0, 0}; static bool setMacFromIp = false; +static bool setMacFromIpValue = true; static bool setMask = false; static uint8_t setMaskLen = 0x1f; static bool setGateway = false; @@ -83,6 +90,10 @@ static bool setArbitrationDelay = false; static uint16_t setArbitrationDelayMicros = 0; static bool setVisualPing = false; static bool setVisualPingOn = false; +static bool setSoftVariant = false; +static uint8_t setSoftVariantValue = 0; +static bool setSoftVariantForced = false; +static bool setHardVariant = false; static char* flashFile = nullptr; static bool reset = false; static bool lowSpeed = false; @@ -127,7 +138,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { verbose = true; break; case 'd': // --dhcp - if (setIp || setMask) { + if (setIp || setMask || setGateway) { argp_error(state, "either DHCP or IP address is needed"); return EINVAL; } @@ -176,7 +187,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { argp_error(state, "mask was specified twice"); return EINVAL; } - if (!parseByte(arg, 0, 0x1e, &setMaskLen)) { + if (!parseByte(arg, 1, 0x1e, &setMaskLen)) { argp_error(state, "invalid IP mask"); return EINVAL; } @@ -259,6 +270,11 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { return EINVAL; case 'M': // --macip setMacFromIp = true; + setMacFromIpValue = true; + break; + case 'I': // --macid + setMacFromIp = true; + setMacFromIpValue = false; break; case 'a': // --arbdel=1000 if (arg == nullptr || arg[0] == 0) { @@ -279,6 +295,41 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { setVisualPing = true; setVisualPingOn = false; break; + case -3: // --softvar=U|W|E|F|N|u|w|e|f|n + if (setHardVariant) { + argp_error(state, "can't set hard and soft jumpers"); + return EINVAL; + } + if (arg == nullptr || arg[0] == 0) { + argp_error(state, "invalid variant"); + return EINVAL; + } + if (arg[0] == 'u' || arg[0] == 'U') { + setSoftVariantValue = 3; + } else if (arg[0] == 'w' || arg[0] == 'W') { + setSoftVariantValue = 2; + } else if (arg[0] == 'e' || arg[0] == 'E') { + setSoftVariantValue = 1; + } else if (arg[0] == 'f' || arg[0] == 'F') { + setSoftVariantValue = 4; + } else if (arg[0] == 'n' || arg[0] == 'N') { + setSoftVariantValue = 0; + } else { + argp_error(state, "invalid variant"); + return EINVAL; + } + setSoftVariantForced = arg[0]<'a'; + setSoftVariant = true; + break; + case -4: // --hardvar + if (setSoftVariant) { + argp_error(state, "can't set hard and soft jumpers"); + return EINVAL; + } + setSoftVariantValue = 3; + setSoftVariantForced = false; + setHardVariant = true; + break; case 'f': // --flash=firmware.hex if (arg == nullptr || arg[0] == 0 || stat(arg, &st) != 0 || !S_ISREG(st.st_mode)) { argp_error(state, "invalid flash file"); @@ -1045,6 +1096,35 @@ int readSettings(int fd, uint8_t* currentData = nullptr) { } else { std::cout << "off" << std::endl; } + std::cout << "Variant: "; // since firmware 20221206 + if ((configData[5]&0x07)==0x07) { + std::cout << "hard jumpers only (includes USB/RPI enhanced when no jumpers are set)" << std::endl; + } else { + switch (configData[5]&0x03) { + case 3: + std::cout << "USB/RPI"; + break; + case 2: + std::cout << "WIFI"; + break; + case 1: + std::cout << "Ethernet"; + break; + default: + std::cout << "non-enhanced "; + if (maskLen) { + std::cout << "Ethernet"; + } else { + std::cout << "USB/RPI/WIFI"; + } + } + if (configData[5]&0x04) { + std::cout << ", prefer hard jumpers"; + } else { + std::cout << ", ignore hard jumpers"; + } + std::cout << std::endl; + } return 0; } @@ -1055,10 +1135,12 @@ bool writeSettings(int fd, uint8_t* currentData = nullptr) { memcpy(configData, currentData, sizeof(configData)); } if (setMacFromIp) { - configData[1] &= ~0x20; // set useMUI + configData[1] = (configData[1]&~0x20) | (setMacFromIpValue ? 0 : 0x20); // set useMUI } - configData[1] = (configData[1]&~0x1f) | (setMaskLen&0x1f); if (setIp) { + if (setMask) { + configData[1] = (configData[1]&~0x1f) | (setMaskLen&0x1f); + } for (int i = 0; i < 4; i++) { configData[i * 2] = setIpAddress[i]; } @@ -1072,6 +1154,14 @@ bool writeSettings(int fd, uint8_t* currentData = nullptr) { if (setVisualPing) { configData[5] = (configData[5]&0x1f) | (setVisualPingOn?0x20:0); } + if (setSoftVariant) { + configData[5] = (configData[5]&0x38) | (setSoftVariantForced?0:0x04) | (setSoftVariantValue&0x03); + if (setSoftVariantValue==0) { + configData[1] = (configData[1]&~0x1f); // set mask=0 to disable Ethernet + } + } else if (setHardVariant) { + configData[5] = (configData[5]&0x38) | 0x07; + } if (writeConfig(fd, 0x0000, 8, configData) != 0) { std::cerr << "failed" << std::endl; return false; @@ -1221,7 +1311,7 @@ int run(int fd) { success = false; } } - if (setIp || setDhcp || setArbitrationDelay || setVisualPing) { + if (setMacFromIp || setIp || setDhcp || setArbitrationDelay || setVisualPing || setSoftVariant || setHardVariant) { if (writeSettings(fd, useCurrentConfigData ? currentConfigData : nullptr)) { std::cout << "Settings changed to:" << std::endl; readSettings(fd); From 1bd7ef5f5af1deba684f6b7eefda3a089ac9d602 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 11 Dec 2022 06:32:01 +0100 Subject: [PATCH 002/345] log opened connection --- src/tools/ebuspicloader.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tools/ebuspicloader.cpp b/src/tools/ebuspicloader.cpp index 9cb684e6b..f0f9c4af6 100644 --- a/src/tools/ebuspicloader.cpp +++ b/src/tools/ebuspicloader.cpp @@ -808,6 +808,7 @@ int openSerial(std::string port) { close(fd); return -1; } + std::cout << "opened " << port << std::endl; return fd; } @@ -819,6 +820,7 @@ int openNet(std::string host, uint16_t port) { return -1; } fcntl(fd, F_SETFL, O_NONBLOCK); // set non-blocking + std::cout << "opened " << host << ":" << static_cast(port) << std::endl; return fd; } @@ -1241,6 +1243,7 @@ int main(int argc, char* argv[]) { continue; } run(fd); + std::cout << std::endl; } return 0; } From 40884341f73c3d597f0c6dd8e9beec9b78a09c9a Mon Sep 17 00:00:00 2001 From: John Date: Sun, 11 Dec 2022 10:00:08 +0100 Subject: [PATCH 003/345] fix misleading default, fix previous commit --- src/tools/ebuspicloader.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/tools/ebuspicloader.cpp b/src/tools/ebuspicloader.cpp index f0f9c4af6..b789843e1 100644 --- a/src/tools/ebuspicloader.cpp +++ b/src/tools/ebuspicloader.cpp @@ -1047,7 +1047,7 @@ int readSettings(int fd, uint8_t* currentData = nullptr) { } std::cout << std::endl; if (maskLen == 0x1f || (ip[0]|ip[1]|ip[2]|ip[3]) == 0) { - std::cout << "IP address: DHCP" << std::endl; + std::cout << "IP address: DHCP (default)" << std::endl; } else { std::cout << "IP address:"; for (uint8_t pos = 0, maskRemain = maskLen; pos < 4; pos++, maskRemain -= maskRemain >= 8 ? 8 : maskRemain) { @@ -1079,9 +1079,6 @@ int readSettings(int fd, uint8_t* currentData = nullptr) { for (int i=0; i < 4; i++) { std::cout << (i == 0?' ':'.') << std::dec << static_cast(ip[i]); } - if (gw == 0x3f) { - std::cout << " (default)"; - } std::cout << std::endl; } uint16_t arbitrationDelay = configData[3]&0x3f; @@ -1139,7 +1136,9 @@ bool writeSettings(int fd, uint8_t* currentData = nullptr) { if (setMacFromIp) { configData[1] = (configData[1]&~0x20) | (setMacFromIpValue ? 0 : 0x20); // set useMUI } - if (setIp) { + if (setDhcp) { + configData[1] |= 0x1f; + } else if (setIp) { if (setMask) { configData[1] = (configData[1]&~0x1f) | (setMaskLen&0x1f); } From 8590a8048c31df9b758f96247f93333d448af58f Mon Sep 17 00:00:00 2001 From: John Date: Sun, 11 Dec 2022 10:01:40 +0100 Subject: [PATCH 004/345] better wording, add sections to cmdline help, simplify+rename variant argument --- src/tools/ebuspicloader.cpp | 120 ++++++++++++++++-------------------- 1 file changed, 53 insertions(+), 67 deletions(-) diff --git a/src/tools/ebuspicloader.cpp b/src/tools/ebuspicloader.cpp index b789843e1..aa7e35d8a 100644 --- a/src/tools/ebuspicloader.cpp +++ b/src/tools/ebuspicloader.cpp @@ -46,32 +46,35 @@ const char *argp_program_version = "eBUS adapter PIC firmware loader"; /** the documentation of the program. */ static const char argpdoc[] = - "A tool for loading firmware to the eBUS adapter PIC and configure some adjustable settings." + "A tool for loading firmware to the eBUS adapter PIC and configure adjustable settings." "\vPORT is either the serial port to use (e.g./dev/ttyUSB0) that also supports a trailing wildcard '*' for testing" - " multiple ports, or a network port as \"ip:port\" for use with e.g. socat or ebusd-esp."; + " multiple ports, or a network port as \"ip:port\" for use with e.g. socat or ebusd-esp in PIC pass-through mode."; static const char argpargsdoc[] = "PORT"; /** the definition of the known program arguments. */ static const struct argp_option argpoptions[] = { - {"verbose", 'v', nullptr, 0, "enable verbose output", 0 }, + {nullptr, 0, nullptr, 0, "IP options:", 1 }, {"dhcp", 'd', nullptr, 0, "set dynamic IP address via DHCP (default)", 0 }, {"ip", 'i', "IP", 0, "set fix IP address (e.g. 192.168.0.10)", 0 }, {"mask", 'm', "MASK", 0, "set fix IP mask (e.g. 24)", 0 }, {"gateway", 'g', "GW", 0, "set fix IP gateway to GW (if necessary and other than net address + 1)", 0 }, {"macip", 'M', nullptr, 0, "set the MAC address suffix from the IP address", 0 }, - {"macid", 'I', nullptr, 0, "set the MAC address suffix from internal ID (default)", 0 }, + {"macid", 'N', nullptr, 0, "set the MAC address suffix from internal ID (default)", 0 }, + {nullptr, 0, nullptr, 0, "eBUS options:", 2 }, {"arbdel", 'a', "US", 0, "set arbitration delay to US microseconds (0-620 in steps of 10, default 200" ", since firmware 20211128)", 0 }, + {nullptr, 0, nullptr, 0, "PIC options:", 3 }, {"pingon", 'p', nullptr, 0, "enable visual ping (default)", 0 }, {"pingoff", 'o', nullptr, 0, "disable visual ping", 0 }, - {"softvar", -3, "VARIANT", 0, "set the soft jumpers VARIANT to U=USB/RPI (default), W=WIFI, E=Ethernet," - " N=non-enhanced USB/RPI/WIFI, F=non-enhanced Ethernet" - " (prefer hard jumpers in lowercase, ignore hard jumpers in uppercase" - ", since firmware 20221206)", 0 }, - {"hardvar", -4, nullptr, 0, "set the variant from hard jumpers only (ignore soft jumpers)", 0 }, + {"variant", -3, "VARIANT", 0, "set the VARIANT to U=USB/RPI, W=WIFI, E=Ethernet," + " N=non-enhanced USB/RPI/WIFI, F=non-enhanced Ethernet" + " (lowercase to allow hardware jumpers, default \"u\"" + ", since firmware 20221206)", 0 }, {"flash", 'f', "FILE", 0, "flash the FILE to the device", 0 }, {"reset", 'r', nullptr, 0, "reset the device at the end on success", 0 }, + {nullptr, 0, nullptr, 0, "Tool options:", 9 }, + {"verbose", 'v', nullptr, 0, "enable verbose output", 0 }, {"slow", 's', nullptr, 0, "use low speed for transfer", 0 }, {nullptr, 0, nullptr, 0, nullptr, 0 }, }; @@ -90,10 +93,9 @@ static bool setArbitrationDelay = false; static uint16_t setArbitrationDelayMicros = 0; static bool setVisualPing = false; static bool setVisualPingOn = false; -static bool setSoftVariant = false; -static uint8_t setSoftVariantValue = 0; -static bool setSoftVariantForced = false; -static bool setHardVariant = false; +static bool setVariant = false; +static uint8_t setVariantValue = 0; +static bool setVariantForced = false; static char* flashFile = nullptr; static bool reset = false; static bool lowSpeed = false; @@ -272,7 +274,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { setMacFromIp = true; setMacFromIpValue = true; break; - case 'I': // --macid + case 'N': // --macid setMacFromIp = true; setMacFromIpValue = false; break; @@ -295,40 +297,27 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { setVisualPing = true; setVisualPingOn = false; break; - case -3: // --softvar=U|W|E|F|N|u|w|e|f|n - if (setHardVariant) { - argp_error(state, "can't set hard and soft jumpers"); - return EINVAL; - } + case -3: // --variant=U|W|E|F|N|u|w|e|f|n if (arg == nullptr || arg[0] == 0) { argp_error(state, "invalid variant"); return EINVAL; } if (arg[0] == 'u' || arg[0] == 'U') { - setSoftVariantValue = 3; + setVariantValue = 3; } else if (arg[0] == 'w' || arg[0] == 'W') { - setSoftVariantValue = 2; + setVariantValue = 2; } else if (arg[0] == 'e' || arg[0] == 'E') { - setSoftVariantValue = 1; + setVariantValue = 1; } else if (arg[0] == 'f' || arg[0] == 'F') { - setSoftVariantValue = 4; + setVariantValue = 4; } else if (arg[0] == 'n' || arg[0] == 'N') { - setSoftVariantValue = 0; + setVariantValue = 0; } else { argp_error(state, "invalid variant"); return EINVAL; } - setSoftVariantForced = arg[0]<'a'; - setSoftVariant = true; - break; - case -4: // --hardvar - if (setSoftVariant) { - argp_error(state, "can't set hard and soft jumpers"); - return EINVAL; - } - setSoftVariantValue = 3; - setSoftVariantForced = false; - setHardVariant = true; + setVariantForced = arg[0]<'a'; + setVariant = true; break; case 'f': // --flash=firmware.hex if (arg == nullptr || arg[0] == 0 || stat(arg, &st) != 0 || !S_ISREG(st.st_mode)) { @@ -1096,34 +1085,33 @@ int readSettings(int fd, uint8_t* currentData = nullptr) { std::cout << "off" << std::endl; } std::cout << "Variant: "; // since firmware 20221206 - if ((configData[5]&0x07)==0x07) { - std::cout << "hard jumpers only (includes USB/RPI enhanced when no jumpers are set)" << std::endl; - } else { - switch (configData[5]&0x03) { - case 3: - std::cout << "USB/RPI"; - break; - case 2: - std::cout << "WIFI"; - break; - case 1: + switch (configData[5]&0x03) { + case 3: + std::cout << "USB/RPI"; + break; + case 2: + std::cout << "WIFI"; + break; + case 1: + std::cout << "Ethernet"; + break; + default: + std::cout << "non-enhanced "; + if (maskLen) { std::cout << "Ethernet"; - break; - default: - std::cout << "non-enhanced "; - if (maskLen) { - std::cout << "Ethernet"; - } else { - std::cout << "USB/RPI/WIFI"; - } - } - if (configData[5]&0x04) { - std::cout << ", prefer hard jumpers"; - } else { - std::cout << ", ignore hard jumpers"; - } - std::cout << std::endl; + } else { + std::cout << "USB/RPI/WIFI"; + } + } + if (configData[5]&0x04) { + std::cout << ", allow hardware jumpers"; + } else { + std::cout << ", ignore hardware jumpers"; } + if ((configData[5]&0x07)==0x07) { + std::cout << " (default)"; + } + std::cout << std::endl; return 0; } @@ -1155,13 +1143,11 @@ bool writeSettings(int fd, uint8_t* currentData = nullptr) { if (setVisualPing) { configData[5] = (configData[5]&0x1f) | (setVisualPingOn?0x20:0); } - if (setSoftVariant) { - configData[5] = (configData[5]&0x38) | (setSoftVariantForced?0:0x04) | (setSoftVariantValue&0x03); - if (setSoftVariantValue==0) { + if (setVariant) { + configData[5] = (configData[5]&0x38) | (setVariantForced?0:0x04) | (setVariantValue&0x03); + if (setVariantValue==0) { configData[1] = (configData[1]&~0x1f); // set mask=0 to disable Ethernet } - } else if (setHardVariant) { - configData[5] = (configData[5]&0x38) | 0x07; } if (writeConfig(fd, 0x0000, 8, configData) != 0) { std::cerr << "failed" << std::endl; @@ -1313,7 +1299,7 @@ int run(int fd) { success = false; } } - if (setMacFromIp || setIp || setDhcp || setArbitrationDelay || setVisualPing || setSoftVariant || setHardVariant) { + if (setMacFromIp || setIp || setDhcp || setArbitrationDelay || setVisualPing || setVariant) { if (writeSettings(fd, useCurrentConfigData ? currentConfigData : nullptr)) { std::cout << "Settings changed to:" << std::endl; readSettings(fd); From b41379986315dac634f1107ce25d1d68815addf3 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 11 Dec 2022 10:02:44 +0100 Subject: [PATCH 005/345] updated to latest source --- src/tools/README.md | 72 ++++++++++++++++++++++++++++++++------------- 1 file changed, 51 insertions(+), 21 deletions(-) diff --git a/src/tools/README.md b/src/tools/README.md index 23b5f5d6d..7dd05eefc 100644 --- a/src/tools/README.md +++ b/src/tools/README.md @@ -3,7 +3,7 @@ eBUS Adapter 3 PIC Loader This is a tool for loading new firmware to the [eBUS adapter 3 PIC](https://adapter.ebusd.eu/) -and to configure IP settings for the Ethernet variant. +and to configure the variant, IP settings for the Ethernet variant, and other settings. All of this is done via the bootloader on the PIC. Consequently, when the bootloader is not running, this tool can't do anything. @@ -18,23 +18,45 @@ Usage `ebuspicloader --help` reveals the options: ``` Usage: ebuspicloader [OPTION...] PORT -A tool for loading firmware to the eBUS adapter PIC. +A tool for loading firmware to the eBUS adapter PIC and configure adjustable +settings. - -a, --arbdel=US set arbitration delay to US microseconds (0-620 in - steps of 10, default 200, since firmware 20211128) - -d, --dhcp set dynamic IP address via DHCP - -f, --flash=FILE flash the FILE to the device + IP options: + -d, --dhcp set dynamic IP address via DHCP (default) + -g, --gateway=GW set fix IP gateway to GW (if necessary and other + than net address + 1) -i, --ip=IP set fix IP address (e.g. 192.168.0.10) -m, --mask=MASK set fix IP mask (e.g. 24) -M, --macip set the MAC address suffix from the IP address + -N, --macid set the MAC address suffix from internal ID + (default) + + eBUS options: + -a, --arbdel=US set arbitration delay to US microseconds (0-620 in + steps of 10, default 200, since firmware + 20211128) + + PIC options: + -f, --flash=FILE flash the FILE to the device + -o, --pingoff disable visual ping + -p, --pingon enable visual ping (default) -r, --reset reset the device at the end on success + --variant=VARIANT set the VARIANT to U=USB/RPI, W=WIFI, E=Ethernet, + N=non-enhanced USB/RPI/WIFI, F=non-enhanced + Ethernet (lowercase to allow hardware jumpers, + default "u", since firmware 20221206) + + Tool options: -s, --slow use low speed for transfer -v, --verbose enable verbose output + -?, --help give this help list --usage give a short usage message -V, --version print program version -PORT is the serial port to use (e.g./dev/ttyUSB0) also supporting a trailing wildcard '*' for testing multiple ports. +PORT is either the serial port to use (e.g./dev/ttyUSB0) that also supports a +trailing wildcard '*' for testing multiple ports, or a network port as +"ip:port" for use with e.g. socat or ebusd-esp in PIC pass-through mode. ``` Flash firmware @@ -42,16 +64,19 @@ Flash firmware For flashing a new firmware, you would typically do something like this: `ebuspicloader -f firmware.hex /dev/ttyUSB0` -On success, the output looks like this: +On success, the output looks similar to this: ``` Device ID: 30b0 (PIC16F15356) -Device revision: 0.1 -Bootloader version: 1 [0a6c] +Device revision: 0.2 +Bootloader version: 2 [73c8] Firmware version not found MAC address: ae:b0:53:26:15:80 -IP address: DHCP +IP address: DHCP (default) +Arbitration delay: 200 us (default) +Visual ping: on (default) +Variant: USB/RPI, allow hardware jumpers (default) -New firmware version: 1 [c5e7] +New firmware version: 1 [7f16] erasing flash: done. flashing: @@ -65,21 +90,26 @@ flashing succeeded. Configure IP ------------ -For changing the IP address of an Ethernet enabled adapter, you would do -something like this: +Changing the IP address of an Ethernet enabled adapter, would be done like this: `ebuspicloader -i 192.168.1.10 -m 24 /dev/ttyUSB0` -On success, the output looks like this: +On success, the output looks similar to this: ``` Device ID: 30b0 (PIC16F15356) -Device revision: 0.1 -Bootloader version: 1 [0a6c] -Firmware version: 1 [c5e7] +Device revision: 0.2 +Bootloader version: 2 [73c8] +Firmware version: 1 [7f16] MAC address: ae:b0:53:26:15:80 -IP address: DHCP +IP address: DHCP (default) +Arbitration delay: 200 us (default) +Visual ping: on (default) +Variant: Ethernet, ignore hardware jumpers -Writing IP settings: done. -IP settings changed to: +Writing settings: done. +Settings changed to: MAC address: ae:80:53:26:15:80 IP address: 192.168.1.10/24 +Arbitration delay: 200 us (default) +Visual ping: on (default) +Variant: Ethernet, ignore hardware jumpers ``` From 8f44d869eef323f30b5eaa0936dab9e8d958f3c6 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 11 Dec 2022 10:14:01 +0100 Subject: [PATCH 006/345] add link to Windows binary --- src/tools/README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/tools/README.md b/src/tools/README.md index 7dd05eefc..e82cc5394 100644 --- a/src/tools/README.md +++ b/src/tools/README.md @@ -10,8 +10,15 @@ bootloader is not running, this tool can't do anything. Check the [eBUS adapter 3 documentation](https://adapter.ebusd.eu/picfirmware) on how to start the bootloader. -This tool was developed due to very unreadable output of the MPLAB bootloader -host application. +The binary is part of the ebusd release and a Windows binary based on Cygwin is available for download here: +[ebuspicloader-windows.zip](https://adapter.ebusd.eu/firmware/ebuspicloader-windows.zip) +It can be started from within Windows `cmd.exe` after extracting the files to a folder and `cd` into that folder. +If Cygwin is already installed, only the `ebuspicloader.exe` needs to be extracted and can be called directly +from within a Cygwin shell. + +This tool is an alternative to the MPLAB bootloader host application that produces a lot +of unreadable output. + Usage ----- From 9e7565c84990010718cb6bdbf7fe907d365135f4 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 11 Dec 2022 10:53:16 +0100 Subject: [PATCH 007/345] updated --- ChangeLog.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index 00ad538e6..cd3deb8de 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -3,9 +3,10 @@ * fix potentially invalid settings picked up from environment variables * fix potentially unnecessary arbitration start for non-enhanced proto * fix smaller issues in KNX integration +* fix numeric replacement+infinite and float min/max values in MQTT JSON payload format ## Features -* add support for setting visual ping and IP gateway to ebuspicloader +* add support for setting visual ping, IP gateway, MAC from ID, and variant to ebuspicloader # 22.4 (2022-09-18) From a7de3175d8ab97575719702967369e2302d1eac4 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 11 Dec 2022 10:57:48 +0100 Subject: [PATCH 008/345] make syslog.h optional --- CMakeLists.txt | 1 + configure.ac | 1 + src/lib/utils/log.cpp | 28 ++++++++++++++++++++++++++-- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2798e3766..5602e55db 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,6 +61,7 @@ check_include_file(pthread.h HAVE_PTHREAD_H) check_include_file(sys/ioctl.h HAVE_SYS_IOCTL_H) check_include_file(sys/select.h HAVE_SYS_SELECT_H) check_include_file(sys/time.h HAVE_SYS_TIME_H) +check_include_file(syslog.h HAVE_SYSLOG_H) check_include_file(time.h HAVE_TIME_H) check_include_file(termios.h HAVE_TERMIOS_H) diff --git a/configure.ac b/configure.ac index 4775300f8..eec309b72 100755 --- a/configure.ac +++ b/configure.ac @@ -23,6 +23,7 @@ AC_CHECK_HEADERS([arpa/inet.h \ sys/ioctl.h \ sys/select.h \ sys/time.h \ + syslog.h \ time.h \ termios.h]) diff --git a/src/lib/utils/log.cpp b/src/lib/utils/log.cpp index fcc7bd720..d34a1613c 100755 --- a/src/lib/utils/log.cpp +++ b/src/lib/utils/log.cpp @@ -27,7 +27,9 @@ #include #include #include +#ifdef HAVE_SYSLOG_H #include +#endif #include "lib/utils/clock.h" namespace ebusd { @@ -53,6 +55,7 @@ static const char* s_levelNames[] = { nullptr }; +#ifdef HAVE_SYSLOG_H /** the syslog level of each @a LogLevel. */ static const int s_syslogLevels[] = { LOG_INFO, @@ -62,6 +65,7 @@ static const int s_syslogLevels[] = { LOG_DEBUG, 0 }; +#endif /** the current log level by log facility. */ static LogLevel s_facilityLogLevel[] = { ll_notice, ll_notice, ll_notice, ll_notice, ll_notice, }; @@ -69,8 +73,10 @@ static LogLevel s_facilityLogLevel[] = { ll_notice, ll_notice, ll_notice, ll_not /** the current log FILE, or nullptr if closed or syslog is used. */ static FILE* s_logFile = stdout; +#ifdef HAVE_SYSLOG_H /** whether to log to syslog. */ static bool s_useSyslog = false; +#endif LogFacility parseLogFacility(const char* facility) { if (!facility) { @@ -148,8 +154,12 @@ LogLevel getFacilityLogLevel(LogFacility facility) { bool setLogFile(const char* filename) { if (filename[0] == 0) { closeLogFile(); +#ifdef HAVE_SYSLOG_H openlog("ebusd", LOG_NDELAY|LOG_PID, LOG_USER); s_useSyslog = true; +#else + s_logFile = stdout; +#endif return true; } FILE* newFile = fopen(filename, "a"); @@ -168,28 +178,40 @@ void closeLogFile() { } s_logFile = nullptr; } +#ifdef HAVE_SYSLOG_H if (s_useSyslog) { closelog(); s_useSyslog = false; } +#endif } bool needsLog(const LogFacility facility, const LogLevel level) { - if (s_logFile == nullptr && !s_useSyslog) { + if (s_logFile == nullptr +#ifdef HAVE_SYSLOG_H + && !s_useSyslog +#endif + ) { return false; } return s_facilityLogLevel[facility] >= level; } void logWrite(const char* facility, const LogLevel level, const char* message, va_list ap) { - if (s_logFile == nullptr && !s_useSyslog) { + if (s_logFile == nullptr +#ifdef HAVE_SYSLOG_H + && !s_useSyslog +#endif + ) { return; } char* buf; if (vasprintf(&buf, message, ap) >= 0 && buf) { +#ifdef HAVE_SYSLOG_H if (s_useSyslog) { syslog(s_syslogLevels[level], "[%s %s] %s", facility, s_levelNames[level], buf); } else { +#endif struct timespec ts; struct tm td; clockGettime(&ts); @@ -199,7 +221,9 @@ void logWrite(const char* facility, const LogLevel level, const char* message, v td.tm_hour, td.tm_min, td.tm_sec, ts.tv_nsec/1000000, facility, s_levelNames[level], buf); fflush(s_logFile); +#ifdef HAVE_SYSLOG_H } +#endif } if (buf) { free(buf); From c87202febe5c05b5f52a1ead53e09623d3ba6def Mon Sep 17 00:00:00 2001 From: John Date: Sun, 11 Dec 2022 12:09:14 +0100 Subject: [PATCH 009/345] add step variable for number fields --- ChangeLog.md | 1 + contrib/etc/ebusd/mqtt-hassio.cfg | 5 +++-- contrib/etc/ebusd/mqtt-integration.cfg | 2 +- src/ebusd/mqtthandler.cpp | 13 +++++++++++++ 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index cd3deb8de..b6da05eb6 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -7,6 +7,7 @@ ## Features * add support for setting visual ping, IP gateway, MAC from ID, and variant to ebuspicloader +* add step variable for numeric values to message definition in MQTT integration # 22.4 (2022-09-18) diff --git a/contrib/etc/ebusd/mqtt-hassio.cfg b/contrib/etc/ebusd/mqtt-hassio.cfg index d3d4d6162..4b4265091 100644 --- a/contrib/etc/ebusd/mqtt-hassio.cfg +++ b/contrib/etc/ebusd/mqtt-hassio.cfg @@ -56,7 +56,7 @@ # %basetype the base data type ID (e.g. "UCH"). # %comment the field comment (if any). # %unit the field unit (if any). -# %min and %max the minimum/maximum possible value for number fields. +# %min, %max, and %step the minimum/maximum possible value and step value for number fields. # %topic the field (or message) update topic built from the mqtttopic configuration option and/or the %topic variable # defined here. @@ -239,7 +239,8 @@ type_part-by = %type_topic type_part-number = , "command_topic":"%topic/set", "min":%min, - "max":%max%unit_of_measurement%state_class%type_class_number + "max":%max, + "step":%step%unit_of_measurement%state_class%type_class_number # HA integration: %type_part variable for sensor %type_topic type_part-sensor = %unit_of_measurement%state_class%type_class_sensor diff --git a/contrib/etc/ebusd/mqtt-integration.cfg b/contrib/etc/ebusd/mqtt-integration.cfg index 82f8ce492..f7e486e60 100644 --- a/contrib/etc/ebusd/mqtt-integration.cfg +++ b/contrib/etc/ebusd/mqtt-integration.cfg @@ -50,7 +50,7 @@ # %basetype the base data type ID (e.g. "UCH"). # %comment the field comment (if any). # %unit the field unit (if any). -# %min and %max the minimum/maximum possible value for number fields. +# %min, %max, and %step the minimum/maximum possible value and step value for number fields. # %topic the field (or message) update topic built from the mqtttopic configuration option and/or the %topic variable # defined here. diff --git a/src/ebusd/mqtthandler.cpp b/src/ebusd/mqtthandler.cpp index a67086abc..4f1ae37ba 100755 --- a/src/ebusd/mqtthandler.cpp +++ b/src/ebusd/mqtthandler.cpp @@ -1085,7 +1085,20 @@ void MqttHandler::run() { } if (dt->getMinMax(true, g_publishFormat, &ostr) == RESULT_OK) { values.set("max", ostr.str()); + ostr.str(""); + } + if (dt->readFromRawValue(1, g_publishFormat, &ostr) != RESULT_OK) { + // fallback method, when smallest number didn't work + int divisor = dt->getDivisor(); + float step = 1.0f; + if (divisor > 1) { + step /= static_cast(divisor); + } else if (divisor < 0) { + step *= static_cast(-divisor); + } + ostr << static_cast(step); } + values.set("step", ostr.str()); } if (!m_typeSwitches.empty()) { values.reduce(true); From d299962566014146e5a298e2081f89e431ff355f Mon Sep 17 00:00:00 2001 From: John Date: Sun, 11 Dec 2022 12:20:53 +0100 Subject: [PATCH 010/345] avoid duplicate definition sent when writable messages are included --- ChangeLog.md | 1 + src/ebusd/mqtthandler.cpp | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index b6da05eb6..4cc12782e 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -4,6 +4,7 @@ * fix potentially unnecessary arbitration start for non-enhanced proto * fix smaller issues in KNX integration * fix numeric replacement+infinite and float min/max values in MQTT JSON payload format +* fix duplicate definition sent for same message when writable messages are included in MQTT integration ## Features * add support for setting visual ping, IP gateway, MAC from ID, and variant to ebuspicloader diff --git a/src/ebusd/mqtthandler.cpp b/src/ebusd/mqtthandler.cpp index 4f1ae37ba..6a49b9390 100755 --- a/src/ebusd/mqtthandler.cpp +++ b/src/ebusd/mqtthandler.cpp @@ -961,6 +961,7 @@ void MqttHandler::run() { ostringstream ostr; deque messages; m_messages->findAll("", "", m_levels, false, true, true, true, true, true, 0, 0, false, &messages); + bool includeActiveWrite = FileReader::matches("w", filterDirection, true, true); for (const auto& message : messages) { bool checkPollAdjust = false; if (filterSeen > 0) { @@ -1011,7 +1012,13 @@ void MqttHandler::run() { if (filterPriority > 0 && (message->getPollPriority() == 0 || message->getPollPriority() > filterPriority)) { continue; } - + if (includeActiveWrite && !message->isWrite()) { + // check for existance of write message with same name + Message* write = m_messages->find(message->getCircuit(), message->getName(), "", true); + if (write) { + continue; // avoid sending definition of read AND write message with the same key + } + } StringReplacers msgValues = m_replacers; // need a copy here as the contents are manipulated msgValues.set("circuit", message->getCircuit()); msgValues.set("name", message->getName()); From deadd51bffbeb1d40ea1a35836f8a0ad825bfadb Mon Sep 17 00:00:00 2001 From: John Date: Sun, 11 Dec 2022 12:49:13 +0100 Subject: [PATCH 011/345] make min+max+step optional --- contrib/etc/ebusd/mqtt-hassio.cfg | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/contrib/etc/ebusd/mqtt-hassio.cfg b/contrib/etc/ebusd/mqtt-hassio.cfg index 4b4265091..3ad026c6d 100644 --- a/contrib/etc/ebusd/mqtt-hassio.cfg +++ b/contrib/etc/ebusd/mqtt-hassio.cfg @@ -229,6 +229,18 @@ state_class ?= , unit_of_measurement ?= , "unit_of_measurement":"%unit" +# HA integration: optional variable with the minimum numeric value +min_number ?= , + "min":%min + +# HA integration: optional variable with the maximum numeric value +max_number ?= , + "max":%max + +# HA integration: optional variable with the numeric step value +step_number ?= , + "step":%step + # field type part suffix to use instead of the field type itself, see below. # HA integration: %type_part variable mapped from the mapped %type_topic type_part-by = %type_topic @@ -237,10 +249,7 @@ type_part-by = %type_topic # %type_part). # HA integration: %type_part variable for number %type_topic type_part-number = , - "command_topic":"%topic/set", - "min":%min, - "max":%max, - "step":%step%unit_of_measurement%state_class%type_class_number + "command_topic":"%topic/set"%min_number%max_number%step_number%unit_of_measurement%state_class%type_class_number # HA integration: %type_part variable for sensor %type_topic type_part-sensor = %unit_of_measurement%state_class%type_class_sensor From f1d56fdc4451f7b34140700057b3c9d918b768e8 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 11 Dec 2022 12:51:24 +0100 Subject: [PATCH 012/345] support yesno values as well in readable+writable switch (fixes #562) --- contrib/etc/ebusd/mqtt-hassio.cfg | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/contrib/etc/ebusd/mqtt-hassio.cfg b/contrib/etc/ebusd/mqtt-hassio.cfg index 3ad026c6d..13b872788 100644 --- a/contrib/etc/ebusd/mqtt-hassio.cfg +++ b/contrib/etc/ebusd/mqtt-hassio.cfg @@ -157,8 +157,8 @@ type_map-datetime = type_switch-by = %name%field,%unit # field type switch variables names to use in addition to %type_switch in case of multiple keys (separated by comma). -# HA integration: var names for topic/class/state values mapped from field type, name, message, and unit. -type_switch-names = type_topic,type_class,type_state +# HA integration: var names for topic/class/state/sub values mapped from field type, name, message, and unit. +type_switch-names = type_topic,type_class,type_state,type_sub # field type switch for each field type and optionally direction (between dash before the field type) available as # %type_switch (as well as the variable names defined in "type_switch-names" if any). @@ -197,10 +197,12 @@ type_switch-number = # HA integration: the mapping list for (potentially) writable binary switch entities by field type, name, message, and unit. type_switch-w-list = switch,, = onoff + switch,,,yesno = yesno # HA integration: the mapping list for rather binary sensor entities by field type, name, message, and unit. type_switch-list = binary_sensor,,measurement = onoff + binary_sensor,,measurement,yesno = yesno sensor,, = # HA integration: currently unused mapping lists for non-numeric/non-binary entities. @@ -242,8 +244,8 @@ step_number ?= , "step":%step # field type part suffix to use instead of the field type itself, see below. -# HA integration: %type_part variable mapped from the mapped %type_topic -type_part-by = %type_topic +# HA integration: %type_part variable mapped from the mapped %type_topic and optional %type_sub +type_part-by = %type_topic%type_sub # field type part mappings for each field type (or the "type_part-by" variable value) in the suffix (available as # %type_part). @@ -260,11 +262,20 @@ type_part-switch = , "payload_on":"on", "payload_off":"off"%state_class +type_part-switchyesno = , + "command_topic":"%topic/set", + "payload_on":"yes", + "payload_off":"no"%state_class + # HA integration: %type_part variable for binary_sensor %type_topic type_part-binary_sensor = , "payload_on":"on", "payload_off":"off"%state_class +type_part-binary_sensoryesno = , + "payload_on":"yes", + "payload_off":"no"%state_class + # the field specific part (evaluated after the message specific part). # HA integration: set to the mapped %type_part from above field_payload = %type_part From 1596c73fa3c9bcfecc1b89150ddbf2aa44941276 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 11 Dec 2022 12:54:49 +0100 Subject: [PATCH 013/345] add curve to writable numbers --- ChangeLog.md | 1 + contrib/etc/ebusd/mqtt-hassio.cfg | 1 + 2 files changed, 2 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index 4cc12782e..25add861d 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -9,6 +9,7 @@ ## Features * add support for setting visual ping, IP gateway, MAC from ID, and variant to ebuspicloader * add step variable for numeric values to message definition in MQTT integration +* add yes/no values and heating curve (when writable) to Home Assistant MQTT discovery integration # 22.4 (2022-09-18) diff --git a/contrib/etc/ebusd/mqtt-hassio.cfg b/contrib/etc/ebusd/mqtt-hassio.cfg index 13b872788..5162aa1a2 100644 --- a/contrib/etc/ebusd/mqtt-hassio.cfg +++ b/contrib/etc/ebusd/mqtt-hassio.cfg @@ -177,6 +177,7 @@ type_switch-w-number = number,pressure, = bar$ number,gas, = gas*/min$ number,humidity, = humid*%%$ + number,, = curve, # HA integration: the mapping list for numeric sensor entities by field type, name, message, and unit. type_switch-number = From 91484ad4ee81a490dfaae3366857f7f61d784339 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 11 Dec 2022 18:06:48 +0100 Subject: [PATCH 014/345] switch to HA update integration for updatecheck --- contrib/etc/ebusd/mqtt-hassio.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/etc/ebusd/mqtt-hassio.cfg b/contrib/etc/ebusd/mqtt-hassio.cfg index 5162aa1a2..b067a1246 100644 --- a/contrib/etc/ebusd/mqtt-hassio.cfg +++ b/contrib/etc/ebusd/mqtt-hassio.cfg @@ -349,9 +349,9 @@ def_global_signal-topic = %haprefix/binary_sensor/%TOPIC/config def_global_signal-payload = %global_prefix, "device_class":"connectivity"%global_boolean_suffix +def_global_updatecheck-topic = %haprefix/update/%TOPIC/config def_global_updatecheck-payload = %global_prefix, - "state_class":"measurement", - "value_template":"{{value_json|truncate(255)}}" + "value_template":"{%% set my_vernum = value_json|truncate(255)|regex_replace(find=',.*| available|revision v|version v',replace='') %%}{%% set my_ver = {'installed_version':'%version','latest_version':my_vernum,'entity_picture':'https://ebusd.eu/logo-32x32.png','release_url':'https://github.com/john30/ebusd/releases/latest'} %%}{%% if my_vernum == '' %%}{%% set my_ver.latest_version = '%version' %%}{%% endif %%}{{ my_ver | tojson }}" } # the topic and payload to listen to in order to republish all config messages. From 8eca44bdee5a9599bf51b4162167966bffcf6ada Mon Sep 17 00:00:00 2001 From: John Date: Sat, 17 Dec 2022 16:45:59 +0100 Subject: [PATCH 015/345] fix for tcp opts applied to udp socket (fixes #733) --- src/lib/utils/tcpsocket.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/utils/tcpsocket.cpp b/src/lib/utils/tcpsocket.cpp index 84a1e8ed2..646e049d3 100755 --- a/src/lib/utils/tcpsocket.cpp +++ b/src/lib/utils/tcpsocket.cpp @@ -79,6 +79,7 @@ int tcpKeepAliveInterval) { close(sfd); return -1; } + return sfd; } int value = 1; ret = setsockopt(sfd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&value), sizeof(value)); From 4bbcc7918f4be61d2bad48fc7ef785cb87a7dd44 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 17 Dec 2022 16:46:23 +0100 Subject: [PATCH 016/345] enable retry mode for older ssl libs --- src/lib/utils/httpclient.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/utils/httpclient.cpp b/src/lib/utils/httpclient.cpp index 67b54129f..10b918ce0 100755 --- a/src/lib/utils/httpclient.cpp +++ b/src/lib/utils/httpclient.cpp @@ -202,6 +202,7 @@ SSLSocket* SSLSocket::connect(const string& host, const uint16_t& port, bool htt if (isError("get_ssl", ssl)) { break; } + SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); const char *hostname = host.c_str(); if (isError("tls_host", SSL_set_tlsext_host_name(ssl, hostname), 1)) { break; From cd6be6002d2f8820d0716e07330f904d455f39ca Mon Sep 17 00:00:00 2001 From: John Date: Sat, 17 Dec 2022 16:48:07 +0100 Subject: [PATCH 017/345] tolerate side data to be retrieved until timeout --- src/lib/ebus/device.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/lib/ebus/device.cpp b/src/lib/ebus/device.cpp index 6896422d5..c43c87842 100755 --- a/src/lib/ebus/device.cpp +++ b/src/lib/ebus/device.cpp @@ -41,6 +41,7 @@ #include #include #include "lib/ebus/data.h" +#include "lib/utils/clock.h" #include "lib/utils/tcpsocket.h" namespace ebusd { @@ -297,6 +298,7 @@ result_t Device::recv(unsigned int timeout, symbol_t* value, ArbitrationState* a } bool repeated = false; timeout += m_latency; + uint64_t until = clockGetMillis() + timeout; do { bool isAvailable = available(); if (!isAvailable && timeout > 0) { @@ -359,7 +361,11 @@ result_t Device::recv(unsigned int timeout, symbol_t* value, ArbitrationState* a timeout = m_latency+ENHANCED_COMPLETE_WAIT_DURATION; continue; } - return RESULT_ERR_TIMEOUT; + uint64_t now = clockGetMillis(); + if (now >= until) { + return RESULT_ERR_TIMEOUT; + } + timeout = static_cast(until - now); } while (true); if (m_enhancedProto || *value != SYN || m_arbitrationMaster == SYN || m_arbitrationCheck) { if (m_listener != nullptr) { @@ -449,7 +455,7 @@ bool Device::available() { if (!m_enhancedProto) { return true; } - // peek into the received enhanced proto bytes to determine symbol availability + // peek into the received enhanced proto bytes to determine received bus symbol availability for (size_t pos = 0; pos < m_bufLen; pos++) { symbol_t ch = m_buffer[(pos+m_bufPos)%m_bufSize]; if (!(ch&ENH_BYTE_FLAG)) { @@ -463,6 +469,7 @@ bool Device::available() { if (pos+1 >= m_bufLen) { return false; } + symbol_t cmd = (ch >> 2)&0xf; // peek into next byte to check if enhanced sequence is ok ch = m_buffer[(pos+m_bufPos+1)%m_bufSize]; if (!(ch&ENH_BYTE_FLAG) || (ch&ENH_BYTE_MASK) != ENH_BYTE2) { @@ -480,6 +487,10 @@ bool Device::available() { pos--; continue; } + if (cmd != ENH_RES_RECEIVED && cmd != ENH_RES_STARTED && cmd != ENH_RES_FAILED) { + pos++; + continue; + } #ifdef DEBUG_RAW_TRAFFIC fprintf(stdout, "raw avail enhanced @%d+%d %2.2x %2.2x\n", m_bufPos, pos, m_buffer[(pos+m_bufPos)%m_bufSize], ch); fflush(stdout); From f3d030c137b9c53a829e7e1ef37e72354267a86b Mon Sep 17 00:00:00 2001 From: John Date: Sat, 17 Dec 2022 17:16:55 +0100 Subject: [PATCH 018/345] reveal enhanced device version in info cmd and add to update check, unify wait --- src/ebusd/mainloop.cpp | 15 ++++++++++++++ src/lib/ebus/device.cpp | 46 +++++++++++++++++++++++++++-------------- src/lib/ebus/device.h | 19 ++++++++++++++++- 3 files changed, 64 insertions(+), 16 deletions(-) diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index 970f21b9b..2f136edb1 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -365,6 +365,12 @@ void MainLoop::run() { << ",\"a\":\"other\"" #endif << ",\"u\":" << (now-start); + if (m_device->isEnhancedProto()) { + string ver = m_device->getEnhancedVersion(); + if (!ver.empty()) { + ostr << ",\"dv\":\"" << ver << "\""; + } + } if (m_reconnectCount) { ostr << ",\"rc\":" << m_reconnectCount; } @@ -1937,12 +1943,21 @@ result_t MainLoop::executeInfo(const vector& args, const string& user, o if (!m_device->isValid()) { *ostream << ", invalid"; } + bool infoAdded = false; if (verbose) { string info = m_device->getEnhancedInfos(); if (!info.empty()) { *ostream << ", " << info; + infoAdded = true; + } + } + if (!infoAdded) { + string info = m_device->getEnhancedVersion(); + if (!info.empty()) { + *ostream << ", firmware " << info; } } + *ostream << "\n"; if (!user.empty()) { *ostream << "user: " << user << "\n"; diff --git a/src/lib/ebus/device.cpp b/src/lib/ebus/device.cpp index c43c87842..5fe40ed28 100755 --- a/src/lib/ebus/device.cpp +++ b/src/lib/ebus/device.cpp @@ -184,7 +184,7 @@ bool Device::isValid() { } result_t Device::requestEnhancedInfo(symbol_t infoId) { - if (!m_enhancedProto || m_extraFatures == 0 || infoId == 0xff) { + if (!m_enhancedProto || m_extraFatures == 0) { return RESULT_ERR_INVALID_ARG; } for (unsigned int i = 0; i < 4; i++) { @@ -196,16 +196,24 @@ result_t Device::requestEnhancedInfo(symbol_t infoId) { if (m_infoId != 0xff) { return RESULT_ERR_DUPLICATE; } + if (infoId == 0xff) { + // just waited for completion + return RESULT_OK; + } + return sendEnhancedInfoRequest(infoId); +} + +result_t Device::sendEnhancedInfoRequest(symbol_t infoId) { symbol_t buf[2] = makeEnhancedSequence(ENH_REQ_INFO, infoId); #ifdef DEBUG_RAW_TRAFFIC fprintf(stdout, "raw enhanced > %2.2x %2.2x\n", buf[0], buf[1]); fflush(stdout); #endif - m_infoPos = 0; - m_infoId = infoId; if (::write(m_fd, buf, 2) != 2) { return RESULT_ERR_DEVICE; } + m_infoPos = 0; + m_infoId = infoId; return RESULT_OK; } @@ -214,7 +222,7 @@ string Device::getEnhancedInfos() { return ""; } result_t res; - if (m_enhInfoTemperature.empty()) { + if (m_enhInfoTemperature.empty()) { // use empty temperature for potential refresh after reset res = requestEnhancedInfo(0); if (res != RESULT_OK) { return "cannot request version"; @@ -244,11 +252,11 @@ string Device::getEnhancedInfos() { if (res != RESULT_OK) { return "cannot request bus voltage"; } - usleep(8*40000); - if (m_infoPos == 0) { - return "did not get info"; + res = requestEnhancedInfo(0xff); + if (res != RESULT_OK) { + m_enhInfoBusVoltage = "bus voltage unknown"; } - return m_enhInfoTemperature + ", " + m_enhInfoSupplyVoltage + ", " + m_enhInfoBusVoltage; + return "firmware " + m_enhInfoVersion + ", " + m_enhInfoTemperature + ", " + m_enhInfoSupplyVoltage + ", " + m_enhInfoBusVoltage; } result_t Device::send(symbol_t value) { @@ -644,13 +652,16 @@ bool Device::read(symbol_t* value, bool isAvailable, ArbitrationState* arbitrati m_enhInfoSupplyVoltage = ""; m_enhInfoBusVoltage = ""; m_infoId = 0xff; + m_extraFatures = data; if (m_resetRequested) { m_resetRequested = false; + if (m_extraFatures&0x01) { + sendEnhancedInfoRequest(0); // request version, ignore result + } } else { close(); // on self-reset of device close and reopen it to have a clean startup cancelRunningArbitration(arbitrationState); } - m_extraFatures = data; if (m_listener != nullptr) { m_listener->notifyStatus(false, (m_extraFatures&0x01) ? "reset, supports info" : "reset"); } @@ -670,19 +681,24 @@ bool Device::read(symbol_t* value, bool isAvailable, ArbitrationState* arbitrati case 0x0200: case 0x0500: // with firmware version and jumper info case 0x0800: // with firmware version, jumper info, and bootloader version - stream << "firmware " << static_cast(m_infoBuf[0]) << "." // version minor + stream << static_cast(m_infoBuf[0]) << "." // version minor << std::hex << static_cast(m_infoBuf[1]); // features mask if (m_infoLen >= 5) { - stream << " [" << std::setfill('0') << std::setw(2) << std::hex << static_cast(m_infoBuf[2]) + stream << "[" << std::setfill('0') << std::setw(2) << std::hex << static_cast(m_infoBuf[2]) << std::setw(2) << static_cast(m_infoBuf[3]) << "]"; - stream << ", jumpers 0x" << std::setw(2) << static_cast(m_infoBuf[4]); - stream << std::setfill(' '); // reset } if (m_infoLen >= 8) { - stream << ", bootloader " << std::dec << static_cast(m_infoBuf[5]); - stream << " [" << std::setfill('0') << std::setw(2) << std::hex << static_cast(m_infoBuf[6]) + stream << "." << std::dec << static_cast(m_infoBuf[5]); + stream << "[" << std::setfill('0') << std::setw(2) << std::hex << static_cast(m_infoBuf[6]) << std::setw(2) << static_cast(m_infoBuf[7]) << "]"; } + m_enhInfoVersion = stream.str(); + stream.str(" "); + stream << "firmware " << m_enhInfoVersion; + if (m_infoLen >= 5) { + stream << ", jumpers 0x" << std::setw(2) << static_cast(m_infoBuf[4]); + } + stream << std::setfill(' '); // reset break; case 0x0901: case 0x0802: diff --git a/src/lib/ebus/device.h b/src/lib/ebus/device.h index 33ca04e3c..46c8d3a64 100755 --- a/src/lib/ebus/device.h +++ b/src/lib/ebus/device.h @@ -212,12 +212,26 @@ class Device { void setListener(DeviceListener* listener) { m_listener = listener; } /** - * Send a request for extra infos to enhanced device. + * Check for a running extra infos request, wait for it to complete, + * and then send a new request for extra infos to enhanced device. * @param infoId the ID of the info to request. * @return @a RESULT_OK on success, or an error code otherwise. */ result_t requestEnhancedInfo(symbol_t infoId); + /** + * Send a request for extra infos to enhanced device. + * @param infoId the ID of the info to request. + * @return @a RESULT_OK on success, or an error code otherwise. + */ + result_t sendEnhancedInfoRequest(symbol_t infoId); + + /** + * Get the enhanced device version. + * @return @a a string with the version infos, or empty. + */ + string getEnhancedVersion() const { return m_enhInfoVersion; } + /** * Retrieve/update all extra infos from an enhanced device. * @return @a a string with the extra infos, or empty. @@ -324,6 +338,9 @@ class Device { /** the info buffer. */ symbol_t m_infoBuf[16]; + /** a string describing the enhanced device version. */ + string m_enhInfoVersion; + /** a string describing the enhanced device temperature. */ string m_enhInfoTemperature; From 6a16cbf4fdb665af81d7c55ad137e41e05ce7ea2 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 17 Dec 2022 17:20:35 +0100 Subject: [PATCH 019/345] ignore invalid info response --- src/lib/ebus/device.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/lib/ebus/device.cpp b/src/lib/ebus/device.cpp index 5fe40ed28..f2c3a4d77 100755 --- a/src/lib/ebus/device.cpp +++ b/src/lib/ebus/device.cpp @@ -668,11 +668,9 @@ bool Device::read(symbol_t* value, bool isAvailable, ArbitrationState* arbitrati break; case ENH_RES_INFO: if (m_infoLen == 0) { - if (data <= 16) { // max length - m_infoLen = data; - m_infoPos = 0; - } - } else if (m_infoPos < m_infoLen) { + m_infoLen = data; + m_infoPos = 0; + } else if (m_infoPos < m_infoLen && m_infoPos < sizeof(m_infoBuf)) { m_infoBuf[m_infoPos++] = data; if (m_infoPos >= m_infoLen) { unsigned int val; @@ -758,6 +756,9 @@ bool Device::read(symbol_t* value, bool isAvailable, ArbitrationState* arbitrati m_infoLen = 0; m_infoId = 0xff; } + } else { + m_infoLen = 0; // reset on invalid response + m_infoId = 0xff; } break; case ENH_RES_ERROR_EBUS: From c488156df1d9830ddcfb65ef2c28c0dde9c34f35 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 17 Dec 2022 17:20:51 +0100 Subject: [PATCH 020/345] correct temp length --- docs/enhanced_proto.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/enhanced_proto.md b/docs/enhanced_proto.md index 3ad225fa5..3593962b3 100644 --- a/docs/enhanced_proto.md +++ b/docs/enhanced_proto.md @@ -126,8 +126,8 @@ The first byte transferred in response is always the number of data bytes to be * `length`: =8 * 8*`config_H` `config_L`: PIC config * 0x03: PIC temperature - * `length`: =1 - * `temp`: temperature in degrees Celsius + * `length`: =2 + * `temp_H` `temp_L`: temperature in degrees Celsius * 0x04: PIC supply voltage * `length`: =2 * `millivolt_H` `millivolt_L`: voltage value in mV From 4c2c198842195a2e81f099c39aec2b0a8798896d Mon Sep 17 00:00:00 2001 From: John Date: Sat, 17 Dec 2022 17:43:39 +0100 Subject: [PATCH 021/345] add device version to update check --- contrib/updatecheck/calcversions.sh | 3 +++ contrib/updatecheck/index.php | 2 +- contrib/updatecheck/prepend.inc | 9 ++++++++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/contrib/updatecheck/calcversions.sh b/contrib/updatecheck/calcversions.sh index 9893e59b0..728e8f3c9 100755 --- a/contrib/updatecheck/calcversions.sh +++ b/contrib/updatecheck/calcversions.sh @@ -3,6 +3,9 @@ version=`head -n 1 ../../VERSION` revision=`git describe --always` echo "ebusd=${version},${revision}" > versions.txt echo "ebusd=${version},${revision}" > oldversions.txt +devver=`curl -s https://adapter.ebusd.eu/firmware/ChangeLog|grep "Version "|head -n 1|sed -e 's#.*]*>##' -e 's#<.*##' -e 's# ##'` +devbl=`curl -s https://adapter.ebusd.eu/firmware/ChangeLog|grep "Bootloader "|head -n 1|sed -e 's#.*]*>##' -e 's#<.*##' -e 's# ##'` +echo "device=${devver},${devbl}" >> versions.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 diff --git a/contrib/updatecheck/index.php b/contrib/updatecheck/index.php index 9a0d92c3f..1fc33e426 100644 --- a/contrib/updatecheck/index.php +++ b/contrib/updatecheck/index.php @@ -5,7 +5,7 @@ $r = @file_get_contents('php://input'); header('Content-Type: text/plain'); $r = @json_decode($r, true); - echo checkUpdate(@$r['v'], @$r['r'], @$r['a'], @$r['l']); + echo checkUpdate(@$r['v'], @$r['r'], @$r['a'], @$r['dv'], @$r['l']); exit; } readVersions(); diff --git a/contrib/updatecheck/prepend.inc b/contrib/updatecheck/prepend.inc index 76c4492f8..fe2488734 100644 --- a/contrib/updatecheck/prepend.inc +++ b/contrib/updatecheck/prepend.inc @@ -16,7 +16,7 @@ function readVersions($old=false) { array_walk($v, $func); } } -function checkUpdate($ebusdVersion, $ebusdRelease, $architecture, $loadedFiles) { +function checkUpdate($ebusdVersion, $ebusdRelease, $architecture, $deviceVersion, $loadedFiles) { if (!$ebusdVersion) { return 'invalid request'; } @@ -30,6 +30,13 @@ function checkUpdate($ebusdVersion, $ebusdRelease, $architecture, $loadedFiles) } else { $ret = 'version '.$versions['ebusd'][0].' available'; } + if ($deviceVersion && @$versions['device']) { + $feat = strtok($deviceVersion, '.'); + $ver = strtok('.'); + if ($ver && $ver!==$versions['device'][0]) { + $ret .= ', device firmware '.$versions['device'][0].' available'; + } + } $newerAvailable = 0; $configs = ''; foreach ($loadedFiles as $k => $val) { From 68540b1d6cfb3a8762fb61a86aa27fb59b8ec713 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 17 Dec 2022 18:57:13 +0100 Subject: [PATCH 022/345] handle enhanced side data without bus signal as well --- src/lib/ebus/device.cpp | 9 +++++++++ src/lib/ebus/device.h | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/src/lib/ebus/device.cpp b/src/lib/ebus/device.cpp index f2c3a4d77..3fc82402d 100755 --- a/src/lib/ebus/device.cpp +++ b/src/lib/ebus/device.cpp @@ -567,6 +567,11 @@ bool Device::read(symbol_t* value, bool isAvailable, ArbitrationState* arbitrati #endif m_bufLen += size; } + if (m_enhancedProto) { + if (handleEnhancedBufferedData(value, arbitrationState)) { + return true; + } + } if (!available()) { if (incomplete) { *incomplete = m_enhancedProto && m_bufLen > 0; @@ -579,6 +584,10 @@ bool Device::read(symbol_t* value, bool isAvailable, ArbitrationState* arbitrati m_bufLen--; return true; } + return handleEnhancedBufferedData(value, arbitrationState); +} + +bool Device::handleEnhancedBufferedData(symbol_t* value, ArbitrationState* arbitrationState) { while (m_bufLen > 0) { symbol_t ch = m_buffer[m_bufPos]; if (!(ch&ENH_BYTE_FLAG)) { diff --git a/src/lib/ebus/device.h b/src/lib/ebus/device.h index 46c8d3a64..ff97472db 100755 --- a/src/lib/ebus/device.h +++ b/src/lib/ebus/device.h @@ -301,6 +301,14 @@ class Device { bool m_resetRequested; private: + /** + * Handle the already buffered enhanced data. + * @param value the reference in which the read byte value is stored. + * @param arbitrationState the variable in which to store the current/received arbitration state (mandatory for enhanced proto). + * @return true if the value was set, false otherwise. + */ + bool handleEnhancedBufferedData(symbol_t* value, ArbitrationState* arbitrationState); + /** the @a DeviceListener, or nullptr. */ DeviceListener* m_listener; From f2fb95913554911ff60e7c5b43ff8b2146a29a34 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 17 Dec 2022 19:06:11 +0100 Subject: [PATCH 023/345] separate device version --- contrib/updatecheck/index.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contrib/updatecheck/index.php b/contrib/updatecheck/index.php index 1fc33e426..ba9519f21 100644 --- a/contrib/updatecheck/index.php +++ b/contrib/updatecheck/index.php @@ -23,10 +23,11 @@ } ?>

latest ebusd version:

+

latest device firmware:

config files: Date: Sat, 17 Dec 2022 19:33:53 +0100 Subject: [PATCH 024/345] add update check for device --- contrib/etc/ebusd/mqtt-hassio.cfg | 17 +++++++++++++++++ src/ebusd/mqtthandler.cpp | 2 ++ 2 files changed, 19 insertions(+) diff --git a/contrib/etc/ebusd/mqtt-hassio.cfg b/contrib/etc/ebusd/mqtt-hassio.cfg index b067a1246..f47c968a8 100644 --- a/contrib/etc/ebusd/mqtt-hassio.cfg +++ b/contrib/etc/ebusd/mqtt-hassio.cfg @@ -354,6 +354,23 @@ def_global_updatecheck-payload = %global_prefix, "value_template":"{%% set my_vernum = value_json|truncate(255)|regex_replace(find=',.*| available|revision v|version v',replace='') %%}{%% set my_ver = {'installed_version':'%version','latest_version':my_vernum,'entity_picture':'https://ebusd.eu/logo-32x32.png','release_url':'https://github.com/john30/ebusd/releases/latest'} %%}{%% if my_vernum == '' %%}{%% set my_ver.latest_version = '%version' %%}{%% endif %%}{{ my_ver | tojson }}" } + +# optional secondary update check for the eBUS device (consuming the same topic though!) +def_global_updatecheck_device-topic = %haprefix/update/%{TOPIC}_device/config +def_global_updatecheck_device-payload = { + "unique_id":"%{TOPIC}_device", + "device":{ + "identifiers":"%{PREFIXN}_device", + "manufacturer":"ebusd.eu", + "name":"%prefixn device", + "via_device":"%PREFIXN", + "suggested_area":"%area" + }, + "state_topic":"%topic", + "name":"%prefixn %name", + "value_template":"{%% set my_vernum = value_json|truncate(255)|regex_replace(find='^[^,]*|, device firmware |,.*| available',replace='') %%}{%% set my_ver = {'installed_version':'old','latest_version':my_vernum,'entity_picture':'https://adapter.ebusd.eu/favicon.ico','release_url':'https://adapter.ebusd.eu/firmware/ChangeLog'} %%}{%% if my_vernum == '' %%}{%% set my_ver.installed_version = 'current' %%}{%% set my_ver.latest_version = '%version' %%}{%% endif %%}{{ my_ver | tojson }}" + } + # the topic and payload to listen to in order to republish all config messages. # HA integration: force republish of all configs when HA goes down and comes up again. config_restart-topic = %haprefix/status diff --git a/src/ebusd/mqtthandler.cpp b/src/ebusd/mqtthandler.cpp index 6a49b9390..96727fd8e 100755 --- a/src/ebusd/mqtthandler.cpp +++ b/src/ebusd/mqtthandler.cpp @@ -952,6 +952,8 @@ void MqttHandler::run() { publishDefinition(m_replacers, "def_global_uptime-", uptimeTopic, "global", "uptime", "def_global-"); publishDefinition(m_replacers, "def_global_updatecheck-", m_globalTopic.get("", "updatecheck"), "global", "updatecheck", "def_global-"); + publishDefinition(m_replacers, "def_global_updatecheck_device-", m_globalTopic.get("", "updatecheck"), "global", + "updatecheck_device", ""); publishDefinition(m_replacers, "def_global_scan-", m_globalTopic.get("", "scan"), "global", "scan", "def_global-"); } From 071de91d14b11c330c957a50b1713d53c2c7b4bc Mon Sep 17 00:00:00 2001 From: John Date: Sat, 17 Dec 2022 20:32:17 +0100 Subject: [PATCH 025/345] fix template scoping --- contrib/etc/ebusd/mqtt-hassio.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/etc/ebusd/mqtt-hassio.cfg b/contrib/etc/ebusd/mqtt-hassio.cfg index f47c968a8..65c2fc145 100644 --- a/contrib/etc/ebusd/mqtt-hassio.cfg +++ b/contrib/etc/ebusd/mqtt-hassio.cfg @@ -351,7 +351,7 @@ def_global_signal-payload = %global_prefix, def_global_updatecheck-topic = %haprefix/update/%TOPIC/config def_global_updatecheck-payload = %global_prefix, - "value_template":"{%% set my_vernum = value_json|truncate(255)|regex_replace(find=',.*| available|revision v|version v',replace='') %%}{%% set my_ver = {'installed_version':'%version','latest_version':my_vernum,'entity_picture':'https://ebusd.eu/logo-32x32.png','release_url':'https://github.com/john30/ebusd/releases/latest'} %%}{%% if my_vernum == '' %%}{%% set my_ver.latest_version = '%version' %%}{%% endif %%}{{ my_ver | tojson }}" + "value_template":"{%% set my_new = value_json|truncate(255)|regex_replace(find=',.*| available|revision v|version v|OK',replace='') %%}{%% if my_new == '' %%}{%% set my_new = '%version' %%}{%% endif %%}{{ {'installed_version':'%version','latest_version':my_new,'entity_picture':'https://ebusd.eu/logo-32x32.png','release_url':'https://github.com/john30/ebusd/releases/latest'} | tojson }}" } @@ -368,7 +368,7 @@ def_global_updatecheck_device-payload = { }, "state_topic":"%topic", "name":"%prefixn %name", - "value_template":"{%% set my_vernum = value_json|truncate(255)|regex_replace(find='^[^,]*|, device firmware |,.*| available',replace='') %%}{%% set my_ver = {'installed_version':'old','latest_version':my_vernum,'entity_picture':'https://adapter.ebusd.eu/favicon.ico','release_url':'https://adapter.ebusd.eu/firmware/ChangeLog'} %%}{%% if my_vernum == '' %%}{%% set my_ver.installed_version = 'current' %%}{%% set my_ver.latest_version = '%version' %%}{%% endif %%}{{ my_ver | tojson }}" + "value_template":"{%% set my_new = value_json|truncate(255)|regex_replace(find='^[^,]*|, device firmware |,.*| available',replace='') %%}{%% set my_cur = 'old' %%}{%% if my_new == '' %%}{%% set my_new = 'current' %%}{%% set my_cur = 'current' %%}{%% endif %%}{{ {'installed_version':my_cur,'latest_version':my_new,'entity_picture':'https://adapter.ebusd.eu/favicon.ico','release_url':'https://adapter.ebusd.eu/firmware/ChangeLog'} | tojson }}" } # the topic and payload to listen to in order to republish all config messages. From d2296295939e247bb13459ce805a81f394795cbe Mon Sep 17 00:00:00 2001 From: John Date: Sat, 17 Dec 2022 20:43:05 +0100 Subject: [PATCH 026/345] updated --- ChangeLog.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index 25add861d..549602435 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -5,11 +5,15 @@ * fix smaller issues in KNX integration * fix numeric replacement+infinite and float min/max values in MQTT JSON payload format * fix duplicate definition sent for same message when writable messages are included in MQTT integration +* fix UDP based devices no longer working since 22.4 +* fix for older SSL libraries not automatically retrying if necessary +* fix enhanced side data transfer from device to host ## Features * add support for setting visual ping, IP gateway, MAC from ID, and variant to ebuspicloader * add step variable for numeric values to message definition in MQTT integration * add yes/no values and heating curve (when writable) to Home Assistant MQTT discovery integration +* add device version to update check and switch to Home Assistant update integration for current update check and additionally for device # 22.4 (2022-09-18) From a73c11ecf3ab70073e5acbd355d7c11ee7486522 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 18 Dec 2022 09:08:28 +0100 Subject: [PATCH 027/345] update docs --- contrib/etc/ebusd/mqtt-hassio.cfg | 4 ++-- contrib/etc/ebusd/mqtt-integration.cfg | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/contrib/etc/ebusd/mqtt-hassio.cfg b/contrib/etc/ebusd/mqtt-hassio.cfg index 65c2fc145..af97d40bb 100644 --- a/contrib/etc/ebusd/mqtt-hassio.cfg +++ b/contrib/etc/ebusd/mqtt-hassio.cfg @@ -153,11 +153,11 @@ type_map-time = type_map-datetime = # field type switch designator, see below. -# HA integration: this is used to set several variable values depending on the field type, name, message, and field unit. +# HA integration: this is used to set several variable values depending on the field type, message name, field name, and unit. type_switch-by = %name%field,%unit # field type switch variables names to use in addition to %type_switch in case of multiple keys (separated by comma). -# HA integration: var names for topic/class/state/sub values mapped from field type, name, message, and unit. +# HA integration: var names for topic/class/state/sub values mapped from field type, message name, field name, and unit. type_switch-names = type_topic,type_class,type_state,type_sub # field type switch for each field type and optionally direction (between dash before the field type) available as diff --git a/contrib/etc/ebusd/mqtt-integration.cfg b/contrib/etc/ebusd/mqtt-integration.cfg index f7e486e60..ecbf52c1a 100644 --- a/contrib/etc/ebusd/mqtt-integration.cfg +++ b/contrib/etc/ebusd/mqtt-integration.cfg @@ -178,11 +178,14 @@ def_global-payload = { #def_global-retain = 0 # individual global running, version, signal, uptime, updatecheck, and scan config topic, payload, and retain setting. +# a secondary update check for the eBUS device (consuming the same updatecheck topic) can be set up additionally, which +# does not use the common global defaults. #def_global_running-... #def_global_version-... #def_global_signal-... #def_global_uptime-... #def_global_updatecheck-... +#def_global_updatecheck_device-... #def_global_scan-... From 240af58b4cd634b5be80d08c6772d0cf7b1c3542 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 18 Dec 2022 09:21:42 +0100 Subject: [PATCH 028/345] tolerate missing parts in type_switch key (fix for #562) --- src/ebusd/mqtthandler.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ebusd/mqtthandler.cpp b/src/ebusd/mqtthandler.cpp index 96727fd8e..d8a5f7b57 100755 --- a/src/ebusd/mqtthandler.cpp +++ b/src/ebusd/mqtthandler.cpp @@ -1132,8 +1132,8 @@ void MqttHandler::run() { if (!typeSwitchNames.empty()) { vector strs; splitFields(typeSwitch, &strs); - for (size_t pos = 0; pos < strs.size() && pos < typeSwitchNames.size(); pos++) { - values.set(typeSwitchNames[pos], strs[pos]); + for (size_t pos = 0; pos < typeSwitchNames.size(); pos++) { + values.set(typeSwitchNames[pos], pos < strs.size() ? strs[pos] : ""); } } } From 5d3c6f097ec66b900f631dde97440eae8d5d8107 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 18 Dec 2022 09:45:56 +0100 Subject: [PATCH 029/345] only send device update info when it is extractable --- contrib/etc/ebusd/mqtt-hassio.cfg | 4 ++-- contrib/etc/ebusd/mqtt-integration.cfg | 4 ++-- src/ebusd/bushandler.h | 5 +++++ src/ebusd/mqtthandler.cpp | 6 ++++-- src/lib/ebus/device.h | 5 +++++ 5 files changed, 18 insertions(+), 6 deletions(-) diff --git a/contrib/etc/ebusd/mqtt-hassio.cfg b/contrib/etc/ebusd/mqtt-hassio.cfg index af97d40bb..824e9df96 100644 --- a/contrib/etc/ebusd/mqtt-hassio.cfg +++ b/contrib/etc/ebusd/mqtt-hassio.cfg @@ -355,14 +355,14 @@ def_global_updatecheck-payload = %global_prefix, } -# optional secondary update check for the eBUS device (consuming the same topic though!) +# optional secondary update check for the enhanced eBUS device (consuming the same topic though!) def_global_updatecheck_device-topic = %haprefix/update/%{TOPIC}_device/config def_global_updatecheck_device-payload = { "unique_id":"%{TOPIC}_device", "device":{ "identifiers":"%{PREFIXN}_device", "manufacturer":"ebusd.eu", - "name":"%prefixn device", + "name":"%prefixn eBUS device", "via_device":"%PREFIXN", "suggested_area":"%area" }, diff --git a/contrib/etc/ebusd/mqtt-integration.cfg b/contrib/etc/ebusd/mqtt-integration.cfg index ecbf52c1a..d07e3d4e6 100644 --- a/contrib/etc/ebusd/mqtt-integration.cfg +++ b/contrib/etc/ebusd/mqtt-integration.cfg @@ -178,8 +178,8 @@ def_global-payload = { #def_global-retain = 0 # individual global running, version, signal, uptime, updatecheck, and scan config topic, payload, and retain setting. -# a secondary update check for the eBUS device (consuming the same updatecheck topic) can be set up additionally, which -# does not use the common global defaults. +# a secondary update check for the eBUS device (consuming the same updatecheck topic) can be set up, which will only be +# used if an enhanced eBUS device supporting extra info is present (does not make use of the common global defaults). #def_global_running-... #def_global_version-... #def_global_signal-... diff --git a/src/ebusd/bushandler.h b/src/ebusd/bushandler.h index 58441d9c1..e9a9e7d31 100755 --- a/src/ebusd/bushandler.h +++ b/src/ebusd/bushandler.h @@ -423,6 +423,11 @@ class BusHandler : public WaitThread { } } + /** + * @return the @a Device instance for accessing the bus. + */ + const Device* getDevice() const { return m_device; } + /** * Clear stored values (e.g. scan results). */ diff --git a/src/ebusd/mqtthandler.cpp b/src/ebusd/mqtthandler.cpp index d8a5f7b57..cdd0baa81 100755 --- a/src/ebusd/mqtthandler.cpp +++ b/src/ebusd/mqtthandler.cpp @@ -952,8 +952,10 @@ void MqttHandler::run() { publishDefinition(m_replacers, "def_global_uptime-", uptimeTopic, "global", "uptime", "def_global-"); publishDefinition(m_replacers, "def_global_updatecheck-", m_globalTopic.get("", "updatecheck"), "global", "updatecheck", "def_global-"); - publishDefinition(m_replacers, "def_global_updatecheck_device-", m_globalTopic.get("", "updatecheck"), "global", - "updatecheck_device", ""); + if (m_busHandler->getDevice()->supportsEnhancedInfos()) { + publishDefinition(m_replacers, "def_global_updatecheck_device-", m_globalTopic.get("", "updatecheck"), + "global", "updatecheck_device", ""); + } publishDefinition(m_replacers, "def_global_scan-", m_globalTopic.get("", "scan"), "global", "scan", "def_global-"); } diff --git a/src/lib/ebus/device.h b/src/lib/ebus/device.h index ff97472db..f28ec09b5 100755 --- a/src/lib/ebus/device.h +++ b/src/lib/ebus/device.h @@ -205,6 +205,11 @@ class Device { */ bool isEnhancedProto() const { return m_enhancedProto; } + /** + * @return whether the device supports the ebusd enhanced protocol and supports querying extra infos. + */ + bool supportsEnhancedInfos() const { return m_enhancedProto && m_extraFatures & 0x01; } + /** * Set the @a DeviceListener. * @param listener the @a DeviceListener. From 1e885611ddf03440fbf878ac0f02d81719cc9a8e Mon Sep 17 00:00:00 2001 From: John Date: Sun, 18 Dec 2022 09:55:34 +0100 Subject: [PATCH 030/345] updated --- ChangeLog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index 549602435..da5703015 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -12,7 +12,7 @@ ## Features * add support for setting visual ping, IP gateway, MAC from ID, and variant to ebuspicloader * add step variable for numeric values to message definition in MQTT integration -* add yes/no values and heating curve (when writable) to Home Assistant MQTT discovery integration +* add yes/no values and writable heating curve to Home Assistant MQTT discovery integration * add device version to update check and switch to Home Assistant update integration for current update check and additionally for device From 4263c98f04da3244e0f71640af8d5e70c3858495 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 2 Jan 2023 19:59:53 +0100 Subject: [PATCH 031/345] add language support to webservices --- contrib/config/index.php | 20 ++++++++++++++------ contrib/updatecheck/calcversions.sh | 10 ++++++++-- contrib/updatecheck/index.php | 19 +++++++++++++------ contrib/updatecheck/prepend.inc | 7 ++++--- 4 files changed, 39 insertions(+), 17 deletions(-) diff --git a/contrib/config/index.php b/contrib/config/index.php index 4bb82fd24..c51504442 100644 --- a/contrib/config/index.php +++ b/contrib/config/index.php @@ -1,19 +1,29 @@ oldversions.txt devver=`curl -s https://adapter.ebusd.eu/firmware/ChangeLog|grep "Version "|head -n 1|sed -e 's#.*]*>##' -e 's#<.*##' -e 's# ##'` devbl=`curl -s https://adapter.ebusd.eu/firmware/ChangeLog|grep "Bootloader "|head -n 1|sed -e 's#.*]*>##' -e 's#<.*##' -e 's# ##'` echo "device=${devver},${devbl}" >> versions.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 +files=`find config/de -type f -or -type l` +../../src/lib/ebus/test/test_filereader $files|sed -e 's#^config/de/##' -e 's#^\([^ ]*\) #\1=#' -e 's# #,#g'|sort >> versions.txt +files=`find config/en -type f -or -type l` +for filever in $(../../src/lib/ebus/test/test_filereader $files|sed -e 's#^config/en/##' -e 's#^\([^ ]*\) #\1=#' -e 's# #,#g'); do + file=${filever%%=*} + ver=${filever#*=} + sed -i -e "s#^$file=\(.*\)\$#$file=\1,$ver#" versions.txt +done #./oldtest_filereader $files|sed -e 's#^config/##' -e 's#^\([^ ]*\) #\1=#' -e 's# #,#g'|sort >> oldversions.txt diff --git a/contrib/updatecheck/index.php b/contrib/updatecheck/index.php index ba9519f21..230a0767b 100644 --- a/contrib/updatecheck/index.php +++ b/contrib/updatecheck/index.php @@ -5,7 +5,7 @@ $r = @file_get_contents('php://input'); header('Content-Type: text/plain'); $r = @json_decode($r, true); - echo checkUpdate(@$r['v'], @$r['r'], @$r['a'], @$r['dv'], @$r['l']); + echo checkUpdate(@$r['v'], @$r['r'], @$r['a'], @$r['dv'], @$r['l'], @$r['lc']); exit; } readVersions(); @@ -24,21 +24,28 @@ ?>

latest ebusd version:

latest device firmware:

-

config files: +

config files:"; global $ref; - $str = "$k: ".date('d.m.Y H:i:s', $v[2]); + $str = date('d.m.Y H:i:s', $v[2]); if ($ref) { - echo "
$str\n"; + echo "\n"; } else { - echo "
$str\n"; + echo "\n"; + } + $str = date('d.m.Y H:i:s', $v[5]); + if ($ref) { + echo "\n"; + } else { + echo "\n"; } }; array_walk($versions, $func); ?> -

+
FileGermanEnglish
$k$str$str$str$str

diff --git a/contrib/updatecheck/prepend.inc b/contrib/updatecheck/prepend.inc index fe2488734..5b422f07b 100644 --- a/contrib/updatecheck/prepend.inc +++ b/contrib/updatecheck/prepend.inc @@ -16,7 +16,7 @@ function readVersions($old=false) { array_walk($v, $func); } } -function checkUpdate($ebusdVersion, $ebusdRelease, $architecture, $deviceVersion, $loadedFiles) { +function checkUpdate($ebusdVersion, $ebusdRelease, $architecture, $deviceVersion, $loadedFiles, $language) { if (!$ebusdVersion) { return 'invalid request'; } @@ -39,11 +39,12 @@ function checkUpdate($ebusdVersion, $ebusdRelease, $architecture, $deviceVersion } $newerAvailable = 0; $configs = ''; + $offset = $language==='en' ? 3 : 0; foreach ($loadedFiles as $k => $val) { $v = $versions[$k]; if ($v) { - $newer = $v[2]>$val['t']; - if ($v[0]!=$val['h'] || $v[1]!=$val['s']) { + $newer = $v[2+$offset]>$val['t']; + if ($v[0+$offset]!=$val['h'] || $v[1+$offset]!=$val['s']) { if ($newer) { $newerAvailable++; } From eb007fe381da678546015e555999908ff9f8ba2e Mon Sep 17 00:00:00 2001 From: John Date: Mon, 2 Jan 2023 20:00:58 +0100 Subject: [PATCH 032/345] use preferred language for config file retrieval --- src/ebusd/bushandler.cpp | 3 +++ src/ebusd/main.cpp | 16 +++++++++++++--- src/lib/ebus/filereader.h | 5 +++++ src/lib/ebus/message.h | 2 +- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/ebusd/bushandler.cpp b/src/ebusd/bushandler.cpp index 47bb5547e..9f12edab7 100644 --- a/src/ebusd/bushandler.cpp +++ b/src/ebusd/bushandler.cpp @@ -1530,6 +1530,9 @@ void BusHandler::formatUpdateInfo(ostringstream* output) const { } *output << "],\"gu\":" << unknownCnt; } + if (!m_messages->getPreferLanguage().empty()) { + *output << ",\"lc\":\"" << m_messages->getPreferLanguage() << "\""; + } unsigned char address = 0; for (int index = 0; index < 256; index++, address++) { bool ownAddress = !m_device->isReadOnly() && (address == m_ownMasterAddress || address == m_ownSlaveAddress); diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index 1a6b9e324..9824c877c 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -151,6 +151,9 @@ static string s_configLocalPrefix = ""; /** the URI prefix (including trailing "/") for retrieving configuration files from HTTPS (empty for local files). */ static string s_configUriPrefix = ""; +/** the optional language query part for retrieving configuration files from HTTPS (empty for local files). */ +static string s_configLangQuery = ""; + /** the @a HttpClient for retrieving configuration files from HTTPS. */ static HttpClient* s_configHttpClient = nullptr; @@ -858,7 +861,8 @@ static result_t collectConfigFiles(const string& relPath, const string& prefix, vector* dirs = nullptr, bool* hasTemplates = nullptr) { const string relPathWithSlash = relPath.empty() ? "" : relPath + "/"; if (!s_configUriPrefix.empty()) { - string uri = s_configUriPrefix + relPathWithSlash + "?t=" + extension.substr(1) + query; + string uri = s_configUriPrefix + relPathWithSlash + s_configLangQuery + (s_configLangQuery.empty() ? "?" : "&") + + "t=" + extension.substr(1) + query; string names; if (!lazyHttpClient() || !s_configHttpClient->get(uri, "", &names)) { return RESULT_ERR_NOTFOUND; @@ -1074,7 +1078,7 @@ result_t loadDefinitionsFromConfigPath(FileReader* reader, const string& filenam stream = FileReader::openFile(s_configLocalPrefix + filename, errorDescription, &mtime); } else { string content; - if (lazyHttpClient() && s_configHttpClient->get(s_configUriPrefix + filename, "", &content, &mtime)) { + if (lazyHttpClient() && s_configHttpClient->get(s_configUriPrefix + filename + s_configLangQuery, "", &content, &mtime)) { stream = new istringstream(content); } } @@ -1423,6 +1427,12 @@ int main(int argc, char* argv[]) { } s_configHttpClient->disconnect(); } + const string lang = MappedFileReader::normalizeLanguage( + s_opt.preferLanguage==nullptr || !s_opt.preferLanguage[0] ? "" : s_opt.preferLanguage + ); + if (!lang.empty()) { + s_configLangQuery = "?l=" + lang; + } if (!s_opt.readOnly && s_opt.scanConfig && s_opt.initialScan == 0) { s_opt.initialScan = BROADCAST; } @@ -1431,7 +1441,7 @@ int main(int argc, char* argv[]) { setFacilitiesLogLevel(s_opt.logAreas, s_opt.logLevel); } - s_messageMap = new MessageMap(s_opt.checkConfig); + s_messageMap = new MessageMap(s_opt.checkConfig, lang); if (s_opt.checkConfig) { logNotice(lf_main, PACKAGE_STRING "." REVISION " performing configuration check..."); diff --git a/src/lib/ebus/filereader.h b/src/lib/ebus/filereader.h index d34a8db48..ff94a6c17 100755 --- a/src/lib/ebus/filereader.h +++ b/src/lib/ebus/filereader.h @@ -222,6 +222,11 @@ class MappedFileReader : public FileReader { */ static const string normalizeLanguage(const string& lang); + /** + * @return the preferred language code (up to 2 characters), or empty. + */ + const string getPreferLanguage() const { return m_preferLanguage; } + // @copydoc result_t readFromStream(istream* stream, const string& filename, const time_t& mtime, bool verbose, map* defaults, string* errorDescription, bool replace = false, size_t* hash = nullptr, diff --git a/src/lib/ebus/message.h b/src/lib/ebus/message.h index 70c6c36f8..9ce3d2848 100644 --- a/src/lib/ebus/message.h +++ b/src/lib/ebus/message.h @@ -1267,7 +1267,7 @@ class MessageMap : public MappedFileReader { * @param deleteData whether to delete the scan message @a DataField during @a Message destruction. */ explicit MessageMap(bool addAll = false, const string& preferLanguage = "", bool deleteData = true) - : MappedFileReader::MappedFileReader(true), + : MappedFileReader::MappedFileReader(true, preferLanguage), m_addAll(addAll), m_additionalScanMessages(false), m_maxIdLength(0), m_maxBroadcastIdLength(0), m_messageCount(0), m_conditionalMessageCount(0), m_passiveMessageCount(0) { m_scanMessage = Message::createScanMessage(false, deleteData); From b5d6a495a1963ea0b0699ec6a28434bbd227de8d Mon Sep 17 00:00:00 2001 From: John Date: Mon, 2 Jan 2023 20:01:32 +0100 Subject: [PATCH 033/345] add jumper flag infos --- docs/enhanced_proto.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/enhanced_proto.md b/docs/enhanced_proto.md index 3593962b3..9023f4e1c 100644 --- a/docs/enhanced_proto.md +++ b/docs/enhanced_proto.md @@ -114,9 +114,9 @@ The first byte transferred in response is always the number of data bytes to be * 0x00: version * `length`: =8 (2 before 20220220, 5 before 20220831) * `version`: version number - * `features`: feature bits + * `features`: feature bits (see above) * `checksum_H` `checksum_L`: checksum (since 20220220) - * `jumpers`: jumper settings + * `jumpers`: jumper settings (0x01=enhanced, 0x02=high speed, 0x04=Ethernet, 0x08=WIFI, 0x10=v3.1, 0x20=ignore hard jumpers) * `bootloader_version`: bootloader version (since 20220831) * `bootloader_checksum_H` `bootloader_checksum_L`: bootloader checksum * 0x01: PIC ID From 8dcf36afd59c9d68e3f4715a981b3a58a79dedcd Mon Sep 17 00:00:00 2001 From: John Date: Mon, 2 Jan 2023 20:03:09 +0100 Subject: [PATCH 034/345] updated --- ChangeLog.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index da5703015..d3ca48ce0 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -13,7 +13,8 @@ * add support for setting visual ping, IP gateway, MAC from ID, and variant to ebuspicloader * add step variable for numeric values to message definition in MQTT integration * add yes/no values and writable heating curve to Home Assistant MQTT discovery integration -* add device version to update check and switch to Home Assistant update integration for current update check and additionally for device +* add device version to update check and switch to Home Assistant update integration for current update check and additionally for device +* add preferred language support to web services and use it instead of DE when configured # 22.4 (2022-09-18) From 00bf9b51042e618c80b1fb0d38640f8ae3ea766c Mon Sep 17 00:00:00 2001 From: John Date: Mon, 2 Jan 2023 20:17:35 +0100 Subject: [PATCH 035/345] update action versions --- .github/workflows/build.yml | 6 +++--- .github/workflows/coverage.yml | 2 +- .github/workflows/preparerelease.yml | 6 +++--- .github/workflows/release.yml | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5bdf57e26..868c9ebfa 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,18 +29,18 @@ jobs: steps: - name: checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: gh-describe id: gittag uses: proudust/gh-describe@v1.4.6 - name: set up QEMU - uses: docker/setup-qemu-action@v1 + uses: docker/setup-qemu-action@v2 - name: set up buildx id: buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v2 with: buildkitd-flags: --debug - diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 50ab4efb2..932fbdfcc 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -15,7 +15,7 @@ jobs: steps: - name: checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: packages run: sudo apt-get update && sudo apt-get install -y libmosquitto1 libmosquitto-dev libssl1.1 libssl-dev diff --git a/.github/workflows/preparerelease.yml b/.github/workflows/preparerelease.yml index b14f2354f..3a60e85b6 100644 --- a/.github/workflows/preparerelease.yml +++ b/.github/workflows/preparerelease.yml @@ -36,18 +36,18 @@ jobs: steps: - name: checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: gh-describe id: gittag uses: proudust/gh-describe@v1.4.6 - name: set up QEMU - uses: docker/setup-qemu-action@v1 + uses: docker/setup-qemu-action@v2 - name: set up buildx id: buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v2 with: buildkitd-flags: --debug - diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c9fce6c21..2422c8f69 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,14 +15,14 @@ jobs: steps: - name: checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: set up QEMU - uses: docker/setup-qemu-action@v1 + uses: docker/setup-qemu-action@v2 - name: set up buildx id: buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v2 with: buildkitd-flags: --debug - From 670fb7749179f0f8be542f69ad62aedbadf4f025 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 2 Jan 2023 20:23:10 +0100 Subject: [PATCH 036/345] update action versions --- .github/workflows/build.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 868c9ebfa..e7bd38e35 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -45,7 +45,7 @@ jobs: buildkitd-flags: --debug - name: login to docker hub - uses: docker/login-action@v1 + uses: docker/login-action@v2 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2422c8f69..81b56fb13 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: buildkitd-flags: --debug - name: login to docker hub - uses: docker/login-action@v1 + uses: docker/login-action@v2 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} From d29be6d96c9f9a2e29147377eaf5642e7e1d0274 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 6 Jan 2023 07:37:49 +0100 Subject: [PATCH 037/345] fix for sequence started immediately after own SYN sent behind sent command (fixes #708) --- src/ebusd/bushandler.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ebusd/bushandler.cpp b/src/ebusd/bushandler.cpp index 9f12edab7..7cef99583 100644 --- a/src/ebusd/bushandler.cpp +++ b/src/ebusd/bushandler.cpp @@ -1032,9 +1032,9 @@ result_t BusHandler::handleSymbol() { case bs_sendSyn: if (!sending) { - return setState(bs_skip, RESULT_ERR_INVALID_ARG); + return setState(bs_ready, RESULT_ERR_INVALID_ARG); } - return setState(bs_skip, RESULT_OK); + return setState(bs_ready, RESULT_OK); } return RESULT_OK; } @@ -1090,7 +1090,8 @@ result_t BusHandler::setState(BusState state, result_t result, bool firstRepetit logDebug(lf_bus, "%s during %s, switching to %s", getResultCode(result), getStateCode(m_state), getStateCode(state)); } else if (m_currentRequest != nullptr || state == bs_sendCmd || state == bs_sendCmdCrc || state == bs_sendCmdAck - || state == bs_sendRes || state == bs_sendResCrc || state == bs_sendResAck || state == bs_sendSyn) { + || state == bs_sendRes || state == bs_sendResCrc || state == bs_sendResAck || state == bs_sendSyn + || m_state == bs_sendSyn) { logDebug(lf_bus, "switching from %s to %s", getStateCode(m_state), getStateCode(state)); } if (state == bs_noSignal) { From be554841fb4e887635685dfcd1a14d2cb2bfb49f Mon Sep 17 00:00:00 2001 From: John Date: Fri, 6 Jan 2023 08:00:19 +0100 Subject: [PATCH 038/345] use configlang for new test files location --- make_debian.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/make_debian.sh b/make_debian.sh index cb8d1f943..72376e0f9 100755 --- a/make_debian.sh +++ b/make_debian.sh @@ -103,7 +103,7 @@ if [ -n "$RUNTEST" ]; then (cd src/lib/ebus/test && make test_symbol && ./test_symbol) > test.txt || testdie "symbol" fi # note: this can't run on file system base when using qemu for arm 32bit and host is 64bit due to glibc readdir() inode 32 bit values (see https://bugs.launchpad.net/qemu/+bug/1805913), thus using test end point instead: - ("$RELEASE/usr/bin/ebusd" -f -s -c https://cfg.ebusd.eu/test -d /dev/null --log=all:debug --inject=stop 10fe0900040000803e/ > test.txt) || testdie "float conversion" + ("$RELEASE/usr/bin/ebusd" -f -s --configlang=tt -d /dev/null --log=all:debug --inject=stop 10fe0900040000803e/ > test.txt) || testdie "float conversion" egrep "received update-read broadcast test QQ=10: 0\.25$" test.txt || testdie "float result" fi From d6854ff87785299b310c301f8140d5b9b0552c7e Mon Sep 17 00:00:00 2001 From: John Date: Fri, 6 Jan 2023 08:00:58 +0100 Subject: [PATCH 039/345] add COM-port hint on cygwin --- src/tools/ebuspicloader.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/tools/ebuspicloader.cpp b/src/tools/ebuspicloader.cpp index aa7e35d8a..20fe849c9 100644 --- a/src/tools/ebuspicloader.cpp +++ b/src/tools/ebuspicloader.cpp @@ -47,7 +47,13 @@ const char *argp_program_version = "eBUS adapter PIC firmware loader"; /** the documentation of the program. */ static const char argpdoc[] = "A tool for loading firmware to the eBUS adapter PIC and configure adjustable settings." - "\vPORT is either the serial port to use (e.g./dev/ttyUSB0) that also supports a trailing wildcard '*' for testing" + "\vPORT is either the serial port to use (e.g. " +#ifdef __CYGWIN__ + "/dev/ttyS0 for COM1 on Windows" +#else + "/dev/ttyUSB0" +#endif + ") that also supports a trailing wildcard '*' for testing" " multiple ports, or a network port as \"ip:port\" for use with e.g. socat or ebusd-esp in PIC pass-through mode."; static const char argpargsdoc[] = "PORT"; From be574f1f8c7b6d72d48b55ed0950605c7d6588ba Mon Sep 17 00:00:00 2001 From: John Date: Fri, 6 Jan 2023 10:51:21 +0100 Subject: [PATCH 040/345] pass env from main and check FreeBSD for endian include (fix #650) --- src/ebusd/main.cpp | 9 +++++---- src/lib/knx/knxnet.h | 6 +++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index 9824c877c..dc50dc283 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -1334,15 +1334,16 @@ bool parseMessage(const string& arg, bool onlyMasterSlave, MasterSymbolString* m * Main function. * @param argc the number of command line arguments. * @param argv the command line arguments. + * @param envp the environment variables. * @return the exit code. */ -int main(int argc, char* argv[]) { +int main(int argc, char* argv[], char* envp[]) { struct argp aargp = { argpoptions, parse_opt, nullptr, argpdoc, datahandler_getargs(), nullptr, nullptr }; setenv("ARGP_HELP_FMT", "no-dup-args-note", 0); char envname[32] = "--"; // needs to cover at least max length of any option name plus "--" char* envopt = envname+2; - for (char ** env = environ; *env; env++) { + for (char ** env = envp; *env; env++) { char* pos = strchr(*env, '='); if (!pos || strncmp(*env, "EBUSD_", sizeof("EBUSD_")-1) != 0) { continue; @@ -1569,6 +1570,6 @@ int main(int argc, char* argv[]) { } // namespace ebusd -int main(int argc, char* argv[]) { - return ebusd::main(argc, argv); +int main(int argc, char* argv[], char* envp[]) { + return ebusd::main(argc, argv, envp); } diff --git a/src/lib/knx/knxnet.h b/src/lib/knx/knxnet.h index 578ee98ce..c17526ee4 100644 --- a/src/lib/knx/knxnet.h +++ b/src/lib/knx/knxnet.h @@ -31,7 +31,11 @@ #include #include #include -#include +#ifdef __FreeBSD__ + #include +#else + #include +#endif #include #include #include From 88f54c0d7f66eb56ee46372f82a2e96817d55e21 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 6 Jan 2023 10:51:47 +0100 Subject: [PATCH 041/345] remove unused --- src/lib/ebus/device.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/lib/ebus/device.cpp b/src/lib/ebus/device.cpp index 3fc82402d..668d070cf 100755 --- a/src/lib/ebus/device.cpp +++ b/src/lib/ebus/device.cpp @@ -896,18 +896,6 @@ void SerialDevice::checkDevice() { } } -#ifdef __CYGWIN__ - #ifndef TCP_KEEPCNT - #define TCP_KEEPCNT 8 - #endif - #ifndef TCP_KEEPINTVL - #define TCP_KEEPINTVL 150 - #endif - #ifndef TCP_KEEPIDLE - #define TCP_KEEPIDLE 14400 - #endif -#endif - result_t NetworkDevice::open() { result_t result = Device::open(); if (result != RESULT_OK) { From 9f51946f6d5a690d473d33cc8ec47d5ae59f799a Mon Sep 17 00:00:00 2001 From: John Date: Fri, 6 Jan 2023 10:58:00 +0100 Subject: [PATCH 042/345] print setsockopt errors and add missing options, add TCP_USER_TIMEOUT if defined --- src/lib/utils/tcpsocket.cpp | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/lib/utils/tcpsocket.cpp b/src/lib/utils/tcpsocket.cpp index 646e049d3..5e6268f7e 100755 --- a/src/lib/utils/tcpsocket.cpp +++ b/src/lib/utils/tcpsocket.cpp @@ -89,13 +89,40 @@ int tcpKeepAliveInterval) { } if (tcpKeepAliveInterval > 0) { value = 1; - setsockopt(sfd, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast(&value), sizeof(value)); + if (setsockopt(sfd, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast(&value), sizeof(value)) != 0) { + perror("setsockopt KEEPALIVE"); + } +#ifndef TCP_KEEPIDLE + #ifdef TCP_KEEPALIVE + #define TCP_KEEPIDLE TCP_KEEPALIVE + #else + #define TCP_KEEPIDLE 4 + #endif +#endif +#ifndef TCP_KEEPINTVL + #define TCP_KEEPINTVL 5 +#endif +#ifndef TCP_KEEPCNT + #define TCP_KEEPCNT 6 +#endif value = tcpKeepAliveInterval+1; // send keepalive after interval + 1 seconds of silence - setsockopt(sfd, IPPROTO_TCP, TCP_KEEPIDLE, reinterpret_cast(&value), sizeof(value)); + if (setsockopt(sfd, IPPROTO_TCP, TCP_KEEPIDLE, reinterpret_cast(&value), sizeof(value)) != 0) { + perror("setsockopt KEEPIDLE"); + } value = tcpKeepAliveInterval; // send keepalive in given interval - setsockopt(sfd, IPPROTO_TCP, TCP_KEEPINTVL, reinterpret_cast(&value), sizeof(value)); + if (setsockopt(sfd, IPPROTO_TCP, TCP_KEEPINTVL, reinterpret_cast(&value), sizeof(value)) != 0) { + perror("setsockopt KEEPINTVL"); + } value = 2; // drop connection after 2 failed keep alive sends - setsockopt(sfd, IPPROTO_TCP, TCP_KEEPCNT, reinterpret_cast(&value), sizeof(value)); + if (setsockopt(sfd, IPPROTO_TCP, TCP_KEEPCNT, reinterpret_cast(&value), sizeof(value)) != 0) { + perror("setsockopt KEEPCNT"); + } +#ifdef TCP_USER_TIMEOUT + value = (2+tcpKeepAliveInterval*3)*1000; // 1 second higher than keepalive timeout + if (setsockopt(sfd, IPPROTO_TCP, TCP_USER_TIMEOUT, reinterpret_cast(&value), sizeof(value)) != 0) { + perror("setsockopt USER_TIMEOUT"); + } +#endif } #ifndef HAVE_PPOLL #ifndef HAVE_PSELECT From 78e12b241b0b35b06f0137f40bf4f3334792f640 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 6 Jan 2023 10:58:29 +0100 Subject: [PATCH 043/345] add COM port hint --- src/tools/README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/tools/README.md b/src/tools/README.md index e82cc5394..938b6675e 100644 --- a/src/tools/README.md +++ b/src/tools/README.md @@ -10,11 +10,12 @@ bootloader is not running, this tool can't do anything. Check the [eBUS adapter 3 documentation](https://adapter.ebusd.eu/picfirmware) on how to start the bootloader. -The binary is part of the ebusd release and a Windows binary based on Cygwin is available for download here: +The binary is part of the [release](https://github.com/john30/ebusd/releases) and a Windows binary based on Cygwin is available for download here: [ebuspicloader-windows.zip](https://adapter.ebusd.eu/firmware/ebuspicloader-windows.zip) It can be started from within Windows `cmd.exe` after extracting the files to a folder and `cd` into that folder. If Cygwin is already installed, only the `ebuspicloader.exe` needs to be extracted and can be called directly -from within a Cygwin shell. +from within a Cygwin shell. +In Cygwin, Windows COM ports are mapped under `/dev/ttyS*`, e.g. `COM1` would be `/dev/ttyS0`. This tool is an alternative to the MPLAB bootloader host application that produces a lot of unreadable output. @@ -61,7 +62,7 @@ settings. --usage give a short usage message -V, --version print program version -PORT is either the serial port to use (e.g./dev/ttyUSB0) that also supports a +PORT is either the serial port to use (e.g. /dev/ttyUSB0) that also supports a trailing wildcard '*' for testing multiple ports, or a network port as "ip:port" for use with e.g. socat or ebusd-esp in PIC pass-through mode. ``` From fe2849ec65608e4037026071c15d658a0a7d97d9 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 6 Jan 2023 10:59:47 +0100 Subject: [PATCH 044/345] updated --- ChangeLog.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index d3ca48ce0..49677cdea 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,5 +1,5 @@ -# next (tbd) -## Buf Fixes +# 23.1 (tbd) +## Bug Fixes * fix potentially invalid settings picked up from environment variables * fix potentially unnecessary arbitration start for non-enhanced proto * fix smaller issues in KNX integration @@ -8,13 +8,14 @@ * fix UDP based devices no longer working since 22.4 * fix for older SSL libraries not automatically retrying if necessary * fix enhanced side data transfer from device to host +* fix for fast participants starting immediately after own SYN at the end of a sent command ## Features * add support for setting visual ping, IP gateway, MAC from ID, and variant to ebuspicloader * add step variable for numeric values to message definition in MQTT integration * add yes/no values and writable heating curve to Home Assistant MQTT discovery integration * add device version to update check and switch to Home Assistant update integration for current update check and additionally for device -* add preferred language support to web services and use it instead of DE when configured +* add preferred language support to web services and use it instead of default LANG environment with fallback to German # 22.4 (2022-09-18) From 780703c6ff2135b9bb6ba6ac5ecf27b936ea04fd Mon Sep 17 00:00:00 2001 From: John Date: Fri, 6 Jan 2023 11:39:13 +0100 Subject: [PATCH 045/345] avoid unnecessary waits when there is no signal --- src/ebusd/bushandler.cpp | 3 +++ src/ebusd/mainloop.cpp | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ebusd/bushandler.cpp b/src/ebusd/bushandler.cpp index 7cef99583..524a3e2c0 100644 --- a/src/ebusd/bushandler.cpp +++ b/src/ebusd/bushandler.cpp @@ -362,6 +362,9 @@ void BusHandler::clear() { } result_t BusHandler::sendAndWait(const MasterSymbolString& master, SlaveSymbolString* slave) { + if (m_state == bs_noSignal) { + return RESULT_ERR_NO_SIGNAL; // don't wait when there is no signal + } result_t result = RESULT_ERR_NO_SIGNAL; slave->clear(); ActiveBusRequest request(master, slave); diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index 2f136edb1..c87d6ed98 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -299,7 +299,7 @@ void MainLoop::run() { reload = false; } } - if (!loadDelay) { + if (!loadDelay && m_busHandler->hasSignal()) { lastScanAddress = m_busHandler->getNextScanAddress(lastScanAddress, scanCompleted >= SCAN_REPEAT_COUNT); if (lastScanAddress == SYN) { taskDelay = 5; @@ -1825,6 +1825,9 @@ result_t MainLoop::executeScan(const vector& args, const string& levels, return RESULT_OK; } + if (!m_busHandler->hasSignal()) { + return RESULT_ERR_NO_SIGNAL; + } result_t result; symbol_t dstAddress = (symbol_t)parseInt(args[1].c_str(), 16, 0, 0xff, &result); if (result == RESULT_OK && !isValidAddress(dstAddress, false)) { From 8af687aa666893cbfcfa0ced1929aae6c0de2948 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 6 Jan 2023 11:49:27 +0100 Subject: [PATCH 046/345] retry config check once --- src/ebusd/main.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index dc50dc283..c7ecb9807 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -1420,8 +1420,12 @@ int main(int argc, char* argv[], char* envp[]) { logError(lf_main, "invalid configPath URL"); return EINVAL; } - if (!lazyHttpClient() - || !s_configHttpClient->connect(configHost, configPort, proto == "https", PACKAGE_NAME "/" PACKAGE_VERSION)) { + if (!lazyHttpClient() || ( + // check with low timeout of 1 second initially: + !s_configHttpClient->connect(configHost, configPort, proto == "https", PACKAGE_NAME "/" PACKAGE_VERSION, 1) + // if that did not work, issue a single retry with default timeout: + && !s_configHttpClient->connect(configHost, configPort, proto == "https", PACKAGE_NAME "/" PACKAGE_VERSION) + )) { logError(lf_main, "invalid configPath URL (connect)"); cleanup(); return EINVAL; From 8e612b9e0e10ba78a8ae4441ecd1f2d2bf1347a3 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 6 Jan 2023 12:06:12 +0100 Subject: [PATCH 047/345] add option to exit non-zero on non-success response from ebusd (fixes #782) --- ChangeLog.md | 2 ++ src/tools/ebusctl.cpp | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 49677cdea..f49c88304 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -9,6 +9,7 @@ * fix for older SSL libraries not automatically retrying if necessary * fix enhanced side data transfer from device to host * fix for fast participants starting immediately after own SYN at the end of a sent command +* add a single retry when initial config location check fails ## Features * add support for setting visual ping, IP gateway, MAC from ID, and variant to ebuspicloader @@ -16,6 +17,7 @@ * add yes/no values and writable heating curve to Home Assistant MQTT discovery integration * add device version to update check and switch to Home Assistant update integration for current update check and additionally for device * add preferred language support to web services and use it instead of default LANG environment with fallback to German +* add option to exit non-zero on non-success response from ebusd to ebusctl # 22.4 (2022-09-18) diff --git a/src/tools/ebusctl.cpp b/src/tools/ebusctl.cpp index ea2642f81..d076b57f1 100755 --- a/src/tools/ebusctl.cpp +++ b/src/tools/ebusctl.cpp @@ -45,6 +45,7 @@ struct options { const char* server; //!< ebusd server host (name or ip) [localhost] uint16_t port; //!< ebusd server port [8888] uint16_t timeout; //!< ebusd connect/send/receive timeout + bool errorResponse; //!< non-zero exit on error response char* const *args; //!< arguments to pass to ebusd unsigned int argCount; //!< number of arguments to pass to ebusd @@ -54,9 +55,10 @@ struct options { static struct options opt = { "localhost", // server 8888, // port - 60, // timeout + 60, // timeout + false, // non-zero exit on error response - nullptr, // args + nullptr, // args 0 // argCount }; @@ -83,6 +85,7 @@ static const struct argp_option argpoptions[] = { {"port", 'p', "PORT", 0, "Connect to " PACKAGE " on PORT [8888]", 0 }, {"timeout", 't', "SECS", 0, "Timeout for connecting to/receiving from " PACKAGE ", 0 for none [60]", 0 }, + {"error", 'e', nullptr, 0, "Exit non-zero if the connection was fine but the response indicates non-success"}, {nullptr, 0, nullptr, 0, nullptr, 0 }, }; @@ -122,6 +125,9 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { } opt->timeout = (uint16_t)value; break; + case 'e': // --error + opt->errorResponse = true; + break; case ARGP_KEY_ARGS: opt->args = state->argv + state->next; opt->argCount = state->argc - state->next; @@ -332,8 +338,12 @@ bool connect(const char* host, uint16_t port, uint16_t timeout, char* const *arg } } } else { - cout << fetchData(socket, listening, timeout, errored); + string response = fetchData(socket, listening, timeout, errored); + cout << response; cout.flush(); + if (errored || (opt.errorResponse && response.substr(0, 4) == "ERR:")) { + ret = false; + } } } } while (!errored && !once && !cin.eof()); From 672dae22e77ca3a06a9336319d7d50b4d7452f00 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 6 Jan 2023 12:14:44 +0100 Subject: [PATCH 048/345] fix previous commit --- src/tools/ebusctl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/ebusctl.cpp b/src/tools/ebusctl.cpp index d076b57f1..9539c5c2c 100755 --- a/src/tools/ebusctl.cpp +++ b/src/tools/ebusctl.cpp @@ -85,7 +85,7 @@ static const struct argp_option argpoptions[] = { {"port", 'p', "PORT", 0, "Connect to " PACKAGE " on PORT [8888]", 0 }, {"timeout", 't', "SECS", 0, "Timeout for connecting to/receiving from " PACKAGE ", 0 for none [60]", 0 }, - {"error", 'e', nullptr, 0, "Exit non-zero if the connection was fine but the response indicates non-success"}, + {"error", 'e', nullptr, 0, "Exit non-zero if the connection was fine but the response indicates non-success", 0}, {nullptr, 0, nullptr, 0, nullptr, 0 }, }; From b42b798e547b6ad0d4e669e567cb2ffa59411f85 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 6 Jan 2023 12:31:29 +0100 Subject: [PATCH 049/345] formatting --- src/ebusd/main.cpp | 7 ++++--- src/lib/ebus/device.cpp | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index c7ecb9807..52611beab 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -1078,7 +1078,8 @@ result_t loadDefinitionsFromConfigPath(FileReader* reader, const string& filenam stream = FileReader::openFile(s_configLocalPrefix + filename, errorDescription, &mtime); } else { string content; - if (lazyHttpClient() && s_configHttpClient->get(s_configUriPrefix + filename + s_configLangQuery, "", &content, &mtime)) { + if (lazyHttpClient() + && s_configHttpClient->get(s_configUriPrefix + filename + s_configLangQuery, "", &content, &mtime)) { stream = new istringstream(content); } } @@ -1412,7 +1413,7 @@ int main(int argc, char* argv[], char* envp[]) { string proto, configHost; if (!HttpClient::parseUrl(s_configPath, &proto, &configHost, &configPort, &s_configUriPrefix)) { #ifndef HAVE_SSL - if (proto=="https") { + if (proto == "https") { logError(lf_main, "invalid configPath URL (HTTPS not supported)"); return EINVAL; } @@ -1433,7 +1434,7 @@ int main(int argc, char* argv[], char* envp[]) { s_configHttpClient->disconnect(); } const string lang = MappedFileReader::normalizeLanguage( - s_opt.preferLanguage==nullptr || !s_opt.preferLanguage[0] ? "" : s_opt.preferLanguage + s_opt.preferLanguage == nullptr || !s_opt.preferLanguage[0] ? "" : s_opt.preferLanguage ); if (!lang.empty()) { s_configLangQuery = "?l=" + lang; diff --git a/src/lib/ebus/device.cpp b/src/lib/ebus/device.cpp index 668d070cf..7b51d1afa 100755 --- a/src/lib/ebus/device.cpp +++ b/src/lib/ebus/device.cpp @@ -256,7 +256,8 @@ string Device::getEnhancedInfos() { if (res != RESULT_OK) { m_enhInfoBusVoltage = "bus voltage unknown"; } - return "firmware " + m_enhInfoVersion + ", " + m_enhInfoTemperature + ", " + m_enhInfoSupplyVoltage + ", " + m_enhInfoBusVoltage; + return "firmware " + m_enhInfoVersion + ", " + m_enhInfoTemperature + ", " + m_enhInfoSupplyVoltage + ", " + + m_enhInfoBusVoltage; } result_t Device::send(symbol_t value) { From ca6767dece2bee01ad8293f386ef09435f98f614 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 6 Jan 2023 12:32:06 +0100 Subject: [PATCH 050/345] allow argp-standalone as well --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5602e55db..e4e68d0eb 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,7 +74,7 @@ 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) + find_library(LIB_ARGP NAMES argp argp-standalone) if (NOT LIB_ARGP) message(FATAL_ERROR "argp library not available") endif(NOT LIB_ARGP) From 4c2d87e40f781b637ac9c2430557d0a9a9665fd9 Mon Sep 17 00:00:00 2001 From: john30 Date: Fri, 6 Jan 2023 12:35:17 +0100 Subject: [PATCH 051/345] updated version to 23.1 --- ChangeLog.md | 2 +- VERSION | 2 +- contrib/alpine/APKBUILD | 2 +- contrib/archlinux/PKGBUILD | 2 +- contrib/archlinux/PKGBUILD.git | 2 +- contrib/html/openapi.yaml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index f49c88304..b966d24d4 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,4 +1,4 @@ -# 23.1 (tbd) +# 23.1 (2022-12-06) ## Bug Fixes * fix potentially invalid settings picked up from environment variables * fix potentially unnecessary arbitration start for non-enhanced proto diff --git a/VERSION b/VERSION index 1da8ccd28..eaccea244 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -22.4 \ No newline at end of file +23.1 \ No newline at end of file diff --git a/contrib/alpine/APKBUILD b/contrib/alpine/APKBUILD index 826b5916a..4cb6a0cd7 100644 --- a/contrib/alpine/APKBUILD +++ b/contrib/alpine/APKBUILD @@ -1,7 +1,7 @@ # Contributor: Tim # Maintainer: Tim pkgname=ebusd -pkgver=22.4 +pkgver=23.1 pkgrel=0 pkgdesc="Daemon for communication with eBUS heating systems" url="https://github.com/john30/ebusd" diff --git a/contrib/archlinux/PKGBUILD b/contrib/archlinux/PKGBUILD index 5b117b4ec..17112bd94 100644 --- a/contrib/archlinux/PKGBUILD +++ b/contrib/archlinux/PKGBUILD @@ -2,7 +2,7 @@ # Contributor: Milan Knizek # Usage: makepkg pkgname=ebusd -pkgver=22.4 +pkgver=23.1 pkgrel=1 pkgdesc="ebusd, the daemon for communication with eBUS heating systems." arch=('i686' 'x86_64' 'armv6h' 'armv7h' 'aarch64') diff --git a/contrib/archlinux/PKGBUILD.git b/contrib/archlinux/PKGBUILD.git index 7885e4f71..f62971367 100644 --- a/contrib/archlinux/PKGBUILD.git +++ b/contrib/archlinux/PKGBUILD.git @@ -3,7 +3,7 @@ # Usage: makepkg -p PKGBUILD.git pkgname=ebusd-git _gitname=ebusd -pkgver=22.4 +pkgver=23.1 pkgrel=1 pkgdesc="ebusd, the daemon for communication with eBUS heating systems." arch=('i686' 'x86_64' 'armv6h' 'armv7h' 'aarch64') diff --git a/contrib/html/openapi.yaml b/contrib/html/openapi.yaml index c0c52845b..1050c7b3c 100644 --- a/contrib/html/openapi.yaml +++ b/contrib/html/openapi.yaml @@ -2,7 +2,7 @@ openapi: 3.1.0 info: title: ebusd-http description: The API that ebusd provides on HTTP port. - version: "22.4" + version: "23.1" servers: - url: http://127.0.0.1:8080/ paths: From de997ab30c4ad61da3b4a941c0aba2e4edb4adc5 Mon Sep 17 00:00:00 2001 From: john30 Date: Fri, 6 Jan 2023 14:14:48 +0100 Subject: [PATCH 052/345] fix new gh location, updated to 23.1 --- contrib/alpine/APKBUILD | 4 ++-- contrib/archlinux/PKGBUILD | 4 ++-- contrib/archlinux/PKGBUILD.git | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/contrib/alpine/APKBUILD b/contrib/alpine/APKBUILD index 4cb6a0cd7..937aa6c6e 100644 --- a/contrib/alpine/APKBUILD +++ b/contrib/alpine/APKBUILD @@ -9,7 +9,7 @@ url="https://github.com/john30/ebusd" arch="x86 x86_64 aarch64 armhf armv7" license="GPL-3.0-only" makedepends="argp-standalone cmake mosquitto-dev openssl-dev" -source="$pkgname-$pkgver.tar.gz::https://github.com/john30/ebusd/archive/refs/tags/v$pkgver.tar.gz" +source="$pkgname-$pkgver.tar.gz::https://github.com/john30/${pkgname}/archive/refs/tags/${pkgver}.tar.gz" build() { cmake -B build \ @@ -28,5 +28,5 @@ package() { } sha512sums=" -f625a8813eb5f844d1148eb9d683b9b730573e8c2bc1e3e2fec6462b3943340cfa1cfaf4cd50ff48b45aac47841189ad5d699316907b6abb6e7d1c20a0352842 ebusd-22.4.tar.gz +3cb1aab16aa4ad596138e48fb1df030f986f948a87db8608fd184b582077d86b818c5b18cfa59e5a538191f3aa25787dadcb3bc5fd325728cdab77371d86d719 ebusd-23.1.tar.gz " diff --git a/contrib/archlinux/PKGBUILD b/contrib/archlinux/PKGBUILD index 17112bd94..8533cd196 100644 --- a/contrib/archlinux/PKGBUILD +++ b/contrib/archlinux/PKGBUILD @@ -16,7 +16,7 @@ provides=('ebusd') install=ebusd.install options=() backup=('etc/conf.d/ebusd') -source=("https://github.com/john30/${pkgname}/archive/v${pkgver}.tar.gz") +source=("https://github.com/john30/${pkgname}/archive/refs/tags/${pkgver}.tar.gz") pkgver() { cat "${srcdir}/${pkgname}-${pkgver}/VERSION"|sed -e 's#-#_#g' @@ -41,4 +41,4 @@ package() { install -m 0644 contrib/etc/ebusd/mqtt-integration.cfg "${pkgdir}/etc/ebusd/mqtt-integration.cfg" } # update md5sums: updpkgsums -md5sums=('a3875319c4e8547b9ee54681ef8e136a') +md5sums=('b73c3adb9fc8594795a6f6b16b1f1db2') diff --git a/contrib/archlinux/PKGBUILD.git b/contrib/archlinux/PKGBUILD.git index f62971367..73aecca8f 100644 --- a/contrib/archlinux/PKGBUILD.git +++ b/contrib/archlinux/PKGBUILD.git @@ -43,7 +43,7 @@ package() { install -m 0644 contrib/archlinux/conf.d/ebusd "${pkgdir}/etc/conf.d/ebusd" install -d "${pkgdir}/etc/ebusd" install -m 0644 contrib/etc/ebusd/mqtt-hassio.cfg "${pkgdir}/etc/ebusd/mqtt-hassio.cfg" - install -m 0644 contrib/etc/ebusd/mqtt-integration.cfg "${pkgdir}/etc/ebusd/mqtt-integration.cfg + install -m 0644 contrib/etc/ebusd/mqtt-integration.cfg "${pkgdir}/etc/ebusd/mqtt-integration.cfg" } md5sums=('SKIP') From 02ce387e127bdd4ae5562dbaeb50e9bf10e1eb19 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 6 Jan 2023 14:49:27 +0100 Subject: [PATCH 053/345] fix new gh location --- contrib/docker/Dockerfile.release | 2 +- contrib/docker/update.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/docker/Dockerfile.release b/contrib/docker/Dockerfile.release index f43157320..e63142d33 100644 --- a/contrib/docker/Dockerfile.release +++ b/contrib/docker/Dockerfile.release @@ -43,7 +43,7 @@ ENV EBUSD_VERSION $EBUSD_VERSION LABEL version="${EBUSD_VERSION}-${EBUSD_ARCH}" -ADD https://github.com/john30/ebusd/releases/download/v${EBUSD_VERSION}/ebusd-${EBUSD_VERSION}_${TARGETARCH}${TARGETVARIANT}-${EBUSD_IMAGE}_mqtt1.deb ebusd.deb +ADD https://github.com/john30/ebusd/releases/download/${EBUSD_VERSION}/ebusd-${EBUSD_VERSION}_${TARGETARCH}${TARGETVARIANT}-${EBUSD_IMAGE}_mqtt1.deb ebusd.deb RUN dpkg -i "--path-exclude=/etc/default/*" "--path-exclude=/etc/init.d/*" "--path-exclude=/lib/systemd/*" ebusd.deb && rm -f ebusd.deb \ && update-ca-certificates \ diff --git a/contrib/docker/update.sh b/contrib/docker/update.sh index 626ae5eab..ce96a22be 100755 --- a/contrib/docker/update.sh +++ b/contrib/docker/update.sh @@ -26,7 +26,7 @@ replaceTemplate # release update version_variant='' make='GIT_REVISION=\$GIT_REVISION ./make_debian.sh' -copydeb="ADD https://github.com/john30/ebusd/releases/download/v\${EBUSD_VERSION}/ebusd-\${EBUSD_VERSION}_\${TARGETARCH}\${TARGETVARIANT}-\${EBUSD_IMAGE}_mqtt1.deb ebusd.deb" +copydeb="ADD https://github.com/john30/ebusd/releases/download/\${EBUSD_VERSION}/ebusd-\${EBUSD_VERSION}_\${TARGETARCH}\${TARGETVARIANT}-\${EBUSD_IMAGE}_mqtt1.deb ebusd.deb" copyentry='COPY contrib/docker/docker-entrypoint.sh /' namesuffix='.release' replaceTemplate From af614ae1e1d880493150e90b9746a393f3533c65 Mon Sep 17 00:00:00 2001 From: john30 Date: Fri, 6 Jan 2023 14:59:24 +0100 Subject: [PATCH 054/345] added current version --- .github/ISSUE_TEMPLATE/bug_report.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 5df83a3bf..f1ca65c1c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,7 @@ body: description: the ebusd version in use options: - current source from git + - '23.1' - '22.4' - '22.3' - '22.2' From fd47929f8ecd735c0edb5cdf57e3d6cf5829129e Mon Sep 17 00:00:00 2001 From: John Date: Mon, 30 Jan 2023 21:59:40 +0100 Subject: [PATCH 055/345] add getStep() method --- src/ebusd/mqtthandler.cpp | 2 +- src/lib/ebus/datatype.cpp | 4 ++++ src/lib/ebus/datatype.h | 8 ++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/ebusd/mqtthandler.cpp b/src/ebusd/mqtthandler.cpp index cdd0baa81..b465dd4da 100755 --- a/src/ebusd/mqtthandler.cpp +++ b/src/ebusd/mqtthandler.cpp @@ -1098,7 +1098,7 @@ void MqttHandler::run() { values.set("max", ostr.str()); ostr.str(""); } - if (dt->readFromRawValue(1, g_publishFormat, &ostr) != RESULT_OK) { + if (dt->getStep(g_publishFormat, &ostr) != RESULT_OK) { // fallback method, when smallest number didn't work int divisor = dt->getDivisor(); float step = 1.0f; diff --git a/src/lib/ebus/datatype.cpp b/src/lib/ebus/datatype.cpp index 86146348e..92f5e22b1 100755 --- a/src/lib/ebus/datatype.cpp +++ b/src/lib/ebus/datatype.cpp @@ -735,6 +735,10 @@ result_t NumberDataType::getMinMax(bool getMax, const OutputFormat outputFormat, return readFromRawValue(getMax ? m_maxValue : m_minValue, outputFormat, output); } +result_t NumberDataType::getStep(const OutputFormat outputFormat, ostream* output) const { + return readFromRawValue(hasFlag(EXP) ? floatToUint(1.0f) : 1, outputFormat, output); +} + result_t NumberDataType::readRawValue(size_t offset, size_t length, const SymbolString& input, unsigned int* value) const { size_t start = 0, count = length; diff --git a/src/lib/ebus/datatype.h b/src/lib/ebus/datatype.h index 388a4126d..34ac913e9 100755 --- a/src/lib/ebus/datatype.h +++ b/src/lib/ebus/datatype.h @@ -517,6 +517,14 @@ class NumberDataType : public DataType { */ result_t getMinMax(bool getMax, const OutputFormat outputFormat, ostream* output) const; + /** + * Get the smallest step value for increment/decrement. + * @param outputFormat the @a OutputFormat options to use. + * @param output the ostream to append the formatted value to. + * @return @a RESULT_OK on success, or an error code. + */ + result_t getStep(const OutputFormat outputFormat, ostream* output) const; + /** * @return the divisor (negative for reciprocal). */ From d35d0bb71de65eafa5275a29d9dbddbefaaa19c8 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 30 Jan 2023 22:07:14 +0100 Subject: [PATCH 056/345] don't set min/max/step values for EXP based types (workaround for #803) --- src/ebusd/mqtthandler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ebusd/mqtthandler.cpp b/src/ebusd/mqtthandler.cpp index b465dd4da..e51d6f172 100755 --- a/src/ebusd/mqtthandler.cpp +++ b/src/ebusd/mqtthandler.cpp @@ -1087,7 +1087,7 @@ void MqttHandler::run() { values.set("basetype", dataType->getId()); values.set("comment", field->getAttribute("comment")); values.set("unit", field->getAttribute("unit")); - if (dataType->isNumeric()) { + if (dataType->isNumeric() && !dataType->hasFlag(EXP)) { auto dt = dynamic_cast(dataType); ostr.str(""); if (dt->getMinMax(false, g_publishFormat, &ostr) == RESULT_OK) { From 3b6d5402c9912ab7288839d4ea1c51ad50810ad1 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 30 Jan 2023 22:21:51 +0100 Subject: [PATCH 057/345] note high-speed mode in help and logging (fixes #804) --- src/tools/README.md | 11 ++++++----- src/tools/ebuspicloader.cpp | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/tools/README.md b/src/tools/README.md index 938b6675e..2584cbe2f 100644 --- a/src/tools/README.md +++ b/src/tools/README.md @@ -49,10 +49,11 @@ settings. -o, --pingoff disable visual ping -p, --pingon enable visual ping (default) -r, --reset reset the device at the end on success - --variant=VARIANT set the VARIANT to U=USB/RPI, W=WIFI, E=Ethernet, - N=non-enhanced USB/RPI/WIFI, F=non-enhanced - Ethernet (lowercase to allow hardware jumpers, - default "u", since firmware 20221206) + --variant=VARIANT set the VARIANT to U=USB/RPI (high-speed), W=WIFI, + E=Ethernet, N=non-enhanced USB/RPI/WIFI, + F=non-enhanced Ethernet (lowercase to allow + hardware jumpers, default "u", since firmware + 20221206) Tool options: -s, --slow use low speed for transfer @@ -82,7 +83,7 @@ MAC address: ae:b0:53:26:15:80 IP address: DHCP (default) Arbitration delay: 200 us (default) Visual ping: on (default) -Variant: USB/RPI, allow hardware jumpers (default) +Variant: USB/RPI (high-speed), allow hardware jumpers (default) New firmware version: 1 [7f16] erasing flash: done. diff --git a/src/tools/ebuspicloader.cpp b/src/tools/ebuspicloader.cpp index 20fe849c9..ec50b8c00 100644 --- a/src/tools/ebuspicloader.cpp +++ b/src/tools/ebuspicloader.cpp @@ -73,7 +73,7 @@ static const struct argp_option argpoptions[] = { {nullptr, 0, nullptr, 0, "PIC options:", 3 }, {"pingon", 'p', nullptr, 0, "enable visual ping (default)", 0 }, {"pingoff", 'o', nullptr, 0, "disable visual ping", 0 }, - {"variant", -3, "VARIANT", 0, "set the VARIANT to U=USB/RPI, W=WIFI, E=Ethernet," + {"variant", -3, "VARIANT", 0, "set the VARIANT to U=USB/RPI (high-speed), W=WIFI, E=Ethernet," " N=non-enhanced USB/RPI/WIFI, F=non-enhanced Ethernet" " (lowercase to allow hardware jumpers, default \"u\"" ", since firmware 20221206)", 0 }, @@ -1093,7 +1093,7 @@ int readSettings(int fd, uint8_t* currentData = nullptr) { std::cout << "Variant: "; // since firmware 20221206 switch (configData[5]&0x03) { case 3: - std::cout << "USB/RPI"; + std::cout << "USB/RPI (high-speed)"; break; case 2: std::cout << "WIFI"; From 9218a1d8e8ae4de3781ff5fc8c1f3319a3d884f5 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 30 Jan 2023 22:32:32 +0100 Subject: [PATCH 058/345] better wording --- src/tools/README.md | 5 +++-- src/tools/ebuspicloader.cpp | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/tools/README.md b/src/tools/README.md index 2584cbe2f..8c75ac8ba 100644 --- a/src/tools/README.md +++ b/src/tools/README.md @@ -17,7 +17,7 @@ If Cygwin is already installed, only the `ebuspicloader.exe` needs to be extract from within a Cygwin shell. In Cygwin, Windows COM ports are mapped under `/dev/ttyS*`, e.g. `COM1` would be `/dev/ttyS0`. -This tool is an alternative to the MPLAB bootloader host application that produces a lot +This tool is an alternative to and extension of the MPLAB bootloader host application that produces a lot of unreadable output. @@ -56,7 +56,8 @@ settings. 20221206) Tool options: - -s, --slow use low speed for transfer + -s, --slow low speed mode for transfer (115kBd instead of + 921kBd) -v, --verbose enable verbose output -?, --help give this help list diff --git a/src/tools/ebuspicloader.cpp b/src/tools/ebuspicloader.cpp index ec50b8c00..894628fb2 100644 --- a/src/tools/ebuspicloader.cpp +++ b/src/tools/ebuspicloader.cpp @@ -81,7 +81,7 @@ static const struct argp_option argpoptions[] = { {"reset", 'r', nullptr, 0, "reset the device at the end on success", 0 }, {nullptr, 0, nullptr, 0, "Tool options:", 9 }, {"verbose", 'v', nullptr, 0, "enable verbose output", 0 }, - {"slow", 's', nullptr, 0, "use low speed for transfer", 0 }, + {"slow", 's', nullptr, 0, "low speed mode for transfer (115kBd instead of 921kBd)", 0 }, {nullptr, 0, nullptr, 0, nullptr, 0 }, }; From 9c0af7b95e9607731819b05d2fd64048833484cb Mon Sep 17 00:00:00 2001 From: John Date: Mon, 30 Jan 2023 22:55:46 +0100 Subject: [PATCH 059/345] fix for variable datatype length bounds check (fixes #805) --- src/lib/ebus/message.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/ebus/message.cpp b/src/lib/ebus/message.cpp index 36d009fed..7bd4eaa3f 100644 --- a/src/lib/ebus/message.cpp +++ b/src/lib/ebus/message.cpp @@ -465,7 +465,7 @@ result_t Message::create(const string& filename, const DataFieldTemplates* templ return result; } } - if (id.size() + data->getLength(pt_masterData, maxLength) > 2 + maxLength + if (id.size() + data->getLength(pt_masterData, maxLength==MAX_POS ? MAX_POS-id.size() : maxLength) > 2 + maxLength || data->getLength(pt_slaveData, maxLength) > maxLength) { // max NN exceeded delete data; From 1895339e4514a3fbb97f46cdd41170ca3f81ad75 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 16 Apr 2023 15:04:20 +0200 Subject: [PATCH 060/345] add timeout for info request and allow more being optional, support shorter config response, comments --- src/lib/ebus/device.cpp | 67 ++++++++++++++++++++++++++++++----------- src/lib/ebus/device.h | 3 ++ 2 files changed, 53 insertions(+), 17 deletions(-) diff --git a/src/lib/ebus/device.cpp b/src/lib/ebus/device.cpp index 7b51d1afa..919be0164 100755 --- a/src/lib/ebus/device.cpp +++ b/src/lib/ebus/device.cpp @@ -82,7 +82,7 @@ Device::Device(const char* name, bool checkDevice, unsigned int latency, bool re m_initialSend(initialSend), m_enhancedProto(enhancedProto), m_fd(-1), m_resetRequested(false), m_listener(nullptr), m_arbitrationMaster(SYN), m_arbitrationCheck(0), m_bufSize(((MAX_LEN+1+3)/4)*4), m_bufLen(0), m_bufPos(0), - m_extraFatures(0), m_infoId(0xff), m_infoLen(0), m_infoPos(0) { + m_extraFatures(0), m_infoId(0xff), m_infoReqTime(0), m_infoLen(0), m_infoPos(0) { m_buffer = reinterpret_cast(malloc(m_bufSize)); if (!m_buffer) { m_bufSize = 0; @@ -194,7 +194,16 @@ result_t Device::requestEnhancedInfo(symbol_t infoId) { usleep(40000 + i*40000); } if (m_infoId != 0xff) { - return RESULT_ERR_DUPLICATE; + if (m_infoReqTime > 0 && time(NULL) > m_infoReqTime+5) { + // request timed out + if (m_listener != nullptr) { + m_listener->notifyStatus(false, "info request timed out"); + } + m_infoId = 0xff; + m_infoReqTime = 0; + } else { + return RESULT_ERR_DUPLICATE; + } } if (infoId == 0xff) { // just waited for completion @@ -214,6 +223,7 @@ result_t Device::sendEnhancedInfoRequest(symbol_t infoId) { } m_infoPos = 0; m_infoId = infoId; + time(&m_infoReqTime); return RESULT_OK; } @@ -222,6 +232,7 @@ string Device::getEnhancedInfos() { return ""; } result_t res; + string fails = ""; if (m_enhInfoTemperature.empty()) { // use empty temperature for potential refresh after reset res = requestEnhancedInfo(0); if (res != RESULT_OK) { @@ -233,7 +244,10 @@ string Device::getEnhancedInfos() { } res = requestEnhancedInfo(2); if (res != RESULT_OK) { - return "cannot request config"; + fails += ", cannot request config"; + requestEnhancedInfo(0xff); // wait for completion + m_infoPos = 0; + m_infoId = 0xff; } } res = requestEnhancedInfo(6); @@ -250,11 +264,13 @@ string Device::getEnhancedInfos() { } res = requestEnhancedInfo(5); if (res != RESULT_OK) { - return "cannot request bus voltage"; + fails += ", cannot request bus voltage"; } - res = requestEnhancedInfo(0xff); + res = requestEnhancedInfo(0xff); // wait for completion if (res != RESULT_OK) { m_enhInfoBusVoltage = "bus voltage unknown"; + m_infoPos = 0; + m_infoId = 0xff; } return "firmware " + m_enhInfoVersion + ", " + m_enhInfoTemperature + ", " + m_enhInfoSupplyVoltage + ", " + m_enhInfoBusVoltage; @@ -493,18 +509,23 @@ bool Device::available() { // drop first byte of invalid sequence m_bufPos = (m_bufPos + 1) % m_bufSize; m_bufLen--; - pos--; + pos--; // check same pos again continue; } - if (cmd != ENH_RES_RECEIVED && cmd != ENH_RES_STARTED && cmd != ENH_RES_FAILED) { - pos++; - continue; + if (cmd == ENH_RES_RECEIVED || cmd == ENH_RES_STARTED || cmd == ENH_RES_FAILED) { + // found a sequence that yields in available bus byte +#ifdef DEBUG_RAW_TRAFFIC + fprintf(stdout, "raw avail enhanced @%d+%d %2.2x %2.2x\n", m_bufPos, pos, m_buffer[(pos+m_bufPos)%m_bufSize], ch); + fflush(stdout); +#endif + return true; } #ifdef DEBUG_RAW_TRAFFIC - fprintf(stdout, "raw avail enhanced @%d+%d %2.2x %2.2x\n", m_bufPos, pos, m_buffer[(pos+m_bufPos)%m_bufSize], ch); + fprintf(stdout, "raw avail enhanced skip cmd %d @%d+%d %2.2x\n", cmd, m_bufPos, pos, ch); fflush(stdout); #endif - return true; + pos++; // skip enhanced sequence of 2 bytes + continue; } #ifdef DEBUG_RAW_TRAFFIC fprintf(stdout, "raw avail enhanced bad @%d+%d %2.2x\n", m_bufPos, pos, ch); @@ -516,7 +537,7 @@ bool Device::available() { // skip byte from erroneous protocol m_bufPos = (m_bufPos+1)%m_bufSize; m_bufLen--; - pos--; + pos--; // check byte 2 again from scratch and allow as byte 1 } return false; } @@ -710,6 +731,7 @@ bool Device::handleEnhancedBufferedData(symbol_t* value, ArbitrationState* arbit break; case 0x0901: case 0x0802: + case 0x0302: stream << (m_infoId == 1 ? "ID" : "config"); stream << std::hex << std::setfill('0'); for (uint8_t pos = 0; pos < m_infoPos; pos++) { @@ -727,14 +749,24 @@ bool Device::handleEnhancedBufferedData(symbol_t* value, ArbitrationState* arbit m_enhInfoTemperature = stream.str(); break; case 0x0204: - val = (static_cast(m_infoBuf[0]) << 8) | static_cast(m_infoBuf[1]); - stream << "supply voltage " << static_cast(val) << " mV"; + stream << "supply voltage "; + if (m_infoBuf[0] | m_infoBuf[1]) { + val = (static_cast(m_infoBuf[0]) << 8) | static_cast(m_infoBuf[1]); + stream << static_cast(val) << " mV"; + } else { + stream << "unknown"; + } m_enhInfoSupplyVoltage = stream.str(); break; case 0x0205: - stream << "bus voltage " << std::fixed << std::setprecision(1) - << static_cast(m_infoBuf[1] / 10.0) << " V - " - << static_cast(m_infoBuf[0] / 10.0) << " V"; + stream << "bus voltage "; + if (m_infoBuf[0] | m_infoBuf[1]) { + stream << std::fixed << std::setprecision(1) + << static_cast(m_infoBuf[1] / 10.0) << " V - " + << static_cast(m_infoBuf[0] / 10.0) << " V"; + } else { + stream << "unknown"; + } m_enhInfoBusVoltage = stream.str(); break; case 0x0206: @@ -864,6 +896,7 @@ result_t SerialDevice::open() { newSettings.c_cc[VMIN] = 1; newSettings.c_cc[VTIME] = 0; + // int flag = TIOCM_RTS|TIOCM_DTR; // empty device buffer tcflush(m_fd, TCIFLUSH); diff --git a/src/lib/ebus/device.h b/src/lib/ebus/device.h index f28ec09b5..a5c31615a 100755 --- a/src/lib/ebus/device.h +++ b/src/lib/ebus/device.h @@ -342,6 +342,9 @@ class Device { /** the ID of the last requested info. */ symbol_t m_infoId; + /** the time of the last info request. */ + time_t m_infoReqTime; + /** the info buffer expected length. */ size_t m_infoLen; From 4886b016f31995dcf60a9be44921c29f522df52b Mon Sep 17 00:00:00 2001 From: John Date: Thu, 18 May 2023 12:03:09 +0200 Subject: [PATCH 061/345] log if a tcp server could not be started --- src/ebusd/network.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ebusd/network.cpp b/src/ebusd/network.cpp index 6bc27bd03..0a51f2fcc 100644 --- a/src/ebusd/network.cpp +++ b/src/ebusd/network.cpp @@ -199,10 +199,14 @@ Network::Network(const bool local, const uint16_t port, const uint16_t httpPort, if (m_tcpServer != nullptr && m_tcpServer->start() == 0) { m_listening = true; + } else { + logError(lf_network, "unable to start TCP server on port %d: error %d", port, errno); } if (httpPort > 0) { m_httpServer = new TCPServer(httpPort, "0.0.0.0"); - m_httpServer->start(); + if (m_httpServer->start() != 0) { + logError(lf_network, "unable to start HTTP server on port %d: error %d", httpPort, errno); + } } else { m_httpServer = nullptr; } From ec6dae6abb240c4bc90624909f21ed5eaad63ef2 Mon Sep 17 00:00:00 2001 From: John Date: Thu, 18 May 2023 12:04:42 +0200 Subject: [PATCH 062/345] allow late init of ssl --- src/lib/utils/httpclient.cpp | 1 + src/lib/utils/httpclient.h | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lib/utils/httpclient.cpp b/src/lib/utils/httpclient.cpp index 10b918ce0..7460e2927 100755 --- a/src/lib/utils/httpclient.cpp +++ b/src/lib/utils/httpclient.cpp @@ -316,6 +316,7 @@ bool HttpClient::parseUrl(const string& url, string* proto, string* host, uint16 bool HttpClient::connect(const string& host, const uint16_t port, bool https, const string& userAgent, const int timeout) { + initialize(); disconnect(); #ifdef HAVE_SSL m_socket = SSLSocket::connect(host, port, https, timeout, m_caFile, m_caPath); diff --git a/src/lib/utils/httpclient.h b/src/lib/utils/httpclient.h index 3c26a463b..d8f13603f 100755 --- a/src/lib/utils/httpclient.h +++ b/src/lib/utils/httpclient.h @@ -124,8 +124,9 @@ class HttpClient { * Constructor. * @param caFile the CA file to use (uses defaults if neither caFile nor caPath are set), or "#" for insecure. * @param caPath the path with CA files to use (uses defaults if neither caFile nor caPath are set). + * @param init whether to immediately initialize the library (instead of during connect()). */ - explicit HttpClient(const char* caFile = nullptr, const char* caPath = nullptr) : + explicit HttpClient(const char* caFile = nullptr, const char* caPath = nullptr, bool init = true) : #ifdef HAVE_SSL m_https(false), m_caFile(caFile), @@ -133,7 +134,9 @@ class HttpClient { #endif m_socket(nullptr), m_port(0), m_timeout(0), m_bufferSize(0), m_buffer(nullptr) { - initialize(); + if (init) { + initialize(); + } } /** From e419c950d850ade0e2c085606825a6be1aa7d772 Mon Sep 17 00:00:00 2001 From: John Date: Thu, 18 May 2023 12:24:50 +0200 Subject: [PATCH 063/345] separate resolving+scanning to own interface+class and avoid use of extern --- src/ebusd/CMakeLists.txt | 1 + src/ebusd/bushandler.cpp | 5 +- src/ebusd/bushandler.h | 9 +- src/ebusd/main.cpp | 581 ++--------------------------- src/ebusd/main.h | 51 --- src/ebusd/mainloop.cpp | 25 +- src/ebusd/mainloop.h | 7 +- src/ebusd/scan.cpp | 510 +++++++++++++++++++++++++ src/ebusd/scan.h | 205 ++++++++++ src/lib/ebus/message.cpp | 16 +- src/lib/ebus/message.h | 53 ++- src/lib/ebus/test/test_message.cpp | 37 +- 12 files changed, 860 insertions(+), 640 deletions(-) create mode 100644 src/ebusd/scan.cpp create mode 100644 src/ebusd/scan.h diff --git a/src/ebusd/CMakeLists.txt b/src/ebusd/CMakeLists.txt index 325663c23..95eb8933f 100644 --- a/src/ebusd/CMakeLists.txt +++ b/src/ebusd/CMakeLists.txt @@ -5,6 +5,7 @@ set(ebusd_SOURCES datahandler.h datahandler.cpp network.h network.cpp mainloop.h mainloop.cpp + scan.h scan.cpp main.h main.cpp ) diff --git a/src/ebusd/bushandler.cpp b/src/ebusd/bushandler.cpp index 524a3e2c0..ded986d7e 100644 --- a/src/ebusd/bushandler.cpp +++ b/src/ebusd/bushandler.cpp @@ -22,7 +22,6 @@ #include "ebusd/bushandler.h" #include -#include "ebusd/main.h" #include "lib/utils/log.h" namespace ebusd { @@ -1637,14 +1636,14 @@ result_t BusHandler::scanAndWait(symbol_t dstAddress, bool loadScanConfig, bool bool timedOut = result == RESULT_ERR_TIMEOUT; bool loadFailed = false; if (timedOut || result == RESULT_OK) { - result = loadScanConfigFile(m_messages, dstAddress, false, &file); // try to load even if one message timed out + result = m_scanHelper->loadScanConfigFile(dstAddress, &file); // try to load even if one message timed out loadFailed = result != RESULT_OK; if (timedOut && loadFailed) { result = RESULT_ERR_TIMEOUT; // back to previous result } } if (result == RESULT_OK) { - executeInstructions(m_messages); + m_scanHelper->executeInstructions(this); setScanConfigLoaded(dstAddress, file); if (!hasAdditionalScanMessages && m_messages->hasAdditionalScanMessages()) { // additional scan messages now available diff --git a/src/ebusd/bushandler.h b/src/ebusd/bushandler.h index e9a9e7d31..a54eb1ec3 100755 --- a/src/ebusd/bushandler.h +++ b/src/ebusd/bushandler.h @@ -25,6 +25,7 @@ #include #include #include +#include "ebusd/scan.h" #include "lib/ebus/message.h" #include "lib/ebus/data.h" #include "lib/ebus/symbol.h" @@ -367,6 +368,7 @@ class BusHandler : public WaitThread { * Construct a new instance. * @param device the @a Device instance for accessing the bus. * @param messages the @a MessageMap instance with all known @a Message instances. + * @param scanHelper the @a ScanHelper instance. * @param ownAddress the own master address. * @param answer whether to answer queries for the own master/slave address. * @param busLostRetries the number of times a send is repeated due to lost arbitration. @@ -377,13 +379,13 @@ class BusHandler : public WaitThread { * @param generateSyn whether to enable AUTO-SYN symbol generation. * @param pollInterval the interval in seconds in which poll messages are cycled, or 0 if disabled. */ - BusHandler(Device* device, MessageMap* messages, + BusHandler(Device* device, MessageMap* messages, ScanHelper* scanHelper, symbol_t ownAddress, bool answer, unsigned int busLostRetries, unsigned int failedSendRetries, unsigned int busAcquireTimeout, unsigned int slaveRecvTimeout, unsigned int lockCount, bool generateSyn, unsigned int pollInterval) - : WaitThread(), m_device(device), m_reconnect(false), m_messages(messages), + : WaitThread(), m_device(device), m_reconnect(false), m_messages(messages), m_scanHelper(scanHelper), m_ownMasterAddress(ownAddress), m_ownSlaveAddress(getSlaveAddress(ownAddress)), m_answer(answer), m_addressConflict(false), m_busLostRetries(busLostRetries), m_failedSendRetries(failedSendRetries), @@ -676,6 +678,9 @@ class BusHandler : public WaitThread { /** the @a MessageMap instance with all known @a Message instances. */ MessageMap* m_messages; + /** the @a ScanHelper instance. */ + ScanHelper* m_scanHelper; + /** the own master address. */ const symbol_t m_ownMasterAddress; diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index 52611beab..9e5b7b6e1 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -33,6 +33,7 @@ #include "ebusd/mainloop.h" #include "lib/utils/log.h" #include "lib/utils/httpclient.h" +#include "ebusd/scan.h" /** the version string of the program. */ @@ -139,24 +140,15 @@ static struct options s_opt = { /** the @a MessageMap instance, or nullptr. */ static MessageMap* s_messageMap = nullptr; +/** the @a ScanHelper instance, or nullptr. */ +static ScanHelper* s_scanHelper = nullptr; + /** the @a MainLoop instance, or nullptr. */ static MainLoop* s_mainLoop = nullptr; /** the (optionally corrected) config path for retrieving configuration files from. */ static string s_configPath = CONFIG_PATH; -/** the path prefix (including trailing "/") for retrieving configuration files from local files (empty for HTTPS). */ -static string s_configLocalPrefix = ""; - -/** the URI prefix (including trailing "/") for retrieving configuration files from HTTPS (empty for local files). */ -static string s_configUriPrefix = ""; - -/** the optional language query part for retrieving configuration files from HTTPS (empty for local files). */ -static string s_configLangQuery = ""; - -/** the @a HttpClient for retrieving configuration files from HTTPS. */ -static HttpClient* s_configHttpClient = nullptr; - /** the documentation of the program. */ static const char argpdoc[] = "A daemon for communication with eBUS heating systems."; @@ -278,15 +270,6 @@ static const struct argp_option argpoptions[] = { {nullptr, 0, nullptr, 0, nullptr, 0 }, }; -/** the global @a DataFieldTemplates. */ -static DataFieldTemplates s_globalTemplates; - -/** - * the loaded @a DataFieldTemplates by relative path (may also carry - * @a globalTemplates as replacement for missing file). - */ -static map s_templatesByPath; - /** * The program argument parsing function. * @param key the key from @a argpoptions. @@ -760,16 +743,9 @@ void cleanup() { delete s_messageMap; s_messageMap = nullptr; } - // free templates - for (const auto& it : s_templatesByPath) { - if (it.second != &s_globalTemplates) { - delete it.second; - } - } - s_templatesByPath.clear(); - if (s_configHttpClient) { - delete s_configHttpClient; - s_configHttpClient = nullptr; + if (s_scanHelper) { + delete s_scanHelper; + s_scanHelper = nullptr; } } @@ -833,503 +809,6 @@ void signalHandler(int sig) { } } -/** - * Lazy create the s_configHttpClient if not already done. - * @return true (always). - */ -bool lazyHttpClient() { - if (!s_configHttpClient) { - s_configHttpClient = new HttpClient(s_opt.caFile, s_opt.caPath); - } - return true; -} - -/** - * Collect configuration files matching the prefix and extension from the specified path. - * @param relPath the relative path from which to collect the files (without trailing "/"). - * @param prefix the filename prefix the files have to match, or empty. - * @param extension the filename extension the files have to match. - * @param files the @a vector to which to add the matching files. - * @param query the query string suffix for HTTPS retrieval starting with "&", or empty. - * @param dirs the @a vector to which to add found directories (without any name check), or nullptr to ignore. - * @param hasTemplates the bool to set when the templates file was found in the path, or nullptr to ignore. - * @return the result code. - */ -static result_t collectConfigFiles(const string& relPath, const string& prefix, const string& extension, - vector* files, - bool ignoreAddressPrefix = false, const string& query = "", - vector* dirs = nullptr, bool* hasTemplates = nullptr) { - const string relPathWithSlash = relPath.empty() ? "" : relPath + "/"; - if (!s_configUriPrefix.empty()) { - string uri = s_configUriPrefix + relPathWithSlash + s_configLangQuery + (s_configLangQuery.empty() ? "?" : "&") - + "t=" + extension.substr(1) + query; - string names; - if (!lazyHttpClient() || !s_configHttpClient->get(uri, "", &names)) { - return RESULT_ERR_NOTFOUND; - } - istringstream stream(names); - string name; - while (getline(stream, name)) { - if (name.empty()) { - continue; - } - if (name == "_templates"+extension) { - if (hasTemplates) { - *hasTemplates = true; - } - continue; - } - if (prefix.length() == 0 ? (!ignoreAddressPrefix || name.length() < 3 || name.find_first_of('.') != 2) - : (name.length() >= prefix.length() && name.substr(0, prefix.length()) == prefix)) { - files->push_back(relPathWithSlash + name); - } - } - return RESULT_OK; - } - const string path = s_configLocalPrefix + relPathWithSlash; - logDebug(lf_main, "reading directory %s", path.c_str()); - DIR* dir = opendir(path.c_str()); - if (dir == nullptr) { - return RESULT_ERR_NOTFOUND; - } - dirent* d; - while ((d = readdir(dir)) != nullptr) { - string name = d->d_name; - if (name == "." || name == "..") { - continue; - } - const string p = path + name; - struct stat stat_buf = {}; - if (stat(p.c_str(), &stat_buf) != 0) { - logError(lf_main, "unable to stat file %s", p.c_str()); - continue; - } - logDebug(lf_main, "file type of %s is %s", p.c_str(), - S_ISDIR(stat_buf.st_mode) ? "dir" : S_ISREG(stat_buf.st_mode) ? "file" : "other"); - if (S_ISDIR(stat_buf.st_mode)) { - if (dirs != nullptr) { - dirs->push_back(relPathWithSlash + name); - } - } else if (S_ISREG(stat_buf.st_mode) && name.length() >= extension.length() - && name.substr(name.length()-extension.length()) == extension) { - if (name == "_templates"+extension) { - if (hasTemplates) { - *hasTemplates = true; - } - continue; - } - if (prefix.length() == 0 ? (!ignoreAddressPrefix || name.length() < 3 || name.find_first_of('.') != 2) - : (name.length() >= prefix.length() && name.substr(0, prefix.length()) == prefix)) { - files->push_back(relPathWithSlash + name); - } - } - } - closedir(dir); - - return RESULT_OK; -} - -DataFieldTemplates* getTemplates(const string& filename) { - if (filename == "*") { - unsigned long maxLength = 0; - DataFieldTemplates* best = nullptr; - for (auto it : s_templatesByPath) { - if (it.first.size() > maxLength) { - best = it.second; - } - } - if (best) { - return best; - } - } else { - string path; - size_t pos = filename.find_last_of('/'); - if (pos != string::npos) { - path = filename.substr(0, pos); - } - const auto it = s_templatesByPath.find(path); - if (it != s_templatesByPath.end()) { - return it->second; - } - } - return &s_globalTemplates; -} - -/** - * Read the @a DataFieldTemplates for the specified path if necessary. - * @param relPath the relative path from which to read the files (without trailing "/"). - * @param extension the filename extension of the files to read. - * @param available whether the templates file is available in the path. - * @param verbose whether to verbosely log problems. - * @return false when the templates for the path were already loaded before, true when the templates for the path were added (independent from @a available). - * @return the @a DataFieldTemplates. - */ -static bool readTemplates(const string relPath, const string extension, bool available, bool verbose = false) { - const auto it = s_templatesByPath.find(relPath); - if (it != s_templatesByPath.end()) { - return false; - } - DataFieldTemplates* templates; - if (relPath.empty() || !available) { - templates = &s_globalTemplates; - } else { - templates = new DataFieldTemplates(s_globalTemplates); - } - s_templatesByPath[relPath] = templates; - if (!available) { - // global templates are stored as replacement in order to determine whether the directory was already loaded - return true; - } - string errorDescription; - string logPath = relPath.empty() ? "/" : relPath; - logInfo(lf_main, "reading templates %s", logPath.c_str()); - string file = (relPath.empty() ? "" : relPath + "/") + "_templates" + extension; - result_t result = loadDefinitionsFromConfigPath(templates, file, verbose, nullptr, &errorDescription, true); - if (result == RESULT_OK) { - logInfo(lf_main, "read templates in %s", logPath.c_str()); - return true; - } - logError(lf_main, "error reading templates in %s: %s, last error: %s", logPath.c_str(), getResultCode(result), - errorDescription.c_str()); - return false; -} - -/** - * Read the configuration files from the specified path. - * @param relPath the relative path from which to read the files (without trailing "/"). - * @param extension the filename extension of the files to read. - * @param messages the @a MessageMap to load the messages into. - * @param recursive whether to load all files recursively. - * @param verbose whether to verbosely log problems. - * @param errorDescription a string in which to store the error description in case of error. - * @return the result code. - */ -static result_t readConfigFiles(const string& relPath, const string& extension, bool recursive, - bool verbose, string* errorDescription, MessageMap* messages) { - vector files, dirs; - bool hasTemplates = false; - result_t result = collectConfigFiles(relPath, "", extension, &files, false, "", &dirs, &hasTemplates); - if (result != RESULT_OK) { - return result; - } - readTemplates(relPath, extension, hasTemplates, verbose); - for (const auto& name : files) { - logInfo(lf_main, "reading file %s", name.c_str()); - result = loadDefinitionsFromConfigPath(messages, name, verbose, nullptr, errorDescription); - if (result != RESULT_OK) { - return result; - } - logInfo(lf_main, "successfully read file %s", name.c_str()); - } - if (recursive) { - for (const auto& name : dirs) { - logInfo(lf_main, "reading dir %s", name.c_str()); - result = readConfigFiles(name, extension, true, verbose, errorDescription, messages); - if (result != RESULT_OK) { - return result; - } - logInfo(lf_main, "successfully read dir %s", name.c_str()); - } - } - return RESULT_OK; -} - -/** - * Helper method for immediate reading of a @a Message from the bus. - * @param message the @a Message to read. - */ -void readMessage(Message* message) { - if (!s_mainLoop || !message) { - return; - } - BusHandler* busHandler = s_mainLoop->getBusHandler(); - result_t result = busHandler->readFromBus(message, ""); - if (result != RESULT_OK) { - logError(lf_main, "error reading message %s %s: %s", message->getCircuit().c_str(), message->getName().c_str(), - getResultCode(result)); - } -} - -result_t executeInstructions(MessageMap* messages, bool verbose) { - string errorDescription; - result_t result = messages->resolveConditions(verbose, &errorDescription); - if (result != RESULT_OK) { - logError(lf_main, "error resolving conditions: %s, last error: %s", getResultCode(result), - errorDescription.c_str()); - } - ostringstream log; - result = messages->executeInstructions(readMessage, &log); - if (result != RESULT_OK) { - logError(lf_main, "error executing instructions: %s, last error: %s", getResultCode(result), - log.str().c_str()); - } else if (verbose && log.tellp() > 0) { - logInfo(lf_main, log.str().c_str()); - } - logNotice(lf_main, "found messages: %d (%d conditional on %d conditions, %d poll, %d update)", messages->size(), - messages->sizeConditional(), messages->sizeConditions(), messages->sizePoll(), messages->sizePassive()); - return result; -} - -result_t loadDefinitionsFromConfigPath(FileReader* reader, const string& filename, bool verbose, - map* defaults, string* errorDescription, bool replace) { - istream* stream = nullptr; - time_t mtime = 0; - if (s_configUriPrefix.empty()) { - stream = FileReader::openFile(s_configLocalPrefix + filename, errorDescription, &mtime); - } else { - string content; - if (lazyHttpClient() - && s_configHttpClient->get(s_configUriPrefix + filename + s_configLangQuery, "", &content, &mtime)) { - stream = new istringstream(content); - } - } - result_t result; - if (stream) { - result = reader->readFromStream(stream, filename, mtime, verbose, defaults, errorDescription, replace); - delete(stream); - } else { - result = RESULT_ERR_NOTFOUND; - } - return result; -} - -result_t loadConfigFiles(MessageMap* messages, bool verbose, bool denyRecursive) { - logInfo(lf_main, "loading configuration files from %s", s_configPath.c_str()); - messages->lock(); - messages->clear(); - s_globalTemplates.clear(); - for (auto& it : s_templatesByPath) { - if (it.second != &s_globalTemplates) { - delete it.second; - } - it.second = nullptr; - } - s_templatesByPath.clear(); - - string errorDescription; - result_t result = readConfigFiles("", ".csv", - (!s_opt.scanConfig || s_opt.checkConfig) && !denyRecursive, verbose, &errorDescription, messages); - if (result == RESULT_OK) { - logInfo(lf_main, "read config files, got %d messages", messages->size()); - } else { - logError(lf_main, "error reading config files from %s: %s, last error: %s", s_configPath.c_str(), - getResultCode(result), errorDescription.c_str()); - } - messages->unlock(); - return s_opt.checkConfig ? result : RESULT_OK; -} - -result_t loadScanConfigFile(MessageMap* messages, symbol_t address, bool verbose, string* relativeFile) { - Message* message = messages->getScanMessage(address); - if (!message || message->getLastUpdateTime() == 0) { - return RESULT_ERR_NOTFOUND; - } - const SlaveSymbolString& data = message->getLastSlaveData(); - if (data.getDataSize() < 1+5+2+2) { - logError(lf_main, "unable to load scan config %2.2x: slave part too short (%d)", address, data.getDataSize()); - return RESULT_EMPTY; - } - DataFieldSet* identFields = DataFieldSet::getIdentFields(); - string manufStr, addrStr, ident; // path: cfgpath/MANUFACTURER, prefix: ZZ., ident: C[C[C[C[C]]]], SW: xxxx, HW: xxxx - unsigned int sw = 0, hw = 0; - ostringstream out; - size_t offset = 0; - size_t field = 0; - bool fromLocal = s_configUriPrefix.empty(); - // manufacturer name - result_t result = (*identFields)[field]->read(data, offset, false, nullptr, -1, OF_NONE, -1, &out); - if (result == RESULT_ERR_NOTFOUND && fromLocal) { - result = (*identFields)[field]->read(data, offset, false, nullptr, -1, OF_NUMERIC, -1, &out); // manufacturer name - } - if (result == RESULT_OK) { - manufStr = out.str(); - transform(manufStr.begin(), manufStr.end(), manufStr.begin(), ::tolower); - out.str(""); - out << setw(2) << hex << setfill('0') << nouppercase << static_cast(address); - addrStr = out.str(); - out.str(""); - out.clear(); - offset += (*identFields)[field++]->getLength(pt_slaveData, MAX_LEN); - result = (*identFields)[field]->read(data, offset, false, nullptr, -1, OF_NONE, -1, &out); // identification string - } - if (result == RESULT_OK) { - ident = out.str(); - out.str(""); - out.clear(); - offset += (*identFields)[field++]->getLength(pt_slaveData, MAX_LEN); - result = (*identFields)[field]->read(data, offset, nullptr, -1, &sw); // software version number - if (result == RESULT_ERR_OUT_OF_RANGE) { - sw = (data.dataAt(offset) << 16) | data.dataAt(offset+1); // use hex value instead - result = RESULT_OK; - } - } - if (result == RESULT_OK) { - offset += (*identFields)[field++]->getLength(pt_slaveData, MAX_LEN); - result = (*identFields)[field]->read(data, offset, nullptr, -1, &hw); // hardware version number - if (result == RESULT_ERR_OUT_OF_RANGE) { - hw = (data.dataAt(offset) << 16) | data.dataAt(offset+1); // use hex value instead - result = RESULT_OK; - } - } - if (result != RESULT_OK) { - logError(lf_main, "unable to load scan config %2.2x: decode field %s %s", address, - identFields->getName(field).c_str(), getResultCode(result)); - return result; - } - bool hasTemplates = false; - string best; - map bestDefaults; - vector files; - auto it = ident.begin(); - while (it != ident.end()) { - if (*it != '_' && !::isalnum(*it)) { - it = ident.erase(it); - } else { - *it = static_cast(::tolower(*it)); - it++; - } - } - // find files matching MANUFACTURER/ZZ.*csv in cfgpath - string query; - if (!fromLocal) { - out << "&a=" << addrStr << "&i=" << ident << "&h=" << dec << static_cast(hw) << "&s=" << dec - << static_cast(sw); - query = out.str(); - out.str(""); - out.clear(); - } - result = collectConfigFiles(manufStr, addrStr + ".", ".csv", &files, false, query, nullptr, &hasTemplates); - if (result != RESULT_OK) { - logError(lf_main, "unable to load scan config %2.2x: list files in %s %s", address, manufStr.c_str(), - getResultCode(result)); - return result; - } - if (files.empty()) { - logError(lf_main, "unable to load scan config %2.2x: no file from %s with prefix %s found", address, - manufStr.c_str(), addrStr.c_str()); - return RESULT_ERR_NOTFOUND; - } - logDebug(lf_main, "found %d matching scan config files from %s with prefix %s: %s", files.size(), manufStr.c_str(), - addrStr.c_str(), getResultCode(result)); - // complete name: cfgpath/MANUFACTURER/ZZ[.C[C[C[C[C]]]]][.circuit][.suffix][.*][.SWxxxx][.HWxxxx][.*].csv - size_t bestMatch = 0; - for (const auto& name : files) { - symbol_t checkDest; - unsigned int checkSw, checkHw; - map defaults; - const string filename = name.substr(manufStr.length()+1); - if (!messages->extractDefaultsFromFilename(filename, &defaults, &checkDest, &checkSw, &checkHw)) { - continue; - } - if (address != checkDest || (checkSw != UINT_MAX && sw != checkSw) || (checkHw != UINT_MAX && hw != checkHw)) { - continue; - } - size_t match = 1; - string checkIdent = defaults["name"]; - if (!checkIdent.empty()) { - string remain = ident; - bool matches = false; - while (remain.length() > 0 && remain.length() >= checkIdent.length()) { - if (checkIdent == remain) { - matches = true; - break; - } - if (!::isdigit(remain[remain.length()-1])) { - break; - } - remain.erase(remain.length()-1); // remove trailing digit - } - if (!matches) { - continue; // IDENT mismatch - } - match += remain.length(); - } - if (match >= bestMatch) { - bestMatch = match; - best = name; - bestDefaults = defaults; - } - } - - if (best.empty()) { - logError(lf_main, - "unable to load scan config %2.2x: no file from %s with prefix %s matches ID \"%s\", SW%4.4d, HW%4.4d", - address, manufStr.c_str(), addrStr.c_str(), ident.c_str(), sw, hw); - return RESULT_ERR_NOTFOUND; - } - - // found the right file. load the templates if necessary, then load the file itself - bool readCommon = readTemplates(manufStr, ".csv", hasTemplates, s_opt.checkConfig); - if (readCommon) { - result = collectConfigFiles(manufStr, "", ".csv", &files, true, "&a=-"); - if (result == RESULT_OK && !files.empty()) { - for (const auto& name : files) { - string baseName = name.substr(manufStr.length()+1, name.length()-manufStr.length()-strlen(".csv")); // *. - if (baseName == "_templates.") { // skip templates - continue; - } - if (baseName.length() < 3 || baseName.find_first_of('.') != 2) { // different from the scheme "ZZ." - string errorDescription; - result = loadDefinitionsFromConfigPath(messages, name, verbose, nullptr, &errorDescription); - if (result == RESULT_OK) { - logNotice(lf_main, "read common config file %s", name.c_str()); - } else { - logError(lf_main, "error reading common config file %s: %s, %s", name.c_str(), getResultCode(result), - errorDescription.c_str()); - } - } - } - } - } - bestDefaults["name"] = ident; - string errorDescription; - result = loadDefinitionsFromConfigPath(messages, best, verbose, &bestDefaults, &errorDescription); - if (result != RESULT_OK) { - logError(lf_main, "error reading scan config file %s for ID \"%s\", SW%4.4d, HW%4.4d: %s, %s", best.c_str(), - ident.c_str(), sw, hw, getResultCode(result), errorDescription.c_str()); - return result; - } - logNotice(lf_main, "read scan config file %s for ID \"%s\", SW%4.4d, HW%4.4d", best.c_str(), ident.c_str(), sw, hw); - *relativeFile = best; - return RESULT_OK; -} - -/** - * Helper method for parsing a master/slave message pair from a command line argument. - * @param arg the argument to parse. - * @param onlyMasterSlave true to parse only a MS message, false to also parse MM and BC message. - * @param master the @a MasterSymbolString to parse into. - * @param slave the @a SlaveSymbolString to parse into. - * @return true when the argument was valid, false otherwise. - */ -bool parseMessage(const string& arg, bool onlyMasterSlave, MasterSymbolString* master, SlaveSymbolString* slave) { - size_t pos = arg.find_first_of('/'); - if (pos == string::npos) { - logError(lf_main, "invalid message %s: missing \"/\"", arg.c_str()); - return false; - } - result_t result = master->parseHex(arg.substr(0, pos)); - if (result == RESULT_OK) { - result = slave->parseHex(arg.substr(pos+1)); - } - if (result != RESULT_OK) { - logError(lf_main, "invalid message %s: %s", arg.c_str(), getResultCode(result)); - return false; - } - if (master->size() < 5) { // skip QQ ZZ PB SB NN - logError(lf_main, "invalid message %s: master part too short", arg.c_str()); - return false; - } - if (!isMaster((*master)[0])) { - logError(lf_main, "invalid message %s: QQ is no master", arg.c_str()); - return false; - } - if (!isValidAddress((*master)[1], !onlyMasterSlave) || (onlyMasterSlave && isMaster((*master)[1]))) { - logError(lf_main, "invalid message %s: ZZ is invalid", arg.c_str()); - return false; - } - return true; -} /** * Main function. @@ -1395,8 +874,13 @@ int main(int argc, char* argv[], char* envp[]) { if (!s_configPath.empty() && s_configPath[s_configPath.length()-1] != '/') { s_configPath += "/"; } + const string lang = MappedFileReader::normalizeLanguage( + s_opt.preferLanguage == nullptr || !s_opt.preferLanguage[0] ? "" : s_opt.preferLanguage + ); + string configLocalPrefix, configUriPrefix; + HttpClient* configHttpClient = nullptr; if (s_configPath.find("://") == string::npos) { - s_configLocalPrefix = s_configPath; + configLocalPrefix = s_configPath; } else { if (!s_opt.scanConfig) { logError(lf_main, "invalid configpath without scanconfig"); @@ -1411,7 +895,7 @@ int main(int argc, char* argv[], char* envp[]) { } uint16_t configPort = 80; string proto, configHost; - if (!HttpClient::parseUrl(s_configPath, &proto, &configHost, &configPort, &s_configUriPrefix)) { + if (!HttpClient::parseUrl(s_configPath, &proto, &configHost, &configPort, &configUriPrefix)) { #ifndef HAVE_SSL if (proto == "https") { logError(lf_main, "invalid configPath URL (HTTPS not supported)"); @@ -1421,23 +905,19 @@ int main(int argc, char* argv[], char* envp[]) { logError(lf_main, "invalid configPath URL"); return EINVAL; } - if (!lazyHttpClient() || ( + configHttpClient = new HttpClient(s_opt.caFile, s_opt.caPath); + if ( // check with low timeout of 1 second initially: - !s_configHttpClient->connect(configHost, configPort, proto == "https", PACKAGE_NAME "/" PACKAGE_VERSION, 1) + !configHttpClient->connect(configHost, configPort, proto == "https", PACKAGE_NAME "/" PACKAGE_VERSION, 1) // if that did not work, issue a single retry with default timeout: - && !s_configHttpClient->connect(configHost, configPort, proto == "https", PACKAGE_NAME "/" PACKAGE_VERSION) - )) { + && !configHttpClient->connect(configHost, configPort, proto == "https", PACKAGE_NAME "/" PACKAGE_VERSION) + ) { logError(lf_main, "invalid configPath URL (connect)"); + delete configHttpClient; cleanup(); return EINVAL; } - s_configHttpClient->disconnect(); - } - const string lang = MappedFileReader::normalizeLanguage( - s_opt.preferLanguage == nullptr || !s_opt.preferLanguage[0] ? "" : s_opt.preferLanguage - ); - if (!lang.empty()) { - s_configLangQuery = "?l=" + lang; + configHttpClient->disconnect(); } if (!s_opt.readOnly && s_opt.scanConfig && s_opt.initialScan == 0) { s_opt.initialScan = BROADCAST; @@ -1448,16 +928,19 @@ int main(int argc, char* argv[], char* envp[]) { } s_messageMap = new MessageMap(s_opt.checkConfig, lang); + s_scanHelper = new ScanHelper(s_messageMap, s_configPath, configLocalPrefix, configUriPrefix, + lang.empty() ? lang : "?l=" + lang, configHttpClient, s_opt.checkConfig); + s_messageMap->setResolver(s_scanHelper); if (s_opt.checkConfig) { logNotice(lf_main, PACKAGE_STRING "." REVISION " performing configuration check..."); - result_t result = loadConfigFiles(s_messageMap, true, s_opt.scanConfig && arg_index < argc); - result_t overallResult = executeInstructions(s_messageMap, true); + result_t result = s_scanHelper->loadConfigFiles(!s_opt.scanConfig || arg_index >= argc); + result_t overallResult = s_scanHelper->executeInstructions(nullptr); MasterSymbolString master; SlaveSymbolString slave; while (result == RESULT_OK && s_opt.scanConfig && arg_index < argc) { // check scan config for each passed ident message - if (!parseMessage(argv[arg_index++], true, &master, &slave)) { + if (!s_scanHelper->parseMessage(argv[arg_index++], true, &master, &slave)) { continue; } symbol_t address = master[1]; @@ -1470,8 +953,8 @@ int main(int argc, char* argv[], char* envp[]) { } else { message->storeLastData(master, slave); string file; - result_t res = loadScanConfigFile(s_messageMap, address, true, &file); - result_t instrRes = executeInstructions(s_messageMap, true); + result_t res = s_scanHelper->loadScanConfigFile(address, &file); + result_t instrRes = s_scanHelper->executeInstructions(nullptr); if (res == RESULT_OK) { logInfo(lf_main, "scan config %2.2x: file %s loaded", address, file.c_str()); } else if (overallResult == RESULT_OK) { @@ -1543,17 +1026,17 @@ int main(int argc, char* argv[], char* envp[]) { device->getName()); // load configuration files - loadConfigFiles(s_messageMap); + s_scanHelper->loadConfigFiles(s_messageMap); // create the MainLoop and start it - s_mainLoop = new MainLoop(s_opt, device, s_messageMap); + s_mainLoop = new MainLoop(s_opt, device, s_messageMap, s_scanHelper); if (s_opt.injectMessages) { BusHandler* busHandler = s_mainLoop->getBusHandler(); while (arg_index < argc) { // add each passed message MasterSymbolString master; SlaveSymbolString slave; - if (!parseMessage(argv[arg_index++], false, &master, &slave)) { + if (!s_scanHelper->parseMessage(argv[arg_index++], false, &master, &slave)) { continue; } busHandler->injectMessage(master, slave); diff --git a/src/ebusd/main.h b/src/ebusd/main.h index c83e66600..09f3174e2 100644 --- a/src/ebusd/main.h +++ b/src/ebusd/main.h @@ -23,7 +23,6 @@ #include #include #include "lib/ebus/data.h" -#include "lib/ebus/message.h" #include "lib/ebus/result.h" #include "lib/utils/log.h" @@ -91,56 +90,6 @@ struct options { bool dumpFlush; //!< flush each byte }; -/** - * Get the @a DataFieldTemplates for the specified configuration file. - * @param filename the full name of the configuration file, or "*" to get the non-root templates with the longest name - * or the root templates if not available. - * @return the @a DataFieldTemplates. - */ -DataFieldTemplates* getTemplates(const string& filename); - -/** - * Load the message definitions from configuration files. - * @param messages the @a MessageMap to load the messages into. - * @param verbose whether to verbosely log problems. - * @param denyRecursive whether to avoid loading all files recursively (e.g. for scan config check). - * @return the result code. - */ -result_t loadConfigFiles(MessageMap* messages, bool verbose = false, bool denyRecursive = false); - -/** - * Load the message definitions from a configuration file matching the scan result. - * @param messages the @a MessageMap to load the messages into. - * @param address the address of the scan participant - * (either master for broadcast master data or slave for read slave data). - * @param data the scan @a SlaveSymbolString for which to load the configuration file. - * @param verbose whether to verbosely log problems. - * @param relativeFile the string in which the name of the configuration file is stored on success. - * @return the result code. - */ -result_t loadScanConfigFile(MessageMap* messages, symbol_t address, bool verbose, string* relativeFile); - -/** - * Helper method for executing all loaded and resolvable instructions. - * @param messages the @a MessageMap instance. - * @param verbose whether to verbosely log all problems. - * @return the result code. - */ -result_t executeInstructions(MessageMap* messages, bool verbose = false); - -/** - * Helper method for loading definitions from a relative file from the config path/URL. - * @param reader the @a FileReader instance to load with the definitions. - * @param filename the relative name of the file being read. - * @param verbose whether to verbosely log problems. - * @param defaults the default values by name (potentially overwritten by file name), or nullptr to not use defaults. - * @param errorDescription a string in which to store the error description in case of error. - * @param replace whether to replace an already existing entry. - * @return @a RESULT_OK on success, or an error code. - */ -result_t loadDefinitionsFromConfigPath(FileReader* reader, const string& filename, bool verbose, - map* defaults, string* errorDescription, bool replace = false); - } // namespace ebusd #endif // EBUSD_MAIN_H_ diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index c87d6ed98..8cd22f846 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -21,6 +21,7 @@ #endif #include "ebusd/mainloop.h" +#include "ebusd/scan.h" #include #include #include @@ -104,8 +105,8 @@ result_t UserList::addFromFile(const string& filename, unsigned int lineNo, map< #define VERBOSITY_4 (VERBOSITY_3 | OF_ALL_ATTRS) -MainLoop::MainLoop(const struct options& opt, Device *device, MessageMap* messages) - : Thread(), m_device(device), m_reconnectCount(0), m_userList(opt.accessLevel), m_messages(messages), +MainLoop::MainLoop(const struct options& opt, Device *device, MessageMap* messages, ScanHelper* scanHelper) + : Thread(), m_device(device), m_reconnectCount(0), m_userList(opt.accessLevel), m_messages(messages), m_scanHelper(scanHelper), m_address(opt.address), m_scanConfig(opt.scanConfig), m_initialScan(opt.readOnly ? ESC : opt.initialScan), m_polling(opt.pollInterval > 0), m_enableHex(opt.enableHex), m_shutdown(false), m_runUpdateCheck(opt.updateCheck), m_httpClient(opt.caFile, opt.caPath) { @@ -148,7 +149,7 @@ MainLoop::MainLoop(const struct options& opt, Device *device, MessageMap* messag } } // create BusHandler - m_busHandler = new BusHandler(m_device, m_messages, + m_busHandler = new BusHandler(m_device, m_messages, scanHelper, m_address, opt.answer, opt.acquireRetries, opt.sendRetries, opt.acquireTimeout, opt.receiveTimeout, @@ -166,7 +167,12 @@ MainLoop::MainLoop(const struct options& opt, Device *device, MessageMap* messag } else { logError(lf_main, "error registering data handlers"); } - m_newlyDefinedMessages = opt.enableDefine ? new MessageMap(true, "", false) : nullptr; + if (opt.enableDefine) { + m_newlyDefinedMessages = new MessageMap(true, "", false); + m_newlyDefinedMessages->setResolver(scanHelper); + } else { + m_newlyDefinedMessages = nullptr; + } } MainLoop::~MainLoop() { @@ -330,7 +336,7 @@ void MainLoop::run() { } else if (reload && m_busHandler->hasSignal()) { reload = false; // execute initial instructions - executeInstructions(m_messages); + m_scanHelper->executeInstructions(m_busHandler); if (m_messages->sizeConditions() > 0 && !m_polling) { logError(lf_main, "conditions require a poll interval > 0"); } @@ -1749,7 +1755,7 @@ result_t MainLoop::executeDecode(const vector& args, ostringstream* ostr time(&now); istringstream defstr("#\n" + args[argPos]); // ensure first line is not used for determining col names string errorDescription; - DataFieldTemplates* templates = getTemplates("*"); + DataFieldTemplates* templates = m_scanHelper->getTemplates("*"); LoadableDataFieldSet fields("", templates); result_t ret = fields.readFromStream(&defstr, "temporary", now, true, nullptr, &errorDescription); if (ret != RESULT_OK) { @@ -1781,7 +1787,7 @@ result_t MainLoop::executeEncode(const vector& args, ostringstream* ostr time(&now); istringstream defstr("#\n" + args[argPos]); // ensure first line is not used for determining col names string errorDescription; - DataFieldTemplates* templates = getTemplates("*"); + DataFieldTemplates* templates = m_scanHelper->getTemplates("*"); LoadableDataFieldSet fields("", templates); result_t ret = fields.readFromStream(&defstr, "temporary", now, true, nullptr, &errorDescription); if (ret != RESULT_OK) { @@ -1922,7 +1928,8 @@ result_t MainLoop::executeReload(const vector& args, ostringstream* ostr return RESULT_OK; } m_busHandler->clear(); - return loadConfigFiles(m_messages); + m_scanHelper->loadConfigFiles(!m_scanConfig); + return RESULT_OK; } result_t MainLoop::executeInfo(const vector& args, const string& user, ostringstream* ostream) { @@ -2343,7 +2350,7 @@ result_t MainLoop::executeGet(const vector& args, bool* connected, ostri time(&now); istringstream defstr("#\n" + def); // ensure first line is not used for determining col names string errorDescription; - DataFieldTemplates* templates = getTemplates("*"); + DataFieldTemplates* templates = m_scanHelper->getTemplates("*"); LoadableDataFieldSet fields("", templates); ret = fields.readFromStream(&defstr, "temporary", now, true, nullptr, &errorDescription); if (ret == RESULT_OK && fields.size()) { diff --git a/src/ebusd/mainloop.h b/src/ebusd/mainloop.h index 1092182ce..b76797ef9 100644 --- a/src/ebusd/mainloop.h +++ b/src/ebusd/mainloop.h @@ -27,6 +27,7 @@ #include "ebusd/bushandler.h" #include "ebusd/datahandler.h" #include "ebusd/network.h" +#include "ebusd/scan.h" #include "lib/ebus/filereader.h" #include "lib/ebus/message.h" #include "lib/utils/rotatefile.h" @@ -105,8 +106,9 @@ class MainLoop : public Thread, DeviceListener { * @param opt the program options. * @param device the @a Device instance. * @param messages the @a MessageMap instance. + * @param scanHelper the @a ScanHelper instance. */ - MainLoop(const struct options& opt, Device *device, MessageMap* messages); + MainLoop(const struct options& opt, Device *device, MessageMap* messages, ScanHelper* scanHelper); /** * Destructor. @@ -406,6 +408,9 @@ class MainLoop : public Thread, DeviceListener { /** the @a MessageMap instance. */ MessageMap* m_messages; + /** the @a ScanHelper instance. */ + ScanHelper* m_scanHelper; + /** the own master address for sending on the bus. */ const symbol_t m_address; diff --git a/src/ebusd/scan.cpp b/src/ebusd/scan.cpp new file mode 100644 index 000000000..64452cfcf --- /dev/null +++ b/src/ebusd/scan.cpp @@ -0,0 +1,510 @@ +/* + * ebusd - daemon for communication with eBUS heating systems. + * Copyright (C) 2014-2023 John Baier + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "ebusd/scan.h" +#include "ebusd/bushandler.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "lib/utils/log.h" + + +namespace ebusd { + +using std::dec; +using std::hex; +using std::setfill; +using std::setw; +using std::nouppercase; +using std::cout; + + +ScanHelper::~ScanHelper() { + // free templates + for (const auto& it : m_templatesByPath) { + if (it.second != &m_globalTemplates) { + delete it.second; + } + } + m_templatesByPath.clear(); + if (m_configHttpClient) { + delete m_configHttpClient; + m_configHttpClient = nullptr; + } +} + +result_t ScanHelper::collectConfigFiles(const string& relPath, const string& prefix, const string& extension, + vector* files, + bool ignoreAddressPrefix, const string& query, + vector* dirs, bool* hasTemplates) { + const string relPathWithSlash = relPath.empty() ? "" : relPath + "/"; + if (!m_configUriPrefix.empty()) { + string uri = m_configUriPrefix + relPathWithSlash + m_configLangQuery + (m_configLangQuery.empty() ? "?" : "&") + + "t=" + extension.substr(1) + query; + string names; + if (!m_configHttpClient->get(uri, "", &names)) { + return RESULT_ERR_NOTFOUND; + } + istringstream stream(names); + string name; + while (getline(stream, name)) { + if (name.empty()) { + continue; + } + if (name == "_templates"+extension) { + if (hasTemplates) { + *hasTemplates = true; + } + continue; + } + if (prefix.length() == 0 ? (!ignoreAddressPrefix || name.length() < 3 || name.find_first_of('.') != 2) + : (name.length() >= prefix.length() && name.substr(0, prefix.length()) == prefix)) { + files->push_back(relPathWithSlash + name); + } + } + return RESULT_OK; + } + const string path = m_configLocalPrefix + relPathWithSlash; + logDebug(lf_main, "reading directory %s", path.c_str()); + DIR* dir = opendir(path.c_str()); + if (dir == nullptr) { + return RESULT_ERR_NOTFOUND; + } + dirent* d; + while ((d = readdir(dir)) != nullptr) { + string name = d->d_name; + if (name == "." || name == "..") { + continue; + } + const string p = path + name; + struct stat stat_buf = {}; + if (stat(p.c_str(), &stat_buf) != 0) { + logError(lf_main, "unable to stat file %s", p.c_str()); + continue; + } + logDebug(lf_main, "file type of %s is %s", p.c_str(), + S_ISDIR(stat_buf.st_mode) ? "dir" : S_ISREG(stat_buf.st_mode) ? "file" : "other"); + if (S_ISDIR(stat_buf.st_mode)) { + if (dirs != nullptr) { + dirs->push_back(relPathWithSlash + name); + } + } else if (S_ISREG(stat_buf.st_mode) && name.length() >= extension.length() + && name.substr(name.length()-extension.length()) == extension) { + if (name == "_templates"+extension) { + if (hasTemplates) { + *hasTemplates = true; + } + continue; + } + if (prefix.length() == 0 ? (!ignoreAddressPrefix || name.length() < 3 || name.find_first_of('.') != 2) + : (name.length() >= prefix.length() && name.substr(0, prefix.length()) == prefix)) { + files->push_back(relPathWithSlash + name); + } + } + } + closedir(dir); + + return RESULT_OK; +} + +DataFieldTemplates* ScanHelper::getTemplates(const string& filename) { + if (filename == "*") { + unsigned long maxLength = 0; + DataFieldTemplates* best = nullptr; + for (auto it : m_templatesByPath) { + if (it.first.size() > maxLength) { + best = it.second; + } + } + if (best) { + return best; + } + } else { + string path; + size_t pos = filename.find_last_of('/'); + if (pos != string::npos) { + path = filename.substr(0, pos); + } + const auto it = m_templatesByPath.find(path); + if (it != m_templatesByPath.end()) { + return it->second; + } + } + return &m_globalTemplates; +} + +bool ScanHelper::readTemplates(const string relPath, const string extension, bool available) { + const auto it = m_templatesByPath.find(relPath); + if (it != m_templatesByPath.end()) { + return false; + } + DataFieldTemplates* templates; + if (relPath.empty() || !available) { + templates = &m_globalTemplates; + } else { + templates = new DataFieldTemplates(m_globalTemplates); + } + m_templatesByPath[relPath] = templates; + if (!available) { + // global templates are stored as replacement in order to determine whether the directory was already loaded + return true; + } + string errorDescription; + string logPath = relPath.empty() ? "/" : relPath; + logInfo(lf_main, "reading templates %s", logPath.c_str()); + string file = (relPath.empty() ? "" : relPath + "/") + "_templates" + extension; + result_t result = loadDefinitionsFromConfigPath(templates, file, nullptr, &errorDescription, true); + if (result == RESULT_OK) { + logInfo(lf_main, "read templates in %s", logPath.c_str()); + return true; + } + logError(lf_main, "error reading templates in %s: %s, last error: %s", logPath.c_str(), getResultCode(result), + errorDescription.c_str()); + return false; +} + +result_t ScanHelper::readConfigFiles(const string& relPath, const string& extension, bool recursive, + string* errorDescription) { + vector files, dirs; + bool hasTemplates = false; + result_t result = collectConfigFiles(relPath, "", extension, &files, false, "", &dirs, &hasTemplates); + if (result != RESULT_OK) { + return result; + } + readTemplates(relPath, extension, hasTemplates); + for (const auto& name : files) { + logInfo(lf_main, "reading file %s", name.c_str()); + result = loadDefinitionsFromConfigPath(m_messages, name, nullptr, errorDescription); + if (result != RESULT_OK) { + return result; + } + logInfo(lf_main, "successfully read file %s", name.c_str()); + } + if (recursive) { + for (const auto& name : dirs) { + logInfo(lf_main, "reading dir %s", name.c_str()); + result = readConfigFiles(name, extension, true, errorDescription); + if (result != RESULT_OK) { + return result; + } + logInfo(lf_main, "successfully read dir %s", name.c_str()); + } + } + return RESULT_OK; +} + +static BusHandler* executeInstructionsBusHandlerInstance = nullptr; + +/** + * Helper method for immediate reading of a @a Message from the bus. + * @param message the @a Message to read. + */ +static void readMessage(Message* message) { + if (!executeInstructionsBusHandlerInstance || !message) { + return; + } + result_t result = executeInstructionsBusHandlerInstance->readFromBus(message, ""); + if (result != RESULT_OK) { + logError(lf_main, "error reading message %s %s: %s", message->getCircuit().c_str(), message->getName().c_str(), + getResultCode(result)); + } +} + +result_t ScanHelper::executeInstructions(BusHandler* busHandler) { + string errorDescription; + result_t result = m_messages->resolveConditions(m_verbose, &errorDescription); + if (result != RESULT_OK) { + logError(lf_main, "error resolving conditions: %s, last error: %s", getResultCode(result), + errorDescription.c_str()); + } + ostringstream log; + executeInstructionsBusHandlerInstance = busHandler; + result = m_messages->executeInstructions(&readMessage, &log); + if (result != RESULT_OK) { + logError(lf_main, "error executing instructions: %s, last error: %s", getResultCode(result), + log.str().c_str()); + } else if (m_verbose && log.tellp() > 0) { + logInfo(lf_main, log.str().c_str()); + } + logNotice(lf_main, "found messages: %d (%d conditional on %d conditions, %d poll, %d update)", m_messages->size(), + m_messages->sizeConditional(), m_messages->sizeConditions(), m_messages->sizePoll(), m_messages->sizePassive()); + return result; +} + +result_t ScanHelper::loadDefinitionsFromConfigPath(FileReader* reader, const string& filename, + map* defaults, string* errorDescription, bool replace) { + istream* stream = nullptr; + time_t mtime = 0; + if (m_configUriPrefix.empty()) { + stream = FileReader::openFile(m_configLocalPrefix + filename, errorDescription, &mtime); + } else { + string content; + if (m_configHttpClient + && m_configHttpClient->get(m_configUriPrefix + filename + m_configLangQuery, "", &content, &mtime)) { + stream = new istringstream(content); + } + } + result_t result; + if (stream) { + result = reader->readFromStream(stream, filename, mtime, m_verbose, defaults, errorDescription, replace); + delete(stream); + } else { + result = RESULT_ERR_NOTFOUND; + } + return result; +} + +result_t ScanHelper::loadConfigFiles(bool recursive) { + logInfo(lf_main, "loading configuration files from %s", m_configPath.c_str()); + m_messages->lock(); + m_messages->clear(); + m_globalTemplates.clear(); + for (auto& it : m_templatesByPath) { + if (it.second != &m_globalTemplates) { + delete it.second; + } + it.second = nullptr; + } + m_templatesByPath.clear(); + + string errorDescription; + result_t result = readConfigFiles("", ".csv", recursive, &errorDescription); + if (result == RESULT_OK) { + logInfo(lf_main, "read config files, got %d messages", m_messages->size()); + } else { + logError(lf_main, "error reading config files from %s: %s, last error: %s", m_configPath.c_str(), + getResultCode(result), errorDescription.c_str()); + } + m_messages->unlock(); + return result; +} + +result_t ScanHelper::loadScanConfigFile(symbol_t address, string* relativeFile) { + Message* message = m_messages->getScanMessage(address); + if (!message || message->getLastUpdateTime() == 0) { + return RESULT_ERR_NOTFOUND; + } + const SlaveSymbolString& data = message->getLastSlaveData(); + if (data.getDataSize() < 1+5+2+2) { + logError(lf_main, "unable to load scan config %2.2x: slave part too short (%d)", address, data.getDataSize()); + return RESULT_EMPTY; + } + DataFieldSet* identFields = DataFieldSet::getIdentFields(); + string manufStr, addrStr, ident; // path: cfgpath/MANUFACTURER, prefix: ZZ., ident: C[C[C[C[C]]]], SW: xxxx, HW: xxxx + unsigned int sw = 0, hw = 0; + ostringstream out; + size_t offset = 0; + size_t field = 0; + bool fromLocal = m_configUriPrefix.empty(); + // manufacturer name + result_t result = (*identFields)[field]->read(data, offset, false, nullptr, -1, OF_NONE, -1, &out); + if (result == RESULT_ERR_NOTFOUND && fromLocal) { + result = (*identFields)[field]->read(data, offset, false, nullptr, -1, OF_NUMERIC, -1, &out); // manufacturer name + } + if (result == RESULT_OK) { + manufStr = out.str(); + transform(manufStr.begin(), manufStr.end(), manufStr.begin(), ::tolower); + out.str(""); + out << setw(2) << hex << setfill('0') << nouppercase << static_cast(address); + addrStr = out.str(); + out.str(""); + out.clear(); + offset += (*identFields)[field++]->getLength(pt_slaveData, MAX_LEN); + result = (*identFields)[field]->read(data, offset, false, nullptr, -1, OF_NONE, -1, &out); // identification string + } + if (result == RESULT_OK) { + ident = out.str(); + out.str(""); + out.clear(); + offset += (*identFields)[field++]->getLength(pt_slaveData, MAX_LEN); + result = (*identFields)[field]->read(data, offset, nullptr, -1, &sw); // software version number + if (result == RESULT_ERR_OUT_OF_RANGE) { + sw = (data.dataAt(offset) << 16) | data.dataAt(offset+1); // use hex value instead + result = RESULT_OK; + } + } + if (result == RESULT_OK) { + offset += (*identFields)[field++]->getLength(pt_slaveData, MAX_LEN); + result = (*identFields)[field]->read(data, offset, nullptr, -1, &hw); // hardware version number + if (result == RESULT_ERR_OUT_OF_RANGE) { + hw = (data.dataAt(offset) << 16) | data.dataAt(offset+1); // use hex value instead + result = RESULT_OK; + } + } + if (result != RESULT_OK) { + logError(lf_main, "unable to load scan config %2.2x: decode field %s %s", address, + identFields->getName(field).c_str(), getResultCode(result)); + return result; + } + bool hasTemplates = false; + string best; + map bestDefaults; + vector files; + auto it = ident.begin(); + while (it != ident.end()) { + if (*it != '_' && !::isalnum(*it)) { + it = ident.erase(it); + } else { + *it = static_cast(::tolower(*it)); + it++; + } + } + // find files matching MANUFACTURER/ZZ.*csv in cfgpath + string query; + if (!fromLocal) { + out << "&a=" << addrStr << "&i=" << ident << "&h=" << dec << static_cast(hw) << "&s=" << dec + << static_cast(sw); + query = out.str(); + out.str(""); + out.clear(); + } + result = collectConfigFiles(manufStr, addrStr + ".", ".csv", &files, false, query, nullptr, &hasTemplates); + if (result != RESULT_OK) { + logError(lf_main, "unable to load scan config %2.2x: list files in %s %s", address, manufStr.c_str(), + getResultCode(result)); + return result; + } + if (files.empty()) { + logError(lf_main, "unable to load scan config %2.2x: no file from %s with prefix %s found", address, + manufStr.c_str(), addrStr.c_str()); + return RESULT_ERR_NOTFOUND; + } + logDebug(lf_main, "found %d matching scan config files from %s with prefix %s: %s", files.size(), manufStr.c_str(), + addrStr.c_str(), getResultCode(result)); + // complete name: cfgpath/MANUFACTURER/ZZ[.C[C[C[C[C]]]]][.circuit][.suffix][.*][.SWxxxx][.HWxxxx][.*].csv + size_t bestMatch = 0; + for (const auto& name : files) { + symbol_t checkDest; + unsigned int checkSw, checkHw; + map defaults; + const string filename = name.substr(manufStr.length()+1); + if (!m_messages->extractDefaultsFromFilename(filename, &defaults, &checkDest, &checkSw, &checkHw)) { + continue; + } + if (address != checkDest || (checkSw != UINT_MAX && sw != checkSw) || (checkHw != UINT_MAX && hw != checkHw)) { + continue; + } + size_t match = 1; + string checkIdent = defaults["name"]; + if (!checkIdent.empty()) { + string remain = ident; + bool matches = false; + while (remain.length() > 0 && remain.length() >= checkIdent.length()) { + if (checkIdent == remain) { + matches = true; + break; + } + if (!::isdigit(remain[remain.length()-1])) { + break; + } + remain.erase(remain.length()-1); // remove trailing digit + } + if (!matches) { + continue; // IDENT mismatch + } + match += remain.length(); + } + if (match >= bestMatch) { + bestMatch = match; + best = name; + bestDefaults = defaults; + } + } + + if (best.empty()) { + logError(lf_main, + "unable to load scan config %2.2x: no file from %s with prefix %s matches ID \"%s\", SW%4.4d, HW%4.4d", + address, manufStr.c_str(), addrStr.c_str(), ident.c_str(), sw, hw); + return RESULT_ERR_NOTFOUND; + } + + // found the right file. load the templates if necessary, then load the file itself + bool readCommon = readTemplates(manufStr, ".csv", hasTemplates); + if (readCommon) { + result = collectConfigFiles(manufStr, "", ".csv", &files, true, "&a=-"); + if (result == RESULT_OK && !files.empty()) { + for (const auto& name : files) { + string baseName = name.substr(manufStr.length()+1, name.length()-manufStr.length()-strlen(".csv")); // *. + if (baseName == "_templates.") { // skip templates + continue; + } + if (baseName.length() < 3 || baseName.find_first_of('.') != 2) { // different from the scheme "ZZ." + string errorDescription; + result = loadDefinitionsFromConfigPath(m_messages, name, nullptr, &errorDescription); + if (result == RESULT_OK) { + logNotice(lf_main, "read common config file %s", name.c_str()); + } else { + logError(lf_main, "error reading common config file %s: %s, %s", name.c_str(), getResultCode(result), + errorDescription.c_str()); + } + } + } + } + } + bestDefaults["name"] = ident; + string errorDescription; + result = loadDefinitionsFromConfigPath(m_messages, best, &bestDefaults, &errorDescription); + if (result != RESULT_OK) { + logError(lf_main, "error reading scan config file %s for ID \"%s\", SW%4.4d, HW%4.4d: %s, %s", best.c_str(), + ident.c_str(), sw, hw, getResultCode(result), errorDescription.c_str()); + return result; + } + logNotice(lf_main, "read scan config file %s for ID \"%s\", SW%4.4d, HW%4.4d", best.c_str(), ident.c_str(), sw, hw); + *relativeFile = best; + return RESULT_OK; +} + +bool ScanHelper::parseMessage(const string& arg, bool onlyMasterSlave, MasterSymbolString* master, SlaveSymbolString* slave) { + size_t pos = arg.find_first_of('/'); + if (pos == string::npos) { + logError(lf_main, "invalid message %s: missing \"/\"", arg.c_str()); + return false; + } + result_t result = master->parseHex(arg.substr(0, pos)); + if (result == RESULT_OK) { + result = slave->parseHex(arg.substr(pos+1)); + } + if (result != RESULT_OK) { + logError(lf_main, "invalid message %s: %s", arg.c_str(), getResultCode(result)); + return false; + } + if (master->size() < 5) { // skip QQ ZZ PB SB NN + logError(lf_main, "invalid message %s: master part too short", arg.c_str()); + return false; + } + if (!isMaster((*master)[0])) { + logError(lf_main, "invalid message %s: QQ is no master", arg.c_str()); + return false; + } + if (!isValidAddress((*master)[1], !onlyMasterSlave) || (onlyMasterSlave && isMaster((*master)[1]))) { + logError(lf_main, "invalid message %s: ZZ is invalid", arg.c_str()); + return false; + } + return true; +} + +} // namespace ebusd diff --git a/src/ebusd/scan.h b/src/ebusd/scan.h new file mode 100644 index 000000000..d5372f354 --- /dev/null +++ b/src/ebusd/scan.h @@ -0,0 +1,205 @@ +/* + * ebusd - daemon for communication with eBUS heating systems. + * Copyright (C) 2014-2023 John Baier + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef EBUSD_SCAN_H_ +#define EBUSD_SCAN_H_ + +#include +#include +#include +#include "lib/ebus/data.h" +#include "lib/ebus/message.h" +#include "lib/ebus/result.h" +#include "lib/utils/httpclient.h" +#include "lib/utils/log.h" + +namespace ebusd { + +/** \file ebusd/scan.h + * Helpers for handling device scanning and config loading. + */ + +class BusHandler; + +/** + * Helper class for handling device scanning and config loading. + */ +class ScanHelper : public Resolver { + + public: + /** + * Constructor. + * @param messages the @a MessageMap to load the messages into. + * @param configPath the (optionally corrected) config path for retrieving configuration files from. + * @param configLocalPrefix the path prefix (including trailing "/") for retrieving configuration files from local files (empty for HTTPS). + * @param configUriPrefix the URI prefix (including trailing "/") for retrieving configuration files from HTTPS (empty for local files). + * @param configLangQuery the optional language query part for retrieving configuration files from HTTPS (empty for local files). + * @param configHttpClient the @a HttpClient for retrieving configuration files from HTTPS. + * @param verbose whether to verbosely log problems. + */ + ScanHelper(MessageMap* messages, + const string configPath, const string configLocalPrefix, + const string configUriPrefix, const string configLangQuery, + HttpClient* configHttpClient, bool verbose) + : Resolver(), m_messages(messages), + m_configPath(configPath), m_configLocalPrefix(configLocalPrefix), + m_configUriPrefix(configUriPrefix), m_configLangQuery(configLangQuery), + m_configHttpClient(configHttpClient), m_verbose(verbose) {} + + /** + * Destructor. + */ + virtual ~ScanHelper(); + + /** + * Try to connect to the specified server. + * @param host the host name to connect to. + * @param port the port to connect to. + * @param https true for HTTPS, false for HTTP. + * @param timeout the timeout in seconds, defaults to 5 seconds. + * @return true on success, false on connect failure. + */ + bool connect(const string& host, uint16_t port, bool https = false, int timeout = 5); + + /** + * Get the @a DataFieldTemplates for the specified configuration file. + * @param filename the full name of the configuration file, or "*" to get the non-root templates with the longest name + * or the root templates if not available. + * @return the @a DataFieldTemplates. + */ + virtual DataFieldTemplates* getTemplates(const string& filename); + + /** + * Load the message definitions from configuration files. + * @param recursive whether to load all files recursively. + * @return the result code. + */ + result_t loadConfigFiles(bool recursive = true); + + /** + * Load the message definitions from a configuration file matching the scan result. + * @param address the address of the scan participant + * (either master for broadcast master data or slave for read slave data). + * @param data the scan @a SlaveSymbolString for which to load the configuration file. + * @param relativeFile the string in which the name of the configuration file is stored on success. + * @return the result code. + */ + result_t loadScanConfigFile(symbol_t address, string* relativeFile); + + /** + * Helper method for executing all loaded and resolvable instructions. + * @param busHandler the @a BusHandler instance. + * @return the result code. + */ + result_t executeInstructions(BusHandler* busHandler); + + /** + * Helper method for loading definitions from a relative file from the config path/URL. + * @param reader the @a FileReader instance to load with the definitions. + * @param filename the relative name of the file being read. + * @param defaults the default values by name (potentially overwritten by file name), or nullptr to not use defaults. + * @param errorDescription a string in which to store the error description in case of error. + * @param replace whether to replace an already existing entry. + * @return @a RESULT_OK on success, or an error code. + */ + virtual result_t loadDefinitionsFromConfigPath(FileReader* reader, const string& filename, + map* defaults, string* errorDescription, bool replace = false); + + /** + * Helper method for parsing a master/slave message pair from a command line argument. + * @param arg the argument to parse. + * @param onlyMasterSlave true to parse only a MS message, false to also parse MM and BC message. + * @param master the @a MasterSymbolString to parse into. + * @param slave the @a SlaveSymbolString to parse into. + * @return true when the argument was valid, false otherwise. + */ + bool parseMessage(const string& arg, bool onlyMasterSlave, MasterSymbolString* master, SlaveSymbolString* slave); + + + private: + /** + * Collect configuration files matching the prefix and extension from the specified path. + * @param relPath the relative path from which to collect the files (without trailing "/"). + * @param prefix the filename prefix the files have to match, or empty. + * @param extension the filename extension the files have to match. + * @param files the @a vector to which to add the matching files. + * @param query the query string suffix for HTTPS retrieval starting with "&", or empty. + * @param dirs the @a vector to which to add found directories (without any name check), or nullptr to ignore. + * @param hasTemplates the bool to set when the templates file was found in the path, or nullptr to ignore. + * @return the result code. + */ + result_t collectConfigFiles(const string& relPath, const string& prefix, const string& extension, + vector* files, + bool ignoreAddressPrefix = false, const string& query = "", + vector* dirs = nullptr, bool* hasTemplates = nullptr); + + /** + * Read the @a DataFieldTemplates for the specified path if necessary. + * @param relPath the relative path from which to read the files (without trailing "/"). + * @param extension the filename extension of the files to read. + * @param available whether the templates file is available in the path. + * @return false when the templates for the path were already loaded before, true when the templates for the path were added (independent from @a available). + * @return the @a DataFieldTemplates. + */ + bool readTemplates(const string relPath, const string extension, bool available); + + /** + * Read the configuration files from the specified path. + * @param relPath the relative path from which to read the files (without trailing "/"). + * @param extension the filename extension of the files to read. + * @param recursive whether to load all files recursively. + * @param errorDescription a string in which to store the error description in case of error. + * @return the result code. + */ + result_t readConfigFiles(const string& relPath, const string& extension, bool recursive, + string* errorDescription); + + /** the @a MessageMap instance. */ + MessageMap* m_messages; + + /** the (optionally corrected) config path for retrieving configuration files from. */ + const string m_configPath; + + /** the path prefix (including trailing "/") for retrieving configuration files from local files (empty for HTTPS). */ + const string m_configLocalPrefix; + + /** the URI prefix (including trailing "/") for retrieving configuration files from HTTPS (empty for local files). */ + const string m_configUriPrefix; + + /** the optional language query part for retrieving configuration files from HTTPS (empty for local files). */ + const string m_configLangQuery; + + /** the @a HttpClient for retrieving configuration files from HTTPS. */ + HttpClient* m_configHttpClient; + + /** whether to verbosely log problems. */ + const bool m_verbose; + + /** the global @a DataFieldTemplates. */ + DataFieldTemplates m_globalTemplates; + + /** + * the loaded @a DataFieldTemplates by relative path (may also carry + * @a globalTemplates as replacement for missing file). + */ + map m_templatesByPath; +}; + +} // namespace ebusd + +#endif // EBUSD_SCAN_H_ diff --git a/src/lib/ebus/message.cpp b/src/lib/ebus/message.cpp index 7bd4eaa3f..1bcb20cd6 100644 --- a/src/lib/ebus/message.cpp +++ b/src/lib/ebus/message.cpp @@ -94,11 +94,6 @@ static const char* defaultMessageFieldMap[] = { // access level not included in /** 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, - map* defaults, string* errorDescription, bool replace = false); - Message::Message(const string& filename, const string& circuit, const string& level, const string& name, bool isWrite, bool isPassive, const map& attributes, @@ -1738,7 +1733,11 @@ void Instruction::getDestination(ostringstream* ostream) const { result_t LoadInstruction::execute(MessageMap* messages, ostringstream* log) { string errorDescription; - result_t result = loadDefinitionsFromConfigPath(messages, m_filename, false, &m_defaults, &errorDescription); + Resolver* resolver = messages->getResolver(); + if (!resolver) { + return RESULT_ERR_MISSING_ARG; + } + result_t result = resolver->loadDefinitionsFromConfigPath(messages, m_filename, &m_defaults, &errorDescription); if (log->tellp() > 0) { *log << ", "; } @@ -2306,7 +2305,10 @@ result_t MessageMap::addFromFile(const string& filename, unsigned int lineNo, ma return RESULT_ERR_INVALID_ARG; } result = RESULT_ERR_EOF; - DataFieldTemplates* templates = getTemplates(filename); + if (!m_resolver) { + return result; + } + DataFieldTemplates* templates = m_resolver->getTemplates(filename); bool hasMulti = types.find(VALUE_SEPARATOR) != string::npos; istringstream stream(types); string type; diff --git a/src/lib/ebus/message.h b/src/lib/ebus/message.h index 9ce3d2848..9bd0be5ac 100644 --- a/src/lib/ebus/message.h +++ b/src/lib/ebus/message.h @@ -1255,6 +1255,43 @@ class LoadedFileInfo { }; +/** + * Interface for resolving templates and loading additional message definitions. + */ +class Resolver { + public: + /** + * Constructor. + */ + Resolver() {} + + /** + * Destructor. + */ + virtual ~Resolver() {} + + /** + * Get the @a DataFieldTemplates for the specified configuration file. + * @param filename the full name of the configuration file, or "*" to get the non-root templates with the longest name + * or the root templates if not available. + * @return the @a DataFieldTemplates. + */ + virtual DataFieldTemplates* getTemplates(const string& filename) = 0; + + /** + * Load definitions from a relative file from the config path/URL. + * @param reader the @a FileReader instance to load with the definitions. + * @param filename the relative name of the file being read. + * @param defaults the default values by name (potentially overwritten by file name), or nullptr to not use defaults. + * @param errorDescription a string in which to store the error description in case of error. + * @param replace whether to replace an already existing entry. + * @return @a RESULT_OK on success, or an error code. + */ + virtual result_t loadDefinitionsFromConfigPath(FileReader* reader, const string& filename, + map* defaults, string* errorDescription, bool replace = false) = 0; +}; + + /** * Holds a map of all known @a Message instances. */ @@ -1267,7 +1304,7 @@ class MessageMap : public MappedFileReader { * @param deleteData whether to delete the scan message @a DataField during @a Message destruction. */ explicit MessageMap(bool addAll = false, const string& preferLanguage = "", bool deleteData = true) - : MappedFileReader::MappedFileReader(true, preferLanguage), + : MappedFileReader::MappedFileReader(true, preferLanguage), m_resolver(nullptr), m_addAll(addAll), m_additionalScanMessages(false), m_maxIdLength(0), m_maxBroadcastIdLength(0), m_messageCount(0), m_conditionalMessageCount(0), m_passiveMessageCount(0) { m_scanMessage = Message::createScanMessage(false, deleteData); @@ -1289,6 +1326,17 @@ class MessageMap : public MappedFileReader { } } + /** + * Set the @a Resolver instance. + * @param the @a Resolver instance. + */ + void setResolver(Resolver* resolver) { m_resolver = resolver; } + + /** + * @return the @a Resolver instance. + */ + Resolver* getResolver() const { return m_resolver; } + /** * Add a @a Message instance to this set. * @param message the @a Message instance to add. @@ -1575,6 +1623,9 @@ class MessageMap : public MappedFileReader { /** empty vector for @a getLoadedFiles(). */ static vector s_noFiles; + /** the @a Resolver instance. */ + Resolver* m_resolver; + /** whether to add all messages, even if duplicate. */ const bool m_addAll; diff --git a/src/lib/ebus/test/test_message.cpp b/src/lib/ebus/test/test_message.cpp index 7b46295a4..260193928 100644 --- a/src/lib/ebus/test/test_message.cpp +++ b/src/lib/ebus/test/test_message.cpp @@ -54,27 +54,29 @@ DataFieldTemplates* templates = nullptr; namespace ebusd { -DataFieldTemplates* getTemplates(const string& filename) { - if (filename == "") { // avoid compiler warning +class TestResolver : public Resolver { + public: + virtual DataFieldTemplates* getTemplates(const string& filename) { + if (filename == "") { // avoid compiler warning + return templates; + } return templates; } - return templates; -} -result_t loadDefinitionsFromConfigPath(FileReader* reader, const string& filename, bool verbose, - map* defaults, string* errorDescription, bool replace = false) { - time_t mtime = 0; - istream* stream = FileReader::openFile(filename, errorDescription, &mtime); - result_t result; - if (stream) { - result = reader->readFromStream(stream, filename, mtime, verbose, defaults, errorDescription); - delete(stream); - } else { - result = RESULT_ERR_NOTFOUND; + virtual result_t loadDefinitionsFromConfigPath(FileReader* reader, const string& filename, + map* defaults, string* errorDescription, bool replace = false) { + time_t mtime = 0; + istream* stream = FileReader::openFile(filename, errorDescription, &mtime); + result_t result; + if (stream) { + result = reader->readFromStream(stream, filename, mtime, false, defaults, errorDescription); + delete(stream); + } else { + result = RESULT_ERR_NOTFOUND; + } + return result; } - return result; -} - +}; } // namespace ebusd @@ -208,6 +210,7 @@ int main() { templates->readLineFromStream(&dummystr, __FILE__, false, &lineNo, &row, &errorDescription, false, nullptr, nullptr); lineNo = 0; MessageMap* messages = new MessageMap(""); + messages->setResolver(new TestResolver()); dummystr.clear(); dummystr.str("#"); messages->readLineFromStream(&dummystr, __FILE__, false, &lineNo, &row, &errorDescription, false, nullptr, nullptr); From 5b53fa313bcc5d1cb12e35f530b06c5801d4ffe6 Mon Sep 17 00:00:00 2001 From: John Date: Thu, 18 May 2023 12:31:11 +0200 Subject: [PATCH 064/345] more wildcards+ignores --- .gitignore | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index d027f22ed..f9ec2d1e4 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,9 @@ Testing cmake_install.cmake Makefile Makefile.in -/build/* +/build/ +/cmake-build-*/ +build-/ /aclocal.m4 /autom4te.cache/ /config.* @@ -21,23 +23,20 @@ Makefile.in /stamp-h? .deps/ *.o +*.a *.dirstamp *.gc* app.info /src/ebusd/ebusd /src/tools/ebusctl /src/tools/ebusfeed -/src/lib/utils/libutils.a -/src/lib/ebus/libebus.a /src/lib/ebus/contrib/test/test_tem /src/lib/ebus/test/test_symbol /src/lib/ebus/test/test_data /src/lib/ebus/test/test_message /src/lib/ebus/test/test_filereader -/src/lib/ebus/contrib/libebuscontrib.a /src/lib/ebus/contrib/test/test_contrib /docs/Doxyfile /docs/doxyfile.stamp /docs/html -/cmake-build-debug*/ -/.idea/ \ No newline at end of file +/.idea/ From e606772e9dd47fd7a09bad62505877449a26d568 Mon Sep 17 00:00:00 2001 From: John Date: Thu, 18 May 2023 13:00:12 +0200 Subject: [PATCH 065/345] code style --- src/ebusd/main.cpp | 3 +-- src/ebusd/mainloop.cpp | 14 +++++++------- src/ebusd/scan.cpp | 7 ++++--- src/ebusd/scan.h | 2 +- src/lib/ebus/device.cpp | 10 +++++----- src/lib/ebus/message.cpp | 2 +- src/lib/utils/httpclient.cpp | 2 +- src/lib/utils/httpclient.h | 3 +-- 8 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index 9e5b7b6e1..a659cf4fc 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -875,8 +875,7 @@ int main(int argc, char* argv[], char* envp[]) { s_configPath += "/"; } const string lang = MappedFileReader::normalizeLanguage( - s_opt.preferLanguage == nullptr || !s_opt.preferLanguage[0] ? "" : s_opt.preferLanguage - ); + s_opt.preferLanguage == nullptr || !s_opt.preferLanguage[0] ? "" : s_opt.preferLanguage); string configLocalPrefix, configUriPrefix; HttpClient* configHttpClient = nullptr; if (s_configPath.find("://") == string::npos) { diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index 8cd22f846..d59745f98 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -21,11 +21,11 @@ #endif #include "ebusd/mainloop.h" -#include "ebusd/scan.h" #include #include #include #include "ebusd/main.h" +#include "ebusd/scan.h" #include "lib/utils/log.h" #include "lib/ebus/data.h" @@ -106,10 +106,10 @@ result_t UserList::addFromFile(const string& filename, unsigned int lineNo, map< MainLoop::MainLoop(const struct options& opt, Device *device, MessageMap* messages, ScanHelper* scanHelper) - : Thread(), m_device(device), m_reconnectCount(0), m_userList(opt.accessLevel), m_messages(messages), m_scanHelper(scanHelper), - m_address(opt.address), m_scanConfig(opt.scanConfig), m_initialScan(opt.readOnly ? ESC : opt.initialScan), - m_polling(opt.pollInterval > 0), m_enableHex(opt.enableHex), m_shutdown(false), m_runUpdateCheck(opt.updateCheck), - m_httpClient(opt.caFile, opt.caPath) { + : Thread(), m_device(device), m_reconnectCount(0), m_userList(opt.accessLevel), m_messages(messages), + m_scanHelper(scanHelper), m_address(opt.address), m_scanConfig(opt.scanConfig), + m_initialScan(opt.readOnly ? ESC : opt.initialScan), m_polling(opt.pollInterval > 0), m_enableHex(opt.enableHex), + m_shutdown(false), m_runUpdateCheck(opt.updateCheck), m_httpClient(opt.caFile, opt.caPath) { m_device->setListener(this); // open Device result_t result = m_device->open(); @@ -170,9 +170,9 @@ MainLoop::MainLoop(const struct options& opt, Device *device, MessageMap* messag if (opt.enableDefine) { m_newlyDefinedMessages = new MessageMap(true, "", false); m_newlyDefinedMessages->setResolver(scanHelper); - } else { + } else { m_newlyDefinedMessages = nullptr; - } + } } MainLoop::~MainLoop() { diff --git a/src/ebusd/scan.cpp b/src/ebusd/scan.cpp index 64452cfcf..cb1cf184d 100644 --- a/src/ebusd/scan.cpp +++ b/src/ebusd/scan.cpp @@ -21,7 +21,6 @@ #endif #include "ebusd/scan.h" -#include "ebusd/bushandler.h" #include #include #include @@ -30,6 +29,7 @@ #include #include #include +#include "ebusd/bushandler.h" #include "lib/utils/log.h" @@ -133,7 +133,7 @@ result_t ScanHelper::collectConfigFiles(const string& relPath, const string& pre DataFieldTemplates* ScanHelper::getTemplates(const string& filename) { if (filename == "*") { - unsigned long maxLength = 0; + size_t maxLength = 0; DataFieldTemplates* best = nullptr; for (auto it : m_templatesByPath) { if (it.first.size() > maxLength) { @@ -478,7 +478,8 @@ result_t ScanHelper::loadScanConfigFile(symbol_t address, string* relativeFile) return RESULT_OK; } -bool ScanHelper::parseMessage(const string& arg, bool onlyMasterSlave, MasterSymbolString* master, SlaveSymbolString* slave) { +bool ScanHelper::parseMessage(const string& arg, bool onlyMasterSlave, MasterSymbolString* master, + SlaveSymbolString* slave) { size_t pos = arg.find_first_of('/'); if (pos == string::npos) { logError(lf_main, "invalid message %s: missing \"/\"", arg.c_str()); diff --git a/src/ebusd/scan.h b/src/ebusd/scan.h index d5372f354..bc8cecf58 100644 --- a/src/ebusd/scan.h +++ b/src/ebusd/scan.h @@ -21,6 +21,7 @@ #include #include +#include #include #include "lib/ebus/data.h" #include "lib/ebus/message.h" @@ -40,7 +41,6 @@ class BusHandler; * Helper class for handling device scanning and config loading. */ class ScanHelper : public Resolver { - public: /** * Constructor. diff --git a/src/lib/ebus/device.cpp b/src/lib/ebus/device.cpp index 919be0164..a53a65dd8 100755 --- a/src/lib/ebus/device.cpp +++ b/src/lib/ebus/device.cpp @@ -245,7 +245,7 @@ string Device::getEnhancedInfos() { res = requestEnhancedInfo(2); if (res != RESULT_OK) { fails += ", cannot request config"; - requestEnhancedInfo(0xff); // wait for completion + requestEnhancedInfo(0xff); // wait for completion m_infoPos = 0; m_infoId = 0xff; } @@ -266,7 +266,7 @@ string Device::getEnhancedInfos() { if (res != RESULT_OK) { fails += ", cannot request bus voltage"; } - res = requestEnhancedInfo(0xff); // wait for completion + res = requestEnhancedInfo(0xff); // wait for completion if (res != RESULT_OK) { m_enhInfoBusVoltage = "bus voltage unknown"; m_infoPos = 0; @@ -509,7 +509,7 @@ bool Device::available() { // drop first byte of invalid sequence m_bufPos = (m_bufPos + 1) % m_bufSize; m_bufLen--; - pos--; // check same pos again + pos--; // check same pos again continue; } if (cmd == ENH_RES_RECEIVED || cmd == ENH_RES_STARTED || cmd == ENH_RES_FAILED) { @@ -524,7 +524,7 @@ bool Device::available() { fprintf(stdout, "raw avail enhanced skip cmd %d @%d+%d %2.2x\n", cmd, m_bufPos, pos, ch); fflush(stdout); #endif - pos++; // skip enhanced sequence of 2 bytes + pos++; // skip enhanced sequence of 2 bytes continue; } #ifdef DEBUG_RAW_TRAFFIC @@ -537,7 +537,7 @@ bool Device::available() { // skip byte from erroneous protocol m_bufPos = (m_bufPos+1)%m_bufSize; m_bufLen--; - pos--; // check byte 2 again from scratch and allow as byte 1 + pos--; // check byte 2 again from scratch and allow as byte 1 } return false; } diff --git a/src/lib/ebus/message.cpp b/src/lib/ebus/message.cpp index 1bcb20cd6..1cd3c1371 100644 --- a/src/lib/ebus/message.cpp +++ b/src/lib/ebus/message.cpp @@ -460,7 +460,7 @@ result_t Message::create(const string& filename, const DataFieldTemplates* templ return result; } } - if (id.size() + data->getLength(pt_masterData, maxLength==MAX_POS ? MAX_POS-id.size() : maxLength) > 2 + maxLength + if (id.size() + data->getLength(pt_masterData, maxLength == MAX_POS ? MAX_POS-id.size() : maxLength) > 2 + maxLength || data->getLength(pt_slaveData, maxLength) > maxLength) { // max NN exceeded delete data; diff --git a/src/lib/utils/httpclient.cpp b/src/lib/utils/httpclient.cpp index 7460e2927..279a031f0 100755 --- a/src/lib/utils/httpclient.cpp +++ b/src/lib/utils/httpclient.cpp @@ -152,7 +152,7 @@ SSLSocket* SSLSocket::connect(const string& host, const uint16_t& port, bool htt time_t until = time(nullptr) + (timeout <= 3 ? 3 : timeout); // at least 3 seconds if (!https) { do { - bio = BIO_new_connect((char*)hostPort.c_str()); + bio = BIO_new_connect(static_cast(hostPort.c_str())); if (isError("connect", bio)) { break; } diff --git a/src/lib/utils/httpclient.h b/src/lib/utils/httpclient.h index d8f13603f..1a9bccadc 100755 --- a/src/lib/utils/httpclient.h +++ b/src/lib/utils/httpclient.h @@ -132,8 +132,7 @@ class HttpClient { m_caFile(caFile), m_caPath(caPath), #endif - m_socket(nullptr), m_port(0), m_timeout(0), m_bufferSize(0), m_buffer(nullptr) - { + m_socket(nullptr), m_port(0), m_timeout(0), m_bufferSize(0), m_buffer(nullptr) { if (init) { initialize(); } From 773a4dd661794aae12433aa5edc5715b0d1e038e Mon Sep 17 00:00:00 2001 From: John Date: Thu, 18 May 2023 13:20:33 +0200 Subject: [PATCH 066/345] improve compilability --- CMakeLists.txt | 1 + configure.ac | 2 ++ src/ebusd/Makefile.am | 1 + src/lib/ebus/device.cpp | 9 +++++++-- src/lib/utils/httpclient.cpp | 2 ++ src/lib/utils/tcpsocket.cpp | 15 ++++++++++----- 6 files changed, 23 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e4e68d0eb..2d3119307 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,6 +64,7 @@ check_include_file(sys/time.h HAVE_SYS_TIME_H) check_include_file(syslog.h HAVE_SYSLOG_H) check_include_file(time.h HAVE_TIME_H) check_include_file(termios.h HAVE_TERMIOS_H) +check_function_exists(cfsetspeed HAVE_CFSETSPEED) set(CMAKE_REQUIRED_LIBRARIES pthread rt) check_function_exists(pthread_setname_np HAVE_PTHREAD_SETNAME_NP) diff --git a/configure.ac b/configure.ac index eec309b72..ad7453ed7 100755 --- a/configure.ac +++ b/configure.ac @@ -27,6 +27,8 @@ AC_CHECK_HEADERS([arpa/inet.h \ time.h \ termios.h]) +AC_CHECK_FUNC([cfsetspeed], [AC_DEFINE(HAVE_CFSETSPEED, [1], [Defined if cfsetspeed() is available.])]) + AC_CHECK_LIB([pthread], [pthread_setname_np], AC_DEFINE([HAVE_PTHREAD_SETNAME_NP], [1], [Defined if pthread_setname_np is available.]), AC_MSG_RESULT([Could not find pthread_setname_np in pthread.])) diff --git a/src/ebusd/Makefile.am b/src/ebusd/Makefile.am index eb6307168..388268241 100644 --- a/src/ebusd/Makefile.am +++ b/src/ebusd/Makefile.am @@ -11,6 +11,7 @@ ebusd_SOURCES = \ datahandler.h datahandler.cpp \ network.h network.cpp \ mainloop.h mainloop.cpp \ + scan.h scan.cpp \ main.h main.cpp if MQTT diff --git a/src/lib/ebus/device.cpp b/src/lib/ebus/device.cpp index a53a65dd8..e8acb0696 100755 --- a/src/lib/ebus/device.cpp +++ b/src/lib/ebus/device.cpp @@ -886,7 +886,12 @@ result_t SerialDevice::open() { // create new settings memset(&newSettings, 0, sizeof(newSettings)); +#ifdef HAVE_CFSETSPEED cfsetspeed(&newSettings, m_enhancedProto ? (m_enhancedHighSpeed ? B115200 : B9600) : B2400); +#else + cfsetispeed(&newSettings, m_enhancedProto ? (m_enhancedHighSpeed ? B115200 : B9600) : B2400); + cfsetospeed(&newSettings, m_enhancedProto ? (m_enhancedHighSpeed ? B115200 : B9600) : B2400); +#endif newSettings.c_cflag |= (CS8 | CLOCAL | CREAD); newSettings.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // non-canonical mode newSettings.c_iflag |= IGNPAR; // ignore parity errors @@ -924,8 +929,8 @@ void SerialDevice::close() { } void SerialDevice::checkDevice() { - int port; - if (ioctl(m_fd, TIOCMGET, &port) == -1) { + int cnt; + if (ioctl(m_fd, FIONREAD, &cnt) == -1) { close(); } } diff --git a/src/lib/utils/httpclient.cpp b/src/lib/utils/httpclient.cpp index 279a031f0..3fa8a9aaa 100755 --- a/src/lib/utils/httpclient.cpp +++ b/src/lib/utils/httpclient.cpp @@ -435,6 +435,7 @@ bool HttpClient::request(const string& method, const string& uri, const string& string headers = result.substr(0, pos+2); // including final \r\n const char* hdrs = headers.c_str(); *response = result.substr(pos+4); +#ifdef HAVE_TIME_H if (time) { pos = headers.find("\r\nLast-Modified: "); if (pos != string::npos && headers.substr(pos+42, 4) == " GMT") { @@ -474,6 +475,7 @@ bool HttpClient::request(const string& method, const string& uri, const string& } } } +#endif pos = headers.find("\r\nContent-Length: "); if (pos == string::npos) { disconnect(); diff --git a/src/lib/utils/tcpsocket.cpp b/src/lib/utils/tcpsocket.cpp index 5e6268f7e..6c6d09a42 100755 --- a/src/lib/utils/tcpsocket.cpp +++ b/src/lib/utils/tcpsocket.cpp @@ -124,11 +124,6 @@ int tcpKeepAliveInterval) { } #endif } -#ifndef HAVE_PPOLL -#ifndef HAVE_PSELECT - timeout = 0; -#endif -#endif if (tcpConnectTimeout > 0 && fcntl(sfd, F_SETFL, O_NONBLOCK) < 0) { // set non-blocking close(sfd); return -1; @@ -140,9 +135,15 @@ int tcpKeepAliveInterval) { return -1; } if (tcpConnectTimeout > 0) { +#if defined(HAVE_PPOLL) || defined(HAVE_PSELECT) struct timespec tdiff; tdiff.tv_sec = tcpConnectTimeout; tdiff.tv_nsec = 0; +#else + struct timeval tdiff; + tdiff.tv_sec = tcpConnectTimeout; + tdiff.tv_usec = 0; +#endif #ifdef HAVE_PPOLL nfds_t nfds = 1; struct pollfd fds[nfds]; @@ -159,7 +160,11 @@ int tcpKeepAliveInterval) { FD_ZERO(&writefds); FD_ZERO(&exceptfds); FD_SET(sfd, &readfds); +#ifdef HAVE_PSELECT ret = pselect(sfd + 1, &readfds, &writefds, &exceptfds, &tdiff, nullptr); +#else + ret = select(sfd + 1, &readfds, &writefds, &exceptfds, &tdiff); +#endif if (ret >= 1 && FD_ISSET(sfd, &exceptfds)) { ret = -1; } From 8408e32d326009293464102aaa105a6353b5760e Mon Sep 17 00:00:00 2001 From: John Date: Mon, 29 May 2023 14:51:32 +0200 Subject: [PATCH 067/345] set state_clss to total_increasing for readable items with "poweron" or "count" in the name, remove no longer value state_class measurement for globals --- contrib/etc/ebusd/mqtt-hassio.cfg | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/contrib/etc/ebusd/mqtt-hassio.cfg b/contrib/etc/ebusd/mqtt-hassio.cfg index 824e9df96..2adb47753 100644 --- a/contrib/etc/ebusd/mqtt-hassio.cfg +++ b/contrib/etc/ebusd/mqtt-hassio.cfg @@ -181,6 +181,7 @@ type_switch-w-number = # HA integration: the mapping list for numeric sensor entities by field type, name, message, and unit. type_switch-number = + sensor,,total_increasing = poweron|count, sensor,temperature,measurement = temp|,°C$ sensor,power_factor,measurement = power*%% sensor,power,measurement = power|,kW$|,W$ @@ -322,7 +323,6 @@ global_prefix = { # HA integration: boolean suffix for global parts global_boolean_suffix = , - "state_class":"measurement", "payload_on":"true", "payload_off":"false" } @@ -331,8 +331,7 @@ global_boolean_suffix = , # and scan if not otherwise defined explicitly). # HA integration: the config topic for HA's MQTT discovery for the ebusd global parts. def_global-topic = %haprefix/sensor/%TOPIC/config -def_global-payload = %global_prefix, - "state_class":"measurement" +def_global-payload = %global_prefix } #def_global-retain = 0 From 4c098545463c77d746e171cdf9db28f90cd79024 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 29 May 2023 17:26:58 +0200 Subject: [PATCH 068/345] explicitly check against null, better x509 free, log timeout on connect and increase default to 5 secs, support wildcard subject --- src/lib/utils/httpclient.cpp | 39 +++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/src/lib/utils/httpclient.cpp b/src/lib/utils/httpclient.cpp index 279a031f0..355f3bfe6 100755 --- a/src/lib/utils/httpclient.cpp +++ b/src/lib/utils/httpclient.cpp @@ -149,11 +149,11 @@ SSLSocket* SSLSocket::connect(const string& host, const uint16_t& port, bool htt ostringstream ostr; ostr << host << ':' << static_cast(port); const string hostPort = ostr.str(); - time_t until = time(nullptr) + (timeout <= 3 ? 3 : timeout); // at least 3 seconds + time_t until = time(nullptr) + 1 + (timeout <= 5 ? 5 : timeout); // at least 5 seconds, 1 extra for rounding if (!https) { do { bio = BIO_new_connect(static_cast(hostPort.c_str())); - if (isError("connect", bio)) { + if (isError("connect", bio!=nullptr)) { break; } BIO_set_nbio(bio, 1); // set non-blocking @@ -163,11 +163,11 @@ SSLSocket* SSLSocket::connect(const string& host, const uint16_t& port, bool htt SSL *ssl = nullptr; do { const SSL_METHOD *method = SSLv23_method(); - if (isError("method", method)) { + if (isError("method", method!=nullptr)) { break; } ctx = SSL_CTX_new(method); - if (isError("ctx_new", ctx)) { + if (isError("ctx_new", ctx!=nullptr)) { break; } bool verifyPeer = !caFile || strcmp(caFile, "#") != 0; @@ -191,7 +191,7 @@ SSLSocket* SSLSocket::connect(const string& host, const uint16_t& port, bool htt const long flags = SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION; SSL_CTX_set_options(ctx, flags); bio = BIO_new_ssl_connect(ctx); - if (isError("new_ssl_conn", bio)) { + if (isError("new_ssl_conn", bio!=nullptr)) { break; } if (isError("conn_hostname", BIO_set_conn_hostname(bio, hostPort.c_str()), 1)) { @@ -199,7 +199,7 @@ SSLSocket* SSLSocket::connect(const string& host, const uint16_t& port, bool htt } BIO_set_nbio(bio, 1); // set non-blocking BIO_get_ssl(bio, &ssl); - if (isError("get_ssl", ssl)) { + if (isError("get_ssl", ssl!=nullptr)) { break; } SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); @@ -208,34 +208,45 @@ SSLSocket* SSLSocket::connect(const string& host, const uint16_t& port, bool htt break; } long res = BIO_do_connect(bio); - while (res <= 0 && BIO_should_retry(bio) && time(nullptr) < until) { + time_t now = 0; + while (res <= 0 && BIO_should_retry(bio)) { + if ((now=time(nullptr)) > until) { + break; + } usleep(SLEEP_NANOS); res = BIO_do_connect(bio); } + if (res <= 0 && now > until) { + logError(lf_network, "HTTP connect: timed out after %d sec", now-until); + break; + } if (isError("connect", res, 1)) { break; } X509 *cert = SSL_get_peer_certificate(ssl); - if (cert) { - X509_free(cert); - } - if (isError("peer_cert", cert)) { + if (isError("peer_cert", cert!=nullptr)) { break; } + X509_free(cert); // decrement reference count incremented by above call if (verifyPeer && isError("verify", SSL_get_verify_result(ssl), X509_V_OK)) { break; } // check hostname X509_NAME *sname = X509_get_subject_name(cert); - if (isError("get_subject", sname)) { + if (isError("get_subject", sname!=nullptr)) { break; } char peerName[64]; if (isError("subject name", X509_NAME_get_text_by_NID(sname, NID_commonName, peerName, sizeof(peerName)) > 0)) { break; } - if (isError("subject", strcmp(peerName, hostname), 0)) { - break; + if (strcmp(peerName, hostname)!=0) { + char* dotpos = NULL; + if (peerName[0]=='*' && peerName[1]=='.' && (dotpos=strchr((char*)hostname, '.')) && strcmp(peerName+2, dotpos+1)==0) { + // wildcard matches + } else if (isError("subject", 1, 0)) { + break; + } } return new SSLSocket(ctx, bio, until); } while (false); From 25a00f5d9622b8e48b4c66e2052339cd20abbbcb Mon Sep 17 00:00:00 2001 From: John Date: Tue, 30 May 2023 08:23:26 +0200 Subject: [PATCH 069/345] fix for commit e419c950 --- src/ebusd/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index a659cf4fc..757da8061 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -1025,7 +1025,7 @@ int main(int argc, char* argv[], char* envp[]) { device->getName()); // load configuration files - s_scanHelper->loadConfigFiles(s_messageMap); + s_scanHelper->loadConfigFiles(!s_opt.scanConfig); // create the MainLoop and start it s_mainLoop = new MainLoop(s_opt, device, s_messageMap, s_scanHelper); From 56aaba03ad5ea1e0fc2d6ab6a4f3dbd8afba5d07 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 30 May 2023 09:23:41 +0200 Subject: [PATCH 070/345] detecte injected scan messages and issue scan procedure for those --- src/ebusd/main.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index 757da8061..0f35089ae 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -1031,6 +1031,8 @@ int main(int argc, char* argv[], char* envp[]) { s_mainLoop = new MainLoop(s_opt, device, s_messageMap, s_scanHelper); if (s_opt.injectMessages) { BusHandler* busHandler = s_mainLoop->getBusHandler(); + int scanAdrCount = 0; + bool scanAddresses[256] = {}; while (arg_index < argc) { // add each passed message MasterSymbolString master; @@ -1039,6 +1041,18 @@ int main(int argc, char* argv[], char* envp[]) { continue; } busHandler->injectMessage(master, slave); + if (s_opt.scanConfig && master.size() >= 5 && master[4] == 0 && master[2] == 0x07 && master[3] == 0x04 + && isValidAddress(master[1], false) && !isMaster(master[1]) && !scanAddresses[master[1]]) { + // scan message, simulate scanning + scanAddresses[master[1]] = true; + scanAdrCount++; + } + } + for (symbol_t address = 0; scanAdrCount > 0; address++) { + if (scanAddresses[address]) { + scanAdrCount--; + busHandler->scanAndWait(address, true); + } } if (s_opt.stopAfterInject) { shutdown(); From 4152ea6a84eaf891d68e4b7ab32ef7cb04b71f62 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 30 May 2023 09:24:02 +0200 Subject: [PATCH 071/345] code style --- src/lib/utils/httpclient.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/lib/utils/httpclient.cpp b/src/lib/utils/httpclient.cpp index eacbf8967..5a71c6819 100755 --- a/src/lib/utils/httpclient.cpp +++ b/src/lib/utils/httpclient.cpp @@ -153,7 +153,7 @@ SSLSocket* SSLSocket::connect(const string& host, const uint16_t& port, bool htt if (!https) { do { bio = BIO_new_connect(static_cast(hostPort.c_str())); - if (isError("connect", bio!=nullptr)) { + if (isError("connect", bio != nullptr)) { break; } BIO_set_nbio(bio, 1); // set non-blocking @@ -163,11 +163,11 @@ SSLSocket* SSLSocket::connect(const string& host, const uint16_t& port, bool htt SSL *ssl = nullptr; do { const SSL_METHOD *method = SSLv23_method(); - if (isError("method", method!=nullptr)) { + if (isError("method", method != nullptr)) { break; } ctx = SSL_CTX_new(method); - if (isError("ctx_new", ctx!=nullptr)) { + if (isError("ctx_new", ctx != nullptr)) { break; } bool verifyPeer = !caFile || strcmp(caFile, "#") != 0; @@ -191,7 +191,7 @@ SSLSocket* SSLSocket::connect(const string& host, const uint16_t& port, bool htt const long flags = SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION; SSL_CTX_set_options(ctx, flags); bio = BIO_new_ssl_connect(ctx); - if (isError("new_ssl_conn", bio!=nullptr)) { + if (isError("new_ssl_conn", bio != nullptr)) { break; } if (isError("conn_hostname", BIO_set_conn_hostname(bio, hostPort.c_str()), 1)) { @@ -199,7 +199,7 @@ SSLSocket* SSLSocket::connect(const string& host, const uint16_t& port, bool htt } BIO_set_nbio(bio, 1); // set non-blocking BIO_get_ssl(bio, &ssl); - if (isError("get_ssl", ssl!=nullptr)) { + if (isError("get_ssl", ssl != nullptr)) { break; } SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); @@ -224,7 +224,7 @@ SSLSocket* SSLSocket::connect(const string& host, const uint16_t& port, bool htt break; } X509 *cert = SSL_get_peer_certificate(ssl); - if (isError("peer_cert", cert!=nullptr)) { + if (isError("peer_cert", cert != nullptr)) { break; } X509_free(cert); // decrement reference count incremented by above call @@ -233,16 +233,17 @@ SSLSocket* SSLSocket::connect(const string& host, const uint16_t& port, bool htt } // check hostname X509_NAME *sname = X509_get_subject_name(cert); - if (isError("get_subject", sname!=nullptr)) { + if (isError("get_subject", sname != nullptr)) { break; } char peerName[64]; if (isError("subject name", X509_NAME_get_text_by_NID(sname, NID_commonName, peerName, sizeof(peerName)) > 0)) { break; } - if (strcmp(peerName, hostname)!=0) { + if (strcmp(peerName, hostname) != 0) { char* dotpos = NULL; - if (peerName[0]=='*' && peerName[1]=='.' && (dotpos=strchr((char*)hostname, '.')) && strcmp(peerName+2, dotpos+1)==0) { + if (peerName[0] == '*' && peerName[1] == '.' && (dotpos=strchr((char*)hostname, '.')) + && strcmp(peerName+2, dotpos+1) == 0) { // wildcard matches } else if (isError("subject", 1, 0)) { break; From 172f8569679ff277f8c9be98318457de8ac653d0 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 30 May 2023 10:22:27 +0200 Subject: [PATCH 072/345] limit number of handled telegrams per loop and wait time for #843 --- src/ebusd/knxhandler.cpp | 21 +++++++++++---------- src/ebusd/knxhandler.h | 3 ++- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/ebusd/knxhandler.cpp b/src/ebusd/knxhandler.cpp index 082421d2a..225c93003 100644 --- a/src/ebusd/knxhandler.cpp +++ b/src/ebusd/knxhandler.cpp @@ -451,10 +451,10 @@ void KnxHandler::sendGlobalValue(global_t index, unsigned int value, bool respon } result_t KnxHandler::receiveTelegram(int maxlen, knx_transfer_t* typ, uint8_t *buf, int *recvlen, - knx_addr_t *src, knx_addr_t *dest) { + knx_addr_t *src, knx_addr_t *dest, bool wait) { struct timespec tdiff = { - .tv_sec = 2, - .tv_nsec = 0, + .tv_sec = wait ? 2 : 0, // 2 seconds when waiting + .tv_nsec = wait ? 0 : 1000, // 1 milliseond when not waiting }; if (!m_con->isConnected()) { return RESULT_ERR_GENERIC_IO; @@ -919,18 +919,19 @@ void KnxHandler::run() { knx_addr_t src, dest; knx_transfer_t typ; // APDU data starting with octet 6 according to spec, contains 2 bits of application layer - result_t res = RESULT_OK; - do { - res = receiveTelegram(sizeof(data), &typ, data, &len, &src, &dest); + // limit number of read telegrams in order to give back control to outer loop for checking updates etc + for (int count = 0; count < 10; count++) { + // wait for telegram on first iteration only + result_t res = receiveTelegram(sizeof(data), &typ, data, &len, &src, &dest, count == 0); if (res != RESULT_OK) { if (res == RESULT_ERR_GENERIC_IO) { m_con->close(); } - } else { - needsWait = false; - handleReceivedTelegram(typ, src, dest, len, data); + break; } - } while (res == RESULT_OK); + needsWait = false; + handleReceivedTelegram(typ, src, dest, len, data); + } } if (!m_updatedMessages.empty()) { m_messages->lock(); diff --git a/src/ebusd/knxhandler.h b/src/ebusd/knxhandler.h index 25f81cb52..c2ad961df 100644 --- a/src/ebusd/knxhandler.h +++ b/src/ebusd/knxhandler.h @@ -170,11 +170,12 @@ class KnxHandler : public DataSink, public DataSource, public WaitThread { * @param recvlen pointer to a variable in which to store the actually received length. * @param src pointer to a variable in which to store the source address. * @param dest pointer to a variable in which to store the destination group address. + * @param wait true to wait up to 2 seconds for a new telegram, false to not wait. * @return the result code, either RESULT_OK on success, RESULT_ERR_GENERIC_IO on I/O error (e.g. socket closed), * or RESULT_ERR_TIMEOUT if no data is available. */ result_t receiveTelegram(int maxlen, knx_transfer_t* typ, uint8_t *buf, int *recvlen, knx_addr_t *src, - knx_addr_t *dest); + knx_addr_t *dest, bool wait = true); /** * Handle a received KNX telegram. From 207435650d809b46f4f786181eb874debeb25011 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 30 May 2023 11:00:08 +0200 Subject: [PATCH 073/345] workaround for potentially faced libssl bug 11574 --- src/lib/utils/httpclient.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/utils/httpclient.cpp b/src/lib/utils/httpclient.cpp index 5a71c6819..2c4b2e02b 100755 --- a/src/lib/utils/httpclient.cpp +++ b/src/lib/utils/httpclient.cpp @@ -209,7 +209,7 @@ SSLSocket* SSLSocket::connect(const string& host, const uint16_t& port, bool htt } long res = BIO_do_connect(bio); time_t now = 0; - while (res <= 0 && BIO_should_retry(bio)) { + while (res <= 0 && (BIO_should_retry(bio) || now == 0)) { // always repeat on first failure if ((now=time(nullptr)) > until) { break; } From 3669385386d2bdd0b464be4cc8d44efb1cf9c11c Mon Sep 17 00:00:00 2001 From: John Date: Tue, 30 May 2023 15:55:48 +0200 Subject: [PATCH 074/345] add ssl debugging, assume ssl_ctx is global as documented (for #751) --- src/lib/utils/httpclient.cpp | 175 ++++++++++++++++++++--------------- src/lib/utils/httpclient.h | 6 +- 2 files changed, 102 insertions(+), 79 deletions(-) diff --git a/src/lib/utils/httpclient.cpp b/src/lib/utils/httpclient.cpp index 2c4b2e02b..937672f45 100755 --- a/src/lib/utils/httpclient.cpp +++ b/src/lib/utils/httpclient.cpp @@ -83,9 +83,6 @@ bool isError(const char* call, long result, long expected) { SSLSocket::~SSLSocket() { BIO_free_all(m_bio); - if (m_ctx) { - SSL_CTX_free(m_ctx); - } } ssize_t SSLSocket::send(const char* data, size_t len) { @@ -142,26 +139,56 @@ bool SSLSocket::isValid() { return time(nullptr) < m_until && !BIO_eof(m_bio); } +void sslInfoCallback(const SSL *ssl, int type, int val) { + if (!needsLog(lf_network, (val == 0) ? ll_error : ll_debug)) { + return; + } + logWrite(lf_network, + (val == 0) ? ll_error : ll_debug, + "SSL state %s: type 0x%x=%s%s%s%s%s%s%s%s%s val %d=%s", + SSL_state_string_long(ssl), + type, + (type & SSL_CB_LOOP) ? "loop," : "", + (type & SSL_CB_EXIT) ? "exit," : "", + (type & SSL_CB_READ) ? "read," : "", + (type & SSL_CB_WRITE) ? "write," : "", + (type & SSL_CB_ALERT) ? "alert," : "", + (type & SSL_ST_ACCEPT) ? "accept," : "", + (type & SSL_ST_CONNECT) ? "connect," : "", + (type & SSL_CB_HANDSHAKE_START) ? "start," : "", + (type & SSL_CB_HANDSHAKE_DONE) ? "done," : "", + val, + (type & SSL_CB_ALERT) ? SSL_alert_desc_string_long(val) : "?"); +} + SSLSocket* SSLSocket::connect(const string& host, const uint16_t& port, bool https, int timeout, const char* caFile, const char* caPath) { - BIO *bio = nullptr; - SSL_CTX *ctx = nullptr; ostringstream ostr; ostr << host << ':' << static_cast(port); const string hostPort = ostr.str(); time_t until = time(nullptr) + 1 + (timeout <= 5 ? 5 : timeout); // at least 5 seconds, 1 extra for rounding if (!https) { do { - bio = BIO_new_connect(static_cast(hostPort.c_str())); + BIO *bio = BIO_new_connect(static_cast(hostPort.c_str())); if (isError("connect", bio != nullptr)) { break; } BIO_set_nbio(bio, 1); // set non-blocking - return new SSLSocket(nullptr, bio, until); + return new SSLSocket(bio, until); } while (false); - } else { - SSL *ssl = nullptr; - do { + return nullptr; + } + BIO *bio = nullptr; + static SSL_CTX *ctx = nullptr; + static int sslContextInitTries = 0; + do { + // const SSL_METHOD *method = TLS_client_method(); + static bool verifyPeer = true; + if (ctx == nullptr) { // according to openssl manpage, ctx is global and should be created once only + if (sslContextInitTries > 2) { // give it up to 3 tries to initialize the context + break; + } + sslContextInitTries++; const SSL_METHOD *method = SSLv23_method(); if (isError("method", method != nullptr)) { break; @@ -170,7 +197,8 @@ SSLSocket* SSLSocket::connect(const string& host, const uint16_t& port, bool htt if (isError("ctx_new", ctx != nullptr)) { break; } - bool verifyPeer = !caFile || strcmp(caFile, "#") != 0; + SSL_CTX_set_info_callback(ctx, sslInfoCallback); + verifyPeer = !caFile || strcmp(caFile, "#") != 0; SSL_CTX_set_verify(ctx, verifyPeer ? SSL_VERIFY_PEER : SSL_VERIFY_NONE, nullptr); if (verifyPeer) { #if OPENSSL_VERSION_NUMBER >= 0x10101000L @@ -185,79 +213,78 @@ SSLSocket* SSLSocket::connect(const string& host, const uint16_t& port, bool htt } #endif if ((caFile || caPath) && isError("verify_loc", SSL_CTX_load_verify_locations(ctx, caFile, caPath), 1)) { + SSL_CTX_free(ctx); + ctx = nullptr; break; } } - const long flags = SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION; - SSL_CTX_set_options(ctx, flags); - bio = BIO_new_ssl_connect(ctx); - if (isError("new_ssl_conn", bio != nullptr)) { - break; - } - if (isError("conn_hostname", BIO_set_conn_hostname(bio, hostPort.c_str()), 1)) { - break; - } - BIO_set_nbio(bio, 1); // set non-blocking - BIO_get_ssl(bio, &ssl); - if (isError("get_ssl", ssl != nullptr)) { - break; - } - SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); - const char *hostname = host.c_str(); - if (isError("tls_host", SSL_set_tlsext_host_name(ssl, hostname), 1)) { - break; - } - long res = BIO_do_connect(bio); - time_t now = 0; - while (res <= 0 && (BIO_should_retry(bio) || now == 0)) { // always repeat on first failure - if ((now=time(nullptr)) > until) { - break; - } - usleep(SLEEP_NANOS); - res = BIO_do_connect(bio); - } - if (res <= 0 && now > until) { - logError(lf_network, "HTTP connect: timed out after %d sec", now-until); - break; - } - if (isError("connect", res, 1)) { - break; - } - X509 *cert = SSL_get_peer_certificate(ssl); - if (isError("peer_cert", cert != nullptr)) { - break; - } - X509_free(cert); // decrement reference count incremented by above call - if (verifyPeer && isError("verify", SSL_get_verify_result(ssl), X509_V_OK)) { - break; - } - // check hostname - X509_NAME *sname = X509_get_subject_name(cert); - if (isError("get_subject", sname != nullptr)) { + SSL_CTX_set_options(ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION); + } + bio = BIO_new_ssl_connect(ctx); + if (isError("new_ssl_conn", bio != nullptr)) { + break; + } + if (isError("conn_hostname", BIO_set_conn_hostname(bio, hostPort.c_str()), 1)) { + break; + } + BIO_set_nbio(bio, 1); // set non-blocking + SSL *ssl = nullptr; + BIO_get_ssl(bio, &ssl); + if (isError("get_ssl", ssl != nullptr)) { + break; + } + SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); + const char *hostname = host.c_str(); + if (isError("tls_host", SSL_set_tlsext_host_name(ssl, hostname), 1)) { + break; + } + long res = BIO_do_connect(bio); + time_t now = 0; + while (res <= 0 && (BIO_should_retry(bio) || now == 0)) { // always repeat on first failure + if ((now=time(nullptr)) > until) { break; } - char peerName[64]; - if (isError("subject name", X509_NAME_get_text_by_NID(sname, NID_commonName, peerName, sizeof(peerName)) > 0)) { + usleep(SLEEP_NANOS); + res = BIO_do_connect(bio); + } + if (res <= 0 && now > until) { + logError(lf_network, "HTTP connect: timed out after %d sec", now-until); + break; + } + if (isError("connect", res, 1)) { + break; + } + X509 *cert = SSL_get_peer_certificate(ssl); + if (isError("peer_cert", cert != nullptr)) { + break; + } + X509_free(cert); // decrement reference count incremented by above call + if (verifyPeer && isError("verify", SSL_get_verify_result(ssl), X509_V_OK)) { + break; + } + // check hostname + X509_NAME *sname = X509_get_subject_name(cert); + if (isError("get_subject", sname != nullptr)) { + break; + } + char peerName[64]; + if (isError("subject name", X509_NAME_get_text_by_NID(sname, NID_commonName, peerName, sizeof(peerName)) > 0)) { + break; + } + if (strcmp(peerName, hostname) != 0) { + char* dotpos = NULL; + if (peerName[0] == '*' && peerName[1] == '.' && (dotpos=strchr((char*)hostname, '.')) + && strcmp(peerName+2, dotpos+1) == 0) { + // wildcard matches + } else if (isError("subject", 1, 0)) { break; } - if (strcmp(peerName, hostname) != 0) { - char* dotpos = NULL; - if (peerName[0] == '*' && peerName[1] == '.' && (dotpos=strchr((char*)hostname, '.')) - && strcmp(peerName+2, dotpos+1) == 0) { - // wildcard matches - } else if (isError("subject", 1, 0)) { - break; - } - } - return new SSLSocket(ctx, bio, until); - } while (false); - } + } + return new SSLSocket(bio, until); + } while (false); if (bio) { BIO_free_all(bio); } - if (ctx) { - SSL_CTX_free(ctx); - } return nullptr; } diff --git a/src/lib/utils/httpclient.h b/src/lib/utils/httpclient.h index 1a9bccadc..94fd7a6f0 100755 --- a/src/lib/utils/httpclient.h +++ b/src/lib/utils/httpclient.h @@ -49,11 +49,10 @@ class SSLSocket { private: /** * Constructor. - * @param ctx the SSL_CTX for cleanup, or nullptr. * @param bio the BIO instance, or nullptr. * @param until the system time until the socket is allowed to be used. */ - SSLSocket(SSL_CTX *ctx, BIO *bio, time_t until) : m_ctx(ctx), m_bio(bio), m_until(until) {} + SSLSocket(BIO *bio, time_t until) : m_bio(bio), m_until(until) {} public: /** @@ -97,9 +96,6 @@ class SSLSocket { bool isValid(); private: - /** the SSL_CTX for cleanup, or nullptr. */ - SSL_CTX *m_ctx; - /** the BIO instance for communication. */ BIO *m_bio; From e2a4695c9b041a2e86b5a26a25ae20921017815d Mon Sep 17 00:00:00 2001 From: John Date: Wed, 31 May 2023 08:18:40 +0200 Subject: [PATCH 075/345] add bio logging and print set_nbio success, clear auto-retry (for #751) --- src/lib/utils/httpclient.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/lib/utils/httpclient.cpp b/src/lib/utils/httpclient.cpp index 937672f45..9268a22b9 100755 --- a/src/lib/utils/httpclient.cpp +++ b/src/lib/utils/httpclient.cpp @@ -145,7 +145,8 @@ void sslInfoCallback(const SSL *ssl, int type, int val) { } logWrite(lf_network, (val == 0) ? ll_error : ll_debug, - "SSL state %s: type 0x%x=%s%s%s%s%s%s%s%s%s val %d=%s", + "SSL state %d=%s: type 0x%x=%s%s%s%s%s%s%s%s%s val %d=%s", + SSL_get_state(ssl), SSL_state_string_long(ssl), type, (type & SSL_CB_LOOP) ? "loop," : "", @@ -161,6 +162,14 @@ void sslInfoCallback(const SSL *ssl, int type, int val) { (type & SSL_CB_ALERT) ? SSL_alert_desc_string_long(val) : "?"); } +int bioInfoCallback(BIO *bio, int state, int res) { + logDebug(lf_network, + "SSL BIO state %d res %d", + state, + res); + return 1; +} + SSLSocket* SSLSocket::connect(const string& host, const uint16_t& port, bool https, int timeout, const char* caFile, const char* caPath) { ostringstream ostr; @@ -224,16 +233,17 @@ SSLSocket* SSLSocket::connect(const string& host, const uint16_t& port, bool htt if (isError("new_ssl_conn", bio != nullptr)) { break; } + BIO_set_info_callback(bio, bioInfoCallback); if (isError("conn_hostname", BIO_set_conn_hostname(bio, hostPort.c_str()), 1)) { break; } - BIO_set_nbio(bio, 1); // set non-blocking + isError("set_nbio", BIO_set_nbio(bio, 1), 1); // set non-blocking SSL *ssl = nullptr; BIO_get_ssl(bio, &ssl); if (isError("get_ssl", ssl != nullptr)) { break; } - SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); + SSL_clear_mode(ssl, SSL_MODE_AUTO_RETRY); const char *hostname = host.c_str(); if (isError("tls_host", SSL_set_tlsext_host_name(ssl, hostname), 1)) { break; From 30cf1a926f7971563ee9716f1112a6b7523883da Mon Sep 17 00:00:00 2001 From: John Date: Wed, 31 May 2023 18:15:47 +0200 Subject: [PATCH 076/345] force use of IPv4 in libssl (see #751) --- src/lib/utils/httpclient.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/utils/httpclient.cpp b/src/lib/utils/httpclient.cpp index 9268a22b9..5e177a137 100755 --- a/src/lib/utils/httpclient.cpp +++ b/src/lib/utils/httpclient.cpp @@ -234,6 +234,7 @@ SSLSocket* SSLSocket::connect(const string& host, const uint16_t& port, bool htt break; } BIO_set_info_callback(bio, bioInfoCallback); + BIO_set_conn_ip_family(bio, 4); // force IPv4 to circumvent docker IPv6 routing issues if (isError("conn_hostname", BIO_set_conn_hostname(bio, hostPort.c_str()), 1)) { break; } From 17d92bca769b57f80364003903708a502a667b2f Mon Sep 17 00:00:00 2001 From: John Date: Wed, 31 May 2023 18:30:28 +0200 Subject: [PATCH 077/345] force logging upon exit, set level+facilities asap, log valid configpath URL --- src/ebusd/main.cpp | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index 0f35089ae..75cfa8779 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -857,20 +857,25 @@ int main(int argc, char* argv[], char* envp[]) { error_t err = argp_parse(&aargp, cnt, envargv, ARGP_PARSE_ARGV0|ARGP_SILENT|ARGP_IN_ORDER, &idx, &s_opt); if (err != 0 && idx == -1) { // ignore args for non-arg boolean options if (err == ESRCH) { // special value to abort immediately - logError(lf_main, "invalid argument in env: %s", envopt); + logWrite(lf_main, ll_error, "invalid argument in env: %s", envopt); // force logging on exit return EINVAL; } - logError(lf_main, "invalid/unknown argument in env (ignored): %s", envopt); + logWrite(lf_main, ll_error, "invalid/unknown argument in env (ignored): %s", envopt); // force logging } s_opt.injectMessages = false; // restore (was not parsed from cmdline args yet) } int arg_index = -1; if (argp_parse(&aargp, argc, argv, ARGP_IN_ORDER, &arg_index, &s_opt) != 0) { - logError(lf_main, "invalid arguments"); + logWrite(lf_main, ll_error, "invalid arguments"); // force logging on exit return EINVAL; } + if (s_opt.logAreas != -1 || s_opt.logLevel != ll_COUNT) { + setFacilitiesLogLevel(LF_ALL, ll_none); + setFacilitiesLogLevel(s_opt.logAreas, s_opt.logLevel); + } + if (!s_configPath.empty() && s_configPath[s_configPath.length()-1] != '/') { s_configPath += "/"; } @@ -882,7 +887,7 @@ int main(int argc, char* argv[], char* envp[]) { configLocalPrefix = s_configPath; } else { if (!s_opt.scanConfig) { - logError(lf_main, "invalid configpath without scanconfig"); + logWrite(lf_main, ll_error, "invalid configpath without scanconfig"); // force logging on exit return EINVAL; } size_t pos = s_configPath.find(PREVIOUS_CONFIG_PATH_SUFFIX); @@ -897,11 +902,11 @@ int main(int argc, char* argv[], char* envp[]) { if (!HttpClient::parseUrl(s_configPath, &proto, &configHost, &configPort, &configUriPrefix)) { #ifndef HAVE_SSL if (proto == "https") { - logError(lf_main, "invalid configPath URL (HTTPS not supported)"); + logWrite(lf_main, ll_error, "invalid configPath URL (HTTPS not supported)"); // force logging on exit return EINVAL; } #endif - logError(lf_main, "invalid configPath URL"); + logWrite(lf_main, ll_error, "invalid configPath URL"); // force logging on exit return EINVAL; } configHttpClient = new HttpClient(s_opt.caFile, s_opt.caPath); @@ -911,20 +916,17 @@ int main(int argc, char* argv[], char* envp[]) { // if that did not work, issue a single retry with default timeout: && !configHttpClient->connect(configHost, configPort, proto == "https", PACKAGE_NAME "/" PACKAGE_VERSION) ) { - logError(lf_main, "invalid configPath URL (connect)"); + logWrite(lf_main, ll_error, "invalid configPath URL (connect)"); // force logging on exit delete configHttpClient; cleanup(); return EINVAL; } + logInfo(lf_main, "configPath URL is valid"); configHttpClient->disconnect(); } if (!s_opt.readOnly && s_opt.scanConfig && s_opt.initialScan == 0) { s_opt.initialScan = BROADCAST; } - if (s_opt.logAreas != -1 || s_opt.logLevel != ll_COUNT) { - setFacilitiesLogLevel(LF_ALL, ll_none); - setFacilitiesLogLevel(s_opt.logAreas, s_opt.logLevel); - } s_messageMap = new MessageMap(s_opt.checkConfig, lang); s_scanHelper = new ScanHelper(s_messageMap, s_configPath, configLocalPrefix, configUriPrefix, @@ -997,14 +999,14 @@ int main(int argc, char* argv[], char* envp[]) { Device *device = Device::create(s_opt.device, s_opt.extraLatency, !s_opt.noDeviceCheck, s_opt.readOnly, s_opt.initialSend); if (device == nullptr) { - logError(lf_main, "unable to create device %s", s_opt.device); + logWrite(lf_main, ll_error, "unable to create device %s", s_opt.device); // force logging on exit cleanup(); return EINVAL; } if (!s_opt.foreground) { if (!setLogFile(s_opt.logFile)) { - logError(lf_main, "unable to open log file %s", s_opt.logFile); + logWrite(lf_main, ll_error, "unable to open log file %s", s_opt.logFile); // force logging on exit cleanup(); return EINVAL; } From 5825975d482805fdabc6a3acbdb1660d0c51d289 Mon Sep 17 00:00:00 2001 From: John Date: Wed, 31 May 2023 18:48:25 +0200 Subject: [PATCH 078/345] make capath+cafile static as they are --- src/ebusd/main.cpp | 3 ++- src/ebusd/mainloop.cpp | 2 +- src/lib/utils/httpclient.cpp | 12 ++++++++---- src/lib/utils/httpclient.h | 20 +++++++------------- 4 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index 75cfa8779..913b20472 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -882,6 +882,7 @@ int main(int argc, char* argv[], char* envp[]) { const string lang = MappedFileReader::normalizeLanguage( s_opt.preferLanguage == nullptr || !s_opt.preferLanguage[0] ? "" : s_opt.preferLanguage); string configLocalPrefix, configUriPrefix; + HttpClient::initialize(s_opt.caFile, s_opt.caPath); HttpClient* configHttpClient = nullptr; if (s_configPath.find("://") == string::npos) { configLocalPrefix = s_configPath; @@ -909,7 +910,7 @@ int main(int argc, char* argv[], char* envp[]) { logWrite(lf_main, ll_error, "invalid configPath URL"); // force logging on exit return EINVAL; } - configHttpClient = new HttpClient(s_opt.caFile, s_opt.caPath); + configHttpClient = new HttpClient(); if ( // check with low timeout of 1 second initially: !configHttpClient->connect(configHost, configPort, proto == "https", PACKAGE_NAME "/" PACKAGE_VERSION, 1) diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index d59745f98..4f426f038 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -109,7 +109,7 @@ MainLoop::MainLoop(const struct options& opt, Device *device, MessageMap* messag : Thread(), m_device(device), m_reconnectCount(0), m_userList(opt.accessLevel), m_messages(messages), m_scanHelper(scanHelper), m_address(opt.address), m_scanConfig(opt.scanConfig), m_initialScan(opt.readOnly ? ESC : opt.initialScan), m_polling(opt.pollInterval > 0), m_enableHex(opt.enableHex), - m_shutdown(false), m_runUpdateCheck(opt.updateCheck), m_httpClient(opt.caFile, opt.caPath) { + m_shutdown(false), m_runUpdateCheck(opt.updateCheck), m_httpClient() { m_device->setListener(this); // open Device result_t result = m_device->open(); diff --git a/src/lib/utils/httpclient.cpp b/src/lib/utils/httpclient.cpp index 5e177a137..0472671a9 100755 --- a/src/lib/utils/httpclient.cpp +++ b/src/lib/utils/httpclient.cpp @@ -300,18 +300,22 @@ SSLSocket* SSLSocket::connect(const string& host, const uint16_t& port, bool htt } bool HttpClient::s_initialized = false; +const char* HttpClient::s_caFile = nullptr; +const char* HttpClient::s_caPath = nullptr; -void HttpClient::initialize() { +void HttpClient::initialize(const char* caFile, const char* caPath) { if (s_initialized) { return; } s_initialized = true; + s_caFile = caFile; + s_caPath = caPath; SSL_library_init(); SSL_load_error_strings(); signal(SIGPIPE, SIG_IGN); // needed to avoid SIGPIPE when writing to a closed pipe } #else // HAVE_SSL -void HttpClient::initialize() { +void HttpClient::initialize(const char* caFile, const char* caPath) { // empty } #endif // HAVE_SSL @@ -369,7 +373,7 @@ bool HttpClient::connect(const string& host, const uint16_t port, bool https, co initialize(); disconnect(); #ifdef HAVE_SSL - m_socket = SSLSocket::connect(host, port, https, timeout, m_caFile, m_caPath); + m_socket = SSLSocket::connect(host, port, https, timeout, s_caFile, s_caPath); m_https = https; #else if (https) { @@ -393,7 +397,7 @@ bool HttpClient::reconnect() { return false; } #ifdef HAVE_SSL - m_socket = SSLSocket::connect(m_host, m_port, m_https, m_timeout, m_caFile, m_caPath); + m_socket = SSLSocket::connect(m_host, m_port, m_https, m_timeout, s_caFile, s_caPath); #else m_socket = TCPSocket::connect(m_host, m_port, m_timeout); #endif diff --git a/src/lib/utils/httpclient.h b/src/lib/utils/httpclient.h index 94fd7a6f0..89493dfe7 100755 --- a/src/lib/utils/httpclient.h +++ b/src/lib/utils/httpclient.h @@ -118,20 +118,12 @@ class HttpClient { public: /** * Constructor. - * @param caFile the CA file to use (uses defaults if neither caFile nor caPath are set), or "#" for insecure. - * @param caPath the path with CA files to use (uses defaults if neither caFile nor caPath are set). - * @param init whether to immediately initialize the library (instead of during connect()). */ - explicit HttpClient(const char* caFile = nullptr, const char* caPath = nullptr, bool init = true) : + HttpClient() : #ifdef HAVE_SSL m_https(false), - m_caFile(caFile), - m_caPath(caPath), #endif m_socket(nullptr), m_port(0), m_timeout(0), m_bufferSize(0), m_buffer(nullptr) { - if (init) { - initialize(); - } } /** @@ -146,9 +138,11 @@ class HttpClient { } /** - * Initialize HttpClient. + * Initialize the underlying SSL library. + * @param caFile the CA file to use (uses defaults if neither caFile nor caPath are set), or "#" for insecure. + * @param caPath the path with CA files to use (uses defaults if neither caFile nor caPath are set). */ - static void initialize(); + static void initialize(const char* caFile = nullptr, const char* caPath = nullptr); /** * Parse an HTTP URL. @@ -238,10 +232,10 @@ class HttpClient { bool m_https; /** the CA file to use. */ - const char* m_caFile; + static const char* s_caFile; /** the path with CA files to use. */ - const char* m_caPath; + static const char* s_caPath; #endif /** the currently connected socket. */ From b024f28e1548188c5bf73931064bc43d48241544 Mon Sep 17 00:00:00 2001 From: Robin Kluth Date: Fri, 2 Jun 2023 07:45:10 +0200 Subject: [PATCH 079/345] Update mqtt-hassio.cfg Updates MQTT discovery link --- contrib/etc/ebusd/mqtt-hassio.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/etc/ebusd/mqtt-hassio.cfg b/contrib/etc/ebusd/mqtt-hassio.cfg index 2adb47753..684fbea6a 100644 --- a/contrib/etc/ebusd/mqtt-hassio.cfg +++ b/contrib/etc/ebusd/mqtt-hassio.cfg @@ -1,7 +1,7 @@ # Configuration file for ebusd MQTT integration with Home Assistant (https://www.home-assistant.io/). # Use this file with ebusd in MQTT JSON mode to have many seen messages automatically appear on HA. This is achieved by -# using the MQTT Discovery feature of Home Assistant (see https://www.home-assistant.io/docs/mqtt/discovery/). +# using the MQTT Discovery feature of Home Assistant (see https://www.home-assistant.io/integrations/mqtt#mqtt-discovery). # The commandline options to ebusd should contain e.g.: # --mqttport=1883 --mqttjson --mqttint=/etc/ebusd/mqtt-hassio.cfg From 699f056381ffd886b60ba04b8a08f8155670eacd Mon Sep 17 00:00:00 2001 From: John Date: Sat, 3 Jun 2023 10:34:32 +0200 Subject: [PATCH 080/345] add value list support --- contrib/etc/ebusd/mqtt-hassio.cfg | 21 ++++++++++++++++++- contrib/etc/ebusd/mqtt-integration.cfg | 11 ++++++++++ src/ebusd/mqtthandler.cpp | 28 ++++++++++++++++++++++++++ src/lib/ebus/data.h | 4 ++++ 4 files changed, 63 insertions(+), 1 deletion(-) diff --git a/contrib/etc/ebusd/mqtt-hassio.cfg b/contrib/etc/ebusd/mqtt-hassio.cfg index 2adb47753..ec8c76fb6 100644 --- a/contrib/etc/ebusd/mqtt-hassio.cfg +++ b/contrib/etc/ebusd/mqtt-hassio.cfg @@ -200,12 +200,13 @@ type_switch-number = type_switch-w-list = switch,, = onoff switch,,,yesno = yesno + select,, = # HA integration: the mapping list for rather binary sensor entities by field type, name, message, and unit. type_switch-list = binary_sensor,,measurement = onoff binary_sensor,,measurement,yesno = yesno - sensor,, = + sensor,,,list = # HA integration: currently unused mapping lists for non-numeric/non-binary entities. #type_switch-string = @@ -278,6 +279,24 @@ type_part-binary_sensoryesno = , "payload_on":"yes", "payload_off":"no"%state_class +# optional format string for converting a fields value list into %field_values. +# "$value" and "$text" are being replaced by the corresponding part. +field_values-entry = "$text" +# optional separator for concatenating of field value list items. +field_values-separator = , +# optional prefix for surrounding field value list items. +field_values-prefix = [ +# optional suffix for surrounding field value list items. +field_values-suffix = ] + +# HA integration: %type_part variable for select %type_topic and sensor with list of known values +type_part-select = , + "command_topic":"%topic/set", + "options":%field_values +type_part-sensorlist = , + "device_class":"enum", + "options":%field_values + # the field specific part (evaluated after the message specific part). # HA integration: set to the mapped %type_part from above field_payload = %type_part diff --git a/contrib/etc/ebusd/mqtt-integration.cfg b/contrib/etc/ebusd/mqtt-integration.cfg index d07e3d4e6..d87a287e7 100644 --- a/contrib/etc/ebusd/mqtt-integration.cfg +++ b/contrib/etc/ebusd/mqtt-integration.cfg @@ -149,6 +149,17 @@ type_map-datetime = string #type_part-number = , +# optional format string for converting a fields value list into %field_values. +# "$value" and "$text" are being replaced by the corresponding part. +#field_values-entry = $text +# optional separator for concatenating of field value list items. +#field_values-separator = , +# optional prefix for surrounding field value list items. +#field_values-prefix = +# optional suffix for surrounding field value list items. +#field_values-suffix = + + # the field specific part (evaluated after the message specific part). #field_payload = %type_part diff --git a/src/ebusd/mqtthandler.cpp b/src/ebusd/mqtthandler.cpp index e51d6f172..a4575062a 100755 --- a/src/ebusd/mqtthandler.cpp +++ b/src/ebusd/mqtthandler.cpp @@ -1111,6 +1111,34 @@ void MqttHandler::run() { } values.set("step", ostr.str()); } + if (dataType->isNumeric() && field->isList() && !values["field_values-entry"].empty()) { + auto vl = (dynamic_cast(field))->getList(); + string entryFormat = values["field_values-entry"]; + string::size_type pos = -1; + while ((pos = entryFormat.find('$', pos+1)) != std::string::npos) { + if (entryFormat.substr(pos+1, 4) == "text" || entryFormat.substr(pos+1, 5) == "value") { + entryFormat.replace(pos, 1, "%"); + } + } + entryFormat.replace(0, 0, "entry = "); + string result = values["field_values-prefix"]; + bool first = true; + for (const auto& it : vl) { + StringReplacers entry; + entry.parseLine(entryFormat); + entry.set("value", it.first); + entry.set("text", it.second); + entry.reduce(); + if (first) { + first = false; + } else { + result += values["field_values-separator"]; + } + result += entry.get("entry", false, false); + } + result += values["field_values-suffix"]; + values.set("field_values", result); + } if (!m_typeSwitches.empty()) { values.reduce(true); str = values.get("type_switch-by", false, false); diff --git a/src/lib/ebus/data.h b/src/lib/ebus/data.h index d154febfc..49b11c518 100755 --- a/src/lib/ebus/data.h +++ b/src/lib/ebus/data.h @@ -551,6 +551,10 @@ class ValueListDataField : public SingleDataField { // @copydoc void dump(bool prependFieldSeparator, OutputFormat outputFormat, ostream* output) const override; + /** + * @return the value=text assignments. + */ + const map& getList() const { return m_values; } protected: // @copydoc From 7134c82848a3103187b0de75ca17b4a0402c6747 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 3 Jun 2023 19:52:19 +0200 Subject: [PATCH 081/345] prefer const iterator --- src/lib/ebus/datatype.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/ebus/datatype.h b/src/lib/ebus/datatype.h index 34ac913e9..1b9a2d6e6 100755 --- a/src/lib/ebus/datatype.h +++ b/src/lib/ebus/datatype.h @@ -676,13 +676,13 @@ class DataTypeList { * Returns an iterator pointing to the first ID/@a DataType pair. * @return an iterator pointing to the first ID/@a DataType pair. */ - map::const_iterator begin() const { return m_typesById.begin(); } + map::const_iterator begin() const { return m_typesById.cbegin(); } /** * Returns an iterator pointing one past the last ID/@a DataType pair. * @return an iterator pointing one past the last ID/@a DataType pair. */ - map::const_iterator end() const { return m_typesById.end(); } + map::const_iterator end() const { return m_typesById.cend(); } private: /** the known @a DataType instances by ID (e.g. "ID:BITS" or just "ID"). From d89a5289cf864e5f5a6092f9e38607ac77363910 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 3 Jun 2023 19:53:05 +0200 Subject: [PATCH 082/345] fix potential (but unlikely) endless loop --- src/ebusd/knxhandler.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ebusd/knxhandler.cpp b/src/ebusd/knxhandler.cpp index 225c93003..a8746bcec 100644 --- a/src/ebusd/knxhandler.cpp +++ b/src/ebusd/knxhandler.cpp @@ -939,6 +939,7 @@ void KnxHandler::run() { for (auto it = m_updatedMessages.begin(); it != m_updatedMessages.end(); ) { const vector* messages = m_messages->getByKey(it->first); if (!messages) { + it = m_updatedMessages.erase(it); continue; } for (const auto& message : *messages) { From 1222eb165e06ff3af9e9b12d43523fa870b9da1d Mon Sep 17 00:00:00 2001 From: John Date: Sat, 3 Jun 2023 19:55:58 +0200 Subject: [PATCH 083/345] copy cmd/resp in messageCompleted() for potential edge cases --- src/ebusd/bushandler.cpp | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/ebusd/bushandler.cpp b/src/ebusd/bushandler.cpp index ded986d7e..3c968f5c2 100644 --- a/src/ebusd/bushandler.cpp +++ b/src/ebusd/bushandler.cpp @@ -1178,10 +1178,10 @@ bool BusHandler::addSeenAddress(symbol_t address) { void BusHandler::messageCompleted() { const char* prefix = m_currentRequest ? "sent" : "received"; - if (m_currentRequest) { - m_command = m_currentRequest->m_master; - } - symbol_t srcAddress = m_command[0], dstAddress = m_command[1]; + // do an explicit copy here in case being called by another thread + const MasterSymbolString command = m_currentRequest ? m_currentRequest->m_master : m_command; + SlaveSymbolString response = m_response; + symbol_t srcAddress = command[0], dstAddress = command[1]; if (srcAddress == dstAddress) { logError(lf_bus, "invalid self-addressed message from %2.2x", srcAddress); return; @@ -1192,8 +1192,8 @@ void BusHandler::messageCompleted() { bool master = isMaster(dstAddress); if (dstAddress == BROADCAST) { - logInfo(lf_update, "%s BC cmd: %s", prefix, m_command.getStr().c_str()); - if (m_command.getDataSize() >= 10 && m_command[2] == 0x07 && m_command[3] == 0x04) { + logInfo(lf_update, "%s BC cmd: %s", prefix, command.getStr().c_str()); + if (command.getDataSize() >= 10 && command[2] == 0x07 && command[3] == 0x04) { symbol_t slaveAddress = getSlaveAddress(srcAddress); addSeenAddress(slaveAddress); Message* message = m_messages->getScanMessage(slaveAddress); @@ -1207,7 +1207,7 @@ void BusHandler::messageCompleted() { SlaveSymbolString idData; idData.push_back(10); for (size_t i = 0; i < 10; i++) { - idData.push_back(m_command.dataAt(i)); + idData.push_back(command.dataAt(i)); } result = message->storeLastData(0, idData); if (result == RESULT_OK) { @@ -1223,13 +1223,13 @@ void BusHandler::messageCompleted() { } } } else if (master) { - logInfo(lf_update, "%s MM cmd: %s", prefix, m_command.getStr().c_str()); + logInfo(lf_update, "%s MM cmd: %s", prefix, command.getStr().c_str()); } else { - logInfo(lf_update, "%s MS cmd: %s / %s", prefix, m_command.getStr().c_str(), m_response.getStr().c_str()); - if (m_command.size() >= 5 && m_command[2] == 0x07 && m_command[3] == 0x04) { + logInfo(lf_update, "%s MS cmd: %s / %s", prefix, command.getStr().c_str(), response.getStr().c_str()); + if (command.size() >= 5 && command[2] == 0x07 && command[3] == 0x04) { Message* message = m_messages->getScanMessage(dstAddress); if (message && (message->getLastUpdateTime() == 0 || message->getLastSlaveData().getDataSize() < 10)) { - result_t result = message->storeLastData(m_command, m_response); + result_t result = message->storeLastData(command, response); if (result == RESULT_OK) { ostringstream output; result = message->decodeLastData(true, nullptr, -1, OF_NONE, &output); @@ -1242,24 +1242,24 @@ void BusHandler::messageCompleted() { } } } - Message* message = m_messages->find(m_command); + Message* message = m_messages->find(command); if (m_grabMessages) { uint64_t key; if (message) { key = message->getKey(); } else { - key = Message::createKey(m_command, m_command[1] == BROADCAST ? 1 : 4); // up to 4 DD bytes (1 for broadcast) + key = Message::createKey(command, command[1] == BROADCAST ? 1 : 4); // up to 4 DD bytes (1 for broadcast) } - m_grabbedMessages[key].setLastData(m_command, m_response); + m_grabbedMessages[key].setLastData(command, response); } if (message == nullptr) { if (dstAddress == BROADCAST) { - logNotice(lf_update, "%s unknown BC cmd: %s", prefix, m_command.getStr().c_str()); + logNotice(lf_update, "%s unknown BC cmd: %s", prefix, command.getStr().c_str()); } else if (master) { - logNotice(lf_update, "%s unknown MM cmd: %s", prefix, m_command.getStr().c_str()); + logNotice(lf_update, "%s unknown MM cmd: %s", prefix, command.getStr().c_str()); } else { - logNotice(lf_update, "%s unknown MS cmd: %s / %s", prefix, m_command.getStr().c_str(), - m_response.getStr().c_str()); + logNotice(lf_update, "%s unknown MS cmd: %s / %s", prefix, command.getStr().c_str(), + response.getStr().c_str()); } } else { m_messages->invalidateCache(message); @@ -1269,14 +1269,14 @@ void BusHandler::messageCompleted() { : message->isPassive() ? message->isWrite() ? "update-write" : "update-read" : message->getPollPriority() > 0 ? message->isWrite() ? "poll-write" : "poll-read" : message->isWrite() ? "write" : "read"; - result_t result = message->storeLastData(m_command, m_response); + result_t result = message->storeLastData(command, response); ostringstream output; if (result == RESULT_OK) { result = message->decodeLastData(false, nullptr, -1, OF_NONE, &output); } if (result < RESULT_OK) { logError(lf_update, "unable to parse %s %s %s from %s / %s: %s", mode, circuit.c_str(), name.c_str(), - m_command.getStr().c_str(), m_response.getStr().c_str(), getResultCode(result)); + command.getStr().c_str(), response.getStr().c_str(), getResultCode(result)); } else { string data = output.str(); if (m_answer && dstAddress == (master ? m_ownMasterAddress : m_ownSlaveAddress)) { From 746b79d17e988c47ad49f8b2f3691f0163113208 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 4 Jun 2023 08:11:51 +0200 Subject: [PATCH 084/345] fix previous commit --- src/ebusd/bushandler.cpp | 4 ++-- src/lib/ebus/symbol.h | 26 ++++++++++++-------------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/ebusd/bushandler.cpp b/src/ebusd/bushandler.cpp index 3c968f5c2..535337f66 100644 --- a/src/ebusd/bushandler.cpp +++ b/src/ebusd/bushandler.cpp @@ -1179,8 +1179,8 @@ bool BusHandler::addSeenAddress(symbol_t address) { void BusHandler::messageCompleted() { const char* prefix = m_currentRequest ? "sent" : "received"; // do an explicit copy here in case being called by another thread - const MasterSymbolString command = m_currentRequest ? m_currentRequest->m_master : m_command; - SlaveSymbolString response = m_response; + const MasterSymbolString command(m_currentRequest ? m_currentRequest->m_master : m_command); + const SlaveSymbolString response(m_response); symbol_t srcAddress = command[0], dstAddress = command[1]; if (srcAddress == dstAddress) { logError(lf_bus, "invalid self-addressed message from %2.2x", srcAddress); diff --git a/src/lib/ebus/symbol.h b/src/lib/ebus/symbol.h index bae2b2c20..4fa6902c5 100755 --- a/src/lib/ebus/symbol.h +++ b/src/lib/ebus/symbol.h @@ -370,6 +370,12 @@ class MasterSymbolString : public SymbolString { */ MasterSymbolString() : SymbolString(true) {} + /** + * Copy constructor. + * @param str the @a MasterSymbolString to copy from. + */ + MasterSymbolString(const MasterSymbolString& str) : SymbolString(str) {} + MasterSymbolString& operator=(const MasterSymbolString& other) { this->m_data = other.m_data; this->m_isMaster = true; @@ -381,13 +387,6 @@ class MasterSymbolString : public SymbolString { this->m_isMaster = true; return *this; } - - private: - /** - * Copy constructor. - * @param str the @a MasterSymbolString to copy from. - */ - MasterSymbolString(const MasterSymbolString& str) : SymbolString(str) {} }; @@ -401,6 +400,12 @@ class SlaveSymbolString : public SymbolString { */ SlaveSymbolString() : SymbolString(false) {} + /** + * Copy constructor. + * @param str the @a SlaveSymbolString to copy from. + */ + SlaveSymbolString(const SlaveSymbolString& str) : SymbolString(str) {} + SlaveSymbolString& operator=(const SlaveSymbolString& other) { this->m_data = other.m_data; this->m_isMaster = false; @@ -412,13 +417,6 @@ class SlaveSymbolString : public SymbolString { this->m_isMaster = false; return *this; } - - private: - /** - * Copy constructor. - * @param str the @a SlaveSymbolString to copy from. - */ - SlaveSymbolString(const SlaveSymbolString& str) : SymbolString(str) {} }; From 7f0328cbae32e60090d8517a1734c0fcf1fc0e9c Mon Sep 17 00:00:00 2001 From: John Date: Sun, 4 Jun 2023 08:28:07 +0200 Subject: [PATCH 085/345] updated [skip ci] --- .github/ISSUE_TEMPLATE/bug_report.yml | 6 ++++-- .github/ISSUE_TEMPLATE/feature_request.yml | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index f1ca65c1c..7f112af5b 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -94,6 +94,10 @@ body: label: Hardware interface description: the eBUS hardware interface in use options: + - adapter 5 via USB + - adapter 5 via WiFi + - adapter 5 via Ethernet + - adapter 5 via Raspberry GPIO - adapter 3.1 USB - adapter 3.1 WiFi - adapter 3.1 Ethernet @@ -103,8 +107,6 @@ body: - adapter 3.0 Ethernet - adapter 3.0 RPi - adapter 2 - - Esera USB - - Esera Ethernet - other validations: required: true diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index bc0428a23..f1b1b6681 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -8,7 +8,7 @@ body: id: description attributes: label: Description - description: A clear and concise description of the requested feature, additional behaviour, different functionality. Anything related to actual message definitions in ebusd-configuration, serial bridge in ebusd-esp, or adapter v3/v2 do not belong here! Use the appropriate repository for those. - placeholder: e.g. during startup, an error message as described below is reported instead of... + description: A clear and concise description of the requested feature, additional behaviour, different functionality. Anything related to actual message definitions in ebusd-configuration, serial bridge in ebusd-esp, or adapter v5/v3/v2 do not belong here! Use the appropriate repository for those. + placeholder: e.g. add direct integration with xxx... validations: required: true From dd7c668af463debaf9b05a9b0b9a38bd33e8b710 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 11 Jun 2023 10:46:07 +0200 Subject: [PATCH 086/345] add updatecheck for v5 --- contrib/updatecheck/calcversions.sh | 6 ++++-- contrib/updatecheck/index.php | 4 ++-- contrib/updatecheck/prepend.inc | 14 +++++++++++--- src/lib/ebus/device.cpp | 4 ++-- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/contrib/updatecheck/calcversions.sh b/contrib/updatecheck/calcversions.sh index 533a94836..42ffb2cb3 100755 --- a/contrib/updatecheck/calcversions.sh +++ b/contrib/updatecheck/calcversions.sh @@ -3,9 +3,11 @@ version=`head -n 1 ../../VERSION` revision=`git describe --always` echo "ebusd=${version},${revision}" > versions.txt echo "ebusd=${version},${revision}" > oldversions.txt -devver=`curl -s https://adapter.ebusd.eu/firmware/ChangeLog|grep "Version "|head -n 1|sed -e 's#.*]*>##' -e 's#<.*##' -e 's# ##'` -devbl=`curl -s https://adapter.ebusd.eu/firmware/ChangeLog|grep "Bootloader "|head -n 1|sed -e 's#.*]*>##' -e 's#<.*##' -e 's# ##'` +devver=`curl -s https://adapter.ebusd.eu/v31/firmware/ChangeLog|grep "Version "|head -n 1|sed -e 's#.*]*>##' -e 's#<.*##' -e 's# ##'` +devbl=`curl -s https://adapter.ebusd.eu/v31/firmware/ChangeLog|grep "Bootloader version"|head -n 1|sed -e 's#.*]*>##' -e 's#<.*##' -e 's# ##'` echo "device=${devver},${devbl}" >> versions.txt +devver=`curl -s https://adapter.ebusd.eu/v5/ChangeLog|grep "Version "|head -n 1|sed -e 's#.*]*>##' -e 's#<.*##' -e 's# ##'` +echo "device=${devver},${devver}" >> versions.txt files=`find config/de -type f -or -type l` ../../src/lib/ebus/test/test_filereader $files|sed -e 's#^config/de/##' -e 's#^\([^ ]*\) #\1=#' -e 's# #,#g'|sort >> versions.txt files=`find config/en -type f -or -type l` diff --git a/contrib/updatecheck/index.php b/contrib/updatecheck/index.php index 230a0767b..74a10897e 100644 --- a/contrib/updatecheck/index.php +++ b/contrib/updatecheck/index.php @@ -23,11 +23,11 @@ } ?>

latest ebusd version:

-

latest device firmware:

+

latest device firmware: v5=, v3*=

config files:"; diff --git a/contrib/updatecheck/prepend.inc b/contrib/updatecheck/prepend.inc index 5b422f07b..16f4792ef 100644 --- a/contrib/updatecheck/prepend.inc +++ b/contrib/updatecheck/prepend.inc @@ -10,7 +10,12 @@ function readVersions($old=false) { global $versions; $val = explode('=', $val); if (count($val)>1) { - $versions[$val[0]] = explode(',', $val[1]); + $key = $val[0]; + $val = explode(',', $val[1]); + if ($key==='device' && count($val)===2 && $val[0]===$val[1]) { + $key = 'devicesame'; + } + $versions[$key] = $val; } }; array_walk($v, $func); @@ -33,8 +38,11 @@ function checkUpdate($ebusdVersion, $ebusdRelease, $architecture, $deviceVersion if ($deviceVersion && @$versions['device']) { $feat = strtok($deviceVersion, '.'); $ver = strtok('.'); - if ($ver && $ver!==$versions['device'][0]) { - $ret .= ', device firmware '.$versions['device'][0].' available'; + if ($ver) { + $key = ($ver===strtok('.')) ? 'devicesame' : 'device'; + if ($ver!==$versions[$key][0]) { + $ret .= ', device firmware '.$versions[$key][0].' available'; + } } } $newerAvailable = 0; diff --git a/src/lib/ebus/device.cpp b/src/lib/ebus/device.cpp index e8acb0696..0ab34b443 100755 --- a/src/lib/ebus/device.cpp +++ b/src/lib/ebus/device.cpp @@ -710,8 +710,8 @@ bool Device::handleEnhancedBufferedData(symbol_t* value, ArbitrationState* arbit case 0x0200: case 0x0500: // with firmware version and jumper info case 0x0800: // with firmware version, jumper info, and bootloader version - stream << static_cast(m_infoBuf[0]) << "." // version minor - << std::hex << static_cast(m_infoBuf[1]); // features mask + stream << std::hex << static_cast(m_infoBuf[1]) // features mask + << static_cast(m_infoBuf[0]) << "."; // version minor if (m_infoLen >= 5) { stream << "[" << std::setfill('0') << std::setw(2) << std::hex << static_cast(m_infoBuf[2]) << std::setw(2) << static_cast(m_infoBuf[3]) << "]"; From c66bd85e03883270258f38b6c6988fd6b362d2e0 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 11 Jun 2023 12:04:48 +0200 Subject: [PATCH 087/345] fix invalid iterator usage (maybe related to #925) --- src/lib/ebus/stringhelper.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/ebus/stringhelper.cpp b/src/lib/ebus/stringhelper.cpp index 2ff6e6eed..449c8df54 100644 --- a/src/lib/ebus/stringhelper.cpp +++ b/src/lib/ebus/stringhelper.cpp @@ -510,13 +510,13 @@ void StringReplacers::reduce(bool compress) { ++it; continue; } - bool restart = set(it->first, str, false); + string key = it->first; it = m_replacers.erase(it); + bool restart = set(key, str, false); reduced = true; if (restart) { - string upper = it->first; - transform(upper.begin(), upper.end(), upper.begin(), ::toupper); - if (m_replacers.erase(upper) > 0) { + transform(key.begin(), key.end(), key.begin(), ::toupper); + if (m_replacers.erase(key) > 0) { break; // restart as iterator is now invalid } } From eb2af024a291cfb38a3fcc77fb034ed84bf04767 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 16 Jun 2023 18:01:41 +0200 Subject: [PATCH 088/345] fix previous commit --- src/lib/ebus/device.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/ebus/device.cpp b/src/lib/ebus/device.cpp index 0ab34b443..6a946390e 100755 --- a/src/lib/ebus/device.cpp +++ b/src/lib/ebus/device.cpp @@ -711,7 +711,7 @@ bool Device::handleEnhancedBufferedData(symbol_t* value, ArbitrationState* arbit case 0x0500: // with firmware version and jumper info case 0x0800: // with firmware version, jumper info, and bootloader version stream << std::hex << static_cast(m_infoBuf[1]) // features mask - << static_cast(m_infoBuf[0]) << "."; // version minor + << "." << static_cast(m_infoBuf[0]); // version minor if (m_infoLen >= 5) { stream << "[" << std::setfill('0') << std::setw(2) << std::hex << static_cast(m_infoBuf[2]) << std::setw(2) << static_cast(m_infoBuf[3]) << "]"; From 3d5bec37a55ed9f35cb4bc2321e722e1a18c6ae4 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 16 Jun 2023 18:02:22 +0200 Subject: [PATCH 089/345] updated --- ChangeLog.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index b966d24d4..57302f311 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,4 +1,20 @@ -# 23.1 (2022-12-06) +# 23.2 (tbd) +## Bug Fixes +* fix bounds check for variable length datatypes +* add timeout for enhanced protocol info exchange and allow more of them being optional +* fix some warnings in Home Assistant MQTT discovery integration +* fix for high traffic on KNX integration +* add workaround for libssl issues, add debug logging and force it using IPv4 +* fix log level potentially set too late during startup + +## Features +* add log entry for unstartable TCP ports +* add support for injecting scan messages being used for scan procedure +* add value lists support to MQTT integration and use it for Home Assistant MQTT discovery integration +* add update check for v5 device + + +# 23.1 (2023-01-06) ## Bug Fixes * fix potentially invalid settings picked up from environment variables * fix potentially unnecessary arbitration start for non-enhanced proto From 6fc44800e36d5cc99d99abd7146a0be7cab82fd8 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 16 Jun 2023 18:41:11 +0200 Subject: [PATCH 090/345] updated link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 79d75fb3f..6ae345f49 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ The main features of the daemon are: * TCP * UDP * enhanced ebusd protocol allowing arbitration to be done directly by the hardware, e.g. for recent - * [ebus adapter 3](https://adapter.ebusd.eu/), or + * [ebus adapter 5](https://adapter.ebusd.eu/v5/), or * [ebusd-esp firmware](https://github.com/john30/ebusd-esp/) * actively send messages to and receive answers from the eBUS * passively listen to messages sent on the eBUS From 9c74288ba9673c649d9692c4938cbc3d11712c5c Mon Sep 17 00:00:00 2001 From: John Date: Fri, 16 Jun 2023 19:23:32 +0200 Subject: [PATCH 091/345] delay check only if scan succeeded --- src/ebusd/mainloop.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index 4f426f038..ee5be00f6 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -317,13 +317,13 @@ void MainLoop::run() { } } else { scanStatus = SCAN_STATUS_RUNNING; - nextCheckRun = now + CHECK_INITIAL_DELAY; result_t result = m_busHandler->scanAndWait(lastScanAddress, true); taskDelay = (result == RESULT_ERR_NO_SIGNAL) ? 10 : 1; if (result != RESULT_OK) { logError(lf_main, "scan config %2.2x: %s", lastScanAddress, getResultCode(result)); } else { logInfo(lf_main, "scan config %2.2x message received", lastScanAddress); + nextCheckRun = now + CHECK_INITIAL_DELAY; // delay update check due to new scan data } } } @@ -354,6 +354,7 @@ void MainLoop::run() { #endif PACKAGE_NAME "/" PACKAGE_VERSION)) { logError(lf_main, "update check connect error"); + nextCheckRun = now + CHECK_INITIAL_DELAY; } else { ostringstream ostr; ostr << "{\"v\":\"" PACKAGE_VERSION "\",\"r\":\"" REVISION << "\"" @@ -394,8 +395,8 @@ void MainLoop::run() { } } } + nextCheckRun = now + CHECK_DELAY; } - nextCheckRun = now + CHECK_DELAY; } time(&lastTaskRun); } From 9d08eec68e37dc2f5afdaa134e9c97c61a797dc5 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 16 Jun 2023 19:45:29 +0200 Subject: [PATCH 092/345] better iterator --- src/lib/ebus/data.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/ebus/data.cpp b/src/lib/ebus/data.cpp index 7b8cf1252..4500771c4 100644 --- a/src/lib/ebus/data.cpp +++ b/src/lib/ebus/data.cpp @@ -793,9 +793,9 @@ result_t ValueListDataField::writeSymbols(size_t offset, istringstream* input, return numType->writeRawValue(numType->getReplacement(), offset, m_length, output, usedLength); } - for (map::const_iterator it = m_values.begin(); it != m_values.end(); ++it) { - if (it->second == inputStr) { - return numType->writeRawValue(it->first, offset, m_length, output, usedLength); + for (const auto& it : m_values) { + if (it.second == inputStr) { + return numType->writeRawValue(it.first, offset, m_length, output, usedLength); } } const char* str = inputStr.c_str(); From 0ede430ece73a2282f3c40b6e56e88d46abf07c8 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 16 Jun 2023 20:26:30 +0200 Subject: [PATCH 093/345] extra layer for device, more abstraction --- src/ebusd/main.cpp | 9 +- src/ebusd/mainloop.cpp | 34 +------- src/ebusd/mqtthandler.cpp | 2 +- src/lib/ebus/device.cpp | 132 ++++++++++++++++++++--------- src/lib/ebus/device.h | 169 +++++++++++++++++++++++++++----------- 5 files changed, 224 insertions(+), 122 deletions(-) diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index 913b20472..a73cd78fe 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -1019,13 +1019,14 @@ int main(int argc, char* argv[], char* envp[]) { signal(SIGINT, signalHandler); signal(SIGTERM, signalHandler); - logNotice(lf_main, PACKAGE_STRING "." REVISION " started%s%s on%s device %s", - device->isReadOnly() ? " read only" : "", + ostringstream ostream; + device->formatInfo(&ostream, false, false, true); + string deviceInfoStr = ostream.str(); + logNotice(lf_main, PACKAGE_STRING "." REVISION " started%s on device: %s", s_opt.scanConfig ? s_opt.initialScan == ESC ? " with auto scan" : s_opt.initialScan == BROADCAST ? " with broadcast scan" : s_opt.initialScan == SYN ? " with full scan" : " with single scan" : "", - device->isEnhancedProto() ? " enhanced" : "", - device->getName()); + deviceInfoStr.c_str()); // load configuration files s_scanHelper->loadConfigFiles(!s_opt.scanConfig); diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index ee5be00f6..5d242fb98 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -372,12 +372,7 @@ void MainLoop::run() { << ",\"a\":\"other\"" #endif << ",\"u\":" << (now-start); - if (m_device->isEnhancedProto()) { - string ver = m_device->getEnhancedVersion(); - if (!ver.empty()) { - ostr << ",\"dv\":\"" << ver << "\""; - } - } + m_device->formatInfo(&ostr, false, true, true); if (m_reconnectCount) { ostr << ",\"rc\":" << m_reconnectCount; } @@ -1944,31 +1939,8 @@ result_t MainLoop::executeInfo(const vector& args, const string& user, o if (!m_updateCheck.empty()) { *ostream << "update check: " << m_updateCheck << "\n"; } - *ostream << "device: " << m_device->getName(); - if (m_device->isEnhancedProto()) { - *ostream << ", enhanced"; - } - if (m_device->isReadOnly()) { - *ostream << ", readonly"; - } - if (!m_device->isValid()) { - *ostream << ", invalid"; - } - bool infoAdded = false; - if (verbose) { - string info = m_device->getEnhancedInfos(); - if (!info.empty()) { - *ostream << ", " << info; - infoAdded = true; - } - } - if (!infoAdded) { - string info = m_device->getEnhancedVersion(); - if (!info.empty()) { - *ostream << ", firmware " << info; - } - } - + *ostream << "device: "; + m_device->formatInfo(ostream, verbose); *ostream << "\n"; if (!user.empty()) { *ostream << "user: " << user << "\n"; diff --git a/src/ebusd/mqtthandler.cpp b/src/ebusd/mqtthandler.cpp index a4575062a..b88c510b5 100755 --- a/src/ebusd/mqtthandler.cpp +++ b/src/ebusd/mqtthandler.cpp @@ -952,7 +952,7 @@ void MqttHandler::run() { publishDefinition(m_replacers, "def_global_uptime-", uptimeTopic, "global", "uptime", "def_global-"); publishDefinition(m_replacers, "def_global_updatecheck-", m_globalTopic.get("", "updatecheck"), "global", "updatecheck", "def_global-"); - if (m_busHandler->getDevice()->supportsEnhancedInfos()) { + if (m_busHandler->getDevice()->supportsUpdateCheck()) { publishDefinition(m_replacers, "def_global_updatecheck_device-", m_globalTopic.get("", "updatecheck"), "global", "updatecheck_device", ""); } diff --git a/src/lib/ebus/device.cpp b/src/lib/ebus/device.cpp index 6a946390e..f96a4ac9b 100755 --- a/src/lib/ebus/device.cpp +++ b/src/lib/ebus/device.cpp @@ -75,25 +75,8 @@ namespace ebusd { #define ENH_BYTE2 ((uint8_t)0x80) #define makeEnhancedSequence(cmd, data) {(uint8_t)(ENH_BYTE1 | ((cmd)<<2) | (((data)&0xc0)>>6)), (uint8_t)(ENH_BYTE2 | ((data)&0x3f))} -Device::Device(const char* name, bool checkDevice, unsigned int latency, bool readOnly, bool initialSend, - bool enhancedProto) - : m_name(name), m_checkDevice(checkDevice), - m_latency(HOST_LATENCY_MS+(enhancedProto?ENHANCED_LATENCY_MS:0)+latency), m_readOnly(readOnly), - m_initialSend(initialSend), m_enhancedProto(enhancedProto), m_fd(-1), m_resetRequested(false), - m_listener(nullptr), m_arbitrationMaster(SYN), - m_arbitrationCheck(0), m_bufSize(((MAX_LEN+1+3)/4)*4), m_bufLen(0), m_bufPos(0), - m_extraFatures(0), m_infoId(0xff), m_infoReqTime(0), m_infoLen(0), m_infoPos(0) { - m_buffer = reinterpret_cast(malloc(m_bufSize)); - if (!m_buffer) { - m_bufSize = 0; - } -} - -Device::~Device() { - close(); - if (m_buffer) { - free(m_buffer); - } +Device::Device(const char* name, bool readOnly, bool initialSend) + : m_name(name), m_readOnly(readOnly), m_initialSend(initialSend), m_listener(nullptr) { } Device* Device::create(const char* name, unsigned int extraLatency, bool checkDevice, bool readOnly, bool initialSend) { @@ -138,14 +121,82 @@ Device* Device::create(const char* name, unsigned int extraLatency, bool checkDe return new SerialDevice(name, checkDevice, extraLatency, readOnly, initialSend, enhanced, highSpeed); } -result_t Device::open() { +result_t Device::afterOpen() { + if (m_initialSend && !send(ESC)) { + return RESULT_ERR_SEND; + } + return RESULT_OK; +} + + +FileDevice::FileDevice(const char* name, bool checkDevice, unsigned int latency, bool readOnly, bool initialSend, + bool enhancedProto) + : Device(name, readOnly, initialSend), + m_checkDevice(checkDevice), + m_latency(HOST_LATENCY_MS+(enhancedProto?ENHANCED_LATENCY_MS:0)+latency), + m_enhancedProto(enhancedProto), m_fd(-1), m_resetRequested(false), + m_arbitrationMaster(SYN), + m_arbitrationCheck(0), m_bufSize(((MAX_LEN+1+3)/4)*4), m_bufLen(0), m_bufPos(0), + m_extraFatures(0), m_infoId(0xff), m_infoReqTime(0), m_infoLen(0), m_infoPos(0) { + m_buffer = reinterpret_cast(malloc(m_bufSize)); + if (!m_buffer) { + m_bufSize = 0; + } +} + +FileDevice::~FileDevice() { + close(); + if (m_buffer) { + free(m_buffer); + } +} + +void FileDevice::formatInfo(ostringstream* ostream, bool verbose, bool asJson, bool noWait) { + if (asJson) { + if (m_enhancedProto) { + string ver = getEnhancedVersion(); + if (!ver.empty()) { + *ostream << ",\"dv\":\"" << ver << "\""; + } + } + return; + } + *ostream << m_name; + string info = getEnhancedProtoInfo(); + if (!info.empty()) { + *ostream << ", " << info; + } + if (isReadOnly()) { + *ostream << ", readonly"; + } + if (noWait) { + return; + } + if (!isValid()) { + *ostream << ", invalid"; + } + bool infoAdded = false; + if (verbose) { + info = getEnhancedInfos(); + if (!info.empty()) { + *ostream << ", " << info; + infoAdded = true; + } + } + if (!infoAdded) { + string ver = getEnhancedVersion(); + if (!ver.empty()) { + *ostream << ", firmware " << ver; + } + } +} + +result_t FileDevice::open() { close(); return m_bufSize == 0 ? RESULT_ERR_DEVICE : RESULT_OK; } -result_t Device::afterOpen() { - m_bufLen = 0; - m_extraFatures = 0; +result_t FileDevice::afterOpen() { if (m_enhancedProto) { symbol_t buf[2] = makeEnhancedSequence(ENH_REQ_INIT, 0x01); // extra feature: info #ifdef DEBUG_RAW_TRAFFIC @@ -165,15 +216,16 @@ result_t Device::afterOpen() { return RESULT_OK; } -void Device::close() { +void FileDevice::close() { if (m_fd != -1) { ::close(m_fd); m_fd = -1; } m_bufLen = 0; // flush read buffer + m_extraFatures = 0; // reset state } -bool Device::isValid() { +bool FileDevice::isValid() { if (m_fd == -1) { return false; } @@ -183,7 +235,7 @@ bool Device::isValid() { return m_fd != -1; } -result_t Device::requestEnhancedInfo(symbol_t infoId) { +result_t FileDevice::requestEnhancedInfo(symbol_t infoId) { if (!m_enhancedProto || m_extraFatures == 0) { return RESULT_ERR_INVALID_ARG; } @@ -212,7 +264,7 @@ result_t Device::requestEnhancedInfo(symbol_t infoId) { return sendEnhancedInfoRequest(infoId); } -result_t Device::sendEnhancedInfoRequest(symbol_t infoId) { +result_t FileDevice::sendEnhancedInfoRequest(symbol_t infoId) { symbol_t buf[2] = makeEnhancedSequence(ENH_REQ_INFO, infoId); #ifdef DEBUG_RAW_TRAFFIC fprintf(stdout, "raw enhanced > %2.2x %2.2x\n", buf[0], buf[1]); @@ -227,7 +279,7 @@ result_t Device::sendEnhancedInfoRequest(symbol_t infoId) { return RESULT_OK; } -string Device::getEnhancedInfos() { +string FileDevice::getEnhancedInfos() { if (!m_enhancedProto || m_extraFatures == 0) { return ""; } @@ -276,7 +328,7 @@ string Device::getEnhancedInfos() { + m_enhInfoBusVoltage; } -result_t Device::send(symbol_t value) { +result_t FileDevice::send(symbol_t value) { if (!isValid()) { return RESULT_ERR_DEVICE; } @@ -296,7 +348,7 @@ result_t Device::send(symbol_t value) { #define ENHANCED_COMPLETE_WAIT_DURATION 10 -bool Device::cancelRunningArbitration(ArbitrationState* arbitrationState) { +bool FileDevice::cancelRunningArbitration(ArbitrationState* arbitrationState) { if (m_enhancedProto && m_arbitrationMaster != SYN) { *arbitrationState = as_error; m_arbitrationMaster = SYN; @@ -313,7 +365,7 @@ bool Device::cancelRunningArbitration(ArbitrationState* arbitrationState) { return true; } -result_t Device::recv(unsigned int timeout, symbol_t* value, ArbitrationState* arbitrationState) { +result_t FileDevice::recv(unsigned int timeout, symbol_t* value, ArbitrationState* arbitrationState) { if (m_arbitrationMaster != SYN) { *arbitrationState = as_running; } @@ -424,7 +476,7 @@ result_t Device::recv(unsigned int timeout, symbol_t* value, ArbitrationState* a return RESULT_OK; } -result_t Device::startArbitration(symbol_t masterAddress) { +result_t FileDevice::startArbitration(symbol_t masterAddress) { if (m_arbitrationCheck) { if (masterAddress != SYN) { return RESULT_ERR_ARB_RUNNING; // should not occur @@ -453,7 +505,7 @@ result_t Device::startArbitration(symbol_t masterAddress) { return RESULT_OK; } -bool Device::write(symbol_t value, bool startArbitration) { +bool FileDevice::write(symbol_t value, bool startArbitration) { if (m_enhancedProto) { symbol_t buf[2] = makeEnhancedSequence(startArbitration ? ENH_REQ_START : ENH_REQ_SEND, value); #ifdef DEBUG_RAW_TRAFFIC @@ -473,7 +525,7 @@ bool Device::write(symbol_t value, bool startArbitration) { #endif } -bool Device::available() { +bool FileDevice::available() { if (m_bufLen <= 0) { return false; } @@ -542,7 +594,7 @@ bool Device::available() { return false; } -bool Device::read(symbol_t* value, bool isAvailable, ArbitrationState* arbitrationState, bool* incomplete) { +bool FileDevice::read(symbol_t* value, bool isAvailable, ArbitrationState* arbitrationState, bool* incomplete) { if (!isAvailable) { if (m_bufLen > 0 && m_bufPos != 0) { if (m_bufLen > m_bufSize / 2) { @@ -609,7 +661,7 @@ bool Device::read(symbol_t* value, bool isAvailable, ArbitrationState* arbitrati return handleEnhancedBufferedData(value, arbitrationState); } -bool Device::handleEnhancedBufferedData(symbol_t* value, ArbitrationState* arbitrationState) { +bool FileDevice::handleEnhancedBufferedData(symbol_t* value, ArbitrationState* arbitrationState) { while (m_bufLen > 0) { symbol_t ch = m_buffer[m_bufPos]; if (!(ch&ENH_BYTE_FLAG)) { @@ -840,7 +892,7 @@ bool Device::handleEnhancedBufferedData(symbol_t* value, ArbitrationState* arbit result_t SerialDevice::open() { - result_t result = Device::open(); + result_t result = FileDevice::open(); if (result != RESULT_OK) { return result; } @@ -857,7 +909,7 @@ result_t SerialDevice::open() { return RESULT_ERR_NOTFOUND; } - if (flock(m_fd, LOCK_EX|LOCK_NB)) { + if (flock(m_fd, LOCK_EX|LOCK_NB) != 0) { close(); return RESULT_ERR_DEVICE; } @@ -925,7 +977,7 @@ void SerialDevice::close() { // restore previous settings of the device tcsetattr(m_fd, TCSANOW, &m_oldSettings); } - Device::close(); + FileDevice::close(); } void SerialDevice::checkDevice() { @@ -936,7 +988,7 @@ void SerialDevice::checkDevice() { } result_t NetworkDevice::open() { - result_t result = Device::open(); + result_t result = FileDevice::open(); if (result != RESULT_OK) { return result; } diff --git a/src/lib/ebus/device.h b/src/lib/ebus/device.h index a5c31615a..8605286e6 100755 --- a/src/lib/ebus/device.h +++ b/src/lib/ebus/device.h @@ -99,20 +99,16 @@ class Device { /** * Construct a new instance. * @param name the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network). - * @param checkDevice whether to regularly check the device availability. - * @param latency the bus transfer latency in milliseconds. * @param readOnly whether to allow read access to the device only. * @param initialSend whether to send an initial @a ESC symbol in @a open(). - * @param enhancedProto whether to use the ebusd enhanced protocol. */ - Device(const char* name, bool checkDevice, unsigned int latency, bool readOnly, bool initialSend, - bool enhancedProto = false); + Device(const char* name, bool readOnly, bool initialSend); public: /** * Destructor. */ - virtual ~Device(); + virtual ~Device() { } /** * Factory method for creating a new instance. @@ -128,34 +124,61 @@ class Device { bool readOnly = false, bool initialSend = false); /** - * Get the transfer latency of this device. - * @return the transfer latency in milliseconds. + * Get the device name. + * @return the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network). */ - virtual unsigned int getLatency() const { return m_latency; } + const char* getName() const { return m_name; } + + /** + * Return whether to allow read access to the device only. + * @return whether to allow read access to the device only. + */ + bool isReadOnly() const { return m_readOnly; } + + /** + * Set the @a DeviceListener. + * @param listener the @a DeviceListener. + */ + void setListener(DeviceListener* listener) { m_listener = listener; } + + /** + * Format device infos in plain text or JSON format. + * @param output the @a ostringstream to append the infos to. + * @param verbose whether to add verbose infos. + * @param asJson whether to format as JSON rather than plain text. + * @param noWait true to not wait for any response asynchronously and return immediately. + */ + virtual void formatInfo(ostringstream* output, bool verbose, bool asJson = false, bool noWait = false) = 0; /** * Open the file descriptor. * @return the @a result_t code. */ - virtual result_t open(); + virtual result_t open() = 0; /** * Has to be called by subclasses upon successful opening the device as last action in open(). * @return the @a result_t code. */ - result_t afterOpen(); + virtual result_t afterOpen(); /** * Close the file descriptor if opened. */ - virtual void close(); + virtual void close() = 0; + + /** + * Return whether the device is opened and available. + * @return whether the device is opened and available. + */ + virtual bool isValid() = 0; /** * Write a single byte to the device. * @param value the byte value to write. * @return the @a result_t code. */ - result_t send(symbol_t value); + virtual result_t send(symbol_t value) = 0; /** * Read a single byte from the device. @@ -165,7 +188,7 @@ class Device { * @a as_won, the received byte is the master address that was successfully arbitrated with. * @return the result_t code. */ - result_t recv(unsigned int timeout, symbol_t* value, ArbitrationState* arbitrationState); + virtual result_t recv(unsigned int timeout, symbol_t* value, ArbitrationState* arbitrationState) = 0; /** * Start the arbitration with the specified master address. A subsequent request while an arbitration is currently in @@ -173,31 +196,89 @@ class Device { * @param masterAddress the master address, or @a SYN to cancel a previous arbitration request. * @return the result_t code. */ - result_t startArbitration(symbol_t masterAddress); + virtual result_t startArbitration(symbol_t masterAddress) = 0; /** * Return whether the device is currently in arbitration. * @return true when the device is currently in arbitration. */ - bool isArbitrating() const { return m_arbitrationMaster != SYN; } + virtual bool isArbitrating() const = 0; /** - * Return the device name. - * @return the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network). + * @return whether the device supports checking for version updates. */ - const char* getName() { return m_name; } + virtual bool supportsUpdateCheck() const = 0; + + protected: + /** the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network). */ + const char* m_name; + + /** whether to allow read access to the device only. */ + const bool m_readOnly; + + /** whether to send an initial @a ESC symbol in @a open(). */ + const bool m_initialSend; + + /** the @a DeviceListener, or nullptr. */ + DeviceListener* m_listener; +}; + +/** + * The common base class for devices using a file descriptor. + */ +class FileDevice : public Device { + protected: /** - * Return whether the device is opened and available. - * @return whether the device is opened and available. + * Construct a new instance. + * @param name the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network). + * @param checkDevice whether to regularly check the device availability. + * @param latency the bus transfer latency in milliseconds. + * @param readOnly whether to allow read access to the device only. + * @param initialSend whether to send an initial @a ESC symbol in @a open(). + * @param enhancedProto whether to use the ebusd enhanced protocol. */ - bool isValid(); + FileDevice(const char* name, bool checkDevice, unsigned int latency, bool readOnly, bool initialSend, + bool enhancedProto = false); + public: /** - * Return whether to allow read access to the device only. - * @return whether to allow read access to the device only. + * Destructor. */ - bool isReadOnly() const { return m_readOnly; } + virtual ~FileDevice(); + + // @copydoc + void formatInfo(ostringstream* output, bool verbose, bool asJson = false, bool noWait = false) override; + + // @copydoc + result_t open() override; + + // @copydoc + result_t afterOpen() override; + + // @copydoc + void close() override; + + // @copydoc + bool isValid() override; + + // @copydoc + result_t send(symbol_t value) override; + + // @copydoc + result_t recv(unsigned int timeout, symbol_t* value, ArbitrationState* arbitrationState) override; + + // @copydoc + result_t startArbitration(symbol_t masterAddress) override; + + // @copydoc + bool isArbitrating() const override { return m_arbitrationMaster != SYN; } + + /** + * Get the transfer latency of this device. + * @return the transfer latency in milliseconds. + */ + virtual unsigned int getLatency() const { return m_latency; } /** * Return whether the device supports the ebusd enhanced protocol. @@ -206,15 +287,18 @@ class Device { bool isEnhancedProto() const { return m_enhancedProto; } /** - * @return whether the device supports the ebusd enhanced protocol and supports querying extra infos. + * Get info about enhanced protocol support as string. + * @return a @a string describing level of enhanced protocol support, or the empty string. */ - bool supportsEnhancedInfos() const { return m_enhancedProto && m_extraFatures & 0x01; } + virtual string getEnhancedProtoInfo() const { return m_enhancedProto ? "enhanced" : ""; } + + // @copydoc + bool supportsUpdateCheck() const override { return m_enhancedProto && m_extraFatures & 0x01; } /** - * Set the @a DeviceListener. - * @param listener the @a DeviceListener. + * @return whether the device supports the ebusd enhanced protocol and supports querying extra infos. */ - void setListener(DeviceListener* listener) { m_listener = listener; } + bool supportsEnhancedInfos() const { return m_enhancedProto && m_extraFatures & 0x01; } /** * Check for a running extra infos request, wait for it to complete, @@ -281,21 +365,12 @@ class Device { virtual bool read(symbol_t* value, bool isAvailable, ArbitrationState* arbitrationState = nullptr, bool* incomplete = nullptr); - /** the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network). */ - const char* m_name; - /** whether to regularly check the device availability. */ const bool m_checkDevice; /** the bus transfer latency in milliseconds. */ const unsigned int m_latency; - /** whether to allow read access to the device only. */ - const bool m_readOnly; - - /** whether to send an initial @a ESC symbol in @a open(). */ - const bool m_initialSend; - /** whether the device supports the ebusd enhanced protocol. */ const bool m_enhancedProto; @@ -314,9 +389,6 @@ class Device { */ bool handleEnhancedBufferedData(symbol_t* value, ArbitrationState* arbitrationState); - /** the @a DeviceListener, or nullptr. */ - DeviceListener* m_listener; - /** the arbitration master address to send when in arbitration, or @a SYN. */ symbol_t m_arbitrationMaster; @@ -371,7 +443,7 @@ class Device { /** * The @a Device for directly connected serial interfaces (tty). */ -class SerialDevice : public Device { +class SerialDevice : public FileDevice { public: /** * Construct a new instance. @@ -385,10 +457,15 @@ class SerialDevice : public Device { */ SerialDevice(const char* name, bool checkDevice, unsigned int extraLatency, bool readOnly, bool initialSend, bool enhancedProto = false, bool enhancedHighSpeed = false) - : Device(name, checkDevice, extraLatency, readOnly, initialSend, enhancedProto), + : FileDevice(name, checkDevice, extraLatency, readOnly, initialSend, enhancedProto), m_enhancedHighSpeed(enhancedHighSpeed) { } + // @copydoc + string getEnhancedProtoInfo() const override { + return m_enhancedProto ? (m_enhancedHighSpeed ? "enhanced high speed" : "enhanced") : ""; + } + // @copydoc result_t open() override; @@ -412,7 +489,7 @@ class SerialDevice : public Device { /** * The @a Device for remote network interfaces. */ -class NetworkDevice : public Device { +class NetworkDevice : public FileDevice { public: /** * Construct a new instance. @@ -428,7 +505,7 @@ class NetworkDevice : public Device { */ NetworkDevice(const char* name, const char* hostOrIp, uint16_t port, unsigned int extraLatency, bool readOnly, bool initialSend, bool udp, bool enhancedProto = false) - : Device(name, true, NETWORK_LATENCY_MS+extraLatency, readOnly, initialSend, enhancedProto), + : FileDevice(name, true, NETWORK_LATENCY_MS+extraLatency, readOnly, initialSend, enhancedProto), m_hostOrIp(hostOrIp), m_port(port), m_udp(udp) {} /** From 2c7cab3dc1b14f5bc4f93fa2962f6cfbedf825db Mon Sep 17 00:00:00 2001 From: John Date: Fri, 16 Jun 2023 20:40:35 +0200 Subject: [PATCH 094/345] update year --- src/ebusd/bushandler.cpp | 2 +- src/ebusd/bushandler.h | 2 +- src/ebusd/datahandler.cpp | 2 +- src/ebusd/datahandler.h | 2 +- src/ebusd/main.cpp | 2 +- src/ebusd/main.h | 2 +- src/ebusd/mainloop.cpp | 2 +- src/ebusd/mainloop.h | 2 +- src/ebusd/mqtthandler.cpp | 2 +- src/ebusd/mqtthandler.h | 2 +- src/ebusd/network.cpp | 2 +- src/ebusd/network.h | 2 +- src/lib/ebus/contrib/contrib.cpp | 2 +- src/lib/ebus/contrib/contrib.h | 2 +- src/lib/ebus/contrib/tem.cpp | 2 +- src/lib/ebus/contrib/tem.h | 2 +- src/lib/ebus/contrib/test/test_tem.cpp | 2 +- src/lib/ebus/data.cpp | 2 +- src/lib/ebus/data.h | 2 +- src/lib/ebus/datatype.cpp | 2 +- src/lib/ebus/datatype.h | 2 +- src/lib/ebus/device.cpp | 2 +- src/lib/ebus/device.h | 2 +- src/lib/ebus/filereader.cpp | 2 +- src/lib/ebus/filereader.h | 2 +- src/lib/ebus/message.cpp | 2 +- src/lib/ebus/message.h | 2 +- src/lib/ebus/result.cpp | 2 +- src/lib/ebus/result.h | 2 +- src/lib/ebus/stringhelper.cpp | 2 +- src/lib/ebus/stringhelper.h | 2 +- src/lib/ebus/symbol.cpp | 2 +- src/lib/ebus/symbol.h | 2 +- src/lib/ebus/test/test_data.cpp | 2 +- src/lib/ebus/test/test_device.cpp | 2 +- src/lib/ebus/test/test_filereader.cpp | 2 +- src/lib/ebus/test/test_message.cpp | 2 +- src/lib/ebus/test/test_symbol.cpp | 2 +- src/lib/knx/knx.cpp | 2 +- src/lib/knx/knx.h | 2 +- src/lib/knx/knxd.h | 2 +- src/lib/knx/knxnet.h | 2 +- src/lib/utils/clock.cpp | 2 +- src/lib/utils/clock.h | 2 +- src/lib/utils/httpclient.cpp | 2 +- src/lib/utils/httpclient.h | 2 +- src/lib/utils/log.cpp | 2 +- src/lib/utils/log.h | 2 +- src/lib/utils/notify.h | 2 +- src/lib/utils/queue.h | 2 +- src/lib/utils/rotatefile.cpp | 2 +- src/lib/utils/rotatefile.h | 2 +- src/lib/utils/tcpsocket.cpp | 2 +- src/lib/utils/tcpsocket.h | 2 +- src/lib/utils/thread.cpp | 2 +- src/lib/utils/thread.h | 2 +- src/tools/ebusctl.cpp | 2 +- src/tools/ebusfeed.cpp | 2 +- src/tools/ebuspicloader.cpp | 2 +- 59 files changed, 59 insertions(+), 59 deletions(-) diff --git a/src/ebusd/bushandler.cpp b/src/ebusd/bushandler.cpp index 535337f66..17a705e2d 100644 --- a/src/ebusd/bushandler.cpp +++ b/src/ebusd/bushandler.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2022 John Baier + * Copyright (C) 2014-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/bushandler.h b/src/ebusd/bushandler.h index a54eb1ec3..66a57e3f8 100755 --- a/src/ebusd/bushandler.h +++ b/src/ebusd/bushandler.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2022 John Baier + * Copyright (C) 2014-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/datahandler.cpp b/src/ebusd/datahandler.cpp index 264c3b80f..fc18ebec0 100755 --- a/src/ebusd/datahandler.cpp +++ b/src/ebusd/datahandler.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2016-2022 John Baier + * Copyright (C) 2016-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/datahandler.h b/src/ebusd/datahandler.h index a4f161e87..84a8b507c 100755 --- a/src/ebusd/datahandler.h +++ b/src/ebusd/datahandler.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2016-2022 John Baier + * Copyright (C) 2016-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index a73cd78fe..3a33e2e53 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2022 John Baier + * Copyright (C) 2014-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/main.h b/src/ebusd/main.h index 09f3174e2..abaaf6675 100644 --- a/src/ebusd/main.h +++ b/src/ebusd/main.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2022 John Baier + * Copyright (C) 2014-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index 5d242fb98..de2f7a8ff 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2022 John Baier + * Copyright (C) 2014-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/mainloop.h b/src/ebusd/mainloop.h index b76797ef9..12a4b528c 100644 --- a/src/ebusd/mainloop.h +++ b/src/ebusd/mainloop.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2022 John Baier + * Copyright (C) 2014-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/mqtthandler.cpp b/src/ebusd/mqtthandler.cpp index b88c510b5..a4adac49c 100755 --- a/src/ebusd/mqtthandler.cpp +++ b/src/ebusd/mqtthandler.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2016-2022 John Baier + * Copyright (C) 2016-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/mqtthandler.h b/src/ebusd/mqtthandler.h index d4f4ad78b..943012332 100644 --- a/src/ebusd/mqtthandler.h +++ b/src/ebusd/mqtthandler.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2016-2022 John Baier + * Copyright (C) 2016-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/network.cpp b/src/ebusd/network.cpp index 0a51f2fcc..a2cdacaa0 100644 --- a/src/ebusd/network.cpp +++ b/src/ebusd/network.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2022 John Baier , Roland Jax 2012-2014 + * Copyright (C) 2014-2023 John Baier , Roland Jax 2012-2014 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/network.h b/src/ebusd/network.h index e972bc72f..22206bd9c 100644 --- a/src/ebusd/network.h +++ b/src/ebusd/network.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2022 John Baier , Roland Jax 2012-2014 + * Copyright (C) 2014-2023 John Baier , Roland Jax 2012-2014 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/contrib/contrib.cpp b/src/lib/ebus/contrib/contrib.cpp index 587353b20..6cbdd077b 100755 --- a/src/lib/ebus/contrib/contrib.cpp +++ b/src/lib/ebus/contrib/contrib.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2016-2022 John Baier + * Copyright (C) 2016-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/contrib/contrib.h b/src/lib/ebus/contrib/contrib.h index 8352001c5..53790c125 100755 --- a/src/lib/ebus/contrib/contrib.h +++ b/src/lib/ebus/contrib/contrib.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2016-2022 John Baier + * Copyright (C) 2016-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/contrib/tem.cpp b/src/lib/ebus/contrib/tem.cpp index ab60bde32..e757c44e1 100755 --- a/src/lib/ebus/contrib/tem.cpp +++ b/src/lib/ebus/contrib/tem.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2016-2022 John Baier + * Copyright (C) 2016-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/contrib/tem.h b/src/lib/ebus/contrib/tem.h index bc2e3c093..495ff5820 100755 --- a/src/lib/ebus/contrib/tem.h +++ b/src/lib/ebus/contrib/tem.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2016-2022 John Baier + * Copyright (C) 2016-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/contrib/test/test_tem.cpp b/src/lib/ebus/contrib/test/test_tem.cpp index ab4a1c497..656c8e906 100755 --- a/src/lib/ebus/contrib/test/test_tem.cpp +++ b/src/lib/ebus/contrib/test/test_tem.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2016-2022 John Baier + * Copyright (C) 2016-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/data.cpp b/src/lib/ebus/data.cpp index 4500771c4..0e9d38bcd 100644 --- a/src/lib/ebus/data.cpp +++ b/src/lib/ebus/data.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2022 John Baier + * Copyright (C) 2014-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/data.h b/src/lib/ebus/data.h index 49b11c518..f205509a8 100755 --- a/src/lib/ebus/data.h +++ b/src/lib/ebus/data.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2022 John Baier + * Copyright (C) 2014-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/datatype.cpp b/src/lib/ebus/datatype.cpp index 92f5e22b1..854097b51 100755 --- a/src/lib/ebus/datatype.cpp +++ b/src/lib/ebus/datatype.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2022 John Baier + * Copyright (C) 2014-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/datatype.h b/src/lib/ebus/datatype.h index 1b9a2d6e6..9dee87cde 100755 --- a/src/lib/ebus/datatype.h +++ b/src/lib/ebus/datatype.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2022 John Baier + * Copyright (C) 2014-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/device.cpp b/src/lib/ebus/device.cpp index f96a4ac9b..68798d489 100755 --- a/src/lib/ebus/device.cpp +++ b/src/lib/ebus/device.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2015-2022 John Baier + * Copyright (C) 2015-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/device.h b/src/lib/ebus/device.h index 8605286e6..407f2262d 100755 --- a/src/lib/ebus/device.h +++ b/src/lib/ebus/device.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2015-2022 John Baier + * Copyright (C) 2015-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/filereader.cpp b/src/lib/ebus/filereader.cpp index d6c4f04fb..545179b51 100755 --- a/src/lib/ebus/filereader.cpp +++ b/src/lib/ebus/filereader.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2022 John Baier + * Copyright (C) 2014-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/filereader.h b/src/lib/ebus/filereader.h index ff94a6c17..08a698ddb 100755 --- a/src/lib/ebus/filereader.h +++ b/src/lib/ebus/filereader.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2022 John Baier + * Copyright (C) 2014-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/message.cpp b/src/lib/ebus/message.cpp index 1cd3c1371..54a634fa5 100644 --- a/src/lib/ebus/message.cpp +++ b/src/lib/ebus/message.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2022 John Baier + * Copyright (C) 2014-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/message.h b/src/lib/ebus/message.h index 9bd0be5ac..5cc5fb5bf 100644 --- a/src/lib/ebus/message.h +++ b/src/lib/ebus/message.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2022 John Baier + * Copyright (C) 2014-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/result.cpp b/src/lib/ebus/result.cpp index a77f2e4a3..9e587d824 100755 --- a/src/lib/ebus/result.cpp +++ b/src/lib/ebus/result.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2022 John Baier + * Copyright (C) 2014-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/result.h b/src/lib/ebus/result.h index 0ab0f9e89..bbdc3856a 100755 --- a/src/lib/ebus/result.h +++ b/src/lib/ebus/result.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2022 John Baier + * Copyright (C) 2014-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/stringhelper.cpp b/src/lib/ebus/stringhelper.cpp index 449c8df54..43b55ba85 100644 --- a/src/lib/ebus/stringhelper.cpp +++ b/src/lib/ebus/stringhelper.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2022 John Baier + * Copyright (C) 2022-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/stringhelper.h b/src/lib/ebus/stringhelper.h index b927a3a10..fd3b7dbe2 100644 --- a/src/lib/ebus/stringhelper.h +++ b/src/lib/ebus/stringhelper.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2022 John Baier + * Copyright (C) 2022-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/symbol.cpp b/src/lib/ebus/symbol.cpp index cc8a92990..d055ec5dc 100755 --- a/src/lib/ebus/symbol.cpp +++ b/src/lib/ebus/symbol.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2022 John Baier + * Copyright (C) 2014-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/symbol.h b/src/lib/ebus/symbol.h index 4fa6902c5..535463959 100755 --- a/src/lib/ebus/symbol.h +++ b/src/lib/ebus/symbol.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2022 John Baier + * Copyright (C) 2014-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/test/test_data.cpp b/src/lib/ebus/test/test_data.cpp index 9f303805a..d4110ad88 100755 --- a/src/lib/ebus/test/test_data.cpp +++ b/src/lib/ebus/test/test_data.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2022 John Baier + * Copyright (C) 2014-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/test/test_device.cpp b/src/lib/ebus/test/test_device.cpp index 2b491ad3c..f4290788f 100755 --- a/src/lib/ebus/test/test_device.cpp +++ b/src/lib/ebus/test/test_device.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2022 John Baier + * Copyright (C) 2014-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/test/test_filereader.cpp b/src/lib/ebus/test/test_filereader.cpp index 61ef0ec5f..30b6d1c42 100755 --- a/src/lib/ebus/test/test_filereader.cpp +++ b/src/lib/ebus/test/test_filereader.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2022 John Baier + * Copyright (C) 2014-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/test/test_message.cpp b/src/lib/ebus/test/test_message.cpp index 260193928..da9029cf1 100644 --- a/src/lib/ebus/test/test_message.cpp +++ b/src/lib/ebus/test/test_message.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2022 John Baier + * Copyright (C) 2014-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/test/test_symbol.cpp b/src/lib/ebus/test/test_symbol.cpp index 6da4ad4f1..85aca5318 100755 --- a/src/lib/ebus/test/test_symbol.cpp +++ b/src/lib/ebus/test/test_symbol.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2022 John Baier + * Copyright (C) 2014-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/knx/knx.cpp b/src/lib/knx/knx.cpp index c71178fdc..6bd1f7d97 100644 --- a/src/lib/knx/knx.cpp +++ b/src/lib/knx/knx.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2022 John Baier + * Copyright (C) 2022-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/knx/knx.h b/src/lib/knx/knx.h index 6dacb0f79..981cc6421 100644 --- a/src/lib/knx/knx.h +++ b/src/lib/knx/knx.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2022 John Baier + * Copyright (C) 2022-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/knx/knxd.h b/src/lib/knx/knxd.h index 1fb7e4609..d4ac4aa73 100644 --- a/src/lib/knx/knxd.h +++ b/src/lib/knx/knxd.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2022 John Baier + * Copyright (C) 2022-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/knx/knxnet.h b/src/lib/knx/knxnet.h index c17526ee4..16de8bf00 100644 --- a/src/lib/knx/knxnet.h +++ b/src/lib/knx/knxnet.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2022 John Baier + * Copyright (C) 2022-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/clock.cpp b/src/lib/utils/clock.cpp index 827cd0a97..d1d355934 100755 --- a/src/lib/utils/clock.cpp +++ b/src/lib/utils/clock.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2015-2022 John Baier + * Copyright (C) 2015-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/clock.h b/src/lib/utils/clock.h index a4eeafacb..bab709693 100755 --- a/src/lib/utils/clock.h +++ b/src/lib/utils/clock.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2015-2022 John Baier + * Copyright (C) 2015-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/httpclient.cpp b/src/lib/utils/httpclient.cpp index 0472671a9..d90bd2a76 100755 --- a/src/lib/utils/httpclient.cpp +++ b/src/lib/utils/httpclient.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2018-2022 John Baier + * Copyright (C) 2018-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/httpclient.h b/src/lib/utils/httpclient.h index 89493dfe7..fcfcddeb6 100755 --- a/src/lib/utils/httpclient.h +++ b/src/lib/utils/httpclient.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2018-2022 John Baier + * Copyright (C) 2018-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/log.cpp b/src/lib/utils/log.cpp index d34a1613c..9db2c46ad 100755 --- a/src/lib/utils/log.cpp +++ b/src/lib/utils/log.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2022 John Baier + * Copyright (C) 2014-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/log.h b/src/lib/utils/log.h index 1364deeda..9de57cb6a 100755 --- a/src/lib/utils/log.h +++ b/src/lib/utils/log.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2022 John Baier + * Copyright (C) 2014-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/notify.h b/src/lib/utils/notify.h index 810235270..ba2fc85ae 100755 --- a/src/lib/utils/notify.h +++ b/src/lib/utils/notify.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2022 John Baier , Roland Jax 2012-2014 + * Copyright (C) 2014-2023 John Baier , Roland Jax 2012-2014 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/queue.h b/src/lib/utils/queue.h index 6d2f60515..94eb54a77 100755 --- a/src/lib/utils/queue.h +++ b/src/lib/utils/queue.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2022 John Baier + * Copyright (C) 2014-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/rotatefile.cpp b/src/lib/utils/rotatefile.cpp index 841058bef..23535756a 100755 --- a/src/lib/utils/rotatefile.cpp +++ b/src/lib/utils/rotatefile.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2016-2022 John Baier + * Copyright (C) 2016-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/rotatefile.h b/src/lib/utils/rotatefile.h index bb1432334..bd54b23ea 100755 --- a/src/lib/utils/rotatefile.h +++ b/src/lib/utils/rotatefile.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2016-2022 John Baier + * Copyright (C) 2016-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/tcpsocket.cpp b/src/lib/utils/tcpsocket.cpp index 6c6d09a42..4a1afa462 100755 --- a/src/lib/utils/tcpsocket.cpp +++ b/src/lib/utils/tcpsocket.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2015-2022 John Baier , Roland Jax 2012-2014 + * Copyright (C) 2015-2023 John Baier , Roland Jax 2012-2014 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/tcpsocket.h b/src/lib/utils/tcpsocket.h index 15a2c165f..eec6c2ba6 100755 --- a/src/lib/utils/tcpsocket.h +++ b/src/lib/utils/tcpsocket.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2022 John Baier , Roland Jax 2012-2014 + * Copyright (C) 2014-2023 John Baier , Roland Jax 2012-2014 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/thread.cpp b/src/lib/utils/thread.cpp index 4a5d56000..6db1ce00a 100755 --- a/src/lib/utils/thread.cpp +++ b/src/lib/utils/thread.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2022 John Baier , Roland Jax 2012-2014 + * Copyright (C) 2014-2023 John Baier , Roland Jax 2012-2014 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/thread.h b/src/lib/utils/thread.h index 4690929ee..33675d52e 100755 --- a/src/lib/utils/thread.h +++ b/src/lib/utils/thread.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2022 John Baier , Roland Jax 2012-2014 + * Copyright (C) 2014-2023 John Baier , Roland Jax 2012-2014 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/tools/ebusctl.cpp b/src/tools/ebusctl.cpp index 9539c5c2c..6f581c7e5 100755 --- a/src/tools/ebusctl.cpp +++ b/src/tools/ebusctl.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2022 John Baier , Roland Jax 2012-2014 + * Copyright (C) 2014-2023 John Baier , Roland Jax 2012-2014 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/tools/ebusfeed.cpp b/src/tools/ebusfeed.cpp index 69579c381..5939184d9 100755 --- a/src/tools/ebusfeed.cpp +++ b/src/tools/ebusfeed.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2022 John Baier , Roland Jax 2012-2014 + * Copyright (C) 2014-2023 John Baier , Roland Jax 2012-2014 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/tools/ebuspicloader.cpp b/src/tools/ebuspicloader.cpp index 894628fb2..f568c6ef0 100644 --- a/src/tools/ebuspicloader.cpp +++ b/src/tools/ebuspicloader.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2020-2022 John Baier + * Copyright (C) 2020-2023 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by From 8f190e9e84a5625364b39813f43b3f6dcc9fcac8 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 17 Jun 2023 08:18:34 +0200 Subject: [PATCH 095/345] add scan status --- ChangeLog.md | 1 + src/ebusd/bushandler.h | 8 ++++++- src/ebusd/mainloop.cpp | 51 +++++++++++++++++++++++++++++++++--------- src/ebusd/mainloop.h | 3 +++ 4 files changed, 51 insertions(+), 12 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 57302f311..7f7867cc4 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -12,6 +12,7 @@ * add support for injecting scan messages being used for scan procedure * add value lists support to MQTT integration and use it for Home Assistant MQTT discovery integration * add update check for v5 device +* add "scan status" command and add the status to "info" command # 23.1 (2023-01-06) diff --git a/src/ebusd/bushandler.h b/src/ebusd/bushandler.h index 66a57e3f8..de0074430 100755 --- a/src/ebusd/bushandler.h +++ b/src/ebusd/bushandler.h @@ -493,6 +493,12 @@ class BusHandler : public WaitThread { */ void setScanFinished(); + /** + * Get the number of scan requests currently running. + * @eturn the number of scan requests currently running. + */ + unsigned int getRunningScans() const { return m_runningScans; } + /** * Format the scan result for a single slave to the @a ostringstream. * @param slave the slave address for which to format the scan result. @@ -762,7 +768,7 @@ class BusHandler : public WaitThread { /** the queue of @a BusRequests that are already finished. */ Queue m_finishedRequests; - /** the number of scan request currently running. */ + /** the number of scan requests currently running. */ unsigned int m_runningScans; /** the offset of the next symbol that needs to be sent from the command or response, diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index de2f7a8ff..3cee7bb69 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -108,7 +108,8 @@ result_t UserList::addFromFile(const string& filename, unsigned int lineNo, map< MainLoop::MainLoop(const struct options& opt, Device *device, MessageMap* messages, ScanHelper* scanHelper) : Thread(), m_device(device), m_reconnectCount(0), m_userList(opt.accessLevel), m_messages(messages), m_scanHelper(scanHelper), m_address(opt.address), m_scanConfig(opt.scanConfig), - m_initialScan(opt.readOnly ? ESC : opt.initialScan), m_polling(opt.pollInterval > 0), m_enableHex(opt.enableHex), + m_initialScan(opt.readOnly ? ESC : opt.initialScan), m_scanStatus(SCAN_STATUS_NONE), + m_polling(opt.pollInterval > 0), m_enableHex(opt.enableHex), m_shutdown(false), m_runUpdateCheck(opt.updateCheck), m_httpClient() { m_device->setListener(this); // open Device @@ -227,7 +228,7 @@ void MainLoop::run() { time_t lastTaskRun, now, start, lastSignal = 0, since, sinkSince = 1, nextCheckRun; int taskDelay = 5; symbol_t lastScanAddress = 0; // 0 is known to be a master - scanStatus_t lastScanStatus = SCAN_STATUS_NONE; + scanStatus_t lastScanStatus = m_scanStatus; int scanCompleted = 0; time(&now); start = now; @@ -264,7 +265,6 @@ void MainLoop::run() { } if (m_scanConfig) { bool loadDelay = false; - scanStatus_t scanStatus = lastScanStatus; if (m_initialScan != ESC && reload && m_busHandler->hasSignal()) { loadDelay = true; result_t result; @@ -272,7 +272,7 @@ void MainLoop::run() { logNotice(lf_main, "starting initial full scan"); result = m_busHandler->startScan(true, "*"); if (result == RESULT_OK) { - scanStatus = SCAN_STATUS_RUNNING; + m_scanStatus = SCAN_STATUS_RUNNING; } } else if (m_initialScan == BROADCAST) { logNotice(lf_main, "starting initial broadcast scan"); @@ -296,7 +296,7 @@ void MainLoop::run() { if (m_busHandler->formatScanResult(m_initialScan, false, &ret)) { logNotice(lf_main, "initial scan result: %s", ret.str().c_str()); } - scanStatus = SCAN_STATUS_RUNNING; + m_scanStatus = SCAN_STATUS_RUNNING; } } if (result != RESULT_OK) { @@ -310,13 +310,13 @@ void MainLoop::run() { if (lastScanAddress == SYN) { taskDelay = 5; lastScanAddress = 0; - scanStatus = SCAN_STATUS_FINISHED; + m_scanStatus = SCAN_STATUS_FINISHED; scanCompleted++; if (scanCompleted > SCAN_REPEAT_COUNT) { // repeat failed scan only every Nth time scanCompleted = 0; } } else { - scanStatus = SCAN_STATUS_RUNNING; + m_scanStatus = SCAN_STATUS_RUNNING; result_t result = m_busHandler->scanAndWait(lastScanAddress, true); taskDelay = (result == RESULT_ERR_NO_SIGNAL) ? 10 : 1; if (result != RESULT_OK) { @@ -327,10 +327,10 @@ void MainLoop::run() { } } } - if (scanStatus != lastScanStatus && !dataSinks.empty()) { - lastScanStatus = scanStatus; + if (lastScanStatus != m_scanStatus && !dataSinks.empty()) { + lastScanStatus = m_scanStatus; for (const auto dataSink : dataSinks) { - dataSink->notifyScanStatus(scanStatus); + dataSink->notifyScanStatus(lastScanStatus); } } } else if (reload && m_busHandler->hasSignal()) { @@ -1827,6 +1827,25 @@ result_t MainLoop::executeScan(const vector& args, const string& levels, return RESULT_OK; } + if (args[1] == "status") { + switch (m_scanStatus) { + case SCAN_STATUS_RUNNING: + *ostream << "running"; + break; + case SCAN_STATUS_FINISHED: + *ostream << "finished"; + break; + default: + *ostream << "unused"; + break; + } + unsigned int running = m_busHandler->getRunningScans(); + if (running > 0) { + *ostream << ", some messages pending"; + } + return RESULT_OK; + } + if (!m_busHandler->hasSignal()) { return RESULT_ERR_NO_SIGNAL; } @@ -1850,7 +1869,8 @@ result_t MainLoop::executeScan(const vector& args, const string& levels, *ostream << "usage: scan [full|ZZ]\n" " or: scan result\n" - " Scan seen slaves, all slaves (full), a single slave (address ZZ), or report scan result."; + " or: scan status\n" + " Scan seen slaves, all slaves (full), a single slave (address ZZ), or report scan result or status."; return RESULT_OK; } @@ -1961,6 +1981,14 @@ result_t MainLoop::executeInfo(const vector& args, const string& user, o *ostream << "min symbol latency: " << m_busHandler->getMinSymbolLatency() << "\n" << "max symbol latency: " << m_busHandler->getMaxSymbolLatency() << "\n"; } + if (m_scanStatus != SCAN_STATUS_NONE) { + *ostream << "scan: " << (m_scanStatus == SCAN_STATUS_FINISHED ? "finished" : "running"); + unsigned int running = m_busHandler->getRunningScans(); + if (running > 0) { + *ostream << ", some messages pending"; + } + *ostream << "\n"; + } } else { *ostream << "signal: no signal\n"; } @@ -2010,6 +2038,7 @@ result_t MainLoop::executeHelp(ostringstream* ostream) { " encode|e Encode field(s): encode DEFINITION VALUE[;VALUE]*\n" " scan Scan slaves: scan [full|ZZ]\n" " Report scan result: scan result\n" + " Report scan status: scan status\n" " log Set log area level: log [AREA[,AREA]* LEVEL]\n" " raw Toggle logging of messages or each byte.\n" " dump Toggle binary dump of received bytes\n" diff --git a/src/ebusd/mainloop.h b/src/ebusd/mainloop.h index 12a4b528c..a2743fe41 100644 --- a/src/ebusd/mainloop.h +++ b/src/ebusd/mainloop.h @@ -421,6 +421,9 @@ class MainLoop : public Thread, DeviceListener { * (@a ESC=none, 0xfe=broadcast ident, @a SYN=full scan, else: single slave address). */ const symbol_t m_initialScan; + /** the current scan status. */ + scanStatus_t m_scanStatus; + /** true when the poll interval is non zero. */ const bool m_polling; From 333ae3b82f57b05135ee2b25a63c057b30e23673 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 17 Jun 2023 10:08:34 +0200 Subject: [PATCH 096/345] apply same workaround to all BIO_should_retry() calls and log timeout in send+recv as well --- src/lib/utils/httpclient.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/lib/utils/httpclient.cpp b/src/lib/utils/httpclient.cpp index d90bd2a76..35b05e202 100755 --- a/src/lib/utils/httpclient.cpp +++ b/src/lib/utils/httpclient.cpp @@ -86,6 +86,7 @@ SSLSocket::~SSLSocket() { } ssize_t SSLSocket::send(const char* data, size_t len) { + time_t now = 0; do { #if OPENSSL_VERSION_NUMBER >= 0x10101000L size_t part = 0; @@ -99,18 +100,23 @@ ssize_t SSLSocket::send(const char* data, size_t len) { return static_cast(res); } #endif - if (!BIO_should_retry(m_bio)) { + if (!BIO_should_retry(m_bio) && now > 0) { // always repeat on first failure if (isError("send", true)) { return -1; } return 0; } + if ((now=time(nullptr)) > m_until) { + logError(lf_network, "HTTP send: timed out after %d sec", now-m_until); + break; + } usleep(SLEEP_NANOS); - } while (time(nullptr) < m_until); + } while (true); return -1; // timeout } ssize_t SSLSocket::recv(char* data, size_t len) { + time_t now = 0; do { #if OPENSSL_VERSION_NUMBER >= 0x10101000L size_t part = 0; @@ -124,14 +130,18 @@ ssize_t SSLSocket::recv(char* data, size_t len) { return static_cast(res); } #endif - if (!BIO_should_retry(m_bio)) { + if (!BIO_should_retry(m_bio) && now > 0) { // always repeat on first failure if (isError("recv", true)) { return -1; } return 0; } + if ((now=time(nullptr)) > m_until) { + logError(lf_network, "HTTP recv: timed out after %d sec", now-m_until); + break; + } usleep(SLEEP_NANOS); - } while (time(nullptr) < m_until); + } while (true); return -1; // timeout } From ea87515ef582498d753368c19eb83b4f8be48b2d Mon Sep 17 00:00:00 2001 From: John Date: Sat, 17 Jun 2023 10:24:08 +0200 Subject: [PATCH 097/345] repeat HTTP requests once --- src/ebusd/mainloop.cpp | 6 ++++-- src/ebusd/scan.cpp | 36 +++++++++++++++++++++++++++++++----- src/lib/utils/httpclient.cpp | 17 ++++++++++++----- src/lib/utils/httpclient.h | 14 +++++++++++--- 4 files changed, 58 insertions(+), 15 deletions(-) diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index 3cee7bb69..03ae96515 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -379,8 +379,10 @@ void MainLoop::run() { m_busHandler->formatUpdateInfo(&ostr); ostr << "}"; string response; - if (!m_httpClient.post("/", ostr.str(), &response)) { + bool repeat = false; + if (!m_httpClient.post("/", ostr.str(), &response, &repeat)) { logError(lf_main, "update check error: %s", response.c_str()); + nextCheckRun = now + (repeat ? CHECK_INITIAL_DELAY : CHECK_DELAY); } else { m_updateCheck = response.empty() ? "unknown" : response; logNotice(lf_main, "update check: %s", response.c_str()); @@ -389,8 +391,8 @@ void MainLoop::run() { dataSink->notifyUpdateCheckResult(response == "OK" ? "" : m_updateCheck); } } + nextCheckRun = now + CHECK_DELAY; } - nextCheckRun = now + CHECK_DELAY; } } time(&lastTaskRun); diff --git a/src/ebusd/scan.cpp b/src/ebusd/scan.cpp index cb1cf184d..2c42450f0 100644 --- a/src/ebusd/scan.cpp +++ b/src/ebusd/scan.cpp @@ -57,6 +57,9 @@ ScanHelper::~ScanHelper() { } } +// the time slice to sleep when repeating an HTTP request +#define REPEAT_NANOS 1000000 + result_t ScanHelper::collectConfigFiles(const string& relPath, const string& prefix, const string& extension, vector* files, bool ignoreAddressPrefix, const string& query, @@ -66,8 +69,19 @@ result_t ScanHelper::collectConfigFiles(const string& relPath, const string& pre string uri = m_configUriPrefix + relPathWithSlash + m_configLangQuery + (m_configLangQuery.empty() ? "?" : "&") + "t=" + extension.substr(1) + query; string names; - if (!m_configHttpClient->get(uri, "", &names)) { - return RESULT_ERR_NOTFOUND; + bool repeat = false; + if (!m_configHttpClient->get(uri, "", &names, &repeat)) { + if (!names.empty()) { + logError(lf_main, "HTTP failure%s: %s", repeat ? ", repeating" : "", names.c_str()); + names = ""; + } + if (!repeat) { + return RESULT_ERR_NOTFOUND; + } + usleep(REPEAT_NANOS); + if (!m_configHttpClient->get(uri, "", &names)) { + return RESULT_ERR_NOTFOUND; + } } istringstream stream(names); string name; @@ -261,11 +275,23 @@ result_t ScanHelper::loadDefinitionsFromConfigPath(FileReader* reader, const str time_t mtime = 0; if (m_configUriPrefix.empty()) { stream = FileReader::openFile(m_configLocalPrefix + filename, errorDescription, &mtime); - } else { + } else if (m_configHttpClient) { + string uri = m_configUriPrefix + filename + m_configLangQuery; string content; - if (m_configHttpClient - && m_configHttpClient->get(m_configUriPrefix + filename + m_configLangQuery, "", &content, &mtime)) { + bool repeat = false; + if (m_configHttpClient->get(uri, "", &content, &repeat, &mtime)) { stream = new istringstream(content); + } else { + if (!content.empty()) { + logError(lf_main, "HTTP failure%s: %s", repeat ? ", repeating" : "", content.c_str()); + content = ""; + } + if (repeat) { + usleep(REPEAT_NANOS); + if (m_configHttpClient->get(uri, "", &content, nullptr, &mtime)) { + stream = new istringstream(content); + } + } } } result_t result; diff --git a/src/lib/utils/httpclient.cpp b/src/lib/utils/httpclient.cpp index 35b05e202..50440491a 100755 --- a/src/lib/utils/httpclient.cpp +++ b/src/lib/utils/httpclient.cpp @@ -431,12 +431,12 @@ void HttpClient::disconnect() { } } -bool HttpClient::get(const string& uri, const string& body, string* response, time_t* time) { - return request("GET", uri, body, response, time); +bool HttpClient::get(const string& uri, const string& body, string* response, bool* repeatable, time_t* time) { + return request("GET", uri, body, response, repeatable, time); } -bool HttpClient::post(const string& uri, const string& body, string* response) { - return request("POST", uri, body, response); +bool HttpClient::post(const string& uri, const string& body, string* response, bool* repeatable) { + return request("POST", uri, body, response, repeatable); } const int indexToMonth[] = { @@ -446,9 +446,13 @@ const int indexToMonth[] = { -1, -1, 4, -1, -1, -1, -1, -1, // 24-31 }; -bool HttpClient::request(const string& method, const string& uri, const string& body, string* response, time_t* time) { +bool HttpClient::request(const string& method, const string& uri, const string& body, string* response, +bool* repeatable, time_t* time) { if (!ensureConnected()) { *response = "not connected"; + if (repeatable) { + *repeatable = true; + } return false; } ostringstream ostr; @@ -473,6 +477,9 @@ bool HttpClient::request(const string& method, const string& uri, const string& if (sent < 0) { disconnect(); *response = "send error"; + if (repeatable) { + *repeatable = true; + } return false; } pos += sent; diff --git a/src/lib/utils/httpclient.h b/src/lib/utils/httpclient.h index fcfcddeb6..7bd3cf1f4 100755 --- a/src/lib/utils/httpclient.h +++ b/src/lib/utils/httpclient.h @@ -188,19 +188,24 @@ class HttpClient { * @param uri the URI string. * @param body the optional body to send. * @param response the response body from the server (or the HTTP header on error). + * @param repeatable optional pointer to a bool in which to store whether the request should be repeated later on + * (e.g. due to temporary connectivity issues). * @param time optional pointer to a @a time_t value for storing the modification time of the file, or nullptr. * @return true on success, false on error. */ - bool get(const string& uri, const string& body, string* response, time_t* time = nullptr); + bool get(const string& uri, const string& body, string* response, bool* repeatable = nullptr, + time_t* time = nullptr); /** * Execute a POST request. * @param uri the URI string. * @param body the optional body to send. * @param response the response body from the server (or the HTTP header on error). + * @param repeatable optional pointer to a bool in which to store whether the request should be repeated later on + * (e.g. due to temporary connectivity issues). * @return true on success, false on error. */ - bool post(const string& uri, const string& body, string* response); + bool post(const string& uri, const string& body, string* response, bool* repeatable = nullptr); /** * Execute an arbitrary request. @@ -208,10 +213,13 @@ class HttpClient { * @param uri the URI string. * @param body the optional body to send. * @param response the response body from the server (or the HTTP header on error). + * @param repeatable optional pointer to a bool in which to store whether the request should be repeated later on + * (e.g. due to temporary connectivity issues). * @param time optional pointer to a @a time_t value for storing the modification time of the file, or nullptr. * @return true on success, false on error. */ - bool request(const string& method, const string& uri, const string& body, string* response, time_t* time = nullptr); + bool request(const string& method, const string& uri, const string& body, string* response, + bool* repeatable = nullptr, time_t* time = nullptr); private: /** From cd097b1d2b0eb307ee9b4076bb6c836c4d4bca6f Mon Sep 17 00:00:00 2001 From: John Date: Sat, 17 Jun 2023 10:25:12 +0200 Subject: [PATCH 098/345] higher config web service timeout, formatting --- ChangeLog.md | 1 + src/ebusd/main.cpp | 7 +++---- src/lib/ebus/result.cpp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 7f7867cc4..24bfa9b16 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -6,6 +6,7 @@ * fix for high traffic on KNX integration * add workaround for libssl issues, add debug logging and force it using IPv4 * fix log level potentially set too late during startup +* fix too low timeout for config web service ## Features * add log entry for unstartable TCP ports diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index 3a33e2e53..290fd8175 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -912,10 +912,9 @@ int main(int argc, char* argv[], char* envp[]) { } configHttpClient = new HttpClient(); if ( - // check with low timeout of 1 second initially: - !configHttpClient->connect(configHost, configPort, proto == "https", PACKAGE_NAME "/" PACKAGE_VERSION, 1) - // if that did not work, issue a single retry with default timeout: - && !configHttpClient->connect(configHost, configPort, proto == "https", PACKAGE_NAME "/" PACKAGE_VERSION) + !configHttpClient->connect(configHost, configPort, proto == "https", PACKAGE_NAME "/" PACKAGE_VERSION) + // if that did not work, issue a single retry with higher timeout: + && !configHttpClient->connect(configHost, configPort, proto == "https", PACKAGE_NAME "/" PACKAGE_VERSION, 8) ) { logWrite(lf_main, ll_error, "invalid configPath URL (connect)"); // force logging on exit delete configHttpClient; diff --git a/src/lib/ebus/result.cpp b/src/lib/ebus/result.cpp index 9e587d824..5e1a3fa04 100755 --- a/src/lib/ebus/result.cpp +++ b/src/lib/ebus/result.cpp @@ -44,7 +44,7 @@ const char* getResultCode(result_t resultCode) { case RESULT_ERR_DUPLICATE: return "ERR: duplicate entry"; case RESULT_ERR_DUPLICATE_NAME: return "ERR: duplicate name"; case RESULT_ERR_BUS_LOST: return "ERR: arbitration lost"; - case RESULT_ERR_ARB_RUNNING: return "ERR: arbitration running"; + case RESULT_ERR_ARB_RUNNING: return "ERR: arbitration running"; case RESULT_ERR_CRC: return "ERR: CRC error"; case RESULT_ERR_ACK: return "ERR: ACK error"; case RESULT_ERR_NAK: return "ERR: NAK received"; From e33810aafec9843c57d95467ea253a7e0c95cc85 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 17 Jun 2023 11:16:38 +0200 Subject: [PATCH 099/345] use vars for executable location, make bash verbose --- test_coverage.sh | 168 ++++++++++++++++++++++++----------------------- 1 file changed, 86 insertions(+), 82 deletions(-) diff --git a/test_coverage.sh b/test_coverage.sh index e76ac9ca8..0fb5e24c8 100755 --- a/test_coverage.sh +++ b/test_coverage.sh @@ -1,58 +1,63 @@ -#!/bin/bash -./src/ebusd/ebusd --help >/dev/null -./src/ebusd/ebusd -r -f -x >/dev/null 2>/dev/null -./src/ebusd/ebusd -f -d "" >/dev/null 2>/dev/null -./src/ebusd/ebusd -f -d "tcp:192.168.999.999:1" >/dev/null 2>/dev/null -./src/ebusd/ebusd -f -d "enh:192.168.999.999:1" >/dev/null 2>/dev/null -./src/ebusd/ebusd -f -d "/dev/ttyUSBx9" >/dev/null 2>/dev/null -./src/ebusd/ebusd -f --nodevicecheck >/dev/null 2>/dev/null -./src/ebusd/ebusd -f --readonly >/dev/null 2>/dev/null -./src/ebusd/ebusd -f --scanconfig=full -r >/dev/null 2>/dev/null -./src/ebusd/ebusd -f --latency 999999 >/dev/null 2>/dev/null -./src/ebusd/ebusd -f -c "" >/dev/null 2>/dev/null -./src/ebusd/ebusd -f -r --scanconfig=fe >/dev/null 2>/dev/null -./src/ebusd/ebusd -f -r --configlang=en >/dev/null 2>/dev/null -./src/ebusd/ebusd -f --pollinterval 999999 >/dev/null 2>/dev/null -./src/ebusd/ebusd -f --inject 01fe030400/ >/dev/null 2>/dev/null -./src/ebusd/ebusd -f --address 999 >/dev/null 2>/dev/null -./src/ebusd/ebusd -f --acquiretimeout 999999 >/dev/null 2>/dev/null -./src/ebusd/ebusd -f --acquireretries 999999 >/dev/null 2>/dev/null -./src/ebusd/ebusd -f --sendretries 999999 >/dev/null 2>/dev/null -./src/ebusd/ebusd -f --receivetimeout 9999999 >/dev/null 2>/dev/null -./src/ebusd/ebusd -f --numbermasters 999999 >/dev/null 2>/dev/null -./src/ebusd/ebusd -f -r --answer >/dev/null 2>/dev/null -./src/ebusd/ebusd -f -r --generatesyn >/dev/null 2>/dev/null -./src/ebusd/ebusd -f -r --initsend >/dev/null 2>/dev/null -./src/ebusd/ebusd -f -r --scanconfig=0 >/dev/null 2>/dev/null -./src/ebusd/ebusd -f --pidfile "" >/dev/null 2>/dev/null -./src/ebusd/ebusd -f -p 999999 >/dev/null 2>/dev/null -./src/ebusd/ebusd -f --localhost >/dev/null 2>/dev/null -./src/ebusd/ebusd -f --httpport 999999 >/dev/null 2>/dev/null -./src/ebusd/ebusd -f --htmlpath "" >/dev/null 2>/dev/null -./src/ebusd/ebusd -f --updatecheck=off >/dev/null 2>/dev/null -./src/ebusd/ebusd -f -l "" >/dev/null 2>/dev/null -./src/ebusd/ebusd -f --log "all debug" >/dev/null 2>/dev/null -./src/ebusd/ebusd -f --logareas some >/dev/null 2>/dev/null -./src/ebusd/ebusd -f --loglevel unknown >/dev/null 2>/dev/null -./src/ebusd/ebusd -f --lograwdata >/dev/null 2>/dev/null -./src/ebusd/ebusd -f --lograwdata=bytes >/dev/null 2>/dev/null -./src/ebusd/ebusd -f --lograwdatafile=/xyz >/dev/null 2>/dev/null -./src/ebusd/ebusd -f --lograwdatasize=9999999 >/dev/null 2>/dev/null -./src/ebusd/ebusd -f --dump >/dev/null 2>/dev/null -./src/ebusd/ebusd -f --dumpfile "" >/dev/null 2>/dev/null -./src/ebusd/ebusd -f --dumpsize 9999999 >/dev/null 2>/dev/null -./src/ebusd/ebusd -f --dumpflush >/dev/null 2>/dev/null -./src/ebusd/ebusd -f --accesslevel=inst >/dev/null 2>/dev/null -./src/ebusd/ebusd -f --aclfile=/ >/dev/null 2>/dev/null -./src/ebusd/ebusd -f --enablehex >/dev/null 2>/dev/null -./src/ebusd/ebusd -f --enabledefine >/dev/null 2>/dev/null -./src/ebusd/ebusd -f --mqttport= >/dev/null 2>/dev/null -./src/ebusd/ebusd -f --mqttport=9999999 >/dev/null 2>/dev/null -./src/ebusd/ebusd -f --mqttuser=username --mqttpass=password --mqttclientid=1234 --mqttport=1883 --mqtttopic=ebusd/%circuit/%name/%field --mqttretain --mqttjson --mqttverbose --mqttlog --mqttignoreinvalid --mqttchanges --mqtthost "" >/dev/null 2>/dev/null -./src/ebusd/ebusd -f --mqttca=/cafile/ --mqttcert=/cert --mqttkey=12345678 --mqttkeypass=secret --mqttinsecure >/dev/null 2>/dev/null -./src/ebusd/ebusd -c contrib/etc/ebusd -s -f --inject=stop "ff08070400/0ab5303132333431313131" >/dev/null 2>/dev/null -./src/ebusd/ebusd -c contrib/etc/ebusd -s -f --inject=stop "ff08070400" >/dev/null 2>/dev/null -./src/ebusd/ebusd -c contrib/etc/ebusd -s -f --inject=stop "ff080704/" >/dev/null 2>/dev/null +#!/bin/bash -x +prefix=. +ebusd=$prefix/src/ebusd/ebusd +ebusctl=$prefix/src/tools/ebusctl +ebuspicloader=$prefix/src/tools/ebuspicloader + +$ebusd --help >/dev/null +$ebusd -r -f -x >/dev/null 2>/dev/null +$ebusd -f -d "" >/dev/null 2>/dev/null +$ebusd -f -d "tcp:192.168.999.999:1" >/dev/null 2>/dev/null +$ebusd -f -d "enh:192.168.999.999:1" >/dev/null 2>/dev/null +$ebusd -f -d "/dev/ttyUSBx9" >/dev/null 2>/dev/null +$ebusd -f --nodevicecheck >/dev/null 2>/dev/null +$ebusd -f --readonly >/dev/null 2>/dev/null +$ebusd -f --scanconfig=full -r >/dev/null 2>/dev/null +$ebusd -f --latency 999999 >/dev/null 2>/dev/null +$ebusd -f -c "" >/dev/null 2>/dev/null +$ebusd -f -r --scanconfig=fe >/dev/null 2>/dev/null +$ebusd -f -r --configlang=en >/dev/null 2>/dev/null +$ebusd -f --pollinterval 999999 >/dev/null 2>/dev/null +$ebusd -f --inject 01fe030400/ >/dev/null 2>/dev/null +$ebusd -f --address 999 >/dev/null 2>/dev/null +$ebusd -f --acquiretimeout 999999 >/dev/null 2>/dev/null +$ebusd -f --acquireretries 999999 >/dev/null 2>/dev/null +$ebusd -f --sendretries 999999 >/dev/null 2>/dev/null +$ebusd -f --receivetimeout 9999999 >/dev/null 2>/dev/null +$ebusd -f --numbermasters 999999 >/dev/null 2>/dev/null +$ebusd -f -r --answer >/dev/null 2>/dev/null +$ebusd -f -r --generatesyn >/dev/null 2>/dev/null +$ebusd -f -r --initsend >/dev/null 2>/dev/null +$ebusd -f -r --scanconfig=0 >/dev/null 2>/dev/null +$ebusd -f --pidfile "" >/dev/null 2>/dev/null +$ebusd -f -p 999999 >/dev/null 2>/dev/null +$ebusd -f --localhost >/dev/null 2>/dev/null +$ebusd -f --httpport 999999 >/dev/null 2>/dev/null +$ebusd -f --htmlpath "" >/dev/null 2>/dev/null +$ebusd -f --updatecheck=off >/dev/null 2>/dev/null +$ebusd -f -l "" >/dev/null 2>/dev/null +$ebusd -f --log "all debug" >/dev/null 2>/dev/null +$ebusd -f --logareas some >/dev/null 2>/dev/null +$ebusd -f --loglevel unknown >/dev/null 2>/dev/null +$ebusd -f --lograwdata >/dev/null 2>/dev/null +$ebusd -f --lograwdata=bytes >/dev/null 2>/dev/null +$ebusd -f --lograwdatafile=/xyz >/dev/null 2>/dev/null +$ebusd -f --lograwdatasize=9999999 >/dev/null 2>/dev/null +$ebusd -f --dump >/dev/null 2>/dev/null +$ebusd -f --dumpfile "" >/dev/null 2>/dev/null +$ebusd -f --dumpsize 9999999 >/dev/null 2>/dev/null +$ebusd -f --dumpflush >/dev/null 2>/dev/null +$ebusd -f --accesslevel=inst >/dev/null 2>/dev/null +$ebusd -f --aclfile=/ >/dev/null 2>/dev/null +$ebusd -f --enablehex >/dev/null 2>/dev/null +$ebusd -f --enabledefine >/dev/null 2>/dev/null +$ebusd -f --mqttport= >/dev/null 2>/dev/null +$ebusd -f --mqttport=9999999 >/dev/null 2>/dev/null +$ebusd -f --mqttuser=username --mqttpass=password --mqttclientid=1234 --mqttport=1883 --mqtttopic=ebusd/%circuit/%name/%field --mqttretain --mqttjson --mqttverbose --mqttlog --mqttignoreinvalid --mqttchanges --mqtthost "" >/dev/null 2>/dev/null +$ebusd -f --mqttca=/cafile/ --mqttcert=/cert --mqttkey=12345678 --mqttkeypass=secret --mqttinsecure >/dev/null 2>/dev/null +$ebusd -c contrib/etc/ebusd -s -f --inject=stop "ff08070400/0ab5303132333431313131" >/dev/null 2>/dev/null +$ebusd -c contrib/etc/ebusd -s -f --inject=stop "ff08070400" >/dev/null 2>/dev/null +$ebusd -c contrib/etc/ebusd -s -f --inject=stop "ff080704/" >/dev/null 2>/dev/null cat >contrib/etc/ebusd/bad.csv </dev/null +$ebusd -c contrib/etc/ebusd --checkconfig >/dev/null rm -f contrib/etc/ebusd/bad.csv -echo > dump -./src/tools/ebusctl -s testserver -p 100000 >/dev/null 2>/dev/null -./src/tools/ebusctl -s "" >/dev/null 2>/dev/null -./src/tools/ebusctl -p "" >/dev/null 2>/dev/null -./src/tools/ebusctl -x >/dev/null 2>/dev/null -./src/tools/ebusctl --help >/dev/null -./src/tools/ebusctl 'help x' >/dev/null 2>/dev/null -./src/tools/ebuspicloader --help >/dev/null -./src/tools/ebuspicloader -i "" >/dev/null 2>/dev/null -./src/tools/ebuspicloader -i x >/dev/null 2>/dev/null -./src/tools/ebuspicloader -i 192.168.0.10.1 >/dev/null 2>/dev/null -./src/tools/ebuspicloader -i 192.168.0.10 -d >/dev/null 2>/dev/null -./src/tools/ebuspicloader -m "" >/dev/null 2>/dev/null -./src/tools/ebuspicloader -m 65 >/dev/null 2>/dev/null -./src/tools/ebuspicloader -d -i 192.168.0.10 >/dev/null 2>/dev/null -./src/tools/ebuspicloader -d -m "" >/dev/null 2>/dev/null -./src/tools/ebuspicloader -d >/dev/null 2>/dev/null -./src/tools/ebuspicloader -a 9000 >/dev/null 2>/dev/null -./src/tools/ebuspicloader -a 150 -f ./src/tools/ebuspicloader -i 192.168.0.10 -m 24 -M -r -s -v /dev/zero >/dev/null 2>/dev/null -./src/tools/ebuspicloader -f x >/dev/null 2>/dev/null +$ebusctl -s testserver -p 100000 >/dev/null 2>/dev/null +$ebusctl -s "" >/dev/null 2>/dev/null +$ebusctl -p "" >/dev/null 2>/dev/null +$ebusctl -x >/dev/null 2>/dev/null +$ebusctl --help >/dev/null +$ebusctl 'help x' >/dev/null 2>/dev/null +$ebuspicloader --help >/dev/null +$ebuspicloader -i "" >/dev/null 2>/dev/null +$ebuspicloader -i x >/dev/null 2>/dev/null +$ebuspicloader -i 192.168.0.10.1 >/dev/null 2>/dev/null +$ebuspicloader -i 192.168.0.10 -d >/dev/null 2>/dev/null +$ebuspicloader -m "" >/dev/null 2>/dev/null +$ebuspicloader -m 65 >/dev/null 2>/dev/null +$ebuspicloader -d -i 192.168.0.10 >/dev/null 2>/dev/null +$ebuspicloader -d -m "" >/dev/null 2>/dev/null +$ebuspicloader -d >/dev/null 2>/dev/null +$ebuspicloader -a 9000 >/dev/null 2>/dev/null +$ebuspicloader -a 150 -f $ebuspicloader -i 192.168.0.10 -m 24 -M -r -s -v /dev/zero >/dev/null 2>/dev/null +$ebuspicloader -f x >/dev/null 2>/dev/null echo -e ':100800008431542CAE3401347E1484314E01961E52\n:00000001FF' > firmware.hex -./src/tools/ebuspicloader -f firmware.hex >/dev/null +$ebuspicloader -f firmware.hex >/dev/null #server: php -r 'echo "php is available";'|egrep 'php is available' if [ ! "$?" = 0 ]; then @@ -243,7 +247,7 @@ elif [[ -n "$1" ]]; then pid=$(ps -C ebusd -o pid=) done else - ./src/ebusd/ebusd -d tcp:127.0.0.1:8876 --initsend --latency 10 -n -c "$PWD/contrib/etc/ebusd" --pollinterval=10 -s -a 31 --acquireretries 3 --answer --generatesyn --receivetimeout 40000 --sendretries 1 --enablehex --htmlpath "$PWD/contrib/html" --httpport 8878 --pidfile "$PWD/ebusd.pid" --localhost -p 8877 -l "$PWD/ebusd.log" --logareas all --loglevel debug --lograwdata=bytes --lograwdatafile "$PWD/ebusd.raw" --lograwdatasize 1 --dumpfile "$PWD/ebusd.dump" --dumpsize 100 -D --scanconfig --aclfile="$PWD/acl.csv" --mqttport=1883 --enablehex --enabledefine + $ebusd -d tcp:127.0.0.1:8876 --initsend --latency 10 -n -c "$PWD/contrib/etc/ebusd" --pollinterval=10 -s -a 31 --acquireretries 3 --answer --generatesyn --receivetimeout 40000 --sendretries 1 --enablehex --htmlpath "$PWD/contrib/html" --httpport 8878 --pidfile "$PWD/ebusd.pid" --localhost -p 8877 -l "$PWD/ebusd.log" --logareas all --loglevel debug --lograwdata=bytes --lograwdatafile "$PWD/ebusd.raw" --lograwdatasize 1 --dumpfile "$PWD/ebusd.dump" --dumpsize 100 -D --scanconfig --aclfile="$PWD/acl.csv" --mqttport=1883 --enablehex --enabledefine sleep 3 pid=`head -n 1 "$PWD/ebusd.pid"` fi @@ -352,7 +356,7 @@ i g define -r "r,cir,nam,cmt,,08,b509,,,,UCH" decode -V -N UCH 102030 -encode UCH 10;1 +encode UCH,,,,uch 10;1 raw bytes raw reload @@ -361,7 +365,7 @@ EOF status=1 cnt=3 function send() { -# ./src/tools/ebusctl -p 8877 -t 10 "$@" +# $ebusctl -p 8877 -t 10 "$@" echo "$@" | nc -N -w 10 localhost 8877 } while [[ ! "$status" = 0 ]] && [[ $cnt -gt 0 ]]; do @@ -382,9 +386,9 @@ if [ "$status" != 0 ]; then fi echo `date` "got signal" sleep 2 -echo "listen"|./src/tools/ebusctl -p 8877 & +echo "listen"|$ebusctl -p 8877 & lstpid=$! -./src/tools/ebusctl -p 8899 >/dev/null 2>/dev/null +$ebusctl -p 8899 >/dev/null 2>/dev/null for line in "${lines[@]}"; do if [ -n "$line" ]; then echo `date` "send: $line" From e295cc34344dccc057ebae862abecc43deea6389 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 8 Jul 2023 09:25:49 +0200 Subject: [PATCH 100/345] typedef for options --- src/ebusd/main.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ebusd/main.h b/src/ebusd/main.h index abaaf6675..786703110 100644 --- a/src/ebusd/main.h +++ b/src/ebusd/main.h @@ -33,7 +33,7 @@ namespace ebusd { */ /** A structure holding all program options. */ -struct options { +typedef struct options { const char* device; //!< eBUS device (serial device or [udp:]ip:port) [/dev/ttyUSB0] bool noDeviceCheck; //!< skip serial eBUS device test bool readOnly; //!< read-only access to the device @@ -88,7 +88,7 @@ struct options { const char* dumpFile; //!< name of dump file [/tmp/ebusd_dump.bin] unsigned int dumpSize; //!< maximum size of dump file in kB [100] bool dumpFlush; //!< flush each byte -}; +} options_t; } // namespace ebusd From c1624f528fb6bde289f4b909154ed87dba31303d Mon Sep 17 00:00:00 2001 From: John Date: Sat, 8 Jul 2023 11:08:29 +0200 Subject: [PATCH 101/345] extract request related parts --- src/ebusd/CMakeLists.txt | 1 + src/ebusd/main.cpp | 32 +++++- src/ebusd/mainloop.cpp | 137 ++++++++--------------- src/ebusd/mainloop.h | 38 +++---- src/ebusd/network.cpp | 60 +++------- src/ebusd/network.h | 202 ++-------------------------------- src/ebusd/request.cpp | 143 ++++++++++++++++++++++++ src/ebusd/request.h | 230 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 490 insertions(+), 353 deletions(-) create mode 100644 src/ebusd/request.cpp create mode 100644 src/ebusd/request.h diff --git a/src/ebusd/CMakeLists.txt b/src/ebusd/CMakeLists.txt index 95eb8933f..7166bd08b 100644 --- a/src/ebusd/CMakeLists.txt +++ b/src/ebusd/CMakeLists.txt @@ -3,6 +3,7 @@ add_definitions(-Wconversion -Wno-unused-parameter) set(ebusd_SOURCES bushandler.h bushandler.cpp datahandler.h datahandler.cpp + request.h request.cpp network.h network.cpp mainloop.h mainloop.cpp scan.h scan.cpp diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index 290fd8175..ee2faf0c7 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -31,6 +31,7 @@ #include #include #include "ebusd/mainloop.h" +#include "ebusd/network.h" #include "lib/utils/log.h" #include "lib/utils/httpclient.h" #include "ebusd/scan.h" @@ -143,9 +144,15 @@ static MessageMap* s_messageMap = nullptr; /** the @a ScanHelper instance, or nullptr. */ static ScanHelper* s_scanHelper = nullptr; +/** the @a Request @a Queue instance, or nullptr. */ +static Queue* s_requestQueue = nullptr; + /** the @a MainLoop instance, or nullptr. */ static MainLoop* s_mainLoop = nullptr; +/** the @a Network instance, or nullptr. */ +static Network* s_network = nullptr; + /** the (optionally corrected) config path for retrieving configuration files from. */ static string s_configPath = CONFIG_PATH; @@ -735,15 +742,27 @@ void daemonize() { * Clean up all dynamically allocated and stop main loop and all dependent components. */ void cleanup() { - if (s_mainLoop) { + if (s_network != nullptr) { + delete s_network; + s_network = nullptr; + } + if (s_mainLoop != nullptr) { delete s_mainLoop; s_mainLoop = nullptr; } - if (s_messageMap) { + if (s_requestQueue != nullptr) { + Request* msg; + while ((msg = s_requestQueue->pop()) != nullptr) { + delete msg; + } + delete s_requestQueue; + s_requestQueue = nullptr; + } + if (s_messageMap != nullptr) { delete s_messageMap; s_messageMap = nullptr; } - if (s_scanHelper) { + if (s_scanHelper != nullptr) { delete s_scanHelper; s_scanHelper = nullptr; } @@ -1030,8 +1049,10 @@ int main(int argc, char* argv[], char* envp[]) { // load configuration files s_scanHelper->loadConfigFiles(!s_opt.scanConfig); + s_requestQueue = new Queue(); + // create the MainLoop and start it - s_mainLoop = new MainLoop(s_opt, device, s_messageMap, s_scanHelper); + s_mainLoop = new MainLoop(s_opt, device, s_messageMap, s_scanHelper, s_requestQueue); if (s_opt.injectMessages) { BusHandler* busHandler = s_mainLoop->getBusHandler(); int scanAdrCount = 0; @@ -1064,6 +1085,9 @@ int main(int argc, char* argv[], char* envp[]) { } s_mainLoop->start("mainloop"); + s_network = new Network(s_opt.localOnly, s_opt.port, s_opt.httpPort, s_requestQueue); + s_network->start("network"); + // wait for end of MainLoop s_mainLoop->join(); diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index 03ae96515..ddcda4eb4 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -105,12 +105,13 @@ result_t UserList::addFromFile(const string& filename, unsigned int lineNo, map< #define VERBOSITY_4 (VERBOSITY_3 | OF_ALL_ATTRS) -MainLoop::MainLoop(const struct options& opt, Device *device, MessageMap* messages, ScanHelper* scanHelper) +MainLoop::MainLoop(const struct options& opt, Device *device, MessageMap* messages, ScanHelper* scanHelper, + Queue* requestQueue) : Thread(), m_device(device), m_reconnectCount(0), m_userList(opt.accessLevel), m_messages(messages), m_scanHelper(scanHelper), m_address(opt.address), m_scanConfig(opt.scanConfig), m_initialScan(opt.readOnly ? ESC : opt.initialScan), m_scanStatus(SCAN_STATUS_NONE), m_polling(opt.pollInterval > 0), m_enableHex(opt.enableHex), - m_shutdown(false), m_runUpdateCheck(opt.updateCheck), m_httpClient() { + m_shutdown(false), m_runUpdateCheck(opt.updateCheck), m_httpClient(), m_requestQueue(requestQueue) { m_device->setListener(this); // open Device result_t result = m_device->open(); @@ -160,8 +161,6 @@ MainLoop::MainLoop(const struct options& opt, Device *device, MessageMap* messag // create network m_htmlPath = opt.htmlPath; - m_network = new Network(opt.localOnly, opt.port, opt.httpPort, &m_netQueue); - m_network->start("network"); logInfo(lf_main, "registering data handlers"); if (datahandler_register(&m_userList, m_busHandler, messages, &m_dataHandlers)) { logInfo(lf_main, "registered data handlers"); @@ -192,10 +191,6 @@ MainLoop::~MainLoop() { delete m_logRawFile; m_logRawFile = nullptr; } - if (m_network != nullptr) { - delete m_network; - m_network = nullptr; - } if (m_busHandler != nullptr) { delete m_busHandler; m_busHandler = nullptr; @@ -204,10 +199,6 @@ MainLoop::~MainLoop() { delete m_device; m_device = nullptr; } - NetMessage* msg; - while ((msg = m_netQueue.pop()) != nullptr) { - delete msg; - } if (m_newlyDefinedMessages) { delete m_newlyDefinedMessages; m_newlyDefinedMessages = nullptr; @@ -245,8 +236,8 @@ void MainLoop::run() { dataHandler->startHandler(); } while (!m_shutdown) { - // pick the next message to handle - NetMessage* netMessage = m_netQueue.pop(taskDelay); + // pick the next request to handle + Request* req = m_requestQueue->pop(taskDelay); time(&now); if (now < lastTaskRun) { // clock skew @@ -410,66 +401,62 @@ void MainLoop::run() { m_messages->unlock(); sinkSince = now; } - if (netMessage == nullptr) { + if (req == nullptr) { continue; } if (m_shutdown) { - netMessage->setResult("ERR: shutdown", "", nullptr, now, true); + req->setResult("ERR: shutdown", "", nullptr, now, true); break; } - string request = netMessage->getRequest(); - string user = netMessage->getUser(); - ClientSettings settings = netMessage->getSettings(&since); - if (!netMessage->isListeningMode()) { + string user = req->getUser(); + RequestMode reqMode = req->getMode(&since); + if (reqMode.listenMode == lm_none) { 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, &settings, &user, &reload, &ostream); - if (!netMessage->isHttp() && (ostream.tellp() == 0 || result != RESULT_OK)) { - if (settings.mode != cm_direct) { + if (!req->empty()) { + req->log(); + result_t result = decodeRequest(req, &connected, &reqMode, &user, &reload, &ostream); + if (!req->isHttp() && (ostream.tellp() == 0 || result != RESULT_OK)) { + if (reqMode.listenMode != lm_direct) { ostream.str(""); } ostream << getResultCode(result); } - if (ostream.tellp() > 100) { - logDebug(lf_main, "<<< %s ...", ostream.str().substr(0, 100).c_str()); - } else { - logDebug(lf_main, "<<< %s", ostream.str().c_str()); - } + const auto resp = ostream.str(); + req->log(&resp); if (ostream.tellp() == 0) { ostream << "\n"; // only for HTTP - } else if (!netMessage->isHttp()) { - ostream << (settings.mode == cm_direct ? "\n" : "\n\n"); + } else if (!req->isHttp()) { + ostream << (reqMode.listenMode == lm_direct ? "\n" : "\n\n"); } } - if (settings.mode == cm_listen) { - if (!settings.listenOnlyUnknown) { + if (reqMode.listenMode == lm_listen) { + if (!reqMode.listenOnlyUnknown) { string levels = getUserLevels(user); messages.clear(); m_messages->findAll("", "", levels, false, true, true, true, true, true, since, now, true, &messages); for (const auto message : messages) { ostream << message->getCircuit() << " " << message->getName() << " = " << dec; - message->decodeLastData(false, nullptr, -1, settings.format, &ostream); + message->decodeLastData(false, nullptr, -1, reqMode.format, &ostream); ostream << endl; } } - if (settings.listenWithUnknown || settings.listenOnlyUnknown) { + if (reqMode.listenWithUnknown || reqMode.listenOnlyUnknown) { if (m_busHandler->isGrabEnabled()) { m_busHandler->formatGrabResult(true, OF_NONE, &ostream, true, since, now); } else { m_busHandler->enableGrab(true); // needed for listening to all messages } } - } else if (settings.mode == cm_direct) { + } else if (reqMode.listenMode == lm_direct) { if (m_busHandler->isGrabEnabled()) { m_busHandler->formatGrabResult(false, OF_NONE, &ostream, true, since, now); } } // send result to client - netMessage->setResult(ostream.str(), user, &settings, now, !connected); + req->setResult(ostream.str(), user, &reqMode, now, !connected); } } @@ -529,49 +516,18 @@ void MainLoop::notifyStatus(bool error, const char* message) { } } -result_t MainLoop::decodeMessage(const string &data, bool isHttp, bool* connected, ClientSettings* settings, +result_t MainLoop::decodeRequest(Request* req, bool* connected, RequestMode* reqMode, string* user, bool* reload, ostringstream* ostream) { - string token, previous; - istringstream stream(data); vector args; - char escaped = 0; - - char delim = ' '; - while (getline(stream, token, delim)) { - if (!isHttp) { - if (escaped) { - args.pop_back(); - if (token.length() > 0 && token[token.length()-1] == escaped) { - token.erase(token.length() - 1, 1); - escaped = 0; - } - token = previous + " " + token; - } 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] == escaped) { - token.erase(token.length() - 1, 1); - escaped = 0; - } - } - } - args.push_back(token); - previous = token; - if (isHttp) { - delim = (args.size() == 1) ? '?' : '\n'; - } - } - - if (isHttp) { + req->split(&args); + string cmd = args.size() > 0 ? args[0] : ""; + if (req->isHttp()) { if (args.size() < 2) { *connected = false; *ostream << "HTTP/1.0 400 Bad Request\r\n\r\n"; return RESULT_OK; } - const char* str = args.size() > 0 ? args[0].c_str() : ""; - if (strcmp(str, "GET") == 0) { + if (cmd == "GET") { return executeGet(args, connected, ostream); } *connected = false; @@ -579,7 +535,6 @@ result_t MainLoop::decodeMessage(const string &data, bool isHttp, bool* connecte return RESULT_OK; } - string cmd = args.size() > 0 ? args[0] : ""; transform(cmd.begin(), cmd.end(), cmd.begin(), ::toupper); if (cmd == "?" || cmd == "H" || cmd == "HELP") { // found "HELP CMD" @@ -587,8 +542,8 @@ result_t MainLoop::decodeMessage(const string &data, bool isHttp, bool* connecte transform(cmd.begin(), cmd.end(), cmd.begin(), ::toupper); args.clear(); // empty args is used as command help indicator } - if (settings->mode == cm_direct) { - return executeDirect(args, &settings->mode, ostream); + if (reqMode->listenMode == lm_direct) { + return executeDirect(args, reqMode, ostream); } if (cmd.empty() && args.size() == 0) { return executeHelp(ostream); @@ -620,10 +575,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, settings, ostream); + return executeListen(args, reqMode, ostream); } if (cmd == "DIRECT") { - return executeDirect(args, &settings->mode, ostream); + return executeDirect(args, reqMode, ostream); } if (cmd == "S" || cmd == "STATE") { return executeState(args, ostream); @@ -1293,10 +1248,10 @@ result_t MainLoop::executeHex(const vector& args, ostringstream* ostream return RESULT_OK; } -result_t MainLoop::executeDirect(const vector& args, ClientMode* mode, ostringstream* ostream) { - if (*mode != cm_direct) { +result_t MainLoop::executeDirect(const vector& args, RequestMode* reqMode, ostringstream* ostream) { + if (reqMode->listenMode != lm_direct) { if (args.size() == 1) { - *mode = cm_direct; + reqMode->listenMode = lm_direct; m_busHandler->enableGrab(true); // needed for listening to all messages *ostream << "direct mode started"; return RESULT_OK; @@ -1308,7 +1263,7 @@ result_t MainLoop::executeDirect(const vector& args, ClientMode* mode, o if (args.size() > 0) { string firstArg = args[0]; if (firstArg == "stop") { - *mode = cm_normal; + reqMode->listenMode = lm_none; *ostream << "direct mode stopped"; return RESULT_OK; } @@ -1321,7 +1276,7 @@ result_t MainLoop::executeDirect(const vector& args, ClientMode* mode, o } *ostream << ":"; if (!m_enableHex) { - *ostream << "ERR: command not enabled"; + *ostream << "ERR: hex command not enabled"; return RESULT_OK; } size_t argPos = 0; @@ -1560,7 +1515,7 @@ result_t MainLoop::executeFind(const vector& args, const string& levels, return RESULT_OK; } -result_t MainLoop::executeListen(const vector& args, ClientSettings* settings, ostringstream* ostream) { +result_t MainLoop::executeListen(const vector& args, RequestMode* reqMode, ostringstream* ostream) { size_t argPos = 1; OutputFormat verbosity = OF_NONE; bool listenWithUnknown = false; @@ -1594,17 +1549,17 @@ result_t MainLoop::executeListen(const vector& args, ClientSettings* set argPos++; } if (argPos > 0 && args.size() == argPos) { - settings->format = verbosity; - settings->listenWithUnknown = listenWithUnknown; - settings->listenOnlyUnknown = listenOnlyUnknown; + reqMode->format = verbosity; + reqMode->listenWithUnknown = listenWithUnknown; + reqMode->listenOnlyUnknown = listenOnlyUnknown; if (listenWithUnknown || listenOnlyUnknown) { m_busHandler->enableGrab(true); // needed for listening to all messages } - if (settings->mode == cm_listen) { + if (reqMode->listenMode == lm_listen) { *ostream << "listen continued"; return RESULT_OK; } - settings->mode = cm_listen; + reqMode->listenMode = lm_listen; *ostream << "listen started"; return RESULT_OK; } @@ -1620,7 +1575,7 @@ result_t MainLoop::executeListen(const vector& args, ClientSettings* set " -U only show unknown messages"; return RESULT_OK; } - settings->mode = cm_normal; + reqMode->listenMode = lm_none; *ostream << "listen stopped"; return RESULT_OK; } diff --git a/src/ebusd/mainloop.h b/src/ebusd/mainloop.h index a2743fe41..b1e8c2341 100644 --- a/src/ebusd/mainloop.h +++ b/src/ebusd/mainloop.h @@ -26,7 +26,7 @@ #include #include "ebusd/bushandler.h" #include "ebusd/datahandler.h" -#include "ebusd/network.h" +#include "ebusd/request.h" #include "ebusd/scan.h" #include "lib/ebus/filereader.h" #include "lib/ebus/message.h" @@ -102,13 +102,15 @@ class UserList : public UserInfo, public MappedFileReader { class MainLoop : public Thread, DeviceListener { public: /** - * Construct the main loop and create network and bus handling components. + * Construct the main loop and create bus handling components. * @param opt the program options. * @param device the @a Device instance. * @param messages the @a MessageMap instance. * @param scanHelper the @a ScanHelper instance. + * @param requestQueue the reference to the @a Request @a Queue. */ - MainLoop(const struct options& opt, Device *device, MessageMap* messages, ScanHelper* scanHelper); + MainLoop(const struct options& opt, Device *device, MessageMap* messages, ScanHelper* scanHelper, + Queue* requestQueue); /** * Destructor. @@ -126,12 +128,6 @@ class MainLoop : public Thread, DeviceListener { */ BusHandler* getBusHandler() { return m_busHandler; } - /** - * Add a client @a NetMessage to the queue. - * @param message the client @a NetMessage to handle. - */ - void addMessage(NetMessage* message) { m_netQueue.push(message); } - // @copydoc void notifyDeviceData(symbol_t symbol, bool received) override; @@ -146,17 +142,16 @@ class MainLoop : public Thread, DeviceListener { private: /** - * Decode and execute client message. - * @param data the data string to decode (may be empty). + * Decode and execute client request. + * @param req the @a Request to decode. * @param connected set to false when the client connection shall be closed. - * @param isHttp true for HTTP message. - * @param settings set to the new client settings. + * @param reqMode the @a RequestMode to use and update. * @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, ClientSettings* settings, + result_t decodeRequest(Request* req, bool* connected, RequestMode* reqMode, string* user, bool* reload, ostringstream* ostream); /** @@ -227,11 +222,11 @@ class MainLoop : public Thread, DeviceListener { /** * 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 reqMode the @a RequestMode to use and update. * @param ostream the @a ostringstream to format the result string to. * @return the result code. */ - result_t executeDirect(const vector& args, ClientMode* mode, ostringstream* ostream); + result_t executeDirect(const vector& args, RequestMode* reqMode, ostringstream* ostream); /** * Execute the find command. @@ -245,11 +240,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 settings set to the new client settings. + * @param reqMode the @a RequestMode to use and update. * @param ostream the @a ostringstream to format the result string to. * @return the result code. */ - result_t executeListen(const vector& args, ClientSettings* settings, ostringstream* ostream); + result_t executeListen(const vector& args, RequestMode* reqMode, ostringstream* ostream); /** * Execute the state command. @@ -445,11 +440,8 @@ class MainLoop : public Thread, DeviceListener { /** the created @a BusHandler instance. */ BusHandler* m_busHandler; - /** the created @a Network instance. */ - Network* m_network; - - /** the @a NetMessage @a Queue. */ - Queue m_netQueue; + /** the reference to the @a Request @a Queue. */ + Queue* m_requestQueue; /** the path for HTML files served by the HTTP port. */ string m_htmlPath; diff --git a/src/ebusd/network.cpp b/src/ebusd/network.cpp index a2cdacaa0..31cc445d6 100644 --- a/src/ebusd/network.cpp +++ b/src/ebusd/network.cpp @@ -37,39 +37,6 @@ int Connection::m_ids = 0; #define POLLRDHUP 0 #endif -bool NetMessage::add(const char* request) { - if (request && request[0]) { - string add = request; - add.erase(remove(add.begin(), add.end(), '\r'), add.end()); - m_request.append(add); - } - size_t pos = m_request.find(m_isHttp ? "\n\n" : "\n"); - if (pos != string::npos) { - if (m_isHttp) { - pos = m_request.find("\n"); - m_request.resize(pos); // reduce to first line - // typical first line: GET /ehp/outsidetemp HTTP/1.1 - pos = m_request.rfind(" HTTP/"); - if (pos != string::npos) { - m_request.resize(pos); // remove "HTTP/x.x" suffix - } - pos = 0; - while ((pos=m_request.find('%', pos)) != string::npos && pos+2 <= m_request.length()) { - unsigned int value1, value2; - if (sscanf("%1x%1x", m_request.c_str()+pos+1, &value1, &value2) < 2) { - break; - } - m_request[pos] = static_cast(((value1&0x0f) << 4) | (value2&0x0f)); - m_request.erase(pos+1, 2); - } - } else if (pos+1 == m_request.length()) { - m_request.resize(pos); // reduce to complete lines - } - return true; - } - return m_request.length() == 0 && isListeningMode(); -} - void Connection::run() { int ret; @@ -108,7 +75,7 @@ void Connection::run() { #endif bool closed = false; - NetMessage message(m_isHttp); + RequestImpl req(m_isHttp); while (!closed) { #ifdef HAVE_PPOLL @@ -146,7 +113,7 @@ void Connection::run() { #endif } - if (newData || message.isListeningMode()) { + if (newData || req.getMode().listenMode != lm_none) { char data[256]; if (!m_socket->isValid()) { @@ -165,21 +132,24 @@ void Connection::run() { } // decode client data - if (message.add(data)) { - m_netQueue->push(&message); + if (req.add(data)) { + m_requestQueue->push(&req); // wait for result logDebug(lf_network, "[%05d] wait for result", getID()); string result; - message.getResult(&result); + bool disconnect = req.waitResponse(&result); if (!m_socket->isValid()) { break; } m_socket->send(result.c_str(), result.size()); + if (disconnect) { + break; + } } - if (message.isDisconnect() || !m_socket->isValid()) { + if (!m_socket->isValid()) { break; } } @@ -193,8 +163,8 @@ void Connection::run() { } -Network::Network(const bool local, const uint16_t port, const uint16_t httpPort, Queue* netQueue) - : Thread(), m_netQueue(netQueue), m_listening(false) { +Network::Network(const bool local, const uint16_t port, const uint16_t httpPort, Queue* requestQueue) + : Thread(), m_requestQueue(requestQueue), m_listening(false) { m_tcpServer = new TCPServer(port, local ? "127.0.0.1" : "0.0.0.0"); if (m_tcpServer != nullptr && m_tcpServer->start() == 0) { @@ -214,9 +184,9 @@ Network::Network(const bool local, const uint16_t port, const uint16_t httpPort, Network::~Network() { stop(); - NetMessage* netMsg; - while ((netMsg = m_netQueue->pop()) != nullptr) { - netMsg->setResult("ERR: shutdown", "", nullptr, 0, true); + Request* req; + while ((req = m_requestQueue->pop()) != nullptr) { + req->setResult("ERR: shutdown", "", nullptr, 0, true); } while (!m_connections.empty()) { Connection* connection = m_connections.back(); @@ -333,7 +303,7 @@ void Network::run() { if (socket == nullptr) { continue; } - Connection* connection = new Connection(socket, isHttp, m_netQueue); + Connection* connection = new Connection(socket, isHttp, m_requestQueue); string ip = socket->getIP(); connection->start("connection"); m_connections.push_back(connection); diff --git a/src/ebusd/network.h b/src/ebusd/network.h index 22206bd9c..9f21735cc 100644 --- a/src/ebusd/network.h +++ b/src/ebusd/network.h @@ -23,6 +23,7 @@ #include #include #include +#include "ebusd/request.h" #include "lib/ebus/datatype.h" #include "lib/utils/tcpsocket.h" #include "lib/utils/queue.h" @@ -35,188 +36,9 @@ namespace ebusd { * The TCP and HTTP client request handling. */ -/** 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 -}; - -/** - * Combination of client settings. - */ -struct ClientSettings { - ClientMode mode; //!< the current client mode - OutputFormat format; //!< the output format settings for listen mode - bool listenWithUnknown; //!< include unknown messages in listen mode - bool listenOnlyUnknown; //!< only print unknown messages in listen mode -}; - -/** - * Class for data/message transfer between @a Connection and @a MainLoop. - */ -class NetMessage { - public: - /** - * Constructor. - * @param isHttp whether this is a HTTP message. - */ - explicit NetMessage(bool isHttp) - : m_isHttp(isHttp), m_resultSet(false), m_disconnect(false), m_listenSince(0) { - m_settings.mode = cm_normal; - m_settings.format = OF_NONE; - m_settings.listenWithUnknown = false; - m_settings.listenOnlyUnknown = false; - pthread_mutex_init(&m_mutex, nullptr); - pthread_cond_init(&m_cond, nullptr); - } - - /** - * Destructor. - */ - ~NetMessage() { - m_resultSet = true; - pthread_mutex_destroy(&m_mutex); - pthread_cond_destroy(&m_cond); - } - - - private: - /** - * Hidden copy constructor. - * @param src the object to copy from. - */ - NetMessage(const NetMessage& src); - - - public: - /** - * Add request data received from the client. - * @param request the request data from the client. - * @return true when the request is complete and the response shall be prepared. - */ - bool add(const char* request); - - /** - * Return whether this is a HTTP message. - * @return whether this is a HTTP message. - */ - bool isHttp() const { return m_isHttp; } - - /** - * Return the request string. - * @return the request string. - */ - const string& getRequest() const { return m_request; } - - /** - * Return the current user name. - * @return the current user name. - */ - const string& getUser() const { return m_user; } - - /** - * Wait for the result being set and return the result string. - * @param result the variable in which to store the result string. - */ - void getResult(string* result) { - pthread_mutex_lock(&m_mutex); - - if (!m_resultSet) { - pthread_cond_wait(&m_cond, &m_mutex); - } - m_request.clear(); - *result = m_result; - m_result.clear(); - m_resultSet = false; - pthread_mutex_unlock(&m_mutex); - } - - /** - * Set the result string and notify the waiting thread. - * @param result the result string. - * @param user the new user name. - * @param settings the new client settings. - * @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, ClientSettings* settings, time_t listenUntil, - bool disconnect) { - pthread_mutex_lock(&m_mutex); - m_result = result; - m_user = user; - m_disconnect = disconnect; - if (settings) { - m_settings = *settings; - } - m_listenSince = listenUntil; - m_resultSet = true; - pthread_cond_signal(&m_cond); - pthread_mutex_unlock(&m_mutex); - } - - /** - * Return the client settings. - * @param listenSince set listening to the specified start time from which to add updates (inclusive). - * @return the client settings. - */ - ClientSettings getSettings(time_t* listenSince = nullptr) { - if (listenSince) { - *listenSince = m_listenSince; - } - return m_settings; - } - - /** - * 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_settings.mode == cm_listen || m_settings.mode == cm_direct; } - - /** - * Return whether the client shall be disconnected. - * @return true when the client shall be disconnected. - */ - bool isDisconnect() { return m_disconnect; } - - - private: - /** whether this is a HTTP message. */ - const bool m_isHttp; - - /** the request string. */ - string m_request; - - /** the current user name. */ - string m_user; - - /** whether the result was already set. */ - bool m_resultSet; - - /** the result string. */ - string m_result; - - /** set to true when the client shall be disconnected. */ - bool m_disconnect; - - /** mutex variable for exclusive lock. */ - pthread_mutex_t m_mutex; - - /** condition variable for exclusive lock. */ - pthread_cond_t m_cond; - - /** the client settings. */ - ClientSettings m_settings; - - /** start timestamp of listening update. */ - time_t m_listenSince; -}; /** - * class connection which handle client and baseloop communication. + * Instance of a connected client, either TCP or HTTP. */ class Connection : public Thread { public: @@ -224,10 +46,10 @@ class Connection : public Thread { * Constructor. * @param socket the @a TCPSocket for communication. * @param isHttp whether this is a HTTP message. - * @param netQueue the reference to the @a NetMessage @a Queue. + * @param requestQueue the reference to the @a Request @a Queue. */ - Connection(TCPSocket* socket, const bool isHttp, Queue* netQueue) - : Thread(), m_isHttp(isHttp), m_socket(socket), m_netQueue(netQueue), m_endedAt(0) { + Connection(TCPSocket* socket, const bool isHttp, Queue* requestQueue) + : Thread(), m_isHttp(isHttp), m_socket(socket), m_requestQueue(requestQueue), m_endedAt(0) { m_id = ++m_ids; } @@ -267,8 +89,8 @@ class Connection : public Thread { /** the @a TCPSocket for communication. */ TCPSocket* m_socket; - /** the reference to the @a NetMessage @a Queue. */ - Queue* m_netQueue; + /** the reference to the @a Request @a Queue. */ + Queue* m_requestQueue; /** notification object for shutdown procedure. */ Notify m_notify; @@ -284,7 +106,7 @@ class Connection : public Thread { }; /** - * class network which listening on tcp socket for incoming connections. + * Handler for all TCP and HTTP client connections and registry of active connections. */ class Network : public Thread { public: @@ -293,9 +115,9 @@ class Network : public Thread { * @param local true to accept connections only for local host. * @param port the port to listen for command line connections. * @param httpPort the port to listen for HTTP connections, or 0. - * @param netQueue the reference to the @a NetMessage @a Queue. + * @param requestQueue the reference to the @a Request @a Queue. */ - Network(const bool local, const uint16_t port, const uint16_t httpPort, Queue* netQueue); + Network(const bool local, const uint16_t port, const uint16_t httpPort, Queue* requestQueue); /** * destructor. @@ -317,8 +139,8 @@ class Network : public Thread { /** the list of active @a Connection instances. */ list m_connections; - /** the reference to the @a NetMessage @a Queue. */ - Queue* m_netQueue; + /** the reference to the @a Request @a Queue. */ + Queue* m_requestQueue; /** the command line @a TCPServer instance. */ TCPServer* m_tcpServer; diff --git a/src/ebusd/request.cpp b/src/ebusd/request.cpp new file mode 100644 index 000000000..383742a46 --- /dev/null +++ b/src/ebusd/request.cpp @@ -0,0 +1,143 @@ +/* + * ebusd - daemon for communication with eBUS heating systems. + * Copyright (C) 2023 John Baier + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "ebusd/request.h" +#include +#include +#include +#include "lib/utils/log.h" + +namespace ebusd { + +RequestImpl::RequestImpl(bool isHttp) + : Request(), m_isHttp(isHttp), m_resultSet(false), m_disconnect(false), m_listenSince(0) { + m_mode.listenMode = lm_none; + m_mode.format = OF_NONE; + m_mode.listenWithUnknown = false; + m_mode.listenOnlyUnknown = false; + pthread_mutex_init(&m_mutex, nullptr); + pthread_cond_init(&m_cond, nullptr); +} + +RequestImpl::~RequestImpl() { + m_resultSet = true; + pthread_mutex_destroy(&m_mutex); + pthread_cond_destroy(&m_cond); +} + +bool RequestImpl::add(const char* request) { + if (request && request[0]) { + string add = request; + add.erase(remove(add.begin(), add.end(), '\r'), add.end()); + m_request.append(add); + } + size_t pos = m_request.find(m_isHttp ? "\n\n" : "\n"); + if (pos != string::npos) { + if (m_isHttp) { + pos = m_request.find("\n"); + m_request.resize(pos); // reduce to first line + // typical first line: GET /ehp/outsidetemp HTTP/1.1 + pos = m_request.rfind(" HTTP/"); + if (pos != string::npos) { + m_request.resize(pos); // remove "HTTP/x.x" suffix + } + pos = 0; + while ((pos=m_request.find('%', pos)) != string::npos && pos+2 <= m_request.length()) { + unsigned int value1, value2; + if (sscanf("%1x%1x", m_request.c_str()+pos+1, &value1, &value2) < 2) { + break; + } + m_request[pos] = static_cast(((value1&0x0f) << 4) | (value2&0x0f)); + m_request.erase(pos+1, 2); + } + } else if (pos+1 == m_request.length()) { + m_request.resize(pos); // reduce to complete lines + } + return true; + } + return m_request.length() == 0 && m_mode.listenMode != lm_none; +} + +void RequestImpl::split(vector* args) { + string token, previous; + istringstream stream(m_request); + char escaped = 0; + + char delim = ' '; + while (getline(stream, token, delim)) { + if (!m_isHttp) { + if (escaped) { + args->pop_back(); + if (token.length() > 0 && token[token.length()-1] == escaped) { + token.erase(token.length() - 1, 1); + escaped = 0; + } + token = previous + " " + token; + } 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] == escaped) { + token.erase(token.length() - 1, 1); + escaped = 0; + } + } + } + args->push_back(token); + previous = token; + if (m_isHttp) { + delim = (args->size() == 1) ? '?' : '\n'; + } + } +} + +bool RequestImpl::waitResponse(string* result) { + pthread_mutex_lock(&m_mutex); + + if (!m_resultSet) { + pthread_cond_wait(&m_cond, &m_mutex); + } + m_request.clear(); + *result = m_result; + m_result.clear(); + m_resultSet = false; + pthread_mutex_unlock(&m_mutex); + return m_disconnect; +} + +void RequestImpl::setResult(const string& result, const string& user, RequestMode* mode, time_t listenUntil, + bool disconnect) { + pthread_mutex_lock(&m_mutex); + m_result = result; + m_user = user; + m_disconnect = disconnect; + if (mode) { + m_mode = *mode; + } + m_listenSince = listenUntil; + m_resultSet = true; + pthread_cond_signal(&m_cond); + pthread_mutex_unlock(&m_mutex); +} + +} // namespace ebusd diff --git a/src/ebusd/request.h b/src/ebusd/request.h new file mode 100644 index 000000000..7bab1248b --- /dev/null +++ b/src/ebusd/request.h @@ -0,0 +1,230 @@ +/* + * ebusd - daemon for communication with eBUS heating systems. + * Copyright (C) 2023 John Baier + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef EBUSD_REQUEST_H_ +#define EBUSD_REQUEST_H_ + +#include +#include +#include +#include +#include "lib/ebus/datatype.h" +#include "lib/utils/queue.h" +#include "lib/utils/notify.h" +#include "lib/utils/thread.h" +#include "lib/utils/log.h" + +namespace ebusd { + +/** \file ebusd/request.h + * Abstraction of ebusd client requests. + */ + +/** the request listen mode. */ +enum ListenMode { + lm_none, //!< normal mode (no listening) + lm_listen, //!< listening mode + lm_direct, //!< direct mode +}; + +/** + * Request mode info. + */ +struct RequestMode { + ListenMode listenMode; //!< whether in listening or direct mode + OutputFormat format; //!< the output format settings for listen mode + bool listenWithUnknown; //!< include unknown messages in listen/direct mode + bool listenOnlyUnknown; //!< only print unknown messages in listen/direct mode +}; + +/** + * Abstract class for request/response. + */ +class Request { + public: + /** + * Destructor. + */ + virtual ~Request() { } + + /** + * Add request data from the client. + * @param request the request data from the client. + * @return true when the request is complete and the response shall be prepared. + */ + virtual bool add(const char* request) = 0; + + /** + * @return whether the request is still empty. + */ + virtual bool empty() const = 0; + + /** + * Split the request into arguments. + * @param args the @a vector to push the arguments to. + */ + virtual void split(vector* args) = 0; + + /** + * Return whether this is a HTTP request. + * @return whether this is a HTTP request. + */ + virtual bool isHttp() const = 0; + + /** + * Log the request or the given response in debug level. + */ + virtual void log(const string* response = nullptr) const = 0; + + /** + * Return the current user name. + * @return the current user name. + */ + virtual const string& getUser() const = 0; + + /** + * Wait for the response being set and return the result string. + * @param result the variable in which to store the result string. + * @return true when the client shall be disconnected. + */ + virtual bool waitResponse(string* result) = 0; + + /** + * Set the result string and notify a waiting thread. + * @param result the result string. + * @param user the new user name. + * @param newMode the new @a RequestMode. + * @param listenUntil the end time to which to updates were added (exclusive). + * @param disconnect true when the client shall be disconnected. + */ + virtual void setResult(const string& result, const string& user, RequestMode* newMode, time_t listenUntil, + bool disconnect) = 0; + + /** + * Return the @a RequestMode. + * @param listenSince set listening to the specified start time from which to add updates (inclusive). + * @return the @a RequestMode. + */ + virtual RequestMode getMode(time_t* listenSince = nullptr) = 0; +}; + +/** + * Default @a Request implementation. + */ +class RequestImpl : public Request { + public: + /** + * Constructor. + * @param isHttp whether this is a HTTP request. + */ + explicit RequestImpl(bool isHttp); + + /** + * Destructor. + */ + virtual ~RequestImpl(); + + + private: + /** + * Hidden copy constructor. + * @param src the object to copy from. + */ + RequestImpl(const RequestImpl& src); + + + public: + // @copydoc + bool add(const char* request) override; + + // @copydoc + bool empty() const override { return m_request.empty(); } + + // @copydoc + void split(vector* args) override; + + // @copydoc + bool isHttp() const override { return m_isHttp; } + + // @copydoc + void log(const string* response = nullptr) const override { + if (response) { + if (response->length() > 100) { + logDebug(lf_main, "<<< %s ...", response->substr(0, 100).c_str()); + } else { + logDebug(lf_main, "<<< %s", response->c_str()); + } + } else { + logDebug(lf_main, ">>> %s", m_request.c_str()); + } + } + + // @copydoc + const string& getUser() const override { return m_user; } + + // @copydoc + bool waitResponse(string* result) override; + + // @copydoc + void setResult(const string& result, const string& user, RequestMode* mode, time_t listenUntil, + bool disconnect) override; + + // @copydoc + RequestMode getMode(time_t* listenSince = nullptr) override { + if (listenSince) { + *listenSince = m_listenSince; + } + return m_mode; + } + + + private: + /** whether this is a HTTP message. */ + const bool m_isHttp; + + /** the request string. */ + string m_request; + + /** the current user name. */ + string m_user; + + /** whether the result was already set. */ + bool m_resultSet; + + /** the result string. */ + string m_result; + + /** set to true when the client shall be disconnected. */ + bool m_disconnect; + + /** mutex variable for exclusive lock. */ + pthread_mutex_t m_mutex; + + /** condition variable for exclusive lock. */ + pthread_cond_t m_cond; + + /** the @a RequestMode. */ + RequestMode m_mode; + + /** start timestamp of listening update. */ + time_t m_listenSince; +}; + +} // namespace ebusd + +#endif // EBUSD_REQUEST_H_ From 43d3599c7363b9e5e4392ce79404476b7738605f Mon Sep 17 00:00:00 2001 From: John Date: Sat, 8 Jul 2023 11:29:27 +0200 Subject: [PATCH 102/345] fix for topic separator check (closes #943) --- src/lib/ebus/stringhelper.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/lib/ebus/stringhelper.cpp b/src/lib/ebus/stringhelper.cpp index 43b55ba85..630b78f3f 100644 --- a/src/lib/ebus/stringhelper.cpp +++ b/src/lib/ebus/stringhelper.cpp @@ -142,14 +142,23 @@ const string StringReplacer::str() const { void StringReplacer::ensureDefault(const string& separator) { if (m_parts.empty()) { m_parts.emplace_back(string(PACKAGE) + separator, -1); - } else if (m_parts.size() == 1 && m_parts[0].second < 0 && m_parts[0].first.find('/') == string::npos) { - m_parts[0] = {m_parts[0].first + separator, -1}; // ensure trailing slash } if (!has("circuit")) { + if (m_parts.back().second >= 0) { + // add separator between two variables + m_parts.emplace_back(separator, -1); + } else if (m_parts.back().first.back() != '/') { + m_parts[m_parts.size() - 1] = {m_parts.back().first + separator, -1}; // ensure trailing slash + } m_parts.emplace_back("circuit", 0); // index of circuit in knownFieldNames - m_parts.emplace_back(separator, -1); } if (!has("name")) { + if (m_parts.back().second >= 0) { + // add separator between two variables + m_parts.emplace_back(separator, -1); + } else if (m_parts.back().first.back() != '/') { + m_parts[m_parts.size() - 1] = {m_parts.back().first + separator, -1}; // ensure trailing slash + } m_parts.emplace_back("name", 1); // index of name in knownFieldNames } } From 739324fb8ee7cf9a27a8e024200d699502186952 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 8 Jul 2023 12:00:31 +0200 Subject: [PATCH 103/345] missed update in c1624f5 --- src/ebusd/Makefile.am | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ebusd/Makefile.am b/src/ebusd/Makefile.am index 388268241..a557e5c5f 100644 --- a/src/ebusd/Makefile.am +++ b/src/ebusd/Makefile.am @@ -9,6 +9,7 @@ bin_PROGRAMS = ebusd ebusd_SOURCES = \ bushandler.h bushandler.cpp \ datahandler.h datahandler.cpp \ + request.h request.cpp \ network.h network.cpp \ mainloop.h mainloop.cpp \ scan.h scan.cpp \ From ae8e689e11490be4912456ddffe3b27b81a0511f Mon Sep 17 00:00:00 2001 From: john30 Date: Sat, 8 Jul 2023 14:28:51 +0200 Subject: [PATCH 104/345] updated version to 23.2 --- .github/ISSUE_TEMPLATE/bug_report.yml | 1 + ChangeLog.md | 3 ++- VERSION | 2 +- contrib/alpine/APKBUILD | 2 +- contrib/archlinux/PKGBUILD | 2 +- contrib/archlinux/PKGBUILD.git | 2 +- contrib/html/openapi.yaml | 2 +- 7 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 7f112af5b..4aef95ed5 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,7 @@ body: description: the ebusd version in use options: - current source from git + - '23.2' - '23.1' - '22.4' - '22.3' diff --git a/ChangeLog.md b/ChangeLog.md index 24bfa9b16..7488317eb 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,4 +1,4 @@ -# 23.2 (tbd) +# 23.2 (2023-07-08) ## Bug Fixes * fix bounds check for variable length datatypes * add timeout for enhanced protocol info exchange and allow more of them being optional @@ -7,6 +7,7 @@ * add workaround for libssl issues, add debug logging and force it using IPv4 * fix log level potentially set too late during startup * fix too low timeout for config web service +* fix MQTT topic construction with prefix containing a separator ## Features * add log entry for unstartable TCP ports diff --git a/VERSION b/VERSION index eaccea244..ce417ddac 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -23.1 \ No newline at end of file +23.2 \ No newline at end of file diff --git a/contrib/alpine/APKBUILD b/contrib/alpine/APKBUILD index 937aa6c6e..66e508b2a 100644 --- a/contrib/alpine/APKBUILD +++ b/contrib/alpine/APKBUILD @@ -1,7 +1,7 @@ # Contributor: Tim # Maintainer: Tim pkgname=ebusd -pkgver=23.1 +pkgver=23.2 pkgrel=0 pkgdesc="Daemon for communication with eBUS heating systems" url="https://github.com/john30/ebusd" diff --git a/contrib/archlinux/PKGBUILD b/contrib/archlinux/PKGBUILD index 8533cd196..702896963 100644 --- a/contrib/archlinux/PKGBUILD +++ b/contrib/archlinux/PKGBUILD @@ -2,7 +2,7 @@ # Contributor: Milan Knizek # Usage: makepkg pkgname=ebusd -pkgver=23.1 +pkgver=23.2 pkgrel=1 pkgdesc="ebusd, the daemon for communication with eBUS heating systems." arch=('i686' 'x86_64' 'armv6h' 'armv7h' 'aarch64') diff --git a/contrib/archlinux/PKGBUILD.git b/contrib/archlinux/PKGBUILD.git index 73aecca8f..8ad5b6bf3 100644 --- a/contrib/archlinux/PKGBUILD.git +++ b/contrib/archlinux/PKGBUILD.git @@ -3,7 +3,7 @@ # Usage: makepkg -p PKGBUILD.git pkgname=ebusd-git _gitname=ebusd -pkgver=23.1 +pkgver=23.2 pkgrel=1 pkgdesc="ebusd, the daemon for communication with eBUS heating systems." arch=('i686' 'x86_64' 'armv6h' 'armv7h' 'aarch64') diff --git a/contrib/html/openapi.yaml b/contrib/html/openapi.yaml index 1050c7b3c..f8fbd219c 100644 --- a/contrib/html/openapi.yaml +++ b/contrib/html/openapi.yaml @@ -2,7 +2,7 @@ openapi: 3.1.0 info: title: ebusd-http description: The API that ebusd provides on HTTP port. - version: "23.1" + version: "23.2" servers: - url: http://127.0.0.1:8080/ paths: From 5c5a150fedf63aed21f0950306b549a1bb475e15 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 8 Jul 2023 15:54:20 +0200 Subject: [PATCH 105/345] drop stretch in favour of bookworm --- .github/ISSUE_TEMPLATE/bug_report.yml | 1 + .github/workflows/preparerelease.yml | 2 +- contrib/docker/build.sh | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 4aef95ed5..92467ee04 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -60,6 +60,7 @@ body: label: Operating system description: the operating system in use options: + - Debian 12 (Bookworm) / Ubuntu 22-23 / Raspberry Pi OS 12 (including lite) - Debian 11 (Bullseye) / Ubuntu 20-21 / Raspbian 11 / Raspberry Pi OS 11 (including lite) - Debian 10 (Buster) / Ubuntu 18-19 / Raspbian 10 / Raspberry Pi OS 10 (including lite) - Debian 9 (Stretch) / Ubuntu 16-17 / Raspbian 9 / Raspberry Pi OS 9 (including lite) diff --git a/.github/workflows/preparerelease.yml b/.github/workflows/preparerelease.yml index 3a60e85b6..aa6ad024f 100644 --- a/.github/workflows/preparerelease.yml +++ b/.github/workflows/preparerelease.yml @@ -26,9 +26,9 @@ on: type: choice options: - '' + - bookworm - bullseye - buster - - stretch jobs: prepare-release: diff --git a/contrib/docker/build.sh b/contrib/docker/build.sh index fbed022fb..a77786914 100755 --- a/contrib/docker/build.sh +++ b/contrib/docker/build.sh @@ -39,7 +39,7 @@ elif [[ "x$1" = "xrelease" ]]; then else namesuffix='.build' target=deb - images='bullseye buster stretch' + images='bookworm bullseye buster' if [[ -n "$LIMITIMG" ]]; then images=$(echo " $images " | sed -e "s#.* \($LIMITIMG\) .*#\1#") echo "limiting to image $images" From d442184f848ca3734e63ee11fc56e4a029162f74 Mon Sep 17 00:00:00 2001 From: john30 Date: Sun, 9 Jul 2023 09:54:09 +0200 Subject: [PATCH 106/345] updated to 23.2 --- contrib/alpine/APKBUILD | 2 +- contrib/archlinux/PKGBUILD | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/alpine/APKBUILD b/contrib/alpine/APKBUILD index 66e508b2a..4a50d8c7c 100644 --- a/contrib/alpine/APKBUILD +++ b/contrib/alpine/APKBUILD @@ -28,5 +28,5 @@ package() { } sha512sums=" -3cb1aab16aa4ad596138e48fb1df030f986f948a87db8608fd184b582077d86b818c5b18cfa59e5a538191f3aa25787dadcb3bc5fd325728cdab77371d86d719 ebusd-23.1.tar.gz +ac19a39f8ddc00792bde4891020022cad46597da54bec71d56a954239b01dd8e8ea79fcb4cce130fc991beb2f4bff01ae7abd95150fcf9e93d65d7b5b93482ce ebusd-23.2.tar.gz " diff --git a/contrib/archlinux/PKGBUILD b/contrib/archlinux/PKGBUILD index 702896963..bbe05ba21 100644 --- a/contrib/archlinux/PKGBUILD +++ b/contrib/archlinux/PKGBUILD @@ -41,4 +41,4 @@ package() { install -m 0644 contrib/etc/ebusd/mqtt-integration.cfg "${pkgdir}/etc/ebusd/mqtt-integration.cfg" } # update md5sums: updpkgsums -md5sums=('b73c3adb9fc8594795a6f6b16b1f1db2') +md5sums=('2ab72843463faccd56975e4bc981eab4') From 329c175b0c7d4f79ba69901c8fabaad4870ef793 Mon Sep 17 00:00:00 2001 From: Alex Samorukov Date: Wed, 19 Jul 2023 19:48:23 +0200 Subject: [PATCH 107/345] message.h: remove std:binary_function This PR removes 'binary_function' reference to fix error on freebsd/current ``` no member named 'binary_function' in namespace 'std'; ``` binary_function is deprecated in C++11 and removed in C++17. Good thing that ebusd is not using it anyway, so IMHO it is safe to remove. --- src/lib/ebus/message.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/ebus/message.h b/src/lib/ebus/message.h index 5cc5fb5bf..fcd60ac9b 100644 --- a/src/lib/ebus/message.h +++ b/src/lib/ebus/message.h @@ -58,7 +58,6 @@ namespace ebusd { * template class. */ -using std::binary_function; using std::priority_queue; using std::deque; From 8e55d44a14344d99a2802aea90a93551ac2a011e Mon Sep 17 00:00:00 2001 From: John Date: Sun, 6 Aug 2023 08:11:45 +0200 Subject: [PATCH 108/345] remove no longer supported libmosquitto0, fix libssl3 detection+dependencies --- make_debian.sh | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/make_debian.sh b/make_debian.sh index 72376e0f9..bd80a78b5 100755 --- a/make_debian.sh +++ b/make_debian.sh @@ -67,23 +67,21 @@ fi make DESTDIR="$PWD/$RELEASE" install-strip || exit 1 extralibs= mqtt= -ldd $RELEASE/usr/bin/ebusd | egrep -q libmosquitto.so.0 +ldd $RELEASE/usr/bin/ebusd | egrep -q libmosquitto.so.1 if [ $? -eq 0 ]; then - extralibs=', libmosquitto0' - PACKAGE="${PACKAGE}_mqtt0" + extralibs=', libmosquitto1' + PACKAGE="${PACKAGE}_mqtt1" mqtt=1 +fi +ldd $RELEASE/usr/bin/ebusd | egrep -q libssl.so.3 +if [ $? -eq 0 ]; then + extralibs="$extralibs, libssl3 (>= 3.0.0), ca-certificates" else - ldd $RELEASE/usr/bin/ebusd | egrep -q libmosquitto.so.1 + ldd $RELEASE/usr/bin/ebusd | egrep -q libssl.so.1.1 if [ $? -eq 0 ]; then - extralibs=', libmosquitto1' - PACKAGE="${PACKAGE}_mqtt1" - mqtt=1 + extralibs="$extralibs, libssl1.1 (>= 1.1.0), ca-certificates" fi fi -ldd $RELEASE/usr/bin/ebusd | egrep -q libssl.so.1.1 -if [ $? -eq 0 ]; then - extralibs="$extralibs, libssl1.1 (>= 1.1.0), ca-certificates" -fi if [ -n "$RUNTEST" ]; then echo From c5bf9754fe22d81dbc006e72859bcaff8a2a25f7 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 6 Aug 2023 08:12:22 +0200 Subject: [PATCH 109/345] add argp hints for standalone --- CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2d3119307..eba61b13c 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,7 +75,10 @@ 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 NAMES argp argp-standalone) + if(NOT ARGP_HINTS) + set(ARGP_HINTS ~/argp-standalone/src) + endif(NOT ARGP_HINTS) + find_library(LIB_ARGP NAMES argp argp-standalone HINTS ARGP_HINTS) if (NOT LIB_ARGP) message(FATAL_ERROR "argp library not available") endif(NOT LIB_ARGP) From 949b366fe62d320f85f518aac236ceed8cd0d688 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 6 Aug 2023 08:26:25 +0200 Subject: [PATCH 110/345] deny mqtttopic starting with '/' (fixes #960) --- src/ebusd/mqtthandler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ebusd/mqtthandler.cpp b/src/ebusd/mqtthandler.cpp index a4adac49c..d8969d929 100755 --- a/src/ebusd/mqtthandler.cpp +++ b/src/ebusd/mqtthandler.cpp @@ -195,7 +195,7 @@ static error_t mqtt_parse_opt(int key, char *arg, struct argp_state *state) { case O_TOPI: // --mqtttopic=ebusd { - if (arg == nullptr || arg[0] == 0 || strchr(arg, '+') || arg[strlen(arg)-1] == '/') { + if (arg == nullptr || arg[0] == 0 || arg[0] == '/' || strchr(arg, '+') || arg[strlen(arg)-1] == '/') { argp_error(state, "invalid mqtttopic"); return EINVAL; } From e3abbf1b09a447b17b26c28fd36121972929bb76 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 6 Aug 2023 08:59:38 +0200 Subject: [PATCH 111/345] set no signal asap when device is detected as invalid (for #965) --- src/ebusd/bushandler.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ebusd/bushandler.cpp b/src/ebusd/bushandler.cpp index 17a705e2d..f880dc75f 100644 --- a/src/ebusd/bushandler.cpp +++ b/src/ebusd/bushandler.cpp @@ -443,6 +443,7 @@ void BusHandler::run() { } else { if (!m_device->isValid()) { logNotice(lf_bus, "device invalid"); + setState(bs_noSignal, RESULT_ERR_DEVICE); } if (!Wait(5)) { break; From b23fa889ff57b0e17bce5b1e3845a31d4e0f63e4 Mon Sep 17 00:00:00 2001 From: lgoenner Date: Wed, 9 Aug 2023 20:57:45 +0200 Subject: [PATCH 112/345] Fix link for TCP client commands --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6ae345f49..15a602bc1 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ The main features of the daemon are: * log messages and problems to a log file * capture messages or sent/received bytes to a log file as text * dump received bytes to binary files for later playback/analysis - * listen for [command line client](3.1.-TCP-client-commands) connections on a dedicated TCP port + * listen for [command line client](https://github.com/john30/ebusd/wiki/3.1.-TCP-client-commands) connections on a dedicated TCP port * provide a rudimentary HTML interface * format messages and data in [JSON on dedicated HTTP port](https://github.com/john30/ebusd/wiki/3.2.-HTTP-client) * publish received data to [MQTT topics](https://github.com/john30/ebusd/wiki/3.3.-MQTT-client) and vice versa (if authorized) From 76184c766bbb07fd864c61613cfad9229cf45b84 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 15 Aug 2023 17:25:34 +0200 Subject: [PATCH 113/345] some docs (also closes #974) --- contrib/docker/README.md | 15 ++++++--------- contrib/docker/docker-compose.example.yaml | 1 + 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/contrib/docker/README.md b/contrib/docker/README.md index 43a80c4da..a707fb575 100644 --- a/contrib/docker/README.md +++ b/contrib/docker/README.md @@ -2,10 +2,10 @@ ebusd Docker image ================== An [ebusd](https://github.com/john30/ebusd/) Docker image is available on the -[Docker Hub](https://hub.docker.com/r/john30/ebusd/) and is able to download the latest released German -[configuration files](https://github.com/john30/ebusd-configuration/) from a dedicated webservice. +[Docker Hub](https://hub.docker.com/r/john30/ebusd/) and is able to download the latest released +[configuration files](https://github.com/john30/ebusd-configuration/) from a [dedicated webservice](https://cfg.ebusd.eu/). -It allows you to run ebusd without actually installing (or even building) it on your system. +It allows running ebusd without actually installing (or even building) it on the host. You might even be able to run it on a non-Linux operating system, which is at least known to work well on a Synology Diskstation as well as Windows with Docker Desktop. @@ -21,15 +21,14 @@ The image is able to run on any of the following architectures and the right ima * arm32v7 * arm64v8 -Due to changes of docker hub policies, the development set of images with "devel" tag are currently not automatically -built with every commit to the git repository. Run the following command to use it: +In addition to the default "latest" tag, a development set of images is available with "devel" tag. This is built +automatically from the latest source on the git repository. Run the following command to use it: > docker pull john30/ebusd:devel Running interactively --------------------- - To run an ebusd container interactively, e.g. on serial device /dev/ttyUSB1, use the following command: > docker run --rm -it --device=/dev/ttyUSB1:/dev/ttyUSB0 -p 8888 john30/ebusd @@ -38,7 +37,6 @@ This will show the ebusd output directly in the terminal. Running in background --------------------- - To start an ebusd container and have it run in the background, e.g. on serial device /dev/ttyUSB1, use the following command: > docker run -d --name=ebusd --device=/dev/ttyUSB1:/dev/ttyUSB0 -p 8888 john30/ebusd @@ -50,7 +48,6 @@ In order to get the log output from ebusd, use the following command: Using a network device ---------------------- - When using a network device, the "--device" argument to docker can be omitted, but the device information has to be passed on to ebusd: > docker run --rm -it -p 8888 john30/ebusd --scanconfig -d 192.168.178.123:10000 --latency=20 @@ -74,7 +71,7 @@ Instead of passing arguments (at the end of docker run) to ebusd, almost all (lo environment variables with the prefix `EBUSD_`, e.g. the following line can be used instead of the last example above: > docker run -d --name=ebusd --device=/dev/ttyUSB0 -p 8888 -e EBUSD_SCANCONFIG= -e EBUSD_DEVICE=/dev/ttyUSB0 -e EBUSD_MQTTPORT=1883 -e EBUSD_MQTTHOST=BROKERHOST john30/ebusd -This eases use of e.g. docker-compose files. +This eases use of e.g. "docker-compose.yaml" files like [the example docker-compose file](https://github.com/john30/ebusd/blob/master/contrib/docker/docker-compose.example.yaml) also describing each available environment variable in it. Running newer images on older operating systems diff --git a/contrib/docker/docker-compose.example.yaml b/contrib/docker/docker-compose.example.yaml index 373b31a1d..61f099eb3 100644 --- a/contrib/docker/docker-compose.example.yaml +++ b/contrib/docker/docker-compose.example.yaml @@ -1,5 +1,6 @@ services: ebusd: + # alternatively, use "john30/ebusd:devel" for the latest build of the current source code image: john30/ebusd container_name: ebusd restart: unless-stopped From f633e0784025f48dae3a8f5f638b9ef154aeb9b5 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 9 Sep 2023 10:20:07 +0200 Subject: [PATCH 114/345] include temperatures in Kelvin (closes #989) --- contrib/etc/ebusd/mqtt-hassio.cfg | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contrib/etc/ebusd/mqtt-hassio.cfg b/contrib/etc/ebusd/mqtt-hassio.cfg index 2a6665699..883e4c931 100644 --- a/contrib/etc/ebusd/mqtt-hassio.cfg +++ b/contrib/etc/ebusd/mqtt-hassio.cfg @@ -170,6 +170,7 @@ type_switch-names = type_topic,type_class,type_state,type_sub # HA integration: the mapping list for (potentially) writable number entities by field type, name, message, and unit. type_switch-w-number = number,temperature, = temp|,°C$ + number,temperature, = temp|,K$ number,power_factor, = power*%% number,power, = power|,kW$|,W$ number,voltage, = volt|,V$ @@ -183,6 +184,7 @@ type_switch-w-number = type_switch-number = sensor,,total_increasing = poweron|count, sensor,temperature,measurement = temp|,°C$ + sensor,temperature,measurement = temp|,K$ sensor,power_factor,measurement = power*%% sensor,power,measurement = power|,kW$|,W$ sensor,voltage,measurement = volt|,V$ From 689c38610b0e032b5630f1644d82222333744a8f Mon Sep 17 00:00:00 2001 From: john30 Date: Sun, 17 Sep 2023 15:50:11 +0200 Subject: [PATCH 115/345] better defines for debugging --- src/lib/ebus/device.cpp | 82 +++++++++++++++-------------------------- 1 file changed, 29 insertions(+), 53 deletions(-) diff --git a/src/lib/ebus/device.cpp b/src/lib/ebus/device.cpp index 68798d489..07166e671 100755 --- a/src/lib/ebus/device.cpp +++ b/src/lib/ebus/device.cpp @@ -75,6 +75,19 @@ namespace ebusd { #define ENH_BYTE2 ((uint8_t)0x80) #define makeEnhancedSequence(cmd, data) {(uint8_t)(ENH_BYTE1 | ((cmd)<<2) | (((data)&0xc0)>>6)), (uint8_t)(ENH_BYTE2 | ((data)&0x3f))} +#ifdef DEBUG_RAW_TRAFFIC + #define DEBUG_RAW_TRAFFIC_HEAD(format, args...) fprintf(stdout, "%lld raw: " format, clockGetMillis(), args) + #define DEBUG_RAW_TRAFFIC_ITEM(args...) fprintf(stdout, args) + #define DEBUG_RAW_TRAFFIC_FINAL() fprintf(stdout, "\n"); fflush(stdout) + #undef DEBUG_RAW_TRAFFIC + #define DEBUG_RAW_TRAFFIC(format, args...) fprintf(stdout, "%lld raw: " format "\n", clockGetMillis(), args); fflush(stdout) +#else + #define DEBUG_RAW_TRAFFIC_HEAD(format, args...) + #undef DEBUG_RAW_TRAFFIC_ITEM + #define DEBUG_RAW_TRAFFIC_FINAL() + #define DEBUG_RAW_TRAFFIC(format, args...) +#endif + Device::Device(const char* name, bool readOnly, bool initialSend) : m_name(name), m_readOnly(readOnly), m_initialSend(initialSend), m_listener(nullptr) { } @@ -199,10 +212,7 @@ result_t FileDevice::open() { result_t FileDevice::afterOpen() { if (m_enhancedProto) { symbol_t buf[2] = makeEnhancedSequence(ENH_REQ_INIT, 0x01); // extra feature: info -#ifdef DEBUG_RAW_TRAFFIC - fprintf(stdout, "raw enhanced > %2.2x %2.2x\n", buf[0], buf[1]); - fflush(stdout); -#endif + DEBUG_RAW_TRAFFIC("enhanced > %2.2x %2.2x", buf[0], buf[1]); if (::write(m_fd, buf, 2) != 2) { return RESULT_ERR_SEND; } @@ -266,10 +276,7 @@ result_t FileDevice::requestEnhancedInfo(symbol_t infoId) { result_t FileDevice::sendEnhancedInfoRequest(symbol_t infoId) { symbol_t buf[2] = makeEnhancedSequence(ENH_REQ_INFO, infoId); -#ifdef DEBUG_RAW_TRAFFIC - fprintf(stdout, "raw enhanced > %2.2x %2.2x\n", buf[0], buf[1]); - fflush(stdout); -#endif + DEBUG_RAW_TRAFFIC("enhanced > %2.2x %2.2x", buf[0], buf[1]); if (::write(m_fd, buf, 2) != 2) { return RESULT_ERR_DEVICE; } @@ -415,9 +422,7 @@ result_t FileDevice::recv(unsigned int timeout, symbol_t* value, ArbitrationStat #endif #endif if (ret == -1) { -#ifdef DEBUG_RAW_TRAFFIC - fprintf(stdout, "poll error %d\n", errno); -#endif + DEBUG_RAW_TRAFFIC("poll error %d", errno); close(); cancelRunningArbitration(arbitrationState); return RESULT_ERR_DEVICE; @@ -508,16 +513,10 @@ result_t FileDevice::startArbitration(symbol_t masterAddress) { bool FileDevice::write(symbol_t value, bool startArbitration) { if (m_enhancedProto) { symbol_t buf[2] = makeEnhancedSequence(startArbitration ? ENH_REQ_START : ENH_REQ_SEND, value); -#ifdef DEBUG_RAW_TRAFFIC - fprintf(stdout, "raw enhanced > %2.2x %2.2x\n", buf[0], buf[1]); - fflush(stdout); -#endif + DEBUG_RAW_TRAFFIC("enhanced > %2.2x %2.2x", buf[0], buf[1]); return ::write(m_fd, buf, 2) == 2; } -#ifdef DEBUG_RAW_TRAFFIC - fprintf(stdout, "raw > %2.2x\n", value); - fflush(stdout); -#endif + DEBUG_RAW_TRAFFIC("> %2.2x", value); #ifdef SIMULATE_NON_WRITABILITY return true; #else @@ -536,10 +535,7 @@ bool FileDevice::available() { for (size_t pos = 0; pos < m_bufLen; pos++) { symbol_t ch = m_buffer[(pos+m_bufPos)%m_bufSize]; if (!(ch&ENH_BYTE_FLAG)) { -#ifdef DEBUG_RAW_TRAFFIC - fprintf(stdout, "raw avail direct @%d+%d %2.2x\n", m_bufPos, pos, ch); - fflush(stdout); -#endif + DEBUG_RAW_TRAFFIC("avail direct @%d+%d %2.2x", m_bufPos, pos, ch); return true; } if ((ch&ENH_BYTE_MASK) == ENH_BYTE1) { @@ -550,11 +546,8 @@ bool FileDevice::available() { // peek into next byte to check if enhanced sequence is ok ch = m_buffer[(pos+m_bufPos+1)%m_bufSize]; if (!(ch&ENH_BYTE_FLAG) || (ch&ENH_BYTE_MASK) != ENH_BYTE2) { -#ifdef DEBUG_RAW_TRAFFIC - fprintf(stdout, "raw avail enhanced following bad @%d+%d %2.2x %2.2x\n", m_bufPos, pos, + DEBUG_RAW_TRAFFIC("avail enhanced following bad @%d+%d %2.2x %2.2x", m_bufPos, pos, m_buffer[(pos+m_bufPos)%m_bufSize], ch); - fflush(stdout); -#endif if (m_listener != nullptr) { m_listener->notifyStatus(true, "unexpected available enhanced following byte 1"); } @@ -566,23 +559,14 @@ bool FileDevice::available() { } if (cmd == ENH_RES_RECEIVED || cmd == ENH_RES_STARTED || cmd == ENH_RES_FAILED) { // found a sequence that yields in available bus byte -#ifdef DEBUG_RAW_TRAFFIC - fprintf(stdout, "raw avail enhanced @%d+%d %2.2x %2.2x\n", m_bufPos, pos, m_buffer[(pos+m_bufPos)%m_bufSize], ch); - fflush(stdout); -#endif + DEBUG_RAW_TRAFFIC("avail enhanced @%d+%d %2.2x %2.2x", m_bufPos, pos, m_buffer[(pos+m_bufPos)%m_bufSize], ch); return true; } -#ifdef DEBUG_RAW_TRAFFIC - fprintf(stdout, "raw avail enhanced skip cmd %d @%d+%d %2.2x\n", cmd, m_bufPos, pos, ch); - fflush(stdout); -#endif + DEBUG_RAW_TRAFFIC("avail enhanced skip cmd %d @%d+%d %2.2x", cmd, m_bufPos, pos, ch); pos++; // skip enhanced sequence of 2 bytes continue; } -#ifdef DEBUG_RAW_TRAFFIC - fprintf(stdout, "raw avail enhanced bad @%d+%d %2.2x\n", m_bufPos, pos, ch); - fflush(stdout); -#endif + DEBUG_RAW_TRAFFIC("avail enhanced bad @%d+%d %2.2x", m_bufPos, pos, ch); if (m_listener != nullptr) { m_listener->notifyStatus(true, "unexpected available enhanced byte 2"); } @@ -610,19 +594,13 @@ bool FileDevice::read(symbol_t* value, bool isAvailable, ArbitrationState* arbit tail = (m_bufPos+m_bufLen) % m_bufSize; size_t head = m_bufLen-tail; memmove(m_buffer+head, m_buffer, tail); -#ifdef DEBUG_RAW_TRAFFIC - fprintf(stdout, "raw move tail %d @0 to @%d\n", tail, head); - fflush(stdout); -#endif + DEBUG_RAW_TRAFFIC("move tail %d @0 to @%d", tail, head); } else { tail = 0; } // move head to first position memmove(m_buffer, m_buffer + m_bufPos, m_bufLen - tail); -#ifdef DEBUG_RAW_TRAFFIC - fprintf(stdout, "raw move head %d @%d to 0\n", m_bufLen - tail, m_bufPos); - fflush(stdout); -#endif + DEBUG_RAW_TRAFFIC("move head %d @%d to 0", m_bufLen - tail, m_bufPos); } } m_bufPos = 0; @@ -631,13 +609,12 @@ bool FileDevice::read(symbol_t* value, bool isAvailable, ArbitrationState* arbit if (size <= 0) { return false; } -#ifdef DEBUG_RAW_TRAFFIC - fprintf(stdout, "raw %ld+%ld <", m_bufLen, size); +#ifdef DEBUG_RAW_TRAFFIC_ITEM + DEBUG_RAW_TRAFFIC_HEAD("%d+%d <", m_bufLen, size); for (int pos=0; pos < size; pos++) { - fprintf(stdout, " %2.2x", m_buffer[(m_bufLen+pos)%m_bufSize]); + DEBUG_RAW_TRAFFIC_ITEM(" %2.2x", m_buffer[(m_bufLen+pos)%m_bufSize]); } - fprintf(stdout, "\n"); - fflush(stdout); + DEBUG_RAW_TRAFFIC_FINAL(); #endif m_bufLen += size; } @@ -953,7 +930,6 @@ result_t SerialDevice::open() { newSettings.c_cc[VMIN] = 1; newSettings.c_cc[VTIME] = 0; - // int flag = TIOCM_RTS|TIOCM_DTR; // empty device buffer tcflush(m_fd, TCIFLUSH); From 9bd534afa41fdd2266b1e425aa79fcd72f37abba Mon Sep 17 00:00:00 2001 From: John Date: Sat, 23 Sep 2023 09:09:05 +0200 Subject: [PATCH 116/345] allow notifying queue and improve shutdown duration --- src/ebusd/mainloop.cpp | 7 +++++++ src/ebusd/mainloop.h | 2 +- src/lib/utils/queue.h | 14 ++++++-------- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index ddcda4eb4..3d90cc4aa 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -205,6 +205,13 @@ MainLoop::~MainLoop() { } } +void MainLoop::shutdown() { + m_shutdown = true; + if (m_requestQueue != nullptr) { + m_requestQueue->push(nullptr); // just to notify potentially waiting thread + } +} + /** the delay for running the update check. */ #define CHECK_DELAY (24*3600) diff --git a/src/ebusd/mainloop.h b/src/ebusd/mainloop.h index b1e8c2341..ae099db57 100644 --- a/src/ebusd/mainloop.h +++ b/src/ebusd/mainloop.h @@ -120,7 +120,7 @@ class MainLoop : public Thread, DeviceListener { /** * Shutdown the main loop. */ - void shutdown() { m_shutdown = true; } + void shutdown(); /** * Get the @a BusHandler instance. diff --git a/src/lib/utils/queue.h b/src/lib/utils/queue.h index 94eb54a77..b41bad26b 100755 --- a/src/lib/utils/queue.h +++ b/src/lib/utils/queue.h @@ -65,11 +65,13 @@ class Queue { public: /** * Add an item to the end of queue. - * @param item the item to add. + * @param item the item to add, or nullptr for notifying only. */ void push(T item) { pthread_mutex_lock(&m_mutex); - m_queue.push_back(item); + if (item) { + m_queue.push_back(item); + } pthread_cond_broadcast(&m_cond); pthread_mutex_unlock(&m_mutex); } @@ -82,15 +84,11 @@ class Queue { T pop(int timeout = 0) { T item; pthread_mutex_lock(&m_mutex); - if (timeout > 0) { + if (timeout > 0 && m_queue.empty()) { struct timespec t; clockGettime(&t); t.tv_sec += timeout; - while (m_queue.empty()) { - if (pthread_cond_timedwait(&m_cond, &m_mutex, &t) != 0) { - break; - } - } + pthread_cond_timedwait(&m_cond, &m_mutex, &t); } if (m_queue.empty()) { item = nullptr; From b0cfb52e3b1005cf340f241d1ebc17c4b653086b Mon Sep 17 00:00:00 2001 From: John Date: Sat, 23 Sep 2023 09:27:55 +0200 Subject: [PATCH 117/345] bring back missing scanconfig default, better help texts, add "off" to scanconfig, better argument combination validation --- src/ebusd/main.cpp | 102 ++++++++++++++++++++------------------------- src/ebusd/main.h | 9 +++- 2 files changed, 53 insertions(+), 58 deletions(-) diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index ee2faf0c7..f57b2bbfc 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -156,6 +156,9 @@ static Network* s_network = nullptr; /** the (optionally corrected) config path for retrieving configuration files from. */ static string s_configPath = CONFIG_PATH; +/** whether scanConfig or configPath were set by arguments. */ +static bool s_scanConfigOrPathSet = false; + /** the documentation of the program. */ static const char argpdoc[] = "A daemon for communication with eBUS heating systems."; @@ -199,9 +202,11 @@ static const char argpdoc[] = static const struct argp_option argpoptions[] = { {nullptr, 0, nullptr, 0, "Device options:", 1 }, {"device", 'd', "DEV", 0, "Use DEV as eBUS device (" - "\"enh:DEVICE\" or \"enh:IP:PORT\" for enhanced device, " - "\"ens:DEVICE\" for enhanced high speed serial device, " - "\"DEVICE\" for serial device, or \"[udp:]IP:PORT\" for network device) [/dev/ttyUSB0]", 0 }, + "prefix \"ens:\" for enhanced high speed device or " + "\"enh:\" for enhanced device, with " + "\"IP:PORT\" for network device or " + "\"DEVICE\" for serial device" + ") [/dev/ttyUSB0]", 0 }, {"nodevicecheck", 'n', nullptr, 0, "Skip serial eBUS device test", 0 }, {"readonly", 'r', nullptr, 0, "Only read from device, never write to it", 0 }, {"initsend", O_INISND, nullptr, 0, "Send an initial escape symbol after connecting device", 0 }, @@ -210,9 +215,13 @@ static const struct argp_option argpoptions[] = { {nullptr, 0, nullptr, 0, "Message configuration options:", 2 }, {"configpath", 'c', "PATH", 0, "Read CSV config files from PATH (local folder or HTTPS URL) [" CONFIG_PATH "]", 0 }, - {"scanconfig", 's', "ADDR", OPTION_ARG_OPTIONAL, "Pick CSV config files matching initial scan (ADDR=" - "\"none\" or empty for no initial scan message, \"full\" for full scan, or a single hex address to scan, " - "default is broadcast ident message). If combined with --checkconfig, you can add scan message data as " + {"scanconfig", 's', "ADDR", OPTION_ARG_OPTIONAL, "Pick CSV config files matching initial scan ADDR: " + "empty for broadcast ident message (default when configpath is not given), " + "\"none\" for no initial scan message, " + "\"full\" for full scan, " + "a single hex address to scan, or " + "\"off\" for not picking CSV files by scan result (default when configpath is given).\n" + "If combined with --checkconfig, you can add scan message data as " "arguments for checking a particular scan configuration, e.g. \"FF08070400/0AB5454850303003277201\".", 0 }, {"configlang", O_CFGLNG, "LANG", 0, "Prefer LANG in multilingual configuration files [system default language]", 0 }, @@ -230,7 +239,7 @@ static const struct argp_option argpoptions[] = { #endif // HAVE_SSL {nullptr, 0, nullptr, 0, "eBUS options:", 3 }, - {"address", 'a', "ADDR", 0, "Use ADDR as own bus address [31]", 0 }, + {"address", 'a', "ADDR", 0, "Use hex ADDR as own master bus address [31]", 0 }, {"answer", O_ANSWER, nullptr, 0, "Actively answer to requests from other masters", 0 }, {"acquiretimeout", O_ACQTIM, "MSEC", 0, "Stop bus acquisition after MSEC ms [10]", 0 }, {"acquireretries", O_ACQRET, "COUNT", 0, "Retry bus acquisition COUNT times [3]", 0 }, @@ -301,18 +310,9 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { opt->noDeviceCheck = true; break; case 'r': // --readonly - if (opt->answer || opt->generateSyn || opt->initialSend - || (opt->scanConfig && opt->initialScan != 0 && opt->initialScan != ESC)) { - argp_error(state, "cannot combine readonly with answer/generatesyn/initsend/scanconfig=*"); - return EINVAL; - } opt->readOnly = true; break; case O_INISND: // --initsend - if (opt->readOnly) { - argp_error(state, "cannot combine readonly with answer/generatesyn/initsend/scanconfig=*"); - return EINVAL; - } opt->initialSend = true; break; case O_DEVLAT: // --latency=10 @@ -331,18 +331,19 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { return EINVAL; } s_configPath = arg; + s_scanConfigOrPathSet = true; break; - case 's': // --scanconfig[=ADDR] (ADDR=|full|) + case 's': // --scanconfig[=ADDR] (ADDR=|none|full||off) { - if (opt->pollInterval == 0) { - argp_error(state, "scanconfig without polling may lead to invalid files included for certain products!"); - return EINVAL; - } - symbol_t initialScan = ESC; - if (!arg || arg[0] == 0 || strcmp("none", arg) == 0) { - // no further setting needed + symbol_t initialScan = 0; + if (!arg || arg[0] == 0) { + initialScan = BROADCAST; // default for no or empty argument + } else if (strcmp("none", arg) == 0) { + initialScan = ESC; } else if (strcmp("full", arg) == 0) { initialScan = SYN; + } else if (strcmp("off", arg) == 0) { + // zero turns scanConfig off } else { auto address = (symbol_t)parseInt(arg, 16, 0x00, 0xff, &result); if (result != RESULT_OK || !isValidAddress(address)) { @@ -355,29 +356,18 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { initialScan = address; } } - if (opt->readOnly && initialScan != ESC) { - argp_error(state, "cannot combine readonly with answer/generatesyn/initsend/scanconfig=*"); - return EINVAL; - } - opt->scanConfig = true; + opt->scanConfig = initialScan != 0; opt->initialScan = initialScan; + s_scanConfigOrPathSet = true; break; } case O_CFGLNG: // --configlang=LANG opt->preferLanguage = arg; break; case O_CHKCFG: // --checkconfig - if (opt->injectMessages) { - argp_error(state, "invalid checkconfig"); - return EINVAL; - } opt->checkConfig = true; break; case O_DMPCFG: // --dumpconfig[=json|csv] - if (opt->injectMessages) { - argp_error(state, "invalid checkconfig"); - return EINVAL; - } if (!arg || arg[0] == 0 || strcmp("csv", arg) == 0) { // no further flags opt->dumpConfig = OF_DEFINITION; @@ -402,17 +392,9 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { argp_error(state, "invalid pollinterval"); return EINVAL; } - if (value == 0 && opt->scanConfig) { - argp_error(state, "scanconfig without polling may lead to invalid files included for certain products!"); - return EINVAL; - } opt->pollInterval = value; break; case 'i': // --inject[=stop] - if (opt->injectMessages || opt->checkConfig) { - argp_error(state, "invalid inject"); - return EINVAL; - } opt->injectMessages = true; opt->stopAfterInject = arg && strcmp("stop", arg) == 0; break; @@ -435,10 +417,6 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { break; } case O_ANSWER: // --answer - if (opt->readOnly) { - argp_error(state, "cannot combine readonly with answer/generatesyn/initsend/scanconfig=*"); - return EINVAL; - } opt->answer = true; break; case O_ACQTIM: // --acquiretimeout=10 @@ -482,10 +460,6 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { opt->masterCount = value; break; case O_GENSYN: // --generatesyn - if (opt->readOnly) { - argp_error(state, "cannot combine readonly with answer/generatesyn/initsend/scanconfig=*"); - return EINVAL; - } opt->generateSyn = true; break; @@ -681,6 +655,21 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { return ARGP_ERR_UNKNOWN; } + + // check for invalid arg combinations + if (opt->readOnly && (opt->answer || opt->generateSyn || opt->initialSend + || (opt->scanConfig && opt->initialScan != ESC))) { + argp_error(state, "cannot combine readonly with answer/generatesyn/initsend/scanconfig"); + return EINVAL; + } + if (opt->scanConfig && opt->pollInterval == 0) { + argp_error(state, "scanconfig without polling may lead to invalid files included for certain products!"); + return EINVAL; + } + if (opt->injectMessages && (opt->checkConfig || opt->dumpConfig)) { + argp_error(state, "cannot combine inject with checkconfig/dumpconfig"); + return EINVAL; + } return 0; } void shutdown(bool error = false); @@ -895,6 +884,10 @@ int main(int argc, char* argv[], char* envp[]) { setFacilitiesLogLevel(s_opt.logAreas, s_opt.logLevel); } + if (!s_opt.readOnly && !s_scanConfigOrPathSet) { + s_opt.scanConfig = true; + s_opt.initialScan = BROADCAST; + } if (!s_configPath.empty() && s_configPath[s_configPath.length()-1] != '/') { s_configPath += "/"; } @@ -943,9 +936,6 @@ int main(int argc, char* argv[], char* envp[]) { logInfo(lf_main, "configPath URL is valid"); configHttpClient->disconnect(); } - if (!s_opt.readOnly && s_opt.scanConfig && s_opt.initialScan == 0) { - s_opt.initialScan = BROADCAST; - } s_messageMap = new MessageMap(s_opt.checkConfig, lang); s_scanHelper = new ScanHelper(s_messageMap, s_configPath, configLocalPrefix, configUriPrefix, diff --git a/src/ebusd/main.h b/src/ebusd/main.h index 786703110..98b421d27 100644 --- a/src/ebusd/main.h +++ b/src/ebusd/main.h @@ -41,8 +41,13 @@ typedef struct options { unsigned int extraLatency; //!< extra transfer latency in ms [0 for USB, 10 for IP] bool scanConfig; //!< pick configuration files matching initial scan - /** the initial address to scan for scanconfig - * (@a ESC=none, 0xfe=broadcast ident, @a SYN=full scan, else: single slave address). */ + /** + * initial address(es) to scan: + * @a ESC=none (no explicit active scanning), + * 0xfe=broadcast ident, + * @a SYN=full scan (all slave addresses), + * else: single slave address. + */ symbol_t initialScan; const char* preferLanguage; //!< preferred language in configuration files bool checkConfig; //!< check config files, then stop From 70db4fa50927148f8173c45e14fb8eab89a87018 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 23 Sep 2023 09:28:35 +0200 Subject: [PATCH 118/345] log invalid env setting more readable --- src/ebusd/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index f57b2bbfc..e3f336e28 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -865,10 +865,10 @@ int main(int argc, char* argv[], char* envp[]) { error_t err = argp_parse(&aargp, cnt, envargv, ARGP_PARSE_ARGV0|ARGP_SILENT|ARGP_IN_ORDER, &idx, &s_opt); if (err != 0 && idx == -1) { // ignore args for non-arg boolean options if (err == ESRCH) { // special value to abort immediately - logWrite(lf_main, ll_error, "invalid argument in env: %s", envopt); // force logging on exit + logWrite(lf_main, ll_error, "invalid argument in env: %s", *env); // force logging on exit return EINVAL; } - logWrite(lf_main, ll_error, "invalid/unknown argument in env (ignored): %s", envopt); // force logging + logWrite(lf_main, ll_error, "invalid/unknown argument in env (ignored): %s", *env); // force logging } s_opt.injectMessages = false; // restore (was not parsed from cmdline args yet) } From 1c1b39ea2907a8619223a9fca61877634e3489b1 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 23 Sep 2023 12:34:21 +0200 Subject: [PATCH 119/345] send empty topic instead of logging an error on messages without a single field (fixes #817) --- src/ebusd/mqtthandler.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ebusd/mqtthandler.cpp b/src/ebusd/mqtthandler.cpp index d8969d929..f508f3223 100755 --- a/src/ebusd/mqtthandler.cpp +++ b/src/ebusd/mqtthandler.cpp @@ -1381,6 +1381,10 @@ void MqttHandler::publishMessage(const Message* message, ostringstream* updates, *updates << message->getCircuit() << UI_FIELD_SEPARATOR << message->getName() << UI_FIELD_SEPARATOR; } result_t result = message->decodeLastData(false, nullptr, -1, outputFormat, updates); + if (result == RESULT_EMPTY) { + publishEmptyTopic(getTopic(message)); // alternatively: , json ? "null" : ""); + return; + } if (result != RESULT_OK) { logOtherError("mqtt", "decode %s %s: %s", message->getCircuit().c_str(), message->getName().c_str(), getResultCode(result)); From 0698906cd14672417ed119978ee89138c267f316 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 23 Sep 2023 14:09:27 +0200 Subject: [PATCH 120/345] allow limiting number of scan retries (fixes 1013) --- src/ebusd/main.cpp | 13 ++++++++++++- src/ebusd/main.h | 1 + src/ebusd/mainloop.cpp | 13 ++++++++++--- src/ebusd/mainloop.h | 3 +++ 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index e3f336e28..79deee4d8 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -92,6 +92,7 @@ static struct options s_opt = { false, // scanConfig 0, // initialScan + 5, // scanRetries getenv("LANG"), // preferLanguage false, // checkConfig OF_NONE, // dumpConfig @@ -165,7 +166,8 @@ static const char argpdoc[] = #define O_INISND -2 #define O_DEVLAT (O_INISND-1) -#define O_CFGLNG (O_DEVLAT-1) +#define O_SCNRET (O_DEVLAT-1) +#define O_CFGLNG (O_SCNRET-1) #define O_CHKCFG (O_CFGLNG-1) #define O_DMPCFG (O_CHKCFG-1) #define O_DMPCTO (O_DMPCFG-1) @@ -223,6 +225,7 @@ static const struct argp_option argpoptions[] = { "\"off\" for not picking CSV files by scan result (default when configpath is given).\n" "If combined with --checkconfig, you can add scan message data as " "arguments for checking a particular scan configuration, e.g. \"FF08070400/0AB5454850303003277201\".", 0 }, + {"scanretries", O_SCNRET, "COUNT", 0, "Retry scanning devices COUNT times [5]", 0 }, {"configlang", O_CFGLNG, "LANG", 0, "Prefer LANG in multilingual configuration files [system default language]", 0 }, {"checkconfig", O_CHKCFG, nullptr, 0, "Check config files, then stop", 0 }, @@ -361,6 +364,14 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { s_scanConfigOrPathSet = true; break; } + case O_SCNRET: // --scanretries=10 + value = parseInt(arg, 10, 0, 100, &result); + if (result != RESULT_OK) { + argp_error(state, "invalid scanretries"); + return EINVAL; + } + opt->scanRetries = value; + break; case O_CFGLNG: // --configlang=LANG opt->preferLanguage = arg; break; diff --git a/src/ebusd/main.h b/src/ebusd/main.h index 98b421d27..a085d0e35 100644 --- a/src/ebusd/main.h +++ b/src/ebusd/main.h @@ -49,6 +49,7 @@ typedef struct options { * else: single slave address. */ symbol_t initialScan; + int scanRetries; //!< number of retries for scanning devices [10] const char* preferLanguage; //!< preferred language in configuration files bool checkConfig; //!< check config files, then stop OutputFormat dumpConfig; //!< dump config files, then stop diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index 3d90cc4aa..c457431ee 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -109,8 +109,8 @@ MainLoop::MainLoop(const struct options& opt, Device *device, MessageMap* messag Queue* requestQueue) : Thread(), m_device(device), m_reconnectCount(0), m_userList(opt.accessLevel), m_messages(messages), m_scanHelper(scanHelper), m_address(opt.address), m_scanConfig(opt.scanConfig), - m_initialScan(opt.readOnly ? ESC : opt.initialScan), m_scanStatus(SCAN_STATUS_NONE), - m_polling(opt.pollInterval > 0), m_enableHex(opt.enableHex), + m_initialScan(opt.readOnly ? ESC : opt.initialScan), m_scanRetries(opt.scanRetries), + m_scanStatus(SCAN_STATUS_NONE), m_polling(opt.pollInterval > 0), m_enableHex(opt.enableHex), m_shutdown(false), m_runUpdateCheck(opt.updateCheck), m_httpClient(), m_requestQueue(requestQueue) { m_device->setListener(this); // open Device @@ -228,6 +228,7 @@ void MainLoop::run() { symbol_t lastScanAddress = 0; // 0 is known to be a master scanStatus_t lastScanStatus = m_scanStatus; int scanCompleted = 0; + int scanRetry = 0; time(&now); start = now; lastTaskRun = now; @@ -261,7 +262,7 @@ void MainLoop::run() { m_busHandler->reconnect(); m_reconnectCount++; } - if (m_scanConfig) { + if (m_scanConfig && scanRetry <= m_scanRetries) { bool loadDelay = false; if (m_initialScan != ESC && reload && m_busHandler->hasSignal()) { loadDelay = true; @@ -312,6 +313,8 @@ void MainLoop::run() { scanCompleted++; if (scanCompleted > SCAN_REPEAT_COUNT) { // repeat failed scan only every Nth time scanCompleted = 0; + scanRetry++; + logNotice(lf_main, "scan completed %d time(s), %s", scanRetry, scanRetry <= m_scanRetries ? "check again" : "end"); } } else { m_scanStatus = SCAN_STATUS_RUNNING; @@ -424,7 +427,11 @@ void MainLoop::run() { bool connected = true; if (!req->empty()) { req->log(); + bool currentReload = reload; result_t result = decodeRequest(req, &connected, &reqMode, &user, &reload, &ostream); + if (reload && !currentReload) { + scanRetry = 0; // restart scan counting + } if (!req->isHttp() && (ostream.tellp() == 0 || result != RESULT_OK)) { if (reqMode.listenMode != lm_direct) { ostream.str(""); diff --git a/src/ebusd/mainloop.h b/src/ebusd/mainloop.h index ae099db57..d5efc5a00 100644 --- a/src/ebusd/mainloop.h +++ b/src/ebusd/mainloop.h @@ -416,6 +416,9 @@ class MainLoop : public Thread, DeviceListener { * (@a ESC=none, 0xfe=broadcast ident, @a SYN=full scan, else: single slave address). */ const symbol_t m_initialScan; + /** number of retries for scanning a device. */ + const int m_scanRetries; + /** the current scan status. */ scanStatus_t m_scanStatus; From d89b7cf29af832b34772b66d83832e019cc74523 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 23 Sep 2023 14:23:06 +0200 Subject: [PATCH 121/345] fix macos build (closes #673) --- src/tools/ebuspicloader.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/tools/ebuspicloader.cpp b/src/tools/ebuspicloader.cpp index f568c6ef0..a6829faac 100644 --- a/src/tools/ebuspicloader.cpp +++ b/src/tools/ebuspicloader.cpp @@ -397,6 +397,10 @@ typedef union #define FRAME_HEADER_LEN 9 #define FRAME_MAX_LEN (FRAME_HEADER_LEN+2*WRITE_FLASH_BLOCKSIZE) #define BAUDRATE_LOW B115200 +#if !defined(B921600) && defined(__APPLE__) +// MAC OS workaround +# define B921600 921600 +#endif #define BAUDRATE_HIGH B921600 #define WAIT_BYTE_TRANSFERRED_MILLIS 200 #define WAIT_BITRATE_DETECTION_MICROS 100 From 087209f2e7621750bae73ec58aa4d3eac881f095 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 24 Sep 2023 08:28:19 +0200 Subject: [PATCH 122/345] fix missing defines --- CMakeLists.txt | 6 +++--- config.h.cmake | 12 ++++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index eba61b13c..b155ab69d 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -73,8 +73,8 @@ 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) +check_function_exists(argp_parse HAVE_ARGP_H) +if(NOT HAVE_ARGP_H) if(NOT ARGP_HINTS) set(ARGP_HINTS ~/argp-standalone/src) endif(NOT ARGP_HINTS) @@ -83,7 +83,7 @@ if(NOT HAVE_ARGP) message(FATAL_ERROR "argp library not available") endif(NOT LIB_ARGP) set(CMAKE_REQUIRED_LIBRARIES "${CMAKE_REQUIRED_LIBRARIES} ${LIB_ARGP}") -endif(NOT HAVE_ARGP) +endif(NOT HAVE_ARGP_H) option(coverage "enable code coverage tracking." OFF) if(NOT coverage STREQUAL OFF) diff --git a/config.h.cmake b/config.h.cmake index 5bfd4250c..0c693a2f0 100755 --- a/config.h.cmake +++ b/config.h.cmake @@ -31,6 +31,18 @@ /* Defined if pthread_setname_np is available. */ #cmakedefine HAVE_PTHREAD_SETNAME_NP +/* Defined if cfsetspeed() is available. */ +#cmakedefine HAVE_CFSETSPEED + +/* Defined if time.h is available. */ +#cmakedefine HAVE_TIME_H + +/* Defined if syslog.h is available. */ +#cmakedefine HAVE_SYSLOG_H + +/* Defined if argp.h is available. */ +#cmakedefine HAVE_ARGP_H + /* The name of package. */ #cmakedefine PACKAGE "${PACKAGE_NAME}" From 26102d0de37357ea1e589e7959cf4407dc0c8fc0 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 24 Sep 2023 08:43:22 +0200 Subject: [PATCH 123/345] simplify some dir/file defines --- CMakeLists.txt | 11 ++++++----- config.h.cmake | 4 ++-- configure.ac | 4 ++-- src/ebusd/Makefile.am | 4 +--- src/ebusd/main.cpp | 18 ++---------------- 5 files changed, 13 insertions(+), 28 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b155ab69d..ab42dca80 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,11 @@ file(STRINGS "VERSION" VERSION) project(ebusd) +include(GNUInstallDirs) +include(CheckFunctionExists) +include(CheckCXXSourceRuns) +include(CheckIncludeFile) + set(PACKAGE ${CMAKE_PROJECT_NAME}) set(PACKAGE_NAME ${CMAKE_PROJECT_NAME}) set(PACKAGE_TARNAME ${CMAKE_PROJECT_NAME}) @@ -45,10 +50,6 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "" CACHE PATH "..." FORCE) endif() -include(GNUInstallDirs) -include(CheckFunctionExists) -include(CheckCXXSourceRuns) -include(CheckIncludeFile) add_definitions(-fpic -Wall -Wno-unused-function -Wextra) @@ -172,7 +173,7 @@ int main() { endif(HAVE_DIRECT_FLOAT_FORMAT_REV) endif(NOT HAVE_DIRECT_FLOAT_FORMAT) -add_definitions(-D_GNU_SOURCE -DHAVE_CONFIG_H -DSYSCONFDIR="${CMAKE_INSTALL_FULL_SYSCONFDIR}" -DLOCALSTATEDIR="${CMAKE_INSTALL_FULL_LOCALSTATEDIR}") +add_definitions(-D_GNU_SOURCE -DHAVE_CONFIG_H) configure_file(config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h) include_directories(${CMAKE_CURRENT_BINARY_DIR}) include_directories(${ebusd_SOURCE_DIR}/src) diff --git a/config.h.cmake b/config.h.cmake index 0c693a2f0..f73c5a54a 100755 --- a/config.h.cmake +++ b/config.h.cmake @@ -50,13 +50,13 @@ #cmakedefine PACKAGE_BUGREPORT "${PACKAGE_BUGREPORT}" /* The path and name of the log file. */ -#define PACKAGE_LOGFILE LOCALSTATEDIR "/log/" PACKAGE ".log" +#cmakedefine PACKAGE_LOGFILE "${PACKAGE_LOGFILE}" /* The full name of this package. */ #cmakedefine PACKAGE_NAME "${PACKAGE_NAME}" /* The path and name of the PID file. */ -#define PACKAGE_PIDFILE LOCALSTATEDIR "/run/" PACKAGE ".pid" +#cmakedefine PACKAGE_PIDFILE "${PACKAGE_PIDFILE}" /* The full name and version of this package. */ #cmakedefine PACKAGE_STRING "${PACKAGE_STRING}" diff --git a/configure.ac b/configure.ac index ad7453ed7..c3fbd6661 100755 --- a/configure.ac +++ b/configure.ac @@ -153,8 +153,8 @@ AM_COND_IF([KNX], [AC_CONFIG_FILES([ src/lib/knx/Makefile ])]) -AC_DEFINE_UNQUOTED(PACKAGE_PIDFILE, LOCALSTATEDIR "/run/" PACKAGE ".pid", [The path and name of the PID file.]) -AC_DEFINE_UNQUOTED(PACKAGE_LOGFILE, LOCALSTATEDIR "/log/" PACKAGE ".log", [The path and name of the log file.]) +AC_DEFINE_UNQUOTED(PACKAGE_PIDFILE, "${localstatedir}/run/${PACKAGE_NAME}.pid", [The path and name of the PID file.]) +AC_DEFINE_UNQUOTED(PACKAGE_LOGFILE, "${localstatedir}/log/${PACKAGE_NAME}.log", [The path and name of the log file.]) AC_DEFINE(SCAN_VERSION, "[m4_esyscmd_s([sed -e 's#^\([0-9]*\.[0-9]*\).*#\1#' -e 's#\.\([0-9]\)$#0\1#' -e 's#\.##' VERSION])]", [The version of the package formatted for the scan result.]) AC_DEFINE(PACKAGE_VERSION_MAJOR, [m4_esyscmd_s([sed -e 's#^\([0-9]*\)\..*$#\1#' VERSION])], [The major version of the package.]) AC_DEFINE(PACKAGE_VERSION_MINOR, [m4_esyscmd_s([sed -e 's#^.*\.\([0-9]*\)$#\1#' VERSION])], [The minor version of the package.]) diff --git a/src/ebusd/Makefile.am b/src/ebusd/Makefile.am index a557e5c5f..673ef653c 100644 --- a/src/ebusd/Makefile.am +++ b/src/ebusd/Makefile.am @@ -1,8 +1,6 @@ AM_CXXFLAGS = -I$(top_srcdir)/src \ -isystem$(top_srcdir) \ - -Wconversion -Wno-unused-parameter \ - -DSYSCONFDIR=\"$(sysconfdir)\" \ - -DLOCALSTATEDIR=\"$(localstatedir)\" + -Wconversion -Wno-unused-parameter bin_PROGRAMS = ebusd diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index 79deee4d8..1cfcdd487 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -52,20 +52,6 @@ using std::setw; using std::nouppercase; using std::cout; -/** the path and name of the PID file. */ -#ifdef PACKAGE_PIDFILE -#define PID_FILE_NAME PACKAGE_PIDFILE -#else -#define PID_FILE_NAME "/var/run/ebusd.pid" -#endif - -/** the path and name of the log file. */ -#ifdef PACKAGE_LOGFILE -#define LOG_FILE_NAME PACKAGE_LOGFILE -#else -#define LOG_FILE_NAME "/var/log/ebusd.log" -#endif - /** the config path part behind the scheme (scheme without "://"). */ #define CONFIG_PATH_SUFFIX "://cfg.ebusd.eu/" @@ -117,7 +103,7 @@ static struct options s_opt = { false, // foreground false, // enableHex false, // enableDefine - PID_FILE_NAME, // pidFile + PACKAGE_PIDFILE, // pidFile 8888, // port false, // localOnly 0, // httpPort @@ -257,7 +243,7 @@ static const struct argp_option argpoptions[] = { {"foreground", 'f', nullptr, 0, "Run in foreground", 0 }, {"enablehex", O_HEXCMD, nullptr, 0, "Enable hex command", 0 }, {"enabledefine", O_DEFCMD, nullptr, 0, "Enable define command", 0 }, - {"pidfile", O_PIDFIL, "FILE", 0, "PID file name (only for daemon) [" PID_FILE_NAME "]", 0 }, + {"pidfile", O_PIDFIL, "FILE", 0, "PID file name (only for daemon) [" PACKAGE_PIDFILE "]", 0 }, {"port", 'p', "PORT", 0, "Listen for command line connections on PORT [8888]", 0 }, {"localhost", O_LOCAL, nullptr, 0, "Listen for command line connections on 127.0.0.1 interface only", 0 }, {"httpport", O_HTTPPT, "PORT", 0, "Listen for HTTP connections on PORT, 0 to disable [0]", 0 }, From 03483babc30a217c276eb5112f53fdf8eb3d708d Mon Sep 17 00:00:00 2001 From: John Date: Sun, 24 Sep 2023 09:21:40 +0200 Subject: [PATCH 124/345] updated --- ChangeLog.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index 7488317eb..766d07a78 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,3 +1,15 @@ +# next (tbd) +## Bug Fixes +* fix MQTT topic string validation +* fix lost scanconfig default behaviour +* fix send empty message instead of logging an error for MQTT on messages without any field +* fix MacOS build + +## Features +* add temperatures in Kelvin and ... to Home Assistant MQTT discovery integration +* add options to turn off scanconfig and limit number of retries + + # 23.2 (2023-07-08) ## Bug Fixes * fix bounds check for variable length datatypes From 8662ff7b0cefc1e68f1c644a07b972c384af2364 Mon Sep 17 00:00:00 2001 From: Danit2 <71522810+Danit2@users.noreply.github.com> Date: Sun, 24 Sep 2023 15:38:34 +0300 Subject: [PATCH 125/345] Update mqtt-hassio.cfg --- contrib/etc/ebusd/mqtt-hassio.cfg | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contrib/etc/ebusd/mqtt-hassio.cfg b/contrib/etc/ebusd/mqtt-hassio.cfg index 883e4c931..6cd115f1a 100644 --- a/contrib/etc/ebusd/mqtt-hassio.cfg +++ b/contrib/etc/ebusd/mqtt-hassio.cfg @@ -171,6 +171,7 @@ type_switch-names = type_topic,type_class,type_state,type_sub type_switch-w-number = number,temperature, = temp|,°C$ number,temperature, = temp|,K$ + number,, = integral|,°min$ number,power_factor, = power*%% number,power, = power|,kW$|,W$ number,voltage, = volt|,V$ @@ -189,6 +190,7 @@ type_switch-number = sensor,power,measurement = power|,kW$|,W$ sensor,voltage,measurement = volt|,V$ sensor,current,measurement = current,|,A$ + sensor,,measurement = integral|,°min$ sensor,energy,total_increasing = energy|,Wh$ sensor,yield,total_increasing = total*,Wh$ sensor,,total_increasing = hours|,h$ From 1b9723c96f751e5891552957ef0be896d782e0d5 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 24 Sep 2023 20:22:58 +0200 Subject: [PATCH 126/345] fix long-running cmds --- test_coverage.sh | 43 ++++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/test_coverage.sh b/test_coverage.sh index 0fb5e24c8..8bb7a0dd9 100755 --- a/test_coverage.sh +++ b/test_coverage.sh @@ -7,10 +7,10 @@ ebuspicloader=$prefix/src/tools/ebuspicloader $ebusd --help >/dev/null $ebusd -r -f -x >/dev/null 2>/dev/null $ebusd -f -d "" >/dev/null 2>/dev/null -$ebusd -f -d "tcp:192.168.999.999:1" >/dev/null 2>/dev/null -$ebusd -f -d "enh:192.168.999.999:1" >/dev/null 2>/dev/null -$ebusd -f -d "/dev/ttyUSBx9" >/dev/null 2>/dev/null -$ebusd -f --nodevicecheck >/dev/null 2>/dev/null +$ebusd -f -d "tcp:192.168.999.999:1" --log bad >/dev/null 2>/dev/null +$ebusd -f -d "enh:192.168.999.999:1" --log bad >/dev/null 2>/dev/null +$ebusd -f -d "/dev/ttyUSBx9" --log bad >/dev/null 2>/dev/null +$ebusd -f --nodevicecheck --log bad >/dev/null 2>/dev/null $ebusd -f --readonly >/dev/null 2>/dev/null $ebusd -f --scanconfig=full -r >/dev/null 2>/dev/null $ebusd -f --latency 999999 >/dev/null 2>/dev/null @@ -18,7 +18,7 @@ $ebusd -f -c "" >/dev/null 2>/dev/null $ebusd -f -r --scanconfig=fe >/dev/null 2>/dev/null $ebusd -f -r --configlang=en >/dev/null 2>/dev/null $ebusd -f --pollinterval 999999 >/dev/null 2>/dev/null -$ebusd -f --inject 01fe030400/ >/dev/null 2>/dev/null +$ebusd -f --inject --checkconfig=stop 01fe030400/ >/dev/null 2>/dev/null $ebusd -f --address 999 >/dev/null 2>/dev/null $ebusd -f --acquiretimeout 999999 >/dev/null 2>/dev/null $ebusd -f --acquireretries 999999 >/dev/null 2>/dev/null @@ -31,30 +31,30 @@ $ebusd -f -r --initsend >/dev/null 2>/dev/null $ebusd -f -r --scanconfig=0 >/dev/null 2>/dev/null $ebusd -f --pidfile "" >/dev/null 2>/dev/null $ebusd -f -p 999999 >/dev/null 2>/dev/null -$ebusd -f --localhost >/dev/null 2>/dev/null +$ebusd -f --localhost --log bad >/dev/null 2>/dev/null $ebusd -f --httpport 999999 >/dev/null 2>/dev/null $ebusd -f --htmlpath "" >/dev/null 2>/dev/null -$ebusd -f --updatecheck=off >/dev/null 2>/dev/null -$ebusd -f -l "" >/dev/null 2>/dev/null -$ebusd -f --log "all debug" >/dev/null 2>/dev/null +$ebusd -f --updatecheck=off --log bad >/dev/null 2>/dev/null +$ebusd -f -l "" --log bad >/dev/null 2>/dev/null +$ebusd -f --log "all debug" --log bad >/dev/null 2>/dev/null $ebusd -f --logareas some >/dev/null 2>/dev/null $ebusd -f --loglevel unknown >/dev/null 2>/dev/null -$ebusd -f --lograwdata >/dev/null 2>/dev/null -$ebusd -f --lograwdata=bytes >/dev/null 2>/dev/null -$ebusd -f --lograwdatafile=/xyz >/dev/null 2>/dev/null +$ebusd -f --lograwdata --log bad >/dev/null 2>/dev/null +$ebusd -f --lograwdata=bytes --log bad >/dev/null 2>/dev/null +$ebusd -f --lograwdatafile=/notexist/xyz --log bad >/dev/null 2>/dev/null $ebusd -f --lograwdatasize=9999999 >/dev/null 2>/dev/null -$ebusd -f --dump >/dev/null 2>/dev/null +$ebusd -f --dump --log bad >/dev/null 2>/dev/null $ebusd -f --dumpfile "" >/dev/null 2>/dev/null $ebusd -f --dumpsize 9999999 >/dev/null 2>/dev/null -$ebusd -f --dumpflush >/dev/null 2>/dev/null -$ebusd -f --accesslevel=inst >/dev/null 2>/dev/null +$ebusd -f --dumpflush --log bad >/dev/null 2>/dev/null +$ebusd -f --accesslevel=inst --log bad >/dev/null 2>/dev/null $ebusd -f --aclfile=/ >/dev/null 2>/dev/null -$ebusd -f --enablehex >/dev/null 2>/dev/null -$ebusd -f --enabledefine >/dev/null 2>/dev/null +$ebusd -f --enablehex --log bad >/dev/null 2>/dev/null +$ebusd -f --enabledefine --log bad >/dev/null 2>/dev/null $ebusd -f --mqttport= >/dev/null 2>/dev/null $ebusd -f --mqttport=9999999 >/dev/null 2>/dev/null $ebusd -f --mqttuser=username --mqttpass=password --mqttclientid=1234 --mqttport=1883 --mqtttopic=ebusd/%circuit/%name/%field --mqttretain --mqttjson --mqttverbose --mqttlog --mqttignoreinvalid --mqttchanges --mqtthost "" >/dev/null 2>/dev/null -$ebusd -f --mqttca=/cafile/ --mqttcert=/cert --mqttkey=12345678 --mqttkeypass=secret --mqttinsecure >/dev/null 2>/dev/null +$ebusd -f --mqttca=/cafile/ --mqttcert=/cert --mqttkey=12345678 --mqttkeypass=secret --mqttinsecure --log bad >/dev/null 2>/dev/null $ebusd -c contrib/etc/ebusd -s -f --inject=stop "ff08070400/0ab5303132333431313131" >/dev/null 2>/dev/null $ebusd -c contrib/etc/ebusd -s -f --inject=stop "ff08070400" >/dev/null 2>/dev/null $ebusd -c contrib/etc/ebusd -s -f --inject=stop "ff080704/" >/dev/null 2>/dev/null @@ -386,7 +386,7 @@ if [ "$status" != 0 ]; then fi echo `date` "got signal" sleep 2 -echo "listen"|$ebusctl -p 8877 & +echo "listen"|nc -w 10 localhost 8877 & lstpid=$! $ebusctl -p 8899 >/dev/null 2>/dev/null for line in "${lines[@]}"; do @@ -436,8 +436,9 @@ curl -s -T test_coverage.sh http://localhost:8878/data/ echo `date` "commands done" kill $lstpid verify=`send info|egrep "^address 04:"` -if [ "x$verify" != 'xaddress 04: slave #25, scanned "MF=153;ID=BBBBB;SW=3031;HW=3031"' ]; then - echo `date` "error unexpected result from info command: $verify" +expect='address 04: slave #25, scanned "MF=153;ID=BBBBB;SW=3031;HW=3031"' +if [ "x$verify" != "x$expect" ]; then + echo -e `date` "error unexpected result from info command:\n expected: >$expect<\n got: >$verify<" ls -latr kill $pid kill $srvpid From 22b6f0275210445b57b392c19b392c37f91f6ab2 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 1 Oct 2023 14:03:28 +0200 Subject: [PATCH 127/345] get rid of argp dependency --- CMakeLists.txt | 12 -- README.md | 2 +- config.h.cmake | 3 - configure.ac | 8 - contrib/alpine/APKBUILD | 2 +- src/ebusd/CMakeLists.txt | 2 +- src/ebusd/datahandler.cpp | 18 +- src/ebusd/datahandler.h | 8 +- src/ebusd/knxhandler.cpp | 31 ++-- src/ebusd/knxhandler.h | 7 +- src/ebusd/main.cpp | 157 ++++++++-------- src/ebusd/mqtthandler.cpp | 56 +++--- src/ebusd/mqtthandler.h | 7 +- src/lib/utils/CMakeLists.txt | 1 + src/lib/utils/arg.cpp | 346 +++++++++++++++++++++++++++++++++++ src/lib/utils/arg.h | 97 ++++++++++ src/tools/CMakeLists.txt | 8 +- src/tools/ebusctl.cpp | 69 +++---- src/tools/ebusfeed.cpp | 94 +++++----- src/tools/ebuspicloader.cpp | 116 ++++++------ 20 files changed, 740 insertions(+), 304 deletions(-) create mode 100755 src/lib/utils/arg.cpp create mode 100755 src/lib/utils/arg.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ab42dca80..ac2a07cba 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,18 +74,6 @@ 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_H) -if(NOT HAVE_ARGP_H) - if(NOT ARGP_HINTS) - set(ARGP_HINTS ~/argp-standalone/src) - endif(NOT ARGP_HINTS) - find_library(LIB_ARGP NAMES argp argp-standalone HINTS ARGP_HINTS) - if (NOT LIB_ARGP) - message(FATAL_ERROR "argp library not available") - endif(NOT LIB_ARGP) - set(CMAKE_REQUIRED_LIBRARIES "${CMAKE_REQUIRED_LIBRARIES} ${LIB_ARGP}") -endif(NOT HAVE_ARGP_H) - option(coverage "enable code coverage tracking." OFF) if(NOT coverage STREQUAL OFF) add_definitions(-g -O0 --coverage -Wall) diff --git a/README.md b/README.md index 15a602bc1..a35230453 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ Building ebusd from the source requires the following packages and/or features: * g++ with C++11 support (>=4.8.1) * make * kernel with pselect or ppoll support - * glibc with argp support or argp-standalone + * glibc with getopt_long support * libmosquitto-dev for MQTT support * libssl-dev for SSL support diff --git a/config.h.cmake b/config.h.cmake index f73c5a54a..cdcadefef 100755 --- a/config.h.cmake +++ b/config.h.cmake @@ -40,9 +40,6 @@ /* Defined if syslog.h is available. */ #cmakedefine HAVE_SYSLOG_H -/* Defined if argp.h is available. */ -#cmakedefine HAVE_ARGP_H - /* The name of package. */ #cmakedefine PACKAGE "${PACKAGE_NAME}" diff --git a/configure.ac b/configure.ac index c3fbd6661..df0602af9 100755 --- a/configure.ac +++ b/configure.ac @@ -48,14 +48,6 @@ if test "x$with_contrib" != "xno"; then fi AC_ARG_WITH(ebusfeed, AS_HELP_STRING([--with-ebusfeed], [enable inclusion of ebusfeed tool]), [with_ebusfeed=yes], []) AM_CONDITIONAL([WITH_EBUSFEED], [test "x$with_ebusfeed" == "xyes"]) -AC_ARG_WITH(argp-lib, AS_HELP_STRING([--with-argp-lib=PATH], [path to argp libraries]), [LDFLAGS+="-L$with_argp_lib"]) -AC_ARG_WITH(argp-include, AS_HELP_STRING([--with-argp-include=PATH], [path to argp includes]), [CXXFLAGS+="-I$with_argp_include"]) -AC_CHECK_FUNC([argp_parse], [have_argp=yes], AC_CHECK_LIB([argp], [argp_parse], [have_argp=yes; LIBS="-largp $LIBS"], [have_argp=no])) -if test "x$have_argp" = "xyes"; then - AC_CHECK_HEADER([argp.h], AC_DEFINE([HAVE_ARGP_H], [1], [Defined if argp.h is available.]), AC_MSG_ERROR([argp.h not found])) -else - AC_MSG_ERROR([argp library not found, specify argp-standalone location in --with-argp-lib= and --with-argp-include= options.]) -fi AC_ARG_WITH(mqtt, AS_HELP_STRING([--without-mqtt], [disable support for MQTT handling]), [], [with_mqtt=yes]) if test "x$with_mqtt" != "xno"; then AC_CHECK_LIB([mosquitto], [mosquitto_lib_init], diff --git a/contrib/alpine/APKBUILD b/contrib/alpine/APKBUILD index 4a50d8c7c..63a249863 100644 --- a/contrib/alpine/APKBUILD +++ b/contrib/alpine/APKBUILD @@ -8,7 +8,7 @@ url="https://github.com/john30/ebusd" # Upstream only supports these archs. arch="x86 x86_64 aarch64 armhf armv7" license="GPL-3.0-only" -makedepends="argp-standalone cmake mosquitto-dev openssl-dev" +makedepends="cmake mosquitto-dev openssl-dev" source="$pkgname-$pkgver.tar.gz::https://github.com/john30/${pkgname}/archive/refs/tags/${pkgver}.tar.gz" build() { diff --git a/src/ebusd/CMakeLists.txt b/src/ebusd/CMakeLists.txt index 7166bd08b..85b41e308 100644 --- a/src/ebusd/CMakeLists.txt +++ b/src/ebusd/CMakeLists.txt @@ -33,7 +33,7 @@ include_directories(../lib/ebus) include_directories(../lib/utils) add_executable(ebusd ${ebusd_SOURCES}) -target_link_libraries(ebusd utils ebus pthread rt ${LIB_ARGP} ${ebusd_LIBS}) +target_link_libraries(ebusd utils ebus pthread rt ${ebusd_LIBS}) install(TARGETS ebusd EXPORT ebusd DESTINATION usr/bin) diff --git a/src/ebusd/datahandler.cpp b/src/ebusd/datahandler.cpp index fc18ebec0..37fa33bf6 100755 --- a/src/ebusd/datahandler.cpp +++ b/src/ebusd/datahandler.cpp @@ -30,11 +30,11 @@ namespace ebusd { -/** the final @a argp_child structure. */ -static const struct argp_child g_last_argp_child = {nullptr, 0, nullptr, 0}; +/** the final @a argParseChildOpt structure. */ +static const argParseChildOpt g_last_arg_child = {nullptr, nullptr}; -/** the list of @a argp_child structures. */ -static struct argp_child g_argp_children[ +/** the list of @a argParseChildOpt structures. */ +static argParseChildOpt g_arg_children[ 1 #ifdef HAVE_MQTT +1 @@ -44,17 +44,17 @@ static struct argp_child g_argp_children[ #endif ]; -const struct argp_child* datahandler_getargs() { +const argParseChildOpt* datahandler_getargs() { size_t count = 0; #ifdef HAVE_MQTT - g_argp_children[count++] = *mqtthandler_getargs(); + g_arg_children[count++] = *mqtthandler_getargs(); #endif #ifdef HAVE_KNX - g_argp_children[count++] = *knxhandler_getargs(); + g_arg_children[count++] = *knxhandler_getargs(); #endif if (count > 0) { - g_argp_children[count] = g_last_argp_child; - return g_argp_children; + g_arg_children[count] = g_last_arg_child; + return g_arg_children; } return nullptr; } diff --git a/src/ebusd/datahandler.h b/src/ebusd/datahandler.h index 84a8b507c..c3c03a751 100755 --- a/src/ebusd/datahandler.h +++ b/src/ebusd/datahandler.h @@ -19,12 +19,12 @@ #ifndef EBUSD_DATAHANDLER_H_ #define EBUSD_DATAHANDLER_H_ -#include #include #include #include #include "ebusd/bushandler.h" #include "lib/ebus/message.h" +#include "lib/utils/arg.h" namespace ebusd { @@ -49,10 +49,10 @@ enum scanStatus_t { /** - * Helper function for getting the argp definition for all known @a DataHandler instances. - * @return a pointer to the argp_child structure, or nullptr. + * Helper function for getting the arg definition for all known @a DataHandler instances. + * @return a pointer to the child argument options, or nullptr. */ -const struct argp_child* datahandler_getargs(); +const argParseChildOpt* datahandler_getargs(); /** * Registration function that is called once during initialization. diff --git a/src/ebusd/knxhandler.cpp b/src/ebusd/knxhandler.cpp index a8746bcec..2f7b52846 100644 --- a/src/ebusd/knxhandler.cpp +++ b/src/ebusd/knxhandler.cpp @@ -52,9 +52,9 @@ using std::dec; #define O_VAR (O_INT-1) /** the definition of the KNX arguments. */ -static const struct argp_option g_knx_argp_options[] = { +static const argDef g_knx_argDefs[] = { {nullptr, 0, nullptr, 0, "KNX options:", 1 }, - {"knxurl", O_URL, "URL", OPTION_ARG_OPTIONAL, "URL to open (i.e. \"[multicast][@interface]\" for KNXnet/IP" + {"knxurl", O_URL, "URL", af_optional, "URL to open (i.e. \"[multicast][@interface]\" for KNXnet/IP" #ifdef HAVE_KNXD " or \"ip:host[:port]\" / \"local:/socketpath\" for knxd" #endif @@ -79,11 +79,11 @@ static vector* g_integrationVars = nullptr; //!< the integration settin /** * The KNX argument parsing function. - * @param key the key from @a g_knx_argp_options. + * @param key the key from @a g_knx_arg_options. * @param arg the option argument, or nullptr. * @param state the parsing state. */ -static error_t knx_parse_opt(int key, char *arg, struct argp_state *state) { +static int knx_parse_opt(int key, char *arg, const argParseOpt *parseOpt) { result_t result; unsigned int value; switch (key) { @@ -94,12 +94,12 @@ static error_t knx_parse_opt(int key, char *arg, struct argp_state *state) { case O_AGR: // --knxrage=5 if (arg == nullptr || arg[0] == 0) { - argp_error(state, "invalid knxrage value"); + argParseError(parseOpt, "invalid knxrage value"); return EINVAL; } value = parseInt(arg, 10, 0, 99999999, &result); if (result != RESULT_OK) { - argp_error(state, "invalid knxrage"); + argParseError(parseOpt, "invalid knxrage"); return EINVAL; } g_maxReadAge = value; @@ -107,12 +107,12 @@ static error_t knx_parse_opt(int key, char *arg, struct argp_state *state) { case O_AGW: // --knxwage=5 if (arg == nullptr || arg[0] == 0) { - argp_error(state, "invalid knxwage value"); + argParseError(parseOpt, "invalid knxwage value"); return EINVAL; } value = parseInt(arg, 10, 0, 99999999, &result); if (result != RESULT_OK) { - argp_error(state, "invalid knxwage"); + argParseError(parseOpt, "invalid knxwage"); return EINVAL; } g_maxWriteAge = value; @@ -120,7 +120,7 @@ static error_t knx_parse_opt(int key, char *arg, struct argp_state *state) { case O_INT: // --knxint=/etc/ebusd/knx.cfg if (arg == nullptr || arg[0] == 0 || strcmp("/", arg) == 0) { - argp_error(state, "invalid knxint file"); + argParseError(parseOpt, "invalid knxint file"); return EINVAL; } g_integrationFile = arg; @@ -129,7 +129,7 @@ static error_t knx_parse_opt(int key, char *arg, struct argp_state *state) { case O_VAR: // --knxvar=NAME=VALUE[,NAME=VALUE]* { if (arg == nullptr || arg[0] == 0 || !strchr(arg, '=')) { - argp_error(state, "invalid knxvar"); + argParseError(parseOpt, "invalid knxvar"); return EINVAL; } if (!g_integrationVars) { @@ -143,18 +143,15 @@ static error_t knx_parse_opt(int key, char *arg, struct argp_state *state) { } default: - return ARGP_ERR_UNKNOWN; + return ESRCH; } return 0; } -static const struct argp g_knx_argp = { g_knx_argp_options, knx_parse_opt, nullptr, nullptr, nullptr, nullptr, - nullptr }; -static const struct argp_child g_knx_argp_child = {&g_knx_argp, 0, "", 1}; +static const argParseChildOpt g_knx_arg_child = { g_knx_argDefs, knx_parse_opt }; - -const struct argp_child* knxhandler_getargs() { - return &g_knx_argp_child; +const argParseChildOpt* knxhandler_getargs() { + return &g_knx_arg_child; } bool knxhandler_register(UserInfo* userInfo, BusHandler* busHandler, MessageMap* messages, diff --git a/src/ebusd/knxhandler.h b/src/ebusd/knxhandler.h index c2ad961df..fcfcebc6d 100644 --- a/src/ebusd/knxhandler.h +++ b/src/ebusd/knxhandler.h @@ -29,6 +29,7 @@ #include "lib/ebus/message.h" #include "lib/ebus/stringhelper.h" #include "lib/knx/knx.h" +#include "lib/utils/arg.h" namespace ebusd { @@ -41,10 +42,10 @@ using std::string; using std::vector; /** - * Helper function for getting the argp definition for KNX. - * @return a pointer to the argp_child structure. + * Helper function for getting the arg definition for KNX. + * @return a pointer to the child argument options, or nullptr. */ -const struct argp_child* knxhandler_getargs(); +const argParseChildOpt* knxhandler_getargs(); /** * Registration function that is called once during initialization. diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index 1cfcdd487..3790a69ae 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -23,7 +23,6 @@ #include "ebusd/main.h" #include #include -#include #include #include #include @@ -32,17 +31,11 @@ #include #include "ebusd/mainloop.h" #include "ebusd/network.h" +#include "lib/utils/arg.h" #include "lib/utils/log.h" #include "lib/utils/httpclient.h" #include "ebusd/scan.h" - -/** the version string of the program. */ -const char *argp_program_version = "" PACKAGE_STRING "." REVISION ""; - -/** the report bugs to address of the program. */ -const char *argp_program_bug_address = "" PACKAGE_BUGREPORT ""; - namespace ebusd { using std::dec; @@ -146,10 +139,6 @@ static string s_configPath = CONFIG_PATH; /** whether scanConfig or configPath were set by arguments. */ static bool s_scanConfigOrPathSet = false; -/** the documentation of the program. */ -static const char argpdoc[] = - "A daemon for communication with eBUS heating systems."; - #define O_INISND -2 #define O_DEVLAT (O_INISND-1) #define O_SCNRET (O_DEVLAT-1) @@ -187,7 +176,7 @@ static const char argpdoc[] = #define O_DMPFLU (O_DMPSIZ-1) /** the definition of the known program arguments. */ -static const struct argp_option argpoptions[] = { +static const argDef argDefs[] = { {nullptr, 0, nullptr, 0, "Device options:", 1 }, {"device", 'd', "DEV", 0, "Use DEV as eBUS device (" "prefix \"ens:\" for enhanced high speed device or " @@ -203,7 +192,7 @@ static const struct argp_option argpoptions[] = { {nullptr, 0, nullptr, 0, "Message configuration options:", 2 }, {"configpath", 'c', "PATH", 0, "Read CSV config files from PATH (local folder or HTTPS URL) [" CONFIG_PATH "]", 0 }, - {"scanconfig", 's', "ADDR", OPTION_ARG_OPTIONAL, "Pick CSV config files matching initial scan ADDR: " + {"scanconfig", 's', "ADDR", af_optional, "Pick CSV config files matching initial scan ADDR: " "empty for broadcast ident message (default when configpath is not given), " "\"none\" for no initial scan message, " "\"full\" for full scan, " @@ -215,11 +204,11 @@ static const struct argp_option argpoptions[] = { {"configlang", O_CFGLNG, "LANG", 0, "Prefer LANG in multilingual configuration files [system default language]", 0 }, {"checkconfig", O_CHKCFG, nullptr, 0, "Check config files, then stop", 0 }, - {"dumpconfig", O_DMPCFG, "FORMAT", OPTION_ARG_OPTIONAL, + {"dumpconfig", O_DMPCFG, "FORMAT", af_optional, "Check and dump config files in FORMAT (\"json\" or \"csv\"), then stop", 0 }, {"dumpconfigto", O_DMPCTO, "FILE", 0, "Dump config files to FILE", 0 }, {"pollinterval", O_POLINT, "SEC", 0, "Poll for data every SEC seconds (0=disable) [5]", 0 }, - {"inject", 'i', "stop", OPTION_ARG_OPTIONAL, "Inject remaining arguments as already seen messages (e.g. " + {"inject", 'i', "stop", af_optional, "Inject remaining arguments as already seen messages (e.g. " "\"FF08070400/0AB5454850303003277201\"), optionally stop afterwards", 0 }, #ifdef HAVE_SSL {"cafile", O_CAFILE, "FILE", 0, "Use CA FILE for checking certificates (uses defaults," @@ -261,7 +250,7 @@ static const struct argp_option argpoptions[] = { " [notice]", 0 }, {nullptr, 0, nullptr, 0, "Raw logging options:", 6 }, - {"lograwdata", O_RAW, "bytes", OPTION_ARG_OPTIONAL, + {"lograwdata", O_RAW, "bytes", af_optional, "Log messages or all received/sent bytes on the bus", 0 }, {"lograwdatafile", O_RAWFIL, "FILE", 0, "Write raw log to FILE [" PACKAGE_LOGFILE "]", 0 }, {"lograwdatasize", O_RAWSIZ, "SIZE", 0, "Make raw log file no larger than SIZE kB [100]", 0 }, @@ -277,12 +266,12 @@ static const struct argp_option argpoptions[] = { /** * The program argument parsing function. - * @param key the key from @a argpoptions. + * @param key the key from @a argDefs. * @param arg the option argument, or nullptr. - * @param state the parsing state. + * @param parseOpt the parse options. */ -error_t parse_opt(int key, char *arg, struct argp_state *state) { - struct options *opt = (struct options*)state->input; +static int parse_opt(int key, char *arg, const argParseOpt *parseOpt) { + struct options *opt = (struct options*)parseOpt->userArg; result_t result = RESULT_OK; unsigned int value; @@ -290,7 +279,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { // Device options: case 'd': // --device=/dev/ttyUSB0 if (arg == nullptr || arg[0] == 0) { - argp_error(state, "invalid device"); + argParseError(parseOpt, "invalid device"); return EINVAL; } opt->device = arg; @@ -307,7 +296,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { case O_DEVLAT: // --latency=10 value = parseInt(arg, 10, 0, 200000, &result); // backwards compatible (micros) if (result != RESULT_OK || (value <= 1000 && value > 200)) { // backwards compatible (micros) - argp_error(state, "invalid latency"); + argParseError(parseOpt, "invalid latency"); return EINVAL; } opt->extraLatency = value > 1000 ? value/1000 : value; // backwards compatible (micros) @@ -316,7 +305,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { // Message configuration options: case 'c': // --configpath=https://cfg.ebusd.eu/ if (arg == nullptr || arg[0] == 0 || strcmp("/", arg) == 0) { - argp_error(state, "invalid configpath"); + argParseError(parseOpt, "invalid configpath"); return EINVAL; } s_configPath = arg; @@ -336,7 +325,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { } else { auto address = (symbol_t)parseInt(arg, 16, 0x00, 0xff, &result); if (result != RESULT_OK || !isValidAddress(address)) { - argp_error(state, "invalid initial scan address"); + argParseError(parseOpt, "invalid initial scan address"); return EINVAL; } if (isMaster(address)) { @@ -353,7 +342,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { case O_SCNRET: // --scanretries=10 value = parseInt(arg, 10, 0, 100, &result); if (result != RESULT_OK) { - argp_error(state, "invalid scanretries"); + argParseError(parseOpt, "invalid scanretries"); return EINVAL; } opt->scanRetries = value; @@ -371,14 +360,14 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { } else if (strcmp("json", arg) == 0) { opt->dumpConfig = OF_DEFINITION | OF_NAMES | OF_UNITS | OF_COMMENTS | OF_VALUENAME | OF_ALL_ATTRS | OF_JSON; } else { - argp_error(state, "invalid dumpconfig"); + argParseError(parseOpt, "invalid dumpconfig"); return EINVAL; } opt->checkConfig = true; break; case O_DMPCTO: // --dumpconfigto=FILE if (!arg || arg[0] == 0) { - argp_error(state, "invalid dumpconfigto"); + argParseError(parseOpt, "invalid dumpconfigto"); return EINVAL; } opt->dumpConfigTo = arg; @@ -386,7 +375,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { case O_POLINT: // --pollinterval=5 value = parseInt(arg, 10, 0, 3600, &result); if (result != RESULT_OK) { - argp_error(state, "invalid pollinterval"); + argParseError(parseOpt, "invalid pollinterval"); return EINVAL; } opt->pollInterval = value; @@ -407,7 +396,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { { auto address = (symbol_t)parseInt(arg, 16, 0, 0xff, &result); if (result != RESULT_OK || !isMaster(address)) { - argp_error(state, "invalid address"); + argParseError(parseOpt, "invalid address"); return EINVAL; } opt->address = address; @@ -419,7 +408,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { case O_ACQTIM: // --acquiretimeout=10 value = parseInt(arg, 10, 1, 100000, &result); // backwards compatible (micros) if (result != RESULT_OK || (value <= 1000 && value > 100)) { // backwards compatible (micros) - argp_error(state, "invalid acquiretimeout"); + argParseError(parseOpt, "invalid acquiretimeout"); return EINVAL; } opt->acquireTimeout = value > 1000 ? value/1000 : value; // backwards compatible (micros) @@ -427,7 +416,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { case O_ACQRET: // --acquireretries=3 value = parseInt(arg, 10, 0, 10, &result); if (result != RESULT_OK) { - argp_error(state, "invalid acquireretries"); + argParseError(parseOpt, "invalid acquireretries"); return EINVAL; } opt->acquireRetries = value; @@ -435,7 +424,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { case O_SNDRET: // --sendretries=2 value = parseInt(arg, 10, 0, 10, &result); if (result != RESULT_OK) { - argp_error(state, "invalid sendretries"); + argParseError(parseOpt, "invalid sendretries"); return EINVAL; } opt->sendRetries = value; @@ -443,7 +432,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { case O_RCVTIM: // --receivetimeout=25 value = parseInt(arg, 10, 1, 100000, &result); // backwards compatible (micros) if (result != RESULT_OK || (value <= 1000 && value > 100)) { // backwards compatible (micros) - argp_error(state, "invalid receivetimeout"); + argParseError(parseOpt, "invalid receivetimeout"); return EINVAL; } opt->receiveTimeout = value > 1000 ? value/1000 : value; // backwards compatible (micros) @@ -451,7 +440,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { case O_MASCNT: // --numbermasters=0 value = parseInt(arg, 10, 0, 25, &result); if (result != RESULT_OK) { - argp_error(state, "invalid numbermasters"); + argParseError(parseOpt, "invalid numbermasters"); return EINVAL; } opt->masterCount = value; @@ -463,14 +452,14 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { // Daemon options: case O_ACLDEF: // --accesslevel=* if (arg == nullptr) { - argp_error(state, "invalid accesslevel"); + argParseError(parseOpt, "invalid accesslevel"); return EINVAL; } opt->accessLevel = arg; break; case O_ACLFIL: // --aclfile=/etc/ebusd/acl if (arg == nullptr || arg[0] == 0 || strcmp("/", arg) == 0) { - argp_error(state, "invalid aclfile"); + argParseError(parseOpt, "invalid aclfile"); return EINVAL; } opt->aclFile = arg; @@ -486,7 +475,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { break; case O_PIDFIL: // --pidfile=/var/run/ebusd.pid if (arg == nullptr || arg[0] == 0 || strcmp("/", arg) == 0) { - argp_error(state, "invalid pidfile"); + argParseError(parseOpt, "invalid pidfile"); return EINVAL; } opt->pidFile = arg; @@ -494,7 +483,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { case 'p': // --port=8888 value = parseInt(arg, 10, 1, 65535, &result); if (result != RESULT_OK) { - argp_error(state, "invalid port"); + argParseError(parseOpt, "invalid port"); return EINVAL; } opt->port = (uint16_t)value; @@ -505,21 +494,21 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { case O_HTTPPT: // --httpport=0 value = parseInt(arg, 10, 1, 65535, &result); if (result != RESULT_OK) { - argp_error(state, "invalid httpport"); + argParseError(parseOpt, "invalid httpport"); return EINVAL; } opt->httpPort = (uint16_t)value; break; case O_HTMLPA: // --htmlpath=/var/ebusd/html if (arg == nullptr || arg[0] == 0 || strcmp("/", arg) == 0) { - argp_error(state, "invalid htmlpath"); + argParseError(parseOpt, "invalid htmlpath"); return EINVAL; } opt->htmlPath = arg; break; case O_UPDCHK: // --updatecheck=on if (arg == nullptr || arg[0] == 0) { - argp_error(state, "invalid updatecheck"); + argParseError(parseOpt, "invalid updatecheck"); return EINVAL; } if (strcmp("on", arg) == 0) { @@ -527,7 +516,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { } else if (strcmp("off", arg) == 0) { opt->updateCheck = false; } else { - argp_error(state, "invalid updatecheck"); + argParseError(parseOpt, "invalid updatecheck"); return EINVAL; } break; @@ -535,7 +524,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 || strcmp("/", arg) == 0) { - argp_error(state, "invalid logfile"); + argParseError(parseOpt, "invalid logfile"); return EINVAL; } opt->logFile = arg; @@ -546,23 +535,23 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { if (pos == nullptr) { pos = strchr(arg, ' '); if (pos == nullptr) { - argp_error(state, "invalid log"); + argParseError(parseOpt, "invalid log"); return EINVAL; } } *pos = 0; int facilities = parseLogFacilities(arg); if (facilities == -1) { - argp_error(state, "invalid log: areas"); + argParseError(parseOpt, "invalid log: areas"); return EINVAL; } LogLevel level = parseLogLevel(pos + 1); if (level == ll_COUNT) { - argp_error(state, "invalid log: level"); + argParseError(parseOpt, "invalid log: level"); return EINVAL; } if (opt->logAreas != -1 || opt->logLevel != ll_COUNT) { - argp_error(state, "invalid log (combined with logareas or loglevel)"); + argParseError(parseOpt, "invalid log (combined with logareas or loglevel)"); return EINVAL; } setFacilitiesLogLevel(facilities, level); @@ -573,11 +562,11 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { { int facilities = parseLogFacilities(arg); if (facilities == -1) { - argp_error(state, "invalid logareas"); + argParseError(parseOpt, "invalid logareas"); return EINVAL; } if (opt->multiLog) { - argp_error(state, "invalid logareas (combined with log)"); + argParseError(parseOpt, "invalid logareas (combined with log)"); return EINVAL; } opt->logAreas = facilities; @@ -587,11 +576,11 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { { LogLevel logLevel = parseLogLevel(arg); if (logLevel == ll_COUNT) { - argp_error(state, "invalid loglevel"); + argParseError(parseOpt, "invalid loglevel"); return EINVAL; } if (opt->multiLog) { - argp_error(state, "invalid loglevel (combined with log)"); + argParseError(parseOpt, "invalid loglevel (combined with log)"); return EINVAL; } opt->logLevel = logLevel; @@ -604,7 +593,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { break; case O_RAWFIL: // --lograwdatafile=/var/log/ebusd.log if (arg == nullptr || arg[0] == 0 || strcmp("/", arg) == 0) { - argp_error(state, "invalid lograwdatafile"); + argParseError(parseOpt, "invalid lograwdatafile"); return EINVAL; } opt->logRawFile = arg; @@ -612,7 +601,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { case O_RAWSIZ: // --lograwdatasize=100 value = parseInt(arg, 10, 1, 1000000, &result); if (result != RESULT_OK) { - argp_error(state, "invalid lograwdatasize"); + argParseError(parseOpt, "invalid lograwdatasize"); return EINVAL; } opt->logRawSize = value; @@ -625,7 +614,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { break; case O_DMPFIL: // --dumpfile=/tmp/ebusd_dump.bin if (arg == nullptr || arg[0] == 0 || strcmp("/", arg) == 0) { - argp_error(state, "invalid dumpfile"); + argParseError(parseOpt, "invalid dumpfile"); return EINVAL; } opt->dumpFile = arg; @@ -633,7 +622,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { case O_DMPSIZ: // --dumpsize=100 value = parseInt(arg, 10, 1, 1000000, &result); if (result != RESULT_OK) { - argp_error(state, "invalid dumpsize"); + argParseError(parseOpt, "invalid dumpsize"); return EINVAL; } opt->dumpSize = value; @@ -642,33 +631,27 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { opt->dumpFlush = true; break; - case ARGP_KEY_ARG: - if (opt->injectMessages || (opt->checkConfig && opt->scanConfig)) { - return ARGP_ERR_UNKNOWN; - } - argp_error(state, "invalid arguments starting with \"%s\"", arg); - return EINVAL; default: - return ARGP_ERR_UNKNOWN; + return ESRCH; } - // check for invalid arg combinations if (opt->readOnly && (opt->answer || opt->generateSyn || opt->initialSend || (opt->scanConfig && opt->initialScan != ESC))) { - argp_error(state, "cannot combine readonly with answer/generatesyn/initsend/scanconfig"); + argParseError(parseOpt, "cannot combine readonly with answer/generatesyn/initsend/scanconfig"); return EINVAL; } if (opt->scanConfig && opt->pollInterval == 0) { - argp_error(state, "scanconfig without polling may lead to invalid files included for certain products!"); + argParseError(parseOpt, "scanconfig without polling may lead to invalid files included for certain products!"); return EINVAL; } if (opt->injectMessages && (opt->checkConfig || opt->dumpConfig)) { - argp_error(state, "cannot combine inject with checkconfig/dumpconfig"); + argParseError(parseOpt, "cannot combine inject with checkconfig/dumpconfig"); return EINVAL; } return 0; } + void shutdown(bool error = false); void daemonize() { @@ -823,8 +806,17 @@ void signalHandler(int sig) { * @return the exit code. */ int main(int argc, char* argv[], char* envp[]) { - struct argp aargp = { argpoptions, parse_opt, nullptr, argpdoc, datahandler_getargs(), nullptr, nullptr }; - setenv("ARGP_HELP_FMT", "no-dup-args-note", 0); + const argParseOpt parseOpt = { + argDefs, + parse_opt, + 0, + "" PACKAGE_NAME, + "[INJECT...]", + "A daemon for communication with eBUS heating systems.", + "Report bugs to " PACKAGE_BUGREPORT " .", + datahandler_getargs(), + &s_opt + }; char envname[32] = "--"; // needs to cover at least max length of any option name plus "--" char* envopt = envname+2; @@ -849,7 +841,7 @@ int main(int argc, char* argv[], char* envp[]) { // ignore those defined in Dockerfile, EBUSD_OPTS, those with final args, and interactive ones continue; } - char* envargv[] = {envname, pos+1}; + char* envargv[] = {argv[0], envname, pos+1}; int cnt = pos[1] ? 2 : 1; if (pos[1] && strlen(*env) < sizeof(envname)-3 && (strcmp(envopt, "scanconfig") == 0 || strcmp(envopt, "lograwdata") == 0)) { @@ -858,8 +850,7 @@ int main(int argc, char* argv[], char* envp[]) { strcat(envopt, pos); } int idx = -1; - s_opt.injectMessages = true; // for skipping unknown values via ARGP_ERR_UNKNOWN - error_t err = argp_parse(&aargp, cnt, envargv, ARGP_PARSE_ARGV0|ARGP_SILENT|ARGP_IN_ORDER, &idx, &s_opt); + int err = argParse(&parseOpt, 1+cnt, envargv, &idx); if (err != 0 && idx == -1) { // ignore args for non-arg boolean options if (err == ESRCH) { // special value to abort immediately logWrite(lf_main, ll_error, "invalid argument in env: %s", *env); // force logging on exit @@ -867,13 +858,27 @@ int main(int argc, char* argv[], char* envp[]) { } logWrite(lf_main, ll_error, "invalid/unknown argument in env (ignored): %s", *env); // force logging } - s_opt.injectMessages = false; // restore (was not parsed from cmdline args yet) } int arg_index = -1; - if (argp_parse(&aargp, argc, argv, ARGP_IN_ORDER, &arg_index, &s_opt) != 0) { - logWrite(lf_main, ll_error, "invalid arguments"); // force logging on exit - return EINVAL; + switch (argParse(&parseOpt, argc, argv, &arg_index)) { + case 0: // OK + break; + case '?': // help printed + return 0; + case 'V': + printf("" PACKAGE_STRING "." REVISION "\n"); + return 0; + default: + logWrite(lf_main, ll_error, "invalid arguments"); // force logging on exit + return EINVAL; + } + + if (arg_index >= 0) { + if (!s_opt.injectMessages && !(s_opt.checkConfig && s_opt.scanConfig)) { + fprintf(stderr, "invalid arguments starting with \"%s\"", argv[arg_index]); + return EINVAL; + } } if (s_opt.logAreas != -1 || s_opt.logLevel != ll_COUNT) { diff --git a/src/ebusd/mqtthandler.cpp b/src/ebusd/mqtthandler.cpp index f508f3223..02014c5d3 100755 --- a/src/ebusd/mqtthandler.cpp +++ b/src/ebusd/mqtthandler.cpp @@ -56,7 +56,7 @@ using std::dec; #define O_VERB (O_INSE+1) /** the definition of the MQTT arguments. */ -static const struct argp_option g_mqtt_argp_options[] = { +static const argDef g_mqtt_argDefs[] = { {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 }, @@ -72,7 +72,7 @@ static const struct argp_option g_mqtt_argp_options[] = { {"mqttqos", O_PQOS, "QOS", 0, "Set the QoS value for all topics (0-2) [0]", 0 }, {"mqttint", O_INTF, "FILE", 0, "Read MQTT integration settings from FILE (no default)", 0 }, {"mqttvar", O_IVAR, "NAME=VALUE[,...]", 0, "Add variable(s) to the read MQTT integration settings", 0 }, - {"mqttjson", O_JSON, "short", OPTION_ARG_OPTIONAL, + {"mqttjson", O_JSON, "short", af_optional, "Publish in JSON format instead of strings, optionally in short (value directly below field key)", 0 }, {"mqttverbose", O_VERB, nullptr, 0, "Publish all available attributes", 0 }, #if (LIBMOSQUITTO_VERSION_NUMBER >= 1003001) @@ -144,17 +144,17 @@ void splitFields(const string& str, vector* row); /** * The MQTT argument parsing function. - * @param key the key from @a g_mqtt_argp_options. + * @param key the key from @a g_mqtt_argDefs. * @param arg the option argument, or nullptr. * @param state the parsing state. */ -static error_t mqtt_parse_opt(int key, char *arg, struct argp_state *state) { +static int mqtt_parse_opt(int key, char *arg, const argParseOpt *parseOpt) { result_t result = RESULT_OK; unsigned int value; switch (key) { case O_HOST: // --mqtthost=localhost if (arg == nullptr || arg[0] == 0) { - argp_error(state, "invalid mqtthost"); + argParseError(parseOpt, "invalid mqtthost"); return EINVAL; } g_host = arg; @@ -163,7 +163,7 @@ static error_t mqtt_parse_opt(int key, char *arg, struct argp_state *state) { case O_PORT: // --mqttport=1883 value = parseInt(arg, 10, 1, 65535, &result); if (result != RESULT_OK) { - argp_error(state, "invalid mqttport"); + argParseError(parseOpt, "invalid mqttport"); return EINVAL; } g_port = (uint16_t)value; @@ -171,7 +171,7 @@ static error_t mqtt_parse_opt(int key, char *arg, struct argp_state *state) { case O_CLID: // --mqttclientid=clientid if (arg == nullptr || arg[0] == 0) { - argp_error(state, "invalid mqttclientid"); + argParseError(parseOpt, "invalid mqttclientid"); return EINVAL; } g_clientId = arg; @@ -179,7 +179,7 @@ static error_t mqtt_parse_opt(int key, char *arg, struct argp_state *state) { case O_USER: // --mqttuser=username if (arg == nullptr) { - argp_error(state, "invalid mqttuser"); + argParseError(parseOpt, "invalid mqttuser"); return EINVAL; } g_username = arg; @@ -187,7 +187,7 @@ static error_t mqtt_parse_opt(int key, char *arg, struct argp_state *state) { case O_PASS: // --mqttpass=password if (arg == nullptr) { - argp_error(state, "invalid mqttpass"); + argParseError(parseOpt, "invalid mqttpass"); return EINVAL; } g_password = replaceSecret(arg); @@ -196,21 +196,21 @@ static error_t mqtt_parse_opt(int key, char *arg, struct argp_state *state) { case O_TOPI: // --mqtttopic=ebusd { if (arg == nullptr || arg[0] == 0 || arg[0] == '/' || strchr(arg, '+') || arg[strlen(arg)-1] == '/') { - argp_error(state, "invalid mqtttopic"); + argParseError(parseOpt, "invalid mqtttopic"); return EINVAL; } char *pos = strchr(arg, '#'); if (pos && (pos == arg || pos[1])) { // allow # only at very last position (to indicate not using any default) - argp_error(state, "invalid mqtttopic"); + argParseError(parseOpt, "invalid mqtttopic"); return EINVAL; } if (g_topic) { - argp_error(state, "duplicate mqtttopic"); + argParseError(parseOpt, "duplicate mqtttopic"); return EINVAL; } StringReplacer replacer; if (!replacer.parse(arg, true)) { - argp_error(state, "malformed mqtttopic"); + argParseError(parseOpt, "malformed mqtttopic"); return ESRCH; // abort in any case due to the above potentially being destructive } g_topic = arg; @@ -219,7 +219,7 @@ static error_t mqtt_parse_opt(int key, char *arg, struct argp_state *state) { case O_GTOP: // --mqttglobal=global/ if (arg == nullptr || strchr(arg, '+') || strchr(arg, '#')) { - argp_error(state, "invalid mqttglobal"); + argParseError(parseOpt, "invalid mqttglobal"); return EINVAL; } g_globalTopic = arg; @@ -232,7 +232,7 @@ static error_t mqtt_parse_opt(int key, char *arg, struct argp_state *state) { case O_PQOS: // --mqttqos=0 value = parseInt(arg, 10, 0, 2, &result); if (result != RESULT_OK) { - argp_error(state, "invalid mqttqos value"); + argParseError(parseOpt, "invalid mqttqos value"); return EINVAL; } g_qos = static_cast(value); @@ -240,7 +240,7 @@ static error_t mqtt_parse_opt(int key, char *arg, struct argp_state *state) { case O_INTF: // --mqttint=/etc/ebusd/mqttint.cfg if (arg == nullptr || arg[0] == 0 || strcmp("/", arg) == 0) { - argp_error(state, "invalid mqttint file"); + argParseError(parseOpt, "invalid mqttint file"); return EINVAL; } g_integrationFile = arg; @@ -248,7 +248,7 @@ static error_t mqtt_parse_opt(int key, char *arg, struct argp_state *state) { case O_IVAR: // --mqttvar=NAME=VALUE[,NAME=VALUE]* if (arg == nullptr || arg[0] == 0 || !strchr(arg, '=')) { - argp_error(state, "invalid mqttvar"); + argParseError(parseOpt, "invalid mqttvar"); return EINVAL; } if (!g_integrationVars) { @@ -277,7 +277,7 @@ static error_t mqtt_parse_opt(int key, char *arg, struct argp_state *state) { #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"); + argParseError(parseOpt, "invalid mqttversion"); return EINVAL; } g_version = strcmp(arg, "3.1.1") == 0 ? MQTT_PROTOCOL_V311 : MQTT_PROTOCOL_V31; @@ -295,7 +295,7 @@ static error_t mqtt_parse_opt(int key, char *arg, struct argp_state *state) { #if (LIBMOSQUITTO_MAJOR >= 1) case O_CAFI: // --mqttca=file or --mqttca=dir/ if (arg == nullptr || arg[0] == 0) { - argp_error(state, "invalid mqttca"); + argParseError(parseOpt, "invalid mqttca"); return EINVAL; } if (arg[strlen(arg)-1] == '/') { @@ -309,7 +309,7 @@ static error_t mqtt_parse_opt(int key, char *arg, struct argp_state *state) { case O_CERT: // --mqttcert=CERTFILE if (arg == nullptr || arg[0] == 0) { - argp_error(state, "invalid mqttcert"); + argParseError(parseOpt, "invalid mqttcert"); return EINVAL; } g_certfile = arg; @@ -317,7 +317,7 @@ static error_t mqtt_parse_opt(int key, char *arg, struct argp_state *state) { case O_KEYF: // --mqttkey=KEYFILE if (arg == nullptr || arg[0] == 0) { - argp_error(state, "invalid mqttkey"); + argParseError(parseOpt, "invalid mqttkey"); return EINVAL; } g_keyfile = arg; @@ -325,7 +325,7 @@ static error_t mqtt_parse_opt(int key, char *arg, struct argp_state *state) { case O_KEPA: // --mqttkeypass=PASSWORD if (arg == nullptr) { - argp_error(state, "invalid mqttkeypass"); + argParseError(parseOpt, "invalid mqttkeypass"); return EINVAL; } g_keypass = replaceSecret(arg); @@ -336,18 +336,18 @@ static error_t mqtt_parse_opt(int key, char *arg, struct argp_state *state) { #endif default: - return ARGP_ERR_UNKNOWN; + return EINVAL; } return 0; } -static const struct argp g_mqtt_argp = { g_mqtt_argp_options, mqtt_parse_opt, nullptr, nullptr, nullptr, nullptr, - nullptr }; -static const struct argp_child g_mqtt_argp_child = {&g_mqtt_argp, 0, "", 1}; +static const argParseChildOpt g_mqtt_arg_child = { + g_mqtt_argDefs, mqtt_parse_opt +}; -const struct argp_child* mqtthandler_getargs() { - return &g_mqtt_argp_child; +const argParseChildOpt* mqtthandler_getargs() { + return &g_mqtt_arg_child; } bool check(int code, const char* method) { diff --git a/src/ebusd/mqtthandler.h b/src/ebusd/mqtthandler.h index 943012332..acef8dab3 100644 --- a/src/ebusd/mqtthandler.h +++ b/src/ebusd/mqtthandler.h @@ -29,6 +29,7 @@ #include "ebusd/bushandler.h" #include "lib/ebus/message.h" #include "lib/ebus/stringhelper.h" +#include "lib/utils/arg.h" namespace ebusd { @@ -42,10 +43,10 @@ using std::string; using std::vector; /** - * Helper function for getting the argp definition for MQTT. - * @return a pointer to the argp_child structure. + * Helper function for getting the arg definition for MQTT. + * @return a pointer to the child argument options, or nullptr. */ -const struct argp_child* mqtthandler_getargs(); +const argParseChildOpt* mqtthandler_getargs(); /** * Registration function that is called once during initialization. diff --git a/src/lib/utils/CMakeLists.txt b/src/lib/utils/CMakeLists.txt index 319b5d464..5fece5f1c 100755 --- a/src/lib/utils/CMakeLists.txt +++ b/src/lib/utils/CMakeLists.txt @@ -1,6 +1,7 @@ add_definitions(-Wconversion) set(libutils_a_SOURCES + arg.h arg.cpp log.h log.cpp tcpsocket.h tcpsocket.cpp thread.h thread.cpp diff --git a/src/lib/utils/arg.cpp b/src/lib/utils/arg.cpp new file mode 100755 index 000000000..f6ae7712b --- /dev/null +++ b/src/lib/utils/arg.cpp @@ -0,0 +1,346 @@ +/* + * ebusd - daemon for communication with eBUS heating systems. + * Copyright (C) 2023 John Baier + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "lib/utils/arg.h" + +#include +#include +#include +#include +#include + +namespace ebusd { + +#define isAlpha(c) (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z')) + +void calcCounts(const argDef *argDefs, int &count, int &shortCharsCount, int &shortOptsCount) { + for (const argDef *arg = argDefs; arg && arg->help; arg++) { + if (!arg->name) { + continue; + } + count++; + if (!isAlpha(arg->key)) { + continue; + } + shortCharsCount++; + shortOptsCount++; + if (arg->valueName) { + shortOptsCount++; + if (arg->flags & af_optional) { + shortOptsCount++; + } + } + } +} + +void buildOpts(const argDef *argDefs, int &count, int &shortCharsCount, int &shortOptsCount, struct option *longOpts, char *shortChars, int *shortIndexes, char *shortOpts, int argDefIdx) { + struct option *opt = longOpts+count; + for (const argDef *arg = argDefs; arg && arg->help; arg++, argDefIdx++) { + if (!arg->name) { + continue; + } + opt->name = arg->name; + opt->has_arg = arg->valueName ? ((arg->flags & af_optional) ? optional_argument : required_argument) : no_argument; + opt->flag = nullptr; + opt->val = argDefIdx; + if (isAlpha(arg->key)) { + shortChars[shortCharsCount] = (char)arg->key; + shortIndexes[shortCharsCount++] = count; + shortOpts[shortOptsCount++] = (char)arg->key; + if (arg->valueName) { + shortOpts[shortOptsCount++] = ':'; + if (arg->flags & af_optional) { + shortOpts[shortOptsCount++] = ':'; + } + } + } + opt++; + count++; + } +} + +static const argDef endArgDef = {nullptr, 0, nullptr, 0, nullptr, 0 }; +static const argDef helpArgDef = {"help", '?', nullptr, 0, "Give this help list", 0 }; +static const argDef helpArgDefs[] = { + helpArgDef, + endArgDef +}; +static const argDef versionArgDef = {"version", 'V', nullptr, 0, "Print program version", 0 }; +static const argDef versionArgDefs[] = { + versionArgDef, + endArgDef +}; + +int argParse(const argParseOpt *parseOpt, int argc, char **argv, int *argIndex) { + int count = 0, shortCharsCount = 0, shortOptsCount = 0; + if (!(parseOpt->flags & af_noHelp)) { + calcCounts(helpArgDefs, count, shortCharsCount, shortOptsCount); + } + if (!(parseOpt->flags & af_noVersion)) { + calcCounts(versionArgDefs, count, shortCharsCount, shortOptsCount); + } + calcCounts(parseOpt->argDefs, count, shortCharsCount, shortOptsCount); + for (const argParseChildOpt *child = parseOpt->childOpts; child && child->argDefs; child++) { + calcCounts(child->argDefs, count, shortCharsCount, shortOptsCount); + } + struct option *longOpts = (struct option*)calloc(count+1, sizeof(struct option)); // room for EOF + char *shortChars = (char*)calloc(shortCharsCount+1, sizeof(char)); // room for \0 + int *shortIndexes = (int*)calloc(shortCharsCount, sizeof(int)); + char *shortOpts = (char*)calloc(2+shortOptsCount+1, sizeof(char)); // room for +, :, and \0 + count = 0; + shortCharsCount = 0; + shortOptsCount = 0; + shortOpts[shortOptsCount++] = '+'; // posix mode to stop at first non-option + shortOpts[shortOptsCount++] = ':'; // return ':' for missing option + if (!(parseOpt->flags & af_noHelp)) { + buildOpts(helpArgDefs, count, shortCharsCount, shortOptsCount, longOpts, shortChars, shortIndexes, shortOpts, 0xff00); + } + if (!(parseOpt->flags & af_noVersion)) { + buildOpts(versionArgDefs, count, shortCharsCount, shortOptsCount, longOpts, shortChars, shortIndexes, shortOpts, 0xff01); + } + buildOpts(parseOpt->argDefs, count, shortCharsCount, shortOptsCount, longOpts, shortChars, shortIndexes, shortOpts, 0); + int children = 0; + for (const argParseChildOpt *child = parseOpt->childOpts; child && child->argDefs; child++) { + buildOpts(child->argDefs, count, shortCharsCount, shortOptsCount, longOpts, shortChars, shortIndexes, shortOpts, 0x100*(++children)); + } + optind = 1; // setting to 0 does not work + int c = 0, longIdx = -1, ret = 0; + while ((c = getopt_long(argc, argv, shortOpts, longOpts, &longIdx)) != -1) { + if (c == '?') { + // unknown option or help + if (optopt != '?') { + ret = '!'; + fprintf(stderr, "invalid argument %s\n", argv[optind - 1]); + } else { + ret = c; + } + break; + } + if (c == ':') { + // missing option + fprintf(stderr, "missing argument to %s\n", argv[optind - 1]); + ret = c; + break; + } + if (isAlpha(c)) { + // short name + int idx = (int)(strchr(shortChars, c) - shortChars); + if (idx >= 0 && idx < shortCharsCount) { + longIdx = shortIndexes[idx]; + } else { + longIdx = -1; + } + } else if (c >= 0 && longIdx < 0) { + longIdx = c; + } + if (longIdx < 0 || longIdx >= count) { + ret = '!'; // error + break; + } + int val = longOpts[longIdx].val; + if (val == 0xff00) { // help + ret = '?'; + break; + } + if (val == 0xff01) { // version + ret = 'V'; + break; + } + const argDef *argDefs; + parse_function_t parser; + if (val & 0xff00) { + const argParseChildOpt *child = parseOpt->childOpts + ((val>>8)-1); + argDefs = child->argDefs; + parser = child->parser; + } else { + argDefs = parseOpt->argDefs; + parser = parseOpt->parser; + } + const argDef *arg = argDefs + (val & 0xff); + c = parser(arg->key, optarg, parseOpt); + if (c != 0) { + ret = c; + break; + } + } + if (ret == '?') { + argHelp(parseOpt); + } else if (argIndex && optind < argc) { + *argIndex = optind; + } + free(longOpts); + free(shortChars); + free(shortOpts); + return ret; +} + +#define MIN_INDENT 18 +#define MAX_INDENT 29 +#define MAX_BREAK 79 + +void wrap(const char* str, size_t pos, size_t indent) { + const char* end = strchr(str, 0); + const char* eol = strchr(str, '\n'); + char buf[MAX_BREAK + 1]; + bool first = true; + while (*str && str < end) { + if (!first) { + if (indent) { + printf("%*c", (int)indent, ' '); + } + pos = indent; + } + // start from max position backwards to find a break char + size_t cnt = MAX_BREAK - pos; + if (eol && eol < str) { + eol = strchr(str, '\n'); + } + if (eol && eol < str + cnt) { + // EOL is before latest possible break + cnt = eol - str; + } else if (end < str + cnt) { + cnt = end - str; + } + for (; cnt > 0; cnt--) { + char ch = str[cnt]; + if (ch == ' ' || ch == '\n' || ch == 0) { + // break found + buf[0] = 0; + strncat(buf, str, cnt); + printf("%s\n", buf); + str += cnt; + if (*str) { + str++; + } + break; // restart + } + } + if (cnt == 0 && *str) { + // final + printf("%s\n", str); + break; + } + first = false; + } +} + +size_t calcIndent(const argDef *argDefs) { + size_t indent = 0; + for (const argDef *arg = argDefs; arg && arg->help; arg++) { + if (!arg->name) { + continue; + } + // e.g. " -d, --device=DEV Use DEV..." + size_t length = 2 + 3 + 3 + strlen(arg->name) + 2; + if (arg->valueName) { + length += 1 + strlen(arg->valueName); + if (arg->flags & af_optional) { + length += 2; + } + } + if (length > indent) { + indent = length; + if (indent > MAX_INDENT) { + return indent; + } + } + } + return indent; +} + +void printArgs(const argDef *argDefs, size_t indent) { + for (const argDef *arg = argDefs; arg && arg->help; arg++) { + if (!arg->name) { + if (*arg->help) { + printf("\n %s\n", arg->help); + } else { + printf("\n"); + } + continue; + } + printf(" "); + if (isAlpha(arg->key) || arg->key == '?') { + printf("-%c,", arg->key); + } else { + printf(" "); + } + printf(" --%s", arg->name); + size_t taken = 2 + 3 + 3 + strlen(arg->name); + if (arg->valueName) { + taken += 1 + strlen(arg->valueName); + if (arg->flags & af_optional) { + printf("[=%s]", arg->valueName); + taken += 2; + } else { + printf("=%s", arg->valueName); + } + } + if (taken > indent) { + printf(" "); + wrap(arg->help, taken+1, indent); + } else { + printf("%*c", (int)(indent - taken), ' '); + wrap(arg->help, indent, indent); + } + } +} + +void argHelp(const argParseOpt *parseOpt) { + size_t indent = calcIndent(parseOpt->argDefs); + if (indent < MAX_INDENT) { + for (const argParseChildOpt *child = parseOpt->childOpts; child && child->argDefs; child++) { + size_t childIndent = calcIndent(child->argDefs); + if (childIndent > indent) { + indent = childIndent; + if (indent > MAX_INDENT) { + break; + } + } + } + } + if (indent > MAX_INDENT) { + indent = MAX_INDENT; + } else if (indent < MIN_INDENT) { + indent = MIN_INDENT; + } + printf("Usage: %s [OPTION...] %s\n", + parseOpt->name, + parseOpt->positional ? parseOpt->positional : "" + ); + wrap(parseOpt->help, 0, 0); + printArgs(parseOpt->argDefs, indent); + for (const argParseChildOpt *child = parseOpt->childOpts; child && child->argDefs; child++) { + printArgs(child->argDefs, indent); + } + if (!(parseOpt->flags & (af_noHelp|af_noVersion))) { + printf("\n"); + if (!(parseOpt->flags & af_noHelp)) { + printArgs(helpArgDefs, indent); + } + if (!(parseOpt->flags & af_noVersion)) { + printArgs(versionArgDefs, indent); + } + } + if (parseOpt->suffix) { + printf("\n"); + wrap(parseOpt->suffix, 0, 0); + } + fflush(stdout); +} + +} // namespace ebusd diff --git a/src/lib/utils/arg.h b/src/lib/utils/arg.h new file mode 100755 index 000000000..f8abb3d9d --- /dev/null +++ b/src/lib/utils/arg.h @@ -0,0 +1,97 @@ +/* + * ebusd - daemon for communication with eBUS heating systems. + * Copyright (C) 2023 John Baier + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LIB_UTILS_ARGS_H_ +#define LIB_UTILS_ARGS_H_ + +namespace ebusd { + +/** \file lib/utils/args.h */ + +/** the available arg flags. */ +enum ArgFlag { + af_optional = 1<<0, //!< optional argument value + af_noHelp = 1<<1, //!< do not include -?/--help option + af_noVersion = 1<<2, //!< do not include -V/--version option +}; + +/** Definition of a single argument. */ +typedef struct argDef { + const char* name; //!< the (long) name of the argument, or nullptr for a group header + int key; //!< the argument key, also used as short name if alphabetic or the question mark + const char* valueName; //!< the optional argument value name + int flags; //!< flags for the argument, bit combination of @a ArgFlag + const char* help; //!< help text (mandatory) + int unused; //!< currently unused (kept for compatibility to argp) +} argDef; + +struct argParseOpt; + +/** + * Function to be called for each argument. + * @param key the argument key as defined. + * @param arg the argument value, or nullptr. + * @param parseOpt ppointer to the @a argParseOpt structure. + * @return 0 on success, non-zero otherwise. + */ +typedef int (*parse_function_t)(int key, char *arg, const struct argParseOpt *parseOpt); + +/** Options for child definitions. */ +typedef struct argParseChildOpt { + const argDef *argDefs; //!< pointer to the argument defintions (last one needs to have nullptr help as end sign) + parse_function_t parser; //!< parse function to use +} argParseChildOpt; + +/** Options to pass to @a argParse(). */ +typedef struct argParseOpt { + const argDef *argDefs; //!< pointer to the argument defintions (last one needs to have nullptr help as end sign) + parse_function_t parser; //!< parse function to use + int flags; //!< flags for the parser, bit combination of @a ArgFlag + const char* name; //!< name of the program parsed + const char* positional; //!< help text for optional positional argument + const char* help; //!< help text for the program (second line of help output) + const char* suffix; //!< optional help suffix text + const argParseChildOpt *childOpts; //!< optional child definitions + void* userArg; //!< optional user argument +} argParseOpt; + +/** + * Parse the arguments given in @a argv. + * @param parseOpt pointer to the @a argParseOpt structure. + * @param argc the argument count (including the full program name in index 0). + * @param argv the argument values (including the full program name in index 0). + * @param argIndex optional pointer for storing the index to the first non-argument found in argv. + * @return 0 on success, '!' for an invalid argument value, ':' for a missing argument value, + * '?' when "-?" was given, or the result of the parse function if non-zero. + */ +int argParse(const argParseOpt *parseOpt, int argc, char **argv, int *argIndex); + +/** + * Print the help text. + * @param parseOpt ppointer to the @a argParseOpt structure. + */ +void argHelp(const argParseOpt *parseOpt); + +/** + * Convenience macro to print an error message to stderr. +*/ +#define argParseError(argParseOpt, message) fprintf(stderr, "%s\n", message); + +} // namespace ebusd + +#endif // LIB_UTILS_ARGS_H_ diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index 7c664e73a..e35b00dbf 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -1,3 +1,5 @@ +add_definitions(-Wno-unused-parameter) + set(ebusctl_SOURCES ebusctl.cpp) set(ebuspicloader_SOURCES ebuspicloader.cpp intelhex/intelhexclass.cpp) @@ -7,13 +9,13 @@ include_directories(intelhex) add_executable(ebusctl ${ebusctl_SOURCES}) add_executable(ebuspicloader ${ebuspicloader_SOURCES}) -target_link_libraries(ebusctl utils ebus ${LIB_ARGP} ${ebusctl_LIBS}) -target_link_libraries(ebuspicloader utils ${LIB_ARGP} ${ebuspicloader_LIBS}) +target_link_libraries(ebusctl utils ebus ${ebusctl_LIBS}) +target_link_libraries(ebuspicloader utils ${ebuspicloader_LIBS}) if(WITH_EBUSFEED) set(ebusfeed_SOURCES ebusfeed.cpp) add_executable(ebusfeed ${ebusfeed_SOURCES}) - target_link_libraries(ebusfeed ebus ${LIB_ARGP} ${ebusfeed_LIBS}) + target_link_libraries(ebusfeed utils ebus ${ebusfeed_LIBS}) endif(WITH_EBUSFEED) install(TARGETS ebusctl ebuspicloader EXPORT ebusd DESTINATION usr/bin) diff --git a/src/tools/ebusctl.cpp b/src/tools/ebusctl.cpp index 6f581c7e5..755c4bca6 100755 --- a/src/tools/ebusctl.cpp +++ b/src/tools/ebusctl.cpp @@ -20,7 +20,6 @@ # include #endif -#include #include #ifdef HAVE_PPOLL # include @@ -30,6 +29,7 @@ #include #include #include +#include "lib/utils/arg.h" #include "lib/utils/tcpsocket.h" namespace ebusd { @@ -62,24 +62,8 @@ static struct options opt = { 0 // argCount }; -/** the version string of the program. */ -const char *argp_program_version = "ebusctl of """ PACKAGE_STRING ""; - -/** the report bugs to address of the program. */ -const char *argp_program_bug_address = "" PACKAGE_BUGREPORT ""; - -/** the documentation of the program. */ -static const char argpdoc[] = - "Client for acessing " PACKAGE " via TCP.\n" - "\v" - "If given, send COMMAND together with CMDOPT options to " PACKAGE ".\n" - "Use 'help' as COMMAND for help on available " PACKAGE " commands."; - -/** the description of the accepted arguments. */ -static char argpargsdoc[] = "\nCOMMAND [CMDOPT...]"; - /** the definition of the known program arguments. */ -static const struct argp_option argpoptions[] = { +static const argDef argDefs[] = { {nullptr, 0, nullptr, 0, "Options:", 1 }, {"server", 's', "HOST", 0, "Connect to " PACKAGE " on HOST (name or IP) [localhost]", 0 }, {"port", 'p', "PORT", 0, "Connect to " PACKAGE " on PORT [8888]", 0 }, @@ -92,19 +76,19 @@ static const struct argp_option argpoptions[] = { /** * The program argument parsing function. - * @param key the key from @a argpoptions. + * @param key the key from @a argDefs. * @param arg the option argument, or nullptr. - * @param state the parsing state. + * @param parseOpt the parse options. */ -error_t parse_opt(int key, char *arg, struct argp_state *state) { - struct options *opt = (struct options*)state->input; +static int parse_opt(int key, char *arg, const argParseOpt *parseOpt) { + struct options *opt = (struct options*)parseOpt->userArg; char* strEnd = nullptr; unsigned int value; switch (key) { // Device settings: case 's': // --server=localhost if (arg == nullptr || arg[0] == 0) { - argp_error(state, "invalid server"); + argParseError(parseOpt, "invalid server"); return EINVAL; } opt->server = arg; @@ -112,7 +96,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { case 'p': // --port=8888 value = strtoul(arg, &strEnd, 10); if (strEnd == nullptr || strEnd == arg || *strEnd != 0 || value < 1 || value > 65535) { - argp_error(state, "invalid port"); + argParseError(parseOpt, "invalid port"); return EINVAL; } opt->port = (uint16_t)value; @@ -120,7 +104,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { case 't': // --timeout=10 value = strtoul(arg, &strEnd, 10); if (strEnd == nullptr || strEnd == arg || *strEnd != 0 || value > 3600) { - argp_error(state, "invalid timeout"); + argParseError(parseOpt, "invalid timeout"); return EINVAL; } opt->timeout = (uint16_t)value; @@ -128,12 +112,8 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { case 'e': // --error opt->errorResponse = true; break; - case ARGP_KEY_ARGS: - opt->args = state->argv + state->next; - opt->argCount = state->argc - state->next; - break; default: - return ARGP_ERR_UNKNOWN; + return ESRCH; } return 0; } @@ -362,11 +342,32 @@ bool connect(const char* host, uint16_t port, uint16_t timeout, char* const *arg * @return the exit code. */ int main(int argc, char* argv[]) { - struct argp argp = { argpoptions, parse_opt, argpargsdoc, argpdoc, nullptr, nullptr, nullptr }; - setenv("ARGP_HELP_FMT", "no-dup-args-note", 0); - if (argp_parse(&argp, argc, argv, ARGP_IN_ORDER, nullptr, &opt) != 0) { - return EINVAL; + argParseOpt parseOpt = { + argDefs, + parse_opt, + af_noVersion, + "ebusctl", + "[COMMAND [CMDOPT...]]", + "Client for accessing " PACKAGE " via TCP.", + "If given, send COMMAND together with CMDOPT options to " PACKAGE ".\n" + "Use 'help' as COMMAND for help on available " PACKAGE " commands.", + nullptr, + &opt + }; + int arg_index = -1; + switch (argParse(&parseOpt, argc, argv, &arg_index)) { + case 0: // OK + break; + case '?': // help printed + return 0; + default: + return EINVAL; } + if (arg_index >= 0) { + opt.args = argv + arg_index; + opt.argCount = argc - arg_index; + } + bool success = connect(opt.server, opt.port, opt.timeout, opt.args, opt.argCount); exit(success ? EXIT_SUCCESS : EXIT_FAILURE); diff --git a/src/tools/ebusfeed.cpp b/src/tools/ebusfeed.cpp index 5939184d9..40fd2ca94 100755 --- a/src/tools/ebusfeed.cpp +++ b/src/tools/ebusfeed.cpp @@ -20,7 +20,6 @@ # include #endif -#include #include #include #include @@ -29,6 +28,7 @@ #include #include "lib/ebus/device.h" #include "lib/ebus/result.h" +#include "lib/utils/arg.h" namespace ebusd { @@ -58,31 +58,8 @@ static struct options opt = { "/tmp/ebus_dump.bin", // dumpFile }; -/** the version string of the program. */ -const char *argp_program_version = "ebusfeed of """ PACKAGE_STRING ""; - -/** the report bugs to address of the program. */ -const char *argp_program_bug_address = "" PACKAGE_BUGREPORT ""; - -/** the documentation of the program. */ -static const char argpdoc[] = - "Feed data from an " PACKAGE " DUMPFILE to a serial device.\n" - "\v" - "With no DUMPFILE, /tmp/ebus_dump.bin is used.\n" - "\n" - "Example for setting up two pseudo terminals with 'socat':\n" - " 1. 'socat -d -d pty,raw,echo=0 pty,raw,echo=0'\n" - " 2. create symbol links to appropriate devices, e.g.\n" - " 'ln -s /dev/pts/2 /dev/ttyUSB60'\n" - " 'ln -s /dev/pts/3 /dev/ttyUSB20'\n" - " 3. start " PACKAGE ": '" PACKAGE " -f -d /dev/ttyUSB20 --nodevicecheck'\n" - " 4. start ebusfeed: 'ebusfeed /path/to/ebus_dump.bin'\n"; - -/** the description of the accepted arguments. */ -static char argpargsdoc[] = "[DUMPFILE]"; - /** the definition of the known program arguments. */ -static const struct argp_option argpoptions[] = { +static const ebusd::argDef argDefs[] = { {"device", 'd', "DEV", 0, "Write to DEV (serial device) [/dev/ttyUSB60]", 0 }, {"time", 't', "USEC", 0, "Delay each byte by USEC us [10000]", 0 }, @@ -91,18 +68,18 @@ static const struct argp_option argpoptions[] = { /** * The program argument parsing function. - * @param key the key from @a argpoptions. + * @param key the key from @a argDefs. * @param arg the option argument, or nullptr. - * @param state the parsing state. + * @param parseOpt the parse options. */ -error_t parse_opt(int key, char *arg, struct argp_state *state) { - struct options *opt = (struct options*)state->input; +static int parse_opt(int key, char *arg, const ebusd::argParseOpt *parseOpt) { + struct options *opt = (struct options*)parseOpt->userArg; char* strEnd = nullptr; switch (key) { // Device settings: case 'd': // --device=/dev/ttyUSB60 if (arg == nullptr || arg[0] == 0) { - argp_error(state, "invalid device"); + argParseError(parseOpt, "invalid device"); return EINVAL; } opt->device = arg; @@ -110,23 +87,12 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { case 't': // --time=10000 opt->time = (unsigned int)strtoul(arg, &strEnd, 10); if (strEnd == nullptr || strEnd == arg || *strEnd != 0 || opt->time < 1000 || opt->time > 100000000) { - argp_error(state, "invalid time"); + argParseError(parseOpt, "invalid time"); return EINVAL; } break; - case ARGP_KEY_ARG: - if (state->arg_num == 0) { - if (arg == nullptr || arg[0] == 0 || strcmp("/", arg) == 0) { - argp_error(state, "invalid dumpfile"); - return EINVAL; - } - opt->dumpFile = arg; - } else { - return ARGP_ERR_UNKNOWN; - } - break; default: - return ARGP_ERR_UNKNOWN; + return ESRCH; } return 0; } @@ -139,11 +105,45 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { * @return the exit code. */ int main(int argc, char* argv[]) { - struct argp argp = { argpoptions, parse_opt, argpargsdoc, argpdoc, nullptr, nullptr, nullptr }; - setenv("ARGP_HELP_FMT", "no-dup-args-note", 0); - if (argp_parse(&argp, argc, argv, ARGP_IN_ORDER, nullptr, &opt) != 0) { - return EINVAL; + ebusd::argParseOpt parseOpt = { + argDefs, + parse_opt, + "ebusfeed", + "[DUMPFILE]", + "Feed data from an " PACKAGE " DUMPFILE to a serial device.", + "With no DUMPFILE, /tmp/ebus_dump.bin is used.\n" + "\n" + "Example for setting up two pseudo terminals with 'socat':\n" + " 1. 'socat -d -d pty,raw,echo=0 pty,raw,echo=0'\n" + " 2. create symbol links to appropriate devices, e.g.\n" + " 'ln -s /dev/pts/2 /dev/ttyUSB60'\n" + " 'ln -s /dev/pts/3 /dev/ttyUSB20'\n" + " 3. start " PACKAGE ": '" PACKAGE " -f -d /dev/ttyUSB20 --nodevicecheck'\n" + " 4. start ebusfeed: 'ebusfeed /path/to/ebus_dump.bin'", + &opt + }; + int arg_index = -1; + switch (ebusd::argParse(&parseOpt, argc, argv, &arg_index)) { + case 0: // OK + break; + case '?': // help printed + return 0; + default: + return EINVAL; } + if (arg_index >= 0) { + if (argv[arg_index][0] == 0 || strcmp("/", argv[arg_index]) == 0) { + argParseError(parseOpt, "invalid dumpfile"); + return EINVAL; + } + if (arg_index != argc -1) { + // more than one arg + argParseError(parseOpt, "multiple dumpfile"); + return EINVAL; + } + opt.dumpFile = argv[arg_index]; + } + Device* device = Device::create(opt.device, false, false, false); if (device == nullptr) { cout << "unable to create device " << opt.device << endl; diff --git a/src/tools/ebuspicloader.cpp b/src/tools/ebuspicloader.cpp index a6829faac..5ce3cc72c 100644 --- a/src/tools/ebuspicloader.cpp +++ b/src/tools/ebuspicloader.cpp @@ -29,7 +29,6 @@ #include #include #include -#include #include #include #include @@ -37,29 +36,13 @@ #include #include #include "intelhex/intelhexclass.h" +#include "lib/utils/arg.h" #include "lib/utils/tcpsocket.h" using ebusd::socketConnect; -/** the version string of the program. */ -const char *argp_program_version = "eBUS adapter PIC firmware loader"; - -/** the documentation of the program. */ -static const char argpdoc[] = - "A tool for loading firmware to the eBUS adapter PIC and configure adjustable settings." - "\vPORT is either the serial port to use (e.g. " -#ifdef __CYGWIN__ - "/dev/ttyS0 for COM1 on Windows" -#else - "/dev/ttyUSB0" -#endif - ") that also supports a trailing wildcard '*' for testing" - " multiple ports, or a network port as \"ip:port\" for use with e.g. socat or ebusd-esp in PIC pass-through mode."; - -static const char argpargsdoc[] = "PORT"; - /** the definition of the known program arguments. */ -static const struct argp_option argpoptions[] = { +static const ebusd::argDef argDefs[] = { {nullptr, 0, nullptr, 0, "IP options:", 1 }, {"dhcp", 'd', nullptr, 0, "set dynamic IP address via DHCP (default)", 0 }, {"ip", 'i', "IP", 0, "set fix IP address (e.g. 192.168.0.10)", 0 }, @@ -82,7 +65,7 @@ static const struct argp_option argpoptions[] = { {nullptr, 0, nullptr, 0, "Tool options:", 9 }, {"verbose", 'v', nullptr, 0, "enable verbose output", 0 }, {"slow", 's', nullptr, 0, "low speed mode for transfer (115kBd instead of 921kBd)", 0 }, - {nullptr, 0, nullptr, 0, nullptr, 0 }, + {nullptr, 0, nullptr, 0, nullptr, 0 }, }; static bool verbose = false; @@ -136,7 +119,13 @@ bool parseShort(const char *arg, uint16_t minValue, uint16_t maxValue, uint16_t return true; } -error_t parse_opt(int key, char *arg, struct argp_state *state) { +/** + * The program argument parsing function. + * @param key the key from @a argDefs. + * @param arg the option argument, or nullptr. + * @param parseOpt the parse options. + */ +static int parse_opt(int key, char *arg, const ebusd::argParseOpt *parseOpt) { char *ip = nullptr, *part = nullptr; int pos = 0, sum = 0; struct stat st; @@ -147,22 +136,22 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { break; case 'd': // --dhcp if (setIp || setMask || setGateway) { - argp_error(state, "either DHCP or IP address is needed"); + argParseError(parseOpt, "either DHCP or IP address is needed"); return EINVAL; } setDhcp = true; break; case 'i': // --ip=192.168.0.10 if (arg == nullptr || arg[0] == 0) { - argp_error(state, "invalid IP address"); + argParseError(parseOpt, "invalid IP address"); return EINVAL; } if (setDhcp) { - argp_error(state, "either DHCP or IP address is needed"); + argParseError(parseOpt, "either DHCP or IP address is needed"); return EINVAL; } if (setIp) { - argp_error(state, "IP address was specified twice"); + argParseError(parseOpt, "IP address was specified twice"); return EINVAL; } ip = strdup(arg); @@ -177,41 +166,41 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { } free(ip); if (pos != 4 || part || sum == 0) { - argp_error(state, "invalid IP address"); + argParseError(parseOpt, "invalid IP address"); return EINVAL; } setIp = true; break; case 'm': // --mask=24 if (arg == nullptr || arg[0] == 0) { - argp_error(state, "invalid IP mask"); + argParseError(parseOpt, "invalid IP mask"); return EINVAL; } if (setDhcp) { - argp_error(state, "either DHCP or IP address is needed"); + argParseError(parseOpt, "either DHCP or IP address is needed"); return EINVAL; } if (setMask) { - argp_error(state, "mask was specified twice"); + argParseError(parseOpt, "mask was specified twice"); return EINVAL; } if (!parseByte(arg, 1, 0x1e, &setMaskLen)) { - argp_error(state, "invalid IP mask"); + argParseError(parseOpt, "invalid IP mask"); return EINVAL; } setMask = true; break; case 'g': // --gateway=192.168.0.11 if (arg == nullptr || arg[0] == 0) { - argp_error(state, "invalid gateway"); + argParseError(parseOpt, "invalid gateway"); return EINVAL; } if (setDhcp) { - argp_error(state, "either DHCP or IP address is needed"); + argParseError(parseOpt, "either DHCP or IP address is needed"); return EINVAL; } if (!setIp || !setMask) { - argp_error(state, "IP and mask need to be specified before gateway"); + argParseError(parseOpt, "IP and mask need to be specified before gateway"); return EINVAL; } ip = strdup(arg); @@ -228,7 +217,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { uint8_t maskRemain = setMaskLen-pos*8; uint8_t mask = maskRemain >= 8 ? 255 : maskRemain == 0 ? 0 : (255^((1 << (8 - maskRemain)) - 1)); if ((address & mask) != (setIpAddress[pos] & mask)) { - argp_error(state, "invalid gateway (different network)"); + argParseError(parseOpt, "invalid gateway (different network)"); free(ip); return EINVAL; } @@ -237,15 +226,15 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { } free(ip); if (pos != 4 || part || sum == 0 || setGatewayBits == 0) { - argp_error(state, "invalid gateway"); + argParseError(parseOpt, "invalid gateway"); return EINVAL; } if (setGatewayBits == hostBits) { - argp_error(state, "invalid gateway (same as address)"); + argParseError(parseOpt, "invalid gateway (same as address)"); return EINVAL; } if (!setGatewayBits || setGatewayBits == ((1 << (32 - setMaskLen)) - 1)) { - argp_error(state, "invalid gateway (net or broadcast address)"); + argParseError(parseOpt, "invalid gateway (net or broadcast address)"); return EINVAL; } if (setGatewayBits == 1) { // default @@ -260,7 +249,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { } if (!(setGatewayBits >> 5)) { if (!(setGatewayBits & 0x1f)) { - argp_error(state, "invalid gateway (net address)"); + argParseError(parseOpt, "invalid gateway (net address)"); return EINVAL; } // fine: host part above max gateway adjustable bits is the same and remainder non-zero @@ -274,7 +263,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { setGateway = true; break; } - argp_error(state, "invalid gateway (out of possible range of first/last 31 hosts in subnet)"); + argParseError(parseOpt, "invalid gateway (out of possible range of first/last 31 hosts in subnet)"); return EINVAL; case 'M': // --macip setMacFromIp = true; @@ -286,11 +275,11 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { break; case 'a': // --arbdel=1000 if (arg == nullptr || arg[0] == 0) { - argp_error(state, "invalid arbitration delay"); + argParseError(parseOpt, "invalid arbitration delay"); return EINVAL; } if (!parseShort(arg, 0, 620, &setArbitrationDelayMicros)) { - argp_error(state, "invalid arbitration delay"); + argParseError(parseOpt, "invalid arbitration delay"); return EINVAL; } setArbitrationDelay = true; @@ -305,7 +294,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { break; case -3: // --variant=U|W|E|F|N|u|w|e|f|n if (arg == nullptr || arg[0] == 0) { - argp_error(state, "invalid variant"); + argParseError(parseOpt, "invalid variant"); return EINVAL; } if (arg[0] == 'u' || arg[0] == 'U') { @@ -319,7 +308,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { } else if (arg[0] == 'n' || arg[0] == 'N') { setVariantValue = 0; } else { - argp_error(state, "invalid variant"); + argParseError(parseOpt, "invalid variant"); return EINVAL; } setVariantForced = arg[0]<'a'; @@ -327,7 +316,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { break; case 'f': // --flash=firmware.hex if (arg == nullptr || arg[0] == 0 || stat(arg, &st) != 0 || !S_ISREG(st.st_mode)) { - argp_error(state, "invalid flash file"); + argParseError(parseOpt, "invalid flash file"); return EINVAL; } flashFile = arg; @@ -339,7 +328,7 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) { lowSpeed = true; break; default: - return ARGP_ERR_UNKNOWN; + return ESRCH; } return 0; } @@ -1094,7 +1083,7 @@ int readSettings(int fd, uint8_t* currentData = nullptr) { } else { std::cout << "off" << std::endl; } - std::cout << "Variant: "; // since firmware 20221206 + std::cout << "Variant: "; // since firmware 20221206 switch (configData[5]&0x03) { case 3: std::cout << "USB/RPI (high-speed)"; @@ -1156,7 +1145,7 @@ bool writeSettings(int fd, uint8_t* currentData = nullptr) { if (setVariant) { configData[5] = (configData[5]&0x38) | (setVariantForced?0:0x04) | (setVariantValue&0x03); if (setVariantValue==0) { - configData[1] = (configData[1]&~0x1f); // set mask=0 to disable Ethernet + configData[1] = (configData[1]&~0x1f); // set mask=0 to disable Ethernet } } if (writeConfig(fd, 0x0000, 8, configData) != 0) { @@ -1170,13 +1159,32 @@ bool writeSettings(int fd, uint8_t* currentData = nullptr) { int run(int fd); int main(int argc, char* argv[]) { - struct argp aargp = { argpoptions, parse_opt, argpargsdoc, argpdoc, nullptr, nullptr, nullptr }; + ebusd::argParseOpt parseOpt = { + argDefs, + parse_opt, + ebusd::af_noVersion, + "ebuspicloader", + "[PORT]", + "A tool for loading firmware to the eBUS adapter PIC and configure adjustable settings.", + "PORT is either the serial port to use (e.g. " +#ifdef __CYGWIN__ + "/dev/ttyS0 for COM1 on Windows" +#else + "/dev/ttyUSB0" +#endif + ") that also supports a trailing wildcard '*' for testing" + " multiple ports, or a network port as \"ip:port\" for use with e.g. socat or ebusd-esp in PIC pass-through mode.", + nullptr, + nullptr + }; int arg_index = -1; - setenv("ARGP_HELP_FMT", "no-dup-args-note", 0); - - if (argp_parse(&aargp, argc, argv, ARGP_IN_ORDER, &arg_index, nullptr) != 0) { - std::cerr << "invalid arguments" << std::endl; - exit(EXIT_FAILURE); + switch (ebusd::argParse(&parseOpt, argc, argv, &arg_index)) { + case 0: // OK + break; + case '?': // help printed + return 0; + default: + return EINVAL; } if (setIp != setMask || (setMacFromIp && !setIp)) { @@ -1188,7 +1196,7 @@ int main(int argc, char* argv[]) { printFileChecksum(); exit(EXIT_SUCCESS); } else { - argp_help(&aargp, stderr, ARGP_HELP_STD_ERR, const_cast("ebuspicloader")); + ebusd::argHelp(&parseOpt); exit(EXIT_FAILURE); } } From acc6eddbba75d5a941a589b72a3a39708242c8d8 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 1 Oct 2023 14:29:11 +0200 Subject: [PATCH 128/345] fix previous commit, formatting --- src/ebusd/mainloop.cpp | 5 +++-- src/lib/utils/Makefile.am | 1 + src/lib/utils/arg.cpp | 18 +++++++++++------- src/lib/utils/arg.h | 6 +++--- src/tools/ebuspicloader.cpp | 8 ++++---- 5 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index c457431ee..48e83c4f7 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -208,7 +208,7 @@ MainLoop::~MainLoop() { void MainLoop::shutdown() { m_shutdown = true; if (m_requestQueue != nullptr) { - m_requestQueue->push(nullptr); // just to notify potentially waiting thread + m_requestQueue->push(nullptr); // just to notify potentially waiting thread } } @@ -314,7 +314,8 @@ void MainLoop::run() { if (scanCompleted > SCAN_REPEAT_COUNT) { // repeat failed scan only every Nth time scanCompleted = 0; scanRetry++; - logNotice(lf_main, "scan completed %d time(s), %s", scanRetry, scanRetry <= m_scanRetries ? "check again" : "end"); + logNotice(lf_main, "scan completed %d time(s), %s", scanRetry, + scanRetry <= m_scanRetries ? "check again" : "end"); } } else { m_scanStatus = SCAN_STATUS_RUNNING; diff --git a/src/lib/utils/Makefile.am b/src/lib/utils/Makefile.am index b48146025..a8a9e070e 100755 --- a/src/lib/utils/Makefile.am +++ b/src/lib/utils/Makefile.am @@ -5,6 +5,7 @@ AM_CXXFLAGS = -I$(top_srcdir)/src \ noinst_LIBRARIES = libutils.a libutils_a_SOURCES = \ + arg.h arg.cpp \ log.h log.cpp \ tcpsocket.h tcpsocket.cpp \ thread.h thread.cpp \ diff --git a/src/lib/utils/arg.cpp b/src/lib/utils/arg.cpp index f6ae7712b..4e9683de7 100755 --- a/src/lib/utils/arg.cpp +++ b/src/lib/utils/arg.cpp @@ -48,7 +48,8 @@ void calcCounts(const argDef *argDefs, int &count, int &shortCharsCount, int &sh } } -void buildOpts(const argDef *argDefs, int &count, int &shortCharsCount, int &shortOptsCount, struct option *longOpts, char *shortChars, int *shortIndexes, char *shortOpts, int argDefIdx) { +void buildOpts(const argDef *argDefs, int &count, int &shortCharsCount, int &shortOptsCount, + struct option *longOpts, char *shortChars, int *shortIndexes, char *shortOpts, int argDefIdx) { struct option *opt = longOpts+count; for (const argDef *arg = argDefs; arg && arg->help; arg++, argDefIdx++) { if (!arg->name) { @@ -108,15 +109,19 @@ int argParse(const argParseOpt *parseOpt, int argc, char **argv, int *argIndex) shortOpts[shortOptsCount++] = '+'; // posix mode to stop at first non-option shortOpts[shortOptsCount++] = ':'; // return ':' for missing option if (!(parseOpt->flags & af_noHelp)) { - buildOpts(helpArgDefs, count, shortCharsCount, shortOptsCount, longOpts, shortChars, shortIndexes, shortOpts, 0xff00); + buildOpts(helpArgDefs, count, shortCharsCount, shortOptsCount, longOpts, shortChars, + shortIndexes, shortOpts, 0xff00); } if (!(parseOpt->flags & af_noVersion)) { - buildOpts(versionArgDefs, count, shortCharsCount, shortOptsCount, longOpts, shortChars, shortIndexes, shortOpts, 0xff01); + buildOpts(versionArgDefs, count, shortCharsCount, shortOptsCount, longOpts, shortChars, + shortIndexes, shortOpts, 0xff01); } - buildOpts(parseOpt->argDefs, count, shortCharsCount, shortOptsCount, longOpts, shortChars, shortIndexes, shortOpts, 0); + buildOpts(parseOpt->argDefs, count, shortCharsCount, shortOptsCount, longOpts, shortChars, + shortIndexes, shortOpts, 0); int children = 0; for (const argParseChildOpt *child = parseOpt->childOpts; child && child->argDefs; child++) { - buildOpts(child->argDefs, count, shortCharsCount, shortOptsCount, longOpts, shortChars, shortIndexes, shortOpts, 0x100*(++children)); + buildOpts(child->argDefs, count, shortCharsCount, shortOptsCount, longOpts, shortChars, + shortIndexes, shortOpts, 0x100*(++children)); } optind = 1; // setting to 0 does not work int c = 0, longIdx = -1, ret = 0; @@ -320,8 +325,7 @@ void argHelp(const argParseOpt *parseOpt) { } printf("Usage: %s [OPTION...] %s\n", parseOpt->name, - parseOpt->positional ? parseOpt->positional : "" - ); + parseOpt->positional ? parseOpt->positional : ""); wrap(parseOpt->help, 0, 0); printArgs(parseOpt->argDefs, indent); for (const argParseChildOpt *child = parseOpt->childOpts; child && child->argDefs; child++) { diff --git a/src/lib/utils/arg.h b/src/lib/utils/arg.h index f8abb3d9d..4a43c6fe1 100755 --- a/src/lib/utils/arg.h +++ b/src/lib/utils/arg.h @@ -16,8 +16,8 @@ * along with this program. If not, see . */ -#ifndef LIB_UTILS_ARGS_H_ -#define LIB_UTILS_ARGS_H_ +#ifndef LIB_UTILS_ARG_H_ +#define LIB_UTILS_ARG_H_ namespace ebusd { @@ -94,4 +94,4 @@ void argHelp(const argParseOpt *parseOpt); } // namespace ebusd -#endif // LIB_UTILS_ARGS_H_ +#endif // LIB_UTILS_ARG_H_ diff --git a/src/tools/ebuspicloader.cpp b/src/tools/ebuspicloader.cpp index 5ce3cc72c..35d27bc63 100644 --- a/src/tools/ebuspicloader.cpp +++ b/src/tools/ebuspicloader.cpp @@ -1053,7 +1053,7 @@ int readSettings(int fd, uint8_t* currentData = nullptr) { // non-mask bits outside of |gw reach uint8_t mask = maskLen <= 24 ? 0 : (255^((1 << (8 - (maskLen-24))) - 1)); ip[3] |= ((~mask)^0x1f) | (gw&0x1f); - if (maskLen<24) { + if (maskLen < 24) { // more than just the last IP byte are affected: set non-mask bits to 1 as well in bytes 0-2 for (uint8_t pos = 0, maskRemain = maskLen; pos < 3; pos++, maskRemain -= maskRemain >= 8 ? 8 : maskRemain) { mask = maskRemain >= 8 ? 255 : maskRemain == 0 ? 0 : (255^((1 << (8 - maskRemain)) - 1)); @@ -1107,7 +1107,7 @@ int readSettings(int fd, uint8_t* currentData = nullptr) { } else { std::cout << ", ignore hardware jumpers"; } - if ((configData[5]&0x07)==0x07) { + if ((configData[5]&0x07) == 0x07) { std::cout << " (default)"; } std::cout << std::endl; @@ -1144,7 +1144,7 @@ bool writeSettings(int fd, uint8_t* currentData = nullptr) { } if (setVariant) { configData[5] = (configData[5]&0x38) | (setVariantForced?0:0x04) | (setVariantValue&0x03); - if (setVariantValue==0) { + if (setVariantValue == 0) { configData[1] = (configData[1]&~0x1f); // set mask=0 to disable Ethernet } } @@ -1191,7 +1191,7 @@ int main(int argc, char* argv[]) { std::cerr << "incomplete IP arguments" << std::endl; arg_index = argc; // force help output } - if (argc-arg_index < 1) { + if (arg_index < 0 || argc-arg_index < 1) { if (flashFile) { printFileChecksum(); exit(EXIT_SUCCESS); From c71b1daf743af8d08a6cae7cbd6cfd677de56dcd Mon Sep 17 00:00:00 2001 From: John Date: Sun, 1 Oct 2023 14:54:27 +0200 Subject: [PATCH 129/345] remove unused param to arg parser --- src/ebusd/knxhandler.cpp | 14 ++--- src/ebusd/main.cpp | 112 ++++++++++++++++++------------------ src/ebusd/mqtthandler.cpp | 48 ++++++++-------- src/lib/utils/arg.cpp | 6 +- src/lib/utils/arg.h | 1 - src/tools/Makefile.am | 3 +- src/tools/ebusctl.cpp | 12 ++-- src/tools/ebusfeed.cpp | 6 +- src/tools/ebuspicloader.cpp | 38 ++++++------ 9 files changed, 120 insertions(+), 120 deletions(-) diff --git a/src/ebusd/knxhandler.cpp b/src/ebusd/knxhandler.cpp index 2f7b52846..15eba41e2 100644 --- a/src/ebusd/knxhandler.cpp +++ b/src/ebusd/knxhandler.cpp @@ -53,20 +53,20 @@ using std::dec; /** the definition of the KNX arguments. */ static const argDef g_knx_argDefs[] = { - {nullptr, 0, nullptr, 0, "KNX options:", 1 }, + {nullptr, 0, nullptr, 0, "KNX options:"}, {"knxurl", O_URL, "URL", af_optional, "URL to open (i.e. \"[multicast][@interface]\" for KNXnet/IP" #ifdef HAVE_KNXD " or \"ip:host[:port]\" / \"local:/socketpath\" for knxd" #endif - ") (no default)", 0 }, + ") (no default)"}, {"knxrage", O_AGR, "SEC", 0, "Maximum age in seconds for using the last value of read messages (0=disable)" - " [5]", 0 }, + " [5]"}, {"knxwage", O_AGW, "SEC", 0, "Maximum age in seconds for using the last value for reads on write messages" - " (0=disable), [99999999]", 0 }, - {"knxint", O_INT, "FILE", 0, "Read KNX integration settings from FILE [/etc/ebusd/knx.cfg]", 0 }, - {"knxvar", O_VAR, "NAME=VALUE[,...]", 0, "Add variable(s) to the read KNX integration settings", 0 }, + " (0=disable), [99999999]"}, + {"knxint", O_INT, "FILE", 0, "Read KNX integration settings from FILE [/etc/ebusd/knx.cfg]"}, + {"knxvar", O_VAR, "NAME=VALUE[,...]", 0, "Add variable(s) to the read KNX integration settings"}, - {nullptr, 0, nullptr, 0, nullptr, 0 }, + {nullptr, 0, nullptr, 0, nullptr}, }; static const char* g_url = nullptr; //!< URL of KNX daemon diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index 3790a69ae..3e5f74f58 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -177,21 +177,21 @@ static bool s_scanConfigOrPathSet = false; /** the definition of the known program arguments. */ static const argDef argDefs[] = { - {nullptr, 0, nullptr, 0, "Device options:", 1 }, + {nullptr, 0, nullptr, 0, "Device options:"}, {"device", 'd', "DEV", 0, "Use DEV as eBUS device (" "prefix \"ens:\" for enhanced high speed device or " "\"enh:\" for enhanced device, with " "\"IP:PORT\" for network device or " "\"DEVICE\" for serial device" - ") [/dev/ttyUSB0]", 0 }, - {"nodevicecheck", 'n', nullptr, 0, "Skip serial eBUS device test", 0 }, - {"readonly", 'r', nullptr, 0, "Only read from device, never write to it", 0 }, - {"initsend", O_INISND, nullptr, 0, "Send an initial escape symbol after connecting device", 0 }, - {"latency", O_DEVLAT, "MSEC", 0, "Extra transfer latency in ms [0]", 0 }, + ") [/dev/ttyUSB0]"}, + {"nodevicecheck", 'n', nullptr, 0, "Skip serial eBUS device test"}, + {"readonly", 'r', nullptr, 0, "Only read from device, never write to it"}, + {"initsend", O_INISND, nullptr, 0, "Send an initial escape symbol after connecting device"}, + {"latency", O_DEVLAT, "MSEC", 0, "Extra transfer latency in ms [0]"}, - {nullptr, 0, nullptr, 0, "Message configuration options:", 2 }, + {nullptr, 0, nullptr, 0, "Message configuration options:"}, {"configpath", 'c', "PATH", 0, "Read CSV config files from PATH (local folder or HTTPS URL) [" - CONFIG_PATH "]", 0 }, + CONFIG_PATH "]"}, {"scanconfig", 's', "ADDR", af_optional, "Pick CSV config files matching initial scan ADDR: " "empty for broadcast ident message (default when configpath is not given), " "\"none\" for no initial scan message, " @@ -199,69 +199,69 @@ static const argDef argDefs[] = { "a single hex address to scan, or " "\"off\" for not picking CSV files by scan result (default when configpath is given).\n" "If combined with --checkconfig, you can add scan message data as " - "arguments for checking a particular scan configuration, e.g. \"FF08070400/0AB5454850303003277201\".", 0 }, - {"scanretries", O_SCNRET, "COUNT", 0, "Retry scanning devices COUNT times [5]", 0 }, + "arguments for checking a particular scan configuration, e.g. \"FF08070400/0AB5454850303003277201\"."}, + {"scanretries", O_SCNRET, "COUNT", 0, "Retry scanning devices COUNT times [5]"}, {"configlang", O_CFGLNG, "LANG", 0, - "Prefer LANG in multilingual configuration files [system default language]", 0 }, - {"checkconfig", O_CHKCFG, nullptr, 0, "Check config files, then stop", 0 }, + "Prefer LANG in multilingual configuration files [system default language]"}, + {"checkconfig", O_CHKCFG, nullptr, 0, "Check config files, then stop"}, {"dumpconfig", O_DMPCFG, "FORMAT", af_optional, - "Check and dump config files in FORMAT (\"json\" or \"csv\"), then stop", 0 }, - {"dumpconfigto", O_DMPCTO, "FILE", 0, "Dump config files to FILE", 0 }, - {"pollinterval", O_POLINT, "SEC", 0, "Poll for data every SEC seconds (0=disable) [5]", 0 }, + "Check and dump config files in FORMAT (\"json\" or \"csv\"), then stop"}, + {"dumpconfigto", O_DMPCTO, "FILE", 0, "Dump config files to FILE"}, + {"pollinterval", O_POLINT, "SEC", 0, "Poll for data every SEC seconds (0=disable) [5]"}, {"inject", 'i', "stop", af_optional, "Inject remaining arguments as already seen messages (e.g. " - "\"FF08070400/0AB5454850303003277201\"), optionally stop afterwards", 0 }, + "\"FF08070400/0AB5454850303003277201\"), optionally stop afterwards"}, #ifdef HAVE_SSL {"cafile", O_CAFILE, "FILE", 0, "Use CA FILE for checking certificates (uses defaults," - " \"#\" for insecure)", 0 }, - {"capath", O_CAPATH, "PATH", 0, "Use CA PATH for checking certificates (uses defaults)", 0 }, + " \"#\" for insecure)"}, + {"capath", O_CAPATH, "PATH", 0, "Use CA PATH for checking certificates (uses defaults)"}, #endif // HAVE_SSL - {nullptr, 0, nullptr, 0, "eBUS options:", 3 }, - {"address", 'a', "ADDR", 0, "Use hex ADDR as own master bus address [31]", 0 }, - {"answer", O_ANSWER, nullptr, 0, "Actively answer to requests from other masters", 0 }, - {"acquiretimeout", O_ACQTIM, "MSEC", 0, "Stop bus acquisition after MSEC ms [10]", 0 }, - {"acquireretries", O_ACQRET, "COUNT", 0, "Retry bus acquisition COUNT times [3]", 0 }, - {"sendretries", O_SNDRET, "COUNT", 0, "Repeat failed sends COUNT times [2]", 0 }, - {"receivetimeout", O_RCVTIM, "MSEC", 0, "Expect a slave to answer within MSEC ms [25]", 0 }, - {"numbermasters", O_MASCNT, "COUNT", 0, "Expect COUNT masters on the bus, 0 for auto detection [0]", 0 }, - {"generatesyn", O_GENSYN, nullptr, 0, "Enable AUTO-SYN symbol generation", 0 }, - - {nullptr, 0, nullptr, 0, "Daemon options:", 4 }, - {"accesslevel", O_ACLDEF, "LEVEL", 0, "Set default access level to LEVEL (\"*\" for everything) [\"\"]", 0 }, - {"aclfile", O_ACLFIL, "FILE", 0, "Read access control list from FILE", 0 }, - {"foreground", 'f', nullptr, 0, "Run in foreground", 0 }, - {"enablehex", O_HEXCMD, nullptr, 0, "Enable hex command", 0 }, - {"enabledefine", O_DEFCMD, nullptr, 0, "Enable define command", 0 }, - {"pidfile", O_PIDFIL, "FILE", 0, "PID file name (only for daemon) [" PACKAGE_PIDFILE "]", 0 }, - {"port", 'p', "PORT", 0, "Listen for command line connections on PORT [8888]", 0 }, - {"localhost", O_LOCAL, nullptr, 0, "Listen for command line connections on 127.0.0.1 interface only", 0 }, - {"httpport", O_HTTPPT, "PORT", 0, "Listen for HTTP connections on PORT, 0 to disable [0]", 0 }, - {"htmlpath", O_HTMLPA, "PATH", 0, "Path for HTML files served by HTTP port [/var/ebusd/html]", 0 }, - {"updatecheck", O_UPDCHK, "MODE", 0, "Set automatic update check to MODE (on|off) [on]", 0 }, - - {nullptr, 0, nullptr, 0, "Log options:", 5 }, + {nullptr, 0, nullptr, 0, "eBUS options:"}, + {"address", 'a', "ADDR", 0, "Use hex ADDR as own master bus address [31]"}, + {"answer", O_ANSWER, nullptr, 0, "Actively answer to requests from other masters"}, + {"acquiretimeout", O_ACQTIM, "MSEC", 0, "Stop bus acquisition after MSEC ms [10]"}, + {"acquireretries", O_ACQRET, "COUNT", 0, "Retry bus acquisition COUNT times [3]"}, + {"sendretries", O_SNDRET, "COUNT", 0, "Repeat failed sends COUNT times [2]"}, + {"receivetimeout", O_RCVTIM, "MSEC", 0, "Expect a slave to answer within MSEC ms [25]"}, + {"numbermasters", O_MASCNT, "COUNT", 0, "Expect COUNT masters on the bus, 0 for auto detection [0]"}, + {"generatesyn", O_GENSYN, nullptr, 0, "Enable AUTO-SYN symbol generation"}, + + {nullptr, 0, nullptr, 0, "Daemon options:"}, + {"accesslevel", O_ACLDEF, "LEVEL", 0, "Set default access level to LEVEL (\"*\" for everything) [\"\"]"}, + {"aclfile", O_ACLFIL, "FILE", 0, "Read access control list from FILE"}, + {"foreground", 'f', nullptr, 0, "Run in foreground"}, + {"enablehex", O_HEXCMD, nullptr, 0, "Enable hex command"}, + {"enabledefine", O_DEFCMD, nullptr, 0, "Enable define command"}, + {"pidfile", O_PIDFIL, "FILE", 0, "PID file name (only for daemon) [" PACKAGE_PIDFILE "]"}, + {"port", 'p', "PORT", 0, "Listen for command line connections on PORT [8888]"}, + {"localhost", O_LOCAL, nullptr, 0, "Listen for command line connections on 127.0.0.1 interface only"}, + {"httpport", O_HTTPPT, "PORT", 0, "Listen for HTTP connections on PORT, 0 to disable [0]"}, + {"htmlpath", O_HTMLPA, "PATH", 0, "Path for HTML files served by HTTP port [/var/ebusd/html]"}, + {"updatecheck", O_UPDCHK, "MODE", 0, "Set automatic update check to MODE (on|off) [on]"}, + + {nullptr, 0, nullptr, 0, "Log options:"}, {"logfile", 'l', "FILE", 0, "Write log to FILE (only for daemon, empty string for using syslog) [" - PACKAGE_LOGFILE "]", 0 }, + PACKAGE_LOGFILE "]"}, {"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 }, + " (alternative to --logareas/--logevel, may be used multiple times) [all:notice]"}, {"logareas", O_LOGARE, "AREAS", 0, "Only write log for matching AREA(S): main|network|bus|update|other" - "|all [all]", 0 }, + "|all [all]"}, {"loglevel", O_LOGLEV, "LEVEL", 0, "Only write log below or equal to LEVEL: error|notice|info|debug" - " [notice]", 0 }, + " [notice]"}, - {nullptr, 0, nullptr, 0, "Raw logging options:", 6 }, + {nullptr, 0, nullptr, 0, "Raw logging options:"}, {"lograwdata", O_RAW, "bytes", af_optional, - "Log messages or all received/sent bytes on the bus", 0 }, - {"lograwdatafile", O_RAWFIL, "FILE", 0, "Write raw log to FILE [" PACKAGE_LOGFILE "]", 0 }, - {"lograwdatasize", O_RAWSIZ, "SIZE", 0, "Make raw log file no larger than SIZE kB [100]", 0 }, + "Log messages or all received/sent bytes on the bus"}, + {"lograwdatafile", O_RAWFIL, "FILE", 0, "Write raw log to FILE [" PACKAGE_LOGFILE "]"}, + {"lograwdatasize", O_RAWSIZ, "SIZE", 0, "Make raw log file no larger than SIZE kB [100]"}, - {nullptr, 0, nullptr, 0, "Binary dump options:", 7 }, - {"dump", 'D', nullptr, 0, "Enable binary dump of received bytes", 0 }, - {"dumpfile", O_DMPFIL, "FILE", 0, "Dump received bytes to FILE [/tmp/" PACKAGE "_dump.bin]", 0 }, - {"dumpsize", O_DMPSIZ, "SIZE", 0, "Make dump file no larger than SIZE kB [100]", 0 }, - {"dumpflush", O_DMPFLU, nullptr, 0, "Flush each byte", 0 }, + {nullptr, 0, nullptr, 0, "Binary dump options:"}, + {"dump", 'D', nullptr, 0, "Enable binary dump of received bytes"}, + {"dumpfile", O_DMPFIL, "FILE", 0, "Dump received bytes to FILE [/tmp/" PACKAGE "_dump.bin]"}, + {"dumpsize", O_DMPSIZ, "SIZE", 0, "Make dump file no larger than SIZE kB [100]"}, + {"dumpflush", O_DMPFLU, nullptr, 0, "Flush each byte"}, - {nullptr, 0, nullptr, 0, nullptr, 0 }, + {nullptr, 0, nullptr, 0, nullptr}, }; /** diff --git a/src/ebusd/mqtthandler.cpp b/src/ebusd/mqtthandler.cpp index 02014c5d3..f2e75b7bf 100755 --- a/src/ebusd/mqtthandler.cpp +++ b/src/ebusd/mqtthandler.cpp @@ -57,43 +57,43 @@ using std::dec; /** the definition of the MQTT arguments. */ static const argDef g_mqtt_argDefs[] = { - {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 }, + {nullptr, 0, nullptr, 0, "MQTT options:"}, + {"mqtthost", O_HOST, "HOST", 0, "Connect to MQTT broker on HOST [localhost]"}, + {"mqttport", O_PORT, "PORT", 0, "Connect to MQTT broker on PORT (usually 1883), 0 to disable [0]"}, {"mqttclientid", O_CLID, "ID", 0, "Set client ID for connection to MQTT broker [" PACKAGE_NAME "_" - PACKAGE_VERSION "_]", 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 }, + PACKAGE_VERSION "_]"}, + {"mqttuser", O_USER, "USER", 0, "Connect as USER to MQTT broker (no default)"}, + {"mqttpass", O_PASS, "PASSWORD", 0, "Use PASSWORD when connecting to MQTT broker (no default)"}, {"mqtttopic", O_TOPI, "TOPIC", 0, - "Use MQTT TOPIC (prefix before /%circuit/%name or complete format) [ebusd]", 0 }, + "Use MQTT TOPIC (prefix before /%circuit/%name or complete format) [ebusd]"}, {"mqttglobal", O_GTOP, "TOPIC", 0, - "Use TOPIC for global data (default is \"global/\" suffix to mqtttopic prefix)", 0 }, - {"mqttretain", O_RETA, nullptr, 0, "Retain all topics instead of only selected global ones", 0 }, - {"mqttqos", O_PQOS, "QOS", 0, "Set the QoS value for all topics (0-2) [0]", 0 }, - {"mqttint", O_INTF, "FILE", 0, "Read MQTT integration settings from FILE (no default)", 0 }, - {"mqttvar", O_IVAR, "NAME=VALUE[,...]", 0, "Add variable(s) to the read MQTT integration settings", 0 }, + "Use TOPIC for global data (default is \"global/\" suffix to mqtttopic prefix)"}, + {"mqttretain", O_RETA, nullptr, 0, "Retain all topics instead of only selected global ones"}, + {"mqttqos", O_PQOS, "QOS", 0, "Set the QoS value for all topics (0-2) [0]"}, + {"mqttint", O_INTF, "FILE", 0, "Read MQTT integration settings from FILE (no default)"}, + {"mqttvar", O_IVAR, "NAME=VALUE[,...]", 0, "Add variable(s) to the read MQTT integration settings"}, {"mqttjson", O_JSON, "short", af_optional, - "Publish in JSON format instead of strings, optionally in short (value directly below field key)", 0 }, - {"mqttverbose", O_VERB, nullptr, 0, "Publish all available attributes", 0 }, + "Publish in JSON format instead of strings, optionally in short (value directly below field key)"}, + {"mqttverbose", O_VERB, nullptr, 0, "Publish all available attributes"}, #if (LIBMOSQUITTO_VERSION_NUMBER >= 1003001) - {"mqttlog", O_LOGL, nullptr, 0, "Log library events", 0 }, + {"mqttlog", O_LOGL, nullptr, 0, "Log library events"}, #endif #if (LIBMOSQUITTO_VERSION_NUMBER >= 1004001) - {"mqttversion", O_VERS, "VERSION", 0, "Use protocol VERSION [3.1]", 0 }, + {"mqttversion", O_VERS, "VERSION", 0, "Use protocol VERSION [3.1]"}, #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 }, + "Ignore invalid parameters during init (e.g. for DNS not resolvable yet)"}, + {"mqttchanges", O_CHGS, nullptr, 0, "Whether to only publish changed messages instead of all received"}, #if (LIBMOSQUITTO_MAJOR >= 1) - {"mqttca", O_CAFI, "CA", 0, "Use CA file or dir (ending with '/') for MQTT TLS (no default)", 0 }, - {"mqttcert", O_CERT, "CERTFILE", 0, "Use CERTFILE for MQTT TLS client certificate (no default)", 0 }, - {"mqttkey", O_KEYF, "KEYFILE", 0, "Use KEYFILE for MQTT TLS client certificate (no default)", 0 }, - {"mqttkeypass", O_KEPA, "PASSWORD", 0, "Use PASSWORD for the encrypted KEYFILE (no default)", 0 }, - {"mqttinsecure", O_INSE, nullptr, 0, "Allow insecure TLS connection (e.g. using a self signed certificate)", 0 }, + {"mqttca", O_CAFI, "CA", 0, "Use CA file or dir (ending with '/') for MQTT TLS (no default)"}, + {"mqttcert", O_CERT, "CERTFILE", 0, "Use CERTFILE for MQTT TLS client certificate (no default)"}, + {"mqttkey", O_KEYF, "KEYFILE", 0, "Use KEYFILE for MQTT TLS client certificate (no default)"}, + {"mqttkeypass", O_KEPA, "PASSWORD", 0, "Use PASSWORD for the encrypted KEYFILE (no default)"}, + {"mqttinsecure", O_INSE, nullptr, 0, "Allow insecure TLS connection (e.g. using a self signed certificate)"}, #endif - {nullptr, 0, nullptr, 0, nullptr, 0 }, + {nullptr, 0, nullptr, 0, nullptr}, }; static const char* g_host = "localhost"; //!< host name of MQTT broker [localhost] diff --git a/src/lib/utils/arg.cpp b/src/lib/utils/arg.cpp index 4e9683de7..86b816681 100755 --- a/src/lib/utils/arg.cpp +++ b/src/lib/utils/arg.cpp @@ -75,13 +75,13 @@ void buildOpts(const argDef *argDefs, int &count, int &shortCharsCount, int &sho } } -static const argDef endArgDef = {nullptr, 0, nullptr, 0, nullptr, 0 }; -static const argDef helpArgDef = {"help", '?', nullptr, 0, "Give this help list", 0 }; +static const argDef endArgDef = {nullptr, 0, nullptr, 0, nullptr}; +static const argDef helpArgDef = {"help", '?', nullptr, 0, "Give this help list"}; static const argDef helpArgDefs[] = { helpArgDef, endArgDef }; -static const argDef versionArgDef = {"version", 'V', nullptr, 0, "Print program version", 0 }; +static const argDef versionArgDef = {"version", 'V', nullptr, 0, "Print program version"}; static const argDef versionArgDefs[] = { versionArgDef, endArgDef diff --git a/src/lib/utils/arg.h b/src/lib/utils/arg.h index 4a43c6fe1..5bc8963f2 100755 --- a/src/lib/utils/arg.h +++ b/src/lib/utils/arg.h @@ -37,7 +37,6 @@ typedef struct argDef { const char* valueName; //!< the optional argument value name int flags; //!< flags for the argument, bit combination of @a ArgFlag const char* help; //!< help text (mandatory) - int unused; //!< currently unused (kept for compatibility to argp) } argDef; struct argParseOpt; diff --git a/src/tools/Makefile.am b/src/tools/Makefile.am index 820489b8f..f9388e091 100644 --- a/src/tools/Makefile.am +++ b/src/tools/Makefile.am @@ -1,5 +1,6 @@ AM_CXXFLAGS = -I$(top_srcdir)/src \ - -isystem$(top_srcdir) + -isystem$(top_srcdir) \ + -Wno-unused-parameter bin_PROGRAMS = ebusctl \ ebuspicloader diff --git a/src/tools/ebusctl.cpp b/src/tools/ebusctl.cpp index 755c4bca6..42d8ad22c 100755 --- a/src/tools/ebusctl.cpp +++ b/src/tools/ebusctl.cpp @@ -64,14 +64,14 @@ static struct options opt = { /** the definition of the known program arguments. */ static const argDef argDefs[] = { - {nullptr, 0, nullptr, 0, "Options:", 1 }, - {"server", 's', "HOST", 0, "Connect to " PACKAGE " on HOST (name or IP) [localhost]", 0 }, - {"port", 'p', "PORT", 0, "Connect to " PACKAGE " on PORT [8888]", 0 }, + {nullptr, 0, nullptr, 0, "Options:"}, + {"server", 's', "HOST", 0, "Connect to " PACKAGE " on HOST (name or IP) [localhost]"}, + {"port", 'p', "PORT", 0, "Connect to " PACKAGE " on PORT [8888]"}, {"timeout", 't', "SECS", 0, "Timeout for connecting to/receiving from " PACKAGE - ", 0 for none [60]", 0 }, - {"error", 'e', nullptr, 0, "Exit non-zero if the connection was fine but the response indicates non-success", 0}, + ", 0 for none [60]"}, + {"error", 'e', nullptr, 0, "Exit non-zero if the connection was fine but the response indicates non-success"}, - {nullptr, 0, nullptr, 0, nullptr, 0 }, + {nullptr, 0, nullptr, 0, nullptr}, }; /** diff --git a/src/tools/ebusfeed.cpp b/src/tools/ebusfeed.cpp index 40fd2ca94..03cf903b2 100755 --- a/src/tools/ebusfeed.cpp +++ b/src/tools/ebusfeed.cpp @@ -60,10 +60,10 @@ static struct options opt = { /** the definition of the known program arguments. */ static const ebusd::argDef argDefs[] = { - {"device", 'd', "DEV", 0, "Write to DEV (serial device) [/dev/ttyUSB60]", 0 }, - {"time", 't', "USEC", 0, "Delay each byte by USEC us [10000]", 0 }, + {"device", 'd', "DEV", 0, "Write to DEV (serial device) [/dev/ttyUSB60]"}, + {"time", 't', "USEC", 0, "Delay each byte by USEC us [10000]"}, - {nullptr, 0, nullptr, 0, nullptr, 0 }, + {nullptr, 0, nullptr, 0, nullptr}, }; /** diff --git a/src/tools/ebuspicloader.cpp b/src/tools/ebuspicloader.cpp index 35d27bc63..36dae377b 100644 --- a/src/tools/ebuspicloader.cpp +++ b/src/tools/ebuspicloader.cpp @@ -43,29 +43,29 @@ using ebusd::socketConnect; /** the definition of the known program arguments. */ static const ebusd::argDef argDefs[] = { - {nullptr, 0, nullptr, 0, "IP options:", 1 }, - {"dhcp", 'd', nullptr, 0, "set dynamic IP address via DHCP (default)", 0 }, - {"ip", 'i', "IP", 0, "set fix IP address (e.g. 192.168.0.10)", 0 }, - {"mask", 'm', "MASK", 0, "set fix IP mask (e.g. 24)", 0 }, - {"gateway", 'g', "GW", 0, "set fix IP gateway to GW (if necessary and other than net address + 1)", 0 }, - {"macip", 'M', nullptr, 0, "set the MAC address suffix from the IP address", 0 }, - {"macid", 'N', nullptr, 0, "set the MAC address suffix from internal ID (default)", 0 }, - {nullptr, 0, nullptr, 0, "eBUS options:", 2 }, + {nullptr, 0, nullptr, 0, "IP options:"}, + {"dhcp", 'd', nullptr, 0, "set dynamic IP address via DHCP (default)"}, + {"ip", 'i', "IP", 0, "set fix IP address (e.g. 192.168.0.10)"}, + {"mask", 'm', "MASK", 0, "set fix IP mask (e.g. 24)"}, + {"gateway", 'g', "GW", 0, "set fix IP gateway to GW (if necessary and other than net address + 1)"}, + {"macip", 'M', nullptr, 0, "set the MAC address suffix from the IP address"}, + {"macid", 'N', nullptr, 0, "set the MAC address suffix from internal ID (default)"}, + {nullptr, 0, nullptr, 0, "eBUS options:"}, {"arbdel", 'a', "US", 0, "set arbitration delay to US microseconds (0-620 in steps of 10, default 200" - ", since firmware 20211128)", 0 }, - {nullptr, 0, nullptr, 0, "PIC options:", 3 }, - {"pingon", 'p', nullptr, 0, "enable visual ping (default)", 0 }, - {"pingoff", 'o', nullptr, 0, "disable visual ping", 0 }, + ", since firmware 20211128)"}, + {nullptr, 0, nullptr, 0, "PIC options:"}, + {"pingon", 'p', nullptr, 0, "enable visual ping (default)"}, + {"pingoff", 'o', nullptr, 0, "disable visual ping"}, {"variant", -3, "VARIANT", 0, "set the VARIANT to U=USB/RPI (high-speed), W=WIFI, E=Ethernet," " N=non-enhanced USB/RPI/WIFI, F=non-enhanced Ethernet" " (lowercase to allow hardware jumpers, default \"u\"" - ", since firmware 20221206)", 0 }, - {"flash", 'f', "FILE", 0, "flash the FILE to the device", 0 }, - {"reset", 'r', nullptr, 0, "reset the device at the end on success", 0 }, - {nullptr, 0, nullptr, 0, "Tool options:", 9 }, - {"verbose", 'v', nullptr, 0, "enable verbose output", 0 }, - {"slow", 's', nullptr, 0, "low speed mode for transfer (115kBd instead of 921kBd)", 0 }, - {nullptr, 0, nullptr, 0, nullptr, 0 }, + ", since firmware 20221206)"}, + {"flash", 'f', "FILE", 0, "flash the FILE to the device"}, + {"reset", 'r', nullptr, 0, "reset the device at the end on success"}, + {nullptr, 0, nullptr, 0, "Tool options:"}, + {"verbose", 'v', nullptr, 0, "enable verbose output"}, + {"slow", 's', nullptr, 0, "low speed mode for transfer (115kBd instead of 921kBd)"}, + {nullptr, 0, nullptr, 0, nullptr}, }; static bool verbose = false; From d9f1bc58f3bcd0afc62c99f00174462fd138cdd8 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 1 Oct 2023 18:24:12 +0200 Subject: [PATCH 130/345] fix building ebusfeed --- src/lib/ebus/CMakeLists.txt | 1 + src/tools/ebusfeed.cpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/lib/ebus/CMakeLists.txt b/src/lib/ebus/CMakeLists.txt index cf69c5e38..22b89d0c1 100644 --- a/src/lib/ebus/CMakeLists.txt +++ b/src/lib/ebus/CMakeLists.txt @@ -16,6 +16,7 @@ if(HAVE_CONTRIB) endif(HAVE_CONTRIB) add_library(ebus ${libebus_a_SOURCES}) +target_link_libraries(ebus utils ${libebus_a_LIBS}) if(BUILD_TESTING) add_subdirectory(test) diff --git a/src/tools/ebusfeed.cpp b/src/tools/ebusfeed.cpp index 03cf903b2..f423611f0 100755 --- a/src/tools/ebusfeed.cpp +++ b/src/tools/ebusfeed.cpp @@ -108,6 +108,7 @@ int main(int argc, char* argv[]) { ebusd::argParseOpt parseOpt = { argDefs, parse_opt, + af_noVersion, "ebusfeed", "[DUMPFILE]", "Feed data from an " PACKAGE " DUMPFILE to a serial device.", @@ -120,6 +121,7 @@ int main(int argc, char* argv[]) { " 'ln -s /dev/pts/3 /dev/ttyUSB20'\n" " 3. start " PACKAGE ": '" PACKAGE " -f -d /dev/ttyUSB20 --nodevicecheck'\n" " 4. start ebusfeed: 'ebusfeed /path/to/ebus_dump.bin'", + nullptr, &opt }; int arg_index = -1; From 5f493255eee3383581bf5db4cb63fa1935a6a88b Mon Sep 17 00:00:00 2001 From: John Date: Sun, 1 Oct 2023 22:31:50 +0200 Subject: [PATCH 131/345] prepare logging without timestamp --- src/lib/utils/log.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/utils/log.cpp b/src/lib/utils/log.cpp index 9db2c46ad..8601c8a75 100755 --- a/src/lib/utils/log.cpp +++ b/src/lib/utils/log.cpp @@ -212,6 +212,9 @@ void logWrite(const char* facility, const LogLevel level, const char* message, v syslog(s_syslogLevels[level], "[%s %s] %s", facility, s_levelNames[level], buf); } else { #endif +#ifdef LOG_WITHOUT_TIMEPREFIX + fprintf(s_logFile, "[%s %s] %s\n", +#else struct timespec ts; struct tm td; clockGettime(&ts); @@ -219,6 +222,7 @@ void logWrite(const char* facility, const LogLevel level, const char* message, v 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, +#endif facility, s_levelNames[level], buf); fflush(s_logFile); #ifdef HAVE_SYSLOG_H From cb0bfe75ff1a3510274b3eae2e0156f1372dcb12 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 1 Oct 2023 22:33:15 +0200 Subject: [PATCH 132/345] fix timeout --- src/ebusd/knxhandler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ebusd/knxhandler.cpp b/src/ebusd/knxhandler.cpp index 15eba41e2..3a4113e2d 100644 --- a/src/ebusd/knxhandler.cpp +++ b/src/ebusd/knxhandler.cpp @@ -451,7 +451,7 @@ result_t KnxHandler::receiveTelegram(int maxlen, knx_transfer_t* typ, uint8_t *b knx_addr_t *src, knx_addr_t *dest, bool wait) { struct timespec tdiff = { .tv_sec = wait ? 2 : 0, // 2 seconds when waiting - .tv_nsec = wait ? 0 : 1000, // 1 milliseond when not waiting + .tv_nsec = wait ? 0 : 1000000, // 1 millisecond when not waiting }; if (!m_con->isConnected()) { return RESULT_ERR_GENERIC_IO; From fe49e9b67fbf0dd9474374ee5e63059e4d92718d Mon Sep 17 00:00:00 2001 From: John Date: Sun, 1 Oct 2023 23:30:40 +0200 Subject: [PATCH 133/345] separate arg parsing --- src/ebusd/CMakeLists.txt | 2 +- src/ebusd/Makefile.am | 3 +- src/ebusd/main.cpp | 668 +------------------------------------ src/ebusd/main.h | 17 + src/ebusd/main_args.cpp | 695 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 726 insertions(+), 659 deletions(-) mode change 100644 => 100755 src/ebusd/main.h create mode 100755 src/ebusd/main_args.cpp diff --git a/src/ebusd/CMakeLists.txt b/src/ebusd/CMakeLists.txt index 85b41e308..db96bcb41 100644 --- a/src/ebusd/CMakeLists.txt +++ b/src/ebusd/CMakeLists.txt @@ -7,7 +7,7 @@ set(ebusd_SOURCES network.h network.cpp mainloop.h mainloop.cpp scan.h scan.cpp - main.h main.cpp + main.h main.cpp main_args.cpp ) if(HAVE_MQTT) diff --git a/src/ebusd/Makefile.am b/src/ebusd/Makefile.am index 673ef653c..ee169cf6b 100644 --- a/src/ebusd/Makefile.am +++ b/src/ebusd/Makefile.am @@ -11,8 +11,7 @@ ebusd_SOURCES = \ network.h network.cpp \ mainloop.h mainloop.cpp \ scan.h scan.cpp \ - main.h main.cpp - + main.h main.cpp main_args.cpp if MQTT ebusd_SOURCES += mqtthandler.cpp mqtthandler.h endif diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index 3e5f74f58..2269a5709 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -31,7 +31,6 @@ #include #include "ebusd/mainloop.h" #include "ebusd/network.h" -#include "lib/utils/arg.h" #include "lib/utils/log.h" #include "lib/utils/httpclient.h" #include "ebusd/scan.h" @@ -45,78 +44,14 @@ using std::setw; using std::nouppercase; using std::cout; -/** the config path part behind the scheme (scheme without "://"). */ -#define CONFIG_PATH_SUFFIX "://cfg.ebusd.eu/" - /** the previous config path part to rewrite to the current one. */ #define PREVIOUS_CONFIG_PATH_SUFFIX "://ebusd.eu/config/" -/** the default path of the configuration files. */ -#ifdef HAVE_SSL -#define CONFIG_PATH "https" CONFIG_PATH_SUFFIX -#else -#define CONFIG_PATH "http" CONFIG_PATH_SUFFIX -#endif - /** the opened PID file, or nullptr. */ static FILE* s_pidFile = nullptr; /** the program options. */ -static struct options s_opt = { - "/dev/ttyUSB0", // device - false, // noDeviceCheck - false, // readOnly - false, // initialSend - 0, // extraLatency - - false, // scanConfig - 0, // initialScan - 5, // scanRetries - getenv("LANG"), // preferLanguage - false, // checkConfig - OF_NONE, // dumpConfig - nullptr, // dumpConfigTo - 5, // pollInterval - false, // injectMessages - false, // stopAfterInject - nullptr, // caFile - nullptr, // caPath - - 0x31, // address - false, // answer - 10, // acquireTimeout - 3, // acquireRetries - 2, // sendRetries - SLAVE_RECV_TIMEOUT*5/3, // receiveTimeout - 0, // masterCount - false, // generateSyn - - "", // accessLevel - "", // aclFile - false, // foreground - false, // enableHex - false, // enableDefine - PACKAGE_PIDFILE, // pidFile - 8888, // port - false, // localOnly - 0, // httpPort - "/var/" PACKAGE "/html", // htmlPath - true, // updateCheck - - PACKAGE_LOGFILE, // logFile - -1, // logAreas - ll_COUNT, // logLevel - false, // multiLog - - 0, // logRaw - PACKAGE_LOGFILE, // logRawFile - 100, // logRawSize - - false, // dump - "/tmp/" PACKAGE "_dump.bin", // dumpFile - 100, // dumpSize - false, // dumpFlush -}; +static struct options s_opt; /** the @a MessageMap instance, or nullptr. */ static MessageMap* s_messageMap = nullptr; @@ -133,525 +68,6 @@ static MainLoop* s_mainLoop = nullptr; /** the @a Network instance, or nullptr. */ static Network* s_network = nullptr; -/** the (optionally corrected) config path for retrieving configuration files from. */ -static string s_configPath = CONFIG_PATH; - -/** whether scanConfig or configPath were set by arguments. */ -static bool s_scanConfigOrPathSet = false; - -#define O_INISND -2 -#define O_DEVLAT (O_INISND-1) -#define O_SCNRET (O_DEVLAT-1) -#define O_CFGLNG (O_SCNRET-1) -#define O_CHKCFG (O_CFGLNG-1) -#define O_DMPCFG (O_CHKCFG-1) -#define O_DMPCTO (O_DMPCFG-1) -#define O_POLINT (O_DMPCTO-1) -#define O_CAFILE (O_POLINT-1) -#define O_CAPATH (O_CAFILE-1) -#define O_ANSWER (O_CAPATH-1) -#define O_ACQTIM (O_ANSWER-1) -#define O_ACQRET (O_ACQTIM-1) -#define O_SNDRET (O_ACQRET-1) -#define O_RCVTIM (O_SNDRET-1) -#define O_MASCNT (O_RCVTIM-1) -#define O_GENSYN (O_MASCNT-1) -#define O_ACLDEF (O_GENSYN-1) -#define O_ACLFIL (O_ACLDEF-1) -#define O_HEXCMD (O_ACLFIL-1) -#define O_DEFCMD (O_HEXCMD-1) -#define O_PIDFIL (O_DEFCMD-1) -#define O_LOCAL (O_PIDFIL-1) -#define O_HTTPPT (O_LOCAL-1) -#define O_HTMLPA (O_HTTPPT-1) -#define O_UPDCHK (O_HTMLPA-1) -#define O_LOG (O_UPDCHK-1) -#define O_LOGARE (O_LOG-1) -#define O_LOGLEV (O_LOGARE-1) -#define O_RAW (O_LOGLEV-1) -#define O_RAWFIL (O_RAW-1) -#define O_RAWSIZ (O_RAWFIL-1) -#define O_DMPFIL (O_RAWSIZ-1) -#define O_DMPSIZ (O_DMPFIL-1) -#define O_DMPFLU (O_DMPSIZ-1) - -/** the definition of the known program arguments. */ -static const argDef argDefs[] = { - {nullptr, 0, nullptr, 0, "Device options:"}, - {"device", 'd', "DEV", 0, "Use DEV as eBUS device (" - "prefix \"ens:\" for enhanced high speed device or " - "\"enh:\" for enhanced device, with " - "\"IP:PORT\" for network device or " - "\"DEVICE\" for serial device" - ") [/dev/ttyUSB0]"}, - {"nodevicecheck", 'n', nullptr, 0, "Skip serial eBUS device test"}, - {"readonly", 'r', nullptr, 0, "Only read from device, never write to it"}, - {"initsend", O_INISND, nullptr, 0, "Send an initial escape symbol after connecting device"}, - {"latency", O_DEVLAT, "MSEC", 0, "Extra transfer latency in ms [0]"}, - - {nullptr, 0, nullptr, 0, "Message configuration options:"}, - {"configpath", 'c', "PATH", 0, "Read CSV config files from PATH (local folder or HTTPS URL) [" - CONFIG_PATH "]"}, - {"scanconfig", 's', "ADDR", af_optional, "Pick CSV config files matching initial scan ADDR: " - "empty for broadcast ident message (default when configpath is not given), " - "\"none\" for no initial scan message, " - "\"full\" for full scan, " - "a single hex address to scan, or " - "\"off\" for not picking CSV files by scan result (default when configpath is given).\n" - "If combined with --checkconfig, you can add scan message data as " - "arguments for checking a particular scan configuration, e.g. \"FF08070400/0AB5454850303003277201\"."}, - {"scanretries", O_SCNRET, "COUNT", 0, "Retry scanning devices COUNT times [5]"}, - {"configlang", O_CFGLNG, "LANG", 0, - "Prefer LANG in multilingual configuration files [system default language]"}, - {"checkconfig", O_CHKCFG, nullptr, 0, "Check config files, then stop"}, - {"dumpconfig", O_DMPCFG, "FORMAT", af_optional, - "Check and dump config files in FORMAT (\"json\" or \"csv\"), then stop"}, - {"dumpconfigto", O_DMPCTO, "FILE", 0, "Dump config files to FILE"}, - {"pollinterval", O_POLINT, "SEC", 0, "Poll for data every SEC seconds (0=disable) [5]"}, - {"inject", 'i', "stop", af_optional, "Inject remaining arguments as already seen messages (e.g. " - "\"FF08070400/0AB5454850303003277201\"), optionally stop afterwards"}, -#ifdef HAVE_SSL - {"cafile", O_CAFILE, "FILE", 0, "Use CA FILE for checking certificates (uses defaults," - " \"#\" for insecure)"}, - {"capath", O_CAPATH, "PATH", 0, "Use CA PATH for checking certificates (uses defaults)"}, -#endif // HAVE_SSL - - {nullptr, 0, nullptr, 0, "eBUS options:"}, - {"address", 'a', "ADDR", 0, "Use hex ADDR as own master bus address [31]"}, - {"answer", O_ANSWER, nullptr, 0, "Actively answer to requests from other masters"}, - {"acquiretimeout", O_ACQTIM, "MSEC", 0, "Stop bus acquisition after MSEC ms [10]"}, - {"acquireretries", O_ACQRET, "COUNT", 0, "Retry bus acquisition COUNT times [3]"}, - {"sendretries", O_SNDRET, "COUNT", 0, "Repeat failed sends COUNT times [2]"}, - {"receivetimeout", O_RCVTIM, "MSEC", 0, "Expect a slave to answer within MSEC ms [25]"}, - {"numbermasters", O_MASCNT, "COUNT", 0, "Expect COUNT masters on the bus, 0 for auto detection [0]"}, - {"generatesyn", O_GENSYN, nullptr, 0, "Enable AUTO-SYN symbol generation"}, - - {nullptr, 0, nullptr, 0, "Daemon options:"}, - {"accesslevel", O_ACLDEF, "LEVEL", 0, "Set default access level to LEVEL (\"*\" for everything) [\"\"]"}, - {"aclfile", O_ACLFIL, "FILE", 0, "Read access control list from FILE"}, - {"foreground", 'f', nullptr, 0, "Run in foreground"}, - {"enablehex", O_HEXCMD, nullptr, 0, "Enable hex command"}, - {"enabledefine", O_DEFCMD, nullptr, 0, "Enable define command"}, - {"pidfile", O_PIDFIL, "FILE", 0, "PID file name (only for daemon) [" PACKAGE_PIDFILE "]"}, - {"port", 'p', "PORT", 0, "Listen for command line connections on PORT [8888]"}, - {"localhost", O_LOCAL, nullptr, 0, "Listen for command line connections on 127.0.0.1 interface only"}, - {"httpport", O_HTTPPT, "PORT", 0, "Listen for HTTP connections on PORT, 0 to disable [0]"}, - {"htmlpath", O_HTMLPA, "PATH", 0, "Path for HTML files served by HTTP port [/var/ebusd/html]"}, - {"updatecheck", O_UPDCHK, "MODE", 0, "Set automatic update check to MODE (on|off) [on]"}, - - {nullptr, 0, nullptr, 0, "Log options:"}, - {"logfile", 'l', "FILE", 0, "Write log to FILE (only for daemon, empty string for using syslog) [" - PACKAGE_LOGFILE "]"}, - {"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]"}, - {"logareas", O_LOGARE, "AREAS", 0, "Only write log for matching AREA(S): main|network|bus|update|other" - "|all [all]"}, - {"loglevel", O_LOGLEV, "LEVEL", 0, "Only write log below or equal to LEVEL: error|notice|info|debug" - " [notice]"}, - - {nullptr, 0, nullptr, 0, "Raw logging options:"}, - {"lograwdata", O_RAW, "bytes", af_optional, - "Log messages or all received/sent bytes on the bus"}, - {"lograwdatafile", O_RAWFIL, "FILE", 0, "Write raw log to FILE [" PACKAGE_LOGFILE "]"}, - {"lograwdatasize", O_RAWSIZ, "SIZE", 0, "Make raw log file no larger than SIZE kB [100]"}, - - {nullptr, 0, nullptr, 0, "Binary dump options:"}, - {"dump", 'D', nullptr, 0, "Enable binary dump of received bytes"}, - {"dumpfile", O_DMPFIL, "FILE", 0, "Dump received bytes to FILE [/tmp/" PACKAGE "_dump.bin]"}, - {"dumpsize", O_DMPSIZ, "SIZE", 0, "Make dump file no larger than SIZE kB [100]"}, - {"dumpflush", O_DMPFLU, nullptr, 0, "Flush each byte"}, - - {nullptr, 0, nullptr, 0, nullptr}, -}; - -/** - * The program argument parsing function. - * @param key the key from @a argDefs. - * @param arg the option argument, or nullptr. - * @param parseOpt the parse options. - */ -static int parse_opt(int key, char *arg, const argParseOpt *parseOpt) { - struct options *opt = (struct options*)parseOpt->userArg; - result_t result = RESULT_OK; - unsigned int value; - - switch (key) { - // Device options: - case 'd': // --device=/dev/ttyUSB0 - if (arg == nullptr || arg[0] == 0) { - argParseError(parseOpt, "invalid device"); - return EINVAL; - } - opt->device = arg; - break; - case 'n': // --nodevicecheck - opt->noDeviceCheck = true; - break; - case 'r': // --readonly - opt->readOnly = true; - break; - case O_INISND: // --initsend - opt->initialSend = true; - break; - case O_DEVLAT: // --latency=10 - value = parseInt(arg, 10, 0, 200000, &result); // backwards compatible (micros) - if (result != RESULT_OK || (value <= 1000 && value > 200)) { // backwards compatible (micros) - argParseError(parseOpt, "invalid latency"); - return EINVAL; - } - opt->extraLatency = value > 1000 ? value/1000 : value; // backwards compatible (micros) - break; - - // Message configuration options: - case 'c': // --configpath=https://cfg.ebusd.eu/ - if (arg == nullptr || arg[0] == 0 || strcmp("/", arg) == 0) { - argParseError(parseOpt, "invalid configpath"); - return EINVAL; - } - s_configPath = arg; - s_scanConfigOrPathSet = true; - break; - case 's': // --scanconfig[=ADDR] (ADDR=|none|full||off) - { - symbol_t initialScan = 0; - if (!arg || arg[0] == 0) { - initialScan = BROADCAST; // default for no or empty argument - } else if (strcmp("none", arg) == 0) { - initialScan = ESC; - } else if (strcmp("full", arg) == 0) { - initialScan = SYN; - } else if (strcmp("off", arg) == 0) { - // zero turns scanConfig off - } else { - auto address = (symbol_t)parseInt(arg, 16, 0x00, 0xff, &result); - if (result != RESULT_OK || !isValidAddress(address)) { - argParseError(parseOpt, "invalid initial scan address"); - return EINVAL; - } - if (isMaster(address)) { - initialScan = getSlaveAddress(address); - } else { - initialScan = address; - } - } - opt->scanConfig = initialScan != 0; - opt->initialScan = initialScan; - s_scanConfigOrPathSet = true; - break; - } - case O_SCNRET: // --scanretries=10 - value = parseInt(arg, 10, 0, 100, &result); - if (result != RESULT_OK) { - argParseError(parseOpt, "invalid scanretries"); - return EINVAL; - } - opt->scanRetries = value; - break; - case O_CFGLNG: // --configlang=LANG - opt->preferLanguage = arg; - break; - case O_CHKCFG: // --checkconfig - opt->checkConfig = true; - break; - case O_DMPCFG: // --dumpconfig[=json|csv] - if (!arg || arg[0] == 0 || strcmp("csv", arg) == 0) { - // no further flags - opt->dumpConfig = OF_DEFINITION; - } else if (strcmp("json", arg) == 0) { - opt->dumpConfig = OF_DEFINITION | OF_NAMES | OF_UNITS | OF_COMMENTS | OF_VALUENAME | OF_ALL_ATTRS | OF_JSON; - } else { - argParseError(parseOpt, "invalid dumpconfig"); - return EINVAL; - } - opt->checkConfig = true; - break; - case O_DMPCTO: // --dumpconfigto=FILE - if (!arg || arg[0] == 0) { - argParseError(parseOpt, "invalid dumpconfigto"); - return EINVAL; - } - opt->dumpConfigTo = arg; - break; - case O_POLINT: // --pollinterval=5 - value = parseInt(arg, 10, 0, 3600, &result); - if (result != RESULT_OK) { - argParseError(parseOpt, "invalid pollinterval"); - return EINVAL; - } - opt->pollInterval = value; - break; - case 'i': // --inject[=stop] - opt->injectMessages = true; - opt->stopAfterInject = arg && strcmp("stop", arg) == 0; - break; - case O_CAFILE: // --cafile=FILE - opt->caFile = arg; - break; - case O_CAPATH: // --capath=PATH - opt->caPath = arg; - break; - - // eBUS options: - case 'a': // --address=31 - { - auto address = (symbol_t)parseInt(arg, 16, 0, 0xff, &result); - if (result != RESULT_OK || !isMaster(address)) { - argParseError(parseOpt, "invalid address"); - return EINVAL; - } - opt->address = address; - break; - } - case O_ANSWER: // --answer - opt->answer = true; - break; - case O_ACQTIM: // --acquiretimeout=10 - value = parseInt(arg, 10, 1, 100000, &result); // backwards compatible (micros) - if (result != RESULT_OK || (value <= 1000 && value > 100)) { // backwards compatible (micros) - argParseError(parseOpt, "invalid acquiretimeout"); - return EINVAL; - } - opt->acquireTimeout = value > 1000 ? value/1000 : value; // backwards compatible (micros) - break; - case O_ACQRET: // --acquireretries=3 - value = parseInt(arg, 10, 0, 10, &result); - if (result != RESULT_OK) { - argParseError(parseOpt, "invalid acquireretries"); - return EINVAL; - } - opt->acquireRetries = value; - break; - case O_SNDRET: // --sendretries=2 - value = parseInt(arg, 10, 0, 10, &result); - if (result != RESULT_OK) { - argParseError(parseOpt, "invalid sendretries"); - return EINVAL; - } - opt->sendRetries = value; - break; - case O_RCVTIM: // --receivetimeout=25 - value = parseInt(arg, 10, 1, 100000, &result); // backwards compatible (micros) - if (result != RESULT_OK || (value <= 1000 && value > 100)) { // backwards compatible (micros) - argParseError(parseOpt, "invalid receivetimeout"); - return EINVAL; - } - opt->receiveTimeout = value > 1000 ? value/1000 : value; // backwards compatible (micros) - break; - case O_MASCNT: // --numbermasters=0 - value = parseInt(arg, 10, 0, 25, &result); - if (result != RESULT_OK) { - argParseError(parseOpt, "invalid numbermasters"); - return EINVAL; - } - opt->masterCount = value; - break; - case O_GENSYN: // --generatesyn - opt->generateSyn = true; - break; - - // Daemon options: - case O_ACLDEF: // --accesslevel=* - if (arg == nullptr) { - argParseError(parseOpt, "invalid accesslevel"); - return EINVAL; - } - opt->accessLevel = arg; - break; - case O_ACLFIL: // --aclfile=/etc/ebusd/acl - if (arg == nullptr || arg[0] == 0 || strcmp("/", arg) == 0) { - argParseError(parseOpt, "invalid aclfile"); - return EINVAL; - } - opt->aclFile = arg; - break; - case 'f': // --foreground - opt->foreground = true; - break; - case O_HEXCMD: // --enablehex - opt->enableHex = true; - break; - case O_DEFCMD: // --enabledefine - opt->enableDefine = true; - break; - case O_PIDFIL: // --pidfile=/var/run/ebusd.pid - if (arg == nullptr || arg[0] == 0 || strcmp("/", arg) == 0) { - argParseError(parseOpt, "invalid pidfile"); - return EINVAL; - } - opt->pidFile = arg; - break; - case 'p': // --port=8888 - value = parseInt(arg, 10, 1, 65535, &result); - if (result != RESULT_OK) { - argParseError(parseOpt, "invalid port"); - return EINVAL; - } - opt->port = (uint16_t)value; - break; - case O_LOCAL: // --localhost - opt->localOnly = true; - break; - case O_HTTPPT: // --httpport=0 - value = parseInt(arg, 10, 1, 65535, &result); - if (result != RESULT_OK) { - argParseError(parseOpt, "invalid httpport"); - return EINVAL; - } - opt->httpPort = (uint16_t)value; - break; - case O_HTMLPA: // --htmlpath=/var/ebusd/html - if (arg == nullptr || arg[0] == 0 || strcmp("/", arg) == 0) { - argParseError(parseOpt, "invalid htmlpath"); - return EINVAL; - } - opt->htmlPath = arg; - break; - case O_UPDCHK: // --updatecheck=on - if (arg == nullptr || arg[0] == 0) { - argParseError(parseOpt, "invalid updatecheck"); - return EINVAL; - } - if (strcmp("on", arg) == 0) { - opt->updateCheck = true; - } else if (strcmp("off", arg) == 0) { - opt->updateCheck = false; - } else { - argParseError(parseOpt, "invalid updatecheck"); - return EINVAL; - } - break; - - // Log options: - case 'l': // --logfile=/var/log/ebusd.log - if (arg == nullptr || strcmp("/", arg) == 0) { - argParseError(parseOpt, "invalid logfile"); - return EINVAL; - } - opt->logFile = arg; - break; - case O_LOG: // --log=area(s):level - { - char* pos = strchr(arg, ':'); - if (pos == nullptr) { - pos = strchr(arg, ' '); - if (pos == nullptr) { - argParseError(parseOpt, "invalid log"); - return EINVAL; - } - } - *pos = 0; - int facilities = parseLogFacilities(arg); - if (facilities == -1) { - argParseError(parseOpt, "invalid log: areas"); - return EINVAL; - } - LogLevel level = parseLogLevel(pos + 1); - if (level == ll_COUNT) { - argParseError(parseOpt, "invalid log: level"); - return EINVAL; - } - if (opt->logAreas != -1 || opt->logLevel != ll_COUNT) { - argParseError(parseOpt, "invalid log (combined with logareas or loglevel)"); - return EINVAL; - } - setFacilitiesLogLevel(facilities, level); - opt->multiLog = true; - break; - } - case O_LOGARE: // --logareas=all - { - int facilities = parseLogFacilities(arg); - if (facilities == -1) { - argParseError(parseOpt, "invalid logareas"); - return EINVAL; - } - if (opt->multiLog) { - argParseError(parseOpt, "invalid logareas (combined with log)"); - return EINVAL; - } - opt->logAreas = facilities; - break; - } - case O_LOGLEV: // --loglevel=notice - { - LogLevel logLevel = parseLogLevel(arg); - if (logLevel == ll_COUNT) { - argParseError(parseOpt, "invalid loglevel"); - return EINVAL; - } - if (opt->multiLog) { - argParseError(parseOpt, "invalid loglevel (combined with log)"); - return EINVAL; - } - opt->logLevel = logLevel; - break; - } - - // Raw logging options: - case O_RAW: // --lograwdata - opt->logRaw = arg && strcmp("bytes", arg) == 0 ? 2 : 1; - break; - case O_RAWFIL: // --lograwdatafile=/var/log/ebusd.log - if (arg == nullptr || arg[0] == 0 || strcmp("/", arg) == 0) { - argParseError(parseOpt, "invalid lograwdatafile"); - return EINVAL; - } - opt->logRawFile = arg; - break; - case O_RAWSIZ: // --lograwdatasize=100 - value = parseInt(arg, 10, 1, 1000000, &result); - if (result != RESULT_OK) { - argParseError(parseOpt, "invalid lograwdatasize"); - return EINVAL; - } - opt->logRawSize = value; - break; - - - // Binary dump options: - case 'D': // --dump - opt->dump = true; - break; - case O_DMPFIL: // --dumpfile=/tmp/ebusd_dump.bin - if (arg == nullptr || arg[0] == 0 || strcmp("/", arg) == 0) { - argParseError(parseOpt, "invalid dumpfile"); - return EINVAL; - } - opt->dumpFile = arg; - break; - case O_DMPSIZ: // --dumpsize=100 - value = parseInt(arg, 10, 1, 1000000, &result); - if (result != RESULT_OK) { - argParseError(parseOpt, "invalid dumpsize"); - return EINVAL; - } - opt->dumpSize = value; - break; - case O_DMPFLU: // --dumpflush - opt->dumpFlush = true; - break; - - default: - return ESRCH; - } - - // check for invalid arg combinations - if (opt->readOnly && (opt->answer || opt->generateSyn || opt->initialSend - || (opt->scanConfig && opt->initialScan != ESC))) { - argParseError(parseOpt, "cannot combine readonly with answer/generatesyn/initsend/scanconfig"); - return EINVAL; - } - if (opt->scanConfig && opt->pollInterval == 0) { - argParseError(parseOpt, "scanconfig without polling may lead to invalid files included for certain products!"); - return EINVAL; - } - if (opt->injectMessages && (opt->checkConfig || opt->dumpConfig)) { - argParseError(parseOpt, "cannot combine inject with checkconfig/dumpconfig"); - return EINVAL; - } - return 0; -} - void shutdown(bool error = false); void daemonize() { @@ -806,62 +222,8 @@ void signalHandler(int sig) { * @return the exit code. */ int main(int argc, char* argv[], char* envp[]) { - const argParseOpt parseOpt = { - argDefs, - parse_opt, - 0, - "" PACKAGE_NAME, - "[INJECT...]", - "A daemon for communication with eBUS heating systems.", - "Report bugs to " PACKAGE_BUGREPORT " .", - datahandler_getargs(), - &s_opt - }; - - char envname[32] = "--"; // needs to cover at least max length of any option name plus "--" - char* envopt = envname+2; - for (char ** env = envp; *env; env++) { - char* pos = strchr(*env, '='); - if (!pos || strncmp(*env, "EBUSD_", sizeof("EBUSD_")-1) != 0) { - continue; - } - char* start = *env+sizeof("EBUSD_")-1; - size_t len = pos-start; - if (len <= 1 || len > sizeof(envname)-3) { // no single char long args - continue; - } - for (size_t i=0; i < len; i++) { - envopt[i] = static_cast(tolower(start[i])); - } - envopt[len] = 0; - if (strcmp(envopt, "version") == 0 || strcmp(envopt, "image") == 0 || strcmp(envopt, "arch") == 0 - || strcmp(envopt, "opts") == 0 || strcmp(envopt, "inject") == 0 - || strcmp(envopt, "checkconfig") == 0 || strncmp(envopt, "dumpconfig", 10) == 0 - ) { - // ignore those defined in Dockerfile, EBUSD_OPTS, those with final args, and interactive ones - continue; - } - char* envargv[] = {argv[0], envname, pos+1}; - int cnt = pos[1] ? 2 : 1; - if (pos[1] && strlen(*env) < sizeof(envname)-3 - && (strcmp(envopt, "scanconfig") == 0 || strcmp(envopt, "lograwdata") == 0)) { - // only really special case: OPTION_ARG_OPTIONAL with non-empty arg needs to use "=" syntax - cnt = 1; - strcat(envopt, pos); - } - int idx = -1; - int err = argParse(&parseOpt, 1+cnt, envargv, &idx); - if (err != 0 && idx == -1) { // ignore args for non-arg boolean options - if (err == ESRCH) { // special value to abort immediately - logWrite(lf_main, ll_error, "invalid argument in env: %s", *env); // force logging on exit - return EINVAL; - } - logWrite(lf_main, ll_error, "invalid/unknown argument in env (ignored): %s", *env); // force logging - } - } - int arg_index = -1; - switch (argParse(&parseOpt, argc, argv, &arg_index)) { + switch (parse_main_args(argc, argv, envp, &s_opt, &arg_index)) { case 0: // OK break; case '?': // help printed @@ -886,35 +248,29 @@ int main(int argc, char* argv[], char* envp[]) { setFacilitiesLogLevel(s_opt.logAreas, s_opt.logLevel); } - if (!s_opt.readOnly && !s_scanConfigOrPathSet) { - s_opt.scanConfig = true; - s_opt.initialScan = BROADCAST; - } - if (!s_configPath.empty() && s_configPath[s_configPath.length()-1] != '/') { - s_configPath += "/"; - } const string lang = MappedFileReader::normalizeLanguage( s_opt.preferLanguage == nullptr || !s_opt.preferLanguage[0] ? "" : s_opt.preferLanguage); string configLocalPrefix, configUriPrefix; HttpClient::initialize(s_opt.caFile, s_opt.caPath); HttpClient* configHttpClient = nullptr; - if (s_configPath.find("://") == string::npos) { - configLocalPrefix = s_configPath; + string configPath = s_opt.configPath; + if (configPath.find("://") == string::npos) { + configLocalPrefix = s_opt.configPath; } else { if (!s_opt.scanConfig) { logWrite(lf_main, ll_error, "invalid configpath without scanconfig"); // force logging on exit return EINVAL; } - size_t pos = s_configPath.find(PREVIOUS_CONFIG_PATH_SUFFIX); + size_t pos = configPath.find(PREVIOUS_CONFIG_PATH_SUFFIX); if (pos != string::npos) { - string newPath = s_configPath.substr(0, pos) + CONFIG_PATH_SUFFIX - + s_configPath.substr(pos+strlen(PREVIOUS_CONFIG_PATH_SUFFIX)); - logNotice(lf_main, "replaced old configPath %s with new one: %s", s_configPath.c_str(), newPath.c_str()); - s_configPath = newPath; + string newPath = configPath.substr(0, pos) + CONFIG_PATH_SUFFIX + + configPath.substr(pos+strlen(PREVIOUS_CONFIG_PATH_SUFFIX)); + logNotice(lf_main, "replaced old configPath %s with new one: %s", s_opt.configPath, newPath.c_str()); + configPath = newPath; } uint16_t configPort = 80; string proto, configHost; - if (!HttpClient::parseUrl(s_configPath, &proto, &configHost, &configPort, &configUriPrefix)) { + if (!HttpClient::parseUrl(configPath, &proto, &configHost, &configPort, &configUriPrefix)) { #ifndef HAVE_SSL if (proto == "https") { logWrite(lf_main, ll_error, "invalid configPath URL (HTTPS not supported)"); // force logging on exit @@ -940,7 +296,7 @@ int main(int argc, char* argv[], char* envp[]) { } s_messageMap = new MessageMap(s_opt.checkConfig, lang); - s_scanHelper = new ScanHelper(s_messageMap, s_configPath, configLocalPrefix, configUriPrefix, + s_scanHelper = new ScanHelper(s_messageMap, configPath, configLocalPrefix, configUriPrefix, lang.empty() ? lang : "?l=" + lang, configHttpClient, s_opt.checkConfig); s_messageMap->setResolver(s_scanHelper); if (s_opt.checkConfig) { diff --git a/src/ebusd/main.h b/src/ebusd/main.h old mode 100644 new mode 100755 index a085d0e35..03543e240 --- a/src/ebusd/main.h +++ b/src/ebusd/main.h @@ -32,6 +32,9 @@ namespace ebusd { * The main entry method doing all the startup handling. */ +/** the config path part behind the scheme (scheme without "://"). */ +#define CONFIG_PATH_SUFFIX "://cfg.ebusd.eu/" + /** A structure holding all program options. */ typedef struct options { const char* device; //!< eBUS device (serial device or [udp:]ip:port) [/dev/ttyUSB0] @@ -40,6 +43,8 @@ typedef struct options { bool initialSend; //!< send an initial escape symbol after connecting device unsigned int extraLatency; //!< extra transfer latency in ms [0 for USB, 10 for IP] + const char* configPath; //!< the configuration files path or URL + bool scanConfigOrPathSet; //!< whether scanConfig or configPath were set by arguments. bool scanConfig; //!< pick configuration files matching initial scan /** * initial address(es) to scan: @@ -96,6 +101,18 @@ typedef struct options { bool dumpFlush; //!< flush each byte } options_t; +/** + * Parse the main command line arguments in @a argv. + * @param argc the number of command line arguments. + * @param argv the command line arguments. + * @param envp the environment variables to parse before the args, or nullptr. + * @param opt pointer to the parsed arguments (will be initialized to defaults first). + * @param argIndex optional pointer for storing the index to the first non-argument found in argv. + * @return 0 on success, '!' for an invalid argument value, ':' for a missing argument value, + * '?' when "-?" was given, or the result of the parse function if non-zero. + */ +int parse_main_args(int argc, char* argv[], char* envp[], options_t* opt, int* argIndex); + } // namespace ebusd #endif // EBUSD_MAIN_H_ diff --git a/src/ebusd/main_args.cpp b/src/ebusd/main_args.cpp new file mode 100755 index 000000000..e7ad5ef5f --- /dev/null +++ b/src/ebusd/main_args.cpp @@ -0,0 +1,695 @@ +/* + * ebusd - daemon for communication with eBUS heating systems. + * Copyright (C) 2023 John Baier + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "ebusd/main.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "ebusd/datahandler.h" +#include "lib/utils/log.h" +#include "lib/utils/arg.h" +#include "ebusd/scan.h" + +namespace ebusd { + +/** the default path of the configuration files. */ +#ifdef HAVE_SSL +#define CONFIG_PATH "https" CONFIG_PATH_SUFFIX +#else +#define CONFIG_PATH "http" CONFIG_PATH_SUFFIX +#endif + +/** the default program options. */ +static const options_t s_default_opt = { + .device = "/dev/ttyUSB0", + .noDeviceCheck = false, + .readOnly = false, + .initialSend = false, + .extraLatency = 0, + + .configPath = nullptr, + .scanConfigOrPathSet = false, + .scanConfig = false, + .initialScan = 0, + .scanRetries = 5, + .preferLanguage = getenv("LANG"), + .checkConfig = false, + .dumpConfig = OF_NONE, + .dumpConfigTo = nullptr, + .pollInterval = 5, + .injectMessages = false, + .stopAfterInject = false, + .caFile = nullptr, + .caPath = nullptr, + + .address = 0x31, + .answer = false, + .acquireTimeout = 10, + .acquireRetries = 3, + .sendRetries = 2, + .receiveTimeout = SLAVE_RECV_TIMEOUT*5/3, + .masterCount = 0, + .generateSyn = false, + + .accessLevel = "", + .aclFile = "", + .foreground = false, + .enableHex = false, + .enableDefine = false, + .pidFile = PACKAGE_PIDFILE, + .port = 8888, + .localOnly = false, + .httpPort = 0, + .htmlPath = "/var/" PACKAGE "/html", + .updateCheck = true, + + .logFile = PACKAGE_LOGFILE, + .logAreas = -1, + .logLevel = ll_COUNT, + .multiLog = false, + + .logRaw = 0, + .logRawFile = PACKAGE_LOGFILE, + .logRawSize = 100, + + .dump = false, + .dumpFile = "/tmp/" PACKAGE "_dump.bin", + .dumpSize = 100, + .dumpFlush = false +}; + +/** the (optionally corrected) config path for retrieving configuration files from. */ +static string s_configPath = CONFIG_PATH; + +#define O_INISND -2 +#define O_DEVLAT (O_INISND-1) +#define O_SCNRET (O_DEVLAT-1) +#define O_CFGLNG (O_SCNRET-1) +#define O_CHKCFG (O_CFGLNG-1) +#define O_DMPCFG (O_CHKCFG-1) +#define O_DMPCTO (O_DMPCFG-1) +#define O_POLINT (O_DMPCTO-1) +#define O_CAFILE (O_POLINT-1) +#define O_CAPATH (O_CAFILE-1) +#define O_ANSWER (O_CAPATH-1) +#define O_ACQTIM (O_ANSWER-1) +#define O_ACQRET (O_ACQTIM-1) +#define O_SNDRET (O_ACQRET-1) +#define O_RCVTIM (O_SNDRET-1) +#define O_MASCNT (O_RCVTIM-1) +#define O_GENSYN (O_MASCNT-1) +#define O_ACLDEF (O_GENSYN-1) +#define O_ACLFIL (O_ACLDEF-1) +#define O_HEXCMD (O_ACLFIL-1) +#define O_DEFCMD (O_HEXCMD-1) +#define O_PIDFIL (O_DEFCMD-1) +#define O_LOCAL (O_PIDFIL-1) +#define O_HTTPPT (O_LOCAL-1) +#define O_HTMLPA (O_HTTPPT-1) +#define O_UPDCHK (O_HTMLPA-1) +#define O_LOG (O_UPDCHK-1) +#define O_LOGARE (O_LOG-1) +#define O_LOGLEV (O_LOGARE-1) +#define O_RAW (O_LOGLEV-1) +#define O_RAWFIL (O_RAW-1) +#define O_RAWSIZ (O_RAWFIL-1) +#define O_DMPFIL (O_RAWSIZ-1) +#define O_DMPSIZ (O_DMPFIL-1) +#define O_DMPFLU (O_DMPSIZ-1) + +/** the definition of the known program arguments. */ +static const argDef argDefs[] = { + {nullptr, 0, nullptr, 0, "Device options:"}, + {"device", 'd', "DEV", 0, "Use DEV as eBUS device (" + "prefix \"ens:\" for enhanced high speed device or " + "\"enh:\" for enhanced device, with " + "\"IP:PORT\" for network device or " + "\"DEVICE\" for serial device" + ") [/dev/ttyUSB0]"}, + {"nodevicecheck", 'n', nullptr, 0, "Skip serial eBUS device test"}, + {"readonly", 'r', nullptr, 0, "Only read from device, never write to it"}, + {"initsend", O_INISND, nullptr, 0, "Send an initial escape symbol after connecting device"}, + {"latency", O_DEVLAT, "MSEC", 0, "Extra transfer latency in ms [0]"}, + + {nullptr, 0, nullptr, 0, "Message configuration options:"}, + {"configpath", 'c', "PATH", 0, "Read CSV config files from PATH (local folder or HTTPS URL) [" + CONFIG_PATH "]"}, + {"scanconfig", 's', "ADDR", af_optional, "Pick CSV config files matching initial scan ADDR: " + "empty for broadcast ident message (default when configpath is not given), " + "\"none\" for no initial scan message, " + "\"full\" for full scan, " + "a single hex address to scan, or " + "\"off\" for not picking CSV files by scan result (default when configpath is given).\n" + "If combined with --checkconfig, you can add scan message data as " + "arguments for checking a particular scan configuration, e.g. \"FF08070400/0AB5454850303003277201\"."}, + {"scanretries", O_SCNRET, "COUNT", 0, "Retry scanning devices COUNT times [5]"}, + {"configlang", O_CFGLNG, "LANG", 0, + "Prefer LANG in multilingual configuration files [system default language]"}, + {"checkconfig", O_CHKCFG, nullptr, 0, "Check config files, then stop"}, + {"dumpconfig", O_DMPCFG, "FORMAT", af_optional, + "Check and dump config files in FORMAT (\"json\" or \"csv\"), then stop"}, + {"dumpconfigto", O_DMPCTO, "FILE", 0, "Dump config files to FILE"}, + {"pollinterval", O_POLINT, "SEC", 0, "Poll for data every SEC seconds (0=disable) [5]"}, + {"inject", 'i', "stop", af_optional, "Inject remaining arguments as already seen messages (e.g. " + "\"FF08070400/0AB5454850303003277201\"), optionally stop afterwards"}, +#ifdef HAVE_SSL + {"cafile", O_CAFILE, "FILE", 0, "Use CA FILE for checking certificates (uses defaults," + " \"#\" for insecure)"}, + {"capath", O_CAPATH, "PATH", 0, "Use CA PATH for checking certificates (uses defaults)"}, +#endif // HAVE_SSL + + {nullptr, 0, nullptr, 0, "eBUS options:"}, + {"address", 'a', "ADDR", 0, "Use hex ADDR as own master bus address [31]"}, + {"answer", O_ANSWER, nullptr, 0, "Actively answer to requests from other masters"}, + {"acquiretimeout", O_ACQTIM, "MSEC", 0, "Stop bus acquisition after MSEC ms [10]"}, + {"acquireretries", O_ACQRET, "COUNT", 0, "Retry bus acquisition COUNT times [3]"}, + {"sendretries", O_SNDRET, "COUNT", 0, "Repeat failed sends COUNT times [2]"}, + {"receivetimeout", O_RCVTIM, "MSEC", 0, "Expect a slave to answer within MSEC ms [25]"}, + {"numbermasters", O_MASCNT, "COUNT", 0, "Expect COUNT masters on the bus, 0 for auto detection [0]"}, + {"generatesyn", O_GENSYN, nullptr, 0, "Enable AUTO-SYN symbol generation"}, + + {nullptr, 0, nullptr, 0, "Daemon options:"}, + {"accesslevel", O_ACLDEF, "LEVEL", 0, "Set default access level to LEVEL (\"*\" for everything) [\"\"]"}, + {"aclfile", O_ACLFIL, "FILE", 0, "Read access control list from FILE"}, + {"foreground", 'f', nullptr, 0, "Run in foreground"}, + {"enablehex", O_HEXCMD, nullptr, 0, "Enable hex command"}, + {"enabledefine", O_DEFCMD, nullptr, 0, "Enable define command"}, + {"pidfile", O_PIDFIL, "FILE", 0, "PID file name (only for daemon) [" PACKAGE_PIDFILE "]"}, + {"port", 'p', "PORT", 0, "Listen for command line connections on PORT [8888]"}, + {"localhost", O_LOCAL, nullptr, 0, "Listen for command line connections on 127.0.0.1 interface only"}, + {"httpport", O_HTTPPT, "PORT", 0, "Listen for HTTP connections on PORT, 0 to disable [0]"}, + {"htmlpath", O_HTMLPA, "PATH", 0, "Path for HTML files served by HTTP port [/var/ebusd/html]"}, + {"updatecheck", O_UPDCHK, "MODE", 0, "Set automatic update check to MODE (on|off) [on]"}, + + {nullptr, 0, nullptr, 0, "Log options:"}, + {"logfile", 'l', "FILE", 0, "Write log to FILE (only for daemon, empty string for using syslog) [" + PACKAGE_LOGFILE "]"}, + {"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]"}, + {"logareas", O_LOGARE, "AREAS", 0, "Only write log for matching AREA(S): main|network|bus|update|other" + "|all [all]"}, + {"loglevel", O_LOGLEV, "LEVEL", 0, "Only write log below or equal to LEVEL: error|notice|info|debug" + " [notice]"}, + + {nullptr, 0, nullptr, 0, "Raw logging options:"}, + {"lograwdata", O_RAW, "bytes", af_optional, + "Log messages or all received/sent bytes on the bus"}, + {"lograwdatafile", O_RAWFIL, "FILE", 0, "Write raw log to FILE [" PACKAGE_LOGFILE "]"}, + {"lograwdatasize", O_RAWSIZ, "SIZE", 0, "Make raw log file no larger than SIZE kB [100]"}, + + {nullptr, 0, nullptr, 0, "Binary dump options:"}, + {"dump", 'D', nullptr, 0, "Enable binary dump of received bytes"}, + {"dumpfile", O_DMPFIL, "FILE", 0, "Dump received bytes to FILE [/tmp/" PACKAGE "_dump.bin]"}, + {"dumpsize", O_DMPSIZ, "SIZE", 0, "Make dump file no larger than SIZE kB [100]"}, + {"dumpflush", O_DMPFLU, nullptr, 0, "Flush each byte"}, + + {nullptr, 0, nullptr, 0, nullptr}, +}; + +/** + * The program argument parsing function. + * @param key the key from @a argDefs. + * @param arg the option argument, or nullptr. + * @param parseOpt the parse options. + */ +static int parse_opt(int key, char *arg, const argParseOpt *parseOpt) { + struct options *opt = (struct options*)parseOpt->userArg; + result_t result = RESULT_OK; + unsigned int value; + + switch (key) { + // Device options: + case 'd': // --device=/dev/ttyUSB0 + if (arg == nullptr || arg[0] == 0) { + argParseError(parseOpt, "invalid device"); + return EINVAL; + } + opt->device = arg; + break; + case 'n': // --nodevicecheck + opt->noDeviceCheck = true; + break; + case 'r': // --readonly + opt->readOnly = true; + break; + case O_INISND: // --initsend + opt->initialSend = true; + break; + case O_DEVLAT: // --latency=10 + value = parseInt(arg, 10, 0, 200000, &result); // backwards compatible (micros) + if (result != RESULT_OK || (value <= 1000 && value > 200)) { // backwards compatible (micros) + argParseError(parseOpt, "invalid latency"); + return EINVAL; + } + opt->extraLatency = value > 1000 ? value/1000 : value; // backwards compatible (micros) + break; + + // Message configuration options: + case 'c': // --configpath=https://cfg.ebusd.eu/ + if (arg == nullptr || arg[0] == 0 || strcmp("/", arg) == 0) { + argParseError(parseOpt, "invalid configpath"); + return EINVAL; + } + s_configPath = arg; + opt->scanConfigOrPathSet = true; + break; + case 's': // --scanconfig[=ADDR] (ADDR=|none|full||off) + { + symbol_t initialScan = 0; + if (!arg || arg[0] == 0) { + initialScan = BROADCAST; // default for no or empty argument + } else if (strcmp("none", arg) == 0) { + initialScan = ESC; + } else if (strcmp("full", arg) == 0) { + initialScan = SYN; + } else if (strcmp("off", arg) == 0) { + // zero turns scanConfig off + } else { + auto address = (symbol_t)parseInt(arg, 16, 0x00, 0xff, &result); + if (result != RESULT_OK || !isValidAddress(address)) { + argParseError(parseOpt, "invalid initial scan address"); + return EINVAL; + } + if (isMaster(address)) { + initialScan = getSlaveAddress(address); + } else { + initialScan = address; + } + } + opt->scanConfig = initialScan != 0; + opt->initialScan = initialScan; + opt->scanConfigOrPathSet = true; + break; + } + case O_SCNRET: // --scanretries=10 + value = parseInt(arg, 10, 0, 100, &result); + if (result != RESULT_OK) { + argParseError(parseOpt, "invalid scanretries"); + return EINVAL; + } + opt->scanRetries = value; + break; + case O_CFGLNG: // --configlang=LANG + opt->preferLanguage = arg; + break; + case O_CHKCFG: // --checkconfig + opt->checkConfig = true; + break; + case O_DMPCFG: // --dumpconfig[=json|csv] + if (!arg || arg[0] == 0 || strcmp("csv", arg) == 0) { + // no further flags + opt->dumpConfig = OF_DEFINITION; + } else if (strcmp("json", arg) == 0) { + opt->dumpConfig = OF_DEFINITION | OF_NAMES | OF_UNITS | OF_COMMENTS | OF_VALUENAME | OF_ALL_ATTRS | OF_JSON; + } else { + argParseError(parseOpt, "invalid dumpconfig"); + return EINVAL; + } + opt->checkConfig = true; + break; + case O_DMPCTO: // --dumpconfigto=FILE + if (!arg || arg[0] == 0) { + argParseError(parseOpt, "invalid dumpconfigto"); + return EINVAL; + } + opt->dumpConfigTo = arg; + break; + case O_POLINT: // --pollinterval=5 + value = parseInt(arg, 10, 0, 3600, &result); + if (result != RESULT_OK) { + argParseError(parseOpt, "invalid pollinterval"); + return EINVAL; + } + opt->pollInterval = value; + break; + case 'i': // --inject[=stop] + opt->injectMessages = true; + opt->stopAfterInject = arg && strcmp("stop", arg) == 0; + break; + case O_CAFILE: // --cafile=FILE + opt->caFile = arg; + break; + case O_CAPATH: // --capath=PATH + opt->caPath = arg; + break; + + // eBUS options: + case 'a': // --address=31 + { + auto address = (symbol_t)parseInt(arg, 16, 0, 0xff, &result); + if (result != RESULT_OK || !isMaster(address)) { + argParseError(parseOpt, "invalid address"); + return EINVAL; + } + opt->address = address; + break; + } + case O_ANSWER: // --answer + opt->answer = true; + break; + case O_ACQTIM: // --acquiretimeout=10 + value = parseInt(arg, 10, 1, 100000, &result); // backwards compatible (micros) + if (result != RESULT_OK || (value <= 1000 && value > 100)) { // backwards compatible (micros) + argParseError(parseOpt, "invalid acquiretimeout"); + return EINVAL; + } + opt->acquireTimeout = value > 1000 ? value/1000 : value; // backwards compatible (micros) + break; + case O_ACQRET: // --acquireretries=3 + value = parseInt(arg, 10, 0, 10, &result); + if (result != RESULT_OK) { + argParseError(parseOpt, "invalid acquireretries"); + return EINVAL; + } + opt->acquireRetries = value; + break; + case O_SNDRET: // --sendretries=2 + value = parseInt(arg, 10, 0, 10, &result); + if (result != RESULT_OK) { + argParseError(parseOpt, "invalid sendretries"); + return EINVAL; + } + opt->sendRetries = value; + break; + case O_RCVTIM: // --receivetimeout=25 + value = parseInt(arg, 10, 1, 100000, &result); // backwards compatible (micros) + if (result != RESULT_OK || (value <= 1000 && value > 100)) { // backwards compatible (micros) + argParseError(parseOpt, "invalid receivetimeout"); + return EINVAL; + } + opt->receiveTimeout = value > 1000 ? value/1000 : value; // backwards compatible (micros) + break; + case O_MASCNT: // --numbermasters=0 + value = parseInt(arg, 10, 0, 25, &result); + if (result != RESULT_OK) { + argParseError(parseOpt, "invalid numbermasters"); + return EINVAL; + } + opt->masterCount = value; + break; + case O_GENSYN: // --generatesyn + opt->generateSyn = true; + break; + + // Daemon options: + case O_ACLDEF: // --accesslevel=* + if (arg == nullptr) { + argParseError(parseOpt, "invalid accesslevel"); + return EINVAL; + } + opt->accessLevel = arg; + break; + case O_ACLFIL: // --aclfile=/etc/ebusd/acl + if (arg == nullptr || arg[0] == 0 || strcmp("/", arg) == 0) { + argParseError(parseOpt, "invalid aclfile"); + return EINVAL; + } + opt->aclFile = arg; + break; + case 'f': // --foreground + opt->foreground = true; + break; + case O_HEXCMD: // --enablehex + opt->enableHex = true; + break; + case O_DEFCMD: // --enabledefine + opt->enableDefine = true; + break; + case O_PIDFIL: // --pidfile=/var/run/ebusd.pid + if (arg == nullptr || arg[0] == 0 || strcmp("/", arg) == 0) { + argParseError(parseOpt, "invalid pidfile"); + return EINVAL; + } + opt->pidFile = arg; + break; + case 'p': // --port=8888 + value = parseInt(arg, 10, 1, 65535, &result); + if (result != RESULT_OK) { + argParseError(parseOpt, "invalid port"); + return EINVAL; + } + opt->port = (uint16_t)value; + break; + case O_LOCAL: // --localhost + opt->localOnly = true; + break; + case O_HTTPPT: // --httpport=0 + value = parseInt(arg, 10, 1, 65535, &result); + if (result != RESULT_OK) { + argParseError(parseOpt, "invalid httpport"); + return EINVAL; + } + opt->httpPort = (uint16_t)value; + break; + case O_HTMLPA: // --htmlpath=/var/ebusd/html + if (arg == nullptr || arg[0] == 0 || strcmp("/", arg) == 0) { + argParseError(parseOpt, "invalid htmlpath"); + return EINVAL; + } + opt->htmlPath = arg; + break; + case O_UPDCHK: // --updatecheck=on + if (arg == nullptr || arg[0] == 0) { + argParseError(parseOpt, "invalid updatecheck"); + return EINVAL; + } + if (strcmp("on", arg) == 0) { + opt->updateCheck = true; + } else if (strcmp("off", arg) == 0) { + opt->updateCheck = false; + } else { + argParseError(parseOpt, "invalid updatecheck"); + return EINVAL; + } + break; + + // Log options: + case 'l': // --logfile=/var/log/ebusd.log + if (arg == nullptr || strcmp("/", arg) == 0) { + argParseError(parseOpt, "invalid logfile"); + return EINVAL; + } + opt->logFile = arg; + break; + case O_LOG: // --log=area(s):level + { + char* pos = strchr(arg, ':'); + if (pos == nullptr) { + pos = strchr(arg, ' '); + if (pos == nullptr) { + argParseError(parseOpt, "invalid log"); + return EINVAL; + } + } + *pos = 0; + int facilities = parseLogFacilities(arg); + if (facilities == -1) { + argParseError(parseOpt, "invalid log: areas"); + return EINVAL; + } + LogLevel level = parseLogLevel(pos + 1); + if (level == ll_COUNT) { + argParseError(parseOpt, "invalid log: level"); + return EINVAL; + } + if (opt->logAreas != -1 || opt->logLevel != ll_COUNT) { + argParseError(parseOpt, "invalid log (combined with logareas or loglevel)"); + return EINVAL; + } + setFacilitiesLogLevel(facilities, level); + opt->multiLog = true; + break; + } + case O_LOGARE: // --logareas=all + { + int facilities = parseLogFacilities(arg); + if (facilities == -1) { + argParseError(parseOpt, "invalid logareas"); + return EINVAL; + } + if (opt->multiLog) { + argParseError(parseOpt, "invalid logareas (combined with log)"); + return EINVAL; + } + opt->logAreas = facilities; + break; + } + case O_LOGLEV: // --loglevel=notice + { + LogLevel logLevel = parseLogLevel(arg); + if (logLevel == ll_COUNT) { + argParseError(parseOpt, "invalid loglevel"); + return EINVAL; + } + if (opt->multiLog) { + argParseError(parseOpt, "invalid loglevel (combined with log)"); + return EINVAL; + } + opt->logLevel = logLevel; + break; + } + + // Raw logging options: + case O_RAW: // --lograwdata + opt->logRaw = arg && strcmp("bytes", arg) == 0 ? 2 : 1; + break; + case O_RAWFIL: // --lograwdatafile=/var/log/ebusd.log + if (arg == nullptr || arg[0] == 0 || strcmp("/", arg) == 0) { + argParseError(parseOpt, "invalid lograwdatafile"); + return EINVAL; + } + opt->logRawFile = arg; + break; + case O_RAWSIZ: // --lograwdatasize=100 + value = parseInt(arg, 10, 1, 1000000, &result); + if (result != RESULT_OK) { + argParseError(parseOpt, "invalid lograwdatasize"); + return EINVAL; + } + opt->logRawSize = value; + break; + + // Binary dump options: + case 'D': // --dump + opt->dump = true; + break; + case O_DMPFIL: // --dumpfile=/tmp/ebusd_dump.bin + if (arg == nullptr || arg[0] == 0 || strcmp("/", arg) == 0) { + argParseError(parseOpt, "invalid dumpfile"); + return EINVAL; + } + opt->dumpFile = arg; + break; + case O_DMPSIZ: // --dumpsize=100 + value = parseInt(arg, 10, 1, 1000000, &result); + if (result != RESULT_OK) { + argParseError(parseOpt, "invalid dumpsize"); + return EINVAL; + } + opt->dumpSize = value; + break; + case O_DMPFLU: // --dumpflush + opt->dumpFlush = true; + break; + + default: + return ESRCH; + } + + // check for invalid arg combinations + if (opt->readOnly && (opt->answer || opt->generateSyn || opt->initialSend + || (opt->scanConfig && opt->initialScan != ESC))) { + argParseError(parseOpt, "cannot combine readonly with answer/generatesyn/initsend/scanconfig"); + return EINVAL; + } + if (opt->scanConfig && opt->pollInterval == 0) { + argParseError(parseOpt, "scanconfig without polling may lead to invalid files included for certain products!"); + return EINVAL; + } + if (opt->injectMessages && (opt->checkConfig || opt->dumpConfig)) { + argParseError(parseOpt, "cannot combine inject with checkconfig/dumpconfig"); + return EINVAL; + } + return 0; +} + +int parse_main_args(int argc, char* argv[], char* envp[], options_t *opt, int *arg_index) { + *opt = s_default_opt; + const argParseOpt parseOpt = { + argDefs, + parse_opt, + 0, + "" PACKAGE_NAME, + "[INJECT...]", + "A daemon for communication with eBUS heating systems.", + "Report bugs to " PACKAGE_BUGREPORT " .", + datahandler_getargs(), + opt + }; + + char envname[32] = "--"; // needs to cover at least max length of any option name plus "--" + char* envopt = envname+2; + for (char ** env = envp; *env; env++) { + char* pos = strchr(*env, '='); + if (!pos || strncmp(*env, "EBUSD_", sizeof("EBUSD_")-1) != 0) { + continue; + } + char* start = *env+sizeof("EBUSD_")-1; + size_t len = pos-start; + if (len <= 1 || len > sizeof(envname)-3) { // no single char long args + continue; + } + for (size_t i=0; i < len; i++) { + envopt[i] = static_cast(tolower(start[i])); + } + envopt[len] = 0; + if (strcmp(envopt, "version") == 0 || strcmp(envopt, "image") == 0 || strcmp(envopt, "arch") == 0 + || strcmp(envopt, "opts") == 0 || strcmp(envopt, "inject") == 0 + || strcmp(envopt, "checkconfig") == 0 || strncmp(envopt, "dumpconfig", 10) == 0 + ) { + // ignore those defined in Dockerfile, EBUSD_OPTS, those with final args, and interactive ones + continue; + } + char* envargv[] = {argv[0], envname, pos+1}; + int cnt = pos[1] ? 2 : 1; + if (pos[1] && strlen(*env) < sizeof(envname)-3 + && (strcmp(envopt, "scanconfig") == 0 || strcmp(envopt, "lograwdata") == 0)) { + // only really special case: OPTION_ARG_OPTIONAL with non-empty arg needs to use "=" syntax + cnt = 1; + strcat(envopt, pos); + } + int idx = -1; + int err = argParse(&parseOpt, 1+cnt, envargv, &idx); + if (err != 0 && idx == -1) { // ignore args for non-arg boolean options + if (err == ESRCH) { // special value to abort immediately + logWrite(lf_main, ll_error, "invalid argument in env: %s", *env); // force logging on exit + return EINVAL; + } + logWrite(lf_main, ll_error, "invalid/unknown argument in env (ignored): %s", *env); // force logging + } + } + + *arg_index = -1; + int ret = argParse(&parseOpt, argc, argv, arg_index); + if (ret != 0) { + return ret; + } + + if (!opt->readOnly && !opt->scanConfigOrPathSet) { + opt->scanConfig = true; + opt->initialScan = BROADCAST; + } + if (!s_configPath.empty()) { + if (s_configPath[s_configPath.length()-1] != '/') { + s_configPath += "/"; + } + opt->configPath = s_configPath.c_str(); // OK as s_configPath is kept + } + return 0; +} + +} // namespace ebusd From 28bebfa49d31503bf0d7073435bb10352c325599 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 1 Oct 2023 23:42:56 +0200 Subject: [PATCH 134/345] check for timegm() --- CMakeLists.txt | 1 + config.h.cmake | 3 +++ configure.ac | 1 + src/lib/utils/httpclient.cpp | 2 +- 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ac2a07cba..b787d40b7 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,6 +71,7 @@ set(CMAKE_REQUIRED_LIBRARIES pthread rt) check_function_exists(pthread_setname_np HAVE_PTHREAD_SETNAME_NP) check_function_exists(pselect HAVE_PSELECT) check_function_exists(ppoll HAVE_PPOLL) +check_function_exists(timegm HAVE_TIMEGM) 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) diff --git a/config.h.cmake b/config.h.cmake index cdcadefef..83e80bd26 100755 --- a/config.h.cmake +++ b/config.h.cmake @@ -37,6 +37,9 @@ /* Defined if time.h is available. */ #cmakedefine HAVE_TIME_H +/* Defined if timegm() is available. */ +#cmakedefine HAVE_TIMEGM + /* Defined if syslog.h is available. */ #cmakedefine HAVE_SYSLOG_H diff --git a/configure.ac b/configure.ac index df0602af9..999f6ba91 100755 --- a/configure.ac +++ b/configure.ac @@ -27,6 +27,7 @@ AC_CHECK_HEADERS([arpa/inet.h \ time.h \ termios.h]) +AC_CHECK_FUNC([timegm], [AC_DEFINE(HAVE_TIMEGM, [1], [Defined if timegm() is available.])]) AC_CHECK_FUNC([cfsetspeed], [AC_DEFINE(HAVE_CFSETSPEED, [1], [Defined if cfsetspeed() is available.])]) AC_CHECK_LIB([pthread], [pthread_setname_np], diff --git a/src/lib/utils/httpclient.cpp b/src/lib/utils/httpclient.cpp index 50440491a..65639514c 100755 --- a/src/lib/utils/httpclient.cpp +++ b/src/lib/utils/httpclient.cpp @@ -506,7 +506,7 @@ bool* repeatable, time_t* time) { string headers = result.substr(0, pos+2); // including final \r\n const char* hdrs = headers.c_str(); *response = result.substr(pos+4); -#ifdef HAVE_TIME_H +#if defined(HAVE_TIME_H) && defined(HAVE_TIMEGM) if (time) { pos = headers.find("\r\nLast-Modified: "); if (pos != string::npos && headers.substr(pos+42, 4) == " GMT") { From 37334c54f9d94cb935cd08a511aadc391c633c93 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 3 Oct 2023 08:28:43 +0200 Subject: [PATCH 135/345] allow envp missing --- src/ebusd/main_args.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ebusd/main_args.cpp b/src/ebusd/main_args.cpp index e7ad5ef5f..82d92141c 100755 --- a/src/ebusd/main_args.cpp +++ b/src/ebusd/main_args.cpp @@ -633,7 +633,7 @@ int parse_main_args(int argc, char* argv[], char* envp[], options_t *opt, int *a char envname[32] = "--"; // needs to cover at least max length of any option name plus "--" char* envopt = envname+2; - for (char ** env = envp; *env; env++) { + for (char ** env = envp; env && *env; env++) { char* pos = strchr(*env, '='); if (!pos || strncmp(*env, "EBUSD_", sizeof("EBUSD_")-1) != 0) { continue; From 9e236bb2068ac4d3daff6f17745e627c93663019 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 7 Oct 2023 16:52:15 +0200 Subject: [PATCH 136/345] abstract MQTT client --- configure.ac | 3 +- src/ebusd/CMakeLists.txt | 3 +- src/ebusd/Makefile.am | 10 +- src/ebusd/mqttclient.cpp | 34 +++ src/ebusd/mqttclient.h | 157 +++++++++++ src/ebusd/mqttclient_mosquitto.cpp | 321 ++++++++++++++++++++++ src/ebusd/mqttclient_mosquitto.h | 80 ++++++ src/ebusd/mqtthandler.cpp | 422 +++++++---------------------- src/ebusd/mqtthandler.h | 35 +-- 9 files changed, 717 insertions(+), 348 deletions(-) create mode 100644 src/ebusd/mqttclient.cpp create mode 100755 src/ebusd/mqttclient.h create mode 100755 src/ebusd/mqttclient_mosquitto.cpp create mode 100755 src/ebusd/mqttclient_mosquitto.h mode change 100644 => 100755 src/ebusd/mqtthandler.h diff --git a/configure.ac b/configure.ac index 999f6ba91..ecfc6b0ab 100755 --- a/configure.ac +++ b/configure.ac @@ -52,8 +52,7 @@ AM_CONDITIONAL([WITH_EBUSFEED], [test "x$with_ebusfeed" == "xyes"]) AC_ARG_WITH(mqtt, AS_HELP_STRING([--without-mqtt], [disable support for MQTT handling]), [], [with_mqtt=yes]) if test "x$with_mqtt" != "xno"; then AC_CHECK_LIB([mosquitto], [mosquitto_lib_init], - [AC_DEFINE_UNQUOTED(HAVE_MQTT, [1], [Defined if MQTT handling is enabled.]) - EXTRA_LIBS+=" -lmosquitto"], + [AC_DEFINE_UNQUOTED(HAVE_MQTT, [1], [Defined if MQTT handling is enabled.])], [AC_MSG_RESULT([Could not find mosquitto_lib_init in libmosquitto.]) with_mqtt="no"]) fi diff --git a/src/ebusd/CMakeLists.txt b/src/ebusd/CMakeLists.txt index db96bcb41..ae34c07ff 100644 --- a/src/ebusd/CMakeLists.txt +++ b/src/ebusd/CMakeLists.txt @@ -11,7 +11,8 @@ set(ebusd_SOURCES ) if(HAVE_MQTT) - set(ebusd_SOURCES ${ebusd_SOURCES} mqtthandler.cpp mqtthandler.h) + set(ebusd_SOURCES ${ebusd_SOURCES} mqtthandler.cpp mqtthandler.h mqttclient.cpp mqttclient.h) + set(ebusd_SOURCES ${ebusd_SOURCES} mqttclient_mosquitto.cpp mqttclient_mosquitto.h) set(ebusd_LIBS ${ebusd_LIBS} mosquitto) endif(HAVE_MQTT) diff --git a/src/ebusd/Makefile.am b/src/ebusd/Makefile.am index ee169cf6b..174750a57 100644 --- a/src/ebusd/Makefile.am +++ b/src/ebusd/Makefile.am @@ -12,15 +12,19 @@ ebusd_SOURCES = \ mainloop.h mainloop.cpp \ scan.h scan.cpp \ main.h main.cpp main_args.cpp -if MQTT -ebusd_SOURCES += mqtthandler.cpp mqtthandler.h -endif + ebusd_LDADD = ../lib/utils/libutils.a \ ../lib/ebus/libebus.a \ -lpthread \ @EXTRA_LIBS@ +if MQTT +ebusd_SOURCES += mqtthandler.cpp mqtthandler.h mqttclient.cpp mqttclient.h +ebusd_SOURCES += mqttclient_mosquitto.cpp mqttclient_mosquitto.h +ebusd_LDADD += -lmosquitto +endif + if KNX ebusd_SOURCES += knxhandler.cpp knxhandler.h ebusd_LDADD += ../lib/knx/libknx.a diff --git a/src/ebusd/mqttclient.cpp b/src/ebusd/mqttclient.cpp new file mode 100644 index 000000000..d752c3fbe --- /dev/null +++ b/src/ebusd/mqttclient.cpp @@ -0,0 +1,34 @@ +/* + * ebusd - daemon for communication with eBUS heating systems. + * Copyright (C) 2023 John Baier + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include "ebusd/mqttclient.h" +#include "ebusd/mqttclient_mosquitto.h" + +namespace ebusd { + +// copydoc +MqttClient* MqttClient::create(mqtt_client_config_t config, MqttClientListener *listener) { + return new MqttClientMosquitto(config, listener); +} + +} // namespace ebusd diff --git a/src/ebusd/mqttclient.h b/src/ebusd/mqttclient.h new file mode 100755 index 000000000..febaa0487 --- /dev/null +++ b/src/ebusd/mqttclient.h @@ -0,0 +1,157 @@ +/* + * ebusd - daemon for communication with eBUS heating systems. + * Copyright (C) 2023 John Baier + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef EBUSD_MQTTCLIENT_H_ +#define EBUSD_MQTTCLIENT_H_ + +#include +#include +#include +#include +#include + +namespace ebusd { + +/** \file ebusd/mqttclient.h + * An abstraction for an MQTT client. + */ + +using std::map; +using std::pair; +using std::string; +using std::vector; + +/** settings for the connection to an MQTT broker. */ +typedef struct mqtt_client_config { + const char* host; //!< host name or IP address of MQTT broker + uint16_t port; //!< optional port of MQTT broker + const char* clientId; //!< optional clientid override for MQTT broker + const char* username; //!< optional user name for MQTT broker + const char* password; //!< optional password for MQTT broker + bool logEvents; //!< whether to log library events + bool version311; //!< true to use protocol version 3.1.1 + bool ignoreInvalidParams; //!< ignore invalid parameters during init + const char* cafile; //!< optional CA file for TLS + const char* capath; //!< optional CA path for TLS + const char* certfile; //!< optional client certificate file for TLS + const char* keyfile; //!< optional client key file for TLS + const char* keypass; //!< optional client key file password for TLS + bool insecure; //!< whether to allow insecure TLS connection + const char* lastWillTopic; //!< optional last will topic. + const char* lastWillData; //!< optional last will data. +} mqtt_client_config_t; + + +/** + * Interface for listening to MQTT client events. + */ +class MqttClientListener { + public: + /** + * Destructor. + */ + virtual ~MqttClientListener() {} + + /** + * Notification of status of connection to the broker. + */ + virtual void notifyMqttStatus(bool connected) = 0; // abstract + + /** + * Notification of a received MQTT message. + * @param topic the topic string. + * @param data the data string. + */ + virtual void notifyMqttTopic(const string& topic, const string& data) = 0; // abstract +}; + + +/** + * An abstract MQTT client. + */ +class MqttClient { + public: + /** + * Constructor. + * @param config the client configuration to use. + * @param listener the client listener to use. + */ + MqttClient(mqtt_client_config_t config, MqttClientListener *listener) + : m_config(config), m_listener(listener) {} + + /** + * Destructor. + */ + virtual ~MqttClient() {} + + /** + * Create a new instance. + * @param config the client configuration to use. + * @param listener the client listener to use. + * @return the new MqttClient, or @a nullptr on error. + */ + static MqttClient* create(mqtt_client_config_t config, MqttClientListener *listener); + + /** + * Connect to the broker and start handling MQTT traffic. + * @param isAsync set to true if the asynchronous client was started and @a run() does not + * have to be called at all, false if the client is synchronous and does + * it's work in @a run() only. + * @param connected set to true if the connection was already established. + * @return true on success, false if connection failed and the client is no longer usable (i.e. should be destroyed). + */ + virtual bool connect(bool &isAsync, bool &connected) = 0; // abstract + + /** + * 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. + */ + virtual bool run(bool allowReconnect, bool &connected) = 0; // abstract + + /** + * Publish a topic update. + * @param topic the topic string. + * @param data the data string. + * @param retain whether the topic shall be retained. + */ + virtual void publishTopic(const string& topic, const string& data, int qos, bool retain = false) = 0; // abstract + + /** + * Publish a topic update without any data. + * @param topic the topic string. + */ + virtual void publishEmptyTopic(const string& topic, int qos, bool retain = false) = 0; // abstract + + /** + * Subscribe to the specified topic pattern. + * @param topic the topic pattern string to subscribe to. + */ + virtual void subscribeTopic(const string& topic) = 0; // abstract + + public: + /** the client configuration to use. */ + mqtt_client_config_t m_config; + + /** the @a MqttClientListener instance. */ + MqttClientListener* m_listener; +}; + +} // namespace ebusd + +#endif // EBUSD_MQTTCLIENT_H_ diff --git a/src/ebusd/mqttclient_mosquitto.cpp b/src/ebusd/mqttclient_mosquitto.cpp new file mode 100755 index 000000000..93d80ce43 --- /dev/null +++ b/src/ebusd/mqttclient_mosquitto.cpp @@ -0,0 +1,321 @@ +/* + * ebusd - daemon for communication with eBUS heating systems. + * Copyright (C) 2023 John Baier + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "ebusd/mqttclient.h" +#include "ebusd/mqttclient_mosquitto.h" +#include +#include +#include +#include +#include "lib/utils/log.h" +#include "lib/ebus/symbol.h" + +namespace ebusd { + +using std::dec; + + +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; +} + + + +#if (LIBMOSQUITTO_MAJOR >= 1) +int on_keypassword(char *buf, int size, int rwflag, void *userdata) { + MqttClientMosquitto* client = reinterpret_cast(userdata); + if (!client || !client->m_config.keypass) { + return 0; + } + int len = static_cast(strlen(client->m_config.keypass)); + if (len > size) { + len = size; + } + memcpy(buf, client->m_config.keypass, len); + return len; +} +#endif + +void on_connect( +#if (LIBMOSQUITTO_MAJOR >= 1) + struct mosquitto *mosq, +#endif + void *obj, int rc) { + if (rc == 0) { + logOtherNotice("mqtt", "connection established"); + MqttClientMosquitto* client = reinterpret_cast(obj); + if (client) { + client->m_listener->notifyMqttStatus(true); + } + } else { + if (rc >= 1 && rc <= 3) { + logOtherError("mqtt", "connection refused: %s", + rc == 1 ? "wrong protocol" : (rc == 2 ? "wrong username/password" : "broker down")); + } else { + logOtherError("mqtt", "connection refused: %d", rc); + } + } +} + +#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) { + MqttClientMosquitto* client = reinterpret_cast(obj); + if (!client || !message) { + return; + } + string topic(message->topic); + string data(message->payloadlen > 0 ? reinterpret_cast(message->payload) : ""); + client->m_listener->notifyMqttTopic(topic, data); +} + +MqttClientMosquitto::MqttClientMosquitto(mqtt_client_config_t config, MqttClientListener *listener) + : MqttClient(config, listener), + m_mosquitto(nullptr), + m_initialConnectFailed(false), + m_lastErrorLogTime(0) { + int major = -1; + 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, will try connecting anyway", major, + LIBMOSQUITTO_MAJOR); + } + logOtherInfo("mqtt", "mosquitto version %d.%d.%d (compiled with %d.%d.%d)", major, minor, revision, + LIBMOSQUITTO_MAJOR, LIBMOSQUITTO_MINOR, LIBMOSQUITTO_REVISION); + if (check(mosquitto_lib_init(), "unable to initialize")) { + signal(SIGPIPE, SIG_IGN); // needed before libmosquitto v. 1.1.3 +#if (LIBMOSQUITTO_MAJOR >= 1) + m_mosquitto = mosquitto_new(config.clientId, true, this); +#else + m_mosquitto = mosquitto_new(config.clientId, this); +#endif + if (!m_mosquitto) { + logOtherError("mqtt", "unable to instantiate"); + } + } + if (m_mosquitto) { +#if (LIBMOSQUITTO_VERSION_NUMBER >= 1004001) + check(mosquitto_threaded_set(m_mosquitto, true), "threaded_set"); + int version = config.version311 ? MQTT_PROTOCOL_V311 : MQTT_PROTOCOL_V31; + check(mosquitto_opts_set(m_mosquitto, MOSQ_OPT_PROTOCOL_VERSION, reinterpret_cast(&version)), + "opts_set protocol version"); +#else + if (config.version311) { + logOtherError("mqtt", "version 3.1.1 not supported"); + } +#endif + if (config.username || config.password) { + if (mosquitto_username_pw_set(m_mosquitto, config.username, config.password) != MOSQ_ERR_SUCCESS) { + logOtherError("mqtt", "unable to set username/password, trying without"); + } + } + if (config.lastWillTopic) { + size_t len = config.lastWillData ? strlen(config.lastWillData) : 0; +#if (LIBMOSQUITTO_MAJOR >= 1) + mosquitto_will_set(m_mosquitto, config.lastWillTopic, (uint32_t)len, + reinterpret_cast(config.lastWillData), 0, true); +#else + mosquitto_will_set(m_mosquitto, true, config.lastWillTopic, (uint32_t)len, + reinterpret_cast(config.lastWillData), 0, true); +#endif + } + + if (config.cafile || config.capath) { +#if (LIBMOSQUITTO_MAJOR >= 1) + mosquitto_user_data_set(m_mosquitto, this); + int ret; + ret = mosquitto_tls_set(m_mosquitto, config.cafile, config.capath, config.certfile, config.keyfile, + on_keypassword); + if (ret != MOSQ_ERR_SUCCESS) { + logOtherError("mqtt", "unable to set TLS: %d", ret); + } else if (config.insecure) { + ret = mosquitto_tls_insecure_set(m_mosquitto, true); + if (ret != MOSQ_ERR_SUCCESS) { + logOtherError("mqtt", "unable to set TLS insecure: %d", ret); + } + } +#else + logOtherError("mqtt", "use of TLS not supported"); +#endif + } + if (config.logEvents) { +#if (LIBMOSQUITTO_VERSION_NUMBER >= 1003001) + mosquitto_log_callback_set(m_mosquitto, on_log); +#else + logOtherError("mqtt", "logging of library events not supported"); +#endif + } + mosquitto_connect_callback_set(m_mosquitto, on_connect); + // mosquitto_disconnect_callback_set(m_mosquitto, on_disconnect); + mosquitto_message_callback_set(m_mosquitto, on_message); + } +} + +bool MqttClientMosquitto::connect(bool &isAsync, bool &connected) { + isAsync = false; + if (!m_mosquitto) { + connected = false; + return false; + } + int ret; +#if (LIBMOSQUITTO_MAJOR >= 1) + ret = mosquitto_connect(m_mosquitto, m_config.host, m_config.port, 60); +#else + ret = mosquitto_connect(m_mosquitto, config.host, config.port, 60, true); +#endif + if (ret == MOSQ_ERR_INVAL && !m_config.ignoreInvalidParams) { + logOtherError("mqtt", "unable to connect (invalid parameters)"); + mosquitto_destroy(m_mosquitto); + m_mosquitto = nullptr; + connected = false; + return false; // never try again + } + if (!check(ret, "unable to connect, retrying")) { + connected = false; + m_initialConnectFailed = m_config.ignoreInvalidParams; + return true; + } + connected = true; // assume success until connect_callback says otherwise + logOtherDebug("mqtt", "connection requested"); + return true; +} + +MqttClientMosquitto::~MqttClientMosquitto() { + if (m_mosquitto) { + mosquitto_destroy(m_mosquitto); + m_mosquitto = nullptr; + } + mosquitto_lib_cleanup(); +} + +bool MqttClientMosquitto::run(bool allowReconnect, bool &connected) { + 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 +#else + ret = mosquitto_loop(m_mosquitto, -1); // waits up to 1 second for network traffic +#endif + if (!connected && (ret == MOSQ_ERR_NO_CONN || ret == MOSQ_ERR_CONN_LOST) && allowReconnect) { + if (m_initialConnectFailed) { +#if (LIBMOSQUITTO_MAJOR >= 1) + ret = mosquitto_connect(m_mosquitto, m_config.host, m_config.port, 60); +#else + 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 (!connected && ret == MOSQ_ERR_SUCCESS) { + connected = true; + logOtherNotice("mqtt", "connection re-established"); + } + if (!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")); + 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; +} + +void MqttClientMosquitto::publishTopic(const string& topic, const string& data, int qos, bool 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(dataStr), qos, retain), "publish"); +} + +void MqttClientMosquitto::publishEmptyTopic(const string& topic, int qos, bool retain) { + const char* topicStr = topic.c_str(); + logOtherDebug("mqtt", "publish empty %s", topicStr); + check(mosquitto_publish(m_mosquitto, nullptr, topicStr, 0, nullptr, qos, retain), "publish empty"); +} + +void MqttClientMosquitto::subscribeTopic(const string& topic) { + check(mosquitto_subscribe(m_mosquitto, nullptr, topic.c_str(), 0), "subscribe"); +} + +} // namespace ebusd diff --git a/src/ebusd/mqttclient_mosquitto.h b/src/ebusd/mqttclient_mosquitto.h new file mode 100755 index 000000000..d749b60b4 --- /dev/null +++ b/src/ebusd/mqttclient_mosquitto.h @@ -0,0 +1,80 @@ +/* + * ebusd - daemon for communication with eBUS heating systems. + * Copyright (C) 2023 John Baier + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef EBUSD_MQTTCLIENT_MOSQUITTO_H_ +#define EBUSD_MQTTCLIENT_MOSQUITTO_H_ + +#include "ebusd/mqttclient.h" +#include +#include +#include +#include +#include +#include + +namespace ebusd { + +/** \file ebusd/mqttclient.h + * An abstraction for an MQTT client. + */ + +using std::map; +using std::pair; +using std::string; +using std::vector; + +class MqttClientMosquitto : public MqttClient { + public: + /** + * Constructor. + * @param config the client configuration to use. + * @param listener the client listener to use. + */ + MqttClientMosquitto(mqtt_client_config_t config, MqttClientListener *listener); + + virtual ~MqttClientMosquitto(); + + // @copydoc + bool connect(bool &isAsync, bool &connected) override; + + // @copydoc + bool run(bool allowReconnect, bool &connected) override; + + // @copydoc + void publishTopic(const string& topic, const string& data, int qos, bool retain = false) override; + + // @copydoc + void publishEmptyTopic(const string& topic, int qos, bool retain = false) override; + + // @copydoc + void subscribeTopic(const string& topic) override; + + private: + /** the mosquitto structure if initialized, or nullptr. */ + struct mosquitto* m_mosquitto; + + /** whether the initial connect failed. */ + bool m_initialConnectFailed; + + /** the last system time when a communication error was logged. */ + time_t m_lastErrorLogTime; +}; + +} // namespace ebusd + +#endif // EBUSD_MQTTCLIENT_MOSQUITTO_H_ diff --git a/src/ebusd/mqtthandler.cpp b/src/ebusd/mqtthandler.cpp index f2e75b7bf..54b4a7600 100755 --- a/src/ebusd/mqtthandler.cpp +++ b/src/ebusd/mqtthandler.cpp @@ -75,32 +75,40 @@ static const argDef g_mqtt_argDefs[] = { {"mqttjson", O_JSON, "short", af_optional, "Publish in JSON format instead of strings, optionally in short (value directly below field key)"}, {"mqttverbose", O_VERB, nullptr, 0, "Publish all available attributes"}, -#if (LIBMOSQUITTO_VERSION_NUMBER >= 1003001) {"mqttlog", O_LOGL, nullptr, 0, "Log library events"}, -#endif -#if (LIBMOSQUITTO_VERSION_NUMBER >= 1004001) {"mqttversion", O_VERS, "VERSION", 0, "Use protocol VERSION [3.1]"}, -#endif {"mqttignoreinvalid", O_IGIN, nullptr, 0, "Ignore invalid parameters during init (e.g. for DNS not resolvable yet)"}, {"mqttchanges", O_CHGS, nullptr, 0, "Whether to only publish changed messages instead of all received"}, -#if (LIBMOSQUITTO_MAJOR >= 1) {"mqttca", O_CAFI, "CA", 0, "Use CA file or dir (ending with '/') for MQTT TLS (no default)"}, {"mqttcert", O_CERT, "CERTFILE", 0, "Use CERTFILE for MQTT TLS client certificate (no default)"}, {"mqttkey", O_KEYF, "KEYFILE", 0, "Use KEYFILE for MQTT TLS client certificate (no default)"}, {"mqttkeypass", O_KEPA, "PASSWORD", 0, "Use PASSWORD for the encrypted KEYFILE (no default)"}, {"mqttinsecure", O_INSE, nullptr, 0, "Allow insecure TLS connection (e.g. using a self signed certificate)"}, -#endif {nullptr, 0, nullptr, 0, nullptr}, }; -static const char* g_host = "localhost"; //!< host name of MQTT broker [localhost] -static uint16_t g_port = 0; //!< optional port of MQTT broker, 0 to disable [0] -static const char* g_clientId = nullptr; //!< optional clientid override for MQTT broker -static const char* g_username = nullptr; //!< optional user name for MQTT broker (no default) -static const char* g_password = nullptr; //!< optional password for MQTT broker (no default) +// options for the MQTT client +static mqtt_client_config_t g_opt = { + .host = "localhost", + .port = 0, + .clientId = nullptr, + .username = nullptr, + .password = nullptr, + .logEvents = false, + .version311 = false, + .ignoreInvalidParams = false, + .cafile = nullptr, + .capath = nullptr, + .certfile = nullptr, + .keyfile = nullptr, + .keypass = nullptr, + .insecure = false, + .lastWillTopic = nullptr, + .lastWillData = nullptr, +}; static const char* g_topic = nullptr; //!< optional topic template static const char* g_globalTopic = nullptr; //!< optional global topic static const char* g_integrationFile = nullptr; //!< the integration settings file @@ -108,24 +116,8 @@ static vector* g_integrationVars = nullptr; //!< the integration settin static bool g_retain = false; //!< whether to retail all topics static int g_qos = 0; //!< the qos value for all topics static OutputFormat g_publishFormat = OF_NONE; //!< 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 = 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 -static const char* g_capath = nullptr; //!< CA path for TLS -static const char* g_certfile = nullptr; //!< client certificate file for TLS -static const char* g_keyfile = nullptr; //!< client key file for TLS -static const char* g_keypass = nullptr; //!< client key file password for TLS -static bool g_insecure = false; //!< whether to allow insecure TLS connection -#endif - /** * Replace all characters in the string with a space and return a copy of the original string. * @param arg the string to replace. @@ -157,7 +149,7 @@ static int mqtt_parse_opt(int key, char *arg, const argParseOpt *parseOpt) { argParseError(parseOpt, "invalid mqtthost"); return EINVAL; } - g_host = arg; + g_opt.host = arg; break; case O_PORT: // --mqttport=1883 @@ -166,7 +158,7 @@ static int mqtt_parse_opt(int key, char *arg, const argParseOpt *parseOpt) { argParseError(parseOpt, "invalid mqttport"); return EINVAL; } - g_port = (uint16_t)value; + g_opt.port = (uint16_t)value; break; case O_CLID: // --mqttclientid=clientid @@ -174,7 +166,7 @@ static int mqtt_parse_opt(int key, char *arg, const argParseOpt *parseOpt) { argParseError(parseOpt, "invalid mqttclientid"); return EINVAL; } - g_clientId = arg; + g_opt.clientId = arg; break; case O_USER: // --mqttuser=username @@ -182,7 +174,7 @@ static int mqtt_parse_opt(int key, char *arg, const argParseOpt *parseOpt) { argParseError(parseOpt, "invalid mqttuser"); return EINVAL; } - g_username = arg; + g_opt.username = arg; break; case O_PASS: // --mqttpass=password @@ -190,7 +182,7 @@ static int mqtt_parse_opt(int key, char *arg, const argParseOpt *parseOpt) { argParseError(parseOpt, "invalid mqttpass"); return EINVAL; } - g_password = replaceSecret(arg); + g_opt.password = replaceSecret(arg); break; case O_TOPI: // --mqtttopic=ebusd @@ -268,72 +260,66 @@ static int mqtt_parse_opt(int key, char *arg, const argParseOpt *parseOpt) { g_publishFormat = (g_publishFormat & ~OF_SHORT) | OF_NAMES|OF_UNITS|OF_COMMENTS|OF_ALL_ATTRS; break; -#if (LIBMOSQUITTO_VERSION_NUMBER >= 1003001) case O_LOGL: - g_logFromLib = true; + g_opt.logEvents = 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)) { argParseError(parseOpt, "invalid mqttversion"); return EINVAL; } - g_version = strcmp(arg, "3.1.1") == 0 ? MQTT_PROTOCOL_V311 : MQTT_PROTOCOL_V31; + g_opt.version311 = strcmp(arg, "3.1.1") == 0; break; -#endif case O_IGIN: - g_ignoreInvalidParams = true; + g_opt.ignoreInvalidParams = true; break; case O_CHGS: g_onlyChanges = true; break; -#if (LIBMOSQUITTO_MAJOR >= 1) - case O_CAFI: // --mqttca=file or --mqttca=dir/ - if (arg == nullptr || arg[0] == 0) { - argParseError(parseOpt, "invalid mqttca"); - return EINVAL; - } - if (arg[strlen(arg)-1] == '/') { - g_cafile = nullptr; - g_capath = arg; - } else { - g_cafile = arg; - g_capath = nullptr; - } - break; + case O_CAFI: // --mqttca=file or --mqttca=dir/ + if (arg == nullptr || arg[0] == 0) { + argParseError(parseOpt, "invalid mqttca"); + return EINVAL; + } + if (arg[strlen(arg)-1] == '/') { + g_opt.cafile = nullptr; + g_opt.capath = arg; + } else { + g_opt.cafile = arg; + g_opt.capath = nullptr; + } + break; - case O_CERT: // --mqttcert=CERTFILE - if (arg == nullptr || arg[0] == 0) { - argParseError(parseOpt, "invalid mqttcert"); - return EINVAL; - } - g_certfile = arg; - break; + case O_CERT: // --mqttcert=CERTFILE + if (arg == nullptr || arg[0] == 0) { + argParseError(parseOpt, "invalid mqttcert"); + return EINVAL; + } + g_opt.certfile = arg; + break; - case O_KEYF: // --mqttkey=KEYFILE - if (arg == nullptr || arg[0] == 0) { - argParseError(parseOpt, "invalid mqttkey"); - return EINVAL; - } - g_keyfile = arg; - break; + case O_KEYF: // --mqttkey=KEYFILE + if (arg == nullptr || arg[0] == 0) { + argParseError(parseOpt, "invalid mqttkey"); + return EINVAL; + } + g_opt.keyfile = arg; + break; - case O_KEPA: // --mqttkeypass=PASSWORD - if (arg == nullptr) { - argParseError(parseOpt, "invalid mqttkeypass"); - return EINVAL; - } - g_keypass = replaceSecret(arg); - break; - case O_INSE: // --mqttinsecure - g_insecure = true; - break; -#endif + case O_KEPA: // --mqttkeypass=PASSWORD + if (arg == nullptr) { + argParseError(parseOpt, "invalid mqttkeypass"); + return EINVAL; + } + g_opt.keypass = replaceSecret(arg); + break; + case O_INSE: // --mqttinsecure + g_opt.insecure = true; + break; default: return EINVAL; @@ -350,117 +336,15 @@ const argParseChildOpt* mqtthandler_getargs() { return &g_mqtt_arg_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* handlers) { - if (g_port > 0) { - int major = -1; - 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, will try connecting anyway", major, - LIBMOSQUITTO_MAJOR); - } - logOtherInfo("mqtt", "mosquitto version %d.%d.%d (compiled with %d.%d.%d)", major, minor, revision, - LIBMOSQUITTO_MAJOR, LIBMOSQUITTO_MINOR, LIBMOSQUITTO_REVISION); + if (g_opt.port > 0) { handlers->push_back(new MqttHandler(userInfo, busHandler, messages)); } return true; } -#if (LIBMOSQUITTO_MAJOR >= 1) -int on_keypassword(char *buf, int size, int rwflag, void *userdata) { - if (!g_keypass) { - return 0; - } - int len = static_cast(strlen(g_keypass)); - if (len > size) { - len = size; - } - memcpy(buf, g_keypass, len); - return len; -} -#endif - -void on_connect( -#if (LIBMOSQUITTO_MAJOR >= 1) - struct mosquitto *mosq, -#endif - void *obj, int rc) { - if (rc == 0) { - logOtherNotice("mqtt", "connection established"); - MqttHandler* handler = reinterpret_cast(obj); - if (handler) { - handler->notifyConnected(); - } - } else { - if (rc >= 1 && rc <= 3) { - logOtherError("mqtt", "connection refused: %s", - rc == 1 ? "wrong protocol" : (rc == 2 ? "wrong username/password" : "broker down")); - } else { - logOtherError("mqtt", "connection refused: %d", rc); - } - } -} - -#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(obj); - if (!handler || !message || !handler->isRunning()) { - return; - } - string topic(message->topic); - string data(message->payloadlen > 0 ? reinterpret_cast(message->payload) : ""); - handler->notifyTopic(topic, data); -} - /** * possible data type names. */ @@ -485,10 +369,9 @@ string removeTrailingNonTopicPart(const string& str) { 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_lastScanStatus(SCAN_STATUS_NONE), - m_lastErrorLogTime(0) { + m_lastUpdateCheckResult("."), m_lastScanStatus(SCAN_STATUS_NONE) { m_definitionsSince = 0; - m_mosquitto = nullptr; + m_client = nullptr; bool hasIntegration = false; if (g_integrationFile != nullptr) { if (!m_replacers.parseFile(g_integrationFile)) { @@ -603,121 +486,60 @@ MqttHandler::MqttHandler(UserInfo* userInfo, BusHandler* busHandler, MessageMap* m_globalTopic.compress(values); } m_subscribeTopic = getTopic(nullptr, "#"); - if (check(mosquitto_lib_init(), "unable to initialize")) { - signal(SIGPIPE, SIG_IGN); // needed before libmosquitto v. 1.1.3 + if (!g_opt.clientId) { ostringstream clientId; - if (g_clientId) { - clientId << g_clientId; - } else { - clientId << PACKAGE_NAME << '_' << PACKAGE_VERSION << '_' << static_cast(getpid()); - } -#if (LIBMOSQUITTO_MAJOR >= 1) - m_mosquitto = mosquitto_new(clientId.str().c_str(), true, this); -#else - m_mosquitto = mosquitto_new(clientId.str().c_str(), this); -#endif - if (!m_mosquitto) { - logOtherError("mqtt", "unable to instantiate"); - } + clientId << PACKAGE_NAME << '_' << PACKAGE_VERSION << '_' << static_cast(getpid()); + g_opt.clientId = strdup(clientId.str().c_str()); } - if (m_mosquitto) { -#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(&g_version)), - "opts_set protocol version"); -#endif - if (g_username || g_password) { - if (!g_username) { - g_username = PACKAGE; - } - if (mosquitto_username_pw_set(m_mosquitto, g_username, g_password) != MOSQ_ERR_SUCCESS) { - logOtherError("mqtt", "unable to set username/password, trying without"); - } - } - string willTopic = m_globalTopic.get("", "running"); - string willData = "false"; - size_t len = willData.length(); -#if (LIBMOSQUITTO_MAJOR >= 1) - mosquitto_will_set(m_mosquitto, willTopic.c_str(), (uint32_t)len, - reinterpret_cast(willData.c_str()), 0, true); -#else - mosquitto_will_set(m_mosquitto, true, willTopic.c_str(), (uint32_t)len, - reinterpret_cast(willData.c_str()), 0, true); -#endif - -#if (LIBMOSQUITTO_MAJOR >= 1) - if (g_cafile || g_capath) { - int ret; - ret = mosquitto_tls_set(m_mosquitto, g_cafile, g_capath, g_certfile, g_keyfile, on_keypassword); - if (ret != MOSQ_ERR_SUCCESS) { - logOtherError("mqtt", "unable to set TLS: %d", ret); - } else if (g_insecure) { - ret = mosquitto_tls_insecure_set(m_mosquitto, true); - if (ret != MOSQ_ERR_SUCCESS) { - logOtherError("mqtt", "unable to set TLS insecure: %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); -#else - ret = mosquitto_connect(m_mosquitto, g_host, g_port, 60, true); -#endif - if (ret == MOSQ_ERR_INVAL && !g_ignoreInvalidParams) { - logOtherError("mqtt", "unable to connect (invalid parameters)"); - mosquitto_destroy(m_mosquitto); - m_mosquitto = nullptr; - } else if (!check(ret, "unable to connect, retrying")) { - m_connected = false; - m_initialConnectFailed = g_ignoreInvalidParams; - } else { - m_connected = true; // assume success until connect_callback says otherwise - logOtherDebug("mqtt", "connection requested"); - } + if (g_opt.password && !g_opt.username) { + g_opt.username = PACKAGE; + } + string willTopic = m_globalTopic.get("", "running"); + if (!willTopic.empty()) { + g_opt.lastWillTopic = strdup(willTopic.c_str()); + g_opt.lastWillData = "false"; + } + m_client = MqttClient::create(g_opt, this); + m_isAsync = false; + bool ret = m_client->connect(m_isAsync, m_connected); + if (!ret) { + logOtherError("mqtt", "unable to connect (invalid parameters)"); + delete m_client; + m_client = nullptr; } } MqttHandler::~MqttHandler() { join(); - if (m_mosquitto) { - mosquitto_destroy(m_mosquitto); - m_mosquitto = nullptr; + if (m_client) { + delete m_client; + m_client = nullptr; } - mosquitto_lib_cleanup(); } void MqttHandler::startHandler() { - if (m_mosquitto) { + if (m_client) { WaitThread::start("MQTT"); } } -void MqttHandler::notifyConnected() { - if (m_mosquitto && isRunning()) { +void MqttHandler::notifyMqttStatus(bool connected) { + if (connected && m_client && isRunning()) { const string sep = (g_publishFormat & OF_JSON) ? "\"" : ""; if (m_globalTopic.has("name")) { - publishTopic(m_globalTopic.get("", "version"), sep + (PACKAGE_STRING "." REVISION) + sep, true); + m_client->publishTopic(m_globalTopic.get("", "version"), sep + (PACKAGE_STRING "." REVISION) + sep, true); } publishTopic(m_globalTopic.get("", "running"), "true", true); if (!m_staticTopic) { - check(mosquitto_subscribe(m_mosquitto, nullptr, m_subscribeTopic.c_str(), 0), "subscribe"); + m_client->subscribeTopic(m_subscribeTopic); if (!m_subscribeConfigRestartTopic.empty()) { - check(mosquitto_subscribe(m_mosquitto, nullptr, m_subscribeConfigRestartTopic.c_str(), 0), "subscribe def."); + m_client->subscribeTopic(m_subscribeConfigRestartTopic); } } } } -void MqttHandler::notifyTopic(const string& topic, const string& data) { +void MqttHandler::notifyMqttTopic(const string& topic, const string& data) { size_t pos = topic.rfind('/'); if (pos == string::npos) { return; @@ -919,7 +741,7 @@ void MqttHandler::run() { bool allowReconnect = false; while (isRunning()) { bool wasConnected = m_connected; - bool needsWait = handleTraffic(allowReconnect); + bool needsWait = m_isAsync || handleTraffic(allowReconnect); bool reconnected = !wasConnected && m_connected; allowReconnect = false; time(&now); @@ -1307,52 +1129,10 @@ void MqttHandler::publishDefinition(const StringReplacers& values) { } 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 -#else - ret = mosquitto_loop(m_mosquitto, -1); // waits up to 1 second for network traffic -#endif - if (!m_connected && (ret == MOSQ_ERR_NO_CONN || ret == MOSQ_ERR_CONN_LOST) && allowReconnect) { - if (m_initialConnectFailed) { -#if (LIBMOSQUITTO_MAJOR >= 1) - ret = mosquitto_connect(m_mosquitto, g_host, g_port, 60); -#else - 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 (!m_connected && ret == MOSQ_ERR_SUCCESS) { - m_connected = true; - logOtherNotice("mqtt", "connection re-established"); - } - if (!m_connected || ret == MOSQ_ERR_SUCCESS) { + if (!m_client) { 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; + return m_client->run(allowReconnect, m_connected); } string MqttHandler::getTopic(const Message* message, const string& suffix, const string& fieldName) { @@ -1423,16 +1203,14 @@ void MqttHandler::publishMessage(const Message* message, ostringstream* updates, void MqttHandler::publishTopic(const string& topic, const string& data, bool 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(dataStr), g_qos, g_retain || retain), "publish"); + m_client->publishTopic(topicStr, data, g_qos, g_retain || retain); } 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"); + m_client->publishEmptyTopic(topic, 0, g_retain); } } // namespace ebusd diff --git a/src/ebusd/mqtthandler.h b/src/ebusd/mqtthandler.h old mode 100644 new mode 100755 index acef8dab3..db20f6dbc --- a/src/ebusd/mqtthandler.h +++ b/src/ebusd/mqtthandler.h @@ -19,7 +19,6 @@ #ifndef EBUSD_MQTTHANDLER_H_ #define EBUSD_MQTTHANDLER_H_ -#include #include #include #include @@ -27,6 +26,7 @@ #include #include "ebusd/datahandler.h" #include "ebusd/bushandler.h" +#include "ebusd/mqttclient.h" #include "lib/ebus/message.h" #include "lib/ebus/stringhelper.h" #include "lib/utils/arg.h" @@ -63,7 +63,7 @@ bool mqtthandler_register(UserInfo* userInfo, BusHandler* busHandler, MessageMap /** * The main class supporting MQTT data handling. */ -class MqttHandler : public DataSink, public DataSource, public WaitThread { +class MqttHandler : public DataSink, public DataSource, public WaitThread, public MqttClientListener { public: /** * Constructor. @@ -82,17 +82,11 @@ class MqttHandler : public DataSink, public DataSource, public WaitThread { // @copydoc void startHandler() override; - /** - * Notify the handler of a (re-)established connection to the broker. - */ - void notifyConnected(); + // @copydoc + void notifyMqttStatus(bool connected) override; - /** - * Notify the handler of a received MQTT message. - * @param topic the topic string. - * @param data the data string. - */ - void notifyTopic(const string& topic, const string& data); + // @copydoc + void notifyMqttTopic(const string& topic, const string& data) override; // @copydoc void notifyUpdateCheckResult(const string& checkResult) override; @@ -199,23 +193,24 @@ class MqttHandler : public DataSink, public DataSource, public WaitThread { /** the last system time when the message definitions were published. */ time_t m_definitionsSince; - /** the mosquitto structure if initialized, or nullptr. */ - struct mosquitto* m_mosquitto; + /** the @a MqttClient instance. */ + MqttClient* m_client; + + /** + * true if the client is asynchronous and its @a run() method does not + * have to be called at all, false if the client is synchronous and does + * it's work in its @a run() method only. + */ + bool m_isAsync; /** whether the connection to the broker is established. */ bool m_connected; - /** whether the initial connect failed. */ - bool m_initialConnectFailed; - /** the last update check result. */ string m_lastUpdateCheckResult; /** the last scan status. */ scanStatus_t m_lastScanStatus; - - /** the last system time when a communication error was logged. */ - time_t m_lastErrorLogTime; }; } // namespace ebusd From 7cc7e64e6546792cfbb3ede19b5f8cb977ae95f8 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 7 Oct 2023 17:42:57 +0200 Subject: [PATCH 137/345] fix HA entity name prefix warnings (closes #1011) --- contrib/etc/ebusd/mqtt-hassio.cfg | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/etc/ebusd/mqtt-hassio.cfg b/contrib/etc/ebusd/mqtt-hassio.cfg index 883e4c931..83d53dc1f 100644 --- a/contrib/etc/ebusd/mqtt-hassio.cfg +++ b/contrib/etc/ebusd/mqtt-hassio.cfg @@ -311,7 +311,7 @@ definition-topic ?= %haprefix/%type_topic/%{TOPIC}_%FIELD/config # HA integration: this is the config topic payload for HA's MQTT discovery. definition-payload = { "unique_id":"%{TOPIC}_%FIELD", - "name":"%prefixn %circuit %name %fieldname", + "name":"%name %fieldname", "device":%circuit_part, "value_template":"{{value_json[\"%field\"].value}}", "state_topic":"%topic"%field_payload @@ -340,7 +340,7 @@ global_prefix = { "unique_id":"%TOPIC", "device":%global_device, "state_topic":"%topic", - "name":"%prefixn %name" + "name":"global %name" # HA integration: boolean suffix for global parts global_boolean_suffix = , @@ -387,7 +387,7 @@ def_global_updatecheck_device-payload = { "suggested_area":"%area" }, "state_topic":"%topic", - "name":"%prefixn %name", + "name":"%name", "value_template":"{%% set my_new = value_json|truncate(255)|regex_replace(find='^[^,]*|, device firmware |,.*| available',replace='') %%}{%% set my_cur = 'old' %%}{%% if my_new == '' %%}{%% set my_new = 'current' %%}{%% set my_cur = 'current' %%}{%% endif %%}{{ {'installed_version':my_cur,'latest_version':my_new,'entity_picture':'https://adapter.ebusd.eu/favicon.ico','release_url':'https://adapter.ebusd.eu/firmware/ChangeLog'} | tojson }}" } From f0d737d22e5acc809c9917a20369631be3d28156 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 7 Oct 2023 18:58:04 +0200 Subject: [PATCH 138/345] add time fields from timer messages (closes #1006) --- contrib/etc/ebusd/mqtt-hassio.cfg | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/contrib/etc/ebusd/mqtt-hassio.cfg b/contrib/etc/ebusd/mqtt-hassio.cfg index 83d53dc1f..98f1e4ba9 100644 --- a/contrib/etc/ebusd/mqtt-hassio.cfg +++ b/contrib/etc/ebusd/mqtt-hassio.cfg @@ -105,7 +105,7 @@ filter-seen = 5 #filter-non-circuit = # include only messages having the specified name (partial match, alternatives and wildcard supported). # HA integration: filter to some useful names for monitoring the heating circuit -filter-name = status|temp|yield|count|energy|power|runtime|hours|starts|mode|curve|^load$|^party$|sensor +filter-name = status|temp|yield|count|energy|power|runtime|hours|starts|mode|curve|^load$|^party$|sensor|timer # exclude messages having the specified name (partial match, alternatives and wildcard supported). #filter-non-name = # include only messages having the specified level (partial match, alternatives and wildcard supported). @@ -146,10 +146,10 @@ circuit_part = { # This is also an implicit field type filter as missing or empty mappings are not published at all. type_map-number = number type_map-list = string -# HA integration: skip string/date/time types completely +type_map-time = time +# HA integration: skip string/date/datetime types completely type_map-string = type_map-date = -type_map-time = type_map-datetime = # field type switch designator, see below. @@ -210,13 +210,16 @@ type_switch-list = binary_sensor,,measurement,yesno = yesno sensor,,,list = +# HA integration: the mapping list for (potentially) writable string entities containing a time value by field type, name, message, and unit. +type_switch-w-time = + text,,,time = from,|to,|time2,|timer + text,,,time3 = time, + # HA integration: currently unused mapping lists for non-numeric/non-binary entities. #type_switch-string = # sensor,, = #type_switch-date = # sensor,,measurement = -#type_switch-time = -# sensor,,measurement = #type_switch-datetime = # sensor, = @@ -281,6 +284,14 @@ type_part-binary_sensoryesno = , "payload_on":"yes", "payload_off":"no"%state_class +# HA integration: %type_part variable for text %type_topic +type_part-texttime = , + "command_topic":"%topic/set", + "pattern": "^[012][0-9]:[0-5][0-9]$"%state_class +type_part-texttime3 = , + "command_topic":"%topic/set", + "pattern": "^[012][0-9]:[0-5][0-9]:[0-5][0-9]$"%state_class + # optional format string for converting a fields value list into %field_values. # "$value" and "$text" are being replaced by the corresponding part. field_values-entry = "$text" From 0c63dc696ed5dea460d41c0b80843de278ddf3aa Mon Sep 17 00:00:00 2001 From: John Date: Sat, 7 Oct 2023 19:42:05 +0200 Subject: [PATCH 139/345] use readable message counter part only for writable message with multiple fields in definition topic if integration does not combine them --- src/ebusd/mqtthandler.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/ebusd/mqtthandler.cpp b/src/ebusd/mqtthandler.cpp index 54b4a7600..19029c06d 100755 --- a/src/ebusd/mqtthandler.cpp +++ b/src/ebusd/mqtthandler.cpp @@ -838,11 +838,21 @@ void MqttHandler::run() { if (filterPriority > 0 && (message->getPollPriority() == 0 || message->getPollPriority() > filterPriority)) { continue; } - if (includeActiveWrite && !message->isWrite()) { - // check for existance of write message with same name - Message* write = m_messages->find(message->getCircuit(), message->getName(), "", true); - if (write) { - continue; // avoid sending definition of read AND write message with the same key + if (includeActiveWrite) { + if (message->isWrite()) { + bool skipMultiFieldWrite = (!m_hasDefinitionFieldsPayload || m_publishByField) && !message->isPassive() && message->getFieldCount() > 1; + if (skipMultiFieldWrite) { + continue; // multi-field message is not writable when publishing by field or combining multiple fields in one definition, so skip it + } + } else { + // check for existance of write message with same name + Message* write = m_messages->find(message->getCircuit(), message->getName(), "", true); + if (write) { + bool skipMultiFieldWrite = (!m_hasDefinitionFieldsPayload || m_publishByField) && write->getFieldCount() > 1; + if (!skipMultiFieldWrite) { + continue; // avoid sending definition of read AND write message with the same key + } // else: multi-field write message is not writable when publishing by field or combining multiple fields in one definition, so skip it + } } } StringReplacers msgValues = m_replacers; // need a copy here as the contents are manipulated From 6a6e68a2c025fb46eb418b6bbe9a295d893e2bc9 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 7 Oct 2023 19:42:29 +0200 Subject: [PATCH 140/345] add readonly timers --- contrib/etc/ebusd/mqtt-hassio.cfg | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/contrib/etc/ebusd/mqtt-hassio.cfg b/contrib/etc/ebusd/mqtt-hassio.cfg index 62be0c7fc..46d770efd 100644 --- a/contrib/etc/ebusd/mqtt-hassio.cfg +++ b/contrib/etc/ebusd/mqtt-hassio.cfg @@ -217,6 +217,10 @@ type_switch-w-time = text,,,time = from,|to,|time2,|timer text,,,time3 = time, +type_switch-time = + sensor,,,time = from,|to,|time2,|timer + sensor,,,time3 = time, + # HA integration: currently unused mapping lists for non-numeric/non-binary entities. #type_switch-string = # sensor,, = From 4133c67a714320f88dc2a47675d077fdc35721ce Mon Sep 17 00:00:00 2001 From: John Date: Sun, 8 Oct 2023 12:49:12 +0200 Subject: [PATCH 141/345] log restart topic --- src/ebusd/mqtthandler.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ebusd/mqtthandler.cpp b/src/ebusd/mqtthandler.cpp index 19029c06d..c63402553 100755 --- a/src/ebusd/mqtthandler.cpp +++ b/src/ebusd/mqtthandler.cpp @@ -545,6 +545,7 @@ void MqttHandler::notifyMqttTopic(const string& topic, const string& data) { return; } if (!m_subscribeConfigRestartTopic.empty() && topic == m_subscribeConfigRestartTopic) { + logOtherDebug("mqtt", "received restart topic %s with data %s", topic.c_str(), data.c_str()); if (m_subscribeConfigRestartPayload.empty() || data == m_subscribeConfigRestartPayload) { m_definitionsSince = 0; } From 6c66fd553b961fe07cdeca8c16537b126f68ccdd Mon Sep 17 00:00:00 2001 From: John Date: Thu, 12 Oct 2023 21:29:31 +0200 Subject: [PATCH 142/345] lint, docs, remove unused includes --- src/ebusd/main_args.cpp | 9 +-------- src/ebusd/mqttclient.cpp | 2 +- src/ebusd/mqttclient.h | 4 ++-- src/lib/knx/knx.cpp | 2 +- src/lib/knx/knxnet.h | 8 ++++---- src/lib/utils/arg.cpp | 16 ++++++++-------- 6 files changed, 17 insertions(+), 24 deletions(-) diff --git a/src/ebusd/main_args.cpp b/src/ebusd/main_args.cpp index 82d92141c..be8a0da19 100755 --- a/src/ebusd/main_args.cpp +++ b/src/ebusd/main_args.cpp @@ -21,14 +21,7 @@ #endif #include "ebusd/main.h" -#include -#include -#include -#include -#include -#include -#include -#include +#include #include "ebusd/datahandler.h" #include "lib/utils/log.h" #include "lib/utils/arg.h" diff --git a/src/ebusd/mqttclient.cpp b/src/ebusd/mqttclient.cpp index d752c3fbe..2d9c127f1 100644 --- a/src/ebusd/mqttclient.cpp +++ b/src/ebusd/mqttclient.cpp @@ -26,7 +26,7 @@ namespace ebusd { -// copydoc +// @copydoc MqttClient* MqttClient::create(mqtt_client_config_t config, MqttClientListener *listener) { return new MqttClientMosquitto(config, listener); } diff --git a/src/ebusd/mqttclient.h b/src/ebusd/mqttclient.h index febaa0487..5309d31a1 100755 --- a/src/ebusd/mqttclient.h +++ b/src/ebusd/mqttclient.h @@ -91,7 +91,7 @@ class MqttClient { * @param config the client configuration to use. * @param listener the client listener to use. */ - MqttClient(mqtt_client_config_t config, MqttClientListener *listener) + MqttClient(const mqtt_client_config_t config, MqttClientListener *listener) : m_config(config), m_listener(listener) {} /** @@ -146,7 +146,7 @@ class MqttClient { public: /** the client configuration to use. */ - mqtt_client_config_t m_config; + const mqtt_client_config_t m_config; /** the @a MqttClientListener instance. */ MqttClientListener* m_listener; diff --git a/src/lib/knx/knx.cpp b/src/lib/knx/knx.cpp index 6bd1f7d97..f47df2ec8 100644 --- a/src/lib/knx/knx.cpp +++ b/src/lib/knx/knx.cpp @@ -88,7 +88,7 @@ knx_addr_t parseAddress(const string &str, bool isGroup, bool* error) { return 0; } -// copydoc +// @copydoc KnxConnection *KnxConnection::create(const char *url) { #ifdef HAVE_KNXD if (strchr(url, ':')) { diff --git a/src/lib/knx/knxnet.h b/src/lib/knx/knxnet.h index 16de8bf00..73ce156f3 100644 --- a/src/lib/knx/knxnet.h +++ b/src/lib/knx/knxnet.h @@ -684,12 +684,12 @@ class KnxNetConnection : public KnxConnection { return nullptr; } - // copydoc + // @copydoc knx_addr_t getAddress() const override { return m_addr; } - // copydoc + // @copydoc void setAddress(knx_addr_t address) override { m_addr = address; // flush duplication check buffers @@ -697,12 +697,12 @@ class KnxNetConnection : public KnxConnection { m_lastSentFrames.reset(); } - // copydoc + // @copydoc bool isProgrammingMode() const override { return m_programmingMode; } - // copydoc + // @copydoc void setProgrammingMode(bool on) override { m_programmingMode = on; } diff --git a/src/lib/utils/arg.cpp b/src/lib/utils/arg.cpp index 86b816681..7653271f8 100755 --- a/src/lib/utils/arg.cpp +++ b/src/lib/utils/arg.cpp @@ -60,9 +60,9 @@ void buildOpts(const argDef *argDefs, int &count, int &shortCharsCount, int &sho opt->flag = nullptr; opt->val = argDefIdx; if (isAlpha(arg->key)) { - shortChars[shortCharsCount] = (char)arg->key; + shortChars[shortCharsCount] = static_cast(arg->key); shortIndexes[shortCharsCount++] = count; - shortOpts[shortOptsCount++] = (char)arg->key; + shortOpts[shortOptsCount++] = static_cast(arg->key); if (arg->valueName) { shortOpts[shortOptsCount++] = ':'; if (arg->flags & af_optional) { @@ -100,9 +100,9 @@ int argParse(const argParseOpt *parseOpt, int argc, char **argv, int *argIndex) calcCounts(child->argDefs, count, shortCharsCount, shortOptsCount); } struct option *longOpts = (struct option*)calloc(count+1, sizeof(struct option)); // room for EOF - char *shortChars = (char*)calloc(shortCharsCount+1, sizeof(char)); // room for \0 - int *shortIndexes = (int*)calloc(shortCharsCount, sizeof(int)); - char *shortOpts = (char*)calloc(2+shortOptsCount+1, sizeof(char)); // room for +, :, and \0 + char *shortChars = reinterpret_cast(calloc(shortCharsCount+1, sizeof(char))); // room for \0 + int *shortIndexes = reinterpret_cast(calloc(shortCharsCount, sizeof(int))); + char *shortOpts = reinterpret_cast(calloc(2+shortOptsCount+1, sizeof(char))); // room for +, :, and \0 count = 0; shortCharsCount = 0; shortOptsCount = 0; @@ -144,7 +144,7 @@ int argParse(const argParseOpt *parseOpt, int argc, char **argv, int *argIndex) } if (isAlpha(c)) { // short name - int idx = (int)(strchr(shortChars, c) - shortChars); + int idx = static_cast(strchr(shortChars, c) - shortChars); if (idx >= 0 && idx < shortCharsCount) { longIdx = shortIndexes[idx]; } else { @@ -206,7 +206,7 @@ void wrap(const char* str, size_t pos, size_t indent) { while (*str && str < end) { if (!first) { if (indent) { - printf("%*c", (int)indent, ' '); + printf("%*c", static_cast(indent), ' '); } pos = indent; } @@ -299,7 +299,7 @@ void printArgs(const argDef *argDefs, size_t indent) { printf(" "); wrap(arg->help, taken+1, indent); } else { - printf("%*c", (int)(indent - taken), ' '); + printf("%*c", static_cast(indent - taken), ' '); wrap(arg->help, indent, indent); } } From 730d92d06cbab848586e626609d3f0e66511a8fe Mon Sep 17 00:00:00 2001 From: John Date: Fri, 13 Oct 2023 08:08:58 +0200 Subject: [PATCH 143/345] abstract bus protocol handling --- src/ebusd/bushandler.cpp | 906 ++--------------------------- src/ebusd/bushandler.h | 405 +------------ src/ebusd/knxhandler.cpp | 4 +- src/ebusd/main.cpp | 2 +- src/ebusd/mainloop.cpp | 90 +-- src/ebusd/mainloop.h | 3 + src/ebusd/mqtthandler.cpp | 2 +- src/lib/ebus/CMakeLists.txt | 2 + src/lib/ebus/Makefile.am | 2 + src/lib/ebus/protocol.cpp | 140 +++++ src/lib/ebus/protocol.h | 477 +++++++++++++++ src/lib/ebus/protocol_direct.cpp | 762 ++++++++++++++++++++++++ src/lib/ebus/protocol_direct.h | 180 ++++++ src/{ebusd => lib/ebus}/states.png | Bin 14 files changed, 1693 insertions(+), 1282 deletions(-) create mode 100644 src/lib/ebus/protocol.cpp create mode 100755 src/lib/ebus/protocol.h create mode 100644 src/lib/ebus/protocol_direct.cpp create mode 100755 src/lib/ebus/protocol_direct.h rename src/{ebusd => lib/ebus}/states.png (100%) diff --git a/src/ebusd/bushandler.cpp b/src/ebusd/bushandler.cpp index f880dc75f..31a0ebbeb 100644 --- a/src/ebusd/bushandler.cpp +++ b/src/ebusd/bushandler.cpp @@ -35,32 +35,6 @@ using std::endl; // the string used for answering to a scan request (07h 04h) #define SCAN_ANSWER ("ebusd.eu;" PACKAGE_NAME ";" SCAN_VERSION ";100") -/** - * Return the string corresponding to the @a BusState. - * @param state the @a BusState. - * @return the string corresponding to the @a BusState. - */ -const char* getStateCode(BusState state) { - switch (state) { - case bs_noSignal: return "no signal"; - case bs_skip: return "skip"; - case bs_ready: return "ready"; - case bs_sendCmd: return "send command"; - case bs_recvCmdCrc: return "receive command CRC"; - case bs_recvCmdAck: return "receive command ACK"; - case bs_recvRes: return "receive response"; - case bs_recvResCrc: return "receive response CRC"; - case bs_sendResAck: return "send response ACK"; - case bs_recvCmd: return "receive command"; - case bs_recvResAck: return "receive response ACK"; - case bs_sendCmdCrc: return "send command CRC"; - case bs_sendCmdAck: return "send command ACK"; - case bs_sendRes: return "send response"; - case bs_sendResCrc: return "send response CRC"; - case bs_sendSyn: return "send SYN"; - default: return "unknown"; - } -} result_t PollRequest::prepare(symbol_t ownMasterAddress) { istringstream input; @@ -181,17 +155,6 @@ bool ScanRequest::notify(result_t result, const SlaveSymbolString& slave) { } -bool ActiveBusRequest::notify(result_t result, const SlaveSymbolString& slave) { - if (result == RESULT_OK) { - string str = slave.getStr(); - logDebug(lf_bus, "read res: %s", str.c_str()); - } - m_result = result; - *m_slave = slave; - return false; -} - - void GrabbedMessage::setLastData(const MasterSymbolString& master, const SlaveSymbolString& slave) { time(&m_lastTime); m_lastMaster = master; @@ -355,40 +318,13 @@ bool GrabbedMessage::dump(bool unknown, MessageMap* messages, bool first, Output void BusHandler::clear() { - memset(m_seenAddresses, 0, sizeof(m_seenAddresses)); - m_masterCount = 1; + m_protocol->clear(); m_scanResults.clear(); } -result_t BusHandler::sendAndWait(const MasterSymbolString& master, SlaveSymbolString* slave) { - if (m_state == bs_noSignal) { - return RESULT_ERR_NO_SIGNAL; // don't wait when there is no signal - } - result_t result = RESULT_ERR_NO_SIGNAL; - slave->clear(); - ActiveBusRequest request(master, slave); - logInfo(lf_bus, "send message: %s", master.getStr().c_str()); - - for (int sendRetries = m_failedSendRetries + 1; sendRetries > 0; sendRetries--) { - m_nextRequests.push(&request); - bool success = m_finishedRequests.remove(&request, true); - result = success ? request.m_result : RESULT_ERR_TIMEOUT; - if (result == RESULT_OK) { - break; - } - if (!success || result == RESULT_ERR_NO_SIGNAL || result == RESULT_ERR_SEND || result == RESULT_ERR_DEVICE) { - logError(lf_bus, "send to %2.2x: %s, give up", master[1], getResultCode(result)); - break; - } - logError(lf_bus, "send to %2.2x: %s%s", master[1], getResultCode(result), sendRetries > 1 ? ", retry" : ""); - request.m_busLostRetries = 0; - } - return result; -} - result_t BusHandler::readFromBus(Message* message, const string& inputStr, symbol_t dstAddress, symbol_t srcAddress) { - symbol_t masterAddress = srcAddress == SYN ? m_ownMasterAddress : srcAddress; + symbol_t masterAddress = srcAddress == SYN ? m_protocol->getOwnMasterAddress() : srcAddress; result_t ret = RESULT_EMPTY; MasterSymbolString master; SlaveSymbolString slave; @@ -400,7 +336,7 @@ result_t BusHandler::readFromBus(Message* message, const string& inputStr, symbo break; } // send message - ret = sendAndWait(master, &slave); + ret = m_protocol->sendAndWait(master, &slave); if (ret != RESULT_OK) { logError(lf_bus, "send message part %d: %s", index, getResultCode(ret)); break; @@ -414,795 +350,50 @@ result_t BusHandler::readFromBus(Message* message, const string& inputStr, symbo return ret; } -void BusHandler::run() { - unsigned int symCount = 0; - time_t now, lastTime; - time(&lastTime); - lastTime += 2; - logNotice(lf_bus, "bus started with own address %2.2x/%2.2x%s", m_ownMasterAddress, m_ownSlaveAddress, - m_answer?" in answer mode":""); - - do { - if (m_device->isValid() && !m_reconnect) { - result_t result = handleSymbol(); - time(&now); - if (result != RESULT_ERR_TIMEOUT && now >= lastTime) { - symCount++; - } - if (now > lastTime) { - m_symPerSec = symCount / (unsigned int)(now-lastTime); - if (m_symPerSec > m_maxSymPerSec) { - m_maxSymPerSec = m_symPerSec; - if (m_maxSymPerSec > 100) { - logNotice(lf_bus, "max. symbols per second: %d", m_maxSymPerSec); - } - } - lastTime = now; - symCount = 0; - } - } else { - if (!m_device->isValid()) { - logNotice(lf_bus, "device invalid"); - setState(bs_noSignal, RESULT_ERR_DEVICE); - } - if (!Wait(5)) { - break; - } - m_reconnect = false; - result_t result = m_device->open(); - if (result == RESULT_OK) { - logNotice(lf_bus, "re-opened %s", m_device->getName()); - } else { - logError(lf_bus, "unable to open %s: %s", m_device->getName(), getResultCode(result)); - setState(bs_noSignal, result); - } - symCount = 0; - m_symbolLatencyMin = m_symbolLatencyMax = m_arbitrationDelayMin = m_arbitrationDelayMax = -1; - time(&lastTime); - lastTime += 2; - } - } while (isRunning()); -} - -#ifndef FALLTHROUGH -#if defined(__GNUC__) && __GNUC__ >= 7 -#define FALLTHROUGH [[fallthrough]]; -#else -#define FALLTHROUGH -#endif -#endif - -result_t BusHandler::handleSymbol() { - unsigned int timeout = SYN_TIMEOUT; - symbol_t sendSymbol = ESC; - bool sending = false; - - // check if another symbol has to be sent and determine timeout for receive - switch (m_state) { - case bs_noSignal: - timeout = m_generateSynInterval > 0 ? m_generateSynInterval : SIGNAL_TIMEOUT; - break; - - case bs_skip: - timeout = SYN_TIMEOUT; - FALLTHROUGH - case bs_ready: - if (m_currentRequest != nullptr) { - setState(bs_ready, RESULT_ERR_TIMEOUT); // just to be sure an old BusRequest is cleaned up - } - if (!m_device->isArbitrating() && m_currentRequest == nullptr && m_remainLockCount == 0) { - BusRequest* startRequest = m_nextRequests.peek(); - if (startRequest == nullptr && m_pollInterval > 0) { // check for poll/scan - time_t now; - time(&now); - if (m_lastPoll == 0 || difftime(now, m_lastPoll) > m_pollInterval) { - Message* message = m_messages->getNextPoll(); - if (message != nullptr) { - m_lastPoll = now; - if (difftime(now, message->getLastUpdateTime()) > m_pollInterval) { - // only poll this message if it was not updated already by other means within the interval - auto request = new PollRequest(message); - result_t ret = request->prepare(m_ownMasterAddress); - if (ret != RESULT_OK) { - logError(lf_bus, "prepare poll message: %s", getResultCode(ret)); - delete request; - } else { - startRequest = request; - m_nextRequests.push(request); - } - } - } - } - } - if (startRequest != nullptr) { // initiate arbitration - logDebug(lf_bus, "start request %2.2x", startRequest->m_master[0]); - result_t ret = m_device->startArbitration(startRequest->m_master[0]); - if (ret == RESULT_OK) { - logDebug(lf_bus, "arbitration start with %2.2x", startRequest->m_master[0]); - } else { - logError(lf_bus, "arbitration start: %s", getResultCode(ret)); - m_nextRequests.remove(startRequest); - m_currentRequest = startRequest; - setState(bs_ready, ret); // force the failed request to be notified - } - } - } - break; - - case bs_recvCmd: - case bs_recvCmdCrc: - timeout = m_slaveRecvTimeout; - break; - - case bs_recvCmdAck: - timeout = m_slaveRecvTimeout; - break; - - case bs_recvRes: - case bs_recvResCrc: - if (m_response.size() > 0 || m_slaveRecvTimeout > SYN_TIMEOUT) { - timeout = m_slaveRecvTimeout; - } else { - timeout = SYN_TIMEOUT; - } - break; - - case bs_recvResAck: - timeout = m_slaveRecvTimeout; - break; - - case bs_sendCmd: - if (m_currentRequest != nullptr) { - sendSymbol = m_currentRequest->m_master[m_nextSendPos]; // unescaped command - sending = true; - } - break; - - case bs_sendCmdCrc: - if (m_currentRequest != nullptr) { - sendSymbol = m_crc; - sending = true; - } - break; - - case bs_sendResAck: - if (m_currentRequest != nullptr) { - sendSymbol = m_crcValid ? ACK : NAK; - sending = true; - } - break; - - case bs_sendCmdAck: - if (m_answer) { - sendSymbol = m_crcValid ? ACK : NAK; - sending = true; - } - break; - - case bs_sendRes: - if (m_answer) { - sendSymbol = m_response[m_nextSendPos]; // unescaped response - sending = true; - } - break; - - case bs_sendResCrc: - if (m_answer) { - sendSymbol = m_crc; - sending = true; - } - break; - - case bs_sendSyn: - sendSymbol = SYN; - sending = true; - break; - } - - // send symbol if necessary - result_t result; - struct timespec sentTime, recvTime; - if (sending) { - if (m_state != bs_sendSyn && (sendSymbol == ESC || sendSymbol == SYN)) { - if (m_escape) { - sendSymbol = (symbol_t)(sendSymbol == ESC ? 0x00 : 0x01); - } else { - m_escape = sendSymbol; - sendSymbol = ESC; - } - } - result = m_device->send(sendSymbol); - clockGettime(&sentTime); - if (result == RESULT_OK) { - if (m_state == bs_ready) { - timeout = m_busAcquireTimeout; - } else { - timeout = SEND_TIMEOUT; - } - } else { - sending = false; - timeout = SYN_TIMEOUT; - setState(bs_skip, result); - } - } else { - clockGettime(&sentTime); // for measuring arbitration delay in enhanced protocol - } - - // receive next symbol (optionally check reception of sent symbol) - symbol_t recvSymbol; - ArbitrationState arbitrationState = as_none; - result = m_device->recv(timeout, &recvSymbol, &arbitrationState); - if (sending) { - clockGettime(&recvTime); - } - bool sentAutoSyn = false; - if (!sending && result == RESULT_ERR_TIMEOUT && m_generateSynInterval > 0 - && timeout >= m_generateSynInterval && (m_state == bs_noSignal || m_state == bs_skip)) { - // check if acting as AUTO-SYN generator is required - result = m_device->send(SYN); - if (result != RESULT_OK) { - return setState(bs_skip, result); - } - clockGettime(&sentTime); - recvSymbol = ESC; - result = m_device->recv(SEND_TIMEOUT, &recvSymbol, &arbitrationState); - clockGettime(&recvTime); - if (result != RESULT_OK) { - logError(lf_bus, "unable to receive sent AUTO-SYN symbol: %s", getResultCode(result)); - return setState(bs_noSignal, result); - } - if (recvSymbol != SYN) { - logError(lf_bus, "received %2.2x instead of AUTO-SYN symbol", recvSymbol); - return setState(bs_noSignal, result); - } - measureLatency(&sentTime, &recvTime); - if (m_generateSynInterval != SYN_TIMEOUT) { - // received own AUTO-SYN symbol back again: act as AUTO-SYN generator now - m_generateSynInterval = SYN_TIMEOUT; - logNotice(lf_bus, "acting as AUTO-SYN generator"); - } - m_remainLockCount = 0; - m_lastSynReceiveTime = recvTime; - sentAutoSyn = true; - setState(bs_ready, RESULT_OK); - } - switch (arbitrationState) { - case as_lost: - case as_timeout: - logDebug(lf_bus, arbitrationState == as_lost ? "arbitration lost" : "arbitration lost (timed out)"); - if (m_currentRequest == nullptr) { - BusRequest *startRequest = m_nextRequests.peek(); - if (startRequest != nullptr && m_nextRequests.remove(startRequest)) { - m_currentRequest = startRequest; // force the failed request to be notified - } - } - setState(m_state, RESULT_ERR_BUS_LOST); - break; - case as_won: // implies RESULT_OK - if (m_currentRequest != nullptr) { - logNotice(lf_bus, "arbitration won while handling another request"); - setState(bs_ready, RESULT_OK); // force the current request to be notified - } else { - BusRequest *startRequest = m_nextRequests.peek(); - if (m_state != bs_ready || startRequest == nullptr || !m_nextRequests.remove(startRequest)) { - logNotice(lf_bus, "arbitration won in invalid state %s", getStateCode(m_state)); - setState(bs_ready, RESULT_ERR_TIMEOUT); - } else { - logDebug(lf_bus, "arbitration won"); - m_currentRequest = startRequest; - sendSymbol = m_currentRequest->m_master[0]; - sending = true; - } - } - break; - case as_running: - break; - case as_error: - logError(lf_bus, "arbitration start error"); - // cancel request - if (!m_currentRequest) { - BusRequest *startRequest = m_nextRequests.peek(); - if (startRequest && m_nextRequests.remove(startRequest)) { - m_currentRequest = startRequest; - } - } - if (m_currentRequest) { - setState(m_state, RESULT_ERR_BUS_LOST); - } - break; - default: // only as_none - break; - } - if (sentAutoSyn && !sending) { - return RESULT_OK; - } - time_t now; - time(&now); - if (result != RESULT_OK) { - if ((m_generateSynInterval != SYN_TIMEOUT && difftime(now, m_lastReceive) > 1) - // at least one full second has passed since last received symbol - || m_state == bs_noSignal) { - return setState(bs_noSignal, result); - } - return setState(bs_skip, result); - } - - m_lastReceive = now; - if ((recvSymbol == SYN) && (m_state != bs_sendSyn)) { - if (!sending && m_remainLockCount > 0 && m_command.size() != 1) { - m_remainLockCount--; - } else if (!sending && m_remainLockCount == 0 && m_command.size() == 1) { - m_remainLockCount = 1; // wait for next AUTO-SYN after SYN / address / SYN (bus locked for own priority) - } - clockGettime(&m_lastSynReceiveTime); - return setState(bs_ready, m_state == bs_skip ? RESULT_OK : RESULT_ERR_SYN); - } - - if (sending && m_state != bs_ready) { // check received symbol for equality if not in arbitration - if (recvSymbol != sendSymbol) { - return setState(bs_skip, RESULT_ERR_SYMBOL); - } - measureLatency(&sentTime, &recvTime); - } - - switch (m_state) { - case bs_ready: - case bs_recvCmd: - case bs_recvRes: - case bs_sendCmd: - case bs_sendRes: - SymbolString::updateCrc(recvSymbol, &m_crc); - break; - default: - break; - } - - if (m_escape) { - // check escape/unescape state - if (sending) { - if (sendSymbol == ESC) { - return RESULT_OK; - } - sendSymbol = recvSymbol = m_escape; - } else { - if (recvSymbol > 0x01) { - return setState(bs_skip, RESULT_ERR_ESC); - } - recvSymbol = recvSymbol == 0x00 ? ESC : SYN; - } - m_escape = 0; - } else if (!sending && recvSymbol == ESC) { - m_escape = ESC; - return RESULT_OK; - } - - switch (m_state) { - case bs_noSignal: - return setState(bs_skip, RESULT_OK); - - case bs_skip: - return RESULT_OK; - - case bs_ready: - if (m_currentRequest != nullptr && sending) { - // check arbitration - if (recvSymbol == sendSymbol) { // arbitration successful - // measure arbitration delay - int64_t latencyLong = (sentTime.tv_sec*1000000000 + sentTime.tv_nsec - - m_lastSynReceiveTime.tv_sec*1000000000 - m_lastSynReceiveTime.tv_nsec)/1000; - if (latencyLong >= 0 && latencyLong <= 10000) { // skip clock skew or out of reasonable range - auto latency = static_cast(latencyLong); - logDebug(lf_bus, "arbitration delay %d micros", latency); - if (m_arbitrationDelayMin < 0 || (latency < m_arbitrationDelayMin || latency > m_arbitrationDelayMax)) { - if (m_arbitrationDelayMin == -1 || latency < m_arbitrationDelayMin) { - m_arbitrationDelayMin = latency; - } - if (m_arbitrationDelayMax == -1 || latency > m_arbitrationDelayMax) { - m_arbitrationDelayMax = latency; - } - logInfo(lf_bus, "arbitration delay %d - %d micros", m_arbitrationDelayMin, m_arbitrationDelayMax); - } - } - m_nextSendPos = 1; - m_repeat = false; - return setState(bs_sendCmd, RESULT_OK); - } - // arbitration lost. if same priority class found, try again after next AUTO-SYN - m_remainLockCount = isMaster(recvSymbol) ? 2 : 1; // number of SYN to wait for before next send try - if ((recvSymbol & 0x0f) != (sendSymbol & 0x0f) && m_lockCount > m_remainLockCount) { - // if different priority class found, try again after N AUTO-SYN symbols (at least next AUTO-SYN) - m_remainLockCount = m_lockCount; - } - setState(m_state, RESULT_ERR_BUS_LOST); // try again later - } - m_command.push_back(recvSymbol); - m_repeat = false; - return setState(bs_recvCmd, RESULT_OK); - - case bs_recvCmd: - m_command.push_back(recvSymbol); - if (m_command.isComplete()) { // all data received - return setState(bs_recvCmdCrc, RESULT_OK); - } - return RESULT_OK; - - case bs_recvCmdCrc: - m_crcValid = recvSymbol == m_crc; - if (m_command[1] == BROADCAST) { - if (m_crcValid) { - addSeenAddress(m_command[0]); - messageCompleted(); - return setState(bs_skip, RESULT_OK); - } - return setState(bs_skip, RESULT_ERR_CRC); - } - if (m_answer) { - symbol_t dstAddress = m_command[1]; - if (dstAddress == m_ownMasterAddress || dstAddress == m_ownSlaveAddress) { - if (m_crcValid) { - addSeenAddress(m_command[0]); - m_currentAnswering = true; - return setState(bs_sendCmdAck, RESULT_OK); - } - return setState(bs_sendCmdAck, RESULT_ERR_CRC); - } - } - if (m_crcValid) { - addSeenAddress(m_command[0]); - return setState(bs_recvCmdAck, RESULT_OK); - } - if (m_repeat) { - return setState(bs_skip, RESULT_ERR_CRC); - } - return setState(bs_recvCmdAck, RESULT_ERR_CRC); - - case bs_recvCmdAck: - if (recvSymbol == ACK) { - if (!m_crcValid) { - return setState(bs_skip, RESULT_ERR_ACK); - } - if (m_currentRequest != nullptr) { - if (isMaster(m_currentRequest->m_master[1])) { - messageCompleted(); - return setState(bs_sendSyn, RESULT_OK); - } - } else if (isMaster(m_command[1])) { - messageCompleted(); - return setState(bs_skip, RESULT_OK); - } - - m_repeat = false; - return setState(bs_recvRes, RESULT_OK); - } - if (recvSymbol == NAK) { - if (!m_repeat) { - m_repeat = true; - m_crc = 0; - m_nextSendPos = 0; - m_command.clear(); - if (m_currentRequest != nullptr) { - return setState(bs_sendCmd, RESULT_ERR_NAK, true); - } - return setState(bs_recvCmd, RESULT_ERR_NAK); - } - return setState(bs_skip, RESULT_ERR_NAK); - } - return setState(bs_skip, RESULT_ERR_ACK); - - case bs_recvRes: - m_response.push_back(recvSymbol); - if (m_response.isComplete()) { // all data received - return setState(bs_recvResCrc, RESULT_OK); - } - return RESULT_OK; - - case bs_recvResCrc: - m_crcValid = recvSymbol == m_crc; - if (m_crcValid) { - if (m_currentRequest != nullptr) { - return setState(bs_sendResAck, RESULT_OK); - } - return setState(bs_recvResAck, RESULT_OK); - } - if (m_repeat) { - if (m_currentRequest != nullptr) { - return setState(bs_sendSyn, RESULT_ERR_CRC); - } - return setState(bs_skip, RESULT_ERR_CRC); - } - if (m_currentRequest != nullptr) { - return setState(bs_sendResAck, RESULT_ERR_CRC); - } - return setState(bs_recvResAck, RESULT_ERR_CRC); - - case bs_recvResAck: - if (recvSymbol == ACK) { - if (!m_crcValid) { - return setState(bs_skip, RESULT_ERR_ACK); - } - messageCompleted(); - return setState(bs_skip, RESULT_OK); - } - if (recvSymbol == NAK) { - if (!m_repeat) { - m_repeat = true; - if (m_currentAnswering) { - m_nextSendPos = 0; - return setState(bs_sendRes, RESULT_ERR_NAK, true); - } - m_response.clear(); - return setState(bs_recvRes, RESULT_ERR_NAK, true); - } - return setState(bs_skip, RESULT_ERR_NAK); - } - return setState(bs_skip, RESULT_ERR_ACK); - - case bs_sendCmd: - if (!sending || m_currentRequest == nullptr) { - return setState(bs_skip, RESULT_ERR_INVALID_ARG); - } - m_nextSendPos++; - if (m_nextSendPos >= m_currentRequest->m_master.size()) { - return setState(bs_sendCmdCrc, RESULT_OK); - } - return RESULT_OK; - - case bs_sendCmdCrc: - if (m_currentRequest->m_master[1] == BROADCAST) { - messageCompleted(); - return setState(bs_sendSyn, RESULT_OK); - } - m_crcValid = true; - return setState(bs_recvCmdAck, RESULT_OK); - - case bs_sendResAck: - if (!sending || m_currentRequest == nullptr) { - return setState(bs_skip, RESULT_ERR_INVALID_ARG); - } - if (!m_crcValid) { - if (!m_repeat) { - m_repeat = true; - m_response.clear(); - return setState(bs_recvRes, RESULT_ERR_NAK, true); - } - return setState(bs_sendSyn, RESULT_ERR_ACK); - } - messageCompleted(); - return setState(bs_sendSyn, RESULT_OK); - - case bs_sendCmdAck: - if (!sending || !m_answer) { - return setState(bs_skip, RESULT_ERR_INVALID_ARG); - } - if (!m_crcValid) { - if (!m_repeat) { - m_repeat = true; - m_crc = 0; - m_command.clear(); - return setState(bs_recvCmd, RESULT_ERR_NAK, true); - } - return setState(bs_skip, RESULT_ERR_ACK); - } - if (isMaster(m_command[1])) { - messageCompleted(); // TODO decode command and store value into database of internal variables - return setState(bs_skip, RESULT_OK); - } - - m_nextSendPos = 0; - m_repeat = false; - { - Message* message; - message = m_messages->find(m_command); - if (message == nullptr) { - message = m_messages->find(m_command, true); - if (message != nullptr && message->getSrcAddress() != SYN) { - message = nullptr; - } - } - if (message == nullptr || message->isWrite()) { - // don't know this request or definition has wrong direction, deny - return setState(bs_skip, RESULT_ERR_INVALID_ARG); - } - istringstream input; // TODO create input from database of internal variables - if (message == m_messages->getScanMessage() || message == m_messages->getScanMessage(m_ownSlaveAddress)) { - input.str(SCAN_ANSWER); - } - // build response and store in m_response for sending back to requesting master - m_response.clear(); - result = message->prepareSlave(&input, &m_response); - if (result != RESULT_OK) { - return setState(bs_skip, result); - } - } - return setState(bs_sendRes, RESULT_OK); - - case bs_sendRes: - if (!sending || !m_answer) { - return setState(bs_skip, RESULT_ERR_INVALID_ARG); - } - m_nextSendPos++; - if (m_nextSendPos >= m_response.size()) { - // slave data completely sent - return setState(bs_sendResCrc, RESULT_OK); - } - return RESULT_OK; - - case bs_sendResCrc: - if (!sending || !m_answer) { - return setState(bs_skip, RESULT_ERR_INVALID_ARG); - } - return setState(bs_recvResAck, RESULT_OK); - - case bs_sendSyn: - if (!sending) { - return setState(bs_ready, RESULT_ERR_INVALID_ARG); - } - return setState(bs_ready, RESULT_OK); - } - return RESULT_OK; +void BusHandler::notifyProtocolStatus(bool signal) { + // ignored } -result_t BusHandler::setState(BusState state, result_t result, bool firstRepetition) { - if (m_currentRequest != nullptr) { - if (result == RESULT_ERR_BUS_LOST && m_currentRequest->m_busLostRetries < m_busLostRetries) { - logDebug(lf_bus, "%s during %s, retry", getResultCode(result), getStateCode(m_state)); - m_currentRequest->m_busLostRetries++; - m_nextRequests.push(m_currentRequest); // repeat - m_currentRequest = nullptr; - } else if (state == bs_sendSyn || (result != RESULT_OK && !firstRepetition)) { - logDebug(lf_bus, "notify request: %s", getResultCode(result)); - bool restart = m_currentRequest->notify( - result == RESULT_ERR_SYN && (m_state == bs_recvCmdAck || m_state == bs_recvRes) - ? RESULT_ERR_TIMEOUT : result, m_response); - if (restart) { - m_currentRequest->m_busLostRetries = 0; - m_nextRequests.push(m_currentRequest); - } else if (m_currentRequest->m_deleteOnFinish) { - delete m_currentRequest; - } else { - m_finishedRequests.push(m_currentRequest); - } - m_currentRequest = nullptr; - } - if (state == bs_skip) { - m_device->startArbitration(SYN); // reset arbitration state - } - } - - if (state == bs_noSignal) { // notify all requests - m_response.clear(); // notify with empty response - while ((m_currentRequest = m_nextRequests.pop()) != nullptr) { - bool restart = m_currentRequest->notify(RESULT_ERR_NO_SIGNAL, m_response); - if (restart) { // should not occur with no signal - m_currentRequest->m_busLostRetries = 0; - m_nextRequests.push(m_currentRequest); - } else if (m_currentRequest->m_deleteOnFinish) { - delete m_currentRequest; - } else { - m_finishedRequests.push(m_currentRequest); - } +result_t BusHandler::notifyProtocolAnswer(const MasterSymbolString& command, SlaveSymbolString* response) { + Message* message = m_messages->find(command); + if (message == nullptr) { + message = m_messages->find(command, true); + if (message != nullptr && message->getSrcAddress() != SYN) { + message = nullptr; } } - - m_escape = 0; - if (state == m_state) { - return result; - } - if ((result < RESULT_OK && !(result == RESULT_ERR_TIMEOUT && state == bs_skip && m_state == bs_ready)) - || (result != RESULT_OK && state == bs_skip && m_state != bs_ready)) { - logDebug(lf_bus, "%s during %s, switching to %s", getResultCode(result), getStateCode(m_state), - getStateCode(state)); - } else if (m_currentRequest != nullptr || state == bs_sendCmd || state == bs_sendCmdCrc || state == bs_sendCmdAck - || state == bs_sendRes || state == bs_sendResCrc || state == bs_sendResAck || state == bs_sendSyn - || m_state == bs_sendSyn) { - logDebug(lf_bus, "switching from %s to %s", getStateCode(m_state), getStateCode(state)); - } - if (state == bs_noSignal) { - if (m_generateSynInterval == 0 || m_state != bs_skip) { - logError(lf_bus, "signal lost"); - } - } else if (m_state == bs_noSignal) { - if (m_generateSynInterval == 0 || state != bs_skip) { - logNotice(lf_bus, "signal acquired"); - } + if (message == nullptr || message->isWrite()) { + // don't know this request or definition has wrong direction, deny + return RESULT_ERR_INVALID_ARG; } - m_state = state; - - if (state == bs_ready || state == bs_skip) { - m_command.clear(); - m_crc = 0; - m_crcValid = false; - m_response.clear(); - m_nextSendPos = 0; - m_currentAnswering = false; - } else if (state == bs_recvRes || state == bs_sendRes) { - m_crc = 0; + istringstream input; // TODO create input from database of internal variables + if (message == m_messages->getScanMessage() + || message == m_messages->getScanMessage(m_protocol->getOwnMasterAddress())) { + input.str(SCAN_ANSWER); } - return result; + // build response and store in m_response for sending back to requesting master + return message->prepareSlave(&input, response); } -void BusHandler::measureLatency(struct timespec* sentTime, struct timespec* recvTime) { - int64_t latencyLong = (recvTime->tv_sec*1000000000 + recvTime->tv_nsec - - sentTime->tv_sec*1000000000 - sentTime->tv_nsec)/1000000; - if (latencyLong < 0 || latencyLong > 1000) { - return; // clock skew or out of reasonable range - } - auto latency = static_cast(latencyLong); - logDebug(lf_bus, "send/receive symbol latency %d ms", latency); - if (m_symbolLatencyMin >= 0 && (latency >= m_symbolLatencyMin && latency <= m_symbolLatencyMax)) { - return; - } - if (m_symbolLatencyMin == -1 || latency < m_symbolLatencyMin) { - m_symbolLatencyMin = latency; - } - if (m_symbolLatencyMax == -1 || latency > m_symbolLatencyMax) { - m_symbolLatencyMax = latency; - } - logInfo(lf_bus, "send/receive symbol latency %d - %d ms", m_symbolLatencyMin, m_symbolLatencyMax); -} -bool BusHandler::addSeenAddress(symbol_t address) { - if (!isValidAddress(address, false)) { - return false; - } - bool hadConflict = m_addressConflict; - if (!isMaster(address)) { - if (!m_device->isReadOnly() && address == m_ownSlaveAddress) { - if (!m_addressConflict) { - m_addressConflict = true; - logError(lf_bus, "own slave address %2.2x is used by another participant", address); - } - } - m_seenAddresses[address] |= SEEN; - address = getMasterAddress(address); - if (address == SYN) { - return m_addressConflict && !hadConflict; - } - } - if ((m_seenAddresses[address]&SEEN) == 0) { - if (!m_device->isReadOnly() && address == m_ownMasterAddress) { - if (!m_addressConflict) { - m_addressConflict = true; - logError(lf_bus, "own master address %2.2x is used by another participant", address); - } - } else { - m_masterCount++; - if (m_autoLockCount && m_masterCount > m_lockCount) { - m_lockCount = m_masterCount; - } - logNotice(lf_bus, "new master %2.2x, master count %d", address, m_masterCount); - } - m_seenAddresses[address] |= SEEN; - } - return m_addressConflict && !hadConflict; +void BusHandler::notifyProtocolSeenAddress(symbol_t address) { + m_seenAddresses[address] |= SEEN; } -void BusHandler::messageCompleted() { - const char* prefix = m_currentRequest ? "sent" : "received"; - // do an explicit copy here in case being called by another thread - const MasterSymbolString command(m_currentRequest ? m_currentRequest->m_master : m_command); - const SlaveSymbolString response(m_response); +void BusHandler::notifyProtocolMessage(bool sent, const MasterSymbolString& command, + const SlaveSymbolString& response) { symbol_t srcAddress = command[0], dstAddress = command[1]; - if (srcAddress == dstAddress) { - logError(lf_bus, "invalid self-addressed message from %2.2x", srcAddress); - return; - } - if (!m_currentAnswering) { - addSeenAddress(dstAddress); - } - bool master = isMaster(dstAddress); if (dstAddress == BROADCAST) { - logInfo(lf_update, "%s BC cmd: %s", prefix, command.getStr().c_str()); if (command.getDataSize() >= 10 && command[2] == 0x07 && command[3] == 0x04) { symbol_t slaveAddress = getSlaveAddress(srcAddress); - addSeenAddress(slaveAddress); + notifyProtocolSeenAddress(slaveAddress); Message* message = m_messages->getScanMessage(slaveAddress); if (message && (message->getLastUpdateTime() == 0 || message->getLastSlaveData().getDataSize() < 10)) { // e.g. 10fe07040a b5564149303001248901 MasterSymbolString dummyMaster; istringstream input; - result_t result = message->prepareMaster(0, m_ownMasterAddress, SYN, UI_FIELD_SEPARATOR, &input, + result_t result = message->prepareMaster(0, m_protocol->getOwnMasterAddress(), SYN, UI_FIELD_SEPARATOR, &input, &dummyMaster); if (result == RESULT_OK) { SlaveSymbolString idData; @@ -1223,10 +414,7 @@ void BusHandler::messageCompleted() { logNotice(lf_update, "store broadcast ident: %s", getResultCode(result)); } } - } else if (master) { - logInfo(lf_update, "%s MM cmd: %s", prefix, command.getStr().c_str()); - } else { - logInfo(lf_update, "%s MS cmd: %s / %s", prefix, command.getStr().c_str(), response.getStr().c_str()); + } else if (!master) { if (command.size() >= 5 && command[2] == 0x07 && command[3] == 0x04) { Message* message = m_messages->getScanMessage(dstAddress); if (message && (message->getLastUpdateTime() == 0 || message->getLastSlaveData().getDataSize() < 10)) { @@ -1253,11 +441,10 @@ void BusHandler::messageCompleted() { } m_grabbedMessages[key].setLastData(command, response); } + const char* prefix = sent ? "sent" : "received"; if (message == nullptr) { - if (dstAddress == BROADCAST) { - logNotice(lf_update, "%s unknown BC cmd: %s", prefix, command.getStr().c_str()); - } else if (master) { - logNotice(lf_update, "%s unknown MM cmd: %s", prefix, command.getStr().c_str()); + if (dstAddress == BROADCAST || master) { + logNotice(lf_update, "%s unknown %s cmd: %s", prefix, master ? "MM" : "BC", command.getStr().c_str()); } else { logNotice(lf_update, "%s unknown MS cmd: %s / %s", prefix, command.getStr().c_str(), response.getStr().c_str()); @@ -1280,7 +467,7 @@ void BusHandler::messageCompleted() { command.getStr().c_str(), response.getStr().c_str(), getResultCode(result)); } else { string data = output.str(); - if (m_answer && dstAddress == (master ? m_ownMasterAddress : m_ownSlaveAddress)) { + if (m_protocol->isOwnAddress(dstAddress)) { logNotice(lf_update, "%s %s self-update %s %s QQ=%2.2x: %s", prefix, mode, circuit.c_str(), name.c_str(), srcAddress, data.c_str()); // TODO store in database of internal variables } else if (message->getDstAddress() == SYN) { // any destination @@ -1307,7 +494,7 @@ result_t BusHandler::prepareScan(symbol_t slave, bool full, const string& levels if (scanMessage == nullptr) { return RESULT_ERR_NOTFOUND; } - if (m_device->isReadOnly()) { + if (m_protocol->getDevice()->isReadOnly()) { return RESULT_OK; } deque messages; @@ -1353,7 +540,7 @@ result_t BusHandler::prepareScan(symbol_t slave, bool full, const string& levels return RESULT_OK; } *request = new ScanRequest(slave == SYN, m_messages, messages, slaves, this, *reload ? 0 : 1); - result_t result = (*request)->prepare(m_ownMasterAddress); + result_t result = (*request)->prepare(m_protocol->getOwnMasterAddress()); if (result < RESULT_OK) { delete *request; *request = nullptr; @@ -1377,7 +564,8 @@ result_t BusHandler::startScan(bool full, const string& levels) { } m_scanResults.clear(); m_runningScans++; - m_nextRequests.push(request); + m_protocol->addRequest(request, false); + // request is deleted by ProtocolHandler after finish return RESULT_OK; } @@ -1447,7 +635,7 @@ void BusHandler::formatScanResult(ostringstream* output) const { void BusHandler::formatSeenInfo(ostringstream* output) const { symbol_t address = 0; for (int index = 0; index < 256; index++, address++) { - bool ownAddress = !m_device->isReadOnly() && (address == m_ownMasterAddress || address == m_ownSlaveAddress); + bool ownAddress = m_protocol->isOwnAddress(address); if (!isValidAddress(address, false) || ((m_seenAddresses[address]&SEEN) == 0 && !ownAddress)) { continue; } @@ -1465,10 +653,10 @@ void BusHandler::formatSeenInfo(ostringstream* output) const { } if (ownAddress) { *output << ", ebusd"; - if (m_answer) { + if (m_protocol->isAnswering()) { *output << " (answering)"; } - if (m_addressConflict && (m_seenAddresses[address]&SEEN) != 0) { + if (m_protocol->isAddressConflict(address)) { *output << ", conflict"; } } @@ -1511,14 +699,14 @@ void BusHandler::formatSeenInfo(ostringstream* output) const { } void BusHandler::formatUpdateInfo(ostringstream* output) const { - if (hasSignal()) { - *output << ",\"s\":" << m_maxSymPerSec; + if (m_protocol->hasSignal()) { + *output << ",\"s\":" << m_protocol->getMaxSymPerSec(); } - *output << ",\"c\":" << m_masterCount + *output << ",\"c\":" << m_protocol->getMasterCount() << ",\"m\":" << m_messages->size() - << ",\"ro\":" << (m_device->isReadOnly() ? 1 : 0) - << ",\"an\":" << (m_answer ? 1 : 0) - << ",\"co\":" << (m_addressConflict ? 1 : 0); + << ",\"ro\":" << (m_protocol->getDevice()->isReadOnly() ? 1 : 0) + << ",\"an\":" << (m_protocol->isAnswering() ? 1 : 0) + << ",\"co\":" << (m_protocol->isAddressConflict(SYN) ? 1 : 0); if (m_grabMessages) { size_t unknownCnt = 0; *output << ",\"gm\":["; @@ -1539,7 +727,8 @@ void BusHandler::formatUpdateInfo(ostringstream* output) const { } unsigned char address = 0; for (int index = 0; index < 256; index++, address++) { - bool ownAddress = !m_device->isReadOnly() && (address == m_ownMasterAddress || address == m_ownSlaveAddress); + bool ownAddress = !m_protocol->getDevice()->isReadOnly() + && (address == m_protocol->getOwnMasterAddress() || address == m_protocol->getOwnSlaveAddress()); if (!isValidAddress(address, false) || ((m_seenAddresses[address]&SEEN) == 0 && !ownAddress)) { continue; } @@ -1626,8 +815,7 @@ result_t BusHandler::scanAndWait(symbol_t dstAddress, bool loadScanConfig, bool m_scanResults[dstAddress].resize(1); } m_runningScans++; - m_nextRequests.push(request); - requestExecuted = m_finishedRequests.remove(request, true); + requestExecuted = m_protocol->addRequest(request, true); result = requestExecuted ? request->m_result : RESULT_ERR_TIMEOUT; delete request; request = nullptr; diff --git a/src/ebusd/bushandler.h b/src/ebusd/bushandler.h index de0074430..e0f6c6849 100755 --- a/src/ebusd/bushandler.h +++ b/src/ebusd/bushandler.h @@ -19,7 +19,6 @@ #ifndef EBUSD_BUSHANDLER_H_ #define EBUSD_BUSHANDLER_H_ -#include #include #include #include @@ -31,59 +30,16 @@ #include "lib/ebus/symbol.h" #include "lib/ebus/result.h" #include "lib/ebus/device.h" -#include "lib/utils/queue.h" -#include "lib/utils/thread.h" +#include "lib/ebus/protocol.h" namespace ebusd { /** @file ebusd/bushandler.h - * Classes, functions, and constants related to handling of symbols on the eBUS. - * - * The following table shows the possible states, symbols, and state transition - * depending on the kind of message to send/receive: - * @image html states.png "ebusd BusHandler states" + * Classes, functions, and constants related to handling messages on the eBUS. */ using std::string; -/** the default time [ms] for retrieving a symbol from an addressed slave. */ -#define SLAVE_RECV_TIMEOUT 15 - -/** the maximum allowed time [ms] for retrieving the AUTO-SYN symbol (45ms + 2*1,2% + 1 Symbol). */ -#define SYN_TIMEOUT 51 - -/** the time [ms] for determining bus signal availability (AUTO-SYN timeout * 5). */ -#define SIGNAL_TIMEOUT 250 - -/** the maximum duration [us] of a single symbol (Start+8Bit+Stop+Extra @ 2400Bd-2*1,2%). */ -#define SYMBOL_DURATION_MICROS 4700 - -/** the maximum duration [ms] of a single symbol (Start+8Bit+Stop+Extra @ 2400Bd-2*1,2%). */ -#define SYMBOL_DURATION 5 - -/** the maximum allowed time [ms] for retrieving back a sent symbol (2x symbol duration). */ -#define SEND_TIMEOUT ((int)((2*SYMBOL_DURATION_MICROS+999)/1000)) - -/** the possible bus states. */ -enum BusState { - bs_noSignal, //!< no signal on the bus - bs_skip, //!< skip all symbols until next @a SYN - bs_ready, //!< ready for next master (after @a SYN symbol, send/receive QQ) - bs_recvCmd, //!< receive command (ZZ, PBSB, master data) [passive set] - bs_recvCmdCrc, //!< receive command CRC [passive set] - bs_recvCmdAck, //!< receive command ACK/NACK [passive set + active set+get] - bs_recvRes, //!< receive response (slave data) [passive set + active get] - bs_recvResCrc, //!< receive response CRC [passive set + active get] - bs_recvResAck, //!< receive response ACK/NACK [passive set] - bs_sendCmd, //!< send command (ZZ, PBSB, master data) [active set+get] - bs_sendCmdCrc, //!< send command CRC [active set+get] - bs_sendResAck, //!< send response ACK/NACK [active get] - bs_sendCmdAck, //!< send command ACK/NACK [passive get] - bs_sendRes, //!< send response (slave data) [passive get] - bs_sendResCrc, //!< send response CRC [passive get] - bs_sendSyn, //!< send SYN for completed transfer [active set+get] -}; - /** bit for the seen state: seen. */ #define SEEN 0x01 @@ -101,47 +57,6 @@ enum BusState { class BusHandler; -/** - * Generic request for sending to and receiving from the bus. - */ -class BusRequest { - friend class BusHandler; - - public: - /** - * Constructor. - * @param master the master data @a MasterSymbolString to send. - * @param deleteOnFinish whether to automatically delete this @a BusRequest when finished. - */ - BusRequest(const MasterSymbolString& master, bool deleteOnFinish) - : m_master(master), m_busLostRetries(0), - m_deleteOnFinish(deleteOnFinish) {} - - /** - * Destructor. - */ - virtual ~BusRequest() {} - - /** - * Notify the request of the specified result. - * @param result the result of the request. - * @param slave the @a SlaveSymbolString received. - * @return true if the request needs to be restarted. - */ - virtual bool notify(result_t result, const SlaveSymbolString& slave) = 0; - - - protected: - /** the master data @a MasterSymbolString to send. */ - const MasterSymbolString& m_master; - - /** the number of times a send is repeated due to lost arbitration. */ - unsigned int m_busLostRetries; - - /** whether to automatically delete this @a BusRequest when finished. */ - const bool m_deleteOnFinish; -}; - /** * A poll @a BusRequest handled by @a BusHandler itself. @@ -259,39 +174,6 @@ class ScanRequest : public BusRequest { }; -/** - * An active @a BusRequest that can be waited for. - */ -class ActiveBusRequest : public BusRequest { - friend class BusHandler; - - public: - /** - * Constructor. - * @param master the master data @a MasterSymbolString to send. - * @param slave reference to @a SlaveSymbolString for filling in the received slave data. - */ - ActiveBusRequest(const MasterSymbolString& master, SlaveSymbolString* slave) - : BusRequest(master, false), m_result(RESULT_ERR_NO_SIGNAL), m_slave(slave) {} - - /** - * Destructor. - */ - virtual ~ActiveBusRequest() {} - - // @copydoc - bool notify(result_t result, const SlaveSymbolString& slave) override; - - - private: - /** the result of handling the request. */ - result_t m_result; - - /** reference to @a SlaveSymbolString for filling in the received slave data. */ - SlaveSymbolString* m_slave; -}; - - /** * Helper class for keeping track of grabbed messages. */ @@ -362,7 +244,7 @@ class GrabbedMessage { /** * Handles input from and output to the bus with respect to the eBUS protocol. */ -class BusHandler : public WaitThread { +class BusHandler : public ProtocolListener { public: /** * Construct a new instance. @@ -380,81 +262,38 @@ class BusHandler : public WaitThread { * @param pollInterval the interval in seconds in which poll messages are cycled, or 0 if disabled. */ BusHandler(Device* device, MessageMap* messages, ScanHelper* scanHelper, - symbol_t ownAddress, bool answer, - unsigned int busLostRetries, unsigned int failedSendRetries, - unsigned int busAcquireTimeout, unsigned int slaveRecvTimeout, - unsigned int lockCount, bool generateSyn, - unsigned int pollInterval) - : WaitThread(), m_device(device), m_reconnect(false), m_messages(messages), m_scanHelper(scanHelper), - m_ownMasterAddress(ownAddress), m_ownSlaveAddress(getSlaveAddress(ownAddress)), - m_answer(answer), m_addressConflict(false), - m_busLostRetries(busLostRetries), m_failedSendRetries(failedSendRetries), - m_busAcquireTimeout(busAcquireTimeout), m_slaveRecvTimeout(slaveRecvTimeout), - m_masterCount(device->isReadOnly()?0:1), m_autoLockCount(lockCount == 0), - m_lockCount(lockCount <= 3 ? 3 : lockCount), m_remainLockCount(m_autoLockCount ? 1 : 0), - m_generateSynInterval(generateSyn ? SYN_TIMEOUT*getMasterNumber(ownAddress)+SYMBOL_DURATION : 0), - m_pollInterval(pollInterval), m_symbolLatencyMin(-1), m_symbolLatencyMax(-1), m_arbitrationDelayMin(-1), - m_arbitrationDelayMax(-1), m_lastReceive(0), m_lastPoll(0), - m_currentRequest(nullptr), m_currentAnswering(false), m_runningScans(0), m_nextSendPos(0), - m_symPerSec(0), m_maxSymPerSec(0), - m_state(bs_noSignal), m_escape(0), m_crc(0), m_crcValid(false), m_repeat(false), + const ebus_protocol_config_t config, unsigned int pollInterval) + : m_messages(messages), m_scanHelper(scanHelper), + m_pollInterval(pollInterval), m_lastPoll(0), m_runningScans(0), m_grabMessages(true) { + m_protocol = ProtocolHandler::create(config, device, this); memset(m_seenAddresses, 0, sizeof(m_seenAddresses)); - m_lastSynReceiveTime.tv_sec = 0; - m_lastSynReceiveTime.tv_nsec = 0; } /** * Destructor. */ virtual ~BusHandler() { - stop(); - join(); - BusRequest* req; - while ((req = m_finishedRequests.pop()) != nullptr) { - delete req; - } - while ((req = m_nextRequests.pop()) != nullptr) { - if (req->m_deleteOnFinish) { - delete req; - } - } - if (m_currentRequest != nullptr) { - delete m_currentRequest; - m_currentRequest = nullptr; + if (m_protocol) { + delete m_protocol; + m_protocol = nullptr; } } /** - * @return the @a Device instance for accessing the bus. + * @return the @a ProtocolHandler instance for accessing the bus. */ - const Device* getDevice() const { return m_device; } + ProtocolHandler* getProtocol() const { return m_protocol; } /** - * Clear stored values (e.g. scan results). + * @return the @a Device instance for accessing the bus. */ - void clear(); + const Device* getDevice() const { return m_protocol->getDevice(); } /** - * Inject a message from outside and treat it as regularly retrieved from the bus. - * @param master the @a MasterSymbolString with the master data. - * @param slave the @a SlaveSymbolString with the slave data. - */ - void injectMessage(const MasterSymbolString& master, const SlaveSymbolString& slave) { - m_command = master; - m_response = slave; - m_addressConflict = true; // avoid conflict messages - messageCompleted(); - m_addressConflict = false; - } - - /** - * Send a message on the bus and wait for the answer. - * @param master the @a MasterSymbolString with the master data to send. - * @param slave the @a SlaveSymbolString that will be filled with retrieved slave data. - * @return the result code. + * Clear stored values (e.g. scan results). */ - result_t sendAndWait(const MasterSymbolString& master, SlaveSymbolString* slave); + void clear(); /** * Prepare the master part for the @a Message, send it to the bus and wait for the answer. @@ -467,11 +306,6 @@ class BusHandler : public WaitThread { result_t readFromBus(Message* message, const string& inputStr, symbol_t dstAddress = SYN, symbol_t srcAddress = SYN); - /** - * Main thread entry. - */ - virtual void run(); - /** * Initiate a scan of the slave addresses. * @param full true for a full scan (all slaves), false for scanning only already seen slaves. @@ -560,59 +394,6 @@ class BusHandler : public WaitThread { void formatGrabResult(bool unknown, OutputFormat outputFormat, ostringstream* output, bool isDirectMode = false, time_t since = 0, time_t until = 0) const; - /** - * Return true when a signal on the bus is available. - * @return true when a signal on the bus is available. - */ - bool hasSignal() const { return m_state != bs_noSignal; } - - /** - * Reconnect the device. - */ - void reconnect() { m_reconnect = true; } - - /** - * Return the current symbol rate. - * @return the number of received symbols in the last second. - */ - unsigned int getSymbolRate() const { return m_symPerSec; } - - /** - * Return the maximum seen symbol rate. - * @return the maximum number of received symbols per second ever seen. - */ - unsigned int getMaxSymbolRate() const { return m_maxSymPerSec; } - - /** - * Return the minimal measured latency between send and receive of a symbol. - * @return the minimal measured latency between send and receive of a symbol in milliseconds, -1 if not yet known. - */ - int getMinSymbolLatency() const { return m_symbolLatencyMin; } - - /** - * Return the maximal measured latency between send and receive of a symbol. - * @return the maximal measured latency between send and receive of a symbol in milliseconds, -1 if not yet known. - */ - int getMaxSymbolLatency() const { return m_symbolLatencyMax; } - - /** - * Return the minimal measured delay between received SYN and sent own master address in microseconds. - * @return the minimal measured delay between received SYN and sent own master address in microseconds, -1 if not yet known. - */ - int getMinArbitrationDelay() const { return m_arbitrationDelayMin; } - - /** - * Return the maximal measured delay between received SYN and sent own master address in microseconds. - * @return the maximal measured delay between received SYN and sent own master address in microseconds, -1 if not yet known. - */ - int getMaxArbitrationDelay() const { return m_arbitrationDelayMax; } - - /** - * Return the number of masters already seen. - * @return the number of masters already seen (including ebusd itself). - */ - unsigned int getMasterCount() const { return m_masterCount; } - /** * Get the next slave address that still needs to be scanned or loaded. * @param lastAddress the last returned slave address, or 0 for returning the first one. @@ -628,42 +409,19 @@ class BusHandler : public WaitThread { */ void setScanConfigLoaded(symbol_t address, const string& file); + // @copydoc + void notifyProtocolStatus(bool signal) override; - private: - /** - * Handle the next symbol on the bus. - * @return RESULT_OK on success, or an error code. - */ - result_t handleSymbol(); - - /** - * Set a new @a BusState and add a log message if necessary. - * @param state the new @a BusState. - * @param result the result code. - * @param firstRepetition true if the first repetition of a message part is being started. - * @return the result code. - */ - result_t setState(BusState state, result_t result, bool firstRepetition = false); - - /** - * Add a seen bus address. - * @param address the seen bus address. - * @return true if a conflict with the own addresses was detected, false otherwise. - */ - bool addSeenAddress(symbol_t address); + // @copydoc + result_t notifyProtocolAnswer(const MasterSymbolString& master, SlaveSymbolString* slave) override; - /** - * Called to measure the latency between send and receive of a symbol. - * @param sentTime the time the symbol was sent. - * @param recvTime the time the symbol was received. - */ - void measureLatency(struct timespec* sentTime, struct timespec* recvTime); + // @copydoc + void notifyProtocolSeenAddress(symbol_t address) override; - /** - * Called when a message sending or reception was successfully completed. - */ - void messageCompleted(); + // @copydoc + void notifyProtocolMessage(bool sent, const MasterSymbolString& master, const SlaveSymbolString& slave) override; + private: /** * Prepare a @a ScanRequest. * @param slave the single slave address to scan, or @a SYN for multiple. @@ -675,11 +433,8 @@ class BusHandler : public WaitThread { */ result_t prepareScan(symbol_t slave, bool full, const string& levels, bool* reload, ScanRequest** request); - /** the @a Device instance for accessing the bus. */ - Device* m_device; - - /** set to @p true when the device shall be reconnected. */ - bool m_reconnect; + /** the @a ProtocolHandler instance for accessing the bus. */ + ProtocolHandler* m_protocol; /** the @a MessageMap instance with all known @a Message instances. */ MessageMap* m_messages; @@ -687,121 +442,15 @@ class BusHandler : public WaitThread { /** the @a ScanHelper instance. */ ScanHelper* m_scanHelper; - /** the own master address. */ - const symbol_t m_ownMasterAddress; - - /** the own slave address. */ - const symbol_t m_ownSlaveAddress; - - /** whether to answer queries for the own master/slave address. */ - const bool m_answer; - - /** set to @p true once an address conflict with the own addresses was detected. */ - bool m_addressConflict; - - /** the number of times a send is repeated due to lost arbitration. */ - const unsigned int m_busLostRetries; - - /** the number of times a failed send is repeated (other than lost arbitration). */ - const unsigned int m_failedSendRetries; - - /** the maximum time in milliseconds for bus acquisition. */ - const unsigned int m_busAcquireTimeout; - - /** the maximum time in milliseconds an addressed slave is expected to acknowledge. */ - const unsigned int m_slaveRecvTimeout; - - /** the number of masters already seen. */ - unsigned int m_masterCount; - - /** whether m_lockCount shall be detected automatically. */ - const bool m_autoLockCount; - - /** the number of AUTO-SYN symbols before sending is allowed after lost arbitration. */ - unsigned int m_lockCount; - - /** the remaining number of AUTO-SYN symbols before sending is allowed again. */ - unsigned int m_remainLockCount; - - /** the interval in milliseconds after which to generate an AUTO-SYN symbol, or 0 if disabled. */ - unsigned int m_generateSynInterval; - /** the interval in seconds in which poll messages are cycled, or 0 if disabled. */ const unsigned int m_pollInterval; - /** the minimal measured latency between send and receive of a symbol in milliseconds, -1 if not yet known. */ - int m_symbolLatencyMin; - - /** the maximal measured latency between send and receive of a symbol in milliseconds, -1 if not yet known. */ - int m_symbolLatencyMax; - - /** - * the minimal measured delay between received SYN and sent own master address in microseconds, - * -1 if not yet known. - */ - int m_arbitrationDelayMin; - - /** - * the maximal measured delay between received SYN and sent own master address in microseconds, - * -1 if not yet known. - */ - int m_arbitrationDelayMax; - - /** the time of the last received SYN symbol, or 0 for never. */ - struct timespec m_lastSynReceiveTime; - - /** the time of the last received symbol, or 0 for never. */ - time_t m_lastReceive; - /** the time of the last poll, or 0 for never. */ time_t m_lastPoll; - /** the queue of @a BusRequests that shall be handled. */ - Queue m_nextRequests; - - /** the currently handled BusRequest, or nullptr. */ - BusRequest* m_currentRequest; - - /** whether currently answering a request from another participant. */ - bool m_currentAnswering; - - /** the queue of @a BusRequests that are already finished. */ - Queue m_finishedRequests; - /** the number of scan requests currently running. */ unsigned int m_runningScans; - /** the offset of the next symbol that needs to be sent from the command or response, - * (only relevant if m_request is set and state is @a bs_command or @a bs_response). */ - size_t m_nextSendPos; - - /** the number of received symbols in the last second. */ - unsigned int m_symPerSec; - - /** the maximum number of received symbols per second ever seen. */ - unsigned int m_maxSymPerSec; - - /** the current @a BusState. */ - BusState m_state; - - /** 0 when not escaping/unescaping, or @a ESC when receiving, or the original value when sending. */ - symbol_t m_escape; - - /** the calculated CRC. */ - symbol_t m_crc; - - /** whether the CRC matched. */ - bool m_crcValid; - - /** whether the current message part is being repeated. */ - bool m_repeat; - - /** the received command @a MasterSymbolString. */ - MasterSymbolString m_command; - - /** the received response @a SlaveSymbolString or response to send. */ - SlaveSymbolString m_response; - /** the participating bus addresses seen so far (0 if not seen yet, or combination of @a SEEN bits). */ symbol_t m_seenAddresses[256]; diff --git a/src/ebusd/knxhandler.cpp b/src/ebusd/knxhandler.cpp index 3a4113e2d..2e73a8d1e 100644 --- a/src/ebusd/knxhandler.cpp +++ b/src/ebusd/knxhandler.cpp @@ -609,7 +609,7 @@ void KnxHandler::handleGroupTelegram(knx_addr_t src, knx_addr_t dest, int len, c sendGlobalValue(GLOBAL_UPTIME, static_cast(time(nullptr) - m_start), true); break; case GLOBAL_SIGNAL: - sendGlobalValue(GLOBAL_SIGNAL, m_busHandler->hasSignal() ? 1 : 0, true); + sendGlobalValue(GLOBAL_SIGNAL, m_busHandler->getProtocol()->hasSignal() ? 1 : 0, true); break; case GLOBAL_SCAN: sendGlobalValue(GLOBAL_SCAN, m_lastScanStatus == SCAN_STATUS_RUNNING ? 1 : 0, true); @@ -893,7 +893,7 @@ void KnxHandler::run() { time(&lastTaskRun); } if (sendSignal) { - if (m_busHandler->hasSignal()) { + if (m_busHandler->getProtocol()->hasSignal()) { lastSignal = now; if (!signal || reconnected) { signal = true; diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index 2269a5709..dc99f5c2f 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -412,7 +412,7 @@ int main(int argc, char* argv[], char* envp[]) { if (!s_scanHelper->parseMessage(argv[arg_index++], false, &master, &slave)) { continue; } - busHandler->injectMessage(master, slave); + busHandler->getProtocol()->injectMessage(master, slave); if (s_opt.scanConfig && master.size() >= 5 && master[4] == 0 && master[2] == 0x07 && master[3] == 0x04 && isValidAddress(master[1], false) && !isMaster(master[1]) && !scanAddresses[master[1]]) { // scan message, simulate scanning diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index 48e83c4f7..5e0eec94c 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -151,13 +151,20 @@ MainLoop::MainLoop(const struct options& opt, Device *device, MessageMap* messag } } // create BusHandler + ebus_protocol_config_t config = { + .ownAddress = m_address, + .answer = opt.answer, + .busLostRetries = opt.acquireRetries, + .failedSendRetries = opt.sendRetries, + .busAcquireTimeout = opt.acquireTimeout, + .slaveRecvTimeout = opt.receiveTimeout, + .lockCount = opt.masterCount, + .generateSyn = opt.generateSyn, + }; m_busHandler = new BusHandler(m_device, m_messages, scanHelper, - m_address, opt.answer, - opt.acquireRetries, opt.sendRetries, - opt.acquireTimeout, opt.receiveTimeout, - opt.masterCount, opt.generateSyn, - opt.pollInterval); - m_busHandler->start("bushandler"); + config, opt.pollInterval); + m_protocol = m_busHandler->getProtocol(); + m_protocol->start("bushandler"); // create network m_htmlPath = opt.htmlPath; @@ -194,6 +201,7 @@ MainLoop::~MainLoop() { if (m_busHandler != nullptr) { delete m_busHandler; m_busHandler = nullptr; + m_protocol = nullptr; // ProtocolHandler is freed by BusHandler } if (m_device != nullptr) { delete m_device; @@ -255,16 +263,16 @@ void MainLoop::run() { lastTaskRun = now; } else if (!m_shutdown && now > lastTaskRun+taskDelay) { logDebug(lf_main, "performing regular tasks"); - if (m_busHandler->hasSignal()) { + if (m_protocol->hasSignal()) { lastSignal = now; } else if (lastSignal && now > lastSignal+RECONNECT_MISSING_SIGNAL) { lastSignal = 0; - m_busHandler->reconnect(); + m_protocol->reconnect(); m_reconnectCount++; } if (m_scanConfig && scanRetry <= m_scanRetries) { bool loadDelay = false; - if (m_initialScan != ESC && reload && m_busHandler->hasSignal()) { + if (m_initialScan != ESC && reload && m_protocol->hasSignal()) { loadDelay = true; result_t result; if (m_initialScan == SYN) { @@ -282,7 +290,7 @@ void MainLoop::run() { istringstream input; result = message->prepareMaster(0, m_address, SYN, UI_FIELD_SEPARATOR, &input, &master); if (result == RESULT_OK) { - result = m_busHandler->sendAndWait(master, &slave); + result = m_protocol->sendAndWait(master, &slave); } } else { result = RESULT_ERR_NOTFOUND; @@ -304,7 +312,7 @@ void MainLoop::run() { reload = false; } } - if (!loadDelay && m_busHandler->hasSignal()) { + if (!loadDelay && m_protocol->hasSignal()) { lastScanAddress = m_busHandler->getNextScanAddress(lastScanAddress, scanCompleted >= SCAN_REPEAT_COUNT); if (lastScanAddress == SYN) { taskDelay = 5; @@ -335,7 +343,7 @@ void MainLoop::run() { dataSink->notifyScanStatus(lastScanStatus); } } - } else if (reload && m_busHandler->hasSignal()) { + } else if (reload && m_protocol->hasSignal()) { reload = false; // execute initial instructions m_scanHelper->executeInstructions(m_busHandler); @@ -873,7 +881,7 @@ result_t MainLoop::executeRead(const vector& args, const string& levels, // send message SlaveSymbolString slave; - ret = m_busHandler->sendAndWait(master, &slave); + ret = m_protocol->sendAndWait(master, &slave); if (ret == RESULT_OK) { ret = message->storeLastData(master, slave); @@ -1095,7 +1103,7 @@ result_t MainLoop::executeWrite(const vector& args, const string levels, } // send message SlaveSymbolString slave; - ret = m_busHandler->sendAndWait(master, &slave); + ret = m_protocol->sendAndWait(master, &slave); if (ret == RESULT_OK) { // also update read messages @@ -1227,7 +1235,7 @@ result_t MainLoop::parseHexAndSend(const vector& args, size_t& argPos, b // send message SlaveSymbolString slave; - ret = m_busHandler->sendAndWait(master, &slave); + ret = m_protocol->sendAndWait(master, &slave); if (ret == RESULT_OK) { if (master[1] == BROADCAST) { @@ -1601,11 +1609,11 @@ result_t MainLoop::executeState(const vector& args, ostringstream* ostre " Report bus state."; return RESULT_OK; } - if (m_busHandler->hasSignal()) { + if (m_protocol->hasSignal()) { *ostream << "signal acquired, " - << m_busHandler->getSymbolRate() << " symbols/sec (" - << m_busHandler->getMaxSymbolRate() << " max), " - << m_busHandler->getMasterCount() << " masters"; + << m_protocol->getSymbolRate() << " symbols/sec (" + << m_protocol->getMaxSymbolRate() << " max), " + << m_protocol->getMasterCount() << " masters"; return RESULT_OK; } return RESULT_ERR_NO_SIGNAL; @@ -1818,7 +1826,7 @@ result_t MainLoop::executeScan(const vector& args, const string& levels, return RESULT_OK; } - if (!m_busHandler->hasSignal()) { + if (!m_protocol->hasSignal()) { return RESULT_ERR_NO_SIGNAL; } result_t result; @@ -1941,17 +1949,17 @@ result_t MainLoop::executeInfo(const vector& args, const string& user, o if (!user.empty() || !levels.empty()) { *ostream << "access: " << levels << "\n"; } - if (m_busHandler->hasSignal()) { + if (m_protocol->hasSignal()) { *ostream << "signal: acquired\n" - << "symbol rate: " << m_busHandler->getSymbolRate() << "\n" - << "max symbol rate: " << m_busHandler->getMaxSymbolRate() << "\n"; - if (m_busHandler->getMinArbitrationDelay() >= 0) { - *ostream << "min arbitration micros: " << m_busHandler->getMinArbitrationDelay() << "\n" - << "max arbitration micros: " << m_busHandler->getMaxArbitrationDelay() << "\n"; + << "symbol rate: " << m_protocol->getSymbolRate() << "\n" + << "max symbol rate: " << m_protocol->getMaxSymbolRate() << "\n"; + if (m_protocol->getMinArbitrationDelay() >= 0) { + *ostream << "min arbitration micros: " << m_protocol->getMinArbitrationDelay() << "\n" + << "max arbitration micros: " << m_protocol->getMaxArbitrationDelay() << "\n"; } - if (m_busHandler->getMinSymbolLatency() >= 0) { - *ostream << "min symbol latency: " << m_busHandler->getMinSymbolLatency() << "\n" - << "max symbol latency: " << m_busHandler->getMaxSymbolLatency() << "\n"; + if (m_protocol->getMinSymbolLatency() >= 0) { + *ostream << "min symbol latency: " << m_protocol->getMinSymbolLatency() << "\n" + << "max symbol latency: " << m_protocol->getMaxSymbolLatency() << "\n"; } if (m_scanStatus != SCAN_STATUS_NONE) { *ostream << "scan: " << (m_scanStatus == SCAN_STATUS_FINISHED ? "finished" : "running"); @@ -1965,7 +1973,7 @@ result_t MainLoop::executeInfo(const vector& args, const string& user, o *ostream << "signal: no signal\n"; } *ostream << "reconnects: " << m_reconnectCount << "\n" - << "masters: " << m_busHandler->getMasterCount() << "\n" + << "masters: " << m_protocol->getMasterCount() << "\n" << "messages: " << m_messages->size() << "\n" << "conditional: " << m_messages->sizeConditional() << "\n" << "poll: " << m_messages->sizePoll() << "\n" @@ -2210,24 +2218,24 @@ result_t MainLoop::executeGet(const vector& args, bool* connected, ostri if (!user.empty() || !levels.empty()) { *ostream << ",\n \"access\": \"" << levels << "\""; } - *ostream << ",\n \"signal\": " << (m_busHandler->hasSignal() ? "true" : "false"); - if (m_busHandler->hasSignal()) { - *ostream << ",\n \"symbolrate\": " << m_busHandler->getSymbolRate() - << ",\n \"maxsymbolrate\": " << m_busHandler->getMaxSymbolRate(); - if (m_busHandler->getMinArbitrationDelay() >= 0) { - *ostream << ",\n \"minarbitrationmicros\": " << m_busHandler->getMinArbitrationDelay() - << ",\n \"maxarbitrationmicros\": " << m_busHandler->getMaxArbitrationDelay(); + *ostream << ",\n \"signal\": " << (m_protocol->hasSignal() ? "true" : "false"); + if (m_protocol->hasSignal()) { + *ostream << ",\n \"symbolrate\": " << m_protocol->getSymbolRate() + << ",\n \"maxsymbolrate\": " << m_protocol->getMaxSymbolRate(); + if (m_protocol->getMinArbitrationDelay() >= 0) { + *ostream << ",\n \"minarbitrationmicros\": " << m_protocol->getMinArbitrationDelay() + << ",\n \"maxarbitrationmicros\": " << m_protocol->getMaxArbitrationDelay(); } - if (m_busHandler->getMinSymbolLatency() >= 0) { - *ostream << ",\n \"minsymbollatency\": " << m_busHandler->getMinSymbolLatency() - << ",\n \"maxsymbollatency\": " << m_busHandler->getMaxSymbolLatency(); + if (m_protocol->getMinSymbolLatency() >= 0) { + *ostream << ",\n \"minsymbollatency\": " << m_protocol->getMinSymbolLatency() + << ",\n \"maxsymbollatency\": " << m_protocol->getMaxSymbolLatency(); } } if (!m_device->isReadOnly()) { *ostream << ",\n \"qq\": " << static_cast(m_address); } *ostream << ",\n \"reconnects\": " << m_reconnectCount - << ",\n \"masters\": " << m_busHandler->getMasterCount() + << ",\n \"masters\": " << m_protocol->getMasterCount() << ",\n \"messages\": " << m_messages->size() << ",\n \"lastup\": " << static_cast(maxLastUp) << "\n }" diff --git a/src/ebusd/mainloop.h b/src/ebusd/mainloop.h index d5efc5a00..4ef5ed086 100644 --- a/src/ebusd/mainloop.h +++ b/src/ebusd/mainloop.h @@ -443,6 +443,9 @@ class MainLoop : public Thread, DeviceListener { /** the created @a BusHandler instance. */ BusHandler* m_busHandler; + /** the created @a ProtocolHandler instance. */ + ProtocolHandler* m_protocol; + /** the reference to the @a Request @a Queue. */ Queue* m_requestQueue; diff --git a/src/ebusd/mqtthandler.cpp b/src/ebusd/mqtthandler.cpp index c63402553..bd7aff727 100755 --- a/src/ebusd/mqtthandler.cpp +++ b/src/ebusd/mqtthandler.cpp @@ -1041,7 +1041,7 @@ void MqttHandler::run() { time(&lastTaskRun); } if (sendSignal) { - if (m_busHandler->hasSignal()) { + if (m_busHandler->getProtocol()->hasSignal()) { lastSignal = now; if (!signal || reconnected) { signal = true; diff --git a/src/lib/ebus/CMakeLists.txt b/src/lib/ebus/CMakeLists.txt index 22b89d0c1..468260afb 100644 --- a/src/lib/ebus/CMakeLists.txt +++ b/src/lib/ebus/CMakeLists.txt @@ -7,6 +7,8 @@ set(libebus_a_SOURCES datatype.h datatype.cpp data.h data.cpp device.h device.cpp + protocol.h protocol.cpp + protocol_direct.h protocol_direct.cpp message.h message.cpp stringhelper.h stringhelper.cpp ) diff --git a/src/lib/ebus/Makefile.am b/src/lib/ebus/Makefile.am index 245ad7542..15b40a5e4 100644 --- a/src/lib/ebus/Makefile.am +++ b/src/lib/ebus/Makefile.am @@ -11,6 +11,8 @@ libebus_a_SOURCES = \ datatype.h datatype.cpp \ data.h data.cpp \ device.h device.cpp \ + protocol.h protocol.cpp \ + protocol_direct.h protocol_direct.cpp \ message.h message.cpp \ stringhelper.h stringhelper.cpp diff --git a/src/lib/ebus/protocol.cpp b/src/lib/ebus/protocol.cpp new file mode 100644 index 000000000..7842120a6 --- /dev/null +++ b/src/lib/ebus/protocol.cpp @@ -0,0 +1,140 @@ +/* + * ebusd - daemon for communication with eBUS heating systems. + * Copyright (C) 2014-2023 John Baier + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "lib/ebus/protocol.h" +#include "lib/ebus/protocol_direct.h" +#include +#include "lib/utils/log.h" + +namespace ebusd { + +bool ActiveBusRequest::notify(result_t result, const SlaveSymbolString& slave) { + if (result == RESULT_OK) { + string str = slave.getStr(); + logDebug(lf_bus, "read res: %s", str.c_str()); + } + m_result = result; + *m_slave = slave; + return false; +} + + +ProtocolHandler* ProtocolHandler::create(const ebus_protocol_config_t config, + Device* device, ProtocolListener* listener) { + return new DirectProtocolHandler(config, device, listener); +} + +void ProtocolHandler::clear() { + memset(m_seenAddresses, 0, sizeof(m_seenAddresses)); + m_masterCount = 1; +} + +bool ProtocolHandler::addRequest(BusRequest* request, bool wait) { + m_nextRequests.push(request); + return !wait || m_finishedRequests.remove(request, true); +} + +result_t ProtocolHandler::sendAndWait(const MasterSymbolString& master, SlaveSymbolString* slave) { + if (!hasSignal()) { + return RESULT_ERR_NO_SIGNAL; // don't wait when there is no signal + } + result_t result = RESULT_ERR_NO_SIGNAL; + slave->clear(); + ActiveBusRequest request(master, slave); + logInfo(lf_bus, "send message: %s", master.getStr().c_str()); + + for (int sendRetries = m_config.failedSendRetries + 1; sendRetries > 0; sendRetries--) { + bool success = addRequest(&request, true); + result = success ? request.m_result : RESULT_ERR_TIMEOUT; + if (result == RESULT_OK) { + break; + } + if (!success || result == RESULT_ERR_NO_SIGNAL || result == RESULT_ERR_SEND || result == RESULT_ERR_DEVICE) { + logError(lf_bus, "send to %2.2x: %s, give up", master[1], getResultCode(result)); + break; + } + logError(lf_bus, "send to %2.2x: %s%s", master[1], getResultCode(result), sendRetries > 1 ? ", retry" : ""); + request.m_busLostRetries = 0; + } + return result; +} + +void ProtocolHandler::measureLatency(struct timespec* sentTime, struct timespec* recvTime) { + int64_t latencyLong = (recvTime->tv_sec*1000000000 + recvTime->tv_nsec + - sentTime->tv_sec*1000000000 - sentTime->tv_nsec)/1000000; + if (latencyLong < 0 || latencyLong > 1000) { + return; // clock skew or out of reasonable range + } + auto latency = static_cast(latencyLong); + logDebug(lf_bus, "send/receive symbol latency %d ms", latency); + if (m_symbolLatencyMin >= 0 && (latency >= m_symbolLatencyMin && latency <= m_symbolLatencyMax)) { + return; + } + if (m_symbolLatencyMin == -1 || latency < m_symbolLatencyMin) { + m_symbolLatencyMin = latency; + } + if (m_symbolLatencyMax == -1 || latency > m_symbolLatencyMax) { + m_symbolLatencyMax = latency; + } + logInfo(lf_bus, "send/receive symbol latency %d - %d ms", m_symbolLatencyMin, m_symbolLatencyMax); +} + +bool ProtocolHandler::addSeenAddress(symbol_t address) { + if (!isValidAddress(address, false)) { + return false; + } + if (!isMaster(address)) { + if (!m_device->isReadOnly() && address == m_ownSlaveAddress) { + if (!m_addressConflict) { + m_addressConflict = true; + logError(lf_bus, "own slave address %2.2x is used by another participant", address); + } + } + if (!m_seenAddresses[address]) { + m_listener->notifyProtocolSeenAddress(address); + } + m_seenAddresses[address] = true; + address = getMasterAddress(address); + if (address == SYN) { + return false; + } + } + if (m_seenAddresses[address]) { + return false; + } + bool ret = false; + if (!m_device->isReadOnly() && address == m_ownMasterAddress) { + if (!m_addressConflict) { + m_addressConflict = true; + logError(lf_bus, "own master address %2.2x is used by another participant", address); + } + } else { + m_masterCount++; + ret = true; + logNotice(lf_bus, "new master %2.2x, master count %d", address, m_masterCount); + } + m_listener->notifyProtocolSeenAddress(address); + m_seenAddresses[address] = true; + return ret; +} + +} // namespace ebusd diff --git a/src/lib/ebus/protocol.h b/src/lib/ebus/protocol.h new file mode 100755 index 000000000..b7faa8e7e --- /dev/null +++ b/src/lib/ebus/protocol.h @@ -0,0 +1,477 @@ +/* + * ebusd - daemon for communication with eBUS heating systems. + * Copyright (C) 2014-2023 John Baier + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LIB_EBUS_PROTOCOL_H_ +#define LIB_EBUS_PROTOCOL_H_ + +#include "lib/ebus/symbol.h" +#include "lib/ebus/result.h" +#include "lib/ebus/device.h" +#include "lib/utils/queue.h" +#include "lib/utils/thread.h" + +namespace ebusd { + +/** @file lib/ebus/protocol.h + * Classes, functions, and constants related to handling the eBUS protocol. + */ + +/** the default time [ms] for retrieving a symbol from an addressed slave. */ +#define SLAVE_RECV_TIMEOUT 15 + +/** the maximum allowed time [ms] for retrieving the AUTO-SYN symbol (45ms + 2*1,2% + 1 Symbol). */ +#define SYN_TIMEOUT 51 + +/** the time [ms] for determining bus signal availability (AUTO-SYN timeout * 5). */ +#define SIGNAL_TIMEOUT 250 + +/** the maximum duration [us] of a single symbol (Start+8Bit+Stop+Extra @ 2400Bd-2*1,2%). */ +#define SYMBOL_DURATION_MICROS 4700 + +/** the maximum duration [ms] of a single symbol (Start+8Bit+Stop+Extra @ 2400Bd-2*1,2%). */ +#define SYMBOL_DURATION 5 + +/** the maximum allowed time [ms] for retrieving back a sent symbol (2x symbol duration). */ +#define SEND_TIMEOUT ((int)((2*SYMBOL_DURATION_MICROS+999)/1000)) + + +/** settings for the eBUS protocol handler. */ +typedef struct ebus_protocol_config { + /** the own master address. */ + symbol_t ownAddress; + /** whether to answer queries for the own master/slave address. */ + bool answer; + /** the number of times a send is repeated due to lost arbitration. */ + unsigned int busLostRetries; + /** the number of times a failed send is repeated (other than lost arbitration). */ + unsigned int failedSendRetries; + /** the maximum time in milliseconds for bus acquisition. */ + unsigned int busAcquireTimeout; + /** the maximum time in milliseconds an addressed slave is expected to acknowledge. */ + unsigned int slaveRecvTimeout; + /** the number of AUTO-SYN symbols before sending is allowed after lost arbitration, or 0 for auto detection. */ + unsigned int lockCount; + /** whether to enable AUTO-SYN symbol generation. */ + bool generateSyn; +} ebus_protocol_config_t; + + +class ProtocolHandler; + +/** + * Generic request for sending to and receiving from the bus. + */ +class BusRequest { + friend class ProtocolHandler; + + public: + /** + * Constructor. + * @param master the master data @a MasterSymbolString to send. + * @param deleteOnFinish whether to automatically delete this @a BusRequest when finished. + */ + BusRequest(const MasterSymbolString& master, bool deleteOnFinish) + : m_master(master), m_busLostRetries(0), + m_deleteOnFinish(deleteOnFinish) {} + + /** + * Destructor. + */ + virtual ~BusRequest() {} + + /** + * @return the master data @a MasterSymbolString to send. + */ + const MasterSymbolString& getMaster() const { return m_master; } + + /** + * @return the number of times a send was repeated due to lost arbitration. + */ + unsigned int getBusLostRetries() const { return m_busLostRetries; } + + /** + * Increment the number of times a send was repeated due to lost arbitration. + */ + void incrementBusLostRetries() { m_busLostRetries++; } + + /** + * Reset the number of times a send was repeated due to lost arbitration. + */ + void resetBusLostRetries() { m_busLostRetries = 0; } + + /** + * @return whether to automatically delete this @a BusRequest when finished. + */ + bool deleteOnFinish() const { return m_deleteOnFinish; } + + /** + * Notify the request of the specified result. + * @param result the result of the request. + * @param slave the @a SlaveSymbolString received. + * @return true if the request needs to be restarted. + */ + virtual bool notify(result_t result, const SlaveSymbolString& slave) = 0; // abstract + + + protected: + /** the master data @a MasterSymbolString to send. */ + const MasterSymbolString& m_master; + + /** the number of times a send was repeated due to lost arbitration. */ + unsigned int m_busLostRetries; + + /** whether to automatically delete this @a BusRequest when finished. */ + const bool m_deleteOnFinish; +}; + + +/** + * An active @a BusRequest that can be waited for. + */ +class ActiveBusRequest : public BusRequest { + friend class ProtocolHandler; + + public: + /** + * Constructor. + * @param master the master data @a MasterSymbolString to send. + * @param slave reference to @a SlaveSymbolString for filling in the received slave data. + */ + ActiveBusRequest(const MasterSymbolString& master, SlaveSymbolString* slave) + : BusRequest(master, false), m_result(RESULT_ERR_NO_SIGNAL), m_slave(slave) {} + + /** + * Destructor. + */ + virtual ~ActiveBusRequest() {} + + // @copydoc + bool notify(result_t result, const SlaveSymbolString& slave) override; + + + private: + /** the result of handling the request. */ + result_t m_result; + + /** reference to @a SlaveSymbolString for filling in the received slave data. */ + SlaveSymbolString* m_slave; +}; + + +/** + * Interface for listening to eBUS protocol data. + */ +class ProtocolListener { + public: + /** + * Destructor. + */ + virtual ~ProtocolListener() {} + + /** + * Called to notify a status update from the protocol. + * @param signal true when signal is acquired, false otherwise. + */ + virtual void notifyProtocolStatus(bool signal) = 0; // abstract + + /** + * Called to notify a new valid seen address on the bus. + * @param address the seen address. + */ + virtual void notifyProtocolSeenAddress(symbol_t address) = 0; // abstract + + /** + * Listener method that is called when a message was sent or received. + * @param sent true when the master part was actively sent, false if the whole message + * was received only. + * @param master the @a MasterSymbolString received. + * @param slave the @a SlaveSymbolString received. + */ + virtual void notifyProtocolMessage(bool sent, const MasterSymbolString& master, + const SlaveSymbolString& slave) = 0; // abstract + + /** + * Listener method that is called when in answer mode and a message targeting ourself was received. + * @param master the @a MasterSymbolString received. + * @param slave the @a SlaveSymbolString for writing the response to. + * @return @a RESULT_OK on success, or an error code. + */ + virtual result_t notifyProtocolAnswer(const MasterSymbolString& master, + SlaveSymbolString* slave) = 0; // abstract +}; + + + +/** + * Handles input from and output to eBUS with respect to the eBUS protocol. + */ +class ProtocolHandler : public WaitThread { + public: + /** + * Construct a new instance. + * @param config the configuration to use. + * @param device the @a Device instance for accessing the bus. + * @param listener the @a ProtocolListener. + */ + ProtocolHandler(const ebus_protocol_config_t config, + Device* device, ProtocolListener* listener) + : WaitThread(), m_config(config), m_device(device), m_listener(listener), + m_reconnect(false), + m_ownMasterAddress(config.ownAddress), m_ownSlaveAddress(getSlaveAddress(config.ownAddress)), + m_addressConflict(false), + m_masterCount(device->isReadOnly()?0:1), + m_symbolLatencyMin(-1), m_symbolLatencyMax(-1), m_arbitrationDelayMin(-1), + m_arbitrationDelayMax(-1), m_lastReceive(0), + m_symPerSec(0), m_maxSymPerSec(0) { + memset(m_seenAddresses, 0, sizeof(m_seenAddresses)); + } + + /** + * Destructor. + */ + virtual ~ProtocolHandler() { + stop(); + join(); + BusRequest* req; + while ((req = m_finishedRequests.pop()) != nullptr) { + delete req; + } + while ((req = m_nextRequests.pop()) != nullptr) { + if (req->m_deleteOnFinish) { + delete req; + } + } + } + + /** + * Create a new instance. + * @param config the configuration to use. + * @param device the @a Device instance for accessing the bus. + * @param listener the @a ProtocolListener. + * @return the new ProtocolHandler, or @a nullptr on error. + */ + static ProtocolHandler* create(const ebus_protocol_config_t config, Device* device, ProtocolListener* listener); + + /** + * @return the own master address. + */ + symbol_t getOwnMasterAddress() const { return m_ownMasterAddress; } + + /** + * @return the own slave address. + */ + symbol_t getOwnSlaveAddress() const { return m_ownSlaveAddress; } + + /** + * @return @p true if answering queries for the own master/slave address (if not readonly). + */ + bool isAnswering() const { return m_config.answer; } + + /** + * @param address the address to check. + * @return @p true when the address is the own master or slave address (if not readonly). + */ + bool isOwnAddress(symbol_t address) const { + return !m_device->isReadOnly() && (address == m_ownMasterAddress || address == m_ownSlaveAddress); + } + + /** + * @param address the own address to check for conflict or @a SYN for any. + * @return @p true when an address conflict with any of the own addresses or the specified own address was detected. + */ + bool isAddressConflict(symbol_t address) const { + return m_addressConflict && (address == SYN || m_seenAddresses[address]); + } + + /** + * @return the maximum number of received symbols per second ever seen. + */ + unsigned int getMaxSymPerSec() const { return m_maxSymPerSec; } + + /** + * @return the @a Device instance for accessing the bus. + */ + const Device* getDevice() const { return m_device; } + + /** + * Clear stored values (e.g. scan results). + */ + virtual void clear(); + + /** + * Inject a message from outside and treat it as regularly retrieved from the bus. + * This may only be called before bus handling was actually started. + * @param master the @a MasterSymbolString with the master data. + * @param slave the @a SlaveSymbolString with the slave data. + */ + virtual void injectMessage(const MasterSymbolString& master, const SlaveSymbolString& slave) = 0; // abstract + + /** + * Add a @a BusRequest to the internal queue and optionally wait for it to complete. + * @param request the @a BusRequest to add. + * @param wait true to wait for it to complete, false to return immediately. + * @return true when it was not waited for or when it was completed. + */ + virtual bool addRequest(BusRequest* request, bool wait); + + /** + * Send a message on the bus and wait for the answer. + * @param master the @a MasterSymbolString with the master data to send. + * @param slave the @a SlaveSymbolString that will be filled with retrieved slave data. + * @return the result code. + */ + virtual result_t sendAndWait(const MasterSymbolString& master, SlaveSymbolString* slave); + + /** + * Main thread entry. + */ + virtual void run() = 0; // abstract + + /** + * Return true when a signal on the bus is available. + * @return true when a signal on the bus is available. + */ + virtual bool hasSignal() const = 0; // abstract + + /** + * Reconnect the device. + */ + virtual void reconnect() { m_reconnect = true; } + + /** + * Return the current symbol rate. + * @return the number of received symbols in the last second. + */ + unsigned int getSymbolRate() const { return m_symPerSec; } + + /** + * Return the maximum seen symbol rate. + * @return the maximum number of received symbols per second ever seen. + */ + unsigned int getMaxSymbolRate() const { return m_maxSymPerSec; } + + /** + * Return the minimal measured latency between send and receive of a symbol. + * @return the minimal measured latency between send and receive of a symbol in milliseconds, -1 if not yet known. + */ + int getMinSymbolLatency() const { return m_symbolLatencyMin; } + + /** + * Return the maximal measured latency between send and receive of a symbol. + * @return the maximal measured latency between send and receive of a symbol in milliseconds, -1 if not yet known. + */ + int getMaxSymbolLatency() const { return m_symbolLatencyMax; } + + /** + * Return the minimal measured delay between received SYN and sent own master address in microseconds. + * @return the minimal measured delay between received SYN and sent own master address in microseconds, -1 if not yet known. + */ + int getMinArbitrationDelay() const { return m_arbitrationDelayMin; } + + /** + * Return the maximal measured delay between received SYN and sent own master address in microseconds. + * @return the maximal measured delay between received SYN and sent own master address in microseconds, -1 if not yet known. + */ + int getMaxArbitrationDelay() const { return m_arbitrationDelayMax; } + + /** + * Return the number of masters already seen. + * @return the number of masters already seen (including ebusd itself). + */ + unsigned int getMasterCount() const { return m_masterCount; } + + + protected: + /** + * Called to measure the latency between send and receive of a symbol. + * @param sentTime the time the symbol was sent. + * @param recvTime the time the symbol was received. + */ + virtual void measureLatency(struct timespec* sentTime, struct timespec* recvTime); + + /** + * Add a seen bus address. + * @param address the seen bus address. + * @return true if a conflict with the own addresses was detected, false otherwise. + */ + virtual bool addSeenAddress(symbol_t address); + + /** the client configuration to use. */ + const ebus_protocol_config_t m_config; + + /** the @a Device instance for accessing the bus. */ + Device* m_device; + + /** the @a ProtocolListener. */ + ProtocolListener *m_listener; + + /** set to @p true when the device shall be reconnected. */ + bool m_reconnect; + + /** the own master address. */ + const symbol_t m_ownMasterAddress; + + /** the own slave address. */ + const symbol_t m_ownSlaveAddress; + + /** set to @p true once an address conflict with the own addresses was detected. */ + bool m_addressConflict; + + /** the number of masters already seen. */ + unsigned int m_masterCount; + + /** the minimal measured latency between send and receive of a symbol in milliseconds, -1 if not yet known. */ + int m_symbolLatencyMin; + + /** the maximal measured latency between send and receive of a symbol in milliseconds, -1 if not yet known. */ + int m_symbolLatencyMax; + + /** + * the minimal measured delay between received SYN and sent own master address in microseconds, + * -1 if not yet known. + */ + int m_arbitrationDelayMin; + + /** + * the maximal measured delay between received SYN and sent own master address in microseconds, + * -1 if not yet known. + */ + int m_arbitrationDelayMax; + + /** the time of the last received symbol, or 0 for never. */ + time_t m_lastReceive; + + /** the queue of @a BusRequests that shall be handled. */ + Queue m_nextRequests; + + /** the queue of @a BusRequests that are already finished. */ + Queue m_finishedRequests; + + /** the number of received symbols in the last second. */ + unsigned int m_symPerSec; + + /** the maximum number of received symbols per second ever seen. */ + unsigned int m_maxSymPerSec; + + /** the participating bus addresses seen so far. */ + bool m_seenAddresses[256]; +}; + +} // namespace ebusd + +#endif // LIB_EBUS_PROTOCOL_H_ diff --git a/src/lib/ebus/protocol_direct.cpp b/src/lib/ebus/protocol_direct.cpp new file mode 100644 index 000000000..1b170b92d --- /dev/null +++ b/src/lib/ebus/protocol_direct.cpp @@ -0,0 +1,762 @@ +/* + * ebusd - daemon for communication with eBUS heating systems. + * Copyright (C) 2014-2023 John Baier + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "lib/ebus/protocol_direct.h" +#include "lib/utils/log.h" + +namespace ebusd { + +/** + * Return the string corresponding to the @a BusState. + * @param state the @a BusState. + * @return the string corresponding to the @a BusState. + */ +const char* getStateCode(BusState state) { + switch (state) { + case bs_noSignal: return "no signal"; + case bs_skip: return "skip"; + case bs_ready: return "ready"; + case bs_sendCmd: return "send command"; + case bs_recvCmdCrc: return "receive command CRC"; + case bs_recvCmdAck: return "receive command ACK"; + case bs_recvRes: return "receive response"; + case bs_recvResCrc: return "receive response CRC"; + case bs_sendResAck: return "send response ACK"; + case bs_recvCmd: return "receive command"; + case bs_recvResAck: return "receive response ACK"; + case bs_sendCmdCrc: return "send command CRC"; + case bs_sendCmdAck: return "send command ACK"; + case bs_sendRes: return "send response"; + case bs_sendResCrc: return "send response CRC"; + case bs_sendSyn: return "send SYN"; + default: return "unknown"; + } +} + + +void DirectProtocolHandler::run() { + unsigned int symCount = 0; + time_t now, lastTime; + time(&lastTime); + lastTime += 2; + logNotice(lf_bus, "bus started with own address %2.2x/%2.2x%s", m_ownMasterAddress, m_ownSlaveAddress, + m_config.answer?" in answer mode":""); + + do { + if (m_device->isValid() && !m_reconnect) { + result_t result = handleSymbol(); + time(&now); + if (result != RESULT_ERR_TIMEOUT && now >= lastTime) { + symCount++; + } + if (now > lastTime) { + m_symPerSec = symCount / (unsigned int)(now-lastTime); + if (m_symPerSec > m_maxSymPerSec) { + m_maxSymPerSec = m_symPerSec; + if (m_maxSymPerSec > 100) { + logNotice(lf_bus, "max. symbols per second: %d", m_maxSymPerSec); + } + } + lastTime = now; + symCount = 0; + } + } else { + if (!m_device->isValid()) { + logNotice(lf_bus, "device invalid"); + setState(bs_noSignal, RESULT_ERR_DEVICE); + } + if (!Wait(5)) { + break; + } + m_reconnect = false; + result_t result = m_device->open(); + if (result == RESULT_OK) { + logNotice(lf_bus, "re-opened %s", m_device->getName()); + } else { + logError(lf_bus, "unable to open %s: %s", m_device->getName(), getResultCode(result)); + setState(bs_noSignal, result); + } + symCount = 0; + m_symbolLatencyMin = m_symbolLatencyMax = m_arbitrationDelayMin = m_arbitrationDelayMax = -1; + time(&lastTime); + lastTime += 2; + } + } while (isRunning()); +} + +#ifndef FALLTHROUGH +#if defined(__GNUC__) && __GNUC__ >= 7 +#define FALLTHROUGH [[fallthrough]]; +#else +#define FALLTHROUGH +#endif +#endif + +result_t DirectProtocolHandler::handleSymbol() { + unsigned int timeout = SYN_TIMEOUT; + symbol_t sendSymbol = ESC; + bool sending = false; + + // check if another symbol has to be sent and determine timeout for receive + switch (m_state) { + case bs_noSignal: + timeout = m_generateSynInterval > 0 ? m_generateSynInterval : SIGNAL_TIMEOUT; + break; + + case bs_skip: + timeout = SYN_TIMEOUT; + FALLTHROUGH + case bs_ready: + if (m_currentRequest != nullptr) { + setState(bs_ready, RESULT_ERR_TIMEOUT); // just to be sure an old BusRequest is cleaned up + } + if (!m_device->isArbitrating() && m_currentRequest == nullptr && m_remainLockCount == 0) { + BusRequest* startRequest = m_nextRequests.peek(); + if (startRequest != nullptr) { // initiate arbitration + symbol_t master = startRequest->getMaster()[0]; + logDebug(lf_bus, "start request %2.2x", master); + result_t ret = m_device->startArbitration(master); + if (ret == RESULT_OK) { + logDebug(lf_bus, "arbitration start with %2.2x", master); + } else { + logError(lf_bus, "arbitration start: %s", getResultCode(ret)); + m_nextRequests.remove(startRequest); + m_currentRequest = startRequest; + setState(bs_ready, ret); // force the failed request to be notified + } + } + } + break; + + case bs_recvCmd: + case bs_recvCmdCrc: + timeout = m_config.slaveRecvTimeout; + break; + + case bs_recvCmdAck: + timeout = m_config.slaveRecvTimeout; + break; + + case bs_recvRes: + case bs_recvResCrc: + if (m_response.size() > 0 || m_config.slaveRecvTimeout > SYN_TIMEOUT) { + timeout = m_config.slaveRecvTimeout; + } else { + timeout = SYN_TIMEOUT; + } + break; + + case bs_recvResAck: + timeout = m_config.slaveRecvTimeout; + break; + + case bs_sendCmd: + if (m_currentRequest != nullptr) { + sendSymbol = m_currentRequest->getMaster()[m_nextSendPos]; // unescaped command + sending = true; + } + break; + + case bs_sendCmdCrc: + if (m_currentRequest != nullptr) { + sendSymbol = m_crc; + sending = true; + } + break; + + case bs_sendResAck: + if (m_currentRequest != nullptr) { + sendSymbol = m_crcValid ? ACK : NAK; + sending = true; + } + break; + + case bs_sendCmdAck: + if (m_config.answer) { + sendSymbol = m_crcValid ? ACK : NAK; + sending = true; + } + break; + + case bs_sendRes: + if (m_config.answer) { + sendSymbol = m_response[m_nextSendPos]; // unescaped response + sending = true; + } + break; + + case bs_sendResCrc: + if (m_config.answer) { + sendSymbol = m_crc; + sending = true; + } + break; + + case bs_sendSyn: + sendSymbol = SYN; + sending = true; + break; + } + + // send symbol if necessary + result_t result; + struct timespec sentTime, recvTime; + if (sending) { + if (m_state != bs_sendSyn && (sendSymbol == ESC || sendSymbol == SYN)) { + if (m_escape) { + sendSymbol = (symbol_t)(sendSymbol == ESC ? 0x00 : 0x01); + } else { + m_escape = sendSymbol; + sendSymbol = ESC; + } + } + result = m_device->send(sendSymbol); + clockGettime(&sentTime); + if (result == RESULT_OK) { + if (m_state == bs_ready) { + timeout = m_config.busAcquireTimeout; + } else { + timeout = SEND_TIMEOUT; + } + } else { + sending = false; + timeout = SYN_TIMEOUT; + setState(bs_skip, result); + } + } else { + clockGettime(&sentTime); // for measuring arbitration delay in enhanced protocol + } + + // receive next symbol (optionally check reception of sent symbol) + symbol_t recvSymbol; + ArbitrationState arbitrationState = as_none; + result = m_device->recv(timeout, &recvSymbol, &arbitrationState); + if (sending) { + clockGettime(&recvTime); + } + bool sentAutoSyn = false; + if (!sending && result == RESULT_ERR_TIMEOUT && m_generateSynInterval > 0 + && timeout >= m_generateSynInterval && (m_state == bs_noSignal || m_state == bs_skip)) { + // check if acting as AUTO-SYN generator is required + result = m_device->send(SYN); + if (result != RESULT_OK) { + return setState(bs_skip, result); + } + clockGettime(&sentTime); + recvSymbol = ESC; + result = m_device->recv(SEND_TIMEOUT, &recvSymbol, &arbitrationState); + clockGettime(&recvTime); + if (result != RESULT_OK) { + logError(lf_bus, "unable to receive sent AUTO-SYN symbol: %s", getResultCode(result)); + return setState(bs_noSignal, result); + } + if (recvSymbol != SYN) { + logError(lf_bus, "received %2.2x instead of AUTO-SYN symbol", recvSymbol); + return setState(bs_noSignal, result); + } + measureLatency(&sentTime, &recvTime); + if (m_generateSynInterval != SYN_TIMEOUT) { + // received own AUTO-SYN symbol back again: act as AUTO-SYN generator now + m_generateSynInterval = SYN_TIMEOUT; + logNotice(lf_bus, "acting as AUTO-SYN generator"); + } + m_remainLockCount = 0; + m_lastSynReceiveTime = recvTime; + sentAutoSyn = true; + setState(bs_ready, RESULT_OK); + } + switch (arbitrationState) { + case as_lost: + case as_timeout: + logDebug(lf_bus, arbitrationState == as_lost ? "arbitration lost" : "arbitration lost (timed out)"); + if (m_currentRequest == nullptr) { + BusRequest *startRequest = m_nextRequests.peek(); + if (startRequest != nullptr && m_nextRequests.remove(startRequest)) { + m_currentRequest = startRequest; // force the failed request to be notified + } + } + setState(m_state, RESULT_ERR_BUS_LOST); + break; + case as_won: // implies RESULT_OK + if (m_currentRequest != nullptr) { + logNotice(lf_bus, "arbitration won while handling another request"); + setState(bs_ready, RESULT_OK); // force the current request to be notified + } else { + BusRequest *startRequest = m_nextRequests.peek(); + if (m_state != bs_ready || startRequest == nullptr || !m_nextRequests.remove(startRequest)) { + logNotice(lf_bus, "arbitration won in invalid state %s", getStateCode(m_state)); + setState(bs_ready, RESULT_ERR_TIMEOUT); + } else { + logDebug(lf_bus, "arbitration won"); + m_currentRequest = startRequest; + sendSymbol = m_currentRequest->getMaster()[0]; + sending = true; + } + } + break; + case as_running: + break; + case as_error: + logError(lf_bus, "arbitration start error"); + // cancel request + if (!m_currentRequest) { + BusRequest *startRequest = m_nextRequests.peek(); + if (startRequest && m_nextRequests.remove(startRequest)) { + m_currentRequest = startRequest; + } + } + if (m_currentRequest) { + setState(m_state, RESULT_ERR_BUS_LOST); + } + break; + default: // only as_none + break; + } + if (sentAutoSyn && !sending) { + return RESULT_OK; + } + time_t now; + time(&now); + if (result != RESULT_OK) { + if ((m_generateSynInterval != SYN_TIMEOUT && difftime(now, m_lastReceive) > 1) + // at least one full second has passed since last received symbol + || m_state == bs_noSignal) { + return setState(bs_noSignal, result); + } + return setState(bs_skip, result); + } + + m_lastReceive = now; + if ((recvSymbol == SYN) && (m_state != bs_sendSyn)) { + if (!sending && m_remainLockCount > 0 && m_command.size() != 1) { + m_remainLockCount--; + } else if (!sending && m_remainLockCount == 0 && m_command.size() == 1) { + m_remainLockCount = 1; // wait for next AUTO-SYN after SYN / address / SYN (bus locked for own priority) + } + clockGettime(&m_lastSynReceiveTime); + return setState(bs_ready, m_state == bs_skip ? RESULT_OK : RESULT_ERR_SYN); + } + + if (sending && m_state != bs_ready) { // check received symbol for equality if not in arbitration + if (recvSymbol != sendSymbol) { + return setState(bs_skip, RESULT_ERR_SYMBOL); + } + measureLatency(&sentTime, &recvTime); + } + + switch (m_state) { + case bs_ready: + case bs_recvCmd: + case bs_recvRes: + case bs_sendCmd: + case bs_sendRes: + SymbolString::updateCrc(recvSymbol, &m_crc); + break; + default: + break; + } + + if (m_escape) { + // check escape/unescape state + if (sending) { + if (sendSymbol == ESC) { + return RESULT_OK; + } + sendSymbol = recvSymbol = m_escape; + } else { + if (recvSymbol > 0x01) { + return setState(bs_skip, RESULT_ERR_ESC); + } + recvSymbol = recvSymbol == 0x00 ? ESC : SYN; + } + m_escape = 0; + } else if (!sending && recvSymbol == ESC) { + m_escape = ESC; + return RESULT_OK; + } + + switch (m_state) { + case bs_noSignal: + return setState(bs_skip, RESULT_OK); + + case bs_skip: + return RESULT_OK; + + case bs_ready: + if (m_currentRequest != nullptr && sending) { + // check arbitration + if (recvSymbol == sendSymbol) { // arbitration successful + // measure arbitration delay + int64_t latencyLong = (sentTime.tv_sec*1000000000 + sentTime.tv_nsec + - m_lastSynReceiveTime.tv_sec*1000000000 - m_lastSynReceiveTime.tv_nsec)/1000; + if (latencyLong >= 0 && latencyLong <= 10000) { // skip clock skew or out of reasonable range + auto latency = static_cast(latencyLong); + logDebug(lf_bus, "arbitration delay %d micros", latency); + if (m_arbitrationDelayMin < 0 || (latency < m_arbitrationDelayMin || latency > m_arbitrationDelayMax)) { + if (m_arbitrationDelayMin == -1 || latency < m_arbitrationDelayMin) { + m_arbitrationDelayMin = latency; + } + if (m_arbitrationDelayMax == -1 || latency > m_arbitrationDelayMax) { + m_arbitrationDelayMax = latency; + } + logInfo(lf_bus, "arbitration delay %d - %d micros", m_arbitrationDelayMin, m_arbitrationDelayMax); + } + } + m_nextSendPos = 1; + m_repeat = false; + return setState(bs_sendCmd, RESULT_OK); + } + // arbitration lost. if same priority class found, try again after next AUTO-SYN + m_remainLockCount = isMaster(recvSymbol) ? 2 : 1; // number of SYN to wait for before next send try + if ((recvSymbol & 0x0f) != (sendSymbol & 0x0f) && m_lockCount > m_remainLockCount) { + // if different priority class found, try again after N AUTO-SYN symbols (at least next AUTO-SYN) + m_remainLockCount = m_lockCount; + } + setState(m_state, RESULT_ERR_BUS_LOST); // try again later + } + m_command.push_back(recvSymbol); + m_repeat = false; + return setState(bs_recvCmd, RESULT_OK); + + case bs_recvCmd: + m_command.push_back(recvSymbol); + if (m_command.isComplete()) { // all data received + return setState(bs_recvCmdCrc, RESULT_OK); + } + return RESULT_OK; + + case bs_recvCmdCrc: + m_crcValid = recvSymbol == m_crc; + if (m_command[1] == BROADCAST) { + if (m_crcValid) { + addSeenAddress(m_command[0]); + messageCompleted(); + return setState(bs_skip, RESULT_OK); + } + return setState(bs_skip, RESULT_ERR_CRC); + } + if (m_config.answer) { + symbol_t dstAddress = m_command[1]; + if (dstAddress == m_ownMasterAddress || dstAddress == m_ownSlaveAddress) { + if (m_crcValid) { + addSeenAddress(m_command[0]); + m_currentAnswering = true; + return setState(bs_sendCmdAck, RESULT_OK); + } + return setState(bs_sendCmdAck, RESULT_ERR_CRC); + } + } + if (m_crcValid) { + addSeenAddress(m_command[0]); + return setState(bs_recvCmdAck, RESULT_OK); + } + if (m_repeat) { + return setState(bs_skip, RESULT_ERR_CRC); + } + return setState(bs_recvCmdAck, RESULT_ERR_CRC); + + case bs_recvCmdAck: + if (recvSymbol == ACK) { + if (!m_crcValid) { + return setState(bs_skip, RESULT_ERR_ACK); + } + if (m_currentRequest != nullptr) { + if (isMaster(m_currentRequest->getMaster()[1])) { + messageCompleted(); + return setState(bs_sendSyn, RESULT_OK); + } + } else if (isMaster(m_command[1])) { + messageCompleted(); + return setState(bs_skip, RESULT_OK); + } + + m_repeat = false; + return setState(bs_recvRes, RESULT_OK); + } + if (recvSymbol == NAK) { + if (!m_repeat) { + m_repeat = true; + m_crc = 0; + m_nextSendPos = 0; + m_command.clear(); + if (m_currentRequest != nullptr) { + return setState(bs_sendCmd, RESULT_ERR_NAK, true); + } + return setState(bs_recvCmd, RESULT_ERR_NAK); + } + return setState(bs_skip, RESULT_ERR_NAK); + } + return setState(bs_skip, RESULT_ERR_ACK); + + case bs_recvRes: + m_response.push_back(recvSymbol); + if (m_response.isComplete()) { // all data received + return setState(bs_recvResCrc, RESULT_OK); + } + return RESULT_OK; + + case bs_recvResCrc: + m_crcValid = recvSymbol == m_crc; + if (m_crcValid) { + if (m_currentRequest != nullptr) { + return setState(bs_sendResAck, RESULT_OK); + } + return setState(bs_recvResAck, RESULT_OK); + } + if (m_repeat) { + if (m_currentRequest != nullptr) { + return setState(bs_sendSyn, RESULT_ERR_CRC); + } + return setState(bs_skip, RESULT_ERR_CRC); + } + if (m_currentRequest != nullptr) { + return setState(bs_sendResAck, RESULT_ERR_CRC); + } + return setState(bs_recvResAck, RESULT_ERR_CRC); + + case bs_recvResAck: + if (recvSymbol == ACK) { + if (!m_crcValid) { + return setState(bs_skip, RESULT_ERR_ACK); + } + messageCompleted(); + return setState(bs_skip, RESULT_OK); + } + if (recvSymbol == NAK) { + if (!m_repeat) { + m_repeat = true; + if (m_currentAnswering) { + m_nextSendPos = 0; + return setState(bs_sendRes, RESULT_ERR_NAK, true); + } + m_response.clear(); + return setState(bs_recvRes, RESULT_ERR_NAK, true); + } + return setState(bs_skip, RESULT_ERR_NAK); + } + return setState(bs_skip, RESULT_ERR_ACK); + + case bs_sendCmd: + if (!sending || m_currentRequest == nullptr) { + return setState(bs_skip, RESULT_ERR_INVALID_ARG); + } + m_nextSendPos++; + if (m_nextSendPos >= m_currentRequest->getMaster().size()) { + return setState(bs_sendCmdCrc, RESULT_OK); + } + return RESULT_OK; + + case bs_sendCmdCrc: + if (m_currentRequest->getMaster()[1] == BROADCAST) { + messageCompleted(); + return setState(bs_sendSyn, RESULT_OK); + } + m_crcValid = true; + return setState(bs_recvCmdAck, RESULT_OK); + + case bs_sendResAck: + if (!sending || m_currentRequest == nullptr) { + return setState(bs_skip, RESULT_ERR_INVALID_ARG); + } + if (!m_crcValid) { + if (!m_repeat) { + m_repeat = true; + m_response.clear(); + return setState(bs_recvRes, RESULT_ERR_NAK, true); + } + return setState(bs_sendSyn, RESULT_ERR_ACK); + } + messageCompleted(); + return setState(bs_sendSyn, RESULT_OK); + + case bs_sendCmdAck: + if (!sending || !m_config.answer) { + return setState(bs_skip, RESULT_ERR_INVALID_ARG); + } + if (!m_crcValid) { + if (!m_repeat) { + m_repeat = true; + m_crc = 0; + m_command.clear(); + return setState(bs_recvCmd, RESULT_ERR_NAK, true); + } + return setState(bs_skip, RESULT_ERR_ACK); + } + if (isMaster(m_command[1])) { + messageCompleted(); // TODO decode command and store value into database of internal variables + return setState(bs_skip, RESULT_OK); + } + + m_nextSendPos = 0; + m_repeat = false; + // build response and store in m_response for sending back to requesting master + m_response.clear(); + result = m_listener->notifyProtocolAnswer(m_command, &m_response); + if (result != RESULT_OK) { + return setState(bs_skip, result); + } + return setState(bs_sendRes, RESULT_OK); + + case bs_sendRes: + if (!sending || !m_config.answer) { + return setState(bs_skip, RESULT_ERR_INVALID_ARG); + } + m_nextSendPos++; + if (m_nextSendPos >= m_response.size()) { + // slave data completely sent + return setState(bs_sendResCrc, RESULT_OK); + } + return RESULT_OK; + + case bs_sendResCrc: + if (!sending || !m_config.answer) { + return setState(bs_skip, RESULT_ERR_INVALID_ARG); + } + return setState(bs_recvResAck, RESULT_OK); + + case bs_sendSyn: + if (!sending) { + return setState(bs_ready, RESULT_ERR_INVALID_ARG); + } + return setState(bs_ready, RESULT_OK); + } + return RESULT_OK; +} + +result_t DirectProtocolHandler::setState(BusState state, result_t result, bool firstRepetition) { + if (m_currentRequest != nullptr) { + if (result == RESULT_ERR_BUS_LOST && m_currentRequest->getBusLostRetries() < m_config.busLostRetries) { + logDebug(lf_bus, "%s during %s, retry", getResultCode(result), getStateCode(m_state)); + m_currentRequest->incrementBusLostRetries(); + m_nextRequests.push(m_currentRequest); // repeat + m_currentRequest = nullptr; + } else if (state == bs_sendSyn || (result != RESULT_OK && !firstRepetition)) { + logDebug(lf_bus, "notify request: %s", getResultCode(result)); + bool restart = m_currentRequest->notify( + result == RESULT_ERR_SYN && (m_state == bs_recvCmdAck || m_state == bs_recvRes) + ? RESULT_ERR_TIMEOUT : result, m_response); + if (restart) { + m_currentRequest->resetBusLostRetries(); + m_nextRequests.push(m_currentRequest); + } else if (m_currentRequest->deleteOnFinish()) { + delete m_currentRequest; + } else { + m_finishedRequests.push(m_currentRequest); + } + m_currentRequest = nullptr; + } + if (state == bs_skip) { + m_device->startArbitration(SYN); // reset arbitration state + } + } + + if (state == bs_noSignal) { // notify all requests + if (m_state != bs_noSignal) { + m_listener->notifyProtocolStatus(false); + } + m_response.clear(); // notify with empty response + while ((m_currentRequest = m_nextRequests.pop()) != nullptr) { + bool restart = m_currentRequest->notify(RESULT_ERR_NO_SIGNAL, m_response); + if (restart) { // should not occur with no signal + m_currentRequest->resetBusLostRetries(); + m_nextRequests.push(m_currentRequest); + } else if (m_currentRequest->deleteOnFinish()) { + delete m_currentRequest; + } else { + m_finishedRequests.push(m_currentRequest); + } + } + } else if (m_state == bs_noSignal) { + m_listener->notifyProtocolStatus(true); + } + + m_escape = 0; + if (state == m_state) { + return result; + } + if ((result < RESULT_OK && !(result == RESULT_ERR_TIMEOUT && state == bs_skip && m_state == bs_ready)) + || (result != RESULT_OK && state == bs_skip && m_state != bs_ready)) { + logDebug(lf_bus, "%s during %s, switching to %s", getResultCode(result), getStateCode(m_state), + getStateCode(state)); + } else if (m_currentRequest != nullptr || state == bs_sendCmd || state == bs_sendCmdCrc || state == bs_sendCmdAck + || state == bs_sendRes || state == bs_sendResCrc || state == bs_sendResAck || state == bs_sendSyn + || m_state == bs_sendSyn) { + logDebug(lf_bus, "switching from %s to %s", getStateCode(m_state), getStateCode(state)); + } + if (state == bs_noSignal) { + if (m_generateSynInterval == 0 || m_state != bs_skip) { + logError(lf_bus, "signal lost"); + } + } else if (m_state == bs_noSignal) { + if (m_generateSynInterval == 0 || state != bs_skip) { + logNotice(lf_bus, "signal acquired"); + } + } + m_state = state; + + if (state == bs_ready || state == bs_skip) { + m_command.clear(); + m_crc = 0; + m_crcValid = false; + m_response.clear(); + m_nextSendPos = 0; + m_currentAnswering = false; + } else if (state == bs_recvRes || state == bs_sendRes) { + m_crc = 0; + } + return result; +} + +bool DirectProtocolHandler::addSeenAddress(symbol_t address) { + if (!ProtocolHandler::addSeenAddress(address)) { + return false; + } + if (m_config.lockCount == 0 && m_masterCount > m_lockCount) { + m_lockCount = m_masterCount; + } + return true; +} + +void DirectProtocolHandler::messageCompleted() { + const char* prefix = m_currentRequest ? "sent" : "received"; + // do an explicit copy here in case being called by another thread + const MasterSymbolString command(m_currentRequest ? m_currentRequest->getMaster() : m_command); + const SlaveSymbolString response(m_response); + symbol_t srcAddress = command[0], dstAddress = command[1]; + if (srcAddress == dstAddress) { + logError(lf_bus, "invalid self-addressed message from %2.2x", srcAddress); + return; + } + if (!m_currentAnswering) { + addSeenAddress(dstAddress); + } + + bool master = isMaster(dstAddress); + if (dstAddress == BROADCAST || master) { + logInfo(lf_update, "%s %s cmd: %s", prefix, master ? "MM" : "BC", command.getStr().c_str()); + } else { + logInfo(lf_update, "%s MS cmd: %s / %s", prefix, command.getStr().c_str(), response.getStr().c_str()); + } + m_listener->notifyProtocolMessage(m_currentRequest != nullptr, command, response); +} + +} // namespace ebusd diff --git a/src/lib/ebus/protocol_direct.h b/src/lib/ebus/protocol_direct.h new file mode 100755 index 000000000..eb185b11d --- /dev/null +++ b/src/lib/ebus/protocol_direct.h @@ -0,0 +1,180 @@ +/* + * ebusd - daemon for communication with eBUS heating systems. + * Copyright (C) 2014-2023 John Baier + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LIB_EBUS_PROTOCOL_DIRECT_H_ +#define LIB_EBUS_PROTOCOL_DIRECT_H_ + +#include "lib/ebus/protocol.h" + +namespace ebusd { + +/** @file lib/ebus/protocol_direct.h + * Implementation of directly handled eBUS protocol. + * + * The following table shows the possible states, symbols, and state transition + * depending on the kind of message to send/receive: + * @image html states.png "ebusd direct ProtocolHandler states" + */ + +/** the possible bus states. */ +enum BusState { + bs_noSignal, //!< no signal on the bus + bs_skip, //!< skip all symbols until next @a SYN + bs_ready, //!< ready for next master (after @a SYN symbol, send/receive QQ) + bs_recvCmd, //!< receive command (ZZ, PBSB, master data) [passive set] + bs_recvCmdCrc, //!< receive command CRC [passive set] + bs_recvCmdAck, //!< receive command ACK/NACK [passive set + active set+get] + bs_recvRes, //!< receive response (slave data) [passive set + active get] + bs_recvResCrc, //!< receive response CRC [passive set + active get] + bs_recvResAck, //!< receive response ACK/NACK [passive set] + bs_sendCmd, //!< send command (ZZ, PBSB, master data) [active set+get] + bs_sendCmdCrc, //!< send command CRC [active set+get] + bs_sendResAck, //!< send response ACK/NACK [active get] + bs_sendCmdAck, //!< send command ACK/NACK [passive get] + bs_sendRes, //!< send response (slave data) [passive get] + bs_sendResCrc, //!< send response CRC [passive get] + bs_sendSyn, //!< send SYN for completed transfer [active set+get] +}; + + + +/** + * Directly handles input from and output to eBUS with respect to the eBUS protocol. + */ +class DirectProtocolHandler : public ProtocolHandler { + public: + /** + * Construct a new instance. + * @param config the configuration to use. + * @param device the @a Device instance for accessing the bus. + * @param listener the @a ProtocolListener. + */ + DirectProtocolHandler(const ebus_protocol_config_t config, + Device* device, ProtocolListener* listener) + : ProtocolHandler(config, device, listener), + m_lockCount(config.lockCount <= 3 ? 3 : config.lockCount), + m_remainLockCount(config.lockCount == 0 ? 1 : 0), + m_generateSynInterval(config.generateSyn ? SYN_TIMEOUT*getMasterNumber(config.ownAddress)+SYMBOL_DURATION : 0), + m_currentRequest(nullptr), m_currentAnswering(false), m_nextSendPos(0), + m_state(bs_noSignal), m_escape(0), m_crc(0), m_crcValid(false), m_repeat(false) { + m_lastSynReceiveTime.tv_sec = 0; + m_lastSynReceiveTime.tv_nsec = 0; + } + + /** + * Destructor. + */ + virtual ~DirectProtocolHandler() { + if (m_currentRequest != nullptr) { + delete m_currentRequest; + m_currentRequest = nullptr; + } + } + + // @copydoc + void injectMessage(const MasterSymbolString& master, const SlaveSymbolString& slave) override { + if (isRunning()) { + return; + } + m_command = master; + m_response = slave; + m_addressConflict = true; // avoid conflict messages + messageCompleted(); + m_addressConflict = false; + } + + /** + * Main thread entry. + */ + virtual void run(); + + // @copydoc + bool hasSignal() const override { return m_state != bs_noSignal; } + + + private: + /** + * Handle the next symbol on the bus. + * @return RESULT_OK on success, or an error code. + */ + result_t handleSymbol(); + + /** + * Set a new @a BusState and add a log message if necessary. + * @param state the new @a BusState. + * @param result the result code. + * @param firstRepetition true if the first repetition of a message part is being started. + * @return the result code. + */ + result_t setState(BusState state, result_t result, bool firstRepetition = false); + + // @copydoc + bool addSeenAddress(symbol_t address) override; + + /** + * Called when a message sending or reception was successfully completed. + */ + void messageCompleted(); + + /** the number of AUTO-SYN symbols before sending is allowed after lost arbitration. */ + unsigned int m_lockCount; + + /** the remaining number of AUTO-SYN symbols before sending is allowed again. */ + unsigned int m_remainLockCount; + + /** the interval in milliseconds after which to generate an AUTO-SYN symbol, or 0 if disabled. */ + unsigned int m_generateSynInterval; + + /** the time of the last received SYN symbol, or 0 for never. */ + struct timespec m_lastSynReceiveTime; + + /** the currently handled BusRequest, or nullptr. */ + BusRequest* m_currentRequest; + + /** whether currently answering a request from another participant. */ + bool m_currentAnswering; + + /** the offset of the next symbol that needs to be sent from the command or response, + * (only relevant if m_request is set and state is @a bs_command or @a bs_response). */ + size_t m_nextSendPos; + + /** the current @a BusState. */ + BusState m_state; + + /** 0 when not escaping/unescaping, or @a ESC when receiving, or the original value when sending. */ + symbol_t m_escape; + + /** the calculated CRC. */ + symbol_t m_crc; + + /** whether the CRC matched. */ + bool m_crcValid; + + /** whether the current message part is being repeated. */ + bool m_repeat; + + /** the received command @a MasterSymbolString. */ + MasterSymbolString m_command; + + /** the received response @a SlaveSymbolString or response to send. */ + SlaveSymbolString m_response; +}; + +} // namespace ebusd + +#endif // LIB_EBUS_PROTOCOL_DIRECT_H_ diff --git a/src/ebusd/states.png b/src/lib/ebus/states.png similarity index 100% rename from src/ebusd/states.png rename to src/lib/ebus/states.png From dc78076fbca82c618de877ece21cbca8dfd249bf Mon Sep 17 00:00:00 2001 From: John Date: Fri, 13 Oct 2023 23:50:20 +0200 Subject: [PATCH 144/345] support json index --- src/ebusd/scan.cpp | 5 ++-- src/lib/utils/httpclient.cpp | 48 +++++++++++++++++++++++++++++++++--- src/lib/utils/httpclient.h | 10 ++++++-- 3 files changed, 55 insertions(+), 8 deletions(-) diff --git a/src/ebusd/scan.cpp b/src/ebusd/scan.cpp index 2c42450f0..9e337fe05 100644 --- a/src/ebusd/scan.cpp +++ b/src/ebusd/scan.cpp @@ -70,8 +70,9 @@ result_t ScanHelper::collectConfigFiles(const string& relPath, const string& pre + "t=" + extension.substr(1) + query; string names; bool repeat = false; - if (!m_configHttpClient->get(uri, "", &names, &repeat)) { - if (!names.empty()) { + bool json = true; + if (!m_configHttpClient->get(uri, "", &names, &repeat, nullptr, &json)) { + if (!names.empty() || json) { logError(lf_main, "HTTP failure%s: %s", repeat ? ", repeating" : "", names.c_str()); names = ""; } diff --git a/src/lib/utils/httpclient.cpp b/src/lib/utils/httpclient.cpp index 65639514c..93fca1dd0 100755 --- a/src/lib/utils/httpclient.cpp +++ b/src/lib/utils/httpclient.cpp @@ -431,8 +431,8 @@ void HttpClient::disconnect() { } } -bool HttpClient::get(const string& uri, const string& body, string* response, bool* repeatable, time_t* time) { - return request("GET", uri, body, response, repeatable, time); +bool HttpClient::get(const string& uri, const string& body, string* response, bool* repeatable, time_t* time, bool* jsonString) { + return request("GET", uri, body, response, repeatable, time, jsonString); } bool HttpClient::post(const string& uri, const string& body, string* response, bool* repeatable) { @@ -447,7 +447,7 @@ const int indexToMonth[] = { }; bool HttpClient::request(const string& method, const string& uri, const string& body, string* response, -bool* repeatable, time_t* time) { +bool* repeatable, time_t* time, bool* jsonString) { if (!ensureConnected()) { *response = "not connected"; if (repeatable) { @@ -559,9 +559,49 @@ bool* repeatable, time_t* time) { *response = "invalid content length "; return false; } + bool isJson = headers.find("\r\nContent-Type: application/json") != string::npos; + if (pos == string::npos) { + disconnect(); + return true; + } pos = readUntil("", length, response); disconnect(); - return pos == length; + if (pos != length) { + return false; + } + if (jsonString && isJson && *jsonString && length >= 2 && response->at(0) == '"') { + // check for inline conversion of JSON to string expecting a single string to de-escape + pos = length; + while (pos > 1 && (response->at(pos-1) == '\r' || response->at(pos-1) == '\n')) { + pos--; + } + if (pos > 2 && response->at(pos-1) == '"') { + response->erase(pos-1); + response->erase(0, 1); + size_t from = 0; + while ((pos = response->find_first_of("\\", from)) != string::npos) { + response->erase(pos, 1); // pos is now pointing at the char behind the backslash + switch (response->at(pos)) { + case 'r': + response->erase(pos, 1); // removed + from = pos; + continue; + case 'n': + (*response)[pos] = '\n'; // replaced + from = pos+1; + break; + default: + from = pos+1; + break; // kept + } + } + isJson = false; + } + } + if (jsonString) { + *jsonString = isJson; + } + return true; } size_t HttpClient::readUntil(const string& delim, size_t length, string* result) { diff --git a/src/lib/utils/httpclient.h b/src/lib/utils/httpclient.h index 7bd3cf1f4..28a4afb0e 100755 --- a/src/lib/utils/httpclient.h +++ b/src/lib/utils/httpclient.h @@ -191,10 +191,13 @@ class HttpClient { * @param repeatable optional pointer to a bool in which to store whether the request should be repeated later on * (e.g. due to temporary connectivity issues). * @param time optional pointer to a @a time_t value for storing the modification time of the file, or nullptr. + * @param jsonString optional pointer to a bool value. When returning, it is set to whether the retrieved + * content-type indicates JSON. When true upon entry, content is JSON, and response is a single JSON string, it + * will be de-escaped to a pure string and the value set to false. * @return true on success, false on error. */ bool get(const string& uri, const string& body, string* response, bool* repeatable = nullptr, - time_t* time = nullptr); + time_t* time = nullptr, bool* jsonString = nullptr); /** * Execute a POST request. @@ -216,10 +219,13 @@ class HttpClient { * @param repeatable optional pointer to a bool in which to store whether the request should be repeated later on * (e.g. due to temporary connectivity issues). * @param time optional pointer to a @a time_t value for storing the modification time of the file, or nullptr. + * @param jsonString optional pointer to a bool value. When returning, it is set to whether the retrieved + * content-type indicates JSON. When true upon entry, content is JSON, and response is a single JSON string, it + * will be de-escaped to a pure string and the value set to false. * @return true on success, false on error. */ bool request(const string& method, const string& uri, const string& body, string* response, - bool* repeatable = nullptr, time_t* time = nullptr); + bool* repeatable = nullptr, time_t* time = nullptr, bool* jsonString = nullptr); private: /** From 8b302de3acb21c459f8a690f2eab272082419e01 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 14 Oct 2023 07:49:10 +0200 Subject: [PATCH 145/345] fix startup --- src/ebusd/main.cpp | 5 ++++- src/ebusd/mainloop.cpp | 1 - src/lib/ebus/protocol.h | 1 - test_coverage.sh | 3 +-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index dc99f5c2f..30fedb765 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -401,8 +401,8 @@ int main(int argc, char* argv[], char* envp[]) { // create the MainLoop and start it s_mainLoop = new MainLoop(s_opt, device, s_messageMap, s_scanHelper, s_requestQueue); + BusHandler* busHandler = s_mainLoop->getBusHandler(); if (s_opt.injectMessages) { - BusHandler* busHandler = s_mainLoop->getBusHandler(); int scanAdrCount = 0; bool scanAddresses[256] = {}; while (arg_index < argc) { @@ -420,6 +420,7 @@ int main(int argc, char* argv[], char* envp[]) { scanAdrCount++; } } + busHandler->getProtocol()->start("bushandler"); for (symbol_t address = 0; scanAdrCount > 0; address++) { if (scanAddresses[address]) { scanAdrCount--; @@ -430,6 +431,8 @@ int main(int argc, char* argv[], char* envp[]) { shutdown(); return 0; } + } else { + busHandler->getProtocol()->start("bushandler"); } s_mainLoop->start("mainloop"); diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index 5e0eec94c..e820e31d8 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -164,7 +164,6 @@ MainLoop::MainLoop(const struct options& opt, Device *device, MessageMap* messag m_busHandler = new BusHandler(m_device, m_messages, scanHelper, config, opt.pollInterval); m_protocol = m_busHandler->getProtocol(); - m_protocol->start("bushandler"); // create network m_htmlPath = opt.htmlPath; diff --git a/src/lib/ebus/protocol.h b/src/lib/ebus/protocol.h index b7faa8e7e..eb743b451 100755 --- a/src/lib/ebus/protocol.h +++ b/src/lib/ebus/protocol.h @@ -245,7 +245,6 @@ class ProtocolHandler : public WaitThread { * Destructor. */ virtual ~ProtocolHandler() { - stop(); join(); BusRequest* req; while ((req = m_finishedRequests.pop()) != nullptr) { diff --git a/test_coverage.sh b/test_coverage.sh index 8bb7a0dd9..c664125ea 100755 --- a/test_coverage.sh +++ b/test_coverage.sh @@ -247,7 +247,7 @@ elif [[ -n "$1" ]]; then pid=$(ps -C ebusd -o pid=) done else - $ebusd -d tcp:127.0.0.1:8876 --initsend --latency 10 -n -c "$PWD/contrib/etc/ebusd" --pollinterval=10 -s -a 31 --acquireretries 3 --answer --generatesyn --receivetimeout 40000 --sendretries 1 --enablehex --htmlpath "$PWD/contrib/html" --httpport 8878 --pidfile "$PWD/ebusd.pid" --localhost -p 8877 -l "$PWD/ebusd.log" --logareas all --loglevel debug --lograwdata=bytes --lograwdatafile "$PWD/ebusd.raw" --lograwdatasize 1 --dumpfile "$PWD/ebusd.dump" --dumpsize 100 -D --scanconfig --aclfile="$PWD/acl.csv" --mqttport=1883 --enablehex --enabledefine + $ebusd -d tcp:127.0.0.1:8876 --initsend --latency 10 -n -c "$PWD/contrib/etc/ebusd" --pollinterval=10 -s --scanretries=0 -a 31 --acquireretries 3 --answer --generatesyn --receivetimeout 40000 --sendretries 1 --enablehex --htmlpath "$PWD/contrib/html" --httpport 8878 --pidfile "$PWD/ebusd.pid" --localhost -p 8877 -l "$PWD/ebusd.log" --logareas all --loglevel debug --lograwdata=bytes --lograwdatafile "$PWD/ebusd.raw" --lograwdatasize 1 --dumpfile "$PWD/ebusd.dump" --dumpsize 100 -D --scanconfig --aclfile="$PWD/acl.csv" --mqttport=1883 --enablehex --enabledefine sleep 3 pid=`head -n 1 "$PWD/ebusd.pid"` fi @@ -359,7 +359,6 @@ decode -V -N UCH 102030 encode UCH,,,,uch 10;1 raw bytes raw -reload nocommand EOF status=1 From 7198a1f01555226110d5540dc22ba3167a533e09 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 14 Oct 2023 09:05:25 +0200 Subject: [PATCH 146/345] dont wait when not running --- src/lib/utils/thread.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lib/utils/thread.cpp b/src/lib/utils/thread.cpp index 6db1ce00a..737087bfa 100755 --- a/src/lib/utils/thread.cpp +++ b/src/lib/utils/thread.cpp @@ -94,6 +94,9 @@ bool WaitThread::join() { } bool WaitThread::Wait(int seconds, int millis) { + if (!isRunning()) { + return false; + } pthread_mutex_lock(&m_mutex); struct timespec t; clockGettime(&t); @@ -123,6 +126,9 @@ void NotifiableThread::notify() { } bool NotifiableThread::waitNotified(int millis) { + if (!isRunning()) { + return false; + } pthread_mutex_lock(&m_mutex); if (!m_notified) { struct timespec t; From be916c73b65ce3fb4a9c220b1008a93b4057ce74 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 14 Oct 2023 09:06:40 +0200 Subject: [PATCH 147/345] fix shutdown --- src/ebusd/main.cpp | 7 ++++--- src/lib/ebus/protocol_direct.h | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index 30fedb765..5da175e01 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -402,6 +402,7 @@ int main(int argc, char* argv[], char* envp[]) { // create the MainLoop and start it s_mainLoop = new MainLoop(s_opt, device, s_messageMap, s_scanHelper, s_requestQueue); BusHandler* busHandler = s_mainLoop->getBusHandler(); + ProtocolHandler* protocol = busHandler->getProtocol(); if (s_opt.injectMessages) { int scanAdrCount = 0; bool scanAddresses[256] = {}; @@ -412,7 +413,7 @@ int main(int argc, char* argv[], char* envp[]) { if (!s_scanHelper->parseMessage(argv[arg_index++], false, &master, &slave)) { continue; } - busHandler->getProtocol()->injectMessage(master, slave); + protocol->injectMessage(master, slave); if (s_opt.scanConfig && master.size() >= 5 && master[4] == 0 && master[2] == 0x07 && master[3] == 0x04 && isValidAddress(master[1], false) && !isMaster(master[1]) && !scanAddresses[master[1]]) { // scan message, simulate scanning @@ -420,7 +421,7 @@ int main(int argc, char* argv[], char* envp[]) { scanAdrCount++; } } - busHandler->getProtocol()->start("bushandler"); + protocol->start("bushandler"); for (symbol_t address = 0; scanAdrCount > 0; address++) { if (scanAddresses[address]) { scanAdrCount--; @@ -432,7 +433,7 @@ int main(int argc, char* argv[], char* envp[]) { return 0; } } else { - busHandler->getProtocol()->start("bushandler"); + protocol->start("bushandler"); } s_mainLoop->start("mainloop"); diff --git a/src/lib/ebus/protocol_direct.h b/src/lib/ebus/protocol_direct.h index eb185b11d..46f0842b3 100755 --- a/src/lib/ebus/protocol_direct.h +++ b/src/lib/ebus/protocol_direct.h @@ -80,6 +80,7 @@ class DirectProtocolHandler : public ProtocolHandler { * Destructor. */ virtual ~DirectProtocolHandler() { + join(); if (m_currentRequest != nullptr) { delete m_currentRequest; m_currentRequest = nullptr; From 8646b0d86c726c3a52015778b1b0465868f1dbef Mon Sep 17 00:00:00 2001 From: John Date: Sat, 14 Oct 2023 11:07:37 +0200 Subject: [PATCH 148/345] updated [skip ci] --- ChangeLog.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index 766d07a78..546d728b8 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -4,10 +4,14 @@ * fix lost scanconfig default behaviour * fix send empty message instead of logging an error for MQTT on messages without any field * fix MacOS build +* fix name prefix warnings in Home Assistant MQTT discovery integration +* fix impossible usage of multi-field writes in MQTT integration ## Features * add temperatures in Kelvin and ... to Home Assistant MQTT discovery integration * add options to turn off scanconfig and limit number of retries +* remove dependency on argp +* add time fields to Home Assistant MQTT discovery integration # 23.2 (2023-07-08) From f2a845d4624b317979f44f823dc964583759fbd8 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 14 Oct 2023 11:59:34 +0200 Subject: [PATCH 149/345] fix initial broadcast scan message --- contrib/etc/ebusd/broadcast.csv | 3 +-- src/ebusd/mainloop.cpp | 20 ++++++++------------ 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/contrib/etc/ebusd/broadcast.csv b/contrib/etc/ebusd/broadcast.csv index c14d0a6e7..faef2cf1e 100644 --- a/contrib/etc/ebusd/broadcast.csv +++ b/contrib/etc/ebusd/broadcast.csv @@ -4,8 +4,7 @@ *w,broadcast,,,,FE,,,,,,,,,,,,,,,,,,,,,,,,,, b,,datetime,date/time,,,0700,,outsidetemp,,D2B,,,°C,time,,BTI,,,,date,,BDA,,,,,,,,, r;b,,id,identification,,,0704,,manufacturer,,UCH,0x06=Dungs;0x0f=FH Ostfalia;0x10=TEM;0x11=Lamberti;0x14=CEB;0x15=Landis-Staefa;0x16=FERRO;0x17=MONDIAL;0x18=Wikon;0x19=Wolf;0x20=RAWE;0x30=Satronic;0x40=ENCON;0x50=Kromschröder;0x60=Eberle;0x65=EBV;0x75=Grässlin;0x85=ebm-papst;0x95=SIG;0xa5=Theben;0xa7=Thermowatt;0xb5=Vaillant;0xc0=Toby;0xc5=Weishaupt;0xfd=ebusd.eu,,device manufacturer,id,,STR:5,,,device id,software,,PIN,,,software version,hardware,,PIN,,,hardware version -w,,id,identification,,FE,0704,,,,,,,,,,,,,,,,,,,,,,,,, -w,,queryexistence,Inquiry of existence,,FE,07FE,,,,,,,,,,,,,,,,,,,,,,,,, +w,,queryexistence,Inquiry of existence,,,07FE,,,,,,,,,,,,,,,,,,,,,,,,, b,,signoflife,sign of life,,,07FF,,,,,,,,,,,,,,,,,,,,,,,,, b,,error,error message,,,FE01,,error,,STR:10,,,,,,,,,,,,,,,,,,,,, b,,netresetstate,reset network start,,,FF00,,,,,,,,,,,,,,,,,,,,,,,,, diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index e820e31d8..902a65407 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -282,18 +282,14 @@ void MainLoop::run() { } } else if (m_initialScan == BROADCAST) { logNotice(lf_main, "starting initial broadcast scan"); - Message* message = m_messages->getScanMessage(BROADCAST); - if (message) { - MasterSymbolString master; - SlaveSymbolString slave; - istringstream input; - result = message->prepareMaster(0, m_address, SYN, UI_FIELD_SEPARATOR, &input, &master); - if (result == RESULT_OK) { - result = m_protocol->sendAndWait(master, &slave); - } - } else { - result = RESULT_ERR_NOTFOUND; - } + MasterSymbolString master; + SlaveSymbolString slave; + master.push_back(m_address); + master.push_back(BROADCAST); + master.push_back(0x07); + master.push_back(0xfe); // query existance message + master.adjustHeader(); + result = m_protocol->sendAndWait(master, &slave); } else { logNotice(lf_main, "starting initial scan for %2.2x", m_initialScan); result = m_busHandler->scanAndWait(m_initialScan, true); From 5e2509b60c5b15135442c68b43f69a813a73b3d4 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 14 Oct 2023 13:28:33 +0200 Subject: [PATCH 150/345] allow initsend for enhanced as well, cleaner separation --- src/ebusd/bushandler.cpp | 45 +++++++++++++++++----- src/ebusd/bushandler.h | 2 +- src/ebusd/main.cpp | 18 ++++----- src/ebusd/mainloop.cpp | 8 ++-- src/lib/ebus/device.cpp | 65 +++++++++++++------------------- src/lib/ebus/device.h | 60 +++++++++++------------------ src/lib/ebus/protocol.cpp | 36 +++++++++++++++--- src/lib/ebus/protocol.h | 43 ++++++++++++++++++--- src/lib/ebus/protocol_direct.cpp | 15 ++++++-- 9 files changed, 178 insertions(+), 114 deletions(-) diff --git a/src/ebusd/bushandler.cpp b/src/ebusd/bushandler.cpp index 31a0ebbeb..277bc9f99 100644 --- a/src/ebusd/bushandler.cpp +++ b/src/ebusd/bushandler.cpp @@ -350,8 +350,32 @@ result_t BusHandler::readFromBus(Message* message, const string& inputStr, symbo return ret; } -void BusHandler::notifyProtocolStatus(bool signal) { - // ignored +void BusHandler::notifyProtocolStatus(ProtocolState state) { + if (state == ps_empty && m_pollInterval > 0) { // check for poll/scan + time_t now; + time(&now); + if (m_lastPoll == 0 || difftime(now, m_lastPoll) > m_pollInterval) { + Message* message = m_messages->getNextPoll(); + if (message != nullptr) { + m_lastPoll = now; + if (difftime(now, message->getLastUpdateTime()) > m_pollInterval) { + // only poll this message if it was not updated already by other means within the interval + auto request = new PollRequest(message); + result_t ret = request->prepare(m_protocol->getOwnMasterAddress()); + if (ret != RESULT_OK) { + logError(lf_bus, "prepare poll message: %s", getResultCode(ret)); + delete request; + } else { + ret = m_protocol->addRequest(request, false); + if (ret != RESULT_OK) { + logError(lf_bus, "push poll message: %s", getResultCode(ret)); + delete request; + } + } + } + } + } + } } result_t BusHandler::notifyProtocolAnswer(const MasterSymbolString& command, SlaveSymbolString* response) { @@ -494,7 +518,7 @@ result_t BusHandler::prepareScan(symbol_t slave, bool full, const string& levels if (scanMessage == nullptr) { return RESULT_ERR_NOTFOUND; } - if (m_protocol->getDevice()->isReadOnly()) { + if (m_protocol->isReadOnly()) { return RESULT_OK; } deque messages; @@ -564,9 +588,8 @@ result_t BusHandler::startScan(bool full, const string& levels) { } m_scanResults.clear(); m_runningScans++; - m_protocol->addRequest(request, false); // request is deleted by ProtocolHandler after finish - return RESULT_OK; + return m_protocol->addRequest(request, false); } void BusHandler::setScanResult(symbol_t dstAddress, size_t index, const string& str) { @@ -704,7 +727,7 @@ void BusHandler::formatUpdateInfo(ostringstream* output) const { } *output << ",\"c\":" << m_protocol->getMasterCount() << ",\"m\":" << m_messages->size() - << ",\"ro\":" << (m_protocol->getDevice()->isReadOnly() ? 1 : 0) + << ",\"ro\":" << (m_protocol->isReadOnly() ? 1 : 0) << ",\"an\":" << (m_protocol->isAnswering() ? 1 : 0) << ",\"co\":" << (m_protocol->isAddressConflict(SYN) ? 1 : 0); if (m_grabMessages) { @@ -727,8 +750,7 @@ void BusHandler::formatUpdateInfo(ostringstream* output) const { } unsigned char address = 0; for (int index = 0; index < 256; index++, address++) { - bool ownAddress = !m_protocol->getDevice()->isReadOnly() - && (address == m_protocol->getOwnMasterAddress() || address == m_protocol->getOwnSlaveAddress()); + bool ownAddress = !m_protocol->isOwnAddress(address); if (!isValidAddress(address, false) || ((m_seenAddresses[address]&SEEN) == 0 && !ownAddress)) { continue; } @@ -815,8 +837,11 @@ result_t BusHandler::scanAndWait(symbol_t dstAddress, bool loadScanConfig, bool m_scanResults[dstAddress].resize(1); } m_runningScans++; - requestExecuted = m_protocol->addRequest(request, true); - result = requestExecuted ? request->m_result : RESULT_ERR_TIMEOUT; + result = m_protocol->addRequest(request, true); + requestExecuted = result == RESULT_OK; + if (requestExecuted) { + result = request->m_result; + } delete request; request = nullptr; } diff --git a/src/ebusd/bushandler.h b/src/ebusd/bushandler.h index e0f6c6849..b7405ca8f 100755 --- a/src/ebusd/bushandler.h +++ b/src/ebusd/bushandler.h @@ -410,7 +410,7 @@ class BusHandler : public ProtocolListener { void setScanConfigLoaded(symbol_t address, const string& file); // @copydoc - void notifyProtocolStatus(bool signal) override; + void notifyProtocolStatus(ProtocolState state) override; // @copydoc result_t notifyProtocolAnswer(const MasterSymbolString& master, SlaveSymbolString* slave) override; diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index 5da175e01..f78270f65 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -363,8 +363,7 @@ int main(int argc, char* argv[], char* envp[]) { } // open the device - Device *device = Device::create(s_opt.device, s_opt.extraLatency, !s_opt.noDeviceCheck, s_opt.readOnly, - s_opt.initialSend); + Device *device = Device::create(s_opt.device, s_opt.extraLatency, !s_opt.noDeviceCheck); if (device == nullptr) { logWrite(lf_main, ll_error, "unable to create device %s", s_opt.device); // force logging on exit cleanup(); @@ -385,8 +384,14 @@ int main(int argc, char* argv[], char* envp[]) { signal(SIGINT, signalHandler); signal(SIGTERM, signalHandler); + // create the MainLoop + s_requestQueue = new Queue(); + s_mainLoop = new MainLoop(s_opt, device, s_messageMap, s_scanHelper, s_requestQueue); + BusHandler* busHandler = s_mainLoop->getBusHandler(); + ProtocolHandler* protocol = busHandler->getProtocol(); + ostringstream ostream; - device->formatInfo(&ostream, false, false, true); + protocol->formatInfo(&ostream, false, true); string deviceInfoStr = ostream.str(); logNotice(lf_main, PACKAGE_STRING "." REVISION " started%s on device: %s", s_opt.scanConfig ? s_opt.initialScan == ESC ? " with auto scan" @@ -397,12 +402,7 @@ int main(int argc, char* argv[], char* envp[]) { // load configuration files s_scanHelper->loadConfigFiles(!s_opt.scanConfig); - s_requestQueue = new Queue(); - - // create the MainLoop and start it - s_mainLoop = new MainLoop(s_opt, device, s_messageMap, s_scanHelper, s_requestQueue); - BusHandler* busHandler = s_mainLoop->getBusHandler(); - ProtocolHandler* protocol = busHandler->getProtocol(); + // start the MainLoop if (s_opt.injectMessages) { int scanAdrCount = 0; bool scanAddresses[256] = {}; diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index 902a65407..f2b7e1071 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -152,6 +152,7 @@ MainLoop::MainLoop(const struct options& opt, Device *device, MessageMap* messag } // create BusHandler ebus_protocol_config_t config = { + .readOnly = opt.readOnly, .ownAddress = m_address, .answer = opt.answer, .busLostRetries = opt.acquireRetries, @@ -160,6 +161,7 @@ MainLoop::MainLoop(const struct options& opt, Device *device, MessageMap* messag .slaveRecvTimeout = opt.receiveTimeout, .lockCount = opt.masterCount, .generateSyn = opt.generateSyn, + .initialSend = opt.initialSend, }; m_busHandler = new BusHandler(m_device, m_messages, scanHelper, config, opt.pollInterval); @@ -377,7 +379,7 @@ void MainLoop::run() { << ",\"a\":\"other\"" #endif << ",\"u\":" << (now-start); - m_device->formatInfo(&ostr, false, true, true); + m_protocol->formatInfoJson(&ostr); if (m_reconnectCount) { ostr << ",\"rc\":" << m_reconnectCount; } @@ -1935,7 +1937,7 @@ result_t MainLoop::executeInfo(const vector& args, const string& user, o *ostream << "update check: " << m_updateCheck << "\n"; } *ostream << "device: "; - m_device->formatInfo(ostream, verbose); + m_protocol->formatInfo(ostream, verbose, false); *ostream << "\n"; if (!user.empty()) { *ostream << "user: " << user << "\n"; @@ -2226,7 +2228,7 @@ result_t MainLoop::executeGet(const vector& args, bool* connected, ostri << ",\n \"maxsymbollatency\": " << m_protocol->getMaxSymbolLatency(); } } - if (!m_device->isReadOnly()) { + if (!m_protocol->isReadOnly()) { *ostream << ",\n \"qq\": " << static_cast(m_address); } *ostream << ",\n \"reconnects\": " << m_reconnectCount diff --git a/src/lib/ebus/device.cpp b/src/lib/ebus/device.cpp index 07166e671..1132bdaa8 100755 --- a/src/lib/ebus/device.cpp +++ b/src/lib/ebus/device.cpp @@ -88,11 +88,11 @@ namespace ebusd { #define DEBUG_RAW_TRAFFIC(format, args...) #endif -Device::Device(const char* name, bool readOnly, bool initialSend) - : m_name(name), m_readOnly(readOnly), m_initialSend(initialSend), m_listener(nullptr) { +Device::Device(const char* name) + : m_name(name), m_listener(nullptr) { } -Device* Device::create(const char* name, unsigned int extraLatency, bool checkDevice, bool readOnly, bool initialSend) { +Device* Device::create(const char* name, unsigned int extraLatency, bool checkDevice) { bool highSpeed = strncmp(name, "ens:", 4) == 0; bool enhanced = highSpeed || strncmp(name, "enh:", 4) == 0; if (enhanced) { @@ -128,23 +128,17 @@ Device* Device::create(const char* name, unsigned int extraLatency, bool checkDe *portpos = 0; char* hostOrIp = strdup(addrpos); free(in); - return new NetworkDevice(name, hostOrIp, port, extraLatency, readOnly, initialSend, udp, enhanced); + return new NetworkDevice(name, hostOrIp, port, extraLatency, udp, enhanced); } // support enh:/dev/, ens:/dev/, and /dev/ - return new SerialDevice(name, checkDevice, extraLatency, readOnly, initialSend, enhanced, highSpeed); + return new SerialDevice(name, checkDevice, extraLatency, enhanced, highSpeed); } -result_t Device::afterOpen() { - if (m_initialSend && !send(ESC)) { - return RESULT_ERR_SEND; - } - return RESULT_OK; -} -FileDevice::FileDevice(const char* name, bool checkDevice, unsigned int latency, bool readOnly, bool initialSend, +FileDevice::FileDevice(const char* name, bool checkDevice, unsigned int latency, bool enhancedProto) - : Device(name, readOnly, initialSend), + : Device(name), m_checkDevice(checkDevice), m_latency(HOST_LATENCY_MS+(enhancedProto?ENHANCED_LATENCY_MS:0)+latency), m_enhancedProto(enhancedProto), m_fd(-1), m_resetRequested(false), @@ -164,33 +158,24 @@ FileDevice::~FileDevice() { } } -void FileDevice::formatInfo(ostringstream* ostream, bool verbose, bool asJson, bool noWait) { - if (asJson) { - if (m_enhancedProto) { - string ver = getEnhancedVersion(); - if (!ver.empty()) { - *ostream << ",\"dv\":\"" << ver << "\""; - } +void FileDevice::formatInfo(ostringstream* ostream, bool verbose, bool prefix) { + if (prefix) { + *ostream << m_name; + string info = getEnhancedProtoInfo(); + if (!info.empty()) { + *ostream << ", " << info; } return; } - *ostream << m_name; - string info = getEnhancedProtoInfo(); - if (!info.empty()) { - *ostream << ", " << info; - } - if (isReadOnly()) { - *ostream << ", readonly"; - } - if (noWait) { - return; - } if (!isValid()) { *ostream << ", invalid"; } + if (!m_enhancedProto) { + return; + } bool infoAdded = false; if (verbose) { - info = getEnhancedInfos(); + string info = getEnhancedInfos(); if (!info.empty()) { *ostream << ", " << info; infoAdded = true; @@ -204,6 +189,15 @@ void FileDevice::formatInfo(ostringstream* ostream, bool verbose, bool asJson, b } } +void FileDevice::formatInfoJson(ostringstream* ostream) { + if (m_enhancedProto) { + string ver = getEnhancedVersion(); + if (!ver.empty()) { + *ostream << ",\"dv\":\"" << ver << "\""; + } + } +} + result_t FileDevice::open() { close(); return m_bufSize == 0 ? RESULT_ERR_DEVICE : RESULT_OK; @@ -220,8 +214,6 @@ result_t FileDevice::afterOpen() { m_listener->notifyStatus(false, "resetting"); } m_resetRequested = true; - } else if (m_initialSend && !write(ESC)) { - return RESULT_ERR_SEND; } return RESULT_OK; } @@ -339,7 +331,7 @@ result_t FileDevice::send(symbol_t value) { if (!isValid()) { return RESULT_ERR_DEVICE; } - if (m_readOnly || !write(value)) { + if (!write(value)) { return RESULT_ERR_SEND; } if (m_listener != nullptr) { @@ -496,9 +488,6 @@ result_t FileDevice::startArbitration(symbol_t masterAddress) { } return RESULT_OK; } - if (m_readOnly) { - return RESULT_ERR_SEND; - } m_arbitrationMaster = masterAddress; if (m_enhancedProto && masterAddress != SYN) { if (!write(masterAddress, true)) { diff --git a/src/lib/ebus/device.h b/src/lib/ebus/device.h index 407f2262d..40bc9d628 100755 --- a/src/lib/ebus/device.h +++ b/src/lib/ebus/device.h @@ -99,10 +99,8 @@ class Device { /** * Construct a new instance. * @param name the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network). - * @param readOnly whether to allow read access to the device only. - * @param initialSend whether to send an initial @a ESC symbol in @a open(). */ - Device(const char* name, bool readOnly, bool initialSend); + explicit Device(const char* name); public: /** @@ -115,13 +113,10 @@ class Device { * @param name the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network). * @param extraLatency the extra bus transfer latency in milliseconds. * @param checkDevice whether to regularly check the device availability (only for serial devices). - * @param readOnly whether to allow read access to the device only. - * @param initialSend whether to send an initial @a ESC symbol in @a open(). * @return the new @a Device, or nullptr on error. * Note: the caller needs to free the created instance. */ - static Device* create(const char* name, unsigned int extraLatency = 0, bool checkDevice = true, - bool readOnly = false, bool initialSend = false); + static Device* create(const char* name, unsigned int extraLatency = 0, bool checkDevice = true); /** * Get the device name. @@ -129,12 +124,6 @@ class Device { */ const char* getName() const { return m_name; } - /** - * Return whether to allow read access to the device only. - * @return whether to allow read access to the device only. - */ - bool isReadOnly() const { return m_readOnly; } - /** * Set the @a DeviceListener. * @param listener the @a DeviceListener. @@ -142,13 +131,18 @@ class Device { void setListener(DeviceListener* listener) { m_listener = listener; } /** - * Format device infos in plain text or JSON format. + * Format device infos in plain text. * @param output the @a ostringstream to append the infos to. * @param verbose whether to add verbose infos. - * @param asJson whether to format as JSON rather than plain text. - * @param noWait true to not wait for any response asynchronously and return immediately. + * @param prefix true for the synchronously retrievable prefix, false for the potentially asynchronous suffix. */ - virtual void formatInfo(ostringstream* output, bool verbose, bool asJson = false, bool noWait = false) = 0; + virtual void formatInfo(ostringstream* output, bool verbose, bool prefix) = 0; + + /** + * Format device infos in JSON format. + * @param output the @a ostringstream to append the infos to. + */ + virtual void formatInfoJson(ostringstream* output) = 0; /** * Open the file descriptor. @@ -160,7 +154,7 @@ class Device { * Has to be called by subclasses upon successful opening the device as last action in open(). * @return the @a result_t code. */ - virtual result_t afterOpen(); + virtual result_t afterOpen() { return RESULT_OK; } /** * Close the file descriptor if opened. @@ -213,12 +207,6 @@ class Device { /** the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network). */ const char* m_name; - /** whether to allow read access to the device only. */ - const bool m_readOnly; - - /** whether to send an initial @a ESC symbol in @a open(). */ - const bool m_initialSend; - /** the @a DeviceListener, or nullptr. */ DeviceListener* m_listener; }; @@ -234,11 +222,9 @@ class FileDevice : public Device { * @param name the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network). * @param checkDevice whether to regularly check the device availability. * @param latency the bus transfer latency in milliseconds. - * @param readOnly whether to allow read access to the device only. - * @param initialSend whether to send an initial @a ESC symbol in @a open(). * @param enhancedProto whether to use the ebusd enhanced protocol. */ - FileDevice(const char* name, bool checkDevice, unsigned int latency, bool readOnly, bool initialSend, + FileDevice(const char* name, bool checkDevice, unsigned int latency, bool enhancedProto = false); public: @@ -248,7 +234,10 @@ class FileDevice : public Device { virtual ~FileDevice(); // @copydoc - void formatInfo(ostringstream* output, bool verbose, bool asJson = false, bool noWait = false) override; + void formatInfo(ostringstream* output, bool verbose, bool prefix) override; + + // @copydoc + void formatInfoJson(ostringstream* output) override; // @copydoc result_t open() override; @@ -450,14 +439,12 @@ class SerialDevice : public FileDevice { * @param name the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network). * @param checkDevice whether to regularly check the device availability. * @param extraLatency the extra bus transfer latency in milliseconds. - * @param readOnly whether to allow read access to the device only. - * @param initialSend whether to send an initial @a ESC symbol in @a open(). * @param enhancedProto whether to use the ebusd enhanced protocol. * @param enhancedHighSpeed whether to use ebusd enhanced protocol in high speed mode. */ - SerialDevice(const char* name, bool checkDevice, unsigned int extraLatency, bool readOnly, bool initialSend, + SerialDevice(const char* name, bool checkDevice, unsigned int extraLatency, bool enhancedProto = false, bool enhancedHighSpeed = false) - : FileDevice(name, checkDevice, extraLatency, readOnly, initialSend, enhancedProto), + : FileDevice(name, checkDevice, extraLatency, enhancedProto), m_enhancedHighSpeed(enhancedHighSpeed) { } @@ -498,14 +485,12 @@ class NetworkDevice : public FileDevice { * @param hostOrIp the host name or IP address of the device. * @param port the TCP or UDP port of the device. * @param extraLatency the extra bus transfer latency in milliseconds. - * @param readOnly whether to allow read access to the device only. - * @param initialSend whether to send an initial @a ESC symbol in @a open(). * @param udp true for UDP, false to TCP. * @param enhancedProto whether to use the ebusd enhanced protocol. */ - NetworkDevice(const char* name, const char* hostOrIp, uint16_t port, unsigned int extraLatency, bool readOnly, - bool initialSend, bool udp, bool enhancedProto = false) - : FileDevice(name, true, NETWORK_LATENCY_MS+extraLatency, readOnly, initialSend, enhancedProto), + NetworkDevice(const char* name, const char* hostOrIp, uint16_t port, unsigned int extraLatency, + bool udp, bool enhancedProto = false) + : FileDevice(name, true, NETWORK_LATENCY_MS+extraLatency, enhancedProto), m_hostOrIp(hostOrIp), m_port(port), m_udp(udp) {} /** @@ -514,6 +499,7 @@ class NetworkDevice : public FileDevice { ~NetworkDevice() override { if (m_hostOrIp) { free((void*)m_hostOrIp); + m_hostOrIp = nullptr; } } diff --git a/src/lib/ebus/protocol.cpp b/src/lib/ebus/protocol.cpp index 7842120a6..9707916aa 100644 --- a/src/lib/ebus/protocol.cpp +++ b/src/lib/ebus/protocol.cpp @@ -43,14 +43,35 @@ ProtocolHandler* ProtocolHandler::create(const ebus_protocol_config_t config, return new DirectProtocolHandler(config, device, listener); } +void ProtocolHandler::formatInfo(ostringstream* ostream, bool verbose, bool noWait) { + m_device->formatInfo(ostream, verbose, true); + if (isReadOnly()) { + *ostream << ", readonly"; + } + if (noWait) { + return; + } + m_device->formatInfo(ostream, verbose, false); +} + +void ProtocolHandler::formatInfoJson(ostringstream* ostream) { + m_device->formatInfoJson(ostream); +} + void ProtocolHandler::clear() { memset(m_seenAddresses, 0, sizeof(m_seenAddresses)); m_masterCount = 1; } -bool ProtocolHandler::addRequest(BusRequest* request, bool wait) { +result_t ProtocolHandler::addRequest(BusRequest* request, bool wait) { + if (m_config.readOnly) { + return RESULT_ERR_DEVICE; + } m_nextRequests.push(request); - return !wait || m_finishedRequests.remove(request, true); + if (!wait || m_finishedRequests.remove(request, true)) { + return RESULT_OK; + } + return RESULT_ERR_TIMEOUT; } result_t ProtocolHandler::sendAndWait(const MasterSymbolString& master, SlaveSymbolString* slave) { @@ -63,8 +84,11 @@ result_t ProtocolHandler::sendAndWait(const MasterSymbolString& master, SlaveSym logInfo(lf_bus, "send message: %s", master.getStr().c_str()); for (int sendRetries = m_config.failedSendRetries + 1; sendRetries > 0; sendRetries--) { - bool success = addRequest(&request, true); - result = success ? request.m_result : RESULT_ERR_TIMEOUT; + result = addRequest(&request, true); + bool success = result == RESULT_OK; + if (success) { + result = request.m_result; + } if (result == RESULT_OK) { break; } @@ -103,7 +127,7 @@ bool ProtocolHandler::addSeenAddress(symbol_t address) { return false; } if (!isMaster(address)) { - if (!m_device->isReadOnly() && address == m_ownSlaveAddress) { + if (!m_config.readOnly && address == m_ownSlaveAddress) { if (!m_addressConflict) { m_addressConflict = true; logError(lf_bus, "own slave address %2.2x is used by another participant", address); @@ -122,7 +146,7 @@ bool ProtocolHandler::addSeenAddress(symbol_t address) { return false; } bool ret = false; - if (!m_device->isReadOnly() && address == m_ownMasterAddress) { + if (!m_config.readOnly && address == m_ownMasterAddress) { if (!m_addressConflict) { m_addressConflict = true; logError(lf_bus, "own master address %2.2x is used by another participant", address); diff --git a/src/lib/ebus/protocol.h b/src/lib/ebus/protocol.h index eb743b451..2424ad071 100755 --- a/src/lib/ebus/protocol.h +++ b/src/lib/ebus/protocol.h @@ -52,6 +52,8 @@ namespace ebusd { /** settings for the eBUS protocol handler. */ typedef struct ebus_protocol_config { + /** whether to allow read access to the device only. */ + bool readOnly; /** the own master address. */ symbol_t ownAddress; /** whether to answer queries for the own master/slave address. */ @@ -68,9 +70,19 @@ typedef struct ebus_protocol_config { unsigned int lockCount; /** whether to enable AUTO-SYN symbol generation. */ bool generateSyn; + /** whether to send an initial escape symbol after connecting device. */ + bool initialSend; } ebus_protocol_config_t; +/** the possible protocol states. */ +enum ProtocolState { + ps_noSignal, //!< no signal on the bus + ps_idle, //!< idle (after @a SYN symbol) + ps_empty, //!< idle, no more lock remaining, and no other request queued +}; + + class ProtocolHandler; /** @@ -185,9 +197,9 @@ class ProtocolListener { /** * Called to notify a status update from the protocol. - * @param signal true when signal is acquired, false otherwise. + * @param state the current protocol state. */ - virtual void notifyProtocolStatus(bool signal) = 0; // abstract + virtual void notifyProtocolStatus(ProtocolState state) = 0; // abstract /** * Called to notify a new valid seen address on the bus. @@ -234,7 +246,7 @@ class ProtocolHandler : public WaitThread { m_reconnect(false), m_ownMasterAddress(config.ownAddress), m_ownSlaveAddress(getSlaveAddress(config.ownAddress)), m_addressConflict(false), - m_masterCount(device->isReadOnly()?0:1), + m_masterCount(config.readOnly ? 0 : 1), m_symbolLatencyMin(-1), m_symbolLatencyMax(-1), m_arbitrationDelayMin(-1), m_arbitrationDelayMax(-1), m_lastReceive(0), m_symPerSec(0), m_maxSymPerSec(0) { @@ -266,6 +278,25 @@ class ProtocolHandler : public WaitThread { */ static ProtocolHandler* create(const ebus_protocol_config_t config, Device* device, ProtocolListener* listener); + /** + * Format device/protocol infos in plain text. + * @param output the @a ostringstream to append the infos to. + * @param verbose whether to add verbose infos. + * @param noWait true to not wait for any response asynchronously and return immediately. + */ + virtual void formatInfo(ostringstream* output, bool verbose, bool noWait); + + /** + * Format device/protocol infos in JSON format. + * @param output the @a ostringstream to append the infos to. + */ + virtual void formatInfoJson(ostringstream* output); + + /** + * @return whether to allow read access to the device only. + */ + bool isReadOnly() const { return m_config.readOnly; } + /** * @return the own master address. */ @@ -286,7 +317,7 @@ class ProtocolHandler : public WaitThread { * @return @p true when the address is the own master or slave address (if not readonly). */ bool isOwnAddress(symbol_t address) const { - return !m_device->isReadOnly() && (address == m_ownMasterAddress || address == m_ownSlaveAddress); + return !m_config.readOnly && (address == m_ownMasterAddress || address == m_ownSlaveAddress); } /** @@ -324,9 +355,9 @@ class ProtocolHandler : public WaitThread { * Add a @a BusRequest to the internal queue and optionally wait for it to complete. * @param request the @a BusRequest to add. * @param wait true to wait for it to complete, false to return immediately. - * @return true when it was not waited for or when it was completed. + * @return the result code of adding the request (i.e. RESULT_OK when it was not waited for or when it was completed). */ - virtual bool addRequest(BusRequest* request, bool wait); + virtual result_t addRequest(BusRequest* request, bool wait); /** * Send a message on the bus and wait for the answer. diff --git a/src/lib/ebus/protocol_direct.cpp b/src/lib/ebus/protocol_direct.cpp index 1b170b92d..c66bd1ccc 100644 --- a/src/lib/ebus/protocol_direct.cpp +++ b/src/lib/ebus/protocol_direct.cpp @@ -91,6 +91,9 @@ void DirectProtocolHandler::run() { result_t result = m_device->open(); if (result == RESULT_OK) { logNotice(lf_bus, "re-opened %s", m_device->getName()); + if (m_config.initialSend && !m_config.readOnly) { + m_device->send(ESC); + } } else { logError(lf_bus, "unable to open %s: %s", m_device->getName(), getResultCode(result)); setState(bs_noSignal, result); @@ -131,6 +134,10 @@ result_t DirectProtocolHandler::handleSymbol() { } if (!m_device->isArbitrating() && m_currentRequest == nullptr && m_remainLockCount == 0) { BusRequest* startRequest = m_nextRequests.peek(); + if (startRequest == nullptr) { + m_listener->notifyProtocolStatus(ps_empty); + startRequest = m_nextRequests.peek(); + } if (startRequest != nullptr) { // initiate arbitration symbol_t master = startRequest->getMaster()[0]; logDebug(lf_bus, "start request %2.2x", master); @@ -220,7 +227,7 @@ result_t DirectProtocolHandler::handleSymbol() { // send symbol if necessary result_t result; struct timespec sentTime, recvTime; - if (sending) { + if (sending && !m_config.readOnly) { if (m_state != bs_sendSyn && (sendSymbol == ESC || sendSymbol == SYN)) { if (m_escape) { sendSymbol = (symbol_t)(sendSymbol == ESC ? 0x00 : 0x01); @@ -254,7 +261,7 @@ result_t DirectProtocolHandler::handleSymbol() { clockGettime(&recvTime); } bool sentAutoSyn = false; - if (!sending && result == RESULT_ERR_TIMEOUT && m_generateSynInterval > 0 + if (!sending && !m_config.readOnly && result == RESULT_ERR_TIMEOUT && m_generateSynInterval > 0 && timeout >= m_generateSynInterval && (m_state == bs_noSignal || m_state == bs_skip)) { // check if acting as AUTO-SYN generator is required result = m_device->send(SYN); @@ -671,7 +678,7 @@ result_t DirectProtocolHandler::setState(BusState state, result_t result, bool f if (state == bs_noSignal) { // notify all requests if (m_state != bs_noSignal) { - m_listener->notifyProtocolStatus(false); + m_listener->notifyProtocolStatus(ps_idle); } m_response.clear(); // notify with empty response while ((m_currentRequest = m_nextRequests.pop()) != nullptr) { @@ -686,7 +693,7 @@ result_t DirectProtocolHandler::setState(BusState state, result_t result, bool f } } } else if (m_state == bs_noSignal) { - m_listener->notifyProtocolStatus(true); + m_listener->notifyProtocolStatus(ps_noSignal); } m_escape = 0; From 4c453a23543abe5fd1b970d08ff675c265a47ae3 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 14 Oct 2023 16:24:46 +0200 Subject: [PATCH 151/345] formatting --- src/ebusd/mainloop.cpp | 3 ++- src/ebusd/mqtthandler.cpp | 14 ++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index f2b7e1071..3d35a3765 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -1095,9 +1095,10 @@ result_t MainLoop::executeWrite(const vector& args, const string levels, if (!message->isWrite()) { return RESULT_ERR_INVALID_ARG; } - if (circuit.length() > 0 && circuit != message->getCircuit()) { + if (!circuit.empty() && circuit != message->getCircuit()) { return RESULT_ERR_INVALID_ARG; // non-matching circuit } + // send message SlaveSymbolString slave; ret = m_protocol->sendAndWait(master, &slave); diff --git a/src/ebusd/mqtthandler.cpp b/src/ebusd/mqtthandler.cpp index bd7aff727..51aedd845 100755 --- a/src/ebusd/mqtthandler.cpp +++ b/src/ebusd/mqtthandler.cpp @@ -841,18 +841,24 @@ void MqttHandler::run() { } if (includeActiveWrite) { if (message->isWrite()) { - bool skipMultiFieldWrite = (!m_hasDefinitionFieldsPayload || m_publishByField) && !message->isPassive() && message->getFieldCount() > 1; + bool skipMultiFieldWrite = (!m_hasDefinitionFieldsPayload || m_publishByField) + && !message->isPassive() && message->getFieldCount() > 1; if (skipMultiFieldWrite) { - continue; // multi-field message is not writable when publishing by field or combining multiple fields in one definition, so skip it + // multi-field message is not writable when publishing by field or combining + // multiple fields in one definition, so skip it + continue; } } else { // check for existance of write message with same name Message* write = m_messages->find(message->getCircuit(), message->getName(), "", true); if (write) { - bool skipMultiFieldWrite = (!m_hasDefinitionFieldsPayload || m_publishByField) && write->getFieldCount() > 1; + bool skipMultiFieldWrite = (!m_hasDefinitionFieldsPayload || m_publishByField) + && write->getFieldCount() > 1; if (!skipMultiFieldWrite) { continue; // avoid sending definition of read AND write message with the same key - } // else: multi-field write message is not writable when publishing by field or combining multiple fields in one definition, so skip it + } + // else: multi-field write message is not writable when publishing by field or combining + // multiple fields in one definition, so skip it } } } From ef8be0133d5ea643d6d41ad48edcfe97b69a9d1b Mon Sep 17 00:00:00 2001 From: John Date: Sat, 14 Oct 2023 16:49:31 +0200 Subject: [PATCH 152/345] workaround for potentially unusable SSL context --- src/lib/utils/httpclient.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/utils/httpclient.cpp b/src/lib/utils/httpclient.cpp index 93fca1dd0..c89e8e9e1 100755 --- a/src/lib/utils/httpclient.cpp +++ b/src/lib/utils/httpclient.cpp @@ -241,6 +241,9 @@ SSLSocket* SSLSocket::connect(const string& host, const uint16_t& port, bool htt } bio = BIO_new_ssl_connect(ctx); if (isError("new_ssl_conn", bio != nullptr)) { + // in this case the ctx seems to be in an invalid state and it never comes out of that again on its own + SSL_CTX_free(ctx); + ctx = nullptr; break; } BIO_set_info_callback(bio, bioInfoCallback); From b752062d5c73fc9391cb785c66ac054d8a238732 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 20 Oct 2023 18:44:33 +0200 Subject: [PATCH 153/345] corrected links --- src/tools/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tools/README.md b/src/tools/README.md index 8c75ac8ba..9294b9833 100644 --- a/src/tools/README.md +++ b/src/tools/README.md @@ -2,16 +2,16 @@ eBUS Adapter 3 PIC Loader ========================= This is a tool for loading new firmware to the -[eBUS adapter 3 PIC](https://adapter.ebusd.eu/) +[eBUS adapter 3 PIC](https://adapter.ebusd.eu/v31) and to configure the variant, IP settings for the Ethernet variant, and other settings. All of this is done via the bootloader on the PIC. Consequently, when the bootloader is not running, this tool can't do anything. -Check the [eBUS adapter 3 documentation](https://adapter.ebusd.eu/picfirmware) +Check the [eBUS adapter 3 documentation](https://adapter.ebusd.eu/v31/picfirmware) on how to start the bootloader. The binary is part of the [release](https://github.com/john30/ebusd/releases) and a Windows binary based on Cygwin is available for download here: -[ebuspicloader-windows.zip](https://adapter.ebusd.eu/firmware/ebuspicloader-windows.zip) +[ebuspicloader-windows.zip](https://adapter.ebusd.eu/v31/firmware/ebuspicloader-windows.zip) It can be started from within Windows `cmd.exe` after extracting the files to a folder and `cd` into that folder. If Cygwin is already installed, only the `ebuspicloader.exe` needs to be extracted and can be called directly from within a Cygwin shell. From b9012326786c02033606745264053ac0d787ee54 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 22 Oct 2023 09:48:07 +0200 Subject: [PATCH 154/345] add /templates endpoint and include types+templates+conditions+chains in dump output --- contrib/html/openapi.yaml | 129 +++++++++++++++++++++++++++++++++++--- src/ebusd/main.cpp | 7 +++ src/ebusd/mainloop.cpp | 12 ++++ src/ebusd/scan.cpp | 13 ++++ src/ebusd/scan.h | 7 +++ src/lib/ebus/data.cpp | 28 ++++++++- src/lib/ebus/data.h | 8 +++ src/lib/ebus/message.cpp | 97 ++++++++++++++++++++++++---- src/lib/ebus/message.h | 32 ++++++++++ 9 files changed, 311 insertions(+), 22 deletions(-) diff --git a/contrib/html/openapi.yaml b/contrib/html/openapi.yaml index f8fbd219c..26ec93a57 100644 --- a/contrib/html/openapi.yaml +++ b/contrib/html/openapi.yaml @@ -2,7 +2,7 @@ openapi: 3.1.0 info: title: ebusd-http description: The API that ebusd provides on HTTP port. - version: "23.2" + version: "23.3" servers: - url: http://127.0.0.1:8080/ paths: @@ -145,6 +145,50 @@ paths: 500: description: General error. content: { } + /templates: + get: + summary: Get all known field templates for the root. + responses: + 200: + description: Success. + content: + application/json;charset=utf-8: + schema: + $ref: '#/components/schemas/Templates' + 400: + description: Invalid request parameters. + content: { } + 403: + description: User not authorized. + content: { } + 500: + description: General error. + content: { } + /templates/{path}: + get: + summary: Get all known field templates for the path. + parameters: + - name: path + in: path + required: true + schema: + type: string + responses: + 200: + description: Success. + content: + application/json;charset=utf-8: + schema: + $ref: '#/components/schemas/Templates' + 400: + description: Invalid request parameters. + content: { } + 403: + description: User not authorized. + content: { } + 500: + description: General error. + content: { } /raw: get: summary: Retrieve raw data from grabbed and/or decoded messages. @@ -392,8 +436,12 @@ components: type: integer minimum: 0 condition: - type: string - description: the condition string in case of a conditional message (only with full). + description: the condition(s) in case of a conditional message (only with full). + oneOf: + - $ref: '#/components/schemas/Condition' + - type: array + items: + $ref: '#/components/schemas/Condition' lastup: $ref: '#/components/schemas/Seconds' description: the time in UTC seconds of the last update of the message (0 @@ -407,8 +455,12 @@ components: description: destination master or slave address. example: 8 id: - $ref: '#/components/schemas/Symbols' - description: the message ID composed of PBSB and further master data bytes (only with def). + description: the message ID composed of PBSB and further master data bytes (only with def), or an array thereof in case of a chained message. + oneOf: + - $ref: '#/components/schemas/Symbols' + - type: array + items: + $ref: '#/components/schemas/Symbols' comment: type: string description: the message comment (only with verbose). @@ -465,10 +517,10 @@ components: - type: string - type: number nullable: true - FieldDef: + FieldTemplate: + description: a single field template. required: - name - - slave - type - isbits - length @@ -477,9 +529,6 @@ components: name: type: string description: the field name. - slave: - type: boolean - description: whether the field is part of the slave data. type: type: string description: the field type. @@ -513,6 +562,66 @@ components: comment: type: string description: the field comment. + FieldDef: + description: a single field definition. + allOf: + - $ref: '#/components/schemas/FieldTemplate' + - type: object + required: + - slave + properties: + slave: + type: boolean + description: whether the field is part of the slave data. + Templates: + type: array + description: list of known field templates. + items: + oneOf: + - $ref: '#/components/schemas/FieldTemplate' + - type: object + required: + - name + - sequence + properties: + name: + type: string + description: the template set name. + sequence: + type: array + description: the sequence of fields. + items: + $ref: '#/components/schemas/FieldTemplate' + Condition: + description: a single condition. + required: + - name + - message + properties: + name: + type: string + description: name of the condition. + message: + type: string + description: name of the referenced message. + circuit: + type: string + description: name of the referenced circuit. + zz: + maximum: 255 + minimum: 0 + type: integer + description: the circuit slave address. + field: + type: string + description: the field name in the referenced message. + value: + type: array + description: the value ranges for the condition. + items: + oneOf: + - type: number + - type: string DataType: description: a known field data type. type: object diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index f78270f65..5995c8ee3 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -352,7 +352,14 @@ int main(int argc, char* argv[], char* envp[]) { } else { logNotice(lf_main, "configuration dump:"); } + *out << "{\"datatypes\":["; + DataTypeList::getInstance()->dump(s_opt.dumpConfig, true, out); + *out << "],\"templates\":["; + const auto tmpl = s_scanHelper->getTemplates(""); + tmpl->dump(s_opt.dumpConfig, out); + *out << "],\"messages\":"; s_messageMap->dump(true, s_opt.dumpConfig, out); + *out << "}"; if (fout.is_open()) { fout.close(); } diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index 3d35a3765..5da4dbab8 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -2254,6 +2254,18 @@ result_t MainLoop::executeGet(const vector& args, bool* connected, ostri return formatHttpResult(ret, type, ostream); } + if (uri == "/templates" || uri.substr(0, 11) == "/templates/") { + *ostream << "["; + OutputFormat verbosity = OF_NAMES|OF_JSON|OF_ALL_ATTRS; + string name = uri == "/templates" ? "" : uri.substr(11) + "/"; + const auto tmpl = m_scanHelper->getTemplates(name); + tmpl->dump(verbosity, ostream); + *ostream << "\n]"; + type = 6; + *connected = false; + return formatHttpResult(ret, type, ostream); + } + if (uri == "/raw") { time_t since = 0, until = 0; bool onlyUnknown = false; diff --git a/src/ebusd/scan.cpp b/src/ebusd/scan.cpp index 9e337fe05..4309cc42c 100644 --- a/src/ebusd/scan.cpp +++ b/src/ebusd/scan.cpp @@ -202,6 +202,19 @@ bool ScanHelper::readTemplates(const string relPath, const string extension, boo return false; } +void ScanHelper::dumpTemplates(OutputFormat outputFormat, ostream* output) const { + bool prependSeparator = false; + for (auto it : m_templatesByPath) { + if (prependSeparator) { + *output << ","; + } + const auto templates = it.second; + if (templates->dump(outputFormat, output)) { + prependSeparator = true; + } + } +} + result_t ScanHelper::readConfigFiles(const string& relPath, const string& extension, bool recursive, string* errorDescription) { vector files, dirs; diff --git a/src/ebusd/scan.h b/src/ebusd/scan.h index bc8cecf58..6cb4d6c3c 100644 --- a/src/ebusd/scan.h +++ b/src/ebusd/scan.h @@ -158,6 +158,13 @@ class ScanHelper : public Resolver { */ bool readTemplates(const string relPath, const string extension, bool available); + /** + * Dump the loaded @a DataFieldTemplates to the output. + * @param outputFormat the @a OutputFormat options. + * @param output the @a ostream to dump to. + */ + void dumpTemplates(OutputFormat outputFormat, ostream* output) const; + /** * Read the configuration files from the specified path. * @param relPath the relative path from which to read the files (without trailing "/"). diff --git a/src/lib/ebus/data.cpp b/src/lib/ebus/data.cpp index 0e9d38bcd..f463ee70e 100644 --- a/src/lib/ebus/data.cpp +++ b/src/lib/ebus/data.cpp @@ -482,7 +482,10 @@ void SingleDataField::dumpPrefix(bool prependFieldSeparator, OutputFormat output dumpString(prependFieldSeparator, m_name, output); } if (outputFormat & OF_JSON) { - *output << ", \"slave\": " << (m_partType == pt_slaveData ? "true" : "false") << ", "; + if (m_partType != pt_any) { + *output << ", \"slave\": " << (m_partType == pt_slaveData ? "true" : "false"); + } + *output << ", "; } else { *output << FIELD_SEPARATOR; if (m_partType == pt_masterData) { @@ -1459,4 +1462,27 @@ const DataField* DataFieldTemplates::get(const string& name) const { return ref->second; } +bool DataFieldTemplates::dump(OutputFormat outputFormat, ostream* output) const { + bool prependFieldSeparator = false; + for (const auto &it : m_fieldsByName) { + const DataField *dataField = it.second; + if (outputFormat & OF_JSON) { + if (dataField->isSet()) { + if (prependFieldSeparator) { + *output << ",\n"; + } + *output << "{\"name\":\"" << dataField->getName(-1) << "\", \"sequence\": ["; + dataField->dump(false, outputFormat, output); + *output << "]}"; + } else { + dataField->dump(prependFieldSeparator, outputFormat, output); + } + } else { + dataField->dump(prependFieldSeparator, outputFormat, output); + } + prependFieldSeparator = true; + } + return !prependFieldSeparator; +} + } // namespace ebusd diff --git a/src/lib/ebus/data.h b/src/lib/ebus/data.h index f205509a8..810c34884 100755 --- a/src/lib/ebus/data.h +++ b/src/lib/ebus/data.h @@ -836,6 +836,14 @@ class DataFieldTemplates : public MappedFileReader { */ const DataField* get(const string& name) const; + /** + * Dump the templates to the output. + * @param outputFormat the @a OutputFormat options. + * @param output the @a ostream to dump to. + * @return true when a template was written to the output. + */ + bool dump(OutputFormat outputFormat, ostream* output) const; + private: /** the known template @a DataField instances by name. */ diff --git a/src/lib/ebus/message.cpp b/src/lib/ebus/message.cpp index 54a634fa5..1c77a7fd0 100644 --- a/src/lib/ebus/message.cpp +++ b/src/lib/ebus/message.cpp @@ -970,9 +970,8 @@ void Message::decodeJson(bool leadingSeparator, bool appendDirectionCondition, b *output << ",\n \"pollprio\": " << setw(0) << dec << getPollPriority(); } if (isConditional()) { - *output << ",\n \"condition\": \""; - m_condition->dump(false, output); - *output << "\""; + *output << ",\n \"condition\": "; + m_condition->dumpJson(output); } } if (withData) { @@ -985,13 +984,8 @@ void Message::decodeJson(bool leadingSeparator, bool appendDirectionCondition, b } *output << ",\n \"zz\": " << dec << static_cast(m_dstAddress); if (withDefinition) { - *output << ",\n \"id\": [" << dec; - for (auto it = m_id.begin(); it < m_id.end(); it++) { - if (it > m_id.begin()) { - *output << ", "; - } - *output << dec << static_cast(*it); - } + *output << ",\n \"id\": ["; + dumpIdsJson(output); *output << "]"; } appendAttributes(outputFormat, output); @@ -1022,6 +1016,15 @@ void Message::decodeJson(bool leadingSeparator, bool appendDirectionCondition, b *output << "\n }"; } +void Message::dumpIdsJson(ostringstream* output) const { + for (auto it = m_id.begin(); it < m_id.end(); it++) { + if (it > m_id.begin()) { + *output << ", "; + } + *output << dec << static_cast(*it); + } +} + bool Message::setDataHandlerState(int state, bool addBits) { if (addBits ? state == (m_dataHandlerState&state) : state == m_dataHandlerState) { return false; @@ -1296,6 +1299,22 @@ void ChainedMessage::dumpField(const string& fieldName, bool withConditions, Out } } +void ChainedMessage::dumpIdsJson(ostringstream* output) const { + for (auto idsit = m_ids.begin(); idsit < m_ids.end(); idsit++) { + if (idsit > m_ids.begin()) { + *output << ","; + } + *output << "["; + for (auto it = (*idsit).begin(); it < (*idsit).end(); it++) { + if (it > (*idsit).begin()) { + *output << ", "; + } + *output << dec << static_cast(*it); + } + *output << "]"; + } +} + /** * Get the first available @a Message from the list. @@ -1527,6 +1546,27 @@ void SimpleCondition::dump(bool matched, ostream* output) const { } } +void SimpleCondition::dumpJson(ostream* output) const { + *output << "{\"name\": \"" << m_refName << "\""; + if (!m_circuit.empty()) { + *output << ",\"circuit\":\"" << m_circuit << "\""; + } + *output << ",\"message\":\"" << (m_name.empty() ? "scan" : m_name) << "\""; + if (m_dstAddress != SYN) { + *output << ",\"zz\":" << dec << static_cast(m_dstAddress); + } + if (!m_field.empty()) { + *output << ",\"field\":\"" << m_field << "\""; + } + if (m_hasValues) { + *output << ",\"value\":["; + dumpValuesJson(output); + *output << "]"; + } + *output << "}"; +} + + CombinedCondition* SimpleCondition::combineAnd(Condition* other) { CombinedCondition* ret = new CombinedCondition(); return ret->combineAnd(this)->combineAnd(other); @@ -1625,6 +1665,17 @@ bool SimpleNumericCondition::checkValue(const Message* message, const string& fi return false; } +void SimpleNumericCondition::dumpValuesJson(ostream* output) const { + bool first = true; + for (const auto value : m_valueRanges) { + if (!first) { + *output << ","; + } + *output << static_cast(value); + first = false; + } +} + bool SimpleStringCondition::checkValue(const Message* message, const string& field) { ostringstream output; @@ -1641,6 +1692,17 @@ bool SimpleStringCondition::checkValue(const Message* message, const string& fie return false; } +void SimpleStringCondition::dumpValuesJson(ostream* output) const { + bool first = true; + for (const auto value : m_values) { + if (!first) { + *output << ","; + } + *output << "\"" << value << "\""; + first = false; + } +} + void CombinedCondition::dump(bool matched, ostream* output) const { for (const auto condition : m_conditions) { @@ -1648,6 +1710,19 @@ void CombinedCondition::dump(bool matched, ostream* output) const { } } +void CombinedCondition::dumpJson(ostream* output) const { + *output << "["; + bool first = true; + for (const auto condition : m_conditions) { + if (!first) { + *output << ","; + } + condition->dumpJson(output); + first = false; + } + *output << "]"; +} + result_t CombinedCondition::resolve(void (*readMessageFunc)(Message* message), MessageMap* messages, ostringstream* errorMessage) { for (const auto condition : m_conditions) { @@ -2817,7 +2892,7 @@ void MessageMap::dump(bool withConditions, OutputFormat outputFormat, ostream* o bool first = true; bool isJson = (outputFormat & OF_JSON) != 0; if (isJson) { - *output << (m_addAll ? "[" : "}"); + *output << (m_addAll ? "[" : "{"); } else { Message::dumpHeader(nullptr, output); } diff --git a/src/lib/ebus/message.h b/src/lib/ebus/message.h index fcd60ac9b..7d314670f 100644 --- a/src/lib/ebus/message.h +++ b/src/lib/ebus/message.h @@ -601,6 +601,12 @@ class Message : public AttributedItem { OutputFormat outputFormat, ostringstream* output) const; protected: + /** + * Dump the ID(s) to the output in JSON format. + * @param output the @a ostringstream to append to. + */ + virtual void dumpIdsJson(ostringstream* output) const; + /** the source filename. */ const string m_filename; @@ -748,6 +754,8 @@ class ChainedMessage : public Message { result_t prepareMasterPart(size_t index, const char separator, istringstream* input, MasterSymbolString* master) override; + // @copydoc + void dumpIdsJson(ostringstream* output) const override; public: // @copydoc @@ -884,6 +892,18 @@ class Condition { */ virtual void dump(bool matched, ostream* output) const = 0; + /** + * Write the condition definition in JSON to the @a ostream. + * @param output the @a ostream to append to. + */ + virtual void dumpJson(ostream* output) const = 0; + + /** + * Write the values part of the condition definition in JSON to the @a ostream. + * @param output the @a ostream to append to. + */ + virtual void dumpValuesJson(ostream* output) const { /* empty on top level*/ }; + /** * Combine this condition with another instance using a logical and. * @param other the @a Condition to combine with. @@ -951,6 +971,9 @@ class SimpleCondition : public Condition { // @copydoc void dump(bool matched, ostream* output) const override; + // @copydoc + void dumpJson(ostream* output) const override; + // @copydoc CombinedCondition* combineAnd(Condition* other) override; @@ -1043,6 +1066,9 @@ class SimpleNumericCondition : public SimpleCondition { // @copydoc bool checkValue(const Message* message, const string& field) override; + // @copydoc + void dumpValuesJson(ostream* output) const override; + private: /** the valid value ranges (pairs of from/to inclusive), empty for @a m_message seen check. */ @@ -1084,6 +1110,9 @@ class SimpleStringCondition : public SimpleCondition { // @copydoc bool checkValue(const Message* message, const string& field) override; + // @copydoc + void dumpValuesJson(ostream* output) const override; + private: /** the valid values. */ @@ -1110,6 +1139,9 @@ class CombinedCondition : public Condition { // @copydoc void dump(bool matched, ostream* output) const override; + // @copydoc + void dumpJson(ostream* output) const override; + // @copydoc CombinedCondition* combineAnd(Condition* other) override { m_conditions.push_back(other); return this; } From 3d9dd9b1c3f9cc7e9f59c177883dd906c5294e5d Mon Sep 17 00:00:00 2001 From: John Date: Wed, 1 Nov 2023 21:48:08 +0100 Subject: [PATCH 155/345] add positionals to arg parser, avoid unnecessary parts in arg options --- src/ebusd/knxhandler.cpp | 2 +- src/ebusd/main.cpp | 14 ++-- src/ebusd/main.h | 6 +- src/ebusd/main_args.cpp | 29 +++++---- src/ebusd/mqtthandler.cpp | 2 +- src/lib/utils/arg.cpp | 125 +++++++++++++++++++++++++----------- src/lib/utils/arg.h | 28 ++++---- src/tools/ebusctl.cpp | 29 ++++----- src/tools/ebusfeed.cpp | 36 ++++------- src/tools/ebuspicloader.cpp | 24 +++---- 10 files changed, 169 insertions(+), 126 deletions(-) diff --git a/src/ebusd/knxhandler.cpp b/src/ebusd/knxhandler.cpp index 2e73a8d1e..912482e8b 100644 --- a/src/ebusd/knxhandler.cpp +++ b/src/ebusd/knxhandler.cpp @@ -83,7 +83,7 @@ static vector* g_integrationVars = nullptr; //!< the integration settin * @param arg the option argument, or nullptr. * @param state the parsing state. */ -static int knx_parse_opt(int key, char *arg, const argParseOpt *parseOpt) { +static int knx_parse_opt(int key, char *arg, const argParseOpt *parseOpt, void *userArg) { result_t result; unsigned int value; switch (key) { diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index 5995c8ee3..c404edf21 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -222,8 +222,7 @@ void signalHandler(int sig) { * @return the exit code. */ int main(int argc, char* argv[], char* envp[]) { - int arg_index = -1; - switch (parse_main_args(argc, argv, envp, &s_opt, &arg_index)) { + switch (parse_main_args(argc, argv, envp, &s_opt)) { case 0: // OK break; case '?': // help printed @@ -236,9 +235,9 @@ int main(int argc, char* argv[], char* envp[]) { return EINVAL; } - if (arg_index >= 0) { + if (s_opt.injectCount > 0) { if (!s_opt.injectMessages && !(s_opt.checkConfig && s_opt.scanConfig)) { - fprintf(stderr, "invalid arguments starting with \"%s\"", argv[arg_index]); + fprintf(stderr, "invalid inject arguments"); return EINVAL; } } @@ -302,10 +301,11 @@ int main(int argc, char* argv[], char* envp[]) { if (s_opt.checkConfig) { logNotice(lf_main, PACKAGE_STRING "." REVISION " performing configuration check..."); - result_t result = s_scanHelper->loadConfigFiles(!s_opt.scanConfig || arg_index >= argc); + result_t result = s_scanHelper->loadConfigFiles(!s_opt.scanConfig || s_opt.injectCount <= 0); result_t overallResult = s_scanHelper->executeInstructions(nullptr); MasterSymbolString master; SlaveSymbolString slave; + int arg_index = argc - s_opt.injectCount; while (result == RESULT_OK && s_opt.scanConfig && arg_index < argc) { // check scan config for each passed ident message if (!s_scanHelper->parseMessage(argv[arg_index++], true, &master, &slave)) { @@ -413,11 +413,11 @@ int main(int argc, char* argv[], char* envp[]) { if (s_opt.injectMessages) { int scanAdrCount = 0; bool scanAddresses[256] = {}; - while (arg_index < argc) { + for (int arg_index = argc - s_opt.injectCount; arg_index < argc; arg_index++) { // add each passed message MasterSymbolString master; SlaveSymbolString slave; - if (!s_scanHelper->parseMessage(argv[arg_index++], false, &master, &slave)) { + if (!s_scanHelper->parseMessage(argv[arg_index], false, &master, &slave)) { continue; } protocol->injectMessage(master, slave); diff --git a/src/ebusd/main.h b/src/ebusd/main.h index 03543e240..2935b1ce7 100755 --- a/src/ebusd/main.h +++ b/src/ebusd/main.h @@ -33,6 +33,8 @@ namespace ebusd { */ /** the config path part behind the scheme (scheme without "://"). */ +//#define CONFIG_PATH_SUFFIX "://ebus.github.io/cfg/de/" + #define CONFIG_PATH_SUFFIX "://cfg.ebusd.eu/" /** A structure holding all program options. */ @@ -62,6 +64,7 @@ typedef struct options { unsigned int pollInterval; //!< poll interval in seconds, 0 to disable [5] bool injectMessages; //!< inject remaining arguments as already seen messages bool stopAfterInject; //!< only inject messages once, then stop + int injectCount; //!< number of message arguments to inject, or 0 const char* caFile; //!< the CA file to use (uses defaults if neither caFile nor caPath are set), or "#" for insecure const char* caPath; //!< the path with CA files to use (uses defaults if neither caFile nor caPath are set) @@ -107,11 +110,10 @@ typedef struct options { * @param argv the command line arguments. * @param envp the environment variables to parse before the args, or nullptr. * @param opt pointer to the parsed arguments (will be initialized to defaults first). - * @param argIndex optional pointer for storing the index to the first non-argument found in argv. * @return 0 on success, '!' for an invalid argument value, ':' for a missing argument value, * '?' when "-?" was given, or the result of the parse function if non-zero. */ -int parse_main_args(int argc, char* argv[], char* envp[], options_t* opt, int* argIndex); +int parse_main_args(int argc, char* argv[], char* envp[], options_t* opt); } // namespace ebusd diff --git a/src/ebusd/main_args.cpp b/src/ebusd/main_args.cpp index be8a0da19..61d39b0b0 100755 --- a/src/ebusd/main_args.cpp +++ b/src/ebusd/main_args.cpp @@ -56,6 +56,7 @@ static const options_t s_default_opt = { .pollInterval = 5, .injectMessages = false, .stopAfterInject = false, + .injectCount = 0, .caFile = nullptr, .caPath = nullptr, @@ -133,6 +134,7 @@ static string s_configPath = CONFIG_PATH; #define O_DMPFIL (O_RAWSIZ-1) #define O_DMPSIZ (O_DMPFIL-1) #define O_DMPFLU (O_DMPSIZ-1) +#define O_INJPOS 0x100 /** the definition of the known program arguments. */ static const argDef argDefs[] = { @@ -169,6 +171,7 @@ static const argDef argDefs[] = { {"pollinterval", O_POLINT, "SEC", 0, "Poll for data every SEC seconds (0=disable) [5]"}, {"inject", 'i', "stop", af_optional, "Inject remaining arguments as already seen messages (e.g. " "\"FF08070400/0AB5454850303003277201\"), optionally stop afterwards"}, + {nullptr, O_INJPOS, "INJECT", af_optional|af_multiple, "Message(s) to inject (if --inject was given)"}, #ifdef HAVE_SSL {"cafile", O_CAFILE, "FILE", 0, "Use CA FILE for checking certificates (uses defaults," " \"#\" for insecure)"}, @@ -229,8 +232,7 @@ static const argDef argDefs[] = { * @param arg the option argument, or nullptr. * @param parseOpt the parse options. */ -static int parse_opt(int key, char *arg, const argParseOpt *parseOpt) { - struct options *opt = (struct options*)parseOpt->userArg; +static int parse_opt(int key, char *arg, const argParseOpt *parseOpt, struct options *opt) { result_t result = RESULT_OK; unsigned int value; @@ -590,7 +592,14 @@ static int parse_opt(int key, char *arg, const argParseOpt *parseOpt) { break; default: - return ESRCH; + if (key >= O_INJPOS) { // INJECT + if (!opt->injectMessages || !arg || !arg[0]) { + return ESRCH; + } + opt->injectCount++; + } else { + return ESRCH; + } } // check for invalid arg combinations @@ -610,18 +619,15 @@ static int parse_opt(int key, char *arg, const argParseOpt *parseOpt) { return 0; } -int parse_main_args(int argc, char* argv[], char* envp[], options_t *opt, int *arg_index) { +int parse_main_args(int argc, char* argv[], char* envp[], options_t *opt) { *opt = s_default_opt; const argParseOpt parseOpt = { argDefs, - parse_opt, + reinterpret_cast(parse_opt), 0, - "" PACKAGE_NAME, - "[INJECT...]", "A daemon for communication with eBUS heating systems.", "Report bugs to " PACKAGE_BUGREPORT " .", - datahandler_getargs(), - opt + datahandler_getargs() }; char envname[32] = "--"; // needs to cover at least max length of any option name plus "--" @@ -651,7 +657,7 @@ int parse_main_args(int argc, char* argv[], char* envp[], options_t *opt, int *a int cnt = pos[1] ? 2 : 1; if (pos[1] && strlen(*env) < sizeof(envname)-3 && (strcmp(envopt, "scanconfig") == 0 || strcmp(envopt, "lograwdata") == 0)) { - // only really special case: OPTION_ARG_OPTIONAL with non-empty arg needs to use "=" syntax + // only really special case: af_optional with non-empty arg needs to use "=" syntax cnt = 1; strcat(envopt, pos); } @@ -666,8 +672,7 @@ int parse_main_args(int argc, char* argv[], char* envp[], options_t *opt, int *a } } - *arg_index = -1; - int ret = argParse(&parseOpt, argc, argv, arg_index); + int ret = argParse(&parseOpt, argc, argv, reinterpret_cast(opt)); if (ret != 0) { return ret; } diff --git a/src/ebusd/mqtthandler.cpp b/src/ebusd/mqtthandler.cpp index 51aedd845..459c99d14 100755 --- a/src/ebusd/mqtthandler.cpp +++ b/src/ebusd/mqtthandler.cpp @@ -140,7 +140,7 @@ void splitFields(const string& str, vector* row); * @param arg the option argument, or nullptr. * @param state the parsing state. */ -static int mqtt_parse_opt(int key, char *arg, const argParseOpt *parseOpt) { +static int mqtt_parse_opt(int key, char *arg, const argParseOpt *parseOpt, void *userArg) { result_t result = RESULT_OK; unsigned int value; switch (key) { diff --git a/src/lib/utils/arg.cpp b/src/lib/utils/arg.cpp index 7653271f8..fc8e4cdc4 100755 --- a/src/lib/utils/arg.cpp +++ b/src/lib/utils/arg.cpp @@ -28,50 +28,50 @@ namespace ebusd { #define isAlpha(c) (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z')) -void calcCounts(const argDef *argDefs, int &count, int &shortCharsCount, int &shortOptsCount) { +void calcCounts(const argDef *argDefs, int *count, int *shortCharsCount, int *shortOptsCount) { for (const argDef *arg = argDefs; arg && arg->help; arg++) { if (!arg->name) { continue; } - count++; + (*count)++; if (!isAlpha(arg->key)) { continue; } - shortCharsCount++; - shortOptsCount++; + (*shortCharsCount)++; + (*shortOptsCount)++; if (arg->valueName) { - shortOptsCount++; + (*shortOptsCount)++; if (arg->flags & af_optional) { - shortOptsCount++; + (*shortOptsCount)++; } } } } -void buildOpts(const argDef *argDefs, int &count, int &shortCharsCount, int &shortOptsCount, +void buildOpts(const argDef *argDefs, int *count, int *shortCharsCount, int *shortOptsCount, struct option *longOpts, char *shortChars, int *shortIndexes, char *shortOpts, int argDefIdx) { - struct option *opt = longOpts+count; + struct option *opt = longOpts+(*count); for (const argDef *arg = argDefs; arg && arg->help; arg++, argDefIdx++) { if (!arg->name) { continue; } opt->name = arg->name; opt->has_arg = arg->valueName ? ((arg->flags & af_optional) ? optional_argument : required_argument) : no_argument; - opt->flag = nullptr; + opt->flag = NULL; opt->val = argDefIdx; if (isAlpha(arg->key)) { - shortChars[shortCharsCount] = static_cast(arg->key); - shortIndexes[shortCharsCount++] = count; - shortOpts[shortOptsCount++] = static_cast(arg->key); + shortChars[(*shortCharsCount)] = static_cast(arg->key); + shortIndexes[(*shortCharsCount)++] = *count; + shortOpts[(*shortOptsCount)++] = static_cast(arg->key); if (arg->valueName) { - shortOpts[shortOptsCount++] = ':'; + shortOpts[(*shortOptsCount)++] = ':'; if (arg->flags & af_optional) { - shortOpts[shortOptsCount++] = ':'; + shortOpts[(*shortOptsCount)++] = ':'; } } } opt++; - count++; + (*count)++; } } @@ -87,17 +87,17 @@ static const argDef versionArgDefs[] = { endArgDef }; -int argParse(const argParseOpt *parseOpt, int argc, char **argv, int *argIndex) { +int argParse(const argParseOpt *parseOpt, int argc, char **argv, void* userArg) { int count = 0, shortCharsCount = 0, shortOptsCount = 0; if (!(parseOpt->flags & af_noHelp)) { - calcCounts(helpArgDefs, count, shortCharsCount, shortOptsCount); + calcCounts(helpArgDefs, &count, &shortCharsCount, &shortOptsCount); } if (!(parseOpt->flags & af_noVersion)) { - calcCounts(versionArgDefs, count, shortCharsCount, shortOptsCount); + calcCounts(versionArgDefs, &count, &shortCharsCount, &shortOptsCount); } - calcCounts(parseOpt->argDefs, count, shortCharsCount, shortOptsCount); + calcCounts(parseOpt->argDefs, &count, &shortCharsCount, &shortOptsCount); for (const argParseChildOpt *child = parseOpt->childOpts; child && child->argDefs; child++) { - calcCounts(child->argDefs, count, shortCharsCount, shortOptsCount); + calcCounts(child->argDefs, &count, &shortCharsCount, &shortOptsCount); } struct option *longOpts = (struct option*)calloc(count+1, sizeof(struct option)); // room for EOF char *shortChars = reinterpret_cast(calloc(shortCharsCount+1, sizeof(char))); // room for \0 @@ -109,18 +109,18 @@ int argParse(const argParseOpt *parseOpt, int argc, char **argv, int *argIndex) shortOpts[shortOptsCount++] = '+'; // posix mode to stop at first non-option shortOpts[shortOptsCount++] = ':'; // return ':' for missing option if (!(parseOpt->flags & af_noHelp)) { - buildOpts(helpArgDefs, count, shortCharsCount, shortOptsCount, longOpts, shortChars, + buildOpts(helpArgDefs, &count, &shortCharsCount, &shortOptsCount, longOpts, shortChars, shortIndexes, shortOpts, 0xff00); } if (!(parseOpt->flags & af_noVersion)) { - buildOpts(versionArgDefs, count, shortCharsCount, shortOptsCount, longOpts, shortChars, + buildOpts(versionArgDefs, &count, &shortCharsCount, &shortOptsCount, longOpts, shortChars, shortIndexes, shortOpts, 0xff01); } - buildOpts(parseOpt->argDefs, count, shortCharsCount, shortOptsCount, longOpts, shortChars, + buildOpts(parseOpt->argDefs, &count, &shortCharsCount, &shortOptsCount, longOpts, shortChars, shortIndexes, shortOpts, 0); int children = 0; for (const argParseChildOpt *child = parseOpt->childOpts; child && child->argDefs; child++) { - buildOpts(child->argDefs, count, shortCharsCount, shortOptsCount, longOpts, shortChars, + buildOpts(child->argDefs, &count, &shortCharsCount, &shortOptsCount, longOpts, shortChars, shortIndexes, shortOpts, 0x100*(++children)); } optind = 1; // setting to 0 does not work @@ -177,16 +177,49 @@ int argParse(const argParseOpt *parseOpt, int argc, char **argv, int *argIndex) parser = parseOpt->parser; } const argDef *arg = argDefs + (val & 0xff); - c = parser(arg->key, optarg, parseOpt); + c = parser(arg->key, optarg, parseOpt, userArg); if (c != 0) { ret = c; break; } } + if (ret == 0) { + // check for positionals + for (const argDef *arg = parseOpt->argDefs; arg && arg->help; arg++) { + if (arg->name || !arg->valueName) { + continue; // short/long arg or group + } + if (optind < argc) { + int key = arg->key; + do { + c = parseOpt->parser(key, argv[optind], parseOpt, userArg); + if (c != 0) { + ret = c; + break; + } + if (optind+1 >= argc || !(arg->flags & af_multiple)) { + break; + } + key++; + optind++; + } while (true); + if (ret != 0) { + break; + } + } else if (!(arg->flags & af_optional)) { + ret = ':'; // missing argument + fprintf(stderr, "missing argument\n"); + break; + } + optind++; + } + if (ret == 0 && optind < argc) { + ret = '!'; // extra unexpected argument + fprintf(stderr, "extra argument %s\n", argv[optind]); + } + } if (ret == '?') { - argHelp(parseOpt); - } else if (argIndex && optind < argc) { - *argIndex = optind; + argHelp(argv[0], parseOpt); } free(longOpts); free(shortChars); @@ -270,7 +303,7 @@ size_t calcIndent(const argDef *argDefs) { void printArgs(const argDef *argDefs, size_t indent) { for (const argDef *arg = argDefs; arg && arg->help; arg++) { - if (!arg->name) { + if (!arg->name && !arg->valueName) { if (*arg->help) { printf("\n %s\n", arg->help); } else { @@ -284,15 +317,21 @@ void printArgs(const argDef *argDefs, size_t indent) { } else { printf(" "); } - printf(" --%s", arg->name); - size_t taken = 2 + 3 + 3 + strlen(arg->name); + size_t taken = 2 + 3 + 3; + if (arg->name) { + printf(" --%s", arg->name); + taken += strlen(arg->name); + } else { + printf(" "); + } if (arg->valueName) { - taken += 1 + strlen(arg->valueName); + bool multi = arg->flags & af_multiple; + taken += (arg->name ? 1 : 0) + strlen(arg->valueName) + (multi ? 3 : 0); if (arg->flags & af_optional) { - printf("[=%s]", arg->valueName); + printf("[%s%s%s]", arg->name ? "=" : "", arg->valueName, multi ? "..." : ""); taken += 2; } else { - printf("=%s", arg->valueName); + printf("%s%s%s", arg->name ? "=" : "", arg->valueName, multi ? "..." : ""); } } if (taken > indent) { @@ -305,7 +344,7 @@ void printArgs(const argDef *argDefs, size_t indent) { } } -void argHelp(const argParseOpt *parseOpt) { +void argHelp(const char* name, const argParseOpt *parseOpt) { size_t indent = calcIndent(parseOpt->argDefs); if (indent < MAX_INDENT) { for (const argParseChildOpt *child = parseOpt->childOpts; child && child->argDefs; child++) { @@ -323,9 +362,19 @@ void argHelp(const argParseOpt *parseOpt) { } else if (indent < MIN_INDENT) { indent = MIN_INDENT; } - printf("Usage: %s [OPTION...] %s\n", - parseOpt->name, - parseOpt->positional ? parseOpt->positional : ""); + printf("Usage: %s [OPTION...]", basename(name)); + for (const argDef *arg = parseOpt->argDefs; arg && arg->help; arg++) { + if (arg->name || !arg->valueName) { + continue; + } + bool multi = arg->flags & af_multiple; + if (arg->flags & af_optional) { + printf(" [%s%s]", arg->valueName, multi ? "..." : ""); + } else { + printf(" %s%s", arg->valueName, multi ? "..." : ""); + } + } + printf("\n"); wrap(parseOpt->help, 0, 0); printArgs(parseOpt->argDefs, indent); for (const argParseChildOpt *child = parseOpt->childOpts; child && child->argDefs; child++) { diff --git a/src/lib/utils/arg.h b/src/lib/utils/arg.h index 5bc8963f2..8ef289612 100755 --- a/src/lib/utils/arg.h +++ b/src/lib/utils/arg.h @@ -26,15 +26,16 @@ namespace ebusd { /** the available arg flags. */ enum ArgFlag { af_optional = 1<<0, //!< optional argument value - af_noHelp = 1<<1, //!< do not include -?/--help option - af_noVersion = 1<<2, //!< do not include -V/--version option + af_multiple = 1<<1, //!< may appear multiple times (only allowed for last positional) + af_noHelp = 1<<2, //!< do not include -?/--help option + af_noVersion = 1<<3, //!< do not include -V/--version option }; /** Definition of a single argument. */ typedef struct argDef { - const char* name; //!< the (long) name of the argument, or nullptr for a group header + const char* name; //!< the (long) name of the argument, or nullptr for a group header or positional int key; //!< the argument key, also used as short name if alphabetic or the question mark - const char* valueName; //!< the optional argument value name + const char* valueName; //!< the optional argument value name, or nullptr for group header or argument without value name int flags; //!< flags for the argument, bit combination of @a ArgFlag const char* help; //!< help text (mandatory) } argDef; @@ -43,12 +44,13 @@ struct argParseOpt; /** * Function to be called for each argument. - * @param key the argument key as defined. + * @param key the argument key as defined. for positional arguments with multiple flag this will be increased with each call. * @param arg the argument value, or nullptr. - * @param parseOpt ppointer to the @a argParseOpt structure. + * @param parseOpt pointer to the @a argParseOpt structure. + * @param userArg pointer to user argument. * @return 0 on success, non-zero otherwise. */ -typedef int (*parse_function_t)(int key, char *arg, const struct argParseOpt *parseOpt); +typedef int (*parse_function_t)(int key, char *arg, const struct argParseOpt *parseOpt, void *userArg); /** Options for child definitions. */ typedef struct argParseChildOpt { @@ -61,12 +63,9 @@ typedef struct argParseOpt { const argDef *argDefs; //!< pointer to the argument defintions (last one needs to have nullptr help as end sign) parse_function_t parser; //!< parse function to use int flags; //!< flags for the parser, bit combination of @a ArgFlag - const char* name; //!< name of the program parsed - const char* positional; //!< help text for optional positional argument const char* help; //!< help text for the program (second line of help output) const char* suffix; //!< optional help suffix text const argParseChildOpt *childOpts; //!< optional child definitions - void* userArg; //!< optional user argument } argParseOpt; /** @@ -74,17 +73,18 @@ typedef struct argParseOpt { * @param parseOpt pointer to the @a argParseOpt structure. * @param argc the argument count (including the full program name in index 0). * @param argv the argument values (including the full program name in index 0). - * @param argIndex optional pointer for storing the index to the first non-argument found in argv. + * @param userArg pointer to user argument to pass to parser. * @return 0 on success, '!' for an invalid argument value, ':' for a missing argument value, * '?' when "-?" was given, or the result of the parse function if non-zero. */ -int argParse(const argParseOpt *parseOpt, int argc, char **argv, int *argIndex); +int argParse(const argParseOpt *parseOpt, int argc, char **argv, void *userArg); /** * Print the help text. - * @param parseOpt ppointer to the @a argParseOpt structure. + * @param name the name of the program. + * @param parseOpt pointer to the @a argParseOpt structure. */ -void argHelp(const argParseOpt *parseOpt); +void argHelp(const char* name, const argParseOpt *parseOpt); /** * Convenience macro to print an error message to stderr. diff --git a/src/tools/ebusctl.cpp b/src/tools/ebusctl.cpp index 42d8ad22c..95b240d39 100755 --- a/src/tools/ebusctl.cpp +++ b/src/tools/ebusctl.cpp @@ -47,7 +47,6 @@ struct options { uint16_t timeout; //!< ebusd connect/send/receive timeout bool errorResponse; //!< non-zero exit on error response - char* const *args; //!< arguments to pass to ebusd unsigned int argCount; //!< number of arguments to pass to ebusd }; @@ -58,7 +57,6 @@ static struct options opt = { 60, // timeout false, // non-zero exit on error response - nullptr, // args 0 // argCount }; @@ -71,6 +69,8 @@ static const argDef argDefs[] = { ", 0 for none [60]"}, {"error", 'e', nullptr, 0, "Exit non-zero if the connection was fine but the response indicates non-success"}, + {nullptr, 0x100, "COMMAND", af_optional|af_multiple, "COMMAND (and arguments) to send to " PACKAGE "."}, + {nullptr, 0, nullptr, 0, nullptr}, }; @@ -80,8 +80,7 @@ static const argDef argDefs[] = { * @param arg the option argument, or nullptr. * @param parseOpt the parse options. */ -static int parse_opt(int key, char *arg, const argParseOpt *parseOpt) { - struct options *opt = (struct options*)parseOpt->userArg; +static int parse_opt(int key, char *arg, const argParseOpt *parseOpt, struct options *opt) { char* strEnd = nullptr; unsigned int value; switch (key) { @@ -113,7 +112,11 @@ static int parse_opt(int key, char *arg, const argParseOpt *parseOpt) { opt->errorResponse = true; break; default: - return ESRCH; + if (key >= 0x100) { + opt->argCount++; + } else { + return ESRCH; + } } return 0; } @@ -344,18 +347,14 @@ bool connect(const char* host, uint16_t port, uint16_t timeout, char* const *arg int main(int argc, char* argv[]) { argParseOpt parseOpt = { argDefs, - parse_opt, + reinterpret_cast(parse_opt), af_noVersion, - "ebusctl", - "[COMMAND [CMDOPT...]]", "Client for accessing " PACKAGE " via TCP.", - "If given, send COMMAND together with CMDOPT options to " PACKAGE ".\n" + "If given, send COMMAND together with arguments to " PACKAGE ".\n" "Use 'help' as COMMAND for help on available " PACKAGE " commands.", nullptr, - &opt }; - int arg_index = -1; - switch (argParse(&parseOpt, argc, argv, &arg_index)) { + switch (argParse(&parseOpt, argc, argv, &opt)) { case 0: // OK break; case '?': // help printed @@ -363,12 +362,8 @@ int main(int argc, char* argv[]) { default: return EINVAL; } - if (arg_index >= 0) { - opt.args = argv + arg_index; - opt.argCount = argc - arg_index; - } - bool success = connect(opt.server, opt.port, opt.timeout, opt.args, opt.argCount); + bool success = connect(opt.server, opt.port, opt.timeout, argv + argc - opt.argCount, opt.argCount); exit(success ? EXIT_SUCCESS : EXIT_FAILURE); } diff --git a/src/tools/ebusfeed.cpp b/src/tools/ebusfeed.cpp index f423611f0..fbc89339a 100755 --- a/src/tools/ebusfeed.cpp +++ b/src/tools/ebusfeed.cpp @@ -62,6 +62,7 @@ static struct options opt = { static const ebusd::argDef argDefs[] = { {"device", 'd', "DEV", 0, "Write to DEV (serial device) [/dev/ttyUSB60]"}, {"time", 't', "USEC", 0, "Delay each byte by USEC us [10000]"}, + {nullptr, 0x100, "DUMPFILE", af_optional, "Dump file to read [/tmp/ebus_dump.bin]"}, {nullptr, 0, nullptr, 0, nullptr}, }; @@ -72,8 +73,7 @@ static const ebusd::argDef argDefs[] = { * @param arg the option argument, or nullptr. * @param parseOpt the parse options. */ -static int parse_opt(int key, char *arg, const ebusd::argParseOpt *parseOpt) { - struct options *opt = (struct options*)parseOpt->userArg; +static int parse_opt(int key, char *arg, const ebusd::argParseOpt *parseOpt, struct options *opt) { char* strEnd = nullptr; switch (key) { // Device settings: @@ -91,6 +91,14 @@ static int parse_opt(int key, char *arg, const ebusd::argParseOpt *parseOpt) { return EINVAL; } break; + case 0x100: // DUMPFILE + if (arg[0] == 0 || strcmp("/", arg) == 0) { + argParseError(parseOpt, "invalid dumpfile"); + return EINVAL; + } + opt->dumpFile = arg; + break; + default: return ESRCH; } @@ -107,13 +115,9 @@ static int parse_opt(int key, char *arg, const ebusd::argParseOpt *parseOpt) { int main(int argc, char* argv[]) { ebusd::argParseOpt parseOpt = { argDefs, - parse_opt, + reinterpret_cast(parse_opt), af_noVersion, - "ebusfeed", - "[DUMPFILE]", "Feed data from an " PACKAGE " DUMPFILE to a serial device.", - "With no DUMPFILE, /tmp/ebus_dump.bin is used.\n" - "\n" "Example for setting up two pseudo terminals with 'socat':\n" " 1. 'socat -d -d pty,raw,echo=0 pty,raw,echo=0'\n" " 2. create symbol links to appropriate devices, e.g.\n" @@ -122,10 +126,8 @@ int main(int argc, char* argv[]) { " 3. start " PACKAGE ": '" PACKAGE " -f -d /dev/ttyUSB20 --nodevicecheck'\n" " 4. start ebusfeed: 'ebusfeed /path/to/ebus_dump.bin'", nullptr, - &opt }; - int arg_index = -1; - switch (ebusd::argParse(&parseOpt, argc, argv, &arg_index)) { + switch (ebusd::argParse(&parseOpt, argc, argv, &opt)) { case 0: // OK break; case '?': // help printed @@ -133,20 +135,8 @@ int main(int argc, char* argv[]) { default: return EINVAL; } - if (arg_index >= 0) { - if (argv[arg_index][0] == 0 || strcmp("/", argv[arg_index]) == 0) { - argParseError(parseOpt, "invalid dumpfile"); - return EINVAL; - } - if (arg_index != argc -1) { - // more than one arg - argParseError(parseOpt, "multiple dumpfile"); - return EINVAL; - } - opt.dumpFile = argv[arg_index]; - } - Device* device = Device::create(opt.device, false, false, false); + Device* device = Device::create(opt.device, 0, false); if (device == nullptr) { cout << "unable to create device " << opt.device << endl; return EINVAL; diff --git a/src/tools/ebuspicloader.cpp b/src/tools/ebuspicloader.cpp index 36dae377b..ab59bb642 100644 --- a/src/tools/ebuspicloader.cpp +++ b/src/tools/ebuspicloader.cpp @@ -65,6 +65,7 @@ static const ebusd::argDef argDefs[] = { {nullptr, 0, nullptr, 0, "Tool options:"}, {"verbose", 'v', nullptr, 0, "enable verbose output"}, {"slow", 's', nullptr, 0, "low speed mode for transfer (115kBd instead of 921kBd)"}, + {nullptr, 0x100, "PORT", ebusd::af_optional, "port to connect to"}, {nullptr, 0, nullptr, 0, nullptr}, }; @@ -88,6 +89,7 @@ static bool setVariantForced = false; static char* flashFile = nullptr; static bool reset = false; static bool lowSpeed = false; +static const char* portArg = nullptr; bool parseByte(const char *arg, uint8_t minValue, uint8_t maxValue, uint8_t *result) { char* strEnd = nullptr; @@ -125,7 +127,7 @@ bool parseShort(const char *arg, uint16_t minValue, uint16_t maxValue, uint16_t * @param arg the option argument, or nullptr. * @param parseOpt the parse options. */ -static int parse_opt(int key, char *arg, const ebusd::argParseOpt *parseOpt) { +static int parse_opt(int key, char *arg, const ebusd::argParseOpt *parseOpt, void *userArg) { char *ip = nullptr, *part = nullptr; int pos = 0, sum = 0; struct stat st; @@ -327,6 +329,9 @@ static int parse_opt(int key, char *arg, const ebusd::argParseOpt *parseOpt) { case 's': // --slow lowSpeed = true; break; + case 0x100: // PORT + portArg = arg; + break; default: return ESRCH; } @@ -1161,10 +1166,8 @@ int run(int fd); int main(int argc, char* argv[]) { ebusd::argParseOpt parseOpt = { argDefs, - parse_opt, + reinterpret_cast(parse_opt), ebusd::af_noVersion, - "ebuspicloader", - "[PORT]", "A tool for loading firmware to the eBUS adapter PIC and configure adjustable settings.", "PORT is either the serial port to use (e.g. " #ifdef __CYGWIN__ @@ -1174,11 +1177,9 @@ int main(int argc, char* argv[]) { #endif ") that also supports a trailing wildcard '*' for testing" " multiple ports, or a network port as \"ip:port\" for use with e.g. socat or ebusd-esp in PIC pass-through mode.", - nullptr, nullptr }; - int arg_index = -1; - switch (ebusd::argParse(&parseOpt, argc, argv, &arg_index)) { + switch (ebusd::argParse(&parseOpt, argc, argv, nullptr)) { case 0: // OK break; case '?': // help printed @@ -1187,20 +1188,21 @@ int main(int argc, char* argv[]) { return EINVAL; } + bool forceHelp = false; if (setIp != setMask || (setMacFromIp && !setIp)) { std::cerr << "incomplete IP arguments" << std::endl; - arg_index = argc; // force help output + forceHelp = true; } - if (arg_index < 0 || argc-arg_index < 1) { + if (forceHelp || !portArg) { if (flashFile) { printFileChecksum(); exit(EXIT_SUCCESS); } else { - ebusd::argHelp(&parseOpt); + ebusd::argHelp(argv[0], &parseOpt); exit(EXIT_FAILURE); } } - std::string port = argv[arg_index]; + std::string port = portArg; std::string::size_type pos = port.find('*'); if (pos == std::string::npos || pos != port.length()-1) { int fd; From 607fc00c0ec40ef968e200b6b042cd8b2392a4db Mon Sep 17 00:00:00 2001 From: John Date: Sat, 4 Nov 2023 08:50:18 +0100 Subject: [PATCH 156/345] move raw logging+dump to proto, more abstraction --- src/ebusd/Makefile.am | 4 +- src/ebusd/bushandler.h | 32 +++------ src/ebusd/main.cpp | 79 ++++++++++++++++----- src/ebusd/main.h | 2 - src/ebusd/mainloop.cpp | 141 +++----------------------------------- src/ebusd/mainloop.h | 56 +++------------ src/ebusd/mqtthandler.cpp | 2 +- src/lib/ebus/protocol.cpp | 122 ++++++++++++++++++++++++++++++++- src/lib/ebus/protocol.h | 99 ++++++++++++++++++++++++-- 9 files changed, 305 insertions(+), 232 deletions(-) diff --git a/src/ebusd/Makefile.am b/src/ebusd/Makefile.am index 174750a57..473c766f6 100644 --- a/src/ebusd/Makefile.am +++ b/src/ebusd/Makefile.am @@ -14,8 +14,8 @@ ebusd_SOURCES = \ main.h main.cpp main_args.cpp -ebusd_LDADD = ../lib/utils/libutils.a \ - ../lib/ebus/libebus.a \ +ebusd_LDADD = ../lib/ebus/libebus.a \ + ../lib/utils/libutils.a \ -lpthread \ @EXTRA_LIBS@ diff --git a/src/ebusd/bushandler.h b/src/ebusd/bushandler.h index b7405ca8f..fe6527b7c 100755 --- a/src/ebusd/bushandler.h +++ b/src/ebusd/bushandler.h @@ -29,7 +29,6 @@ #include "lib/ebus/data.h" #include "lib/ebus/symbol.h" #include "lib/ebus/result.h" -#include "lib/ebus/device.h" #include "lib/ebus/protocol.h" namespace ebusd { @@ -248,25 +247,15 @@ class BusHandler : public ProtocolListener { public: /** * Construct a new instance. - * @param device the @a Device instance for accessing the bus. * @param messages the @a MessageMap instance with all known @a Message instances. * @param scanHelper the @a ScanHelper instance. - * @param ownAddress the own master address. - * @param answer whether to answer queries for the own master/slave address. - * @param busLostRetries the number of times a send is repeated due to lost arbitration. - * @param failedSendRetries the number of times a failed send is repeated (other than lost arbitration). - * @param busAcquireTimeout the maximum time in milliseconds for bus acquisition. - * @param slaveRecvTimeout the maximum time in milliseconds an addressed slave is expected to acknowledge. - * @param lockCount the number of AUTO-SYN symbols before sending is allowed after lost arbitration, or 0 for auto detection. - * @param generateSyn whether to enable AUTO-SYN symbol generation. * @param pollInterval the interval in seconds in which poll messages are cycled, or 0 if disabled. */ - BusHandler(Device* device, MessageMap* messages, ScanHelper* scanHelper, - const ebus_protocol_config_t config, unsigned int pollInterval) - : m_messages(messages), m_scanHelper(scanHelper), + BusHandler(MessageMap* messages, ScanHelper* scanHelper, + unsigned int pollInterval) + : m_protocol(nullptr), m_messages(messages), m_scanHelper(scanHelper), m_pollInterval(pollInterval), m_lastPoll(0), m_runningScans(0), m_grabMessages(true) { - m_protocol = ProtocolHandler::create(config, device, this); memset(m_seenAddresses, 0, sizeof(m_seenAddresses)); } @@ -274,21 +263,18 @@ class BusHandler : public ProtocolListener { * Destructor. */ virtual ~BusHandler() { - if (m_protocol) { - delete m_protocol; - m_protocol = nullptr; - } } /** - * @return the @a ProtocolHandler instance for accessing the bus. + * Set the @a ProtocolHandler instance for accessing the bus. + * @param protocol the @a ProtocolHandler instance for accessing the bus. */ - ProtocolHandler* getProtocol() const { return m_protocol; } + void setProtocol(ProtocolHandler* protocol) { m_protocol = protocol; } /** - * @return the @a Device instance for accessing the bus. + * @return the @a ProtocolHandler instance for accessing the bus. */ - const Device* getDevice() const { return m_protocol->getDevice(); } + ProtocolHandler* getProtocol() const { return m_protocol; } /** * Clear stored values (e.g. scan results). @@ -433,7 +419,7 @@ class BusHandler : public ProtocolListener { */ result_t prepareScan(symbol_t slave, bool full, const string& levels, bool* reload, ScanRequest** request); - /** the @a ProtocolHandler instance for accessing the bus. */ + /** the @a ProtocolHandler instance for accessing the bus (loosely coupled but set quickly after construction). */ ProtocolHandler* m_protocol; /** the @a MessageMap instance with all known @a Message instances. */ diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index c404edf21..871fd24a6 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -29,6 +29,7 @@ #include #include #include +#include "ebusd/bushandler.h" #include "ebusd/mainloop.h" #include "ebusd/network.h" #include "lib/utils/log.h" @@ -37,16 +38,14 @@ namespace ebusd { -using std::dec; -using std::hex; -using std::setfill; -using std::setw; -using std::nouppercase; using std::cout; /** the previous config path part to rewrite to the current one. */ #define PREVIOUS_CONFIG_PATH_SUFFIX "://ebusd.eu/config/" +/** the second previous config path part to rewrite to the current one. */ +#define PREVIOUS_CONFIG_PATH_SUFFIX2 "://cfg.ebusd.eu/" + /** the opened PID file, or nullptr. */ static FILE* s_pidFile = nullptr; @@ -59,6 +58,12 @@ static MessageMap* s_messageMap = nullptr; /** the @a ScanHelper instance, or nullptr. */ static ScanHelper* s_scanHelper = nullptr; +/** the @a ProtocolHandler instance, or nullptr. */ +static ProtocolHandler* s_protocol = nullptr; + +/** the @a BusHandler instance, or nullptr. */ +static BusHandler* s_busHandler = nullptr; + /** the @a Request @a Queue instance, or nullptr. */ static Queue* s_requestQueue = nullptr; @@ -143,6 +148,14 @@ void cleanup() { delete s_requestQueue; s_requestQueue = nullptr; } + if (s_protocol != nullptr) { + delete s_protocol; + s_protocol = nullptr; + } + if (s_busHandler != nullptr) { + delete s_busHandler; + s_busHandler = nullptr; + } if (s_messageMap != nullptr) { delete s_messageMap; s_messageMap = nullptr; @@ -369,13 +382,31 @@ int main(int argc, char* argv[], char* envp[]) { return overallResult == RESULT_OK ? EXIT_SUCCESS : EXIT_FAILURE; } - // open the device - Device *device = Device::create(s_opt.device, s_opt.extraLatency, !s_opt.noDeviceCheck); - if (device == nullptr) { - logWrite(lf_main, ll_error, "unable to create device %s", s_opt.device); // force logging on exit + s_busHandler = new BusHandler(s_messageMap, s_scanHelper, s_opt.pollInterval); + + // create the protocol and open the device + ebus_protocol_config_t config = { + .device = s_opt.device, + .noDeviceCheck = s_opt.noDeviceCheck, + .readOnly = s_opt.readOnly, + .extraLatency = s_opt.extraLatency, + .ownAddress = s_opt.address, + .answer = s_opt.answer, + .busLostRetries = s_opt.acquireRetries, + .failedSendRetries = s_opt.sendRetries, + .busAcquireTimeout = s_opt.acquireTimeout, + .slaveRecvTimeout = s_opt.receiveTimeout, + .lockCount = s_opt.masterCount, + .generateSyn = s_opt.generateSyn, + .initialSend = s_opt.initialSend, + }; + s_protocol = ProtocolHandler::create(config, s_busHandler); + if (s_protocol == nullptr) { + logWrite(lf_main, ll_error, "unable to create protocol/device %s", s_opt.device); // force logging on exit cleanup(); return EINVAL; } + s_busHandler->setProtocol(s_protocol); if (!s_opt.foreground) { if (!setLogFile(s_opt.logFile)) { @@ -391,14 +422,28 @@ int main(int argc, char* argv[], char* envp[]) { signal(SIGINT, signalHandler); signal(SIGTERM, signalHandler); + if (s_opt.dumpFile[0]) { + s_protocol->setDumpFile(s_opt.dumpFile, s_opt.dumpSize, s_opt.dumpFlush); + if (s_opt.dump) { + s_protocol->toggleDump(); + } + } + if (s_opt.logRawFile[0] && strcmp(s_opt.logRawFile, s_opt.logFile) != 0) { + s_protocol->setLogRawFile(s_opt.logRawFile, s_opt.logRawSize); + } + if (s_opt.logRaw != 0) { + s_protocol->toggleLogRaw(s_opt.logRaw == 2); + } + + // open Device + s_protocol->open(); + // create the MainLoop s_requestQueue = new Queue(); - s_mainLoop = new MainLoop(s_opt, device, s_messageMap, s_scanHelper, s_requestQueue); - BusHandler* busHandler = s_mainLoop->getBusHandler(); - ProtocolHandler* protocol = busHandler->getProtocol(); + s_mainLoop = new MainLoop(s_opt, s_busHandler, s_messageMap, s_scanHelper, s_requestQueue); ostringstream ostream; - protocol->formatInfo(&ostream, false, true); + s_protocol->formatInfo(&ostream, false, true); string deviceInfoStr = ostream.str(); logNotice(lf_main, PACKAGE_STRING "." REVISION " started%s on device: %s", s_opt.scanConfig ? s_opt.initialScan == ESC ? " with auto scan" @@ -420,7 +465,7 @@ int main(int argc, char* argv[], char* envp[]) { if (!s_scanHelper->parseMessage(argv[arg_index], false, &master, &slave)) { continue; } - protocol->injectMessage(master, slave); + s_protocol->injectMessage(master, slave); if (s_opt.scanConfig && master.size() >= 5 && master[4] == 0 && master[2] == 0x07 && master[3] == 0x04 && isValidAddress(master[1], false) && !isMaster(master[1]) && !scanAddresses[master[1]]) { // scan message, simulate scanning @@ -428,11 +473,11 @@ int main(int argc, char* argv[], char* envp[]) { scanAdrCount++; } } - protocol->start("bushandler"); + s_protocol->start("bushandler"); for (symbol_t address = 0; scanAdrCount > 0; address++) { if (scanAddresses[address]) { scanAdrCount--; - busHandler->scanAndWait(address, true); + s_busHandler->scanAndWait(address, true); } } if (s_opt.stopAfterInject) { @@ -440,7 +485,7 @@ int main(int argc, char* argv[], char* envp[]) { return 0; } } else { - protocol->start("bushandler"); + s_protocol->start("bushandler"); } s_mainLoop->start("mainloop"); diff --git a/src/ebusd/main.h b/src/ebusd/main.h index 2935b1ce7..d29e6582d 100755 --- a/src/ebusd/main.h +++ b/src/ebusd/main.h @@ -33,8 +33,6 @@ namespace ebusd { */ /** the config path part behind the scheme (scheme without "://"). */ -//#define CONFIG_PATH_SUFFIX "://ebus.github.io/cfg/de/" - #define CONFIG_PATH_SUFFIX "://cfg.ebusd.eu/" /** A structure holding all program options. */ diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index 5da4dbab8..aafb7491b 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -32,8 +32,6 @@ namespace ebusd { using std::dec; -using std::hex; -using std::setfill; using std::setw; using std::endl; using std::ifstream; @@ -105,41 +103,19 @@ result_t UserList::addFromFile(const string& filename, unsigned int lineNo, map< #define VERBOSITY_4 (VERBOSITY_3 | OF_ALL_ATTRS) -MainLoop::MainLoop(const struct options& opt, Device *device, MessageMap* messages, ScanHelper* scanHelper, - Queue* requestQueue) - : Thread(), m_device(device), m_reconnectCount(0), m_userList(opt.accessLevel), m_messages(messages), +MainLoop::MainLoop(const struct options& opt, BusHandler* busHandler, + MessageMap* messages, ScanHelper* scanHelper, Queue* requestQueue) + : Thread(), m_busHandler(busHandler), m_protocol(busHandler->getProtocol()), m_reconnectCount(0), + m_userList(opt.accessLevel), m_messages(messages), m_scanHelper(scanHelper), m_address(opt.address), m_scanConfig(opt.scanConfig), m_initialScan(opt.readOnly ? ESC : opt.initialScan), m_scanRetries(opt.scanRetries), m_scanStatus(SCAN_STATUS_NONE), m_polling(opt.pollInterval > 0), m_enableHex(opt.enableHex), m_shutdown(false), m_runUpdateCheck(opt.updateCheck), m_httpClient(), m_requestQueue(requestQueue) { - m_device->setListener(this); - // open Device - result_t result = m_device->open(); - if (result != RESULT_OK) { - logError(lf_bus, "unable to open %s: %s", m_device->getName(), getResultCode(result)); - } else if (!m_device->isValid()) { - logError(lf_bus, "device %s not available", m_device->getName()); - } - if (opt.dumpFile[0]) { - m_dumpFile = new RotateFile(opt.dumpFile, opt.dumpSize, false, opt.dumpFlush ? 1 : 16); - m_dumpFile->setEnabled(opt.dump); - } else { - m_dumpFile = nullptr; - } - m_logRawEnabled = opt.logRaw != 0; - if (opt.logRawFile[0] && strcmp(opt.logRawFile, opt.logFile) != 0) { - m_logRawFile = new RotateFile(opt.logRawFile, opt.logRawSize, true); - m_logRawFile->setEnabled(m_logRawEnabled); - } else { - m_logRawFile = nullptr; - } - m_logRawBytes = opt.logRaw == 2; - m_logRawLastReceived = true; - m_logRawLastSymbol = SYN; if (opt.aclFile[0]) { string errorDescription; time_t mtime = 0; istream* stream = FileReader::openFile(opt.aclFile, &errorDescription, &mtime); + result_t result; if (stream) { result = m_userList.readFromStream(stream, opt.aclFile, mtime, false, nullptr, &errorDescription); delete(stream); @@ -150,24 +126,7 @@ MainLoop::MainLoop(const struct options& opt, Device *device, MessageMap* messag logError(lf_main, "error reading ACL file \"%s\": %s", opt.aclFile, getResultCode(result)); } } - // create BusHandler - ebus_protocol_config_t config = { - .readOnly = opt.readOnly, - .ownAddress = m_address, - .answer = opt.answer, - .busLostRetries = opt.acquireRetries, - .failedSendRetries = opt.sendRetries, - .busAcquireTimeout = opt.acquireTimeout, - .slaveRecvTimeout = opt.receiveTimeout, - .lockCount = opt.masterCount, - .generateSyn = opt.generateSyn, - .initialSend = opt.initialSend, - }; - m_busHandler = new BusHandler(m_device, m_messages, scanHelper, - config, opt.pollInterval); - m_protocol = m_busHandler->getProtocol(); - - // create network + m_htmlPath = opt.htmlPath; logInfo(lf_main, "registering data handlers"); if (datahandler_register(&m_userList, m_busHandler, messages, &m_dataHandlers)) { @@ -191,23 +150,6 @@ MainLoop::~MainLoop() { delete dataHandler; } m_dataHandlers.clear(); - if (m_dumpFile) { - delete m_dumpFile; - m_dumpFile = nullptr; - } - if (m_logRawFile) { - delete m_logRawFile; - m_logRawFile = nullptr; - } - if (m_busHandler != nullptr) { - delete m_busHandler; - m_busHandler = nullptr; - m_protocol = nullptr; // ProtocolHandler is freed by BusHandler - } - if (m_device != nullptr) { - delete m_device; - m_device = nullptr; - } if (m_newlyDefinedMessages) { delete m_newlyDefinedMessages; m_newlyDefinedMessages = nullptr; @@ -480,62 +422,6 @@ void MainLoop::run() { } } -void MainLoop::notifyDeviceData(symbol_t symbol, bool received) { - if (received && m_dumpFile) { - m_dumpFile->write(&symbol, 1); - } - if (!m_logRawFile && !m_logRawEnabled) { - return; - } - if (m_logRawBytes) { - if (m_logRawFile) { - m_logRawFile->write(&symbol, 1, received); - } else if (m_logRawEnabled) { - if (received) { - logNotice(lf_bus, "<%02x", symbol); - } else { - logNotice(lf_bus, ">%02x", symbol); - } - } - return; - } - if (symbol != SYN) { - if (received && !m_logRawLastReceived && symbol == m_logRawLastSymbol) { - return; // skip received echo of previously sent symbol - } - if (m_logRawBuffer.tellp() == 0 || received != m_logRawLastReceived) { - m_logRawLastReceived = received; - if (m_logRawBuffer.tellp() == 0 && m_logRawLastSymbol != SYN) { - m_logRawBuffer << "..."; - } - m_logRawBuffer << (received ? "<" : ">"); - } - m_logRawBuffer << setw(2) << setfill('0') << hex << static_cast(symbol); - } - m_logRawLastSymbol = symbol; - if (m_logRawBuffer.tellp() > (symbol == SYN ? 0 : 64)) { // flush: direction+5 hdr+24 max data+crc+direction+ack+1 - if (symbol != SYN) { - m_logRawBuffer << "..."; - } - const string bufStr = m_logRawBuffer.str(); - const char* str = bufStr.c_str(); - if (m_logRawFile) { - m_logRawFile->write((const unsigned char*)str, strlen(str), received, false); - } else { - logNotice(lf_bus, str); - } - m_logRawBuffer.str(""); - } -} - -void MainLoop::notifyStatus(bool error, const char* message) { - if (error) { - logError(lf_bus, "device status: %s", message); - } else { - logNotice(lf_bus, "device status: %s", message); - } -} - result_t MainLoop::decodeRequest(Request* req, bool* connected, RequestMode* reqMode, string* user, bool* reload, ostringstream* ostream) { vector args; @@ -1886,15 +1772,7 @@ result_t MainLoop::executeRaw(const vector& args, ostringstream* ostream " Toggle logging of messages or each byte."; return RESULT_OK; } - bool enabled; - m_logRawBytes = bytes; - if (m_logRawFile) { - enabled = !m_logRawFile->isEnabled(); - m_logRawFile->setEnabled(enabled); - } else { - enabled = !m_logRawEnabled; - m_logRawEnabled = enabled; - } + bool enabled = m_protocol->toggleLogRaw(bytes); *ostream << (enabled ? "raw logging enabled" : "raw logging disabled"); return RESULT_OK; } @@ -1905,12 +1783,11 @@ result_t MainLoop::executeDump(const vector& args, ostringstream* ostrea " Toggle binary dump of received bytes."; return RESULT_OK; } - if (!m_dumpFile) { + if (!m_protocol->hasDumpFile()) { *ostream << "dump not configured"; return RESULT_OK; } - bool enabled = !m_dumpFile->isEnabled(); - m_dumpFile->setEnabled(enabled); + bool enabled = m_protocol->toggleDump(); *ostream << (enabled ? "dump enabled" : "dump disabled"); return RESULT_OK; } diff --git a/src/ebusd/mainloop.h b/src/ebusd/mainloop.h index 4ef5ed086..43255752d 100644 --- a/src/ebusd/mainloop.h +++ b/src/ebusd/mainloop.h @@ -30,7 +30,7 @@ #include "ebusd/scan.h" #include "lib/ebus/filereader.h" #include "lib/ebus/message.h" -#include "lib/utils/rotatefile.h" +#include "lib/ebus/protocol.h" #include "lib/utils/httpclient.h" namespace ebusd { @@ -99,18 +99,18 @@ class UserList : public UserInfo, public MappedFileReader { /** * The main loop handling requests from connected clients. */ -class MainLoop : public Thread, DeviceListener { +class MainLoop : public Thread { public: /** * Construct the main loop and create bus handling components. * @param opt the program options. - * @param device the @a Device instance. + * @param busHandler @a BusHandler instance. * @param messages the @a MessageMap instance. * @param scanHelper the @a ScanHelper instance. * @param requestQueue the reference to the @a Request @a Queue. */ - MainLoop(const struct options& opt, Device *device, MessageMap* messages, ScanHelper* scanHelper, - Queue* requestQueue); + MainLoop(const struct options& opt, BusHandler* busHandler, + MessageMap* messages, ScanHelper* scanHelper, Queue* requestQueue); /** * Destructor. @@ -122,18 +122,6 @@ class MainLoop : public Thread, DeviceListener { */ void shutdown(); - /** - * Get the @a BusHandler instance. - * @return the created @a BusHandler instance. - */ - BusHandler* getBusHandler() { return m_busHandler; } - - // @copydoc - void notifyDeviceData(symbol_t symbol, bool received) override; - - // @copydoc - void notifyStatus(bool error, const char* message) override; - protected: // @copydoc @@ -370,33 +358,15 @@ class MainLoop : public Thread, DeviceListener { */ result_t formatHttpResult(result_t ret, int type, ostringstream* ostream); - /** the @a Device instance. */ - Device* m_device; + /** the @a BusHandler instance. */ + BusHandler* m_busHandler; + + /** the @a ProtocolHandler instance. */ + ProtocolHandler* m_protocol; /** the number of reconnects requested from the @a Device. */ unsigned int m_reconnectCount; - /** the @a RotateFile for writing sent/received bytes in log format, or nullptr. */ - RotateFile* m_logRawFile; - - /** whether raw logging to @p logNotice is enabled (only relevant if m_logRawFile is nullptr). */ - bool m_logRawEnabled; - - /** whether to log raw bytes instead of messages with @a m_logRawEnabled. */ - bool m_logRawBytes; - - /** the buffer for building log raw message. */ - ostringstream m_logRawBuffer; - - /** true when the last byte in @a m_logRawBuffer was receive, false if it was sent. */ - bool m_logRawLastReceived; - - /** the last sent/received symbol.*/ - symbol_t m_logRawLastSymbol; - - /** the @a RotateFile for dumping received data, or nullptr. */ - RotateFile* m_dumpFile; - /** the @a UserList instance. */ UserList m_userList; @@ -440,12 +410,6 @@ class MainLoop : public Thread, DeviceListener { /** the @a HttpClient for performing the update check. */ HttpClient m_httpClient; - /** the created @a BusHandler instance. */ - BusHandler* m_busHandler; - - /** the created @a ProtocolHandler instance. */ - ProtocolHandler* m_protocol; - /** the reference to the @a Request @a Queue. */ Queue* m_requestQueue; diff --git a/src/ebusd/mqtthandler.cpp b/src/ebusd/mqtthandler.cpp index 459c99d14..c490b55d3 100755 --- a/src/ebusd/mqtthandler.cpp +++ b/src/ebusd/mqtthandler.cpp @@ -775,7 +775,7 @@ void MqttHandler::run() { publishDefinition(m_replacers, "def_global_uptime-", uptimeTopic, "global", "uptime", "def_global-"); publishDefinition(m_replacers, "def_global_updatecheck-", m_globalTopic.get("", "updatecheck"), "global", "updatecheck", "def_global-"); - if (m_busHandler->getDevice()->supportsUpdateCheck()) { + if (m_busHandler->getProtocol()->supportsUpdateCheck()) { publishDefinition(m_replacers, "def_global_updatecheck_device-", m_globalTopic.get("", "updatecheck"), "global", "updatecheck_device", ""); } diff --git a/src/lib/ebus/protocol.cpp b/src/lib/ebus/protocol.cpp index 9707916aa..e87e594e8 100644 --- a/src/lib/ebus/protocol.cpp +++ b/src/lib/ebus/protocol.cpp @@ -20,13 +20,18 @@ # include #endif +#include +#include #include "lib/ebus/protocol.h" #include "lib/ebus/protocol_direct.h" -#include #include "lib/utils/log.h" namespace ebusd { +using std::hex; +using std::setfill; +using std::setw; + bool ActiveBusRequest::notify(result_t result, const SlaveSymbolString& slave) { if (result == RESULT_OK) { string str = slave.getStr(); @@ -37,12 +42,25 @@ bool ActiveBusRequest::notify(result_t result, const SlaveSymbolString& slave) { return false; } - ProtocolHandler* ProtocolHandler::create(const ebus_protocol_config_t config, - Device* device, ProtocolListener* listener) { + ProtocolListener* listener) { + Device *device = Device::create(config.device, config.extraLatency, !config.noDeviceCheck); + if (device == nullptr) { + return nullptr; + } return new DirectProtocolHandler(config, device, listener); } +result_t ProtocolHandler::open() { + result_t result = m_device->open(); + if (result != RESULT_OK) { + logError(lf_bus, "unable to open %s: %s", m_device->getName(), getResultCode(result)); + } else if (!m_device->isValid()) { + logError(lf_bus, "device %s not available", m_device->getName()); + } + return result; +} + void ProtocolHandler::formatInfo(ostringstream* ostream, bool verbose, bool noWait) { m_device->formatInfo(ostream, verbose, true); if (isReadOnly()) { @@ -58,6 +76,63 @@ void ProtocolHandler::formatInfoJson(ostringstream* ostream) { m_device->formatInfoJson(ostream); } +void ProtocolHandler::notifyDeviceData(symbol_t symbol, bool received) { + if (received && m_dumpFile) { + m_dumpFile->write(&symbol, 1); + } + if (!m_logRawFile && !m_logRawEnabled) { + return; + } + if (m_logRawBytes) { + if (m_logRawFile) { + m_logRawFile->write(&symbol, 1, received); + } else if (m_logRawEnabled) { + if (received) { + logNotice(lf_bus, "<%02x", symbol); + } else { + logNotice(lf_bus, ">%02x", symbol); + } + } + return; + } + if (symbol != SYN) { + if (received && !m_logRawLastReceived && symbol == m_logRawLastSymbol) { + return; // skip received echo of previously sent symbol + } + if (m_logRawBuffer.tellp() == 0 || received != m_logRawLastReceived) { + m_logRawLastReceived = received; + if (m_logRawBuffer.tellp() == 0 && m_logRawLastSymbol != SYN) { + m_logRawBuffer << "..."; + } + m_logRawBuffer << (received ? "<" : ">"); + } + m_logRawBuffer << setw(2) << setfill('0') << hex << static_cast(symbol); + } + m_logRawLastSymbol = symbol; + if (m_logRawBuffer.tellp() > (symbol == SYN ? 0 : 64)) { // flush: direction+5 hdr+24 max data+crc+direction+ack+1 + if (symbol != SYN) { + m_logRawBuffer << "..."; + } + const string bufStr = m_logRawBuffer.str(); + const char* str = bufStr.c_str(); + if (m_logRawFile) { + m_logRawFile->write((const unsigned char*)str, strlen(str), received, false); + } else { + logNotice(lf_bus, str); + } + m_logRawBuffer.str(""); + } +} + +void ProtocolHandler::notifyStatus(bool error, const char* message) { + if (error) { + logError(lf_bus, "device status: %s", message); + } else { + logNotice(lf_bus, "device status: %s", message); + } +} + + void ProtocolHandler::clear() { memset(m_seenAddresses, 0, sizeof(m_seenAddresses)); m_masterCount = 1; @@ -161,4 +236,45 @@ bool ProtocolHandler::addSeenAddress(symbol_t address) { return ret; } +void ProtocolHandler::setDumpFile(const char* dumpFile, unsigned int dumpSize, bool dumpFlush) { + if (m_dumpFile) { + delete m_dumpFile; + m_dumpFile = nullptr; + } + if (dumpFile && dumpFile[0]) { + m_dumpFile = new RotateFile(dumpFile, dumpSize, false, dumpFlush ? 1 : 16); + } +} + +bool ProtocolHandler::toggleDump() { + if (!m_dumpFile) { + return false; + } + bool enabled = !m_dumpFile->isEnabled(); + m_dumpFile->setEnabled(enabled); + return enabled; +} + +void ProtocolHandler::setLogRawFile(const char* logRawFile, unsigned int logRawSize) { + if (logRawFile[0]) { + m_logRawFile = new RotateFile(logRawFile, logRawSize, true); + m_logRawFile->setEnabled(m_logRawEnabled); + } else { + m_logRawFile = nullptr; + } +} + +bool ProtocolHandler::toggleLogRaw(bool bytes) { + bool enabled; + m_logRawBytes = bytes; + if (m_logRawFile) { + enabled = !m_logRawFile->isEnabled(); + m_logRawFile->setEnabled(enabled); + } else { + enabled = !m_logRawEnabled; + m_logRawEnabled = enabled; + } + return enabled; +} + } // namespace ebusd diff --git a/src/lib/ebus/protocol.h b/src/lib/ebus/protocol.h index 2424ad071..e5c244df8 100755 --- a/src/lib/ebus/protocol.h +++ b/src/lib/ebus/protocol.h @@ -23,6 +23,7 @@ #include "lib/ebus/result.h" #include "lib/ebus/device.h" #include "lib/utils/queue.h" +#include "lib/utils/rotatefile.h" #include "lib/utils/thread.h" namespace ebusd { @@ -52,8 +53,14 @@ namespace ebusd { /** settings for the eBUS protocol handler. */ typedef struct ebus_protocol_config { + /** eBUS device string (serial device or [udp:]ip:port) with optional protocol prefix (enh: or ens:). */ + const char* device; + /** whether to skip serial eBUS device test. */ + bool noDeviceCheck; /** whether to allow read access to the device only. */ bool readOnly; + /** extra transfer latency in ms. */ + unsigned int extraLatency; /** the own master address. */ symbol_t ownAddress; /** whether to answer queries for the own master/slave address. */ @@ -232,7 +239,7 @@ class ProtocolListener { /** * Handles input from and output to eBUS with respect to the eBUS protocol. */ -class ProtocolHandler : public WaitThread { +class ProtocolHandler : public WaitThread, DeviceListener { public: /** * Construct a new instance. @@ -249,8 +256,13 @@ class ProtocolHandler : public WaitThread { m_masterCount(config.readOnly ? 0 : 1), m_symbolLatencyMin(-1), m_symbolLatencyMax(-1), m_arbitrationDelayMin(-1), m_arbitrationDelayMax(-1), m_lastReceive(0), - m_symPerSec(0), m_maxSymPerSec(0) { + m_symPerSec(0), m_maxSymPerSec(0), + m_logRawFile(nullptr), m_logRawEnabled(false), m_logRawBytes(false), + m_logRawLastSymbol(SYN), m_dumpFile(nullptr) { memset(m_seenAddresses, 0, sizeof(m_seenAddresses)); + m_device->setListener(this); + m_logRawLastReceived = true; + m_logRawLastSymbol = SYN; } /** @@ -267,16 +279,33 @@ class ProtocolHandler : public WaitThread { delete req; } } + if (m_dumpFile) { + delete m_dumpFile; + m_dumpFile = nullptr; + } + if (m_logRawFile) { + delete m_logRawFile; + m_logRawFile = nullptr; + } + if (m_device != nullptr) { + delete m_device; + m_device = nullptr; + } } /** * Create a new instance. * @param config the configuration to use. - * @param device the @a Device instance for accessing the bus. * @param listener the @a ProtocolListener. * @return the new ProtocolHandler, or @a nullptr on error. */ - static ProtocolHandler* create(const ebus_protocol_config_t config, Device* device, ProtocolListener* listener); + static ProtocolHandler* create(const ebus_protocol_config_t config, ProtocolListener* listener); + + /** + * Open the device. + * @return the @a result_t code. + */ + virtual result_t open(); /** * Format device/protocol infos in plain text. @@ -334,9 +363,15 @@ class ProtocolHandler : public WaitThread { unsigned int getMaxSymPerSec() const { return m_maxSymPerSec; } /** - * @return the @a Device instance for accessing the bus. + * @return whether the device supports checking for version updates. */ - const Device* getDevice() const { return m_device; } + virtual bool supportsUpdateCheck() const { return m_device->supportsUpdateCheck(); } + + // @copydoc + void notifyDeviceData(symbol_t symbol, bool received) override; + + // @copydoc + void notifyStatus(bool error, const char* message) override; /** * Clear stored values (e.g. scan results). @@ -425,6 +460,37 @@ class ProtocolHandler : public WaitThread { */ unsigned int getMasterCount() const { return m_masterCount; } + /** + * Set the dump file to use. + * @param dumpFile the dump file to use, or nullptr. + * @param dumpSize the maximum file size. + * @param dumpFlush true to early flush the file. + */ + void setDumpFile(const char* dumpFile, unsigned int dumpSize, bool dumpFlush); + + /** + * @return whether a dump file is set. + */ + bool hasDumpFile() const { return m_dumpFile; } + + /** + * Toggle dumping to file. + * @return true if dumping is now enabled. + */ + bool toggleDump(); + + /** + * Set the log raw data file to use. + * @param logRawFile the log raw file to use, or nullptr. + * @param logRawSize the maximum file size. + */ + void setLogRawFile(const char* logRawFile, unsigned int logRawSize); + + /** + * Toggle logging raw data. + * @return true if logging raw data is now enabled. + */ + bool toggleLogRaw(bool bytes); protected: /** @@ -500,6 +566,27 @@ class ProtocolHandler : public WaitThread { /** the participating bus addresses seen so far. */ bool m_seenAddresses[256]; + + /** the @a RotateFile for writing sent/received bytes in log format, or nullptr. */ + RotateFile* m_logRawFile; + + /** whether raw logging to @p logNotice is enabled (only relevant if m_logRawFile is nullptr). */ + bool m_logRawEnabled; + + /** whether to log raw bytes instead of messages with @a m_logRawEnabled. */ + bool m_logRawBytes; + + /** the buffer for building log raw message. */ + ostringstream m_logRawBuffer; + + /** true when the last byte in @a m_logRawBuffer was receive, false if it was sent. */ + bool m_logRawLastReceived; + + /** the last sent/received symbol.*/ + symbol_t m_logRawLastSymbol; + + /** the @a RotateFile for dumping received data, or nullptr. */ + RotateFile* m_dumpFile; }; } // namespace ebusd From 077d09b620980da301b4d549d5c43b5c0e8e9833 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 4 Nov 2023 09:31:08 +0100 Subject: [PATCH 157/345] lint --- src/ebusd/mqttclient_mosquitto.cpp | 2 -- src/ebusd/mqtthandler.cpp | 2 +- src/ebusd/scan.cpp | 1 - src/lib/ebus/data.cpp | 3 +-- src/lib/ebus/datatype.cpp | 2 -- src/lib/ebus/device.cpp | 40 ++++++++++++++++++------------ src/lib/ebus/filereader.cpp | 2 -- src/lib/ebus/filereader.h | 6 ++++- src/lib/ebus/message.h | 2 +- src/lib/ebus/stringhelper.cpp | 2 +- src/lib/ebus/stringhelper.h | 2 +- src/lib/utils/arg.h | 3 ++- src/lib/utils/httpclient.cpp | 4 +-- 13 files changed, 38 insertions(+), 33 deletions(-) diff --git a/src/ebusd/mqttclient_mosquitto.cpp b/src/ebusd/mqttclient_mosquitto.cpp index 93d80ce43..903496571 100755 --- a/src/ebusd/mqttclient_mosquitto.cpp +++ b/src/ebusd/mqttclient_mosquitto.cpp @@ -31,8 +31,6 @@ namespace ebusd { -using std::dec; - bool check(int code, const char* method) { if (code == MOSQ_ERR_SUCCESS) { diff --git a/src/ebusd/mqtthandler.cpp b/src/ebusd/mqtthandler.cpp index c490b55d3..c734cc5cc 100755 --- a/src/ebusd/mqtthandler.cpp +++ b/src/ebusd/mqtthandler.cpp @@ -954,7 +954,7 @@ void MqttHandler::run() { auto vl = (dynamic_cast(field))->getList(); string entryFormat = values["field_values-entry"]; string::size_type pos = -1; - while ((pos = entryFormat.find('$', pos+1)) != std::string::npos) { + while ((pos = entryFormat.find('$', pos+1)) != string::npos) { if (entryFormat.substr(pos+1, 4) == "text" || entryFormat.substr(pos+1, 5) == "value") { entryFormat.replace(pos, 1, "%"); } diff --git a/src/ebusd/scan.cpp b/src/ebusd/scan.cpp index 4309cc42c..f3ee1fc88 100644 --- a/src/ebusd/scan.cpp +++ b/src/ebusd/scan.cpp @@ -40,7 +40,6 @@ using std::hex; using std::setfill; using std::setw; using std::nouppercase; -using std::cout; ScanHelper::~ScanHelper() { diff --git a/src/lib/ebus/data.cpp b/src/lib/ebus/data.cpp index f463ee70e..8defc41f9 100644 --- a/src/lib/ebus/data.cpp +++ b/src/lib/ebus/data.cpp @@ -32,7 +32,6 @@ namespace ebusd { using std::dec; -using std::hex; using std::setw; /** the week day names. */ @@ -1469,7 +1468,7 @@ bool DataFieldTemplates::dump(OutputFormat outputFormat, ostream* output) const if (outputFormat & OF_JSON) { if (dataField->isSet()) { if (prependFieldSeparator) { - *output << ",\n"; + *output << ",\n"; } *output << "{\"name\":\"" << dataField->getName(-1) << "\", \"sequence\": ["; dataField->dump(false, outputFormat, output); diff --git a/src/lib/ebus/datatype.cpp b/src/lib/ebus/datatype.cpp index 854097b51..6320f9447 100755 --- a/src/lib/ebus/datatype.cpp +++ b/src/lib/ebus/datatype.cpp @@ -34,13 +34,11 @@ namespace ebusd { using std::dec; -using std::defaultfloat; using std::hex; using std::fixed; using std::setfill; using std::setprecision; using std::setw; -using std::endl; using std::isfinite; diff --git a/src/lib/ebus/device.cpp b/src/lib/ebus/device.cpp index 1132bdaa8..b250b24f0 100755 --- a/src/lib/ebus/device.cpp +++ b/src/lib/ebus/device.cpp @@ -46,6 +46,14 @@ namespace ebusd { +using std::hex; +using std::dec; +using std::setfill; +using std::setw; +using std::setprecision; +using std::fixed; + + #define MTU 1540 #ifndef POLLRDHUP @@ -728,37 +736,37 @@ bool FileDevice::handleEnhancedBufferedData(symbol_t* value, ArbitrationState* a case 0x0200: case 0x0500: // with firmware version and jumper info case 0x0800: // with firmware version, jumper info, and bootloader version - stream << std::hex << static_cast(m_infoBuf[1]) // features mask + stream << hex << static_cast(m_infoBuf[1]) // features mask << "." << static_cast(m_infoBuf[0]); // version minor if (m_infoLen >= 5) { - stream << "[" << std::setfill('0') << std::setw(2) << std::hex << static_cast(m_infoBuf[2]) - << std::setw(2) << static_cast(m_infoBuf[3]) << "]"; + stream << "[" << setfill('0') << setw(2) << hex << static_cast(m_infoBuf[2]) + << setw(2) << static_cast(m_infoBuf[3]) << "]"; } if (m_infoLen >= 8) { - stream << "." << std::dec << static_cast(m_infoBuf[5]); - stream << "[" << std::setfill('0') << std::setw(2) << std::hex << static_cast(m_infoBuf[6]) - << std::setw(2) << static_cast(m_infoBuf[7]) << "]"; + stream << "." << dec << static_cast(m_infoBuf[5]); + stream << "[" << setfill('0') << setw(2) << hex << static_cast(m_infoBuf[6]) + << setw(2) << static_cast(m_infoBuf[7]) << "]"; } m_enhInfoVersion = stream.str(); stream.str(" "); stream << "firmware " << m_enhInfoVersion; if (m_infoLen >= 5) { - stream << ", jumpers 0x" << std::setw(2) << static_cast(m_infoBuf[4]); + stream << ", jumpers 0x" << setw(2) << static_cast(m_infoBuf[4]); } - stream << std::setfill(' '); // reset + stream << setfill(' '); // reset break; case 0x0901: case 0x0802: case 0x0302: stream << (m_infoId == 1 ? "ID" : "config"); - stream << std::hex << std::setfill('0'); + stream << hex << setfill('0'); for (uint8_t pos = 0; pos < m_infoPos; pos++) { - stream << " " << std::setw(2) << static_cast(m_infoBuf[pos]); + stream << " " << setw(2) << static_cast(m_infoBuf[pos]); } if (m_infoId == 2 && (m_infoBuf[2]&0x3f) != 0x3f) { // non-default arbitration delay val = (m_infoBuf[2]&0x3f)*10; // steps of 10us - stream << ", arbitration delay " << std::dec << static_cast(val) << " us"; + stream << ", arbitration delay " << dec << static_cast(val) << " us"; } break; case 0x0203: @@ -779,7 +787,7 @@ bool FileDevice::handleEnhancedBufferedData(symbol_t* value, ArbitrationState* a case 0x0205: stream << "bus voltage "; if (m_infoBuf[0] | m_infoBuf[1]) { - stream << std::fixed << std::setprecision(1) + stream << fixed << setprecision(1) << static_cast(m_infoBuf[1] / 10.0) << " V - " << static_cast(m_infoBuf[0] / 10.0) << " V"; } else { @@ -807,8 +815,8 @@ bool FileDevice::handleEnhancedBufferedData(symbol_t* value, ArbitrationState* a } break; default: - stream << "unknown 0x" << std::hex << std::setfill('0') << std::setw(2) - << static_cast(m_infoId) << ", len " << std::dec << std::setw(0) + stream << "unknown 0x" << hex << setfill('0') << setw(2) + << static_cast(m_infoId) << ", len " << dec << setw(0) << static_cast(m_infoPos); break; } @@ -834,7 +842,7 @@ bool FileDevice::handleEnhancedBufferedData(symbol_t* value, ArbitrationState* a stream << "overrun"; break; default: - stream << "unknown 0x" << std::setw(2) << std::setfill('0') << std::hex << static_cast(data); + stream << "unknown 0x" << setw(2) << setfill('0') << hex << static_cast(data); break; } string str = stream.str(); @@ -845,7 +853,7 @@ bool FileDevice::handleEnhancedBufferedData(symbol_t* value, ArbitrationState* a default: if (m_listener != nullptr) { ostringstream stream; - stream << "unexpected enhanced command 0x" << std::setw(2) << std::setfill('0') << std::hex + stream << "unexpected enhanced command 0x" << setw(2) << setfill('0') << hex << static_cast(cmd); string str = stream.str(); m_listener->notifyStatus(true, str.c_str()); diff --git a/src/lib/ebus/filereader.cpp b/src/lib/ebus/filereader.cpp index 545179b51..98072b290 100755 --- a/src/lib/ebus/filereader.cpp +++ b/src/lib/ebus/filereader.cpp @@ -32,8 +32,6 @@ using std::ifstream; using std::ostringstream; using std::cout; using std::endl; -using std::setw; -using std::dec; istream* FileReader::openFile(const string& filename, string* errorDescription, time_t* time) { diff --git a/src/lib/ebus/filereader.h b/src/lib/ebus/filereader.h index 08a698ddb..906e1fde0 100755 --- a/src/lib/ebus/filereader.h +++ b/src/lib/ebus/filereader.h @@ -44,6 +44,10 @@ using std::string; using std::map; using std::ostream; using std::istream; +using std::hex; +using std::dec; +using std::setw; +using std::setfill; /** the separator character used between fields. */ #define FIELD_SEPARATOR ',' @@ -174,7 +178,7 @@ class FileReader { * @param stream the @a ostream to write to. */ static void formatHash(size_t hash, ostream* stream) { - *stream << std::hex << std::setw(8) << std::setfill('0') << (hash & 0xffffffff) << std::dec << std::setw(0); + *stream << hex << setw(8) << setfill('0') << (hash & 0xffffffff) << dec << setw(0); } /** diff --git a/src/lib/ebus/message.h b/src/lib/ebus/message.h index 7d314670f..647539f77 100644 --- a/src/lib/ebus/message.h +++ b/src/lib/ebus/message.h @@ -902,7 +902,7 @@ class Condition { * Write the values part of the condition definition in JSON to the @a ostream. * @param output the @a ostream to append to. */ - virtual void dumpValuesJson(ostream* output) const { /* empty on top level*/ }; + virtual void dumpValuesJson(ostream* output) const { /* empty on top level*/ } /** * Combine this condition with another instance using a logical and. diff --git a/src/lib/ebus/stringhelper.cpp b/src/lib/ebus/stringhelper.cpp index 630b78f3f..22b27ad5c 100644 --- a/src/lib/ebus/stringhelper.cpp +++ b/src/lib/ebus/stringhelper.cpp @@ -499,7 +499,7 @@ bool StringReplacers::set(const string& key, const string& value, bool removeRep } void StringReplacers::set(const string& key, int value) { - std::ostringstream str; + ostringstream str; str << static_cast(value); m_constants[key] = str.str(); } diff --git a/src/lib/ebus/stringhelper.h b/src/lib/ebus/stringhelper.h index fd3b7dbe2..01561ee65 100644 --- a/src/lib/ebus/stringhelper.h +++ b/src/lib/ebus/stringhelper.h @@ -162,7 +162,7 @@ class StringReplacer { * the number is negative for plain strings, the index to @a knownFieldNames for a known field, or the size of * @a knownFieldNames for an unknown field. */ - vector> m_parts; + vector> m_parts; /** true when the complete result is supposed to be empty when at least one referenced variable * is empty or not defined. */ diff --git a/src/lib/utils/arg.h b/src/lib/utils/arg.h index 8ef289612..2cc98b2de 100755 --- a/src/lib/utils/arg.h +++ b/src/lib/utils/arg.h @@ -35,7 +35,8 @@ enum ArgFlag { typedef struct argDef { const char* name; //!< the (long) name of the argument, or nullptr for a group header or positional int key; //!< the argument key, also used as short name if alphabetic or the question mark - const char* valueName; //!< the optional argument value name, or nullptr for group header or argument without value name + // the optional argument value name, or nullptr for group header or argument without value name + const char* valueName; int flags; //!< flags for the argument, bit combination of @a ArgFlag const char* help; //!< help text (mandatory) } argDef; diff --git a/src/lib/utils/httpclient.cpp b/src/lib/utils/httpclient.cpp index c89e8e9e1..bbdc71c41 100755 --- a/src/lib/utils/httpclient.cpp +++ b/src/lib/utils/httpclient.cpp @@ -33,7 +33,6 @@ namespace ebusd { using std::string; using std::ostringstream; using std::dec; -using std::hex; #ifdef HAVE_SSL @@ -434,7 +433,8 @@ void HttpClient::disconnect() { } } -bool HttpClient::get(const string& uri, const string& body, string* response, bool* repeatable, time_t* time, bool* jsonString) { +bool HttpClient::get(const string& uri, const string& body, string* response, bool* repeatable, +time_t* time, bool* jsonString) { return request("GET", uri, body, response, repeatable, time, jsonString); } From e345b40d34990e26ab294edda6cca794f9431e89 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 10 Nov 2023 07:14:15 +0100 Subject: [PATCH 158/345] rename method --- src/lib/ebus/device.cpp | 22 +++++++++++----------- src/lib/ebus/device.h | 2 +- src/lib/ebus/protocol.cpp | 2 +- src/lib/ebus/protocol.h | 4 ++-- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/lib/ebus/device.cpp b/src/lib/ebus/device.cpp index b250b24f0..4be30ef6f 100755 --- a/src/lib/ebus/device.cpp +++ b/src/lib/ebus/device.cpp @@ -219,7 +219,7 @@ result_t FileDevice::afterOpen() { return RESULT_ERR_SEND; } if (m_listener != nullptr) { - m_listener->notifyStatus(false, "resetting"); + m_listener->notifyDeviceStatus(false, "resetting"); } m_resetRequested = true; } @@ -259,7 +259,7 @@ result_t FileDevice::requestEnhancedInfo(symbol_t infoId) { if (m_infoReqTime > 0 && time(NULL) > m_infoReqTime+5) { // request timed out if (m_listener != nullptr) { - m_listener->notifyStatus(false, "info request timed out"); + m_listener->notifyDeviceStatus(false, "info request timed out"); } m_infoId = 0xff; m_infoReqTime = 0; @@ -546,7 +546,7 @@ bool FileDevice::available() { DEBUG_RAW_TRAFFIC("avail enhanced following bad @%d+%d %2.2x %2.2x", m_bufPos, pos, m_buffer[(pos+m_bufPos)%m_bufSize], ch); if (m_listener != nullptr) { - m_listener->notifyStatus(true, "unexpected available enhanced following byte 1"); + m_listener->notifyDeviceStatus(true, "unexpected available enhanced following byte 1"); } // drop first byte of invalid sequence m_bufPos = (m_bufPos + 1) % m_bufSize; @@ -565,7 +565,7 @@ bool FileDevice::available() { } DEBUG_RAW_TRAFFIC("avail enhanced bad @%d+%d %2.2x", m_bufPos, pos, ch); if (m_listener != nullptr) { - m_listener->notifyStatus(true, "unexpected available enhanced byte 2"); + m_listener->notifyDeviceStatus(true, "unexpected available enhanced byte 2"); } // skip byte from erroneous protocol m_bufPos = (m_bufPos+1)%m_bufSize; @@ -582,7 +582,7 @@ bool FileDevice::read(symbol_t* value, bool isAvailable, ArbitrationState* arbit // more than half of input buffer consumed is taken as signal that ebusd is too slow m_bufLen = 0; if (m_listener != nullptr) { - m_listener->notifyStatus(true, "buffer overflow"); + m_listener->notifyDeviceStatus(true, "buffer overflow"); } } else { size_t tail; @@ -652,7 +652,7 @@ bool FileDevice::handleEnhancedBufferedData(symbol_t* value, ArbitrationState* a m_bufLen--; if (kind == ENH_BYTE2) { if (m_listener != nullptr) { - m_listener->notifyStatus(true, "unexpected enhanced byte 2"); + m_listener->notifyDeviceStatus(true, "unexpected enhanced byte 2"); } return false; } @@ -662,7 +662,7 @@ bool FileDevice::handleEnhancedBufferedData(symbol_t* value, ArbitrationState* a m_bufLen--; if ((ch2 & ENH_BYTE_MASK) != ENH_BYTE2) { if (m_listener != nullptr) { - m_listener->notifyStatus(true, "missing enhanced byte 2"); + m_listener->notifyDeviceStatus(true, "missing enhanced byte 2"); } return false; } @@ -720,7 +720,7 @@ bool FileDevice::handleEnhancedBufferedData(symbol_t* value, ArbitrationState* a cancelRunningArbitration(arbitrationState); } if (m_listener != nullptr) { - m_listener->notifyStatus(false, (m_extraFatures&0x01) ? "reset, supports info" : "reset"); + m_listener->notifyDeviceStatus(false, (m_extraFatures&0x01) ? "reset, supports info" : "reset"); } break; case ENH_RES_INFO: @@ -820,7 +820,7 @@ bool FileDevice::handleEnhancedBufferedData(symbol_t* value, ArbitrationState* a << static_cast(m_infoPos); break; } - m_listener->notifyStatus(false, ("extra info: "+stream.str()).c_str()); + m_listener->notifyDeviceStatus(false, ("extra info: "+stream.str()).c_str()); m_infoLen = 0; m_infoId = 0xff; } @@ -846,7 +846,7 @@ bool FileDevice::handleEnhancedBufferedData(symbol_t* value, ArbitrationState* a break; } string str = stream.str(); - m_listener->notifyStatus(true, str.c_str()); + m_listener->notifyDeviceStatus(true, str.c_str()); } cancelRunningArbitration(arbitrationState); break; @@ -856,7 +856,7 @@ bool FileDevice::handleEnhancedBufferedData(symbol_t* value, ArbitrationState* a stream << "unexpected enhanced command 0x" << setw(2) << setfill('0') << hex << static_cast(cmd); string str = stream.str(); - m_listener->notifyStatus(true, str.c_str()); + m_listener->notifyDeviceStatus(true, str.c_str()); } return false; } diff --git a/src/lib/ebus/device.h b/src/lib/ebus/device.h index 40bc9d628..00d913eb8 100755 --- a/src/lib/ebus/device.h +++ b/src/lib/ebus/device.h @@ -87,7 +87,7 @@ class DeviceListener { * @param error true for an error message, false for an info message. * @param message the message string. */ - virtual void notifyStatus(bool error, const char* message) = 0; // abstract + virtual void notifyDeviceStatus(bool error, const char* message) = 0; // abstract }; diff --git a/src/lib/ebus/protocol.cpp b/src/lib/ebus/protocol.cpp index e87e594e8..7eb657d87 100644 --- a/src/lib/ebus/protocol.cpp +++ b/src/lib/ebus/protocol.cpp @@ -124,7 +124,7 @@ void ProtocolHandler::notifyDeviceData(symbol_t symbol, bool received) { } } -void ProtocolHandler::notifyStatus(bool error, const char* message) { +void ProtocolHandler::notifyDeviceStatus(bool error, const char* message) { if (error) { logError(lf_bus, "device status: %s", message); } else { diff --git a/src/lib/ebus/protocol.h b/src/lib/ebus/protocol.h index e5c244df8..12476aa12 100755 --- a/src/lib/ebus/protocol.h +++ b/src/lib/ebus/protocol.h @@ -260,7 +260,7 @@ class ProtocolHandler : public WaitThread, DeviceListener { m_logRawFile(nullptr), m_logRawEnabled(false), m_logRawBytes(false), m_logRawLastSymbol(SYN), m_dumpFile(nullptr) { memset(m_seenAddresses, 0, sizeof(m_seenAddresses)); - m_device->setListener(this); + device->setListener(this); m_logRawLastReceived = true; m_logRawLastSymbol = SYN; } @@ -371,7 +371,7 @@ class ProtocolHandler : public WaitThread, DeviceListener { void notifyDeviceData(symbol_t symbol, bool received) override; // @copydoc - void notifyStatus(bool error, const char* message) override; + void notifyDeviceStatus(bool error, const char* message) override; /** * Clear stored values (e.g. scan results). From 009729cbdfe020268eaf6b1b144e95694122971e Mon Sep 17 00:00:00 2001 From: John Date: Fri, 10 Nov 2023 07:22:00 +0100 Subject: [PATCH 159/345] extract method --- src/lib/ebus/device.cpp | 191 +++++++++++++++++++++------------------- src/lib/ebus/device.h | 5 ++ 2 files changed, 105 insertions(+), 91 deletions(-) diff --git a/src/lib/ebus/device.cpp b/src/lib/ebus/device.cpp index 4be30ef6f..4b565d2d1 100755 --- a/src/lib/ebus/device.cpp +++ b/src/lib/ebus/device.cpp @@ -730,97 +730,7 @@ bool FileDevice::handleEnhancedBufferedData(symbol_t* value, ArbitrationState* a } else if (m_infoPos < m_infoLen && m_infoPos < sizeof(m_infoBuf)) { m_infoBuf[m_infoPos++] = data; if (m_infoPos >= m_infoLen) { - unsigned int val; - ostringstream stream; - switch ((m_infoLen << 8) | m_infoId) { - case 0x0200: - case 0x0500: // with firmware version and jumper info - case 0x0800: // with firmware version, jumper info, and bootloader version - stream << hex << static_cast(m_infoBuf[1]) // features mask - << "." << static_cast(m_infoBuf[0]); // version minor - if (m_infoLen >= 5) { - stream << "[" << setfill('0') << setw(2) << hex << static_cast(m_infoBuf[2]) - << setw(2) << static_cast(m_infoBuf[3]) << "]"; - } - if (m_infoLen >= 8) { - stream << "." << dec << static_cast(m_infoBuf[5]); - stream << "[" << setfill('0') << setw(2) << hex << static_cast(m_infoBuf[6]) - << setw(2) << static_cast(m_infoBuf[7]) << "]"; - } - m_enhInfoVersion = stream.str(); - stream.str(" "); - stream << "firmware " << m_enhInfoVersion; - if (m_infoLen >= 5) { - stream << ", jumpers 0x" << setw(2) << static_cast(m_infoBuf[4]); - } - stream << setfill(' '); // reset - break; - case 0x0901: - case 0x0802: - case 0x0302: - stream << (m_infoId == 1 ? "ID" : "config"); - stream << hex << setfill('0'); - for (uint8_t pos = 0; pos < m_infoPos; pos++) { - stream << " " << setw(2) << static_cast(m_infoBuf[pos]); - } - if (m_infoId == 2 && (m_infoBuf[2]&0x3f) != 0x3f) { - // non-default arbitration delay - val = (m_infoBuf[2]&0x3f)*10; // steps of 10us - stream << ", arbitration delay " << dec << static_cast(val) << " us"; - } - break; - case 0x0203: - val = (static_cast(m_infoBuf[0]) << 8) | static_cast(m_infoBuf[1]); - stream << "temperature " << static_cast(val) << " °C"; - m_enhInfoTemperature = stream.str(); - break; - case 0x0204: - stream << "supply voltage "; - if (m_infoBuf[0] | m_infoBuf[1]) { - val = (static_cast(m_infoBuf[0]) << 8) | static_cast(m_infoBuf[1]); - stream << static_cast(val) << " mV"; - } else { - stream << "unknown"; - } - m_enhInfoSupplyVoltage = stream.str(); - break; - case 0x0205: - stream << "bus voltage "; - if (m_infoBuf[0] | m_infoBuf[1]) { - stream << fixed << setprecision(1) - << static_cast(m_infoBuf[1] / 10.0) << " V - " - << static_cast(m_infoBuf[0] / 10.0) << " V"; - } else { - stream << "unknown"; - } - m_enhInfoBusVoltage = stream.str(); - break; - case 0x0206: - stream << "reset cause "; - if (m_infoBuf[0]) { - stream << static_cast(m_infoBuf[0]) << "="; - switch (m_infoBuf[0]) { - case 1: stream << "power-on"; break; - case 2: stream << "brown-out"; break; - case 3: stream << "watchdog"; break; - case 4: stream << "clear"; break; - case 5: stream << "reset"; break; - case 6: stream << "stack"; break; - case 7: stream << "memory"; break; - default: stream << "other"; break; - } - stream << ", restart count " << static_cast(m_infoBuf[1]); - } else { - stream << "unknown"; - } - break; - default: - stream << "unknown 0x" << hex << setfill('0') << setw(2) - << static_cast(m_infoId) << ", len " << dec << setw(0) - << static_cast(m_infoPos); - break; - } - m_listener->notifyDeviceStatus(false, ("extra info: "+stream.str()).c_str()); + notifyInfoRetrieved(); m_infoLen = 0; m_infoId = 0xff; } @@ -864,6 +774,105 @@ bool FileDevice::handleEnhancedBufferedData(symbol_t* value, ArbitrationState* a return false; } +void FileDevice::notifyInfoRetrieved() { + symbol_t* data = m_infoBuf; + size_t len = m_infoLen; + symbol_t id = m_infoId; + unsigned int val; + ostringstream stream; + switch ((len << 8) | id) { + case 0x0200: + case 0x0500: // with firmware version and jumper info + case 0x0800: // with firmware version, jumper info, and bootloader version + stream << hex << static_cast(data[1]) // features mask + << "." << static_cast(data[0]); // version minor + if (len >= 5) { + stream << "[" << setfill('0') << setw(2) << hex << static_cast(data[2]) + << setw(2) << static_cast(data[3]) << "]"; + } + if (len >= 8) { + stream << "." << dec << static_cast(data[5]); + stream << "[" << setfill('0') << setw(2) << hex << static_cast(data[6]) + << setw(2) << static_cast(data[7]) << "]"; + } + m_enhInfoVersion = stream.str(); + stream.str(" "); + stream << "firmware " << m_enhInfoVersion; + if (len >= 5) { + stream << ", jumpers 0x" << setw(2) << static_cast(data[4]); + } + stream << setfill(' '); // reset + break; + case 0x0901: + case 0x0802: + case 0x0302: + stream << (id == 1 ? "ID" : "config"); + stream << hex << setfill('0'); + for (uint8_t pos = 0; pos < len; pos++) { + stream << " " << setw(2) << static_cast(data[pos]); + } + if (id == 2 && (data[2]&0x3f) != 0x3f) { + // non-default arbitration delay + val = (data[2]&0x3f)*10; // steps of 10us + stream << ", arbitration delay " << dec << static_cast(val) << " us"; + } + break; + case 0x0203: + val = (static_cast(data[0]) << 8) | static_cast(data[1]); + stream << "temperature " << static_cast(val) << " °C"; + m_enhInfoTemperature = stream.str(); + break; + case 0x0204: + stream << "supply voltage "; + if (data[0] | data[1]) { + val = (static_cast(data[0]) << 8) | static_cast(data[1]); + stream << static_cast(val) << " mV"; + } else { + stream << "unknown"; + } + m_enhInfoSupplyVoltage = stream.str(); + break; + case 0x0205: + stream << "bus voltage "; + if (data[0] | data[1]) { + stream << fixed << setprecision(1) + << static_cast(data[1] / 10.0) << " V - " + << static_cast(data[0] / 10.0) << " V"; + } else { + stream << "unknown"; + } + m_enhInfoBusVoltage = stream.str(); + break; + case 0x0206: + stream << "reset cause "; + if (data[0]) { + stream << static_cast(data[0]) << "="; + switch (data[0]) { + case 1: stream << "power-on"; break; + case 2: stream << "brown-out"; break; + case 3: stream << "watchdog"; break; + case 4: stream << "clear"; break; + case 5: stream << "reset"; break; + case 6: stream << "stack"; break; + case 7: stream << "memory"; break; + default: stream << "other"; break; + } + stream << ", restart count " << static_cast(data[1]); + } else { + stream << "unknown"; + } + break; + default: + stream << "unknown 0x" << hex << setfill('0') << setw(2) + << static_cast(id) << ", len " << dec << setw(0) + << static_cast(len); + break; + } + if (m_listener != nullptr) { + m_listener->notifyDeviceStatus(false, ("extra info: "+stream.str()).c_str()); + } +} + result_t SerialDevice::open() { result_t result = FileDevice::open(); diff --git a/src/lib/ebus/device.h b/src/lib/ebus/device.h index 00d913eb8..27ba4c66a 100755 --- a/src/lib/ebus/device.h +++ b/src/lib/ebus/device.h @@ -378,6 +378,11 @@ class FileDevice : public Device { */ bool handleEnhancedBufferedData(symbol_t* value, ArbitrationState* arbitrationState); + /** + * Called when reception of an info ID was completed. + */ + void notifyInfoRetrieved(); + /** the arbitration master address to send when in arbitration, or @a SYN. */ symbol_t m_arbitrationMaster; From 046864381701aff9d85c4fd4a50c425d5cf4a729 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 10 Nov 2023 18:28:28 +0100 Subject: [PATCH 160/345] add enum, small optimization, formatting --- src/lib/ebus/device.cpp | 89 ++++++++++++++++++++++------------------- src/lib/ebus/device.h | 43 +++++++++++--------- 2 files changed, 70 insertions(+), 62 deletions(-) diff --git a/src/lib/ebus/device.cpp b/src/lib/ebus/device.cpp index 4b565d2d1..13a2889cc 100755 --- a/src/lib/ebus/device.cpp +++ b/src/lib/ebus/device.cpp @@ -81,7 +81,9 @@ using std::fixed; #define ENH_BYTE_MASK ((uint8_t)0xc0) #define ENH_BYTE1 ((uint8_t)0xc0) #define ENH_BYTE2 ((uint8_t)0x80) -#define makeEnhancedSequence(cmd, data) {(uint8_t)(ENH_BYTE1 | ((cmd)<<2) | (((data)&0xc0)>>6)), (uint8_t)(ENH_BYTE2 | ((data)&0x3f))} +#define makeEnhancedByte1(cmd, data) (uint8_t)(ENH_BYTE1 | ((cmd)<<2) | (((data)&0xc0)>>6)) +#define makeEnhancedByte2(cmd, data) (uint8_t)(ENH_BYTE2 | ((data)&0x3f)) +#define makeEnhancedSequence(cmd, data) {makeEnhancedByte1(cmd, data), makeEnhancedByte2(cmd, data)} #ifdef DEBUG_RAW_TRAFFIC #define DEBUG_RAW_TRAFFIC_HEAD(format, args...) fprintf(stdout, "%lld raw: " format, clockGetMillis(), args) @@ -101,24 +103,26 @@ Device::Device(const char* name) } Device* Device::create(const char* name, unsigned int extraLatency, bool checkDevice) { - bool highSpeed = strncmp(name, "ens:", 4) == 0; - bool enhanced = highSpeed || strncmp(name, "enh:", 4) == 0; - if (enhanced) { - name += 4; + EnhancedLevel enhanced = el_none; + if (strncmp(name, "en", 2) == 0 && name[2] && name[3] == ':') { + switch (name[2]) { + case 's': + enhanced = el_speed; + break; + case 'h': + enhanced = el_basic; + break; + } + if (enhanced) { + name += 4; + } } if (strchr(name, '/') == nullptr && strchr(name, ':') != nullptr) { char* in = strdup(name); bool udp = false; char* addrpos = in; char* portpos = strchr(addrpos, ':'); - if (!enhanced && portpos >= addrpos+3 && strncmp(addrpos, "enh", 3) == 0) { - enhanced = true; // support enhtcp:: and enhudp:: - addrpos += 3; - if (portpos == addrpos) { - addrpos++; - portpos = strchr(addrpos, ':'); - } - } // else: support enh:: defaulting to TCP + // support tcp:: and udp:: if (portpos == addrpos+3 && (strncmp(addrpos, "tcp", 3) == 0 || (udp=(strncmp(addrpos, "udp", 3) == 0)))) { addrpos += 4; portpos = strchr(addrpos, ':'); @@ -139,17 +143,17 @@ Device* Device::create(const char* name, unsigned int extraLatency, bool checkDe return new NetworkDevice(name, hostOrIp, port, extraLatency, udp, enhanced); } // support enh:/dev/, ens:/dev/, and /dev/ - return new SerialDevice(name, checkDevice, extraLatency, enhanced, highSpeed); + return new SerialDevice(name, checkDevice, extraLatency, enhanced); } FileDevice::FileDevice(const char* name, bool checkDevice, unsigned int latency, - bool enhancedProto) + EnhancedLevel enhancedLevel) : Device(name), m_checkDevice(checkDevice), - m_latency(HOST_LATENCY_MS+(enhancedProto?ENHANCED_LATENCY_MS:0)+latency), - m_enhancedProto(enhancedProto), m_fd(-1), m_resetRequested(false), + m_latency(HOST_LATENCY_MS+(enhancedLevel?ENHANCED_LATENCY_MS:0)+latency), + m_enhancedLevel(enhancedLevel), m_fd(-1), m_resetRequested(false), m_arbitrationMaster(SYN), m_arbitrationCheck(0), m_bufSize(((MAX_LEN+1+3)/4)*4), m_bufLen(0), m_bufPos(0), m_extraFatures(0), m_infoId(0xff), m_infoReqTime(0), m_infoLen(0), m_infoPos(0) { @@ -163,6 +167,7 @@ FileDevice::~FileDevice() { close(); if (m_buffer) { free(m_buffer); + m_buffer = nullptr; } } @@ -178,7 +183,7 @@ void FileDevice::formatInfo(ostringstream* ostream, bool verbose, bool prefix) { if (!isValid()) { *ostream << ", invalid"; } - if (!m_enhancedProto) { + if (!m_enhancedLevel) { return; } bool infoAdded = false; @@ -198,7 +203,7 @@ void FileDevice::formatInfo(ostringstream* ostream, bool verbose, bool prefix) { } void FileDevice::formatInfoJson(ostringstream* ostream) { - if (m_enhancedProto) { + if (m_enhancedLevel) { string ver = getEnhancedVersion(); if (!ver.empty()) { *ostream << ",\"dv\":\"" << ver << "\""; @@ -212,7 +217,7 @@ result_t FileDevice::open() { } result_t FileDevice::afterOpen() { - if (m_enhancedProto) { + if (m_enhancedLevel) { symbol_t buf[2] = makeEnhancedSequence(ENH_REQ_INIT, 0x01); // extra feature: info DEBUG_RAW_TRAFFIC("enhanced > %2.2x %2.2x", buf[0], buf[1]); if (::write(m_fd, buf, 2) != 2) { @@ -246,7 +251,7 @@ bool FileDevice::isValid() { } result_t FileDevice::requestEnhancedInfo(symbol_t infoId) { - if (!m_enhancedProto || m_extraFatures == 0) { + if (!m_enhancedLevel || m_extraFatures == 0) { return RESULT_ERR_INVALID_ARG; } for (unsigned int i = 0; i < 4; i++) { @@ -287,7 +292,7 @@ result_t FileDevice::sendEnhancedInfoRequest(symbol_t infoId) { } string FileDevice::getEnhancedInfos() { - if (!m_enhancedProto || m_extraFatures == 0) { + if (!m_enhancedLevel || m_extraFatures == 0) { return ""; } result_t res; @@ -356,14 +361,14 @@ result_t FileDevice::send(symbol_t value) { bool FileDevice::cancelRunningArbitration(ArbitrationState* arbitrationState) { - if (m_enhancedProto && m_arbitrationMaster != SYN) { + if (m_enhancedLevel && m_arbitrationMaster != SYN) { *arbitrationState = as_error; m_arbitrationMaster = SYN; m_arbitrationCheck = 0; write(SYN, true); return true; } - if (m_enhancedProto || m_arbitrationMaster == SYN) { + if (m_enhancedLevel || m_arbitrationMaster == SYN) { return false; } *arbitrationState = as_error; @@ -449,11 +454,11 @@ result_t FileDevice::recv(unsigned int timeout, symbol_t* value, ArbitrationStat } timeout = static_cast(until - now); } while (true); - if (m_enhancedProto || *value != SYN || m_arbitrationMaster == SYN || m_arbitrationCheck) { + if (m_enhancedLevel || *value != SYN || m_arbitrationMaster == SYN || m_arbitrationCheck) { if (m_listener != nullptr) { m_listener->notifyDeviceData(*value, true); } - if (!m_enhancedProto && m_arbitrationMaster != SYN) { + if (!m_enhancedLevel && m_arbitrationMaster != SYN) { if (m_arbitrationCheck) { *arbitrationState = *value == m_arbitrationMaster ? as_won : as_lost; m_arbitrationMaster = SYN; @@ -488,7 +493,7 @@ result_t FileDevice::startArbitration(symbol_t masterAddress) { } m_arbitrationCheck = 0; m_arbitrationMaster = SYN; - if (m_enhancedProto) { + if (m_enhancedLevel) { // cancel running arbitration if (!write(SYN, true)) { return RESULT_ERR_SEND; @@ -497,7 +502,7 @@ result_t FileDevice::startArbitration(symbol_t masterAddress) { return RESULT_OK; } m_arbitrationMaster = masterAddress; - if (m_enhancedProto && masterAddress != SYN) { + if (m_enhancedLevel && masterAddress != SYN) { if (!write(masterAddress, true)) { m_arbitrationMaster = SYN; return RESULT_ERR_SEND; @@ -508,7 +513,7 @@ result_t FileDevice::startArbitration(symbol_t masterAddress) { } bool FileDevice::write(symbol_t value, bool startArbitration) { - if (m_enhancedProto) { + if (m_enhancedLevel) { symbol_t buf[2] = makeEnhancedSequence(startArbitration ? ENH_REQ_START : ENH_REQ_SEND, value); DEBUG_RAW_TRAFFIC("enhanced > %2.2x %2.2x", buf[0], buf[1]); return ::write(m_fd, buf, 2) == 2; @@ -525,7 +530,7 @@ bool FileDevice::available() { if (m_bufLen <= 0) { return false; } - if (!m_enhancedProto) { + if (!m_enhancedLevel) { return true; } // peek into the received enhanced proto bytes to determine received bus symbol availability @@ -615,18 +620,18 @@ bool FileDevice::read(symbol_t* value, bool isAvailable, ArbitrationState* arbit #endif m_bufLen += size; } - if (m_enhancedProto) { + if (m_enhancedLevel) { if (handleEnhancedBufferedData(value, arbitrationState)) { return true; } } if (!available()) { if (incomplete) { - *incomplete = m_enhancedProto && m_bufLen > 0; + *incomplete = m_enhancedLevel && m_bufLen > 0; } return false; } - if (!m_enhancedProto) { + if (!m_enhancedLevel) { *value = m_buffer[m_bufPos]; m_bufPos = (m_bufPos+1)%m_bufSize; m_bufLen--; @@ -785,7 +790,7 @@ void FileDevice::notifyInfoRetrieved() { case 0x0500: // with firmware version and jumper info case 0x0800: // with firmware version, jumper info, and bootloader version stream << hex << static_cast(data[1]) // features mask - << "." << static_cast(data[0]); // version minor + << "." << static_cast(data[0]); // version minor if (len >= 5) { stream << "[" << setfill('0') << setw(2) << hex << static_cast(data[2]) << setw(2) << static_cast(data[3]) << "]"; @@ -793,7 +798,7 @@ void FileDevice::notifyInfoRetrieved() { if (len >= 8) { stream << "." << dec << static_cast(data[5]); stream << "[" << setfill('0') << setw(2) << hex << static_cast(data[6]) - << setw(2) << static_cast(data[7]) << "]"; + << setw(2) << static_cast(data[7]) << "]"; } m_enhInfoVersion = stream.str(); stream.str(" "); @@ -836,8 +841,8 @@ void FileDevice::notifyInfoRetrieved() { stream << "bus voltage "; if (data[0] | data[1]) { stream << fixed << setprecision(1) - << static_cast(data[1] / 10.0) << " V - " - << static_cast(data[0] / 10.0) << " V"; + << static_cast(data[1] / 10.0) << " V - " + << static_cast(data[0] / 10.0) << " V"; } else { stream << "unknown"; } @@ -864,8 +869,8 @@ void FileDevice::notifyInfoRetrieved() { break; default: stream << "unknown 0x" << hex << setfill('0') << setw(2) - << static_cast(id) << ", len " << dec << setw(0) - << static_cast(len); + << static_cast(id) << ", len " << dec << setw(0) + << static_cast(len); break; } if (m_listener != nullptr) { @@ -922,10 +927,10 @@ result_t SerialDevice::open() { memset(&newSettings, 0, sizeof(newSettings)); #ifdef HAVE_CFSETSPEED - cfsetspeed(&newSettings, m_enhancedProto ? (m_enhancedHighSpeed ? B115200 : B9600) : B2400); + cfsetspeed(&newSettings, m_enhancedLevel ? (m_enhancedLevel >= el_speed ? B115200 : B9600) : B2400); #else - cfsetispeed(&newSettings, m_enhancedProto ? (m_enhancedHighSpeed ? B115200 : B9600) : B2400); - cfsetospeed(&newSettings, m_enhancedProto ? (m_enhancedHighSpeed ? B115200 : B9600) : B2400); + cfsetispeed(&newSettings, m_enhancedLevel ? (m_enhancedLevel >= el_speed ? B115200 : B9600) : B2400); + cfsetospeed(&newSettings, m_enhancedLevel ? (m_enhancedLevel >= el_speed ? B115200 : B9600) : B2400); #endif newSettings.c_cflag |= (CS8 | CLOCAL | CREAD); newSettings.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // non-canonical mode diff --git a/src/lib/ebus/device.h b/src/lib/ebus/device.h index 27ba4c66a..238297b77 100755 --- a/src/lib/ebus/device.h +++ b/src/lib/ebus/device.h @@ -76,7 +76,7 @@ class DeviceListener { virtual ~DeviceListener() {} /** - * Listener method that is called when a symbol was received/sent. + * Listener method that is called when a symbol was received from/sent to eBUS. * @param symbol the received/sent symbol. * @param received @a true on reception, @a false on sending. */ @@ -212,6 +212,14 @@ class Device { }; +/** the possible enhanced protocol levels. */ +enum EnhancedLevel { + el_none = 0, //!< non-enhanced + el_basic = 1, //!< enhanced basic + el_speed = 2, //!< enhanced high-speed +}; + + /** * The common base class for devices using a file descriptor. */ @@ -222,10 +230,10 @@ class FileDevice : public Device { * @param name the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network). * @param checkDevice whether to regularly check the device availability. * @param latency the bus transfer latency in milliseconds. - * @param enhancedProto whether to use the ebusd enhanced protocol. + * @param enhancedLevel whether to use the ebusd enhanced protocol. */ FileDevice(const char* name, bool checkDevice, unsigned int latency, - bool enhancedProto = false); + EnhancedLevel enhancedLevel); public: /** @@ -273,21 +281,21 @@ class FileDevice : public Device { * Return whether the device supports the ebusd enhanced protocol. * @return whether the device supports the ebusd enhanced protocol. */ - bool isEnhancedProto() const { return m_enhancedProto; } + bool isEnhancedProto() const { return m_enhancedLevel != el_none; } /** * Get info about enhanced protocol support as string. * @return a @a string describing level of enhanced protocol support, or the empty string. */ - virtual string getEnhancedProtoInfo() const { return m_enhancedProto ? "enhanced" : ""; } + virtual string getEnhancedProtoInfo() const { return m_enhancedLevel ? "enhanced" : ""; } // @copydoc - bool supportsUpdateCheck() const override { return m_enhancedProto && m_extraFatures & 0x01; } + bool supportsUpdateCheck() const override { return m_enhancedLevel && m_extraFatures & 0x01; } /** * @return whether the device supports the ebusd enhanced protocol and supports querying extra infos. */ - bool supportsEnhancedInfos() const { return m_enhancedProto && m_extraFatures & 0x01; } + bool supportsEnhancedInfos() const { return m_enhancedLevel && m_extraFatures & 0x01; } /** * Check for a running extra infos request, wait for it to complete, @@ -361,7 +369,7 @@ class FileDevice : public Device { const unsigned int m_latency; /** whether the device supports the ebusd enhanced protocol. */ - const bool m_enhancedProto; + const EnhancedLevel m_enhancedLevel; /** the opened file descriptor, or -1. */ int m_fd; @@ -444,18 +452,16 @@ class SerialDevice : public FileDevice { * @param name the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network). * @param checkDevice whether to regularly check the device availability. * @param extraLatency the extra bus transfer latency in milliseconds. - * @param enhancedProto whether to use the ebusd enhanced protocol. - * @param enhancedHighSpeed whether to use ebusd enhanced protocol in high speed mode. + * @param enhancedLevel whether to use the ebusd enhanced protocol. */ SerialDevice(const char* name, bool checkDevice, unsigned int extraLatency, - bool enhancedProto = false, bool enhancedHighSpeed = false) - : FileDevice(name, checkDevice, extraLatency, enhancedProto), - m_enhancedHighSpeed(enhancedHighSpeed) { + EnhancedLevel enhancedLevel) + : FileDevice(name, checkDevice, extraLatency, enhancedLevel) { } // @copydoc string getEnhancedProtoInfo() const override { - return m_enhancedProto ? (m_enhancedHighSpeed ? "enhanced high speed" : "enhanced") : ""; + return m_enhancedLevel == el_speed ? "enhanced high speed" : FileDevice::getEnhancedProtoInfo(); } // @copydoc @@ -471,9 +477,6 @@ class SerialDevice : public FileDevice { private: - /** whether to use ebusd enhanced protocol in high speed mode. */ - bool m_enhancedHighSpeed; - /** the previous settings of the device for restoring. */ termios m_oldSettings; }; @@ -491,11 +494,11 @@ class NetworkDevice : public FileDevice { * @param port the TCP or UDP port of the device. * @param extraLatency the extra bus transfer latency in milliseconds. * @param udp true for UDP, false to TCP. - * @param enhancedProto whether to use the ebusd enhanced protocol. + * @param enhancedLevel whether to use the ebusd enhanced protocol. */ NetworkDevice(const char* name, const char* hostOrIp, uint16_t port, unsigned int extraLatency, - bool udp, bool enhancedProto = false) - : FileDevice(name, true, NETWORK_LATENCY_MS+extraLatency, enhancedProto), + bool udp, EnhancedLevel enhancedLevel) + : FileDevice(name, true, NETWORK_LATENCY_MS+extraLatency, enhancedLevel), m_hostOrIp(hostOrIp), m_port(port), m_udp(udp) {} /** From 307786e08b7a65f4c453a7e54d548e6925e76104 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 10 Nov 2023 18:47:40 +0100 Subject: [PATCH 161/345] move factory method --- src/lib/ebus/device.cpp | 44 ------------------------------------- src/lib/ebus/device.h | 10 --------- src/lib/ebus/protocol.cpp | 46 ++++++++++++++++++++++++++++++++++++--- 3 files changed, 43 insertions(+), 57 deletions(-) diff --git a/src/lib/ebus/device.cpp b/src/lib/ebus/device.cpp index 13a2889cc..da3ee80cd 100755 --- a/src/lib/ebus/device.cpp +++ b/src/lib/ebus/device.cpp @@ -102,50 +102,6 @@ Device::Device(const char* name) : m_name(name), m_listener(nullptr) { } -Device* Device::create(const char* name, unsigned int extraLatency, bool checkDevice) { - EnhancedLevel enhanced = el_none; - if (strncmp(name, "en", 2) == 0 && name[2] && name[3] == ':') { - switch (name[2]) { - case 's': - enhanced = el_speed; - break; - case 'h': - enhanced = el_basic; - break; - } - if (enhanced) { - name += 4; - } - } - if (strchr(name, '/') == nullptr && strchr(name, ':') != nullptr) { - char* in = strdup(name); - bool udp = false; - char* addrpos = in; - char* portpos = strchr(addrpos, ':'); - // support tcp:: and udp:: - if (portpos == addrpos+3 && (strncmp(addrpos, "tcp", 3) == 0 || (udp=(strncmp(addrpos, "udp", 3) == 0)))) { - addrpos += 4; - portpos = strchr(addrpos, ':'); - } - if (portpos == nullptr) { - free(in); - return nullptr; // invalid protocol or missing port - } - result_t result = RESULT_OK; - uint16_t port = (uint16_t)parseInt(portpos+1, 10, 1, 65535, &result); - if (result != RESULT_OK) { - free(in); - return nullptr; // invalid port - } - *portpos = 0; - char* hostOrIp = strdup(addrpos); - free(in); - return new NetworkDevice(name, hostOrIp, port, extraLatency, udp, enhanced); - } - // support enh:/dev/, ens:/dev/, and /dev/ - return new SerialDevice(name, checkDevice, extraLatency, enhanced); -} - FileDevice::FileDevice(const char* name, bool checkDevice, unsigned int latency, diff --git a/src/lib/ebus/device.h b/src/lib/ebus/device.h index 238297b77..60bfa53b9 100755 --- a/src/lib/ebus/device.h +++ b/src/lib/ebus/device.h @@ -108,16 +108,6 @@ class Device { */ virtual ~Device() { } - /** - * Factory method for creating a new instance. - * @param name the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network). - * @param extraLatency the extra bus transfer latency in milliseconds. - * @param checkDevice whether to regularly check the device availability (only for serial devices). - * @return the new @a Device, or nullptr on error. - * Note: the caller needs to free the created instance. - */ - static Device* create(const char* name, unsigned int extraLatency = 0, bool checkDevice = true); - /** * Get the device name. * @return the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network). diff --git a/src/lib/ebus/protocol.cpp b/src/lib/ebus/protocol.cpp index 7eb657d87..fa31bd4ca 100644 --- a/src/lib/ebus/protocol.cpp +++ b/src/lib/ebus/protocol.cpp @@ -44,9 +44,49 @@ bool ActiveBusRequest::notify(result_t result, const SlaveSymbolString& slave) { ProtocolHandler* ProtocolHandler::create(const ebus_protocol_config_t config, ProtocolListener* listener) { - Device *device = Device::create(config.device, config.extraLatency, !config.noDeviceCheck); - if (device == nullptr) { - return nullptr; + const char* name = config.device; + EnhancedLevel enhanced = el_none; + if (strncmp(name, "en", 2) == 0 && name[2] && name[3] == ':') { + switch (name[2]) { + case 's': + enhanced = el_speed; + break; + case 'h': + enhanced = el_basic; + break; + } + if (enhanced) { + name += 4; + } + } + FileDevice* device = nullptr; + if (strchr(name, '/') == nullptr && strchr(name, ':') != nullptr) { + char* in = strdup(name); + bool udp = false; + char* addrpos = in; + char* portpos = strchr(addrpos, ':'); + // support tcp:: and udp:: + if (portpos == addrpos+3 && (strncmp(addrpos, "tcp", 3) == 0 || (udp=(strncmp(addrpos, "udp", 3) == 0)))) { + addrpos += 4; + portpos = strchr(addrpos, ':'); + } + if (portpos == nullptr) { + free(in); + return nullptr; // invalid protocol or missing port + } + result_t result = RESULT_OK; + uint16_t port = (uint16_t)parseInt(portpos+1, 10, 1, 65535, &result); + if (result != RESULT_OK) { + free(in); + return nullptr; // invalid port + } + *portpos = 0; + char* hostOrIp = strdup(addrpos); + free(in); + device = new NetworkDevice(name, hostOrIp, port, config.extraLatency, udp, enhanced); + } else { + // support enx:/dev/, ens:/dev/, enh:/dev/, and /dev/ + device = new SerialDevice(name, !config.noDeviceCheck, config.extraLatency, enhanced); } return new DirectProtocolHandler(config, device, listener); } From dbbf235c50bfc09cdc0b44c9e170f1b477c5dcf3 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 10 Nov 2023 19:06:30 +0100 Subject: [PATCH 162/345] fix syn generator timing --- src/lib/ebus/protocol.h | 3 +++ src/lib/ebus/protocol_direct.cpp | 6 +++--- src/lib/ebus/protocol_direct.h | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/lib/ebus/protocol.h b/src/lib/ebus/protocol.h index 12476aa12..1936d3ec9 100755 --- a/src/lib/ebus/protocol.h +++ b/src/lib/ebus/protocol.h @@ -35,6 +35,9 @@ namespace ebusd { /** the default time [ms] for retrieving a symbol from an addressed slave. */ #define SLAVE_RECV_TIMEOUT 15 +/** the desired delay time [ms] for sending the AUTO-SYN symbol after last seen symbol. */ +#define SYN_INTERVAL 40 + /** the maximum allowed time [ms] for retrieving the AUTO-SYN symbol (45ms + 2*1,2% + 1 Symbol). */ #define SYN_TIMEOUT 51 diff --git a/src/lib/ebus/protocol_direct.cpp b/src/lib/ebus/protocol_direct.cpp index c66bd1ccc..28fe84ab7 100644 --- a/src/lib/ebus/protocol_direct.cpp +++ b/src/lib/ebus/protocol_direct.cpp @@ -281,9 +281,9 @@ result_t DirectProtocolHandler::handleSymbol() { return setState(bs_noSignal, result); } measureLatency(&sentTime, &recvTime); - if (m_generateSynInterval != SYN_TIMEOUT) { + if (m_generateSynInterval != SYN_INTERVAL) { // received own AUTO-SYN symbol back again: act as AUTO-SYN generator now - m_generateSynInterval = SYN_TIMEOUT; + m_generateSynInterval = SYN_INTERVAL; logNotice(lf_bus, "acting as AUTO-SYN generator"); } m_remainLockCount = 0; @@ -344,7 +344,7 @@ result_t DirectProtocolHandler::handleSymbol() { time_t now; time(&now); if (result != RESULT_OK) { - if ((m_generateSynInterval != SYN_TIMEOUT && difftime(now, m_lastReceive) > 1) + if ((m_generateSynInterval != SYN_INTERVAL && difftime(now, m_lastReceive) > 1) // at least one full second has passed since last received symbol || m_state == bs_noSignal) { return setState(bs_noSignal, result); diff --git a/src/lib/ebus/protocol_direct.h b/src/lib/ebus/protocol_direct.h index 46f0842b3..872d37d39 100755 --- a/src/lib/ebus/protocol_direct.h +++ b/src/lib/ebus/protocol_direct.h @@ -69,7 +69,7 @@ class DirectProtocolHandler : public ProtocolHandler { : ProtocolHandler(config, device, listener), m_lockCount(config.lockCount <= 3 ? 3 : config.lockCount), m_remainLockCount(config.lockCount == 0 ? 1 : 0), - m_generateSynInterval(config.generateSyn ? SYN_TIMEOUT*getMasterNumber(config.ownAddress)+SYMBOL_DURATION : 0), + m_generateSynInterval(config.generateSyn ? 10*getMasterNumber(config.ownAddress)+SYN_TIMEOUT : 0), m_currentRequest(nullptr), m_currentAnswering(false), m_nextSendPos(0), m_state(bs_noSignal), m_escape(0), m_crc(0), m_crcValid(false), m_repeat(false) { m_lastSynReceiveTime.tv_sec = 0; From bfe7b451a55ad2ece7eacf548dc620c459e43b5d Mon Sep 17 00:00:00 2001 From: John Date: Sat, 11 Nov 2023 09:05:06 +0100 Subject: [PATCH 163/345] add result code to protocol listener --- src/ebusd/bushandler.cpp | 2 +- src/ebusd/bushandler.h | 2 +- src/lib/ebus/protocol.cpp | 12 ++++++++++++ src/lib/ebus/protocol.h | 17 +++++++++++++++-- src/lib/ebus/protocol_direct.cpp | 20 ++++++++++++++------ 5 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/ebusd/bushandler.cpp b/src/ebusd/bushandler.cpp index 277bc9f99..ccd1d0fab 100644 --- a/src/ebusd/bushandler.cpp +++ b/src/ebusd/bushandler.cpp @@ -350,7 +350,7 @@ result_t BusHandler::readFromBus(Message* message, const string& inputStr, symbo return ret; } -void BusHandler::notifyProtocolStatus(ProtocolState state) { +void BusHandler::notifyProtocolStatus(ProtocolState state, result_t result) { if (state == ps_empty && m_pollInterval > 0) { // check for poll/scan time_t now; time(&now); diff --git a/src/ebusd/bushandler.h b/src/ebusd/bushandler.h index fe6527b7c..7bc185db7 100755 --- a/src/ebusd/bushandler.h +++ b/src/ebusd/bushandler.h @@ -396,7 +396,7 @@ class BusHandler : public ProtocolListener { void setScanConfigLoaded(symbol_t address, const string& file); // @copydoc - void notifyProtocolStatus(ProtocolState state) override; + void notifyProtocolStatus(ProtocolState state, result_t result) override; // @copydoc result_t notifyProtocolAnswer(const MasterSymbolString& master, SlaveSymbolString* slave) override; diff --git a/src/lib/ebus/protocol.cpp b/src/lib/ebus/protocol.cpp index fa31bd4ca..95157c92e 100644 --- a/src/lib/ebus/protocol.cpp +++ b/src/lib/ebus/protocol.cpp @@ -32,6 +32,18 @@ using std::hex; using std::setfill; using std::setw; +const char* getProtocolStateCode(ProtocolState state) { + switch (state) { + case ps_noSignal: return "no signal"; + case ps_idle: return "idle"; + case ps_idleSYN: return "idle, SYN generator"; + case ps_recv: return "receive"; + case ps_send: return "send"; + case ps_empty: return "idle, empty"; + default: return "unknown"; + } +} + bool ActiveBusRequest::notify(result_t result, const SlaveSymbolString& slave) { if (result == RESULT_OK) { string str = slave.getStr(); diff --git a/src/lib/ebus/protocol.h b/src/lib/ebus/protocol.h index 1936d3ec9..183826e19 100755 --- a/src/lib/ebus/protocol.h +++ b/src/lib/ebus/protocol.h @@ -89,9 +89,18 @@ typedef struct ebus_protocol_config { enum ProtocolState { ps_noSignal, //!< no signal on the bus ps_idle, //!< idle (after @a SYN symbol) + ps_idleSYN, //!< idle (after sent SYN symbol in acting as SYN generator) + ps_recv, //!< receiving + ps_send, //!< sending ps_empty, //!< idle, no more lock remaining, and no other request queued }; +/** + * Return the string corresponding to the @a ProtocolState. + * @param state the @a ProtocolState. + * @return the string corresponding to the @a ProtocolState. + */ +const char* getProtocolStateCode(ProtocolState state); class ProtocolHandler; @@ -208,8 +217,9 @@ class ProtocolListener { /** * Called to notify a status update from the protocol. * @param state the current protocol state. + * @param result the error code reason for the state change, or @a RESULT_OK. */ - virtual void notifyProtocolStatus(ProtocolState state) = 0; // abstract + virtual void notifyProtocolStatus(ProtocolState state, result_t result) = 0; // abstract /** * Called to notify a new valid seen address on the bus. @@ -253,7 +263,7 @@ class ProtocolHandler : public WaitThread, DeviceListener { ProtocolHandler(const ebus_protocol_config_t config, Device* device, ProtocolListener* listener) : WaitThread(), m_config(config), m_device(device), m_listener(listener), - m_reconnect(false), + m_listenerState(ps_noSignal), m_reconnect(false), m_ownMasterAddress(config.ownAddress), m_ownSlaveAddress(getSlaveAddress(config.ownAddress)), m_addressConflict(false), m_masterCount(config.readOnly ? 0 : 1), @@ -519,6 +529,9 @@ class ProtocolHandler : public WaitThread, DeviceListener { /** the @a ProtocolListener. */ ProtocolListener *m_listener; + /** the last state the listener was informed with. */ + ProtocolState m_listenerState; + /** set to @p true when the device shall be reconnected. */ bool m_reconnect; diff --git a/src/lib/ebus/protocol_direct.cpp b/src/lib/ebus/protocol_direct.cpp index 28fe84ab7..6b789e230 100644 --- a/src/lib/ebus/protocol_direct.cpp +++ b/src/lib/ebus/protocol_direct.cpp @@ -135,7 +135,7 @@ result_t DirectProtocolHandler::handleSymbol() { if (!m_device->isArbitrating() && m_currentRequest == nullptr && m_remainLockCount == 0) { BusRequest* startRequest = m_nextRequests.peek(); if (startRequest == nullptr) { - m_listener->notifyProtocolStatus(ps_empty); + m_listener->notifyProtocolStatus(ps_empty, RESULT_OK); startRequest = m_nextRequests.peek(); } if (startRequest != nullptr) { // initiate arbitration @@ -677,9 +677,6 @@ result_t DirectProtocolHandler::setState(BusState state, result_t result, bool f } if (state == bs_noSignal) { // notify all requests - if (m_state != bs_noSignal) { - m_listener->notifyProtocolStatus(ps_idle); - } m_response.clear(); // notify with empty response while ((m_currentRequest = m_nextRequests.pop()) != nullptr) { bool restart = m_currentRequest->notify(RESULT_ERR_NO_SIGNAL, m_response); @@ -692,12 +689,13 @@ result_t DirectProtocolHandler::setState(BusState state, result_t result, bool f m_finishedRequests.push(m_currentRequest); } } - } else if (m_state == bs_noSignal) { - m_listener->notifyProtocolStatus(ps_noSignal); } m_escape = 0; if (state == m_state) { + if (m_listener && result != RESULT_OK) { + m_listener->notifyProtocolStatus(m_listenerState, result); + } return result; } if ((result < RESULT_OK && !(result == RESULT_ERR_TIMEOUT && state == bs_skip && m_state == bs_ready)) @@ -718,6 +716,16 @@ result_t DirectProtocolHandler::setState(BusState state, result_t result, bool f logNotice(lf_bus, "signal acquired"); } } + if (m_listener) { + ProtocolState pstate = protocolStateByBusState[state]; + if (pstate == ps_idle && m_generateSynInterval == SYN_INTERVAL) { + pstate = ps_idleSYN; + } + if (result != RESULT_OK || pstate != m_listenerState) { + m_listener->notifyProtocolStatus(pstate, result); + m_listenerState = pstate; + } + } m_state = state; if (state == bs_ready || state == bs_skip) { From dd3721e7f40fd84c90d7bb898d8643e998193e41 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 11 Nov 2023 09:30:01 +0100 Subject: [PATCH 164/345] rework info transfer, missed commit --- src/lib/ebus/device.cpp | 106 ++++++++++++++++++++++--------- src/lib/ebus/device.h | 25 +++++--- src/lib/ebus/protocol_direct.cpp | 22 +++++++ 3 files changed, 116 insertions(+), 37 deletions(-) diff --git a/src/lib/ebus/device.cpp b/src/lib/ebus/device.cpp index da3ee80cd..ced40f588 100755 --- a/src/lib/ebus/device.cpp +++ b/src/lib/ebus/device.cpp @@ -110,9 +110,10 @@ FileDevice::FileDevice(const char* name, bool checkDevice, unsigned int latency, m_checkDevice(checkDevice), m_latency(HOST_LATENCY_MS+(enhancedLevel?ENHANCED_LATENCY_MS:0)+latency), m_enhancedLevel(enhancedLevel), m_fd(-1), m_resetRequested(false), - m_arbitrationMaster(SYN), - m_arbitrationCheck(0), m_bufSize(((MAX_LEN+1+3)/4)*4), m_bufLen(0), m_bufPos(0), - m_extraFatures(0), m_infoId(0xff), m_infoReqTime(0), m_infoLen(0), m_infoPos(0) { + m_arbitrationMaster(SYN), m_arbitrationCheck(0), + m_bufSize(((MAX_LEN+1+3)/4)*4), m_bufLen(0), m_bufPos(0), + m_sendBuf(nullptr), m_sendBufSize(0), + m_extraFatures(0), m_infoReqTime(0), m_infoLen(0), m_infoPos(0) { m_buffer = reinterpret_cast(malloc(m_bufSize)); if (!m_buffer) { m_bufSize = 0; @@ -125,6 +126,10 @@ FileDevice::~FileDevice() { free(m_buffer); m_buffer = nullptr; } + if (m_sendBuf) { + free(m_sendBuf); + m_sendBuf = nullptr; + } } void FileDevice::formatInfo(ostringstream* ostream, bool verbose, bool prefix) { @@ -211,18 +216,18 @@ result_t FileDevice::requestEnhancedInfo(symbol_t infoId) { return RESULT_ERR_INVALID_ARG; } for (unsigned int i = 0; i < 4; i++) { - if (m_infoId == 0xff) { + if (m_infoLen == 0) { break; } usleep(40000 + i*40000); } - if (m_infoId != 0xff) { + if (m_infoLen > 0) { if (m_infoReqTime > 0 && time(NULL) > m_infoReqTime+5) { // request timed out if (m_listener != nullptr) { m_listener->notifyDeviceStatus(false, "info request timed out"); } - m_infoId = 0xff; + m_infoLen = 0; m_infoReqTime = 0; } else { return RESULT_ERR_DUPLICATE; @@ -232,19 +237,67 @@ result_t FileDevice::requestEnhancedInfo(symbol_t infoId) { // just waited for completion return RESULT_OK; } - return sendEnhancedInfoRequest(infoId); + return sendSequence(sid_info, &infoId, 1); } -result_t FileDevice::sendEnhancedInfoRequest(symbol_t infoId) { - symbol_t buf[2] = makeEnhancedSequence(ENH_REQ_INFO, infoId); - DEBUG_RAW_TRAFFIC("enhanced > %2.2x %2.2x", buf[0], buf[1]); - if (::write(m_fd, buf, 2) != 2) { +result_t FileDevice::sendSequence(SequenceId id, const uint8_t* data, size_t len) { + if (!isValid()) { return RESULT_ERR_DEVICE; } - m_infoPos = 0; - m_infoId = infoId; - time(&m_infoReqTime); - return RESULT_OK; + if (m_enhancedLevel >= el_basic && id==sid_info && (m_extraFatures&0x01) != 0) { + // request is supported + } else { + return RESULT_ERR_NOTFOUND; // not supported + } + size_t pos = (1+len)*2; + if (pos > m_sendBufSize) { + m_sendBuf = static_cast(realloc(m_sendBuf, pos)); + if (!m_sendBuf) { + m_sendBufSize = 0; + return RESULT_ERR_DEVICE; + } + m_sendBufSize = pos; + } + pos = 0; + uint8_t cmd = id==sid_info ? ENH_REQ_INFO : 0; + if (cmd == 0) { + return RESULT_ERR_NOTFOUND; // not supported + } + if (cmd & 0x8) { + // sequence-encoded + m_sendBuf[pos++] = makeEnhancedByte1(cmd, len); + m_sendBuf[pos++] = makeEnhancedByte2(cmd, len); + cmd = static_cast(cmd & ~0x8); + } else { + // direct-encoded + uint8_t val = data ? *data : 0; + m_sendBuf[pos++] = makeEnhancedByte1(cmd, val); + m_sendBuf[pos++] = makeEnhancedByte2(cmd, val); + if (id==sid_info) { + m_infoBuf[0] = val; + m_infoLen = 0; + } + if (len > 0) { + len--; + data++; + } + } + while (len > 0) { + m_sendBuf[pos++] = makeEnhancedByte1(cmd, *data); + m_sendBuf[pos++] = makeEnhancedByte2(cmd, *data); + data++; + len--; + } + // DEBUG_RAW_TRAFFIC("enhanced > %2.2x %2.2x", buf[0], buf[1]); + if (::write(m_fd, m_sendBuf, pos) == pos) { + if (id==sid_info) { + m_infoLen = 1; + m_infoPos = 1; + time(&m_infoReqTime); + } + return RESULT_OK; + } + return RESULT_ERR_DEVICE; } string FileDevice::getEnhancedInfos() { @@ -266,8 +319,7 @@ string FileDevice::getEnhancedInfos() { if (res != RESULT_OK) { fails += ", cannot request config"; requestEnhancedInfo(0xff); // wait for completion - m_infoPos = 0; - m_infoId = 0xff; + m_infoLen = 0; // cancel anyway } } res = requestEnhancedInfo(6); @@ -289,8 +341,7 @@ string FileDevice::getEnhancedInfos() { res = requestEnhancedInfo(0xff); // wait for completion if (res != RESULT_OK) { m_enhInfoBusVoltage = "bus voltage unknown"; - m_infoPos = 0; - m_infoId = 0xff; + m_infoLen = 0; // cancel anyway } return "firmware " + m_enhInfoVersion + ", " + m_enhInfoTemperature + ", " + m_enhInfoSupplyVoltage + ", " + m_enhInfoBusVoltage; @@ -669,12 +720,12 @@ bool FileDevice::handleEnhancedBufferedData(symbol_t* value, ArbitrationState* a m_enhInfoTemperature = ""; m_enhInfoSupplyVoltage = ""; m_enhInfoBusVoltage = ""; - m_infoId = 0xff; + m_infoLen = 0; m_extraFatures = data; if (m_resetRequested) { m_resetRequested = false; if (m_extraFatures&0x01) { - sendEnhancedInfoRequest(0); // request version, ignore result + sendSequence(sid_info); // request version, ignore result } } else { close(); // on self-reset of device close and reopen it to have a clean startup @@ -685,19 +736,16 @@ bool FileDevice::handleEnhancedBufferedData(symbol_t* value, ArbitrationState* a } break; case ENH_RES_INFO: - if (m_infoLen == 0) { - m_infoLen = data; - m_infoPos = 0; + if (m_infoLen == 1) { + m_infoLen = data+1; } else if (m_infoPos < m_infoLen && m_infoPos < sizeof(m_infoBuf)) { m_infoBuf[m_infoPos++] = data; if (m_infoPos >= m_infoLen) { notifyInfoRetrieved(); m_infoLen = 0; - m_infoId = 0xff; } } else { m_infoLen = 0; // reset on invalid response - m_infoId = 0xff; } break; case ENH_RES_ERROR_EBUS: @@ -736,9 +784,9 @@ bool FileDevice::handleEnhancedBufferedData(symbol_t* value, ArbitrationState* a } void FileDevice::notifyInfoRetrieved() { - symbol_t* data = m_infoBuf; - size_t len = m_infoLen; - symbol_t id = m_infoId; + symbol_t id = m_infoBuf[0]; + symbol_t* data = m_infoBuf+1; + size_t len = m_infoLen-1; unsigned int val; ostringstream stream; switch ((len << 8) | id) { diff --git a/src/lib/ebus/device.h b/src/lib/ebus/device.h index 60bfa53b9..41fc7e64b 100755 --- a/src/lib/ebus/device.h +++ b/src/lib/ebus/device.h @@ -65,6 +65,11 @@ enum ArbitrationState { as_won, //!< arbitration won }; +/** the sequence IDs as handled by @a FileDevice. */ +enum SequenceId { + sid_info, //!< send/receive info +}; + /** * Interface for listening to data received on/sent to a device. */ @@ -296,11 +301,13 @@ class FileDevice : public Device { result_t requestEnhancedInfo(symbol_t infoId); /** - * Send a request for extra infos to enhanced device. - * @param infoId the ID of the info to request. - * @return @a RESULT_OK on success, or an error code otherwise. + * Write a sequence of bytes to the device. + * @param id the ID of the sequence. + * @param data the buffer with the data to send. + * @param len the length of the buffer. + * @return the @a result_t code. */ - result_t sendEnhancedInfoRequest(symbol_t infoId); + virtual result_t sendSequence(SequenceId id, const uint8_t* data = nullptr, size_t len = 0); /** * Get the enhanced device version. @@ -399,13 +406,15 @@ class FileDevice : public Device { /** the read buffer read position. */ size_t m_bufPos; + /** the send buffer. */ + uint8_t* m_sendBuf; + + /** the send buffer size. */ + size_t m_sendBufSize; /** the extra features supported by the device. */ symbol_t m_extraFatures; - /** the ID of the last requested info. */ - symbol_t m_infoId; - /** the time of the last info request. */ time_t m_infoReqTime; @@ -416,7 +425,7 @@ class FileDevice : public Device { size_t m_infoPos; /** the info buffer. */ - symbol_t m_infoBuf[16]; + symbol_t m_infoBuf[16+1]; /** a string describing the enhanced device version. */ string m_enhInfoVersion; diff --git a/src/lib/ebus/protocol_direct.cpp b/src/lib/ebus/protocol_direct.cpp index 6b789e230..80eb1e302 100644 --- a/src/lib/ebus/protocol_direct.cpp +++ b/src/lib/ebus/protocol_direct.cpp @@ -52,6 +52,28 @@ const char* getStateCode(BusState state) { } } +/** + * The @a ProtocolState value by internal @a BusState value index. + */ +static const ProtocolState protocolStateByBusState[] = { + ps_noSignal, // bs_noSignal + ps_idle, // bs_skip + ps_idle, // bs_ready + ps_recv, // bs_recvCmd + ps_recv, // bs_recvCmdCrc + ps_recv, // bs_recvCmdAck + ps_recv, // bs_recvRes + ps_recv, // bs_recvResCrc + ps_recv, // bs_recvResAck + ps_send, // bs_sendCmd + ps_send, // bs_sendCmdCrc + ps_send, // bs_sendResAck + ps_send, // bs_sendCmdAck + ps_send, // bs_sendRes + ps_send, // bs_sendResCrc + ps_send, // bs_sendSyn +}; + void DirectProtocolHandler::run() { unsigned int symCount = 0; From a8bd30a68f59d8047b78833d5a80623fe0e8d621 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 11 Nov 2023 09:33:36 +0100 Subject: [PATCH 165/345] check pb+sb for validity, simplify request notification on signal loss --- src/lib/ebus/protocol_direct.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/lib/ebus/protocol_direct.cpp b/src/lib/ebus/protocol_direct.cpp index 80eb1e302..98dbfd181 100644 --- a/src/lib/ebus/protocol_direct.cpp +++ b/src/lib/ebus/protocol_direct.cpp @@ -467,6 +467,10 @@ result_t DirectProtocolHandler::handleSymbol() { return setState(bs_recvCmd, RESULT_OK); case bs_recvCmd: + if ((m_command.size() == 0 && !isMaster(recvSymbol)) + || (m_command.size() == 1 && !isValidAddress(recvSymbol))) { + return setState(bs_skip, RESULT_ERR_INVALID_ADDR); + } m_command.push_back(recvSymbol); if (m_command.isComplete()) { // all data received return setState(bs_recvCmdCrc, RESULT_OK); @@ -701,11 +705,8 @@ result_t DirectProtocolHandler::setState(BusState state, result_t result, bool f if (state == bs_noSignal) { // notify all requests m_response.clear(); // notify with empty response while ((m_currentRequest = m_nextRequests.pop()) != nullptr) { - bool restart = m_currentRequest->notify(RESULT_ERR_NO_SIGNAL, m_response); - if (restart) { // should not occur with no signal - m_currentRequest->resetBusLostRetries(); - m_nextRequests.push(m_currentRequest); - } else if (m_currentRequest->deleteOnFinish()) { + m_currentRequest->notify(RESULT_ERR_NO_SIGNAL, m_response); + if (m_currentRequest->deleteOnFinish()) { delete m_currentRequest; } else { m_finishedRequests.push(m_currentRequest); From bceeb5f4f913c67d3bccf197d2a25852cd3359d9 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 11 Nov 2023 09:38:42 +0100 Subject: [PATCH 166/345] add access to data --- ChangeLog.md | 5 +++++ src/lib/ebus/symbol.h | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index 546d728b8..87893857e 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -6,12 +6,17 @@ * fix MacOS build * fix name prefix warnings in Home Assistant MQTT discovery integration * fix impossible usage of multi-field writes in MQTT integration +* fix initial broadcast scan +* fix potentially unusable SSL context +* fix SYN generator timing +* fix missing check for PB/SB validity ## Features * add temperatures in Kelvin and ... to Home Assistant MQTT discovery integration * add options to turn off scanconfig and limit number of retries * remove dependency on argp * add time fields to Home Assistant MQTT discovery integration ++ add templates endpoint to HTTP JSON # 23.2 (2023-07-08) diff --git a/src/lib/ebus/symbol.h b/src/lib/ebus/symbol.h index 535463959..d0680517f 100755 --- a/src/lib/ebus/symbol.h +++ b/src/lib/ebus/symbol.h @@ -294,6 +294,12 @@ class SymbolString { return m_data.size() - lengthOffset - 1; } + /** + * Return a pointer to the data bytes. + * @return a pointer to the data bytes. + */ + const symbol_t* data() const { return m_data.data(); } + /** * Return the data byte at the specified index (within DD). * @param index the index of the data byte (within DD) to return. From 15de49cab1500ca35950bd32d1ea150dcb59ae13 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 11 Nov 2023 10:19:03 +0100 Subject: [PATCH 167/345] fix non-ssl build --- src/ebusd/main.cpp | 6 +++++- src/ebusd/main.h | 3 ++- src/ebusd/main_args.cpp | 10 ++++++---- src/ebusd/mainloop.cpp | 4 ++-- src/lib/utils/httpclient.cpp | 12 ++++++------ src/lib/utils/httpclient.h | 6 +++--- 6 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index 871fd24a6..576c9fb44 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -263,7 +263,11 @@ int main(int argc, char* argv[], char* envp[]) { const string lang = MappedFileReader::normalizeLanguage( s_opt.preferLanguage == nullptr || !s_opt.preferLanguage[0] ? "" : s_opt.preferLanguage); string configLocalPrefix, configUriPrefix; +#ifdef HAVE_SSL HttpClient::initialize(s_opt.caFile, s_opt.caPath); +#else // HAVE_SSL + HttpClient::initialize(nullptr, nullptr); +#endif // HAVE_SSL HttpClient* configHttpClient = nullptr; string configPath = s_opt.configPath; if (configPath.find("://") == string::npos) { @@ -288,7 +292,7 @@ int main(int argc, char* argv[], char* envp[]) { logWrite(lf_main, ll_error, "invalid configPath URL (HTTPS not supported)"); // force logging on exit return EINVAL; } -#endif +#endif // HAVE_SSL logWrite(lf_main, ll_error, "invalid configPath URL"); // force logging on exit return EINVAL; } diff --git a/src/ebusd/main.h b/src/ebusd/main.h index d29e6582d..c8cdca63f 100755 --- a/src/ebusd/main.h +++ b/src/ebusd/main.h @@ -63,9 +63,10 @@ typedef struct options { bool injectMessages; //!< inject remaining arguments as already seen messages bool stopAfterInject; //!< only inject messages once, then stop int injectCount; //!< number of message arguments to inject, or 0 +#ifdef HAVE_SSL const char* caFile; //!< the CA file to use (uses defaults if neither caFile nor caPath are set), or "#" for insecure const char* caPath; //!< the path with CA files to use (uses defaults if neither caFile nor caPath are set) - +#endif // HAVE_SSL symbol_t address; //!< own bus address [31] bool answer; //!< answer to requests from other masters unsigned int acquireTimeout; //!< bus acquisition timeout in ms [10] diff --git a/src/ebusd/main_args.cpp b/src/ebusd/main_args.cpp index 61d39b0b0..b270207e3 100755 --- a/src/ebusd/main_args.cpp +++ b/src/ebusd/main_args.cpp @@ -32,9 +32,9 @@ namespace ebusd { /** the default path of the configuration files. */ #ifdef HAVE_SSL #define CONFIG_PATH "https" CONFIG_PATH_SUFFIX -#else +#else // HAVE_SSL #define CONFIG_PATH "http" CONFIG_PATH_SUFFIX -#endif +#endif // HAVE_SSL /** the default program options. */ static const options_t s_default_opt = { @@ -57,9 +57,10 @@ static const options_t s_default_opt = { .injectMessages = false, .stopAfterInject = false, .injectCount = 0, +#ifdef HAVE_SSL .caFile = nullptr, .caPath = nullptr, - +#endif // HAVE_SSL .address = 0x31, .answer = false, .acquireTimeout = 10, @@ -345,13 +346,14 @@ static int parse_opt(int key, char *arg, const argParseOpt *parseOpt, struct opt opt->injectMessages = true; opt->stopAfterInject = arg && strcmp("stop", arg) == 0; break; +#ifdef HAVE_SSL case O_CAFILE: // --cafile=FILE opt->caFile = arg; break; case O_CAPATH: // --capath=PATH opt->caPath = arg; break; - +#endif // HAVE_SSL // eBUS options: case 'a': // --address=31 { diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index aafb7491b..f8351d7bc 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -298,9 +298,9 @@ void MainLoop::run() { if (!m_httpClient.connect("upd.ebusd.eu", #ifdef HAVE_SSL 443, true, -#else +#else // HAVE_SSL 80, false, -#endif +#endif // HAVE_SSL PACKAGE_NAME "/" PACKAGE_VERSION)) { logError(lf_main, "update check connect error"); nextCheckRun = now + CHECK_INITIAL_DELAY; diff --git a/src/lib/utils/httpclient.cpp b/src/lib/utils/httpclient.cpp index bbdc71c41..8a369c93d 100755 --- a/src/lib/utils/httpclient.cpp +++ b/src/lib/utils/httpclient.cpp @@ -344,11 +344,11 @@ bool HttpClient::parseUrl(const string& url, string* proto, string* host, uint16 if (!isSsl && *proto != "http") { return false; } -#else +#else // HAVE_SSL if (*proto != "http") { return false; } -#endif +#endif // HAVE_SSL size_t pos = url.find('/', hostPos); if (pos == hostPos) { return false; @@ -387,12 +387,12 @@ bool HttpClient::connect(const string& host, const uint16_t port, bool https, co #ifdef HAVE_SSL m_socket = SSLSocket::connect(host, port, https, timeout, s_caFile, s_caPath); m_https = https; -#else +#else // HAVE_SSL if (https) { return false; } m_socket = TCPSocket::connect(host, port, timeout); -#endif +#endif // HAVE_SSL if (!m_socket) { return false; } @@ -410,9 +410,9 @@ bool HttpClient::reconnect() { } #ifdef HAVE_SSL m_socket = SSLSocket::connect(m_host, m_port, m_https, m_timeout, s_caFile, s_caPath); -#else +#else // HAVE_SSL m_socket = TCPSocket::connect(m_host, m_port, m_timeout); -#endif +#endif // HAVE_SSL if (!m_socket) { return false; } diff --git a/src/lib/utils/httpclient.h b/src/lib/utils/httpclient.h index 28a4afb0e..f5645eac5 100755 --- a/src/lib/utils/httpclient.h +++ b/src/lib/utils/httpclient.h @@ -32,7 +32,7 @@ # include # include # include -#endif +#endif // HAVE_SSL /** typedef for referencing @a sockaddr_in within namespace. */ typedef struct sockaddr_in socketaddress; @@ -122,7 +122,7 @@ class HttpClient { HttpClient() : #ifdef HAVE_SSL m_https(false), -#endif +#endif // HAVE_SSL m_socket(nullptr), m_port(0), m_timeout(0), m_bufferSize(0), m_buffer(nullptr) { } @@ -250,7 +250,7 @@ class HttpClient { /** the path with CA files to use. */ static const char* s_caPath; -#endif +#endif // HAVE_SSL /** the currently connected socket. */ SocketClass* m_socket; From a7d6eedf30f7890a4d96c1a8ed3e7de70b8ef8c5 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 11 Nov 2023 10:25:39 +0100 Subject: [PATCH 168/345] lint --- src/lib/ebus/device.cpp | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/lib/ebus/device.cpp b/src/lib/ebus/device.cpp index ced40f588..c0f498bbb 100755 --- a/src/lib/ebus/device.cpp +++ b/src/lib/ebus/device.cpp @@ -244,7 +244,7 @@ result_t FileDevice::sendSequence(SequenceId id, const uint8_t* data, size_t len if (!isValid()) { return RESULT_ERR_DEVICE; } - if (m_enhancedLevel >= el_basic && id==sid_info && (m_extraFatures&0x01) != 0) { + if (m_enhancedLevel >= el_basic && id == sid_info && (m_extraFatures&0x01) != 0) { // request is supported } else { return RESULT_ERR_NOTFOUND; // not supported @@ -259,7 +259,7 @@ result_t FileDevice::sendSequence(SequenceId id, const uint8_t* data, size_t len m_sendBufSize = pos; } pos = 0; - uint8_t cmd = id==sid_info ? ENH_REQ_INFO : 0; + uint8_t cmd = id == sid_info ? ENH_REQ_INFO : 0; if (cmd == 0) { return RESULT_ERR_NOTFOUND; // not supported } @@ -273,7 +273,7 @@ result_t FileDevice::sendSequence(SequenceId id, const uint8_t* data, size_t len uint8_t val = data ? *data : 0; m_sendBuf[pos++] = makeEnhancedByte1(cmd, val); m_sendBuf[pos++] = makeEnhancedByte2(cmd, val); - if (id==sid_info) { + if (id == sid_info) { m_infoBuf[0] = val; m_infoLen = 0; } @@ -289,15 +289,16 @@ result_t FileDevice::sendSequence(SequenceId id, const uint8_t* data, size_t len len--; } // DEBUG_RAW_TRAFFIC("enhanced > %2.2x %2.2x", buf[0], buf[1]); - if (::write(m_fd, m_sendBuf, pos) == pos) { - if (id==sid_info) { - m_infoLen = 1; - m_infoPos = 1; - time(&m_infoReqTime); - } - return RESULT_OK; + ssize_t sent = ::write(m_fd, m_sendBuf, pos); + if (sent < 0 || static_cast(sent) != pos) { + return RESULT_ERR_DEVICE; } - return RESULT_ERR_DEVICE; + if (id == sid_info) { + m_infoLen = 1; + m_infoPos = 1; + time(&m_infoReqTime); + } + return RESULT_OK; } string FileDevice::getEnhancedInfos() { From 82693fb51fa17da35dd558c67fdc0265f086b01f Mon Sep 17 00:00:00 2001 From: John Date: Sun, 26 Nov 2023 15:06:28 +0100 Subject: [PATCH 169/345] rework ebus protocol engine (consume buffered data in a row, avoid starting arbitration when data is buffered, separate transport to/from device) --- ChangeLog.md | 4 +- src/lib/ebus/CMakeLists.txt | 1 + src/lib/ebus/Makefile.am | 1 + src/lib/ebus/device.cpp | 857 +++++++++---------------------- src/lib/ebus/device.h | 400 ++++----------- src/lib/ebus/protocol.cpp | 92 ++-- src/lib/ebus/protocol.h | 4 +- src/lib/ebus/protocol_direct.cpp | 196 ++++--- src/lib/ebus/protocol_direct.h | 27 +- src/lib/ebus/transport.cpp | 356 +++++++++++++ src/lib/ebus/transport.h | 334 ++++++++++++ 11 files changed, 1219 insertions(+), 1053 deletions(-) create mode 100644 src/lib/ebus/transport.cpp create mode 100755 src/lib/ebus/transport.h diff --git a/ChangeLog.md b/ChangeLog.md index 87893857e..b83166345 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -10,13 +10,15 @@ * fix potentially unusable SSL context * fix SYN generator timing * fix missing check for PB/SB validity +* fix non-SSL build ## Features * add temperatures in Kelvin and ... to Home Assistant MQTT discovery integration * add options to turn off scanconfig and limit number of retries * remove dependency on argp * add time fields to Home Assistant MQTT discovery integration -+ add templates endpoint to HTTP JSON +* add templates endpoint to HTTP JSON +* add reworked eBUS protocol engine that is especially useful for slow network issues # 23.2 (2023-07-08) diff --git a/src/lib/ebus/CMakeLists.txt b/src/lib/ebus/CMakeLists.txt index 468260afb..e3f3b0970 100644 --- a/src/lib/ebus/CMakeLists.txt +++ b/src/lib/ebus/CMakeLists.txt @@ -7,6 +7,7 @@ set(libebus_a_SOURCES datatype.h datatype.cpp data.h data.cpp device.h device.cpp + transport.h transport.cpp protocol.h protocol.cpp protocol_direct.h protocol_direct.cpp message.h message.cpp diff --git a/src/lib/ebus/Makefile.am b/src/lib/ebus/Makefile.am index 15b40a5e4..40882f0a5 100644 --- a/src/lib/ebus/Makefile.am +++ b/src/lib/ebus/Makefile.am @@ -11,6 +11,7 @@ libebus_a_SOURCES = \ datatype.h datatype.cpp \ data.h data.cpp \ device.h device.cpp \ + transport.h transport.cpp \ protocol.h protocol.cpp \ protocol_direct.h protocol_direct.cpp \ message.h message.cpp \ diff --git a/src/lib/ebus/device.cpp b/src/lib/ebus/device.cpp index c0f498bbb..5ae58e275 100755 --- a/src/lib/ebus/device.cpp +++ b/src/lib/ebus/device.cpp @@ -21,28 +21,10 @@ #endif #include "lib/ebus/device.h" -#include -#include -#include -#include -#ifdef HAVE_LINUX_SERIAL -# include -#endif -#ifdef HAVE_FREEBSD_UFTDI -# include -#endif -#ifdef HAVE_PPOLL -# include -#endif #include -#include #include -#include -#include #include -#include "lib/ebus/data.h" #include "lib/utils/clock.h" -#include "lib/utils/tcpsocket.h" namespace ebusd { @@ -53,13 +35,6 @@ using std::setw; using std::setprecision; using std::fixed; - -#define MTU 1540 - -#ifndef POLLRDHUP -#define POLLRDHUP 0 -#endif - // ebusd enhanced protocol IDs: #define ENH_REQ_INIT ((uint8_t)0x0) #define ENH_RES_RESETTED ((uint8_t)0x0) @@ -81,70 +56,111 @@ using std::fixed; #define ENH_BYTE_MASK ((uint8_t)0xc0) #define ENH_BYTE1 ((uint8_t)0xc0) #define ENH_BYTE2 ((uint8_t)0x80) -#define makeEnhancedByte1(cmd, data) (uint8_t)(ENH_BYTE1 | ((cmd)<<2) | (((data)&0xc0)>>6)) +#define makeEnhancedByte1(cmd, data) (uint8_t)(ENH_BYTE1 | ((cmd) << 2) | (((data)&0xc0) >> 6)) #define makeEnhancedByte2(cmd, data) (uint8_t)(ENH_BYTE2 | ((data)&0x3f)) #define makeEnhancedSequence(cmd, data) {makeEnhancedByte1(cmd, data), makeEnhancedByte2(cmd, data)} -#ifdef DEBUG_RAW_TRAFFIC - #define DEBUG_RAW_TRAFFIC_HEAD(format, args...) fprintf(stdout, "%lld raw: " format, clockGetMillis(), args) - #define DEBUG_RAW_TRAFFIC_ITEM(args...) fprintf(stdout, args) - #define DEBUG_RAW_TRAFFIC_FINAL() fprintf(stdout, "\n"); fflush(stdout) - #undef DEBUG_RAW_TRAFFIC - #define DEBUG_RAW_TRAFFIC(format, args...) fprintf(stdout, "%lld raw: " format "\n", clockGetMillis(), args); fflush(stdout) -#else - #define DEBUG_RAW_TRAFFIC_HEAD(format, args...) - #undef DEBUG_RAW_TRAFFIC_ITEM - #define DEBUG_RAW_TRAFFIC_FINAL() - #define DEBUG_RAW_TRAFFIC(format, args...) -#endif -Device::Device(const char* name) - : m_name(name), m_listener(nullptr) { +result_t PlainCharDevice::send(symbol_t value) { + result_t result = m_transport->write(&value, 1); + if (result == RESULT_OK && m_listener != nullptr) { + m_listener->notifyDeviceData(&value, 1, false); + } + return result; } +result_t PlainCharDevice::recv(unsigned int timeout, symbol_t* value, ArbitrationState* arbitrationState) { + if (m_arbitrationMaster != SYN) { + *arbitrationState = as_running; + } + uint64_t until = timeout == 0 ? 0 : clockGetMillis() + timeout + m_transport->getLatency(); + const uint8_t* data = nullptr; + size_t len = 0; + result_t result; + do { + result = m_transport->read(timeout, &data, &len); + if (result == RESULT_OK) { + break; + } + if (result != RESULT_ERR_TIMEOUT) { + cancelRunningArbitration(arbitrationState); + return result; + } + if (timeout == 0) { + break; + } + uint64_t now = clockGetMillis(); + if (timeout == 0 || now >= until) { + break; + } + timeout = static_cast(until - now); + } while (true); + if (result == RESULT_OK && len > 0 && data) { + *value = *data; + m_transport->readConsumed(1); + if (m_listener != nullptr) { + m_listener->notifyDeviceData(value, 1, true); + } + if (len > 1) { + result = RESULT_CONTINUE; + } + if (*value != SYN || m_arbitrationMaster == SYN || m_arbitrationCheck) { + if (m_arbitrationMaster != SYN) { + if (m_arbitrationCheck) { + *arbitrationState = *value == m_arbitrationMaster ? as_won : as_lost; + m_arbitrationMaster = SYN; + m_arbitrationCheck = 0; + } else { + *arbitrationState = m_arbitrationMaster == SYN ? as_none : as_start; + } + } + return result; + } + if (len == 1) { + // arbitration executed by ebusd itself + bool wrote = m_transport->write(&m_arbitrationMaster, 1) == RESULT_OK; // send as fast as possible + if (!wrote) { + cancelRunningArbitration(arbitrationState); + return result; + } + if (m_listener != nullptr) { + m_listener->notifyDeviceData(&m_arbitrationMaster, 1, false); + } + m_arbitrationCheck = 1; + *arbitrationState = as_running; + } + } + return result; +} - -FileDevice::FileDevice(const char* name, bool checkDevice, unsigned int latency, - EnhancedLevel enhancedLevel) - : Device(name), - m_checkDevice(checkDevice), - m_latency(HOST_LATENCY_MS+(enhancedLevel?ENHANCED_LATENCY_MS:0)+latency), - m_enhancedLevel(enhancedLevel), m_fd(-1), m_resetRequested(false), - m_arbitrationMaster(SYN), m_arbitrationCheck(0), - m_bufSize(((MAX_LEN+1+3)/4)*4), m_bufLen(0), m_bufPos(0), - m_sendBuf(nullptr), m_sendBufSize(0), - m_extraFatures(0), m_infoReqTime(0), m_infoLen(0), m_infoPos(0) { - m_buffer = reinterpret_cast(malloc(m_bufSize)); - if (!m_buffer) { - m_bufSize = 0; +result_t CharDevice::startArbitration(symbol_t masterAddress) { + if (m_arbitrationCheck) { + if (masterAddress != SYN) { + return RESULT_ERR_ARB_RUNNING; // should not occur + } + return RESULT_OK; } + m_arbitrationMaster = masterAddress; + return RESULT_OK; } -FileDevice::~FileDevice() { - close(); - if (m_buffer) { - free(m_buffer); - m_buffer = nullptr; +bool CharDevice::cancelRunningArbitration(ArbitrationState* arbitrationState) { + if (m_arbitrationMaster == SYN) { + return false; } - if (m_sendBuf) { - free(m_sendBuf); - m_sendBuf = nullptr; + if (arbitrationState) { + *arbitrationState = as_error; } + m_arbitrationMaster = SYN; + m_arbitrationCheck = 0; + return true; } -void FileDevice::formatInfo(ostringstream* ostream, bool verbose, bool prefix) { + +void EnhancedCharDevice::formatInfo(ostringstream* ostream, bool verbose, bool prefix) { + CharDevice::formatInfo(ostream, verbose, prefix); if (prefix) { - *ostream << m_name; - string info = getEnhancedProtoInfo(); - if (!info.empty()) { - *ostream << ", " << info; - } - return; - } - if (!isValid()) { - *ostream << ", invalid"; - } - if (!m_enhancedLevel) { + *ostream << ", enhanced"; return; } bool infoAdded = false; @@ -163,146 +179,57 @@ void FileDevice::formatInfo(ostringstream* ostream, bool verbose, bool prefix) { } } -void FileDevice::formatInfoJson(ostringstream* ostream) { - if (m_enhancedLevel) { - string ver = getEnhancedVersion(); - if (!ver.empty()) { - *ostream << ",\"dv\":\"" << ver << "\""; - } - } -} - -result_t FileDevice::open() { - close(); - return m_bufSize == 0 ? RESULT_ERR_DEVICE : RESULT_OK; -} - -result_t FileDevice::afterOpen() { - if (m_enhancedLevel) { - symbol_t buf[2] = makeEnhancedSequence(ENH_REQ_INIT, 0x01); // extra feature: info - DEBUG_RAW_TRAFFIC("enhanced > %2.2x %2.2x", buf[0], buf[1]); - if (::write(m_fd, buf, 2) != 2) { - return RESULT_ERR_SEND; - } - if (m_listener != nullptr) { - m_listener->notifyDeviceStatus(false, "resetting"); - } - m_resetRequested = true; - } - return RESULT_OK; -} - -void FileDevice::close() { - if (m_fd != -1) { - ::close(m_fd); - m_fd = -1; +void EnhancedCharDevice::formatInfoJson(ostringstream* ostream) const { + string ver = getEnhancedVersion(); + if (!ver.empty()) { + *ostream << ",\"dv\":\"" << ver << "\""; } - m_bufLen = 0; // flush read buffer - m_extraFatures = 0; // reset state } -bool FileDevice::isValid() { - if (m_fd == -1) { - return false; - } - if (m_checkDevice) { - checkDevice(); - } - return m_fd != -1; -} - -result_t FileDevice::requestEnhancedInfo(symbol_t infoId) { - if (!m_enhancedLevel || m_extraFatures == 0) { +result_t EnhancedCharDevice::requestEnhancedInfo(symbol_t infoId, bool wait) { + if (m_extraFatures == 0) { return RESULT_ERR_INVALID_ARG; } - for (unsigned int i = 0; i < 4; i++) { - if (m_infoLen == 0) { - break; + if (wait) { + for (unsigned int i = 0; i < 4; i++) { + if (m_infoLen == 0) { + break; + } + usleep(40000 + i*40000); } - usleep(40000 + i*40000); - } - if (m_infoLen > 0) { - if (m_infoReqTime > 0 && time(NULL) > m_infoReqTime+5) { - // request timed out - if (m_listener != nullptr) { - m_listener->notifyDeviceStatus(false, "info request timed out"); + if (m_infoLen > 0) { + if (m_infoReqTime > 0 && time(NULL) > m_infoReqTime+5) { + // request timed out + if (m_listener != nullptr) { + m_listener->notifyDeviceStatus(false, "info request timed out"); + } + m_infoLen = 0; + m_infoReqTime = 0; + } else { + return RESULT_ERR_DUPLICATE; } - m_infoLen = 0; - m_infoReqTime = 0; - } else { - return RESULT_ERR_DUPLICATE; } } if (infoId == 0xff) { // just waited for completion return RESULT_OK; } - return sendSequence(sid_info, &infoId, 1); -} - -result_t FileDevice::sendSequence(SequenceId id, const uint8_t* data, size_t len) { - if (!isValid()) { - return RESULT_ERR_DEVICE; - } - if (m_enhancedLevel >= el_basic && id == sid_info && (m_extraFatures&0x01) != 0) { - // request is supported - } else { - return RESULT_ERR_NOTFOUND; // not supported - } - size_t pos = (1+len)*2; - if (pos > m_sendBufSize) { - m_sendBuf = static_cast(realloc(m_sendBuf, pos)); - if (!m_sendBuf) { - m_sendBufSize = 0; - return RESULT_ERR_DEVICE; - } - m_sendBufSize = pos; - } - pos = 0; - uint8_t cmd = id == sid_info ? ENH_REQ_INFO : 0; - if (cmd == 0) { - return RESULT_ERR_NOTFOUND; // not supported - } - if (cmd & 0x8) { - // sequence-encoded - m_sendBuf[pos++] = makeEnhancedByte1(cmd, len); - m_sendBuf[pos++] = makeEnhancedByte2(cmd, len); - cmd = static_cast(cmd & ~0x8); - } else { - // direct-encoded - uint8_t val = data ? *data : 0; - m_sendBuf[pos++] = makeEnhancedByte1(cmd, val); - m_sendBuf[pos++] = makeEnhancedByte2(cmd, val); - if (id == sid_info) { - m_infoBuf[0] = val; - m_infoLen = 0; - } - if (len > 0) { - len--; - data++; - } - } - while (len > 0) { - m_sendBuf[pos++] = makeEnhancedByte1(cmd, *data); - m_sendBuf[pos++] = makeEnhancedByte2(cmd, *data); - data++; - len--; - } - // DEBUG_RAW_TRAFFIC("enhanced > %2.2x %2.2x", buf[0], buf[1]); - ssize_t sent = ::write(m_fd, m_sendBuf, pos); - if (sent < 0 || static_cast(sent) != pos) { - return RESULT_ERR_DEVICE; - } - if (id == sid_info) { + uint8_t buf[] = makeEnhancedSequence(ENH_REQ_INFO, infoId); + result_t result = m_transport->write(buf, 2); + if (result == RESULT_OK) { + m_infoBuf[0] = infoId; m_infoLen = 1; m_infoPos = 1; time(&m_infoReqTime); + } else { + m_infoLen = 0; + m_infoPos = 0; } - return RESULT_OK; + return result; } -string FileDevice::getEnhancedInfos() { - if (!m_enhancedLevel || m_extraFatures == 0) { +string EnhancedCharDevice::getEnhancedInfos() { + if (m_extraFatures == 0) { return ""; } result_t res; @@ -348,359 +275,159 @@ string FileDevice::getEnhancedInfos() { + m_enhInfoBusVoltage; } -result_t FileDevice::send(symbol_t value) { - if (!isValid()) { - return RESULT_ERR_DEVICE; - } - if (!write(value)) { - return RESULT_ERR_SEND; - } - if (m_listener != nullptr) { - m_listener->notifyDeviceData(value, false); - } - return RESULT_OK; -} - -/** - * the maximum duration in milliseconds to wait for an enhanced sequence to complete after the first part was already - * retrieved (3ms rounded up to the next 10ms): 2* (Start+8Bit+Stop+Extra @ 9600Bd) - */ -#define ENHANCED_COMPLETE_WAIT_DURATION 10 - - -bool FileDevice::cancelRunningArbitration(ArbitrationState* arbitrationState) { - if (m_enhancedLevel && m_arbitrationMaster != SYN) { - *arbitrationState = as_error; - m_arbitrationMaster = SYN; - m_arbitrationCheck = 0; - write(SYN, true); - return true; - } - if (m_enhancedLevel || m_arbitrationMaster == SYN) { - return false; +result_t EnhancedCharDevice::send(symbol_t value) { + uint8_t buf[] = makeEnhancedSequence(ENH_REQ_SEND, value); + result_t result = m_transport->write(buf, 2); + if (result == RESULT_OK && m_listener != nullptr) { + m_listener->notifyDeviceData(&value, 1, false); } - *arbitrationState = as_error; - m_arbitrationMaster = SYN; - m_arbitrationCheck = 0; - return true; + return result; } -result_t FileDevice::recv(unsigned int timeout, symbol_t* value, ArbitrationState* arbitrationState) { +result_t EnhancedCharDevice::recv(unsigned int timeout, symbol_t* value, ArbitrationState* arbitrationState) { if (m_arbitrationMaster != SYN) { *arbitrationState = as_running; } - if (!isValid()) { - cancelRunningArbitration(arbitrationState); - return RESULT_ERR_DEVICE; - } - bool repeated = false; - timeout += m_latency; - uint64_t until = clockGetMillis() + timeout; + uint64_t until = timeout == 0 ? 0 : clockGetMillis() + timeout + m_transport->getLatency(); + const uint8_t* data = nullptr; + size_t len = 0; + result_t result; do { - bool isAvailable = available(); - if (!isAvailable && timeout > 0) { - int ret; - struct timespec tdiff; - - // set select timeout - tdiff.tv_sec = timeout/1000; - tdiff.tv_nsec = (timeout%1000)*1000000; - -#ifdef HAVE_PPOLL - nfds_t nfds = 1; - struct pollfd fds[nfds]; - - memset(fds, 0, sizeof(fds)); - - fds[0].fd = m_fd; - fds[0].events = POLLIN | POLLERR | POLLHUP | POLLRDHUP; - ret = ppoll(fds, nfds, &tdiff, nullptr); - if (ret >= 0 && fds[0].revents & (POLLERR | POLLHUP | POLLRDHUP)) { - ret = -1; - } -#else -#ifdef HAVE_PSELECT - fd_set readfds, exceptfds; - - FD_ZERO(&readfds); - FD_ZERO(&exceptfds); - FD_SET(m_fd, &readfds); - - ret = pselect(m_fd + 1, &readfds, nullptr, &exceptfds, &tdiff, nullptr); - if (ret >= 1 && FD_ISSET(m_fd, &exceptfds)) { - ret = -1; - } -#else - ret = 1; // ignore timeout if neither ppoll nor pselect are available -#endif -#endif - if (ret == -1) { - DEBUG_RAW_TRAFFIC("poll error %d", errno); - close(); - cancelRunningArbitration(arbitrationState); - return RESULT_ERR_DEVICE; - } - if (ret == 0) { - return RESULT_ERR_TIMEOUT; + result = m_transport->read(timeout, &data, &len); + if (result == RESULT_OK) { + result = handleEnhancedBufferedData(data, len, value, arbitrationState); + if (result >= RESULT_OK) { + break; } } - - // directly read byte from device - bool incomplete = false; - if (read(value, isAvailable, arbitrationState, &incomplete)) { - break; // don't repeat on successful read + if (result != RESULT_ERR_TIMEOUT) { + cancelRunningArbitration(arbitrationState); + return result; } - if (!isAvailable && incomplete && !repeated) { - // for a two-byte transfer another poll is needed - repeated = true; - timeout = m_latency+ENHANCED_COMPLETE_WAIT_DURATION; - continue; + if (timeout == 0) { + break; } uint64_t now = clockGetMillis(); - if (now >= until) { - return RESULT_ERR_TIMEOUT; + if (timeout == 0 || now >= until) { + break; } timeout = static_cast(until - now); } while (true); - if (m_enhancedLevel || *value != SYN || m_arbitrationMaster == SYN || m_arbitrationCheck) { - if (m_listener != nullptr) { - m_listener->notifyDeviceData(*value, true); - } - if (!m_enhancedLevel && m_arbitrationMaster != SYN) { - if (m_arbitrationCheck) { - *arbitrationState = *value == m_arbitrationMaster ? as_won : as_lost; - m_arbitrationMaster = SYN; - m_arbitrationCheck = 0; - } else { - *arbitrationState = m_arbitrationMaster == SYN ? as_none : as_start; - } - } - return RESULT_OK; - } - // non-enhanced: arbitration executed by ebusd itself - bool wrote = write(m_arbitrationMaster); // send as fast as possible - if (m_listener != nullptr) { - m_listener->notifyDeviceData(*value, true); - } - if (!wrote) { - cancelRunningArbitration(arbitrationState); - return RESULT_OK; - } - if (m_listener != nullptr) { - m_listener->notifyDeviceData(m_arbitrationMaster, false); - } - m_arbitrationCheck = 1; - *arbitrationState = as_running; - return RESULT_OK; + return result; } -result_t FileDevice::startArbitration(symbol_t masterAddress) { +result_t EnhancedCharDevice::startArbitration(symbol_t masterAddress) { if (m_arbitrationCheck) { if (masterAddress != SYN) { return RESULT_ERR_ARB_RUNNING; // should not occur } - m_arbitrationCheck = 0; - m_arbitrationMaster = SYN; - if (m_enhancedLevel) { - // cancel running arbitration - if (!write(SYN, true)) { - return RESULT_ERR_SEND; - } + if (!cancelRunningArbitration(nullptr)) { + return RESULT_ERR_SEND; } return RESULT_OK; } m_arbitrationMaster = masterAddress; - if (m_enhancedLevel && masterAddress != SYN) { - if (!write(masterAddress, true)) { + if (masterAddress != SYN) { + uint8_t buf[] = makeEnhancedSequence(ENH_REQ_START, masterAddress); + result_t result = m_transport->write(buf, 2); + if (result != RESULT_OK) { m_arbitrationMaster = SYN; - return RESULT_ERR_SEND; + return result; } m_arbitrationCheck = 1; } return RESULT_OK; } -bool FileDevice::write(symbol_t value, bool startArbitration) { - if (m_enhancedLevel) { - symbol_t buf[2] = makeEnhancedSequence(startArbitration ? ENH_REQ_START : ENH_REQ_SEND, value); - DEBUG_RAW_TRAFFIC("enhanced > %2.2x %2.2x", buf[0], buf[1]); - return ::write(m_fd, buf, 2) == 2; - } - DEBUG_RAW_TRAFFIC("> %2.2x", value); -#ifdef SIMULATE_NON_WRITABILITY - return true; -#else - return ::write(m_fd, &value, 1) == 1; -#endif -} - -bool FileDevice::available() { - if (m_bufLen <= 0) { +bool EnhancedCharDevice::cancelRunningArbitration(ArbitrationState* arbitrationState) { + if (!CharDevice::cancelRunningArbitration(arbitrationState)) { return false; } - if (!m_enhancedLevel) { - return true; - } - // peek into the received enhanced proto bytes to determine received bus symbol availability - for (size_t pos = 0; pos < m_bufLen; pos++) { - symbol_t ch = m_buffer[(pos+m_bufPos)%m_bufSize]; - if (!(ch&ENH_BYTE_FLAG)) { - DEBUG_RAW_TRAFFIC("avail direct @%d+%d %2.2x", m_bufPos, pos, ch); - return true; - } - if ((ch&ENH_BYTE_MASK) == ENH_BYTE1) { - if (pos+1 >= m_bufLen) { - return false; - } - symbol_t cmd = (ch >> 2)&0xf; - // peek into next byte to check if enhanced sequence is ok - ch = m_buffer[(pos+m_bufPos+1)%m_bufSize]; - if (!(ch&ENH_BYTE_FLAG) || (ch&ENH_BYTE_MASK) != ENH_BYTE2) { - DEBUG_RAW_TRAFFIC("avail enhanced following bad @%d+%d %2.2x %2.2x", m_bufPos, pos, - m_buffer[(pos+m_bufPos)%m_bufSize], ch); - if (m_listener != nullptr) { - m_listener->notifyDeviceStatus(true, "unexpected available enhanced following byte 1"); - } - // drop first byte of invalid sequence - m_bufPos = (m_bufPos + 1) % m_bufSize; - m_bufLen--; - pos--; // check same pos again - continue; - } - if (cmd == ENH_RES_RECEIVED || cmd == ENH_RES_STARTED || cmd == ENH_RES_FAILED) { - // found a sequence that yields in available bus byte - DEBUG_RAW_TRAFFIC("avail enhanced @%d+%d %2.2x %2.2x", m_bufPos, pos, m_buffer[(pos+m_bufPos)%m_bufSize], ch); - return true; - } - DEBUG_RAW_TRAFFIC("avail enhanced skip cmd %d @%d+%d %2.2x", cmd, m_bufPos, pos, ch); - pos++; // skip enhanced sequence of 2 bytes - continue; - } - DEBUG_RAW_TRAFFIC("avail enhanced bad @%d+%d %2.2x", m_bufPos, pos, ch); - if (m_listener != nullptr) { - m_listener->notifyDeviceStatus(true, "unexpected available enhanced byte 2"); - } - // skip byte from erroneous protocol - m_bufPos = (m_bufPos+1)%m_bufSize; - m_bufLen--; - pos--; // check byte 2 again from scratch and allow as byte 1 - } - return false; + symbol_t buf[2] = makeEnhancedSequence(ENH_REQ_START, SYN); + return m_transport->write(buf, 2) == RESULT_OK; } -bool FileDevice::read(symbol_t* value, bool isAvailable, ArbitrationState* arbitrationState, bool* incomplete) { - if (!isAvailable) { - if (m_bufLen > 0 && m_bufPos != 0) { - if (m_bufLen > m_bufSize / 2) { - // more than half of input buffer consumed is taken as signal that ebusd is too slow - m_bufLen = 0; - if (m_listener != nullptr) { - m_listener->notifyDeviceStatus(true, "buffer overflow"); - } - } else { - size_t tail; - if (m_bufPos+m_bufLen > m_bufSize) { - // move wrapped tail away - tail = (m_bufPos+m_bufLen) % m_bufSize; - size_t head = m_bufLen-tail; - memmove(m_buffer+head, m_buffer, tail); - DEBUG_RAW_TRAFFIC("move tail %d @0 to @%d", tail, head); - } else { - tail = 0; - } - // move head to first position - memmove(m_buffer, m_buffer + m_bufPos, m_bufLen - tail); - DEBUG_RAW_TRAFFIC("move head %d @%d to 0", m_bufLen - tail, m_bufPos); - } - } - m_bufPos = 0; - // fill up the buffer - ssize_t size = ::read(m_fd, m_buffer + m_bufLen, m_bufSize - m_bufLen); - if (size <= 0) { - return false; - } -#ifdef DEBUG_RAW_TRAFFIC_ITEM - DEBUG_RAW_TRAFFIC_HEAD("%d+%d <", m_bufLen, size); - for (int pos=0; pos < size; pos++) { - DEBUG_RAW_TRAFFIC_ITEM(" %2.2x", m_buffer[(m_bufLen+pos)%m_bufSize]); - } - DEBUG_RAW_TRAFFIC_FINAL(); -#endif - m_bufLen += size; - } - if (m_enhancedLevel) { - if (handleEnhancedBufferedData(value, arbitrationState)) { - return true; - } - } - if (!available()) { - if (incomplete) { - *incomplete = m_enhancedLevel && m_bufLen > 0; +result_t EnhancedCharDevice::notifyTransportStatus(bool opened) { + result_t result = CharDevice::notifyTransportStatus(opened); // always OK + if (opened) { + symbol_t buf[2] = makeEnhancedSequence(ENH_REQ_INIT, 0x01); // extra feature: info + result = m_transport->write(buf, 2); + if (result != RESULT_OK) { + return result; } - return false; - } - if (!m_enhancedLevel) { - *value = m_buffer[m_bufPos]; - m_bufPos = (m_bufPos+1)%m_bufSize; - m_bufLen--; - return true; + m_resetRequested = true; + } else { + // reset state + m_extraFatures = 0; + m_infoLen = 0; + m_arbitrationMaster = SYN; + m_arbitrationCheck = 0; } - return handleEnhancedBufferedData(value, arbitrationState); + return result; } -bool FileDevice::handleEnhancedBufferedData(symbol_t* value, ArbitrationState* arbitrationState) { - while (m_bufLen > 0) { - symbol_t ch = m_buffer[m_bufPos]; + +result_t EnhancedCharDevice::handleEnhancedBufferedData(const uint8_t* data, size_t len, +symbol_t* value, ArbitrationState* arbitrationState) { + bool valueSet = false; + bool sent = false; + bool more = false; + size_t pos; + for (pos = 0; pos < len; pos++) { + symbol_t ch = data[pos]; if (!(ch&ENH_BYTE_FLAG)) { + if (valueSet) { + more = true; + break; + } *value = ch; - m_bufPos = (m_bufPos+1)%m_bufSize; - m_bufLen--; - return true; + valueSet = true; + continue; } uint8_t kind = ch&ENH_BYTE_MASK; - if (kind == ENH_BYTE1 && m_bufLen < 2) { - return false; // transfer not complete yet + if (kind == ENH_BYTE1 && len < pos + 2) { + break; // transfer not complete yet } - m_bufPos = (m_bufPos+1)%m_bufSize; - m_bufLen--; if (kind == ENH_BYTE2) { if (m_listener != nullptr) { m_listener->notifyDeviceStatus(true, "unexpected enhanced byte 2"); } - return false; + continue; } // kind is ENH_BYTE1 - symbol_t ch2 = m_buffer[m_bufPos]; - m_bufPos = (m_bufPos + 1) % m_bufSize; - m_bufLen--; + pos++; + symbol_t ch2 = data[pos]; if ((ch2 & ENH_BYTE_MASK) != ENH_BYTE2) { if (m_listener != nullptr) { m_listener->notifyDeviceStatus(true, "missing enhanced byte 2"); } - return false; + continue; } symbol_t data = (symbol_t)(((ch&0x03) << 6) | (ch2&0x3f)); symbol_t cmd = (ch >> 2)&0xf; switch (cmd) { case ENH_RES_STARTED: - *arbitrationState = as_won; - if (m_listener != NULL) { - m_listener->notifyDeviceData(data, false); - } - m_arbitrationMaster = SYN; - m_arbitrationCheck = 0; - *value = data; - return true; case ENH_RES_FAILED: - *arbitrationState = as_lost; - if (m_listener != NULL) { - m_listener->notifyDeviceData(m_arbitrationMaster, false); + if (valueSet) { + more = true; + pos--; // keep ENH_BYTE1 for later run + len = 0; // abort outer loop + break; } + sent = cmd == ENH_RES_STARTED; + *arbitrationState = sent ? as_won : as_lost; m_arbitrationMaster = SYN; m_arbitrationCheck = 0; *value = data; - return true; + valueSet = true; + break; case ENH_RES_RECEIVED: + if (valueSet) { + more = true; + pos--; // keep ENH_BYTE1 for later run + len = 0; // abort outer loop + break; + } *value = data; if (data == SYN && *arbitrationState == as_running && m_arbitrationCheck) { if (m_arbitrationCheck < 3) { // wait for three SYN symbols before switching to timeout @@ -711,7 +438,8 @@ bool FileDevice::handleEnhancedBufferedData(symbol_t* value, ArbitrationState* a m_arbitrationCheck = 0; } } - return true; + valueSet = true; + break; case ENH_RES_RESETTED: if (*arbitrationState != as_none) { *arbitrationState = as_error; @@ -723,23 +451,24 @@ bool FileDevice::handleEnhancedBufferedData(symbol_t* value, ArbitrationState* a m_enhInfoBusVoltage = ""; m_infoLen = 0; m_extraFatures = data; + if (m_listener != nullptr) { + m_listener->notifyDeviceStatus(false, (m_extraFatures&0x01) ? "reset, supports info" : "reset"); + } if (m_resetRequested) { m_resetRequested = false; if (m_extraFatures&0x01) { - sendSequence(sid_info); // request version, ignore result + requestEnhancedInfo(0, false); // request version, ignore result } - } else { - close(); // on self-reset of device close and reopen it to have a clean startup - cancelRunningArbitration(arbitrationState); - } - if (m_listener != nullptr) { - m_listener->notifyDeviceStatus(false, (m_extraFatures&0x01) ? "reset, supports info" : "reset"); + valueSet = false; + break; } + m_transport->close(); // on self-reset of device close and reopen it to have a clean startup + cancelRunningArbitration(arbitrationState); break; case ENH_RES_INFO: if (m_infoLen == 1) { m_infoLen = data+1; - } else if (m_infoPos < m_infoLen && m_infoPos < sizeof(m_infoBuf)) { + } else if (m_infoLen && m_infoPos < m_infoLen && m_infoPos < sizeof(m_infoBuf)) { m_infoBuf[m_infoPos++] = data; if (m_infoPos >= m_infoLen) { notifyInfoRetrieved(); @@ -778,13 +507,21 @@ bool FileDevice::handleEnhancedBufferedData(symbol_t* value, ArbitrationState* a string str = stream.str(); m_listener->notifyDeviceStatus(true, str.c_str()); } - return false; + len = 0; // abort outer loop + break; } + if (len == 0) { + break; // abort received + } + } + m_transport->readConsumed(pos); + if (valueSet && m_listener != nullptr) { + m_listener->notifyDeviceData(value, 1, !sent); } - return false; + return more ? RESULT_CONTINUE : valueSet ? RESULT_OK : RESULT_ERR_TIMEOUT; } -void FileDevice::notifyInfoRetrieved() { +void EnhancedCharDevice::notifyInfoRetrieved() { symbol_t id = m_infoBuf[0]; symbol_t* data = m_infoBuf+1; size_t len = m_infoLen-1; @@ -883,136 +620,4 @@ void FileDevice::notifyInfoRetrieved() { } } - -result_t SerialDevice::open() { - result_t result = FileDevice::open(); - if (result != RESULT_OK) { - return result; - } - struct termios newSettings; - - // open file descriptor - m_fd = ::open(m_name, O_RDWR | O_NOCTTY | O_NDELAY); - - if (m_fd < 0) { - return RESULT_ERR_NOTFOUND; - } - if (isatty(m_fd) == 0) { - close(); - return RESULT_ERR_NOTFOUND; - } - - if (flock(m_fd, LOCK_EX|LOCK_NB) != 0) { - close(); - return RESULT_ERR_DEVICE; - } - -#ifdef HAVE_LINUX_SERIAL - struct serial_struct serial; - if (ioctl(m_fd, TIOCGSERIAL, &serial) == 0) { - serial.flags |= ASYNC_LOW_LATENCY; - ioctl(m_fd, TIOCSSERIAL, &serial); - } -#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)); - -#ifdef HAVE_CFSETSPEED - cfsetspeed(&newSettings, m_enhancedLevel ? (m_enhancedLevel >= el_speed ? B115200 : B9600) : B2400); -#else - cfsetispeed(&newSettings, m_enhancedLevel ? (m_enhancedLevel >= el_speed ? B115200 : B9600) : B2400); - cfsetospeed(&newSettings, m_enhancedLevel ? (m_enhancedLevel >= el_speed ? B115200 : B9600) : B2400); -#endif - 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; - - // non-canonical mode: read() blocks until at least one byte is available - newSettings.c_cc[VMIN] = 1; - newSettings.c_cc[VTIME] = 0; - - // empty device buffer - tcflush(m_fd, TCIFLUSH); - - // activate new settings of serial device - if (tcsetattr(m_fd, TCSANOW, &newSettings)) { - close(); - return RESULT_ERR_DEVICE; - } - - // set serial device into blocking mode - fcntl(m_fd, F_SETFL, fcntl(m_fd, F_GETFL) & ~O_NONBLOCK); - - return afterOpen(); -} - -void SerialDevice::close() { - if (m_fd != -1) { - // empty device buffer - tcflush(m_fd, TCIOFLUSH); - - // restore previous settings of the device - tcsetattr(m_fd, TCSANOW, &m_oldSettings); - } - FileDevice::close(); -} - -void SerialDevice::checkDevice() { - int cnt; - if (ioctl(m_fd, FIONREAD, &cnt) == -1) { - close(); - } -} - -result_t NetworkDevice::open() { - result_t result = FileDevice::open(); - if (result != RESULT_OK) { - return result; - } - m_fd = socketConnect(m_hostOrIp, m_port, m_udp, nullptr, 5, 2); // wait up to 5 seconds for established connection - if (m_fd < 0) { - return RESULT_ERR_GENERIC_IO; - } - if (!m_udp) { - usleep(25000); // wait 25ms for potential initial garbage - } - int cnt; - symbol_t buf[MTU]; - int ioerr; - while ((ioerr=ioctl(m_fd, FIONREAD, &cnt)) >= 0 && cnt > 1) { - // skip buffered input - ssize_t read = ::read(m_fd, &buf, MTU); - if (read <= 0) { - break; - } - } - if (ioerr < 0) { - close(); - return RESULT_ERR_GENERIC_IO; - } - return afterOpen(); -} - -void NetworkDevice::checkDevice() { - int cnt; - if (ioctl(m_fd, FIONREAD, &cnt) < 0) { - close(); - } -} - } // namespace ebusd diff --git a/src/lib/ebus/device.h b/src/lib/ebus/device.h index 41fc7e64b..61db9450a 100755 --- a/src/lib/ebus/device.h +++ b/src/lib/ebus/device.h @@ -19,15 +19,9 @@ #ifndef LIB_EBUS_DEVICE_H_ #define LIB_EBUS_DEVICE_H_ -#include -#include -#include -#include -#include -#include -#include #include #include "lib/ebus/result.h" +#include "lib/ebus/transport.h" #include "lib/ebus/symbol.h" namespace ebusd { @@ -35,25 +29,12 @@ namespace ebusd { /** @file lib/ebus/device.h * Classes providing access to the eBUS. * - * A @a Device is either a @a SerialDevice directly connected to a local tty - * port or a remote @a NetworkDevice handled via a TCP socket. It allows to - * send and receive bytes to/from the eBUS while optionally dumping the data - * to a file and/or forwarding it to a logging function. + * A @a Device allows to send and receive data to/from a local or remote eBUS + * device while optionally dumping the data to a file and/or forwarding it to + * a logging function. + * The data transport itself is handled by a @a Transport instance. */ -/** the transfer latency of the network device [ms]. */ -#define NETWORK_LATENCY_MS 30 - -/** the extra transfer latency to take into account for enhanced protocol. */ -#define ENHANCED_LATENCY_MS 10 - -/** the latency of the host [ms]. */ -#if defined(__CYGWIN__) || defined(_WIN32) -#define HOST_LATENCY_MS 20 -#else -#define HOST_LATENCY_MS 10 -#endif - /** the arbitration state handled by @a Device. */ enum ArbitrationState { as_none, //!< no arbitration in process @@ -65,11 +46,6 @@ enum ArbitrationState { as_won, //!< arbitration won }; -/** the sequence IDs as handled by @a FileDevice. */ -enum SequenceId { - sid_info, //!< send/receive info -}; - /** * Interface for listening to data received on/sent to a device. */ @@ -81,11 +57,12 @@ class DeviceListener { virtual ~DeviceListener() {} /** - * Listener method that is called when a symbol was received from/sent to eBUS. - * @param symbol the received/sent symbol. + * Listener method that is called when symbols were received from/sent to eBUS. + * @param data the received/sent data. + * @param len the length of received/sent data. * @param received @a true on reception, @a false on sending. */ - virtual void notifyDeviceData(symbol_t symbol, bool received) = 0; // abstract + virtual void notifyDeviceData(symbol_t* data, size_t len, bool received) = 0; // abstract /** * Called to notify a status message from the device. @@ -99,25 +76,32 @@ class DeviceListener { /** * The base class for accessing an eBUS. */ -class Device { +class Device : public TransportListener { protected: /** * Construct a new instance. - * @param name the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network). + * @param transport the @a Transport to use. */ - explicit Device(const char* name); + explicit Device(Transport* transport) + : m_transport(transport), m_listener(nullptr) { + } public: /** * Destructor. */ - virtual ~Device() { } + virtual ~Device() { + if (m_transport) { + delete m_transport; + m_transport = nullptr; + } + } /** * Get the device name. * @return the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network). */ - const char* getName() const { return m_name; } + const char* getName() const { return m_transport->getName(); } /** * Set the @a DeviceListener. @@ -131,43 +115,75 @@ class Device { * @param verbose whether to add verbose infos. * @param prefix true for the synchronously retrievable prefix, false for the potentially asynchronous suffix. */ - virtual void formatInfo(ostringstream* output, bool verbose, bool prefix) = 0; + virtual void formatInfo(ostringstream* output, bool verbose, bool prefix) { + if (prefix) { + *output << m_transport->getName() << ", " << m_transport->getTransportInfo(); + } else if (!m_transport->isValid()) { + *output << ", invalid"; + } + } /** * Format device infos in JSON format. * @param output the @a ostringstream to append the infos to. */ - virtual void formatInfoJson(ostringstream* output) = 0; + virtual void formatInfoJson(ostringstream* output) const {} /** - * Open the file descriptor. - * @return the @a result_t code. + * @return whether the device supports checking for version updates. */ - virtual result_t open() = 0; + virtual bool supportsUpdateCheck() const { return false; } + + // @copydoc + virtual result_t notifyTransportStatus(bool opened) { + m_listener->notifyDeviceStatus(!opened, opened ? "transport opened" : "transport closed"); + return RESULT_OK; + } + + // @copydoc + virtual void notifyTransportMessage(bool error, const char* message) { + m_listener->notifyDeviceStatus(error, message); + } /** - * Has to be called by subclasses upon successful opening the device as last action in open(). + * Open the file descriptor. * @return the @a result_t code. */ - virtual result_t afterOpen() { return RESULT_OK; } + virtual result_t open() { return m_transport->open(); } /** - * Close the file descriptor if opened. + * Return whether the device is opened and available. + * @return whether the device is opened and available. */ - virtual void close() = 0; + virtual bool isValid() { return m_transport->isValid(); } + protected: + /** the @a Transport to use. */ + Transport* m_transport; + + /** the @a DeviceListener, or nullptr. */ + DeviceListener* m_listener; +}; + + +class CharDevice : public Device { + protected: /** - * Return whether the device is opened and available. - * @return whether the device is opened and available. + * Construct a new instance. + * @param transport the @a Transport to use. */ - virtual bool isValid() = 0; + explicit CharDevice(Transport* transport) + : Device(transport), m_arbitrationMaster(SYN), m_arbitrationCheck(0) { + transport->setListener(this); + } + public: /** * Write a single byte to the device. * @param value the byte value to write. * @return the @a result_t code. */ - virtual result_t send(symbol_t value) = 0; + virtual result_t send(symbol_t value) = 0; // abstract /** * Read a single byte from the device. @@ -177,7 +193,7 @@ class Device { * @a as_won, the received byte is the master address that was successfully arbitrated with. * @return the result_t code. */ - virtual result_t recv(unsigned int timeout, symbol_t* value, ArbitrationState* arbitrationState) = 0; + virtual result_t recv(unsigned int timeout, symbol_t* value, ArbitrationState* arbitrationState) = 0; // abstract /** * Start the arbitration with the specified master address. A subsequent request while an arbitration is currently in @@ -185,74 +201,65 @@ class Device { * @param masterAddress the master address, or @a SYN to cancel a previous arbitration request. * @return the result_t code. */ - virtual result_t startArbitration(symbol_t masterAddress) = 0; + virtual result_t startArbitration(symbol_t masterAddress); /** * Return whether the device is currently in arbitration. * @return true when the device is currently in arbitration. */ - virtual bool isArbitrating() const = 0; + virtual bool isArbitrating() const { return m_arbitrationMaster != SYN; } /** - * @return whether the device supports checking for version updates. + * Cancel a running arbitration. + * @param arbitrationState the reference in which @a as_error is stored when cancelled. + * @return true if it was cancelled, false if not. */ - virtual bool supportsUpdateCheck() const = 0; + virtual bool cancelRunningArbitration(ArbitrationState* arbitrationState); protected: - /** the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network). */ - const char* m_name; - - /** the @a DeviceListener, or nullptr. */ - DeviceListener* m_listener; -}; - + /** the arbitration master address to send when in arbitration, or @a SYN. */ + symbol_t m_arbitrationMaster; -/** the possible enhanced protocol levels. */ -enum EnhancedLevel { - el_none = 0, //!< non-enhanced - el_basic = 1, //!< enhanced basic - el_speed = 2, //!< enhanced high-speed + /** >0 when in arbitration and the next received symbol needs to be checked against the sent master address, + * incremented with each received SYN when arbitration was not performed as expected and needs to be stopped. */ + size_t m_arbitrationCheck; }; -/** - * The common base class for devices using a file descriptor. - */ -class FileDevice : public Device { - protected: - /** - * Construct a new instance. - * @param name the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network). - * @param checkDevice whether to regularly check the device availability. - * @param latency the bus transfer latency in milliseconds. - * @param enhancedLevel whether to use the ebusd enhanced protocol. - */ - FileDevice(const char* name, bool checkDevice, unsigned int latency, - EnhancedLevel enhancedLevel); - +class PlainCharDevice : public CharDevice { public: /** - * Destructor. + * Construct a new instance. + * @param transport the @a Transport to use. */ - virtual ~FileDevice(); + explicit PlainCharDevice(Transport* transport) + : CharDevice(transport) { + } // @copydoc - void formatInfo(ostringstream* output, bool verbose, bool prefix) override; + result_t send(symbol_t value) override; // @copydoc - void formatInfoJson(ostringstream* output) override; + result_t recv(unsigned int timeout, symbol_t* value, ArbitrationState* arbitrationState) override; +}; - // @copydoc - result_t open() override; - // @copydoc - result_t afterOpen() override; +class EnhancedCharDevice : public CharDevice { + public: + /** + * Construct a new instance. + * @param transport the @a Transport to use. + */ + explicit EnhancedCharDevice(Transport* transport) + : CharDevice(transport), m_resetRequested(false), + m_extraFatures(0), m_infoReqTime(0), m_infoLen(0), m_infoPos(0) { + } // @copydoc - void close() override; + void formatInfo(ostringstream* output, bool verbose, bool prefix) override; // @copydoc - bool isValid() override; + void formatInfoJson(ostringstream* output) const override; // @copydoc result_t send(symbol_t value) override; @@ -264,50 +271,19 @@ class FileDevice : public Device { result_t startArbitration(symbol_t masterAddress) override; // @copydoc - bool isArbitrating() const override { return m_arbitrationMaster != SYN; } - - /** - * Get the transfer latency of this device. - * @return the transfer latency in milliseconds. - */ - virtual unsigned int getLatency() const { return m_latency; } - - /** - * Return whether the device supports the ebusd enhanced protocol. - * @return whether the device supports the ebusd enhanced protocol. - */ - bool isEnhancedProto() const { return m_enhancedLevel != el_none; } - - /** - * Get info about enhanced protocol support as string. - * @return a @a string describing level of enhanced protocol support, or the empty string. - */ - virtual string getEnhancedProtoInfo() const { return m_enhancedLevel ? "enhanced" : ""; } + virtual result_t notifyTransportStatus(bool opened); // @copydoc - bool supportsUpdateCheck() const override { return m_enhancedLevel && m_extraFatures & 0x01; } - - /** - * @return whether the device supports the ebusd enhanced protocol and supports querying extra infos. - */ - bool supportsEnhancedInfos() const { return m_enhancedLevel && m_extraFatures & 0x01; } + bool supportsUpdateCheck() const override { return m_extraFatures & 0x01; } /** * Check for a running extra infos request, wait for it to complete, * and then send a new request for extra infos to enhanced device. * @param infoId the ID of the info to request. + * @param wait true to wait for a running request to complete, false to send right away. * @return @a RESULT_OK on success, or an error code otherwise. */ - result_t requestEnhancedInfo(symbol_t infoId); - - /** - * Write a sequence of bytes to the device. - * @param id the ID of the sequence. - * @param data the buffer with the data to send. - * @param len the length of the buffer. - * @return the @a result_t code. - */ - virtual result_t sendSequence(SequenceId id, const uint8_t* data = nullptr, size_t len = 0); + result_t requestEnhancedInfo(symbol_t infoId, bool wait = true); /** * Get the enhanced device version. @@ -321,12 +297,7 @@ class FileDevice : public Device { */ string getEnhancedInfos(); - protected: - /** - * Check if the device is still available and close it if not. - */ - virtual void checkDevice() = 0; // abstract - + private: /** * Cancel a running arbitration. * @param arbitrationState the reference in which @a as_error is stored when cancelled. @@ -334,83 +305,22 @@ class FileDevice : public Device { */ bool cancelRunningArbitration(ArbitrationState* arbitrationState); - /** - * Write a single byte. - * @param value the byte value to write. - * @param startArbitration true to start arbitration. - * @return true on success, false on error. - */ - virtual bool write(symbol_t value, bool startArbitration = false); - - /** - * Check whether a symbol is available for reading immediately (without waiting). - * @return true when a symbol is available for reading immediately. - */ - virtual bool available(); - - /** - * Read a single byte. - * @param value the reference in which the read byte value is stored. - * @param isAvailable the result of the immediately preceding call to @a available(). - * @param arbitrationState the variable in which to store the current/received arbitration state (mandatory for enhanced proto). - * @param incomplete the variable in which to store when a partial transfer needs another poll. - * @return true on success, false on error. - */ - virtual bool read(symbol_t* value, bool isAvailable, ArbitrationState* arbitrationState = nullptr, - bool* incomplete = nullptr); - - /** whether to regularly check the device availability. */ - const bool m_checkDevice; - - /** the bus transfer latency in milliseconds. */ - const unsigned int m_latency; - - /** whether the device supports the ebusd enhanced protocol. */ - const EnhancedLevel m_enhancedLevel; - - /** the opened file descriptor, or -1. */ - int m_fd; - - /** whether the reset of an enhanced device was already requested. */ - bool m_resetRequested; - - private: /** * Handle the already buffered enhanced data. * @param value the reference in which the read byte value is stored. - * @param arbitrationState the variable in which to store the current/received arbitration state (mandatory for enhanced proto). - * @return true if the value was set, false otherwise. + * @param arbitrationState the variable in which to store the current/received arbitration state. + * @return the @a result_t code, especially RESULT_CONTINE if the value was set and more data is available immediately. */ - bool handleEnhancedBufferedData(symbol_t* value, ArbitrationState* arbitrationState); + result_t handleEnhancedBufferedData(const uint8_t* data, size_t len, symbol_t* value, + ArbitrationState* arbitrationState); /** * Called when reception of an info ID was completed. */ void notifyInfoRetrieved(); - /** the arbitration master address to send when in arbitration, or @a SYN. */ - symbol_t m_arbitrationMaster; - - /** >0 when in arbitration and the next received symbol needs to be checked against the sent master address, - * incremented with each received SYN when arbitration was not performed as expected and needs to be stopped. */ - size_t m_arbitrationCheck; - - /** the read buffer. */ - symbol_t* m_buffer; - - /** the read buffer size (multiple of 4). */ - size_t m_bufSize; - - /** the read buffer fill length. */ - size_t m_bufLen; - - /** the read buffer read position. */ - size_t m_bufPos; - /** the send buffer. */ - uint8_t* m_sendBuf; - - /** the send buffer size. */ - size_t m_sendBufSize; + /** whether the reset of the device was already requested. */ + bool m_resetRequested; /** the extra features supported by the device. */ symbol_t m_extraFatures; @@ -440,96 +350,6 @@ class FileDevice : public Device { string m_enhInfoBusVoltage; }; - -/** - * The @a Device for directly connected serial interfaces (tty). - */ -class SerialDevice : public FileDevice { - public: - /** - * Construct a new instance. - * @param name the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network). - * @param checkDevice whether to regularly check the device availability. - * @param extraLatency the extra bus transfer latency in milliseconds. - * @param enhancedLevel whether to use the ebusd enhanced protocol. - */ - SerialDevice(const char* name, bool checkDevice, unsigned int extraLatency, - EnhancedLevel enhancedLevel) - : FileDevice(name, checkDevice, extraLatency, enhancedLevel) { - } - - // @copydoc - string getEnhancedProtoInfo() const override { - return m_enhancedLevel == el_speed ? "enhanced high speed" : FileDevice::getEnhancedProtoInfo(); - } - - // @copydoc - result_t open() override; - - // @copydoc - void close() override; - - - protected: - // @copydoc - void checkDevice() override; - - - private: - /** the previous settings of the device for restoring. */ - termios m_oldSettings; -}; - -/** - * The @a Device for remote network interfaces. - */ -class NetworkDevice : public FileDevice { - public: - /** - * Construct a new instance. - * @param name the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network). - * @param address the socket address of the device. - * @param hostOrIp the host name or IP address of the device. - * @param port the TCP or UDP port of the device. - * @param extraLatency the extra bus transfer latency in milliseconds. - * @param udp true for UDP, false to TCP. - * @param enhancedLevel whether to use the ebusd enhanced protocol. - */ - NetworkDevice(const char* name, const char* hostOrIp, uint16_t port, unsigned int extraLatency, - bool udp, EnhancedLevel enhancedLevel) - : FileDevice(name, true, NETWORK_LATENCY_MS+extraLatency, enhancedLevel), - m_hostOrIp(hostOrIp), m_port(port), m_udp(udp) {} - - /** - * Destructor. - */ - ~NetworkDevice() override { - if (m_hostOrIp) { - free((void*)m_hostOrIp); - m_hostOrIp = nullptr; - } - } - - // @copydoc - result_t open() override; - - - protected: - // @copydoc - void checkDevice() override; - - - private: - /** the host name or IP address of the device. */ - const char* m_hostOrIp; - - /** the TCP or UDP port of the device. */ - const uint16_t m_port; - - /** true for UDP, false to TCP. */ - const bool m_udp; -}; - } // namespace ebusd #endif // LIB_EBUS_DEVICE_H_ diff --git a/src/lib/ebus/protocol.cpp b/src/lib/ebus/protocol.cpp index 95157c92e..b6fe0b673 100644 --- a/src/lib/ebus/protocol.cpp +++ b/src/lib/ebus/protocol.cpp @@ -57,21 +57,18 @@ bool ActiveBusRequest::notify(result_t result, const SlaveSymbolString& slave) { ProtocolHandler* ProtocolHandler::create(const ebus_protocol_config_t config, ProtocolListener* listener) { const char* name = config.device; - EnhancedLevel enhanced = el_none; - if (strncmp(name, "en", 2) == 0 && name[2] && name[3] == ':') { - switch (name[2]) { - case 's': - enhanced = el_speed; - break; - case 'h': - enhanced = el_basic; - break; - } + bool enhanced = false; + uint8_t speed = 0; + if (name[0] == 'e' && name[1] && name[2] && name[3] == ':') { + speed = name[2] == 's' ? 2 : name[2] == 'h' ? 1 : 0; + enhanced = speed > 0 && name[1] == 'n'; if (enhanced) { name += 4; + } else { + speed = 0; } } - FileDevice* device = nullptr; + Transport* transport; if (strchr(name, '/') == nullptr && strchr(name, ':') != nullptr) { char* in = strdup(name); bool udp = false; @@ -95,10 +92,16 @@ ProtocolHandler* ProtocolHandler::create(const ebus_protocol_config_t config, *portpos = 0; char* hostOrIp = strdup(addrpos); free(in); - device = new NetworkDevice(name, hostOrIp, port, config.extraLatency, udp, enhanced); + transport = new NetworkTransport(name, config.extraLatency, hostOrIp, port, udp); } else { - // support enx:/dev/, ens:/dev/, enh:/dev/, and /dev/ - device = new SerialDevice(name, !config.noDeviceCheck, config.extraLatency, enhanced); + // support ens:/dev/, enh:/dev/, and /dev/ + transport = new SerialTransport(name, config.extraLatency, !config.noDeviceCheck, speed); + } + CharDevice* device; + if (enhanced) { + device = new EnhancedCharDevice(transport); + } else { + device = new PlainCharDevice(transport); } return new DirectProtocolHandler(config, device, listener); } @@ -124,55 +127,56 @@ void ProtocolHandler::formatInfo(ostringstream* ostream, bool verbose, bool noWa m_device->formatInfo(ostream, verbose, false); } -void ProtocolHandler::formatInfoJson(ostringstream* ostream) { +void ProtocolHandler::formatInfoJson(ostringstream* ostream) const { m_device->formatInfoJson(ostream); } -void ProtocolHandler::notifyDeviceData(symbol_t symbol, bool received) { +void ProtocolHandler::notifyDeviceData(symbol_t* data, size_t len, bool received) { if (received && m_dumpFile) { - m_dumpFile->write(&symbol, 1); + m_dumpFile->write(data, len); } if (!m_logRawFile && !m_logRawEnabled) { return; } if (m_logRawBytes) { if (m_logRawFile) { - m_logRawFile->write(&symbol, 1, received); + m_logRawFile->write(data, len, received); } else if (m_logRawEnabled) { - if (received) { - logNotice(lf_bus, "<%02x", symbol); - } else { - logNotice(lf_bus, ">%02x", symbol); + for (size_t pos = 0; pos < len; pos++) { + logNotice(lf_bus, "%c%02x", received ? '<' : '>', data[pos]); } } return; } - if (symbol != SYN) { - if (received && !m_logRawLastReceived && symbol == m_logRawLastSymbol) { - return; // skip received echo of previously sent symbol - } - if (m_logRawBuffer.tellp() == 0 || received != m_logRawLastReceived) { - m_logRawLastReceived = received; - if (m_logRawBuffer.tellp() == 0 && m_logRawLastSymbol != SYN) { - m_logRawBuffer << "..."; + for (size_t pos = 0; pos < len; pos++) { + symbol_t symbol = data[pos]; + if (symbol != SYN) { + if (received && !m_logRawLastReceived && symbol == m_logRawLastSymbol) { + continue; // skip received echo of previously sent symbol + } + if (m_logRawBuffer.tellp() == 0 || received != m_logRawLastReceived) { + m_logRawLastReceived = received; + if (m_logRawBuffer.tellp() == 0 && m_logRawLastSymbol != SYN) { + m_logRawBuffer << "..."; + } + m_logRawBuffer << (received ? "<" : ">"); } - m_logRawBuffer << (received ? "<" : ">"); + m_logRawBuffer << setw(2) << setfill('0') << hex << static_cast(symbol); } - m_logRawBuffer << setw(2) << setfill('0') << hex << static_cast(symbol); - } m_logRawLastSymbol = symbol; - if (m_logRawBuffer.tellp() > (symbol == SYN ? 0 : 64)) { // flush: direction+5 hdr+24 max data+crc+direction+ack+1 - if (symbol != SYN) { - m_logRawBuffer << "..."; - } - const string bufStr = m_logRawBuffer.str(); - const char* str = bufStr.c_str(); - if (m_logRawFile) { - m_logRawFile->write((const unsigned char*)str, strlen(str), received, false); - } else { - logNotice(lf_bus, str); + if (m_logRawBuffer.tellp() > (symbol == SYN ? 0 : 64)) { // flush: direction+5 hdr+24 max data+crc+direction+ack+1 + if (symbol != SYN) { + m_logRawBuffer << "..."; + } + const string bufStr = m_logRawBuffer.str(); + const char* str = bufStr.c_str(); + if (m_logRawFile) { + m_logRawFile->write((const unsigned char*)str, strlen(str), received, false); + } else { + logNotice(lf_bus, str); + } + m_logRawBuffer.str(""); } - m_logRawBuffer.str(""); } } diff --git a/src/lib/ebus/protocol.h b/src/lib/ebus/protocol.h index 183826e19..283952cb3 100755 --- a/src/lib/ebus/protocol.h +++ b/src/lib/ebus/protocol.h @@ -332,7 +332,7 @@ class ProtocolHandler : public WaitThread, DeviceListener { * Format device/protocol infos in JSON format. * @param output the @a ostringstream to append the infos to. */ - virtual void formatInfoJson(ostringstream* output); + virtual void formatInfoJson(ostringstream* output) const; /** * @return whether to allow read access to the device only. @@ -381,7 +381,7 @@ class ProtocolHandler : public WaitThread, DeviceListener { virtual bool supportsUpdateCheck() const { return m_device->supportsUpdateCheck(); } // @copydoc - void notifyDeviceData(symbol_t symbol, bool received) override; + virtual void notifyDeviceData(symbol_t* symbols, size_t len, bool received); // @copydoc void notifyDeviceStatus(bool error, const char* message) override; diff --git a/src/lib/ebus/protocol_direct.cpp b/src/lib/ebus/protocol_direct.cpp index 98dbfd181..c99f14ef1 100644 --- a/src/lib/ebus/protocol_direct.cpp +++ b/src/lib/ebus/protocol_direct.cpp @@ -82,27 +82,38 @@ void DirectProtocolHandler::run() { lastTime += 2; logNotice(lf_bus, "bus started with own address %2.2x/%2.2x%s", m_ownMasterAddress, m_ownSlaveAddress, m_config.answer?" in answer mode":""); - do { - if (m_device->isValid() && !m_reconnect) { - result_t result = handleSymbol(); - time(&now); - if (result != RESULT_ERR_TIMEOUT && now >= lastTime) { - symCount++; - } - if (now > lastTime) { - m_symPerSec = symCount / (unsigned int)(now-lastTime); - if (m_symPerSec > m_maxSymPerSec) { - m_maxSymPerSec = m_symPerSec; - if (m_maxSymPerSec > 100) { - logNotice(lf_bus, "max. symbols per second: %d", m_maxSymPerSec); + bool valid = m_device->isValid(); + if (valid && !m_reconnect) { + unsigned int recvTimeout = 0; + symbol_t sentSymbol = ESC; + struct timespec sentTime; + result_t result = handleSend(&recvTimeout, &sentSymbol, &sentTime); + bool sent = result == RESULT_CONTINUE; + do { + if (result >= RESULT_OK) { + result = handleReceive(recvTimeout, sent, sentSymbol, &sentTime); + } + time(&now); + if (result != RESULT_ERR_TIMEOUT && now >= lastTime) { + symCount++; + } + if (now > lastTime) { + m_symPerSec = symCount / (unsigned int)(now-lastTime); + if (m_symPerSec > m_maxSymPerSec) { + m_maxSymPerSec = m_symPerSec; + if (m_maxSymPerSec > 100) { + logNotice(lf_bus, "max. symbols per second: %d", m_maxSymPerSec); + } } + lastTime = now; + symCount = 0; } - lastTime = now; - symCount = 0; - } + recvTimeout = 0; // for further buffered bytes + sent = false; + } while (result == RESULT_CONTINUE); } else { - if (!m_device->isValid()) { + if (!valid) { logNotice(lf_bus, "device invalid"); setState(bs_noSignal, RESULT_ERR_DEVICE); } @@ -136,7 +147,8 @@ void DirectProtocolHandler::run() { #endif #endif -result_t DirectProtocolHandler::handleSymbol() { +result_t DirectProtocolHandler::handleSend(unsigned int* recvTimeout, symbol_t* sentSymbol, +struct timespec* sentTime) { unsigned int timeout = SYN_TIMEOUT; symbol_t sendSymbol = ESC; bool sending = false; @@ -247,8 +259,6 @@ result_t DirectProtocolHandler::handleSymbol() { } // send symbol if necessary - result_t result; - struct timespec sentTime, recvTime; if (sending && !m_config.readOnly) { if (m_state != bs_sendSyn && (sendSymbol == ESC || sendSymbol == SYN)) { if (m_escape) { @@ -258,43 +268,51 @@ result_t DirectProtocolHandler::handleSymbol() { sendSymbol = ESC; } } - result = m_device->send(sendSymbol); - clockGettime(&sentTime); + result_t result = m_device->send(sendSymbol); + clockGettime(sentTime); if (result == RESULT_OK) { if (m_state == bs_ready) { timeout = m_config.busAcquireTimeout; } else { timeout = SEND_TIMEOUT; } + *sentSymbol = sendSymbol; } else { sending = false; timeout = SYN_TIMEOUT; setState(bs_skip, result); } + *recvTimeout = timeout; + return sending ? RESULT_CONTINUE : result; } else { - clockGettime(&sentTime); // for measuring arbitration delay in enhanced protocol + clockGettime(sentTime); // for measuring arbitration delay in enhanced protocol } + *recvTimeout = timeout; + return RESULT_OK; +} +result_t DirectProtocolHandler::handleReceive(unsigned int timeout, bool sending, symbol_t sentSymbol, +struct timespec* sentTime) { // receive next symbol (optionally check reception of sent symbol) symbol_t recvSymbol; + struct timespec recvTime; ArbitrationState arbitrationState = as_none; - result = m_device->recv(timeout, &recvSymbol, &arbitrationState); + result_t result = m_device->recv(timeout, &recvSymbol, &arbitrationState); + bool sentAutoSyn = false; if (sending) { clockGettime(&recvTime); - } - bool sentAutoSyn = false; - if (!sending && !m_config.readOnly && result == RESULT_ERR_TIMEOUT && m_generateSynInterval > 0 + } else if (!m_config.readOnly && result == RESULT_ERR_TIMEOUT && m_generateSynInterval > 0 && timeout >= m_generateSynInterval && (m_state == bs_noSignal || m_state == bs_skip)) { // check if acting as AUTO-SYN generator is required result = m_device->send(SYN); if (result != RESULT_OK) { return setState(bs_skip, result); } - clockGettime(&sentTime); + clockGettime(sentTime); recvSymbol = ESC; result = m_device->recv(SEND_TIMEOUT, &recvSymbol, &arbitrationState); clockGettime(&recvTime); - if (result != RESULT_OK) { + if (result < RESULT_OK) { logError(lf_bus, "unable to receive sent AUTO-SYN symbol: %s", getResultCode(result)); return setState(bs_noSignal, result); } @@ -302,7 +320,7 @@ result_t DirectProtocolHandler::handleSymbol() { logError(lf_bus, "received %2.2x instead of AUTO-SYN symbol", recvSymbol); return setState(bs_noSignal, result); } - measureLatency(&sentTime, &recvTime); + measureLatency(sentTime, &recvTime); if (m_generateSynInterval != SYN_INTERVAL) { // received own AUTO-SYN symbol back again: act as AUTO-SYN generator now m_generateSynInterval = SYN_INTERVAL; @@ -337,7 +355,7 @@ result_t DirectProtocolHandler::handleSymbol() { } else { logDebug(lf_bus, "arbitration won"); m_currentRequest = startRequest; - sendSymbol = m_currentRequest->getMaster()[0]; + sentSymbol = m_currentRequest->getMaster()[0]; sending = true; } } @@ -361,11 +379,11 @@ result_t DirectProtocolHandler::handleSymbol() { break; } if (sentAutoSyn && !sending) { - return RESULT_OK; + return result; } time_t now; time(&now); - if (result != RESULT_OK) { + if (result < RESULT_OK) { if ((m_generateSynInterval != SYN_INTERVAL && difftime(now, m_lastReceive) > 1) // at least one full second has passed since last received symbol || m_state == bs_noSignal) { @@ -376,20 +394,26 @@ result_t DirectProtocolHandler::handleSymbol() { m_lastReceive = now; if ((recvSymbol == SYN) && (m_state != bs_sendSyn)) { - if (!sending && m_remainLockCount > 0 && m_command.size() != 1) { - m_remainLockCount--; - } else if (!sending && m_remainLockCount == 0 && m_command.size() == 1) { - m_remainLockCount = 1; // wait for next AUTO-SYN after SYN / address / SYN (bus locked for own priority) + if (result == RESULT_CONTINUE) { + if (m_remainLockCount == 0) { + m_remainLockCount = 1; // avoid starting arbitration when more data is already buffered + } + } else if (!sending) { + if (m_remainLockCount > 0 && m_command.size() != 1) { + m_remainLockCount--; + } else if (m_remainLockCount == 0 && m_command.size() == 1) { + m_remainLockCount = 1; // wait for next AUTO-SYN after SYN / address / SYN (bus locked for own priority) + } } - clockGettime(&m_lastSynReceiveTime); - return setState(bs_ready, m_state == bs_skip ? RESULT_OK : RESULT_ERR_SYN); + m_lastSynReceiveTime = recvTime; + return setState(bs_ready, m_state == bs_skip || m_remainLockCount > 0 ? result : RESULT_ERR_SYN); } if (sending && m_state != bs_ready) { // check received symbol for equality if not in arbitration - if (recvSymbol != sendSymbol) { + if (recvSymbol != sentSymbol) { return setState(bs_skip, RESULT_ERR_SYMBOL); } - measureLatency(&sentTime, &recvTime); + measureLatency(sentTime, &recvTime); } switch (m_state) { @@ -407,10 +431,10 @@ result_t DirectProtocolHandler::handleSymbol() { if (m_escape) { // check escape/unescape state if (sending) { - if (sendSymbol == ESC) { - return RESULT_OK; + if (sentSymbol == ESC) { + return result; } - sendSymbol = recvSymbol = m_escape; + sentSymbol = recvSymbol = m_escape; } else { if (recvSymbol > 0x01) { return setState(bs_skip, RESULT_ERR_ESC); @@ -420,23 +444,23 @@ result_t DirectProtocolHandler::handleSymbol() { m_escape = 0; } else if (!sending && recvSymbol == ESC) { m_escape = ESC; - return RESULT_OK; + return result; } switch (m_state) { case bs_noSignal: - return setState(bs_skip, RESULT_OK); + return setState(bs_skip, result); case bs_skip: - return RESULT_OK; + return result; case bs_ready: if (m_currentRequest != nullptr && sending) { // check arbitration - if (recvSymbol == sendSymbol) { // arbitration successful + if (recvSymbol == sentSymbol) { // arbitration successful // measure arbitration delay - int64_t latencyLong = (sentTime.tv_sec*1000000000 + sentTime.tv_nsec - - m_lastSynReceiveTime.tv_sec*1000000000 - m_lastSynReceiveTime.tv_nsec)/1000; + int64_t latencyLong = (sentTime->tv_sec*1000000000LL + sentTime->tv_nsec + - m_lastSynReceiveTime.tv_sec*1000000000LL - m_lastSynReceiveTime.tv_nsec)/1000; if (latencyLong >= 0 && latencyLong <= 10000) { // skip clock skew or out of reasonable range auto latency = static_cast(latencyLong); logDebug(lf_bus, "arbitration delay %d micros", latency); @@ -452,11 +476,11 @@ result_t DirectProtocolHandler::handleSymbol() { } m_nextSendPos = 1; m_repeat = false; - return setState(bs_sendCmd, RESULT_OK); + return setState(bs_sendCmd, result); } // arbitration lost. if same priority class found, try again after next AUTO-SYN m_remainLockCount = isMaster(recvSymbol) ? 2 : 1; // number of SYN to wait for before next send try - if ((recvSymbol & 0x0f) != (sendSymbol & 0x0f) && m_lockCount > m_remainLockCount) { + if ((recvSymbol & 0x0f) != (sentSymbol & 0x0f) && m_lockCount > m_remainLockCount) { // if different priority class found, try again after N AUTO-SYN symbols (at least next AUTO-SYN) m_remainLockCount = m_lockCount; } @@ -464,7 +488,7 @@ result_t DirectProtocolHandler::handleSymbol() { } m_command.push_back(recvSymbol); m_repeat = false; - return setState(bs_recvCmd, RESULT_OK); + return setState(bs_recvCmd, result); case bs_recvCmd: if ((m_command.size() == 0 && !isMaster(recvSymbol)) @@ -473,9 +497,9 @@ result_t DirectProtocolHandler::handleSymbol() { } m_command.push_back(recvSymbol); if (m_command.isComplete()) { // all data received - return setState(bs_recvCmdCrc, RESULT_OK); + return setState(bs_recvCmdCrc, result); } - return RESULT_OK; + return result; case bs_recvCmdCrc: m_crcValid = recvSymbol == m_crc; @@ -483,7 +507,7 @@ result_t DirectProtocolHandler::handleSymbol() { if (m_crcValid) { addSeenAddress(m_command[0]); messageCompleted(); - return setState(bs_skip, RESULT_OK); + return setState(bs_skip, result); } return setState(bs_skip, RESULT_ERR_CRC); } @@ -493,14 +517,14 @@ result_t DirectProtocolHandler::handleSymbol() { if (m_crcValid) { addSeenAddress(m_command[0]); m_currentAnswering = true; - return setState(bs_sendCmdAck, RESULT_OK); + return setState(bs_sendCmdAck, result); } return setState(bs_sendCmdAck, RESULT_ERR_CRC); } } if (m_crcValid) { addSeenAddress(m_command[0]); - return setState(bs_recvCmdAck, RESULT_OK); + return setState(bs_recvCmdAck, result); } if (m_repeat) { return setState(bs_skip, RESULT_ERR_CRC); @@ -515,15 +539,15 @@ result_t DirectProtocolHandler::handleSymbol() { if (m_currentRequest != nullptr) { if (isMaster(m_currentRequest->getMaster()[1])) { messageCompleted(); - return setState(bs_sendSyn, RESULT_OK); + return setState(bs_sendSyn, result); } } else if (isMaster(m_command[1])) { messageCompleted(); - return setState(bs_skip, RESULT_OK); + return setState(bs_skip, result); } m_repeat = false; - return setState(bs_recvRes, RESULT_OK); + return setState(bs_recvRes, result); } if (recvSymbol == NAK) { if (!m_repeat) { @@ -543,17 +567,17 @@ result_t DirectProtocolHandler::handleSymbol() { case bs_recvRes: m_response.push_back(recvSymbol); if (m_response.isComplete()) { // all data received - return setState(bs_recvResCrc, RESULT_OK); + return setState(bs_recvResCrc, result); } - return RESULT_OK; + return result; case bs_recvResCrc: m_crcValid = recvSymbol == m_crc; if (m_crcValid) { if (m_currentRequest != nullptr) { - return setState(bs_sendResAck, RESULT_OK); + return setState(bs_sendResAck, result); } - return setState(bs_recvResAck, RESULT_OK); + return setState(bs_recvResAck, result); } if (m_repeat) { if (m_currentRequest != nullptr) { @@ -572,7 +596,7 @@ result_t DirectProtocolHandler::handleSymbol() { return setState(bs_skip, RESULT_ERR_ACK); } messageCompleted(); - return setState(bs_skip, RESULT_OK); + return setState(bs_skip, result); } if (recvSymbol == NAK) { if (!m_repeat) { @@ -594,17 +618,17 @@ result_t DirectProtocolHandler::handleSymbol() { } m_nextSendPos++; if (m_nextSendPos >= m_currentRequest->getMaster().size()) { - return setState(bs_sendCmdCrc, RESULT_OK); + return setState(bs_sendCmdCrc, result); } - return RESULT_OK; + return result; case bs_sendCmdCrc: if (m_currentRequest->getMaster()[1] == BROADCAST) { messageCompleted(); - return setState(bs_sendSyn, RESULT_OK); + return setState(bs_sendSyn, result); } m_crcValid = true; - return setState(bs_recvCmdAck, RESULT_OK); + return setState(bs_recvCmdAck, result); case bs_sendResAck: if (!sending || m_currentRequest == nullptr) { @@ -619,7 +643,7 @@ result_t DirectProtocolHandler::handleSymbol() { return setState(bs_sendSyn, RESULT_ERR_ACK); } messageCompleted(); - return setState(bs_sendSyn, RESULT_OK); + return setState(bs_sendSyn, result); case bs_sendCmdAck: if (!sending || !m_config.answer) { @@ -636,18 +660,20 @@ result_t DirectProtocolHandler::handleSymbol() { } if (isMaster(m_command[1])) { messageCompleted(); // TODO decode command and store value into database of internal variables - return setState(bs_skip, RESULT_OK); + return setState(bs_skip, result); } m_nextSendPos = 0; m_repeat = false; // build response and store in m_response for sending back to requesting master m_response.clear(); - result = m_listener->notifyProtocolAnswer(m_command, &m_response); - if (result != RESULT_OK) { - return setState(bs_skip, result); + { + result_t result = m_listener->notifyProtocolAnswer(m_command, &m_response); + if (result != RESULT_OK) { + return setState(bs_skip, result); + } } - return setState(bs_sendRes, RESULT_OK); + return setState(bs_sendRes, result); case bs_sendRes: if (!sending || !m_config.answer) { @@ -656,23 +682,23 @@ result_t DirectProtocolHandler::handleSymbol() { m_nextSendPos++; if (m_nextSendPos >= m_response.size()) { // slave data completely sent - return setState(bs_sendResCrc, RESULT_OK); + return setState(bs_sendResCrc, result); } - return RESULT_OK; + return result; case bs_sendResCrc: if (!sending || !m_config.answer) { return setState(bs_skip, RESULT_ERR_INVALID_ARG); } - return setState(bs_recvResAck, RESULT_OK); + return setState(bs_recvResAck, result); case bs_sendSyn: if (!sending) { return setState(bs_ready, RESULT_ERR_INVALID_ARG); } - return setState(bs_ready, RESULT_OK); + return setState(bs_ready, result); } - return RESULT_OK; + return result; } result_t DirectProtocolHandler::setState(BusState state, result_t result, bool firstRepetition) { @@ -682,7 +708,7 @@ result_t DirectProtocolHandler::setState(BusState state, result_t result, bool f m_currentRequest->incrementBusLostRetries(); m_nextRequests.push(m_currentRequest); // repeat m_currentRequest = nullptr; - } else if (state == bs_sendSyn || (result != RESULT_OK && !firstRepetition)) { + } else if (state == bs_sendSyn || (result < RESULT_OK && !firstRepetition)) { logDebug(lf_bus, "notify request: %s", getResultCode(result)); bool restart = m_currentRequest->notify( result == RESULT_ERR_SYN && (m_state == bs_recvCmdAck || m_state == bs_recvRes) @@ -716,13 +742,13 @@ result_t DirectProtocolHandler::setState(BusState state, result_t result, bool f m_escape = 0; if (state == m_state) { - if (m_listener && result != RESULT_OK) { + if (m_listener && result < RESULT_OK && state != bs_noSignal) { m_listener->notifyProtocolStatus(m_listenerState, result); } return result; } if ((result < RESULT_OK && !(result == RESULT_ERR_TIMEOUT && state == bs_skip && m_state == bs_ready)) - || (result != RESULT_OK && state == bs_skip && m_state != bs_ready)) { + || (result < RESULT_OK && state == bs_skip && m_state != bs_ready)) { logDebug(lf_bus, "%s during %s, switching to %s", getResultCode(result), getStateCode(m_state), getStateCode(state)); } else if (m_currentRequest != nullptr || state == bs_sendCmd || state == bs_sendCmdCrc || state == bs_sendCmdAck @@ -744,7 +770,7 @@ result_t DirectProtocolHandler::setState(BusState state, result_t result, bool f if (pstate == ps_idle && m_generateSynInterval == SYN_INTERVAL) { pstate = ps_idleSYN; } - if (result != RESULT_OK || pstate != m_listenerState) { + if (result < RESULT_OK || pstate != m_listenerState) { m_listener->notifyProtocolStatus(pstate, result); m_listenerState = pstate; } diff --git a/src/lib/ebus/protocol_direct.h b/src/lib/ebus/protocol_direct.h index 872d37d39..984b5513b 100755 --- a/src/lib/ebus/protocol_direct.h +++ b/src/lib/ebus/protocol_direct.h @@ -65,8 +65,8 @@ class DirectProtocolHandler : public ProtocolHandler { * @param listener the @a ProtocolListener. */ DirectProtocolHandler(const ebus_protocol_config_t config, - Device* device, ProtocolListener* listener) - : ProtocolHandler(config, device, listener), + CharDevice* device, ProtocolListener* listener) + : ProtocolHandler(config, device, listener), m_device(device), m_lockCount(config.lockCount <= 3 ? 3 : config.lockCount), m_remainLockCount(config.lockCount == 0 ? 1 : 0), m_generateSynInterval(config.generateSyn ? 10*getMasterNumber(config.ownAddress)+SYN_TIMEOUT : 0), @@ -110,10 +110,24 @@ class DirectProtocolHandler : public ProtocolHandler { private: /** - * Handle the next symbol on the bus. - * @return RESULT_OK on success, or an error code. + * Handle sending the next symbol to the bus. + * @param recvTimeout pointer to a variable in which to put the timeout for the receive. + * @param sentSymbol pointer to a variable in which to put the sent symbol. + * @param sentTime pointer to a variable in which to put the system time when the symbol was sent. + * @return RESULT_OK on success, RESULT_CONTINUE when a symbol was sent, or an error code. */ - result_t handleSymbol(); + result_t handleSend(unsigned int* recvTimeout, symbol_t* sentSymbol, struct timespec* sentTime); + + /** + * Handle receiving the next symbol from the bus. + * @param timeout the timeout for the receive. + * @param sending whether a symbol was sent before entry. + * @param sentSymbol the sent symbol to verify (if sending). + * @param sentTime pointer to a variable with the system time when the symbol was sent. + * @return RESULT_OK on success, RESULT_CONTINUE when further received symbols are buffered, + * or an error code. + */ + result_t handleReceive(unsigned int timeout, bool sending, symbol_t sentSymbol, struct timespec* sentTime); /** * Set a new @a BusState and add a log message if necessary. @@ -132,6 +146,9 @@ class DirectProtocolHandler : public ProtocolHandler { */ void messageCompleted(); + /** the @a CharDevice instance for accessing the bus. */ + CharDevice* m_device; + /** the number of AUTO-SYN symbols before sending is allowed after lost arbitration. */ unsigned int m_lockCount; diff --git a/src/lib/ebus/transport.cpp b/src/lib/ebus/transport.cpp new file mode 100644 index 000000000..e2fee3a5e --- /dev/null +++ b/src/lib/ebus/transport.cpp @@ -0,0 +1,356 @@ +/* + * ebusd - daemon for communication with eBUS heating systems. + * Copyright (C) 2023 John Baier + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "lib/ebus/transport.h" +#include +#include +#include +#ifdef HAVE_LINUX_SERIAL +# include +#endif +#ifdef HAVE_FREEBSD_UFTDI +# include +#endif +#ifdef HAVE_PPOLL +# include +#endif +#include "lib/ebus/data.h" +#include "lib/utils/tcpsocket.h" + +namespace ebusd { + + +#define MTU 1540 + +#ifndef POLLRDHUP +#define POLLRDHUP 0 +#endif + + +#ifdef DEBUG_RAW_TRAFFIC + #define DEBUG_RAW_TRAFFIC_HEAD(format, args...) fprintf(stdout, "%ld raw: " format, clockGetMillis(), args) + #define DEBUG_RAW_TRAFFIC_ITEM(args...) fprintf(stdout, args) + #define DEBUG_RAW_TRAFFIC_FINAL() fprintf(stdout, "\n"); fflush(stdout) + #undef DEBUG_RAW_TRAFFIC + #define DEBUG_RAW_TRAFFIC(format, args...) fprintf(stdout, "%ld raw: " format "\n", clockGetMillis(), args); fflush(stdout) +#else + #define DEBUG_RAW_TRAFFIC_HEAD(format, args...) + #undef DEBUG_RAW_TRAFFIC_ITEM + #define DEBUG_RAW_TRAFFIC_FINAL() + #define DEBUG_RAW_TRAFFIC(format, args...) +#endif + + + +FileTransport::FileTransport(const char* name, unsigned int latency, bool checkDevice) + : Transport(name, HOST_LATENCY_MS+latency), + m_checkDevice(checkDevice), + m_fd(-1), + m_bufSize(((MAX_LEN+1+3)/4)*4), m_bufLen(0) { + m_buffer = reinterpret_cast(malloc(m_bufSize)); + if (!m_buffer) { + m_bufSize = 0; + } +} + +FileTransport::~FileTransport() { + close(); + if (m_buffer) { + free(m_buffer); + m_buffer = nullptr; + } +} + +result_t FileTransport::open() { + close(); + result_t result; + if (m_bufSize == 0) { + result = RESULT_ERR_DEVICE; + } else { + result = openInternal(); + } + if (m_listener != nullptr) { + result = m_listener->notifyTransportStatus(result == RESULT_OK); + } + if (result != RESULT_OK) { + close(); + } + return result; +} + +void FileTransport::close() { + if (m_fd == -1) { + return; + } + ::close(m_fd); + m_fd = -1; + m_bufLen = 0; // flush read buffer + if (m_listener != nullptr) { + m_listener->notifyTransportStatus(false); + } +} + +bool FileTransport::isValid() { + if (m_fd == -1) { + return false; + } + if (m_checkDevice) { + checkDevice(); + } + return m_fd != -1; +} + +result_t FileTransport::write(const uint8_t* data, size_t len) { + if (!isValid()) { + return RESULT_ERR_DEVICE; + } +#ifdef DEBUG_RAW_TRAFFIC_ITEM + DEBUG_RAW_TRAFFIC_HEAD("%ld >", len); + for (size_t pos=0; pos < len; pos++) { + DEBUG_RAW_TRAFFIC_ITEM(" %2.2x", data[pos]); + } + DEBUG_RAW_TRAFFIC_FINAL(); +#endif + return (::write(m_fd, data, len) == len) ? RESULT_OK : RESULT_ERR_DEVICE; +} + +result_t FileTransport::read(unsigned int timeout, const uint8_t** data, size_t* len) { + if (!isValid()) { + return RESULT_ERR_DEVICE; + } + if (timeout == 0) { + if (m_bufLen > 0) { + *data = m_buffer; + *len = m_bufLen; + return RESULT_OK; + } + return RESULT_ERR_TIMEOUT; + } + if (timeout > 0) { + timeout += m_latency; + int ret; + struct timespec tdiff; + + // set select timeout + tdiff.tv_sec = timeout/1000; + tdiff.tv_nsec = (timeout%1000)*1000000; + +#ifdef HAVE_PPOLL + nfds_t nfds = 1; + struct pollfd fds[nfds]; + + memset(fds, 0, sizeof(fds)); + + fds[0].fd = m_fd; + fds[0].events = POLLIN | POLLERR | POLLHUP | POLLRDHUP; + ret = ppoll(fds, nfds, &tdiff, nullptr); + if (ret >= 0 && fds[0].revents & (POLLERR | POLLHUP | POLLRDHUP)) { + ret = -1; + } +#else +#ifdef HAVE_PSELECT + fd_set readfds, exceptfds; + + FD_ZERO(&readfds); + FD_ZERO(&exceptfds); + FD_SET(m_fd, &readfds); + + ret = pselect(m_fd + 1, &readfds, nullptr, &exceptfds, &tdiff, nullptr); + if (ret >= 1 && FD_ISSET(m_fd, &exceptfds)) { + ret = -1; + } +#else + ret = 1; // ignore timeout if neither ppoll nor pselect are available +#endif +#endif + if (ret == -1) { + DEBUG_RAW_TRAFFIC("poll error %d", errno); + close(); + return RESULT_ERR_DEVICE; + } + if (ret == 0) { + return RESULT_ERR_TIMEOUT; + } + } + + // directly read byte from device + if (m_bufLen > 0 && m_bufLen > m_bufSize - m_bufSize / 4) { + // more than 3/4 of input buffer consumed is taken as signal that ebusd is too slow + m_bufLen = 0; + if (m_listener != nullptr) { + m_listener->notifyTransportMessage(true, "buffer overflow"); + } + } + // fill up the buffer + ssize_t size = ::read(m_fd, m_buffer + m_bufLen, m_bufSize - m_bufLen); + if (size <= 0) { + return RESULT_ERR_TIMEOUT; + } +#ifdef DEBUG_RAW_TRAFFIC_ITEM + DEBUG_RAW_TRAFFIC_HEAD("%ld+%ld <", m_bufLen, size); + for (int pos=0; pos < size; pos++) { + DEBUG_RAW_TRAFFIC_ITEM(" %2.2x", m_buffer[(m_bufLen+pos)%m_bufSize]); + } + DEBUG_RAW_TRAFFIC_FINAL(); +#endif + m_bufLen += size; + *data = m_buffer; + *len = m_bufLen; + return RESULT_OK; +} + +void FileTransport::readConsumed(size_t len) { + if (len >= m_bufLen) { + m_bufLen = 0; + } else if (len > 0) { + size_t tail = m_bufLen - len; + memmove(m_buffer, m_buffer + len, tail); + DEBUG_RAW_TRAFFIC("move %ld @%ld to 0", tail, len); + m_bufLen = tail; + } +} + + +result_t SerialTransport::openInternal() { + struct termios newSettings; + + // open file descriptor + m_fd = ::open(m_name, O_RDWR | O_NOCTTY | O_NDELAY); + + if (m_fd < 0) { + return RESULT_ERR_NOTFOUND; + } + if (isatty(m_fd) == 0) { + close(); + return RESULT_ERR_NOTFOUND; + } + + if (flock(m_fd, LOCK_EX|LOCK_NB) != 0) { + close(); + return RESULT_ERR_DEVICE; + } + +#ifdef HAVE_LINUX_SERIAL + struct serial_struct serial; + if (ioctl(m_fd, TIOCGSERIAL, &serial) == 0) { + serial.flags |= ASYNC_LOW_LATENCY; + ioctl(m_fd, TIOCSSERIAL, &serial); + } +#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)); + +#ifdef HAVE_CFSETSPEED + cfsetspeed(&newSettings, m_speed ? (m_speed > 1 ? B115200 : B9600) : B2400); +#else + cfsetispeed(&newSettings, m_speed ? (m_speed > 1 ? B115200 : B9600) : B2400); + cfsetospeed(&newSettings, m_enhancedLevel ? (m_enhancedLevel >= el_speed ? B115200 : B9600) : B2400); +#endif + 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; + + // non-canonical mode: read() blocks until at least one byte is available + newSettings.c_cc[VMIN] = 1; + newSettings.c_cc[VTIME] = 0; + + // empty device buffer + tcflush(m_fd, TCIFLUSH); + + // activate new settings of serial device + if (tcsetattr(m_fd, TCSANOW, &newSettings)) { + close(); + return RESULT_ERR_DEVICE; + } + + // set serial device into blocking mode + fcntl(m_fd, F_SETFL, fcntl(m_fd, F_GETFL) & ~O_NONBLOCK); + + return RESULT_OK; +} + +void SerialTransport::close() { + if (m_fd != -1) { + // empty device buffer + tcflush(m_fd, TCIOFLUSH); + + // restore previous settings of the device + tcsetattr(m_fd, TCSANOW, &m_oldSettings); + } + FileTransport::close(); +} + +void SerialTransport::checkDevice() { + int cnt; + if (ioctl(m_fd, FIONREAD, &cnt) == -1) { + close(); + } +} + +result_t NetworkTransport::openInternal() { + m_fd = socketConnect(m_hostOrIp, m_port, m_udp, nullptr, 5, 2); // wait up to 5 seconds for established connection + if (m_fd < 0) { + return RESULT_ERR_GENERIC_IO; + } + if (!m_udp) { + usleep(25000); // wait 25ms for potential initial garbage + } + int cnt; + symbol_t buf[MTU]; + int ioerr; + while ((ioerr=ioctl(m_fd, FIONREAD, &cnt)) >= 0 && cnt > 1) { + // skip buffered input + ssize_t read = ::read(m_fd, &buf, MTU); + if (read <= 0) { + break; + } + } + if (ioerr < 0) { + close(); + return RESULT_ERR_GENERIC_IO; + } + return RESULT_OK; +} + +void NetworkTransport::checkDevice() { + int cnt; + if (ioctl(m_fd, FIONREAD, &cnt) < 0) { + close(); + } +} + +} // namespace ebusd diff --git a/src/lib/ebus/transport.h b/src/lib/ebus/transport.h new file mode 100755 index 000000000..ec4c2319f --- /dev/null +++ b/src/lib/ebus/transport.h @@ -0,0 +1,334 @@ +/* + * ebusd - daemon for communication with eBUS heating systems. + * Copyright (C) 2023 John Baier + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LIB_EBUS_TRANSPORT_H_ +#define LIB_EBUS_TRANSPORT_H_ + +#include +#include +#include +#include "lib/ebus/result.h" +#include "lib/ebus/symbol.h" + +namespace ebusd { + +/** @file lib/ebus/transport.h + * Classes for low level transport to/from the eBUS device. + * + * A @a Transport is either a @a SerialTransport directly connected + * to a local tty port or a remote @a NetworkTransport handled via a + * socket. + */ + +/** the transfer latency of the network device [ms]. */ +#define NETWORK_LATENCY_MS 30 + +/** the latency of the host [ms]. */ +#if defined(__CYGWIN__) || defined(_WIN32) +#define HOST_LATENCY_MS 20 +#else +#define HOST_LATENCY_MS 10 +#endif + +/** + * Interface for listening to data received on/sent to a @a Transport. + */ +class TransportListener { + public: + /** + * Destructor. + */ + virtual ~TransportListener() {} + + /** + * Called to notify a status change from the @a Transport. + * @param opened true when the transport was successfully opened, false when it was closed or open failed. + * @return the result_t code (other than RESULT_OK if an extra open action was performed unsuccessfully). + */ + virtual result_t notifyTransportStatus(bool opened) = 0; // abstract + + /** + * Called to notify a message from the @a Transport. + * @param error true for an error message, false for an info message. + * @param message the message string. + */ + virtual void notifyTransportMessage(bool error, const char* message) = 0; // abstract +}; + + +/** + * The base class for low level transport to/from the eBUS device. + */ +class Transport { + protected: + /** + * Construct a new instance. + * @param name the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network). + */ + Transport(const char* name, unsigned int latency) + : m_name(name), m_latency(latency), m_listener(nullptr) {} + + public: + /** + * Destructor. + */ + virtual ~Transport() { } + + /** + * Get the device name. + * @return the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network). + */ + const char* getName() const { return m_name; } + + /** + * Get the transfer latency of this device. + * @return the transfer latency in milliseconds. + */ + unsigned int getLatency() const { return m_latency; } + + /** + * Get info about the transport as string. + * @return a @a string describing the transport. + */ + virtual string getTransportInfo() const = 0; // abstract + + /** + * Set the @a TransportListener. + * @param listener the @a TransportListener. + */ + void setListener(TransportListener* listener) { m_listener = listener; } + + /** + * Open the transport. + * @return the @a result_t code. + */ + virtual result_t open() = 0; // abstract + + /** + * Close the device if opened. + */ + virtual void close() = 0; // abstract + + /** + * Return whether the device is opened and available. + * @return whether the device is opened and available. + */ + virtual bool isValid() = 0; // abstract + + /** + * Write arbitrary data to the device. + * @param data the data to send. + * @param len the length of data. + * @return the @a result_t code. + */ + virtual result_t write(const uint8_t* data, size_t len) = 0; // abstract + + /** + * Read data from the device. + * @param timeout maximum time to wait for the byte in milliseconds, or 0 for returning only already buffered data. + * @param data pointer to a variable in which to put the received data. + * @param len pointer to a variable in which to put the number of available bytes. + * @return the @a result_t code. + */ + virtual result_t read(unsigned int timeout, const uint8_t** data, size_t* len) = 0; // abstract + + /** + * Needs to be called after @a read() in order to mark all or parts of the available + * bytes as consumed. + * @param len the number of bytes consumed. + */ + virtual void readConsumed(size_t len) = 0; // abstract + + protected: + /** + * Internal method for opening the device. Called from @a open(). + * @return the @a result_t code. + */ + virtual result_t openInternal() = 0; // abstract + + /** the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network). */ + const char* m_name; + + /** the bus transfer latency in milliseconds. */ + const unsigned int m_latency; + + /** the @a TransportListener, or nullptr. */ + TransportListener* m_listener; +}; + + +/** + * The common base class for transport using a file descriptor. + */ +class FileTransport : public Transport { + protected: + /** + * Construct a new instance. + * @param name the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network). + * @param latency the bus transfer latency in milliseconds. + * @param checkDevice whether to regularly check the device availability. + */ + FileTransport(const char* name, unsigned int latency, bool checkDevice); + + public: + /** + * Destructor. + */ + virtual ~FileTransport(); + + // @copydoc + result_t open() override; + + // @copydoc + void close() override; + + // @copydoc + bool isValid() override; + + // @copydoc + result_t write(const uint8_t* data, size_t len) override; + + // @copydoc + result_t read(unsigned int timeout, const uint8_t** data, size_t* len) override; + + // @copydoc + void readConsumed(size_t len) override; + + protected: + /** + * Check if the device is still available and close it if not. + */ + virtual void checkDevice() = 0; // abstract + + /** whether to regularly check the device availability. */ + const bool m_checkDevice; + + /** the opened file descriptor, or -1. */ + int m_fd; + + private: + /** the receive buffer. */ + symbol_t* m_buffer; + + /** the receive buffer size (multiple of 4). */ + size_t m_bufSize; + + /** the receive buffer fill length. */ + size_t m_bufLen; +}; + + +/** + * The @a Transport for a directly connected serial interface (tty). + */ +class SerialTransport : public FileTransport { + public: + /** + * Construct a new instance. + * @param name the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network). + * @param extraLatency the extra bus transfer latency in milliseconds. + * @param checkDevice whether to regularly check the device availability. + * @param speed 0 for normal speed, 1 for 4x speed, or 2 for 48x speed. + */ + SerialTransport(const char* name, unsigned int extraLatency, bool checkDevice, uint8_t speed) + : FileTransport(name, extraLatency, checkDevice), m_speed(speed) { + } + + // @copydoc + string getTransportInfo() const override { + return m_speed ? (m_speed == 1 ? "serial speed" : "serial high speed") : "serial"; + } + + // @copydoc + result_t openInternal() override; + + // @copydoc + void close() override; + + + protected: + // @copydoc + void checkDevice() override; + + + private: + /** the previous settings of the device for restoring. */ + termios m_oldSettings; + + /** 0 for normal speed, 1 for 4x speed, or 2 for 48x speed. */ + const int m_speed; +}; + + +/** + * The @a Transport for a remote network interface. + */ +class NetworkTransport : public FileTransport { + public: + /** + * Construct a new instance. + * @param name the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network). + * @param extraLatency the extra bus transfer latency in milliseconds. + * @param address the socket address of the device. + * @param hostOrIp the host name or IP address of the device. + * @param port the TCP or UDP port of the device. + * @param udp true for UDP, false to TCP. + */ + NetworkTransport(const char* name, unsigned int extraLatency, const char* hostOrIp, uint16_t port, + bool udp) + : FileTransport(name, NETWORK_LATENCY_MS+extraLatency, true), + m_hostOrIp(hostOrIp), m_port(port), m_udp(udp) {} + + /** + * Destructor. + */ + ~NetworkTransport() override { + if (m_hostOrIp) { + free((void*)m_hostOrIp); + m_hostOrIp = nullptr; + } + } + + // @copydoc + string getTransportInfo() const override { + return m_udp ? "UDP" : "TCP"; + } + + // @copydoc + result_t openInternal() override; + + + protected: + // @copydoc + void checkDevice() override; + + + private: + /** the host name or IP address of the device. */ + const char* m_hostOrIp; + + /** the TCP or UDP port of the device. */ + const uint16_t m_port; + + /** true for UDP, false to TCP. */ + const bool m_udp; +}; + +} // namespace ebusd + +#endif // LIB_EBUS_TRANSPORT_H_ From 92675ba36721ff050d5ded60d57ce5f3458bd75b Mon Sep 17 00:00:00 2001 From: John Date: Sun, 26 Nov 2023 16:37:33 +0100 Subject: [PATCH 170/345] fix last syn receive time --- src/lib/ebus/protocol_direct.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/ebus/protocol_direct.cpp b/src/lib/ebus/protocol_direct.cpp index c99f14ef1..372b3a662 100644 --- a/src/lib/ebus/protocol_direct.cpp +++ b/src/lib/ebus/protocol_direct.cpp @@ -405,7 +405,7 @@ struct timespec* sentTime) { m_remainLockCount = 1; // wait for next AUTO-SYN after SYN / address / SYN (bus locked for own priority) } } - m_lastSynReceiveTime = recvTime; + clockGettime(&m_lastSynReceiveTime); return setState(bs_ready, m_state == bs_skip || m_remainLockCount > 0 ? result : RESULT_ERR_SYN); } From 8bbb6811a50c182a8249e2c7b7f69bcbf70d5ebd Mon Sep 17 00:00:00 2001 From: Andy Barratt Date: Mon, 27 Nov 2023 13:41:09 +0000 Subject: [PATCH 171/345] Update mqtt-hassio.cfg to include humidity --- contrib/etc/ebusd/mqtt-hassio.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/etc/ebusd/mqtt-hassio.cfg b/contrib/etc/ebusd/mqtt-hassio.cfg index 46d770efd..ab000d8bb 100644 --- a/contrib/etc/ebusd/mqtt-hassio.cfg +++ b/contrib/etc/ebusd/mqtt-hassio.cfg @@ -105,7 +105,7 @@ filter-seen = 5 #filter-non-circuit = # include only messages having the specified name (partial match, alternatives and wildcard supported). # HA integration: filter to some useful names for monitoring the heating circuit -filter-name = status|temp|yield|count|energy|power|runtime|hours|starts|mode|curve|^load$|^party$|sensor|timer +filter-name = status|temp|humidity|yield|count|energy|power|runtime|hours|starts|mode|curve|^load$|^party$|sensor|timer # exclude messages having the specified name (partial match, alternatives and wildcard supported). #filter-non-name = # include only messages having the specified level (partial match, alternatives and wildcard supported). From f00db143c7f165ed94b7c436c13cfcb981ca03e9 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 1 Dec 2023 20:46:35 +0100 Subject: [PATCH 172/345] more const, safety checks, formatting, docs --- src/lib/ebus/device.cpp | 19 ++++++++++--------- src/lib/ebus/device.h | 4 ++-- src/lib/ebus/protocol.cpp | 2 +- src/lib/ebus/protocol.h | 4 ++-- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/lib/ebus/device.cpp b/src/lib/ebus/device.cpp index 5ae58e275..2e6ac88a8 100755 --- a/src/lib/ebus/device.cpp +++ b/src/lib/ebus/device.cpp @@ -48,7 +48,7 @@ using std::fixed; #define ENH_RES_ERROR_EBUS ((uint8_t)0xb) #define ENH_RES_ERROR_HOST ((uint8_t)0xc) -// ebusd enhanced error codes for the ERROR_* responses +// ebusd enhanced error codes for the ENH_RES_ERROR_* responses #define ENH_ERR_FRAMING ((uint8_t)0x00) #define ENH_ERR_OVERRUN ((uint8_t)0x01) @@ -70,7 +70,7 @@ result_t PlainCharDevice::send(symbol_t value) { } result_t PlainCharDevice::recv(unsigned int timeout, symbol_t* value, ArbitrationState* arbitrationState) { - if (m_arbitrationMaster != SYN) { + if (m_arbitrationMaster != SYN && arbitrationState) { *arbitrationState = as_running; } uint64_t until = timeout == 0 ? 0 : clockGetMillis() + timeout + m_transport->getLatency(); @@ -105,7 +105,7 @@ result_t PlainCharDevice::recv(unsigned int timeout, symbol_t* value, Arbitratio result = RESULT_CONTINUE; } if (*value != SYN || m_arbitrationMaster == SYN || m_arbitrationCheck) { - if (m_arbitrationMaster != SYN) { + if (m_arbitrationMaster != SYN && arbitrationState) { if (m_arbitrationCheck) { *arbitrationState = *value == m_arbitrationMaster ? as_won : as_lost; m_arbitrationMaster = SYN; @@ -116,7 +116,7 @@ result_t PlainCharDevice::recv(unsigned int timeout, symbol_t* value, Arbitratio } return result; } - if (len == 1) { + if (len == 1 && arbitrationState) { // arbitration executed by ebusd itself bool wrote = m_transport->write(&m_arbitrationMaster, 1) == RESULT_OK; // send as fast as possible if (!wrote) { @@ -285,7 +285,7 @@ result_t EnhancedCharDevice::send(symbol_t value) { } result_t EnhancedCharDevice::recv(unsigned int timeout, symbol_t* value, ArbitrationState* arbitrationState) { - if (m_arbitrationMaster != SYN) { + if (arbitrationState && m_arbitrationMaster != SYN) { *arbitrationState = as_running; } uint64_t until = timeout == 0 ? 0 : clockGetMillis() + timeout + m_transport->getLatency(); @@ -366,7 +366,6 @@ result_t EnhancedCharDevice::notifyTransportStatus(bool opened) { return result; } - result_t EnhancedCharDevice::handleEnhancedBufferedData(const uint8_t* data, size_t len, symbol_t* value, ArbitrationState* arbitrationState) { bool valueSet = false; @@ -415,7 +414,9 @@ symbol_t* value, ArbitrationState* arbitrationState) { break; } sent = cmd == ENH_RES_STARTED; - *arbitrationState = sent ? as_won : as_lost; + if (arbitrationState) { + *arbitrationState = sent ? as_won : as_lost; + } m_arbitrationMaster = SYN; m_arbitrationCheck = 0; *value = data; @@ -429,7 +430,7 @@ symbol_t* value, ArbitrationState* arbitrationState) { break; } *value = data; - if (data == SYN && *arbitrationState == as_running && m_arbitrationCheck) { + if (data == SYN && arbitrationState && *arbitrationState == as_running && m_arbitrationCheck) { if (m_arbitrationCheck < 3) { // wait for three SYN symbols before switching to timeout m_arbitrationCheck++; } else { @@ -441,7 +442,7 @@ symbol_t* value, ArbitrationState* arbitrationState) { valueSet = true; break; case ENH_RES_RESETTED: - if (*arbitrationState != as_none) { + if (arbitrationState && *arbitrationState != as_none) { *arbitrationState = as_error; m_arbitrationMaster = SYN; m_arbitrationCheck = 0; diff --git a/src/lib/ebus/device.h b/src/lib/ebus/device.h index 61db9450a..9e67ca908 100755 --- a/src/lib/ebus/device.h +++ b/src/lib/ebus/device.h @@ -35,7 +35,7 @@ namespace ebusd { * The data transport itself is handled by a @a Transport instance. */ -/** the arbitration state handled by @a Device. */ +/** the arbitration state handled by @a CharDevice. */ enum ArbitrationState { as_none, //!< no arbitration in process as_start, //!< arbitration start requested @@ -62,7 +62,7 @@ class DeviceListener { * @param len the length of received/sent data. * @param received @a true on reception, @a false on sending. */ - virtual void notifyDeviceData(symbol_t* data, size_t len, bool received) = 0; // abstract + virtual void notifyDeviceData(const symbol_t* data, size_t len, bool received) = 0; // abstract /** * Called to notify a status message from the device. diff --git a/src/lib/ebus/protocol.cpp b/src/lib/ebus/protocol.cpp index b6fe0b673..3d605f672 100644 --- a/src/lib/ebus/protocol.cpp +++ b/src/lib/ebus/protocol.cpp @@ -131,7 +131,7 @@ void ProtocolHandler::formatInfoJson(ostringstream* ostream) const { m_device->formatInfoJson(ostream); } -void ProtocolHandler::notifyDeviceData(symbol_t* data, size_t len, bool received) { +void ProtocolHandler::notifyDeviceData(const symbol_t* data, size_t len, bool received) { if (received && m_dumpFile) { m_dumpFile->write(data, len); } diff --git a/src/lib/ebus/protocol.h b/src/lib/ebus/protocol.h index 283952cb3..540945b8d 100755 --- a/src/lib/ebus/protocol.h +++ b/src/lib/ebus/protocol.h @@ -252,7 +252,7 @@ class ProtocolListener { /** * Handles input from and output to eBUS with respect to the eBUS protocol. */ -class ProtocolHandler : public WaitThread, DeviceListener { +class ProtocolHandler : public WaitThread, public DeviceListener { public: /** * Construct a new instance. @@ -381,7 +381,7 @@ class ProtocolHandler : public WaitThread, DeviceListener { virtual bool supportsUpdateCheck() const { return m_device->supportsUpdateCheck(); } // @copydoc - virtual void notifyDeviceData(symbol_t* symbols, size_t len, bool received); + virtual void notifyDeviceData(const symbol_t* data, size_t len, bool received); // @copydoc void notifyDeviceStatus(bool error, const char* message) override; From 3aadafaa5616e7465edfc3d8761ed6d4ef3aba89 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 1 Dec 2023 22:06:08 +0100 Subject: [PATCH 173/345] fir for invalid scan status on no signal --- src/ebusd/bushandler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ebusd/bushandler.cpp b/src/ebusd/bushandler.cpp index ccd1d0fab..74c93dbd5 100644 --- a/src/ebusd/bushandler.cpp +++ b/src/ebusd/bushandler.cpp @@ -130,7 +130,7 @@ bool ScanRequest::notify(result_t result, const SlaveSymbolString& slave) { } } m_result = result; - if (m_slaves.empty()) { + if (m_slaves.empty() || result == RESULT_ERR_NO_SIGNAL) { if (m_deleteOnFinish) { logNotice(lf_bus, "scan finished"); } From 53fcc90f6e1ff1e419da7603a22e27a6c68414c8 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 17 Dec 2023 14:40:39 +0100 Subject: [PATCH 174/345] missed change in 3d9dd9b1c3f9cc7e9f59c177883dd906c5294e5d (fixes #1092) --- src/ebusd/main_args.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ebusd/main_args.cpp b/src/ebusd/main_args.cpp index b270207e3..230681c53 100755 --- a/src/ebusd/main_args.cpp +++ b/src/ebusd/main_args.cpp @@ -663,9 +663,8 @@ int parse_main_args(int argc, char* argv[], char* envp[], options_t *opt) { cnt = 1; strcat(envopt, pos); } - int idx = -1; - int err = argParse(&parseOpt, 1+cnt, envargv, &idx); - if (err != 0 && idx == -1) { // ignore args for non-arg boolean options + int err = argParse(&parseOpt, 1+cnt, envargv, reinterpret_cast(opt)); + if (err != 0) { if (err == ESRCH) { // special value to abort immediately logWrite(lf_main, ll_error, "invalid argument in env: %s", *env); // force logging on exit return EINVAL; From 2b5bd1b67ea35dc30d623734c944d56d9f60f5b1 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 24 Dec 2023 07:31:45 +0100 Subject: [PATCH 175/345] fix dpt 9 with negative values (closes #1074) and add rounding --- src/ebusd/knxhandler.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ebusd/knxhandler.cpp b/src/ebusd/knxhandler.cpp index 912482e8b..db100bd68 100644 --- a/src/ebusd/knxhandler.cpp +++ b/src/ebusd/knxhandler.cpp @@ -326,14 +326,14 @@ uint32_t floatToInt16(float val) { if (negative) { val = -val; } - val *= 100; + val = roundf(val*100.0f); int exp = ilogb(val)-10; if (exp < -10 || exp > 15) { return 0x7fff; // invalid value DPT 9 } auto shift = exp > 0 ? exp : 0; auto sig = static_cast(val * exp2(-shift)); - uint32_t value = static_cast(shift << 11) | sig; + uint32_t value = static_cast(shift << 11) | (negative ? 0x800-sig : sig); if (negative) { return value | 0x8000; } @@ -350,7 +350,7 @@ float int16ToFloat(uint16_t val) { bool negative = val&0x8000; int exp = (val>>11)&0xf; int sig = val&0x7ff; - return static_cast(sig * exp2(exp) * (negative ? -0.01 : 0.01)); + return static_cast((negative ? sig-0x800 : sig) * exp2(exp) * 0.01); } result_t KnxHandler::sendGroupValue(knx_addr_t dest, apci_t apci, dtlf_t& lengthFlag, unsigned int value, From f587fea69817b62c416002b3cb58a88f4edf3b11 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 24 Dec 2023 09:50:14 +0100 Subject: [PATCH 176/345] better rounding and avoid to loose precision, move 16 bit float to datatype --- src/ebusd/knxhandler.cpp | 42 +++------------------------------------ src/lib/ebus/datatype.cpp | 28 ++++++++++++++++++++++++++ src/lib/ebus/datatype.h | 20 +++++++++++++++++-- 3 files changed, 49 insertions(+), 41 deletions(-) diff --git a/src/ebusd/knxhandler.cpp b/src/ebusd/knxhandler.cpp index db100bd68..8d257408a 100644 --- a/src/ebusd/knxhandler.cpp +++ b/src/ebusd/knxhandler.cpp @@ -317,42 +317,6 @@ result_t getFieldLength(const SingleDataField *field, dtlf_t *length) { return RESULT_OK; } -uint32_t floatToInt16(float val) { - // (0.01*m)(2^e) format with sign, 12 bits mantissa (incl. sign), 4 bits exponent - if (val == 0) { - return 0; - } - bool negative = val < 0; - if (negative) { - val = -val; - } - val = roundf(val*100.0f); - int exp = ilogb(val)-10; - if (exp < -10 || exp > 15) { - return 0x7fff; // invalid value DPT 9 - } - auto shift = exp > 0 ? exp : 0; - auto sig = static_cast(val * exp2(-shift)); - uint32_t value = static_cast(shift << 11) | (negative ? 0x800-sig : sig); - if (negative) { - return value | 0x8000; - } - return value; -} - -float int16ToFloat(uint16_t val) { - if (val == 0) { - return 0; - } - if (val == 0x7fff) { - return static_cast(0xffffffff); // NaN - } - bool negative = val&0x8000; - int exp = (val>>11)&0xf; - int sig = val&0x7ff; - return static_cast((negative ? sig-0x800 : sig) * exp2(exp) * 0.01); -} - result_t KnxHandler::sendGroupValue(knx_addr_t dest, apci_t apci, dtlf_t& lengthFlag, unsigned int value, const SingleDataField *field) const { if (!m_con || !m_con->isConnected() || !m_con->getAddress()) { @@ -382,7 +346,7 @@ const SingleDataField *field) const { return ret; } else if (lengthFlag.length == 2) { // convert to (0.01*m)(2^e) format with sign, 12 bits mantissa (incl. sign), 4 bits exponent - value = floatToInt16(fval); + value = floatToUint16(fval); } else if (lengthFlag.length == 4) { // convert to IEEE 754 value = floatToUint(fval); @@ -686,8 +650,8 @@ void KnxHandler::handleGroupTelegram(knx_addr_t src, knx_addr_t dest, int len, c if (lengthFlag.isFloat || lengthFlag.hasDivisor) { float fval; if (lengthFlag.length == 2) { - // convert from (0.01*m)(2^e) format with sign, 12 bits mantissa (incl. sign), 4 bits exponent - fval = int16ToFloat(static_cast(value)); + // convert from (0.01*m)(2^e) format + fval = uint16ToFloat(static_cast(value)); } else if (lengthFlag.length == 4) { // convert from IEEE 754 bool negative = (value & (1u << 31)) != 0; diff --git a/src/lib/ebus/datatype.cpp b/src/lib/ebus/datatype.cpp index 6320f9447..160b01a9a 100755 --- a/src/lib/ebus/datatype.cpp +++ b/src/lib/ebus/datatype.cpp @@ -95,6 +95,34 @@ uint32_t floatToUint(float val) { #endif } +float uint16ToFloat(uint16_t val) { + if (val == 0) { + return 0; + } + if (val == 0x7fff) { + return static_cast(0xffffffff); // NaN + } + bool negative = val&0x8000; + int exp = (val>>11)&0xf; + int sig = val&0x7ff; + return static_cast((negative ? sig-0x800 : sig) * exp2(exp) * 0.01); +} + +uint16_t floatToUint16(float value) { + // (0.01*m)(2^e) format with sign, 12 bits mantissa (incl. sign), 4 bits exponent + if (value == 0) { + return 0; + } + bool negative = value < 0; + double val = round(value*(negative ? -100.0 : 100.0)); + int exp = ilogb(val)-10; + if (exp < -10 || exp > 15) { + return 0x7fff; // invalid value DPT 9 + } + auto shift = exp > 0 ? exp : 0; + auto sig = static_cast(val * exp2(-shift)); + return static_cast((shift << 11) | (negative ? 0x8000 | (0x800-sig) : sig)); +} bool DataType::dump(OutputFormat outputFormat, size_t length, bool appendDivisor, ostream* output) const { if (outputFormat & OF_JSON) { diff --git a/src/lib/ebus/datatype.h b/src/lib/ebus/datatype.h index 9dee87cde..e3bf9cba4 100755 --- a/src/lib/ebus/datatype.h +++ b/src/lib/ebus/datatype.h @@ -194,10 +194,26 @@ float uintToFloat(unsigned int value, bool negative); /** * Format a float value to the 32 bit representation (IEEE 754). - * @param val the float value. + * @param value the float value. * @return the 32 bit representation of the float value, or 0xffffffff if NaN. */ -uint32_t floatToUint(float val); +uint32_t floatToUint(float value); + +/** + * Parse a float value with precision of 2 decimal from 16 bit format with + * sign, 11 bit mantissa, 4 bit exponent as (0.01*m)(2^e). + * @param value the 16 bit representation of the float value. + * @return the float value. + */ +float uint16ToFloat(uint16_t value); + +/** + * Format a float value with precision of 2 decimal from 16 bit format with + * sign, 11 bit mantissa, 4 bit exponent as (0.01*m)(2^e). + * @param value the float value. + * @return the 16 bit representation of the float value, or 0xffff if NaN. + */ +uint16_t floatToUint16(float value); /** * Base class for all kinds of data types. From 68d05f37554b750b7957385b1f6419923a748998 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 24 Dec 2023 10:53:58 +0100 Subject: [PATCH 177/345] use nan for float replacement value --- src/lib/ebus/datatype.cpp | 6 ++-- src/lib/ebus/test/test_data.cpp | 52 +++++++++++++++++++++++++++++++-- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/src/lib/ebus/datatype.cpp b/src/lib/ebus/datatype.cpp index 160b01a9a..c301aab6b 100755 --- a/src/lib/ebus/datatype.cpp +++ b/src/lib/ebus/datatype.cpp @@ -100,7 +100,7 @@ float uint16ToFloat(uint16_t val) { return 0; } if (val == 0x7fff) { - return static_cast(0xffffffff); // NaN + return NAN; } bool negative = val&0x8000; int exp = (val>>11)&0xf; @@ -1275,9 +1275,9 @@ DataTypeList::DataTypeList() { // signed number (fraction 1/1000), -32.767 - +32.767, big endian add(new NumberDataType("FLR", 16, SIG|REV, 0x8000, 0x8001, 0x7fff, 1000)); // signed number (IEEE 754 binary32: 1 bit sign, 8 bits exponent, 23 bits significand), little endian - add(new NumberDataType("EXP", 32, SIG|EXP, 0x7f800000, 0xfeffffff, 0x7effffff, 1)); + add(new NumberDataType("EXP", 32, SIG|EXP, 0x7fc00000, 0xfeffffff, 0x7effffff, 1)); // signed number (IEEE 754 binary32: 1 bit sign, 8 bits exponent, 23 bits significand), big endian - add(new NumberDataType("EXR", 32, SIG|EXP|REV, 0x7f800000, 0xfeffffff, 0x7effffff, 1)); + add(new NumberDataType("EXR", 32, SIG|EXP|REV, 0x7fc00000, 0xfeffffff, 0x7effffff, 1)); // unsigned integer, 0 - 65534, little endian add(new NumberDataType("UIN", 16, 0, 0xffff, 0, 0xfffe, 1)); // unsigned integer, 0 - 65534, big endian diff --git a/src/lib/ebus/test/test_data.cpp b/src/lib/ebus/test/test_data.cpp index d4110ad88..44f7d8e0c 100755 --- a/src/lib/ebus/test/test_data.cpp +++ b/src/lib/ebus/test/test_data.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include #include "lib/ebus/data.h" @@ -25,6 +26,8 @@ using namespace ebusd; using std::cout; using std::endl; +using std::isnan; +using std::setprecision; static bool error = false; @@ -87,6 +90,12 @@ class TestReader : public MappedFileReader { const DataField* m_fields; }; +typedef struct floatCheck_t { + float encval; + float decval; + uint16_t ival; +} floatCheck_t; + int main() { // entry: definition, decoded value, master data, slave data, flags @@ -421,18 +430,17 @@ int main() { {"x,,exp", "-0.09", "10feffff04ec51b8bd", "00", ""}, {"x,,exp", "0.0", "10feffff0400000000", "00", ""}, {"x,,exp", "-0.001", "10feffff046f1283ba", "00", ""}, - {"x,,exp", "-", "10feffff040000807f", "00", ""}, + {"x,,exp", "-", "10feffff040000c07f", "00", ""}, {"x,,exp", "-32.767", "10feffff04681103c2", "00", ""}, {"x,,exp,1000", "-0.000090000", "10feffff04ec51b8bd", "00", ""}, {"x,,exp,-100", "-9", "10feffff04ec51b8bd", "00", ""}, {"x,,exp", "0.25", "10feffff040000803e", "00", ""}, - {"x,,exp", "-", "10feffff040000c07f", "00", "W"}, {"x,,exp", "0.95", "10feffff043333733f", "00", ""}, {"x,,exp", "0.65", "10feffff046666263f", "00", ""}, {"x,,exr", "-0.09", "10feffff04bdb851ec", "00", ""}, {"x,,exr", "0.0", "10feffff0400000000", "00", ""}, {"x,,exr", "-0.001", "10feffff04ba83126f", "00", ""}, - {"x,,exr", "-", "10feffff047f800000", "00", ""}, + {"x,,exr", "-", "10feffff047fc00000", "00", ""}, {"x,,exr", "-32.767", "10feffff04c2031168", "00", ""}, {"x,,exr,1000", "-0.000090000", "10feffff04bdb851ec", "00", ""}, {"x,,exr,-100", "-9", "10feffff04bdb851ec", "00", ""}, @@ -750,5 +758,43 @@ int main() { delete templates; + const floatCheck_t floatChecks[] = { + {0.0f, 0.0f, 0}, + {0.01f, 0.01f, 1}, + {-0.01f, -0.01f, 0x8000|(0x800-1)}, + {0.09f, 0.09f, 9}, + {-0.09f, -0.09f, 0x8000|(0x800-9)}, + {0.99f, 0.99f, 99}, + {-0.99f, -0.99f, 0x8000|(0x800-99)}, + {1.0f, 1.0f, 100}, + {-1.0f, -1.0f, 0x8000|(0x800-100)}, + {670433.25f, 670433.5f, 0x7ffe}, + {-670760.94f, -671088.62f, 0xf801}, + {NAN, NAN, 0x7fff}, + // some extra tests: + {-30.0f, -30.0f, 0x8A24}, + {-327.68f, -327.68f, 0xAC00}, + {46039.04f, 46039.04f, 0x6464}, + {-9461.76f, -9461.76f, 0xC8C8}, + }; + for (unsigned int i = 0; i < sizeof(floatChecks) / sizeof(floatChecks[0]); i++) { + floatCheck_t check = floatChecks[i]; + float value = uint16ToFloat(check.ival); + cout << " parse 0x" << hex << check.ival; + if (isnan(value) ? !isnan(check.encval) : value != check.encval) { + cout << " invalid: " << setprecision(8) << value << " instead of " << setprecision(8) << check.encval << endl; + error = true; + } else { + cout << ": OK" << endl; + } + uint32_t ivalue = floatToUint16(check.decval); + cout << " format " << setprecision(8) << check.decval; + if (ivalue != check.ival) { + cout << " invalid: " << ivalue << " instead of " << check.ival << endl; + error = true; + } else { + cout << ": OK" << endl; + } + } return error ? 1 : 0; } From 79c14a579c36060ca5068904e61ee1c45f72fc98 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 25 Dec 2023 14:34:56 +0100 Subject: [PATCH 178/345] fix escaping --- contrib/docker/update.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/docker/update.sh b/contrib/docker/update.sh index ce96a22be..4c237db53 100755 --- a/contrib/docker/update.sh +++ b/contrib/docker/update.sh @@ -34,7 +34,7 @@ replaceTemplate if [[ -n "$1" ]]; then # build releases update make='GIT_REVISION=\$GIT_REVISION ./make_all.sh' - upload_lines='ARG UPLOAD_URL\nARG UPLOAD_CREDENTIALS\nARG UPLOAD_OS\nRUN if [ -n "\$UPLOAD_URL" ] \&\& [ -n "\$UPLOAD_CREDENTIALS" ]; then for img in ebusd-*.deb; do echo -n "upload \$img: "; curl -fsSk -u "\$UPLOAD_CREDENTIALS" -X POST --data-binary "@\$img" -H "Content-Type: application/octet-stream" "\$UPLOAD_URL/\$img?a=\$EBUSD_ARCH\&o=\$UPLOAD_OS\&v=\$EBUSD_VERSION&b=$GIT_BRANCH" || echo "failed"; done; fi' + upload_lines='ARG UPLOAD_URL\nARG UPLOAD_CREDENTIALS\nARG UPLOAD_OS\nRUN if [ -n "\$UPLOAD_URL" ] \&\& [ -n "\$UPLOAD_CREDENTIALS" ]; then for img in ebusd-*.deb; do echo -n "upload \$img: "; curl -fsSk -u "\$UPLOAD_CREDENTIALS" -X POST --data-binary "@\$img" -H "Content-Type: application/octet-stream" "\$UPLOAD_URL/\$img?a=\$EBUSD_ARCH\&o=\$UPLOAD_OS\&v=\$EBUSD_VERSION\&b=$GIT_BRANCH" || echo "failed"; done; fi' upload_lines+='\n\n\nFROM scratch as deb\nCOPY --from=build /build/*.deb /' namesuffix='.build' replaceTemplate From 84433b37def122adacf92594aa2446e7835c5257 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 25 Dec 2023 15:26:27 +0100 Subject: [PATCH 179/345] add missing dependency for --with-knxd --- make_debian.sh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/make_debian.sh b/make_debian.sh index bd80a78b5..37f1871e0 100755 --- a/make_debian.sh +++ b/make_debian.sh @@ -66,12 +66,14 @@ if [ -n "$reusebuilddir" ] || [ -z "$keepbuilddir" ]; then fi make DESTDIR="$PWD/$RELEASE" install-strip || exit 1 extralibs= -mqtt= +ldd $RELEASE/usr/bin/ebusd | egrep -q libeibclient.so.0 +if [ $? -eq 0 ]; then + extralibs="$extralibs, knxd-tools" +fi ldd $RELEASE/usr/bin/ebusd | egrep -q libmosquitto.so.1 if [ $? -eq 0 ]; then - extralibs=', libmosquitto1' + extralibs="$extralibs, libmosquitto1" PACKAGE="${PACKAGE}_mqtt1" - mqtt=1 fi ldd $RELEASE/usr/bin/ebusd | egrep -q libssl.so.3 if [ $? -eq 0 ]; then From a9a2484374f615f26471c4ac731b76d96f82e935 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 25 Dec 2023 15:29:00 +0100 Subject: [PATCH 180/345] include knxd support in devel image (closes #1051) --- contrib/docker/Dockerfile | 6 +++--- contrib/docker/Dockerfile.template | 4 ++-- contrib/docker/update.sh | 6 ++++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/contrib/docker/Dockerfile b/contrib/docker/Dockerfile index a999bf357..842081add 100755 --- a/contrib/docker/Dockerfile +++ b/contrib/docker/Dockerfile @@ -3,7 +3,7 @@ ARG BASE_IMAGE FROM $BASE_IMAGE as build RUN apt-get update && apt-get install -y \ - libmosquitto-dev libssl-dev libstdc++6 libc6 libgcc1 \ + knxd-dev knxd- libmosquitto-dev libssl-dev libstdc++6 libc6 libgcc1 \ curl \ autoconf automake g++ make git \ && rm -rf /var/lib/apt/lists/* @@ -20,7 +20,7 @@ ENV EBUSD_VERSION $EBUSD_VERSION ENV GIT_REVISION $GIT_REVISION ADD . /build -RUN RUNTEST=full GIT_REVISION=$GIT_REVISION ./make_debian.sh +RUN RUNTEST=full GIT_REVISION=$GIT_REVISION ./make_debian.sh --with-knxd @@ -28,7 +28,7 @@ RUN RUNTEST=full GIT_REVISION=$GIT_REVISION ./make_debian.sh FROM $BASE_IMAGE-slim as image RUN apt-get update && apt-get install -y \ - libmosquitto1 libssl1.1 ca-certificates libstdc++6 libc6 libgcc1 \ + knxd-dev knxd- libmosquitto1 libssl1.1 ca-certificates libstdc++6 libc6 libgcc1 \ && rm -rf /var/lib/apt/lists/* LABEL maintainer="ebusd@ebusd.eu" diff --git a/contrib/docker/Dockerfile.template b/contrib/docker/Dockerfile.template index ae486783f..5f0a717f2 100644 --- a/contrib/docker/Dockerfile.template +++ b/contrib/docker/Dockerfile.template @@ -3,7 +3,7 @@ ARG BASE_IMAGE FROM $BASE_IMAGE as build RUN apt-get update && apt-get install -y \ - libmosquitto-dev libssl-dev libstdc++6 libc6 libgcc1 \ + %EBUSD_EXTRAPKGS%libmosquitto-dev libssl-dev libstdc++6 libc6 libgcc1 \ curl \ autoconf automake g++ make git \ && rm -rf /var/lib/apt/lists/* @@ -28,7 +28,7 @@ RUN %EBUSD_MAKE% FROM $BASE_IMAGE-slim as image RUN apt-get update && apt-get install -y \ - libmosquitto1 libssl1.1 ca-certificates libstdc++6 libc6 libgcc1 \ + %EBUSD_EXTRAPKGS%libmosquitto1 libssl1.1 ca-certificates libstdc++6 libc6 libgcc1 \ && rm -rf /var/lib/apt/lists/* LABEL maintainer="ebusd@ebusd.eu" diff --git a/contrib/docker/update.sh b/contrib/docker/update.sh index 4c237db53..529b47e6e 100755 --- a/contrib/docker/update.sh +++ b/contrib/docker/update.sh @@ -5,6 +5,7 @@ function replaceTemplate () { sed \ -e "s#%EBUSD_MAKE%#${make}#g" \ -e "s#%EBUSD_VERSION_VARIANT%#${version_variant}#g" \ + -e "s#%EBUSD_EXTRAPKGS%#${extrapkgs}#g" \ -e "s#%EBUSD_UPLOAD_LINES%#${upload_lines}#g" \ -e "s#%EBUSD_COPYDEB%#${copydeb}#g" \ -e "s#%EBUSD_DEBSRC%#${debsrc}#g" \ @@ -15,8 +16,8 @@ function replaceTemplate () { # devel update version_variant='-devel' -make='RUNTEST=full GIT_REVISION=\$GIT_REVISION ./make_debian.sh' -upload_lines='' +make='RUNTEST=full GIT_REVISION=\$GIT_REVISION ./make_debian.sh --with-knxd' +extrapkgs='knxd-dev knxd- ' copydeb='COPY --from=build /build/ebusd-*_mqtt1.deb ebusd.deb' debsrc='ebusd.deb \&\& rm -f ebusd.deb' copyentry='COPY --from=build /build/contrib/docker/docker-entrypoint.sh /' @@ -26,6 +27,7 @@ replaceTemplate # release update version_variant='' make='GIT_REVISION=\$GIT_REVISION ./make_debian.sh' +extrapkgs='' copydeb="ADD https://github.com/john30/ebusd/releases/download/\${EBUSD_VERSION}/ebusd-\${EBUSD_VERSION}_\${TARGETARCH}\${TARGETVARIANT}-\${EBUSD_IMAGE}_mqtt1.deb ebusd.deb" copyentry='COPY contrib/docker/docker-entrypoint.sh /' namesuffix='.release' From e59fbc460545cf3d98e88509a6b5990be1390501 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 25 Dec 2023 15:44:44 +0100 Subject: [PATCH 181/345] avoid using less portable basename() (fixes #1094) --- src/lib/utils/arg.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/utils/arg.cpp b/src/lib/utils/arg.cpp index fc8e4cdc4..957d62b11 100755 --- a/src/lib/utils/arg.cpp +++ b/src/lib/utils/arg.cpp @@ -362,7 +362,9 @@ void argHelp(const char* name, const argParseOpt *parseOpt) { } else if (indent < MIN_INDENT) { indent = MIN_INDENT; } - printf("Usage: %s [OPTION...]", basename(name)); + const char* basename = strrchr(name, '/'); + basename = basename ? basename+1 : name; + printf("Usage: %s [OPTION...]", basename); for (const argDef *arg = parseOpt->argDefs; arg && arg->help; arg++) { if (arg->name || !arg->valueName) { continue; From 97aa449be3e86f0a9f4955e4a6d1401241022889 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 25 Dec 2023 15:51:22 +0100 Subject: [PATCH 182/345] updated [skip ci] --- ChangeLog.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index b83166345..35ded87c4 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -11,6 +11,8 @@ * fix SYN generator timing * fix missing check for PB/SB validity * fix non-SSL build +* fix scan when no signal ++ fix negative float values in KNX integration ## Features * add temperatures in Kelvin and ... to Home Assistant MQTT discovery integration @@ -19,6 +21,7 @@ * add time fields to Home Assistant MQTT discovery integration * add templates endpoint to HTTP JSON * add reworked eBUS protocol engine that is especially useful for slow network issues +* add knxd support to devel docker image # 23.2 (2023-07-08) From 5ff16a46d706a24a424e92ee8cd56041c142da59 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 26 Dec 2023 08:06:32 +0100 Subject: [PATCH 183/345] add dependabot --- .github/dependabot.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..120c6893b --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" \ No newline at end of file From 642e57d725fb9390841c1d0994e162d7ab0dc869 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Dec 2023 07:07:02 +0000 Subject: [PATCH 184/345] Bump docker/setup-qemu-action from 2 to 3 Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 2 to 3. - [Release notes](https://github.com/docker/setup-qemu-action/releases) - [Commits](https://github.com/docker/setup-qemu-action/compare/v2...v3) --- updated-dependencies: - dependency-name: docker/setup-qemu-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yml | 2 +- .github/workflows/preparerelease.yml | 2 +- .github/workflows/release.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e7bd38e35..05a5e8364 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -36,7 +36,7 @@ jobs: uses: proudust/gh-describe@v1.4.6 - name: set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 - name: set up buildx id: buildx diff --git a/.github/workflows/preparerelease.yml b/.github/workflows/preparerelease.yml index aa6ad024f..14efbbb51 100644 --- a/.github/workflows/preparerelease.yml +++ b/.github/workflows/preparerelease.yml @@ -43,7 +43,7 @@ jobs: uses: proudust/gh-describe@v1.4.6 - name: set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 - name: set up buildx id: buildx diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 81b56fb13..745c4f8d2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ jobs: uses: actions/checkout@v3 - name: set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 - name: set up buildx id: buildx From e7b8e5ed9e3aa0a39719deef509ed4a294aebe4f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Dec 2023 07:07:05 +0000 Subject: [PATCH 185/345] Bump proudust/gh-describe from 1.4.6 to 1.6.0 Bumps [proudust/gh-describe](https://github.com/proudust/gh-describe) from 1.4.6 to 1.6.0. - [Release notes](https://github.com/proudust/gh-describe/releases) - [Commits](https://github.com/proudust/gh-describe/compare/v1.4.6...v1.6.0) --- updated-dependencies: - dependency-name: proudust/gh-describe dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yml | 2 +- .github/workflows/preparerelease.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e7bd38e35..8b3302f02 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,7 +33,7 @@ jobs: - name: gh-describe id: gittag - uses: proudust/gh-describe@v1.4.6 + uses: proudust/gh-describe@v1.6.0 - name: set up QEMU uses: docker/setup-qemu-action@v2 diff --git a/.github/workflows/preparerelease.yml b/.github/workflows/preparerelease.yml index aa6ad024f..fcd299850 100644 --- a/.github/workflows/preparerelease.yml +++ b/.github/workflows/preparerelease.yml @@ -40,7 +40,7 @@ jobs: - name: gh-describe id: gittag - uses: proudust/gh-describe@v1.4.6 + uses: proudust/gh-describe@v1.6.0 - name: set up QEMU uses: docker/setup-qemu-action@v2 From 0301bdea48f7483bfb04b6630a5b639d30b57e93 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Dec 2023 07:07:08 +0000 Subject: [PATCH 186/345] Bump actions/upload-artifact from 2 to 4 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 2 to 4. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v2...v4) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/preparerelease.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/preparerelease.yml b/.github/workflows/preparerelease.yml index aa6ad024f..48f9a0c22 100644 --- a/.github/workflows/preparerelease.yml +++ b/.github/workflows/preparerelease.yml @@ -61,7 +61,7 @@ jobs: LIMITIMG: ${{ github.event.inputs.limitimg }} - name: archive - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: debian packages path: | From 314b8f80dc58f4b3d55334e0cf8b3decceb24ec3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Dec 2023 07:07:11 +0000 Subject: [PATCH 187/345] Bump docker/login-action from 2 to 3 Bumps [docker/login-action](https://github.com/docker/login-action) from 2 to 3. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/v2...v3) --- updated-dependencies: - dependency-name: docker/login-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e7bd38e35..3cd0ee000 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -45,7 +45,7 @@ jobs: buildkitd-flags: --debug - name: login to docker hub - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 81b56fb13..7ac88c6a9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: buildkitd-flags: --debug - name: login to docker hub - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} From f6364d4d08f81cd2db3ad82ed62dad75ffc6fa55 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Dec 2023 07:07:14 +0000 Subject: [PATCH 188/345] Bump actions/checkout from 3 to 4 Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yml | 2 +- .github/workflows/coverage.yml | 2 +- .github/workflows/preparerelease.yml | 2 +- .github/workflows/release.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e7bd38e35..9cac1c691 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,7 +29,7 @@ jobs: steps: - name: checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: gh-describe id: gittag diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 932fbdfcc..cb671fb0c 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -15,7 +15,7 @@ jobs: steps: - name: checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: packages run: sudo apt-get update && sudo apt-get install -y libmosquitto1 libmosquitto-dev libssl1.1 libssl-dev diff --git a/.github/workflows/preparerelease.yml b/.github/workflows/preparerelease.yml index aa6ad024f..c7f86b564 100644 --- a/.github/workflows/preparerelease.yml +++ b/.github/workflows/preparerelease.yml @@ -36,7 +36,7 @@ jobs: steps: - name: checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: gh-describe id: gittag diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 81b56fb13..6e67a6547 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,7 +15,7 @@ jobs: steps: - name: checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: set up QEMU uses: docker/setup-qemu-action@v2 From 115ef29384296e97478bdf6f2a7fa8f2fa753f28 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 26 Dec 2023 08:28:23 +0100 Subject: [PATCH 189/345] better loop variable --- src/lib/ebus/device.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/ebus/device.cpp b/src/lib/ebus/device.cpp index 2e6ac88a8..0fd83b0bf 100755 --- a/src/lib/ebus/device.cpp +++ b/src/lib/ebus/device.cpp @@ -556,7 +556,7 @@ void EnhancedCharDevice::notifyInfoRetrieved() { case 0x0302: stream << (id == 1 ? "ID" : "config"); stream << hex << setfill('0'); - for (uint8_t pos = 0; pos < len; pos++) { + for (size_t pos = 0; pos < len; pos++) { stream << " " << setw(2) << static_cast(data[pos]); } if (id == 2 && (data[2]&0x3f) != 0x3f) { From 8fdab797ca472b1004215369c9de8d834becb9e0 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 26 Dec 2023 08:53:15 +0100 Subject: [PATCH 190/345] add CodeQL, add knxd dependancy [skip ci] --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index a35230453..1a031119a 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ ebusd is a daemon for handling communication with eBUS devices connected to a 2-wire bus system ("energy bus" used by numerous heating systems). [![Build](https://github.com/john30/ebusd/actions/workflows/build.yml/badge.svg)](https://github.com/john30/ebusd/actions/workflows/build.yml) +![CodeQL](https://github.com/john30/ebusd/workflows/CodeQL/badge.svg) [![codecov](https://codecov.io/gh/john30/ebusd/branch/master/graph/badge.svg)](https://codecov.io/gh/john30/ebusd) [![Release Downloads](https://img.shields.io/github/downloads/john30/ebusd/total)](https://github.com/john30/ebusd/releases/latest) [![Docker Downloads](https://img.shields.io/docker/pulls/john30/ebusd)](https://hub.docker.com/repository/docker/john30/ebusd) @@ -62,6 +63,7 @@ Building ebusd from the source requires the following packages and/or features: * make * kernel with pselect or ppoll support * glibc with getopt_long support + * optional: knxd-dev for knxd support (KNXnet/IP support is always included) * libmosquitto-dev for MQTT support * libssl-dev for SSL support From 8ac8e593837adb39364c9c01da804e271d74c7f6 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 26 Dec 2023 10:50:48 +0100 Subject: [PATCH 191/345] mark unused attributes --- src/lib/knx/knx.h | 10 ++++++++-- src/lib/utils/httpclient.cpp | 8 +++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/lib/knx/knx.h b/src/lib/knx/knx.h index 981cc6421..ea376fde0 100644 --- a/src/lib/knx/knx.h +++ b/src/lib/knx/knx.h @@ -22,6 +22,12 @@ #include #include +#if defined(__GNUC__) +#define MAYBE_UNUSED __attribute__((unused)) +#else +#define MAYBE_UNUSED +#endif + namespace ebusd { /** @file lib/knx/knx.h @@ -160,7 +166,7 @@ class KnxConnection { /** * @param address the individual address to set. */ - virtual void setAddress(knx_addr_t address) { + virtual void setAddress(MAYBE_UNUSED knx_addr_t address) { // default implementation does nothing } @@ -176,7 +182,7 @@ class KnxConnection { * Set the programming mode. * @param on true to start programming mode, false to stop it. */ - virtual void setProgrammingMode(bool on) { + virtual void setProgrammingMode(MAYBE_UNUSED bool on) { // default implementation does nothing } }; diff --git a/src/lib/utils/httpclient.cpp b/src/lib/utils/httpclient.cpp index 8a369c93d..48b9f6614 100755 --- a/src/lib/utils/httpclient.cpp +++ b/src/lib/utils/httpclient.cpp @@ -28,6 +28,12 @@ #endif // HAVE_SSL #include "lib/utils/log.h" +#if defined(__GNUC__) +#define MAYBE_UNUSED __attribute__((unused)) +#else +#define MAYBE_UNUSED +#endif + namespace ebusd { using std::string; @@ -171,7 +177,7 @@ void sslInfoCallback(const SSL *ssl, int type, int val) { (type & SSL_CB_ALERT) ? SSL_alert_desc_string_long(val) : "?"); } -int bioInfoCallback(BIO *bio, int state, int res) { +int bioInfoCallback(MAYBE_UNUSED BIO *bio, int state, int res) { logDebug(lf_network, "SSL BIO state %d res %d", state, From 1cdb714b276383f131b5219b4187da2adf191ab0 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 26 Dec 2023 10:51:21 +0100 Subject: [PATCH 192/345] less copying, remove unnecessary call --- src/lib/ebus/message.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib/ebus/message.cpp b/src/lib/ebus/message.cpp index 1c77a7fd0..75b2770f5 100644 --- a/src/lib/ebus/message.cpp +++ b/src/lib/ebus/message.cpp @@ -1694,7 +1694,7 @@ bool SimpleStringCondition::checkValue(const Message* message, const string& fie void SimpleStringCondition::dumpValuesJson(ostream* output) const { bool first = true; - for (const auto value : m_values) { + for (const auto& value : m_values) { if (!first) { *output << ","; } @@ -2806,7 +2806,6 @@ void MessageMap::clear() { m_loadedFileInfos.clear(); // clear poll messages while (!m_pollMessages.empty()) { - m_pollMessages.top(); m_pollMessages.pop(); } // free message instances by name From b320fb602717513f28fcd7f52281f41f8f0220fa Mon Sep 17 00:00:00 2001 From: John Date: Tue, 26 Dec 2023 12:00:57 +0100 Subject: [PATCH 193/345] fix helper script, add helper scripts for checking the build --- contrib/alpine/APKBUILD.git | 39 ++++++++++++++++++++++++++++++++++ contrib/alpine/build-git.sh | 3 +++ contrib/alpine/build.sh | 4 ++-- contrib/archlinux/build-git.sh | 3 +++ contrib/archlinux/build.sh | 3 +++ 5 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 contrib/alpine/APKBUILD.git create mode 100644 contrib/alpine/build-git.sh create mode 100644 contrib/archlinux/build-git.sh create mode 100644 contrib/archlinux/build.sh diff --git a/contrib/alpine/APKBUILD.git b/contrib/alpine/APKBUILD.git new file mode 100644 index 000000000..49b42a9fa --- /dev/null +++ b/contrib/alpine/APKBUILD.git @@ -0,0 +1,39 @@ +# Maintainer: John +pkgname=ebusd-git +_gitname=ebusd +pkgver=23.2 +pkgrel=0 +pkgdesc="Daemon for communication with eBUS heating systems" +url="https://github.com/john30/ebusd" +# Upstream only supports these archs. +arch="x86 x86_64 aarch64 armhf armv7" +license="GPL-3.0-only" +makedepends="cmake mosquitto-dev openssl-dev" +source="$pkgname-$pkgver.tar.gz::https://codeload.github.com/john30/${_gitname}/legacy.tar.gz/refs/heads/master" + +unpack() { + mkdir -p "$srcdir" + msg "Unpacking $s..." + tar -C "$srcdir" -zxf "$SRCDEST/$(filename_from_uri $source)" --strip-components=1 || return 1 +} + +build() { + cmake -B build \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DCMAKE_BUILD_TYPE=MinSizeRel \ + -DBUILD_TESTING=ON \ + "$srcdir" + cmake --build build +} + +check() { + ctest --output-on-failure --test-dir build +} + +package() { + DESTDIR="$pkgdir" cmake --install build +} + +sha512sums=" +REPLACED ${pkgname}-${pkgver}.tar.gz +" \ No newline at end of file diff --git a/contrib/alpine/build-git.sh b/contrib/alpine/build-git.sh new file mode 100644 index 000000000..bb09aaf76 --- /dev/null +++ b/contrib/alpine/build-git.sh @@ -0,0 +1,3 @@ +#!/bin/bash +# helper script to check the build from git master is fine +docker run -it --rm -v $PWD:/build -w /build/contrib/alpine alpine sh -c 'apk add --upgrade abuild && adduser -D test && addgroup test abuild && su test -c "abuild-keygen -a && mkdir git && cd git && cp ../APKBUILD.git APKBUILD && abuild -r checksum && abuild -r"' \ No newline at end of file diff --git a/contrib/alpine/build.sh b/contrib/alpine/build.sh index 9ddf0e31e..068b92e50 100644 --- a/contrib/alpine/build.sh +++ b/contrib/alpine/build.sh @@ -1,3 +1,3 @@ #!/bin/bash -# helper script to check the build is fine -docker run -it --rm -v $PWD:/build -w /build/contrib/alpine alpine sh -c 'apk add --upgrade abuild && adduser -D test && addgroup test abuild && su test -c "abuild-keygen && abuild -r"' \ No newline at end of file +# helper script to check the release build is fine +docker run -it --rm -v $PWD:/build -w /build/contrib/alpine alpine sh -c 'apk add --upgrade abuild && adduser -D test && addgroup test abuild && su test -c "abuild-keygen -a && abuild -r"' \ No newline at end of file diff --git a/contrib/archlinux/build-git.sh b/contrib/archlinux/build-git.sh new file mode 100644 index 000000000..7c9aac705 --- /dev/null +++ b/contrib/archlinux/build-git.sh @@ -0,0 +1,3 @@ +#!/bin/bash +# helper script to check the build from git master is fine +docker run -it --rm -v $PWD:/build -w /build/contrib/archlinux archlinux sh -c 'pacman -Sy && pacman -Sq fakeroot binutils mosquitto autoconf automake make gcc git && useradd test && su test -c "makepkg -p PKGBUILD.git"' \ No newline at end of file diff --git a/contrib/archlinux/build.sh b/contrib/archlinux/build.sh new file mode 100644 index 000000000..50bf85e34 --- /dev/null +++ b/contrib/archlinux/build.sh @@ -0,0 +1,3 @@ +#!/bin/bash +# helper script to check the release build is fine +docker run -it --rm -v $PWD:/build -w /build/contrib/archlinux archlinux sh -c 'pacman -Sy && pacman -Sq fakeroot binutils mosquitto autoconf automake make gcc && useradd test && su test -c "makepkg"' \ No newline at end of file From 44c59aed5ad105b8d9a3c327a94b628688c8e355 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 26 Dec 2023 12:40:25 +0100 Subject: [PATCH 194/345] update buildx action --- .github/workflows/build.yml | 2 +- .github/workflows/preparerelease.yml | 2 +- .github/workflows/release.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a9980500c..ad4f539ca 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -40,7 +40,7 @@ jobs: - name: set up buildx id: buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 with: buildkitd-flags: --debug - diff --git a/.github/workflows/preparerelease.yml b/.github/workflows/preparerelease.yml index 8c777e04c..d2e75f782 100644 --- a/.github/workflows/preparerelease.yml +++ b/.github/workflows/preparerelease.yml @@ -47,7 +47,7 @@ jobs: - name: set up buildx id: buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 with: buildkitd-flags: --debug - diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a7b92eac5..86d1ddeab 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,7 +22,7 @@ jobs: - name: set up buildx id: buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 with: buildkitd-flags: --debug - From 6630a81f33ad8f18a332f6a6bbecb0bc0852e025 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 26 Dec 2023 13:16:53 +0100 Subject: [PATCH 195/345] updated to optional voltage levels, rephrased --- docs/enhanced_proto.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/enhanced_proto.md b/docs/enhanced_proto.md index 9023f4e1c..ea157a73f 100644 --- a/docs/enhanced_proto.md +++ b/docs/enhanced_proto.md @@ -1,6 +1,6 @@ ## Transfer speed -In order to compensate potential overhead of transfer encoding, the transfer speed is set to 9600 Baud or 115200 Baud +In order to compensate potential overhead of transfer encoding, the transfer speed is set to 115200 Baud (or 9600 Baud) with 8 bits, no parity, and 1 stop bit. @@ -73,7 +73,7 @@ first second The data byte in `d` contains the error message. * host communication error ` ` - Indicates an error in the host UART. + Indicates an error in the host receiver/transmitter. The data byte in `d` contains the error message. @@ -106,7 +106,7 @@ These are the predefined symbols as used above. ### Feature bits (both directions) * bit 7-2: tbd * // planned: bit 1: full message sending (complete sequence instead of single bytes) - * bit 0: additional infos (version, PIC ID, etc.) + * bit 0: additional infos (version, HW ID, etc.) ### Information IDs (both directions) The first level below is the `info_id` value and the second level describes the response data byte sequence. @@ -119,22 +119,22 @@ The first byte transferred in response is always the number of data bytes to be * `jumpers`: jumper settings (0x01=enhanced, 0x02=high speed, 0x04=Ethernet, 0x08=WIFI, 0x10=v3.1, 0x20=ignore hard jumpers) * `bootloader_version`: bootloader version (since 20220831) * `bootloader_checksum_H` `bootloader_checksum_L`: bootloader checksum - * 0x01: PIC ID + * 0x01: HW ID * `length`: =9 - * 9*`mui`: PIC MUI - * 0x02: PIC config + * 9*`hwid`: hardware identifier + * 0x02: HW config * `length`: =8 - * 8*`config_H` `config_L`: PIC config - * 0x03: PIC temperature + * `config_H` `config_L`: hardware config (chip specific) + * 0x03: HW temperature * `length`: =2 - * `temp_H` `temp_L`: temperature in degrees Celsius - * 0x04: PIC supply voltage + * `temp_H` `temp_L`: hardware temperature in degrees Celsius + * 0x04: HW supply voltage * `length`: =2 - * `millivolt_H` `millivolt_L`: voltage value in mV + * `millivolt_H` `millivolt_L`: supply voltage in mV, or 0 if unknown * 0x05: bus voltage * `length`: =2 - * `voltage_max`: maximum bus voltage in 10th volts - * `voltage_min`: minimum bus voltage in 10th volts + * `voltage_max`: maximum eBUS voltage in 10th volts, or 0 if unknown + * `voltage_min`: minimum eBUS voltage in 10th volts, or 0 if unknown * 0x06: reset info (since 20220831) * `length`: =2 * `reset_cause`: reset cause (1=power-on, 2=brown-out, 3=watchdog, 4=clear, 5=reset, 6=stack, 7=memory) From 442ff4ece86dd6a460c21c00fecec7a58631b876 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 26 Dec 2023 15:18:16 +0100 Subject: [PATCH 196/345] add rssi info, reset stored enhanced infos when device was reset --- docs/enhanced_proto.md | 3 +++ src/lib/ebus/device.cpp | 20 ++++++++++++++++++++ src/lib/ebus/device.h | 5 ++++- 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/docs/enhanced_proto.md b/docs/enhanced_proto.md index ea157a73f..2869bbcbf 100644 --- a/docs/enhanced_proto.md +++ b/docs/enhanced_proto.md @@ -139,3 +139,6 @@ The first byte transferred in response is always the number of data bytes to be * `length`: =2 * `reset_cause`: reset cause (1=power-on, 2=brown-out, 3=watchdog, 4=clear, 5=reset, 6=stack, 7=memory) * `restart_count`: restart count (within same power cycle) + * 0x07: WIFI status (since 20231226) + * `length`: =2 + * `rssi`: signal strength in dBm (rssi, usually negative), 0 if unknown diff --git a/src/lib/ebus/device.cpp b/src/lib/ebus/device.cpp index 0fd83b0bf..55494bd15 100755 --- a/src/lib/ebus/device.cpp +++ b/src/lib/ebus/device.cpp @@ -266,6 +266,12 @@ string EnhancedCharDevice::getEnhancedInfos() { if (res != RESULT_OK) { fails += ", cannot request bus voltage"; } + if (m_enhInfoIsWifi) { + res = requestEnhancedInfo(7); + if (res != RESULT_OK) { + fails += ", cannot request rssi"; + } + } res = requestEnhancedInfo(0xff); // wait for completion if (res != RESULT_OK) { m_enhInfoBusVoltage = "bus voltage unknown"; @@ -360,6 +366,11 @@ result_t EnhancedCharDevice::notifyTransportStatus(bool opened) { // reset state m_extraFatures = 0; m_infoLen = 0; + m_enhInfoVersion = ""; + m_enhInfoIsWifi = false; + m_enhInfoTemperature = ""; + m_enhInfoSupplyVoltage = ""; + m_enhInfoBusVoltage = ""; m_arbitrationMaster = SYN; m_arbitrationCheck = 0; } @@ -548,6 +559,7 @@ void EnhancedCharDevice::notifyInfoRetrieved() { stream << "firmware " << m_enhInfoVersion; if (len >= 5) { stream << ", jumpers 0x" << setw(2) << static_cast(data[4]); + m_enhInfoIsWifi = (data[4]&0x08)!=0; } stream << setfill(' '); // reset break; @@ -610,6 +622,14 @@ void EnhancedCharDevice::notifyInfoRetrieved() { stream << "unknown"; } break; + case 0x0107: + stream << "rssi "; + if (data[0]) { + stream << static_cast(((int8_t*)data)[0]) << " dBm"; + } else { + stream << "unknown"; + } + break; default: stream << "unknown 0x" << hex << setfill('0') << setw(2) << static_cast(id) << ", len " << dec << setw(0) diff --git a/src/lib/ebus/device.h b/src/lib/ebus/device.h index 9e67ca908..d4224017e 100755 --- a/src/lib/ebus/device.h +++ b/src/lib/ebus/device.h @@ -252,7 +252,7 @@ class EnhancedCharDevice : public CharDevice { */ explicit EnhancedCharDevice(Transport* transport) : CharDevice(transport), m_resetRequested(false), - m_extraFatures(0), m_infoReqTime(0), m_infoLen(0), m_infoPos(0) { + m_extraFatures(0), m_infoReqTime(0), m_infoLen(0), m_infoPos(0), m_enhInfoIsWifi(false) { } // @copydoc @@ -340,6 +340,9 @@ class EnhancedCharDevice : public CharDevice { /** a string describing the enhanced device version. */ string m_enhInfoVersion; + /** whether the device is known to be connected via WIFI. */ + bool m_enhInfoIsWifi; + /** a string describing the enhanced device temperature. */ string m_enhInfoTemperature; From 62db6ec1f07e2075d979c12957cf73fdba210411 Mon Sep 17 00:00:00 2001 From: john30 Date: Tue, 26 Dec 2023 15:25:46 +0100 Subject: [PATCH 197/345] updated to 23.3 --- .github/ISSUE_TEMPLATE/bug_report.yml | 1 + ChangeLog.md | 2 +- VERSION | 2 +- contrib/alpine/APKBUILD | 2 +- contrib/archlinux/PKGBUILD | 2 +- contrib/archlinux/PKGBUILD.git | 2 +- 6 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 92467ee04..c72e0b320 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,7 @@ body: description: the ebusd version in use options: - current source from git + - '23.3' - '23.2' - '23.1' - '22.4' diff --git a/ChangeLog.md b/ChangeLog.md index 35ded87c4..21f339ba0 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,4 +1,4 @@ -# next (tbd) +# 23.3 (2023-12-26) ## Bug Fixes * fix MQTT topic string validation * fix lost scanconfig default behaviour diff --git a/VERSION b/VERSION index ce417ddac..99730abcf 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -23.2 \ No newline at end of file +23.3 \ No newline at end of file diff --git a/contrib/alpine/APKBUILD b/contrib/alpine/APKBUILD index 63a249863..d81244f59 100644 --- a/contrib/alpine/APKBUILD +++ b/contrib/alpine/APKBUILD @@ -1,7 +1,7 @@ # Contributor: Tim # Maintainer: Tim pkgname=ebusd -pkgver=23.2 +pkgver=23.3 pkgrel=0 pkgdesc="Daemon for communication with eBUS heating systems" url="https://github.com/john30/ebusd" diff --git a/contrib/archlinux/PKGBUILD b/contrib/archlinux/PKGBUILD index bbe05ba21..f0300f35a 100644 --- a/contrib/archlinux/PKGBUILD +++ b/contrib/archlinux/PKGBUILD @@ -2,7 +2,7 @@ # Contributor: Milan Knizek # Usage: makepkg pkgname=ebusd -pkgver=23.2 +pkgver=23.3 pkgrel=1 pkgdesc="ebusd, the daemon for communication with eBUS heating systems." arch=('i686' 'x86_64' 'armv6h' 'armv7h' 'aarch64') diff --git a/contrib/archlinux/PKGBUILD.git b/contrib/archlinux/PKGBUILD.git index 8ad5b6bf3..131483d98 100644 --- a/contrib/archlinux/PKGBUILD.git +++ b/contrib/archlinux/PKGBUILD.git @@ -3,7 +3,7 @@ # Usage: makepkg -p PKGBUILD.git pkgname=ebusd-git _gitname=ebusd -pkgver=23.2 +pkgver=23.3 pkgrel=1 pkgdesc="ebusd, the daemon for communication with eBUS heating systems." arch=('i686' 'x86_64' 'armv6h' 'armv7h' 'aarch64') From a5e315c9b0494241cad5b898c4aa5ed7def9b0db Mon Sep 17 00:00:00 2001 From: john30 Date: Tue, 26 Dec 2023 17:13:26 +0100 Subject: [PATCH 198/345] updated to 23.3 --- contrib/alpine/APKBUILD | 2 +- contrib/alpine/APKBUILD.git | 2 +- contrib/archlinux/PKGBUILD | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/alpine/APKBUILD b/contrib/alpine/APKBUILD index d81244f59..e20f364f0 100644 --- a/contrib/alpine/APKBUILD +++ b/contrib/alpine/APKBUILD @@ -28,5 +28,5 @@ package() { } sha512sums=" -ac19a39f8ddc00792bde4891020022cad46597da54bec71d56a954239b01dd8e8ea79fcb4cce130fc991beb2f4bff01ae7abd95150fcf9e93d65d7b5b93482ce ebusd-23.2.tar.gz +acf155d36e99db1c9c2ff04eabbfddba8493f2566b6691f609c86af0e42e3cb0594618fd51e874e475cfc7b9c742d1e010099f38e19c21f52c953ebcfb0f2ea2 ebusd-23.3.tar.gz " diff --git a/contrib/alpine/APKBUILD.git b/contrib/alpine/APKBUILD.git index 49b42a9fa..58f21f83e 100644 --- a/contrib/alpine/APKBUILD.git +++ b/contrib/alpine/APKBUILD.git @@ -1,7 +1,7 @@ # Maintainer: John pkgname=ebusd-git _gitname=ebusd -pkgver=23.2 +pkgver=23.3 pkgrel=0 pkgdesc="Daemon for communication with eBUS heating systems" url="https://github.com/john30/ebusd" diff --git a/contrib/archlinux/PKGBUILD b/contrib/archlinux/PKGBUILD index f0300f35a..857a30b43 100644 --- a/contrib/archlinux/PKGBUILD +++ b/contrib/archlinux/PKGBUILD @@ -41,4 +41,4 @@ package() { install -m 0644 contrib/etc/ebusd/mqtt-integration.cfg "${pkgdir}/etc/ebusd/mqtt-integration.cfg" } # update md5sums: updpkgsums -md5sums=('2ab72843463faccd56975e4bc981eab4') +md5sums=('4702250aff5be67ac38ba81e1acae231') From 303ea9627dd425c52dbb9f6ba8a7ded6b5046f37 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 30 Dec 2023 17:34:23 +0100 Subject: [PATCH 199/345] align with upcoming upstream, switch to edge for newest gcc as used in alpine ci --- contrib/alpine/APKBUILD | 7 +++---- contrib/alpine/APKBUILD.git | 8 +++----- contrib/alpine/build-git.sh | 2 +- contrib/alpine/build.sh | 2 +- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/contrib/alpine/APKBUILD b/contrib/alpine/APKBUILD index e20f364f0..bb1acb881 100644 --- a/contrib/alpine/APKBUILD +++ b/contrib/alpine/APKBUILD @@ -1,5 +1,5 @@ # Contributor: Tim -# Maintainer: Tim +# Maintainer: John pkgname=ebusd pkgver=23.3 pkgrel=0 @@ -8,12 +8,11 @@ url="https://github.com/john30/ebusd" # Upstream only supports these archs. arch="x86 x86_64 aarch64 armhf armv7" license="GPL-3.0-only" -makedepends="cmake mosquitto-dev openssl-dev" +makedepends="cmake mosquitto-dev openssl-dev samurai" source="$pkgname-$pkgver.tar.gz::https://github.com/john30/${pkgname}/archive/refs/tags/${pkgver}.tar.gz" build() { - cmake -B build \ - -DCMAKE_INSTALL_PREFIX=/usr \ + cmake -B build -G Ninja \ -DCMAKE_BUILD_TYPE=MinSizeRel \ -DBUILD_TESTING=ON cmake --build build diff --git a/contrib/alpine/APKBUILD.git b/contrib/alpine/APKBUILD.git index 58f21f83e..48711351b 100644 --- a/contrib/alpine/APKBUILD.git +++ b/contrib/alpine/APKBUILD.git @@ -1,6 +1,5 @@ # Maintainer: John pkgname=ebusd-git -_gitname=ebusd pkgver=23.3 pkgrel=0 pkgdesc="Daemon for communication with eBUS heating systems" @@ -8,7 +7,7 @@ url="https://github.com/john30/ebusd" # Upstream only supports these archs. arch="x86 x86_64 aarch64 armhf armv7" license="GPL-3.0-only" -makedepends="cmake mosquitto-dev openssl-dev" +makedepends="cmake mosquitto-dev openssl-dev samurai" source="$pkgname-$pkgver.tar.gz::https://codeload.github.com/john30/${_gitname}/legacy.tar.gz/refs/heads/master" unpack() { @@ -18,11 +17,10 @@ unpack() { } build() { - cmake -B build \ - -DCMAKE_INSTALL_PREFIX=/usr \ + cmake -B build -G Ninja \ -DCMAKE_BUILD_TYPE=MinSizeRel \ -DBUILD_TESTING=ON \ - "$srcdir" + "$srcdir" cmake --build build } diff --git a/contrib/alpine/build-git.sh b/contrib/alpine/build-git.sh index bb09aaf76..ade6166a6 100644 --- a/contrib/alpine/build-git.sh +++ b/contrib/alpine/build-git.sh @@ -1,3 +1,3 @@ #!/bin/bash # helper script to check the build from git master is fine -docker run -it --rm -v $PWD:/build -w /build/contrib/alpine alpine sh -c 'apk add --upgrade abuild && adduser -D test && addgroup test abuild && su test -c "abuild-keygen -a && mkdir git && cd git && cp ../APKBUILD.git APKBUILD && abuild -r checksum && abuild -r"' \ No newline at end of file +docker run -it --rm -v $PWD:/build -w /build/contrib/alpine alpine:edge sh -c 'apk add --upgrade abuild build-base && adduser -D test && addgroup test abuild && su test -c "abuild-keygen -a && mkdir git && cd git && cp ../APKBUILD.git APKBUILD && abuild -r checksum && abuild -r"' \ No newline at end of file diff --git a/contrib/alpine/build.sh b/contrib/alpine/build.sh index 068b92e50..83e374155 100644 --- a/contrib/alpine/build.sh +++ b/contrib/alpine/build.sh @@ -1,3 +1,3 @@ #!/bin/bash # helper script to check the release build is fine -docker run -it --rm -v $PWD:/build -w /build/contrib/alpine alpine sh -c 'apk add --upgrade abuild && adduser -D test && addgroup test abuild && su test -c "abuild-keygen -a && abuild -r"' \ No newline at end of file +docker run -it --rm -v $PWD:/build -w /build/contrib/alpine alpine:edge sh -c 'apk add --upgrade abuild build-base && adduser -D test && addgroup test abuild && su test -c "abuild-keygen -a && abuild -r"' \ No newline at end of file From 3384f3780087bd6b94d46bf18cdad18201ad516c Mon Sep 17 00:00:00 2001 From: John Date: Sat, 30 Dec 2023 17:38:19 +0100 Subject: [PATCH 200/345] add missed includes as reported by gcc13 --- src/ebusd/mqttclient.h | 1 + src/ebusd/mqttclient_mosquitto.h | 1 + src/lib/ebus/device.h | 1 + src/lib/ebus/transport.h | 1 + 4 files changed, 4 insertions(+) diff --git a/src/ebusd/mqttclient.h b/src/ebusd/mqttclient.h index 5309d31a1..965647ba6 100755 --- a/src/ebusd/mqttclient.h +++ b/src/ebusd/mqttclient.h @@ -19,6 +19,7 @@ #ifndef EBUSD_MQTTCLIENT_H_ #define EBUSD_MQTTCLIENT_H_ +#include #include #include #include diff --git a/src/ebusd/mqttclient_mosquitto.h b/src/ebusd/mqttclient_mosquitto.h index d749b60b4..e08ca5eba 100755 --- a/src/ebusd/mqttclient_mosquitto.h +++ b/src/ebusd/mqttclient_mosquitto.h @@ -21,6 +21,7 @@ #include "ebusd/mqttclient.h" #include +#include #include #include #include diff --git a/src/lib/ebus/device.h b/src/lib/ebus/device.h index d4224017e..8da116a76 100755 --- a/src/lib/ebus/device.h +++ b/src/lib/ebus/device.h @@ -19,6 +19,7 @@ #ifndef LIB_EBUS_DEVICE_H_ #define LIB_EBUS_DEVICE_H_ +#include #include #include "lib/ebus/result.h" #include "lib/ebus/transport.h" diff --git a/src/lib/ebus/transport.h b/src/lib/ebus/transport.h index ec4c2319f..ba3a83243 100755 --- a/src/lib/ebus/transport.h +++ b/src/lib/ebus/transport.h @@ -21,6 +21,7 @@ #include #include +#include #include #include "lib/ebus/result.h" #include "lib/ebus/symbol.h" From 412f65d617a342e3df02fcd908645f30790d03f5 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 30 Dec 2023 17:46:09 +0100 Subject: [PATCH 201/345] fix previous commit --- contrib/alpine/APKBUILD | 2 +- contrib/alpine/APKBUILD.git | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/alpine/APKBUILD b/contrib/alpine/APKBUILD index bb1acb881..38b93cc59 100644 --- a/contrib/alpine/APKBUILD +++ b/contrib/alpine/APKBUILD @@ -9,7 +9,7 @@ url="https://github.com/john30/ebusd" arch="x86 x86_64 aarch64 armhf armv7" license="GPL-3.0-only" makedepends="cmake mosquitto-dev openssl-dev samurai" -source="$pkgname-$pkgver.tar.gz::https://github.com/john30/${pkgname}/archive/refs/tags/${pkgver}.tar.gz" +source="$pkgname-$pkgver.tar.gz::https://github.com/john30/ebusd/archive/refs/tags/$pkgver.tar.gz" build() { cmake -B build -G Ninja \ diff --git a/contrib/alpine/APKBUILD.git b/contrib/alpine/APKBUILD.git index 48711351b..45a020a71 100644 --- a/contrib/alpine/APKBUILD.git +++ b/contrib/alpine/APKBUILD.git @@ -8,7 +8,7 @@ url="https://github.com/john30/ebusd" arch="x86 x86_64 aarch64 armhf armv7" license="GPL-3.0-only" makedepends="cmake mosquitto-dev openssl-dev samurai" -source="$pkgname-$pkgver.tar.gz::https://codeload.github.com/john30/${_gitname}/legacy.tar.gz/refs/heads/master" +source="$pkgname-$pkgver.tar.gz::https://codeload.github.com/john30/ebusd/legacy.tar.gz/refs/heads/master" unpack() { mkdir -p "$srcdir" From 47d2a3aa00159c3007bbb72d0bf733a271db0cb5 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 31 Dec 2023 10:54:05 +0100 Subject: [PATCH 202/345] remove no longer maintained ebusfeed (closes #1111) --- configure.ac | 2 -- src/tools/CMakeLists.txt | 6 ------ src/tools/Makefile.am | 7 ------- 3 files changed, 15 deletions(-) diff --git a/configure.ac b/configure.ac index ecfc6b0ab..90368e046 100755 --- a/configure.ac +++ b/configure.ac @@ -47,8 +47,6 @@ AC_ARG_WITH(contrib, AS_HELP_STRING([--without-contrib], [disable inclusion of c if test "x$with_contrib" != "xno"; then AC_DEFINE_UNQUOTED(HAVE_CONTRIB, [1], [Defined if contributed sources are enabled.]) fi -AC_ARG_WITH(ebusfeed, AS_HELP_STRING([--with-ebusfeed], [enable inclusion of ebusfeed tool]), [with_ebusfeed=yes], []) -AM_CONDITIONAL([WITH_EBUSFEED], [test "x$with_ebusfeed" == "xyes"]) AC_ARG_WITH(mqtt, AS_HELP_STRING([--without-mqtt], [disable support for MQTT handling]), [], [with_mqtt=yes]) if test "x$with_mqtt" != "xno"; then AC_CHECK_LIB([mosquitto], [mosquitto_lib_init], diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index e35b00dbf..38ea734ac 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -12,10 +12,4 @@ add_executable(ebuspicloader ${ebuspicloader_SOURCES}) target_link_libraries(ebusctl utils ebus ${ebusctl_LIBS}) target_link_libraries(ebuspicloader utils ${ebuspicloader_LIBS}) -if(WITH_EBUSFEED) - set(ebusfeed_SOURCES ebusfeed.cpp) - add_executable(ebusfeed ${ebusfeed_SOURCES}) - target_link_libraries(ebusfeed utils ebus ${ebusfeed_LIBS}) -endif(WITH_EBUSFEED) - install(TARGETS ebusctl ebuspicloader EXPORT ebusd DESTINATION usr/bin) diff --git a/src/tools/Makefile.am b/src/tools/Makefile.am index f9388e091..37c1058d2 100644 --- a/src/tools/Makefile.am +++ b/src/tools/Makefile.am @@ -11,13 +11,6 @@ ebusctl_LDADD = ../lib/utils/libutils.a ebuspicloader_SOURCES = ebuspicloader.cpp intelhex/intelhexclass.cpp ebuspicloader_LDADD = ../lib/utils/libutils.a -if WITH_EBUSFEED -bin_PROGRAMS += ebusfeed -ebusfeed_SOURCES = ebusfeed.cpp -ebusfeed_LDADD = ../lib/utils/libutils.a \ - ../lib/ebus/libebus.a -endif - distclean-local: -rm -f Makefile.in -rm -rf .libs From 6c2d102544f45856fb14484a3fc763822ff03376 Mon Sep 17 00:00:00 2001 From: Conrad Kostecki Date: Sat, 13 Jan 2024 21:33:28 +0100 Subject: [PATCH 203/345] configure.ac: quote CXXFLAGS CXXCLAGS needs to be quote, as otherwise the test could fail on some CXXFLAGS like '-mfpmath=sse,387' test: syntax error: `-mfpmath=sse,387' unexpected Signed-off-by: Conrad Kostecki --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 90368e046..0469eb207 100755 --- a/configure.ac +++ b/configure.ac @@ -8,7 +8,7 @@ AC_CONFIG_AUX_DIR([build]) AC_CONFIG_MACRO_DIR([m4]) AC_GNU_SOURCE -if test -z $CXXFLAGS; then +if test -z "${CXXFLAGS}"; then CXXFLAGS="-fpic -Wall -Wno-unused-function -Wextra -g -O2" fi AC_PROG_CXX([g++-6 g++-5 g++-4.9 g++-4.8 g++]) From fcb8066624fdf2cda246c255cec4a201b8c948ce Mon Sep 17 00:00:00 2001 From: Conrad Kostecki Date: Sat, 13 Jan 2024 21:44:48 +0100 Subject: [PATCH 204/345] CMakeLists.txt: add BUILD_SHARED_LIBS=OFF This disabled shared libs, as otherwise the compilation could fail. The installed binary files are still dynamically linked, as this affects only internal linking here. Closes: https://github.com/john30/ebusd/issues/1132 Signed-off-by: Conrad Kostecki --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index b787d40b7..cb532e877 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,6 +53,8 @@ endif() add_definitions(-fpic -Wall -Wno-unused-function -Wextra) +set(BUILD_SHARED_LIBS OFF) + check_include_file(arpa/inet.h HAVE_ARPA_INET_H) check_include_file(dirent.h HAVE_DIRENT_H) check_include_file(fcntl.h HAVE_FCNTL_H) From 80792ff12a147a572981b5b4ceed7074a09143e3 Mon Sep 17 00:00:00 2001 From: Conrad Kostecki Date: Sat, 13 Jan 2024 21:59:58 +0100 Subject: [PATCH 205/345] src/{ebusd,tools}/CMakeLists.txt: remove usr path /usr should not be hardcoded, as otherwise this will cause a double prefix during installation. The correct prefix is already being handled by CMAKE_INSTALL_PREFIX. Example: >>> /usr/usr/bin/ebuspicloader >>> /usr/usr/bin/ebusctl >>> /usr/usr/bin/ebusd Signed-off-by: Conrad Kostecki --- src/ebusd/CMakeLists.txt | 2 +- src/tools/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ebusd/CMakeLists.txt b/src/ebusd/CMakeLists.txt index ae34c07ff..e8e56e47e 100644 --- a/src/ebusd/CMakeLists.txt +++ b/src/ebusd/CMakeLists.txt @@ -36,5 +36,5 @@ include_directories(../lib/utils) add_executable(ebusd ${ebusd_SOURCES}) target_link_libraries(ebusd utils ebus pthread rt ${ebusd_LIBS}) -install(TARGETS ebusd EXPORT ebusd DESTINATION usr/bin) +install(TARGETS ebusd EXPORT ebusd DESTINATION bin) diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index 38ea734ac..2537ad372 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -12,4 +12,4 @@ add_executable(ebuspicloader ${ebuspicloader_SOURCES}) target_link_libraries(ebusctl utils ebus ${ebusctl_LIBS}) target_link_libraries(ebuspicloader utils ${ebuspicloader_LIBS}) -install(TARGETS ebusctl ebuspicloader EXPORT ebusd DESTINATION usr/bin) +install(TARGETS ebusctl ebuspicloader EXPORT ebusd DESTINATION bin) From 1fbf13f5c943142aed6bf01edd20d4645bc70e6a Mon Sep 17 00:00:00 2001 From: Conrad Kostecki Date: Sat, 13 Jan 2024 22:18:41 +0100 Subject: [PATCH 206/345] configure.ac: guard docs behind an option Currently, docs are automatically build, if doxygen is being found. This should be an option, as in Gentoo, we want to control this via use flag, if someone wants to have docs, even, if doxygen is installed. Default is still being yes, so it can be optionally disbled. Signed-off-by: Conrad Kostecki --- configure.ac | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/configure.ac b/configure.ac index 90368e046..d6e84b5c0 100755 --- a/configure.ac +++ b/configure.ac @@ -47,6 +47,13 @@ AC_ARG_WITH(contrib, AS_HELP_STRING([--without-contrib], [disable inclusion of c if test "x$with_contrib" != "xno"; then AC_DEFINE_UNQUOTED(HAVE_CONTRIB, [1], [Defined if contributed sources are enabled.]) fi +AC_ARG_WITH(docs, AS_HELP_STRING([--without-docs], [disable generation of docs]), [], [with_docs=yes]) +if test "x$with_docs" != "xno"; then + AC_CHECK_PROGS([HAVE_DOXYGEN], [doxygen], []) + if test -z "${HAVE_DOXYGEN}"; then + AC_MSG_WARN([Doxygen not found - continuing without Doxygen support.]) + fi +fi AC_ARG_WITH(mqtt, AS_HELP_STRING([--without-mqtt], [disable support for MQTT handling]), [], [with_mqtt=yes]) if test "x$with_mqtt" != "xno"; then AC_CHECK_LIB([mosquitto], [mosquitto_lib_init], @@ -154,12 +161,8 @@ if test -n "$GIT_REVISION"; then else AC_DEFINE(REVISION, "[m4_esyscmd_s([git describe --always 2>/dev/null || (date +p%Y%m%d)])]", [The revision of the package.]) fi -AC_CHECK_PROGS([HAVE_DOXYGEN], [doxygen], []) -if test -z "$HAVE_DOXYGEN"; then - AC_MSG_WARN([Doxygen not found - continuing without Doxygen support.]) -fi -AM_CONDITIONAL([HAVE_DOXYGEN], [test -n "$HAVE_DOXYGEN"]) +AM_CONDITIONAL([HAVE_DOXYGEN], [test -n "${HAVE_DOXYGEN}"]) AM_COND_IF([HAVE_DOXYGEN], [AC_CONFIG_FILES([docs/Doxyfile])]) AM_INIT_AUTOMAKE([1.11 -Wall -Werror foreign subdir-objects]) From 4397e142db44d2fea7035a96cd5178eedbdbccc2 Mon Sep 17 00:00:00 2001 From: Conrad Kostecki Date: Sun, 14 Jan 2024 00:36:41 +0100 Subject: [PATCH 207/345] Update Gentoo initd/confd files Signed-off-by: Conrad Kostecki --- contrib/gentoo/conf.d/ebusd | 6 +++--- contrib/gentoo/init.d/ebusd | 19 +++++++++++-------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/contrib/gentoo/conf.d/ebusd b/contrib/gentoo/conf.d/ebusd index 0d1b72876..2abae6bec 100644 --- a/contrib/gentoo/conf.d/ebusd +++ b/contrib/gentoo/conf.d/ebusd @@ -1,5 +1,5 @@ -# /etc/conf.d/ebusd: -# config file for ebusd service. +# Copyright 1999-2024 Gentoo Authors +# Distributed under the terms of the GNU General Public License v2 -# Options to pass to ebusd (run "ebusd -?" for more info): +# Options to pass to ebusd (run "ebusd --help" for more info) EBUSD_OPTS="--scanconfig" diff --git a/contrib/gentoo/init.d/ebusd b/contrib/gentoo/init.d/ebusd index 9735db355..24582d79f 100755 --- a/contrib/gentoo/init.d/ebusd +++ b/contrib/gentoo/init.d/ebusd @@ -1,14 +1,17 @@ -#!/sbin/runscript -# Copyright 1999-2013 Gentoo Foundation +#!/sbin/openrc-run +# Copyright 1999-2024 Gentoo Foundation # Distributed under the terms of the GNU General Public License v2 -# $Header: $ command="/usr/bin/ebusd" command_args="${EBUSD_OPTS}" -start_stop_daemon_args="--quiet" -description="ebusd, the daemon for communication with eBUS heating systems." +logfile_path="/var/log/ebusd" +logfile="${pidfile_path}/ebusd.log" +name="eBUS daemon" +pidfile_path="/run/ebusd" +pidfile="${pidfile_path}/ebusd.pid" -depend() { - need localmount - use logger +start_pre() { + checkpath -d -q "${logfile_path}" + checkpath -d -q "${pidfile_path}" + checkpath -f -q "${logfile}" } From a9bb6cf862e077ab972f2ee3a6af5ab39ff0eb64 Mon Sep 17 00:00:00 2001 From: Conrad Kostecki Date: Sun, 14 Jan 2024 00:37:22 +0100 Subject: [PATCH 208/345] Add Gentoo systemd unit Signed-off-by: Conrad Kostecki --- contrib/gentoo/systemd/ebusd.service | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 contrib/gentoo/systemd/ebusd.service diff --git a/contrib/gentoo/systemd/ebusd.service b/contrib/gentoo/systemd/ebusd.service new file mode 100644 index 000000000..b66f44352 --- /dev/null +++ b/contrib/gentoo/systemd/ebusd.service @@ -0,0 +1,13 @@ +[Unit] +Description=eBUS daemon +After=network-online.target + +[Service] +Type=forking +User=ebusd +Group=ebusd +ExecStart=/usr/bin/ebusd ${EBUSD_OPTS} +Restart=always + +[Install] +WantedBy=multi-user.target From 3232f4ff956f5a32c0aac20c9e19d3b244a4b6b2 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 14 Jan 2024 08:40:50 +0100 Subject: [PATCH 209/345] add missed include for debugging --- src/lib/ebus/transport.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/ebus/transport.cpp b/src/lib/ebus/transport.cpp index e2fee3a5e..2d4483b84 100644 --- a/src/lib/ebus/transport.cpp +++ b/src/lib/ebus/transport.cpp @@ -35,6 +35,9 @@ #endif #include "lib/ebus/data.h" #include "lib/utils/tcpsocket.h" +#ifdef DEBUG_RAW_TRAFFIC +# include "lib/utils/clock.h" +#endif namespace ebusd { From 1316291740a481d560ef1f0d0a06cf2027f2b23c Mon Sep 17 00:00:00 2001 From: John Date: Sun, 14 Jan 2024 11:04:10 +0100 Subject: [PATCH 210/345] also send MQTT definition topic and handle knx subscription for conditional messages evaluated later --- src/ebusd/knxhandler.cpp | 8 +++++--- src/ebusd/mqtthandler.cpp | 3 ++- src/lib/ebus/message.cpp | 17 +++++++++++++++-- src/lib/ebus/message.h | 15 +++++++++++++++ 4 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/ebusd/knxhandler.cpp b/src/ebusd/knxhandler.cpp index 8d257408a..4a912a566 100644 --- a/src/ebusd/knxhandler.cpp +++ b/src/ebusd/knxhandler.cpp @@ -760,7 +760,7 @@ void KnxHandler::run() { } if (m_con->isConnected()) { deque messages; - m_messages->findAll("", "", m_levels, false, true, true, true, true, true, 0, 0, true, &messages); + m_messages->findAll("", "", m_levels, false, true, true, true, true, true, 0, 0, false, &messages); int addCnt = 0; for (const auto& message : messages) { const auto mit = m_subscribedMessages.find(message->getKey()); @@ -770,10 +770,12 @@ void KnxHandler::run() { if (message->getDstAddress() == SYN) { continue; // not usable in absence of destination address } - bool isWrite = message->isWrite() && !message->isPassive(); // from KNX perspective - if (message->getCreateTime() <= definitionsSince) { // only newer defined + if (message->getCreateTime() <= definitionsSince // only newer defined + && (!message->isConditional() || message->getAvailableSinceTime() <= definitionsSince)) { // unless conditional continue; } + logOtherDebug("knx", "checking association to %s %s", message->getCircuit().c_str(), message->getName().c_str()); + bool isWrite = message->isWrite() && !message->isPassive(); // from KNX perspective ssize_t fieldCount = static_cast(message->getFieldCount()); if (isWrite && fieldCount > 1) { // impossible with more than one field diff --git a/src/ebusd/mqtthandler.cpp b/src/ebusd/mqtthandler.cpp index c734cc5cc..20ec1f79b 100755 --- a/src/ebusd/mqtthandler.cpp +++ b/src/ebusd/mqtthandler.cpp @@ -816,7 +816,8 @@ void MqttHandler::run() { } } message->setDataHandlerState(1, true); - } else if (message->getCreateTime() <= m_definitionsSince) { // only newer defined + } else if (message->getCreateTime() <= m_definitionsSince // only newer defined + && (!message->isConditional() || message->getAvailableSinceTime() <= m_definitionsSince)) { // unless conditional continue; } if (!FileReader::matches(message->getCircuit(), filterCircuit, true, true) diff --git a/src/lib/ebus/message.cpp b/src/lib/ebus/message.cpp index 75b2770f5..f7faa0519 100644 --- a/src/lib/ebus/message.cpp +++ b/src/lib/ebus/message.cpp @@ -109,7 +109,7 @@ Message::Message(const string& filename, const string& circuit, const string& le m_id(id), m_key(createKey(id, isWrite, isPassive, srcAddress, dstAddress)), m_data(data), m_deleteData(deleteData), m_pollPriority(pollPriority), - m_usedByCondition(false), m_isScanMessage(false), m_condition(condition), + m_usedByCondition(false), m_isScanMessage(false), m_condition(condition), m_availableSinceTime(0), m_dataHandlerState(0), m_lastUpdateTime(0), m_lastChangeTime(0), m_pollOrder(0), m_lastPollTime(0) { if (circuit == "scan") { setScanMessage(); @@ -129,7 +129,7 @@ Message::Message(const string& circuit, const string& level, const string& name, m_id({pb, sb}), m_key(createKey(pb, sb, broadcast)), m_data(data), m_deleteData(deleteData), m_pollPriority(0), - m_usedByCondition(false), m_isScanMessage(true), m_condition(nullptr), + m_usedByCondition(false), m_isScanMessage(true), m_condition(nullptr), m_availableSinceTime(0), m_lastUpdateTime(0), m_lastChangeTime(0), m_pollOrder(0), m_lastPollTime(0) { time(&m_createTime); } @@ -652,6 +652,19 @@ bool Message::isAvailable() { return (m_condition == nullptr) || m_condition->isTrue(); } +time_t Message::getAvailableSinceTime() { + if (m_condition == nullptr) { + return m_createTime; + } + if (!m_condition->isTrue()) { + return 0; + } + if (m_availableSinceTime == 0) { // was not yet available + m_availableSinceTime = m_condition->getLastCheckTime(); + } + return m_availableSinceTime; +} + bool Message::hasField(const char* fieldName, bool numeric) const { return m_data->hasField(fieldName, numeric); } diff --git a/src/lib/ebus/message.h b/src/lib/ebus/message.h index 647539f77..21e8118bd 100644 --- a/src/lib/ebus/message.h +++ b/src/lib/ebus/message.h @@ -399,6 +399,12 @@ class Message : public AttributedItem { */ bool isAvailable(); + /** + * Get the time when this (potentially conditional) message first became available. + * @return the time when this message first became available, or 0 if it is not available. + */ + time_t getAvailableSinceTime(); + /** * Return whether the field is available. * @param fieldName the name of the field to find, or nullptr for any. @@ -673,6 +679,9 @@ class Message : public AttributedItem { /** the @a Condition for this message, or nullptr. */ Condition* m_condition; + /** the time when the @a Condition first became available, or 0. */ + time_t m_availableSinceTime; + /** the last seen @a MasterSymbolString. */ MasterSymbolString m_lastMasterData; @@ -927,6 +936,12 @@ class Condition { */ virtual bool isTrue() = 0; + /** + * Get the system time when the condition was last checked. + * @return the system time when the condition was last checked, 0 for never. + */ + time_t getLastCheckTime() const { return m_lastCheckTime; } + protected: /** the system time when the condition was last checked, 0 for never. */ From 5e10f7251e90deffd91e2d1ac81e19c5bd89246e Mon Sep 17 00:00:00 2001 From: John Date: Sun, 14 Jan 2024 11:09:43 +0100 Subject: [PATCH 211/345] add inject command --- ChangeLog.md | 7 +++++++ src/ebusd/mainloop.cpp | 31 ++++++++++++++++++++++++++++++- src/ebusd/mainloop.h | 8 ++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/ChangeLog.md b/ChangeLog.md index 21f339ba0..cd0bc7ced 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,3 +1,10 @@ +# next (tbd) +## Bug Fixes +* fix conditional messages not being sent to message definition in MQTT integration and not being used in KNX group association +## Features +* add "inject" command + + # 23.3 (2023-12-26) ## Bug Fixes * fix MQTT topic string validation diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index f8351d7bc..6ad18c761 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -477,6 +477,13 @@ result_t MainLoop::decodeRequest(Request* req, bool* connected, RequestMode* req *ostream << "ERR: command not enabled"; return RESULT_OK; } + if (cmd == "INJECT") { + if (m_enableHex) { + return executeInject(args, ostream); + } + *ostream << "ERR: command not enabled"; + return RESULT_OK; + } if (cmd == "F" || cmd == "FIND") { return executeFind(args, getUserLevels(*user), ostream); } @@ -1155,6 +1162,27 @@ result_t MainLoop::executeHex(const vector& args, ostringstream* ostream return RESULT_OK; } +result_t MainLoop::executeInject(const vector& args, ostringstream* ostream) { + size_t argPos = 1; + if (argPos < args.size()) { + MasterSymbolString master; + SlaveSymbolString slave; + if (!m_scanHelper->parseMessage(args[argPos++], false, &master, &slave)) { + return RESULT_ERR_INVALID_ARG; + } + m_busHandler->notifyProtocolMessage(false, master, slave); + return RESULT_OK; + } + *ostream << "usage: inject QQZZPBSBNN[DD]*/[NN[DD]*]\n" + " Inject hex data (without sending to bus).\n" + " QQ source address\n" + " ZZ destination address\n" + " PB SB primary/secondary command byte\n" + " NN number of following data bytes\n" + " DD data byte(s)"; + return RESULT_OK; +} + result_t MainLoop::executeDirect(const vector& args, RequestMode* reqMode, ostringstream* ostream) { if (reqMode->listenMode != lm_direct) { if (args.size() == 1) { @@ -1879,10 +1907,11 @@ result_t MainLoop::executeHelp(ostringstream* ostream) { " 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] [-n] ZZPBSB[NN][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 [-v|-V] [-n|-N] [-u|-U] [stop]\n" + " hex Send hex data: hex [-s QQ] [-n] ZZPBSB[NN][DD]* (if enabled)\n" + " inject Inject hex data: inject QQZZPBSBNN[DD]*/[NN[DD]*] (if enabled)\n" " direct Enter direct mode\n" " state|s Report bus state\n" " info|i Report information about the daemon, configuration, seen participants, and the device.\n" diff --git a/src/ebusd/mainloop.h b/src/ebusd/mainloop.h index 43255752d..3703a79ef 100644 --- a/src/ebusd/mainloop.h +++ b/src/ebusd/mainloop.h @@ -207,6 +207,14 @@ class MainLoop : public Thread { */ result_t executeHex(const vector& args, ostringstream* ostream); + /** + * Execute the inject command. + * @param args the arguments passed to the command (starting with the command itself), or empty for help. + * @param ostream the @a ostringstream to format the result string to. + * @return the result code. + */ + result_t executeInject(const vector& args, ostringstream* ostream); + /** * Execute the direct command. * @param args the arguments passed to the command (starting with the command itself), or empty for help. From 175c990e74039c52a88490ee6e6ccc9163136d3f Mon Sep 17 00:00:00 2001 From: John Date: Sun, 14 Jan 2024 16:48:47 +0100 Subject: [PATCH 212/345] fix csv dump --- src/ebusd/main.cpp | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index 576c9fb44..427aeb89f 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -369,14 +369,18 @@ int main(int argc, char* argv[], char* envp[]) { } else { logNotice(lf_main, "configuration dump:"); } - *out << "{\"datatypes\":["; - DataTypeList::getInstance()->dump(s_opt.dumpConfig, true, out); - *out << "],\"templates\":["; - const auto tmpl = s_scanHelper->getTemplates(""); - tmpl->dump(s_opt.dumpConfig, out); - *out << "],\"messages\":"; - s_messageMap->dump(true, s_opt.dumpConfig, out); - *out << "}"; + if (s_opt.dumpConfig & OF_JSON) { + *out << "{\"datatypes\":["; + DataTypeList::getInstance()->dump(s_opt.dumpConfig, true, out); + *out << "],\"templates\":["; + const auto tmpl = s_scanHelper->getTemplates(""); + tmpl->dump(s_opt.dumpConfig, out); + *out << "],\"messages\":"; + s_messageMap->dump(true, s_opt.dumpConfig, out); + *out << "}"; + } else { + s_messageMap->dump(true, s_opt.dumpConfig, out); + } if (fout.is_open()) { fout.close(); } From 344f044bc2522b0005888104438f6b33ad0a642a Mon Sep 17 00:00:00 2001 From: John Date: Sat, 20 Jan 2024 15:01:56 +0100 Subject: [PATCH 213/345] use --with-docs instead of --without-docs and fail if doxygen is missing --- configure.ac | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/configure.ac b/configure.ac index 1896fe2c6..54cbb18a8 100755 --- a/configure.ac +++ b/configure.ac @@ -47,13 +47,13 @@ AC_ARG_WITH(contrib, AS_HELP_STRING([--without-contrib], [disable inclusion of c if test "x$with_contrib" != "xno"; then AC_DEFINE_UNQUOTED(HAVE_CONTRIB, [1], [Defined if contributed sources are enabled.]) fi -AC_ARG_WITH(docs, AS_HELP_STRING([--without-docs], [disable generation of docs]), [], [with_docs=yes]) -if test "x$with_docs" != "xno"; then +AC_ARG_WITH(docs, AS_HELP_STRING([--with-docs], [generate documentation]), [ AC_CHECK_PROGS([HAVE_DOXYGEN], [doxygen], []) - if test -z "${HAVE_DOXYGEN}"; then - AC_MSG_WARN([Doxygen not found - continuing without Doxygen support.]) + if test -z "$HAVE_DOXYGEN"; then + AC_MSG_ERROR([Could not find doxygen.]) fi -fi +], []) + AC_ARG_WITH(mqtt, AS_HELP_STRING([--without-mqtt], [disable support for MQTT handling]), [], [with_mqtt=yes]) if test "x$with_mqtt" != "xno"; then AC_CHECK_LIB([mosquitto], [mosquitto_lib_init], @@ -162,7 +162,7 @@ else AC_DEFINE(REVISION, "[m4_esyscmd_s([git describe --always 2>/dev/null || (date +p%Y%m%d)])]", [The revision of the package.]) fi -AM_CONDITIONAL([HAVE_DOXYGEN], [test -n "${HAVE_DOXYGEN}"]) +AM_CONDITIONAL([HAVE_DOXYGEN], [test -n "$HAVE_DOXYGEN"]) AM_COND_IF([HAVE_DOXYGEN], [AC_CONFIG_FILES([docs/Doxyfile])]) AM_INIT_AUTOMAKE([1.11 -Wall -Werror foreign subdir-objects]) From 1a1c0633e32509035cd9f89c8fe288a6db69e5d9 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 20 Jan 2024 15:06:54 +0100 Subject: [PATCH 214/345] fix previous commit --- contrib/gentoo/init.d/ebusd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/gentoo/init.d/ebusd b/contrib/gentoo/init.d/ebusd index 24582d79f..eabbf36d9 100755 --- a/contrib/gentoo/init.d/ebusd +++ b/contrib/gentoo/init.d/ebusd @@ -5,7 +5,7 @@ command="/usr/bin/ebusd" command_args="${EBUSD_OPTS}" logfile_path="/var/log/ebusd" -logfile="${pidfile_path}/ebusd.log" +logfile="${logfile_path}/ebusd.log" name="eBUS daemon" pidfile_path="/run/ebusd" pidfile="${pidfile_path}/ebusd.pid" From ec5c91f29b6247f2f6fcf061c725d58f52dd477d Mon Sep 17 00:00:00 2001 From: John Date: Sat, 20 Jan 2024 15:13:07 +0100 Subject: [PATCH 215/345] avoid egrep --- contrib/munin/ebusd_ | 4 ++-- contrib/scripts/readall.sh | 2 +- make_debian.sh | 10 +++++----- test_all.sh | 4 ++-- test_coverage.sh | 8 ++++---- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/contrib/munin/ebusd_ b/contrib/munin/ebusd_ index e1e6388d8..b3e76595f 100755 --- a/contrib/munin/ebusd_ +++ b/contrib/munin/ebusd_ @@ -57,9 +57,9 @@ if [ -n "$find2" ]; then fi result="$result\n$result2" fi -sensors=`echo "$result"|sed -e 's# =.*$##' -e 's# #:#'|egrep "$match"` +sensors=`echo "$result"|sed -e 's# =.*$##' -e 's# #:#'|grep -E "$match"` if [ -n "$skip" ]; then - sensors=`echo "$sensors"|egrep -v "$skip"` + sensors=`echo "$sensors"|grep -Ev "$skip"` fi sensors=`echo "$sensors"|sort -u` if [ "$1" = "config" ]; then diff --git a/contrib/scripts/readall.sh b/contrib/scripts/readall.sh index 26d37e433..74da9ee6d 100755 --- a/contrib/scripts/readall.sh +++ b/contrib/scripts/readall.sh @@ -11,7 +11,7 @@ if [ "x$1" = "x-R" ]; then readargs=$1 shift fi -for i in `echo "f -F circuit,name" "$@"|nc -q 1 127.0.0.1 $port|sort -u|egrep ','`; do +for i in `echo "f -F circuit,name" "$@"|nc -q 1 127.0.0.1 $port|sort -u|grep ','`; do circuit=${i%%,*} name=${i##*,} if [ -z "$circuit" ] || [ -z "$name" ] || [ "$circuit,$name" = "scan,id" ]; then diff --git a/make_debian.sh b/make_debian.sh index 37f1871e0..13a8ec17a 100755 --- a/make_debian.sh +++ b/make_debian.sh @@ -66,20 +66,20 @@ if [ -n "$reusebuilddir" ] || [ -z "$keepbuilddir" ]; then fi make DESTDIR="$PWD/$RELEASE" install-strip || exit 1 extralibs= -ldd $RELEASE/usr/bin/ebusd | egrep -q libeibclient.so.0 +ldd $RELEASE/usr/bin/ebusd | grep -q libeibclient.so.0 if [ $? -eq 0 ]; then extralibs="$extralibs, knxd-tools" fi -ldd $RELEASE/usr/bin/ebusd | egrep -q libmosquitto.so.1 +ldd $RELEASE/usr/bin/ebusd | grep -q libmosquitto.so.1 if [ $? -eq 0 ]; then extralibs="$extralibs, libmosquitto1" PACKAGE="${PACKAGE}_mqtt1" fi -ldd $RELEASE/usr/bin/ebusd | egrep -q libssl.so.3 +ldd $RELEASE/usr/bin/ebusd | grep -q libssl.so.3 if [ $? -eq 0 ]; then extralibs="$extralibs, libssl3 (>= 3.0.0), ca-certificates" else - ldd $RELEASE/usr/bin/ebusd | egrep -q libssl.so.1.1 + ldd $RELEASE/usr/bin/ebusd | grep -q libssl.so.1.1 if [ $? -eq 0 ]; then extralibs="$extralibs, libssl1.1 (>= 1.1.0), ca-certificates" fi @@ -104,7 +104,7 @@ if [ -n "$RUNTEST" ]; then fi # note: this can't run on file system base when using qemu for arm 32bit and host is 64bit due to glibc readdir() inode 32 bit values (see https://bugs.launchpad.net/qemu/+bug/1805913), thus using test end point instead: ("$RELEASE/usr/bin/ebusd" -f -s --configlang=tt -d /dev/null --log=all:debug --inject=stop 10fe0900040000803e/ > test.txt) || testdie "float conversion" - egrep "received update-read broadcast test QQ=10: 0\.25$" test.txt || testdie "float result" + grep -E "received update-read broadcast test QQ=10: 0\.25$" test.txt || testdie "float result" fi echo diff --git a/test_all.sh b/test_all.sh index f9e2874f7..cca8e7839 100755 --- a/test_all.sh +++ b/test_all.sh @@ -1,3 +1,3 @@ #!/bin/sh -(cd src/lib/ebus/test && make >/dev/null && ./test_filereader && ./test_data && ./test_message && ./test_symbol && echo "standard: OK!")|egrep -v "OK$" -(cd src/lib/ebus/contrib/test && make >/dev/null && ./test_contrib && echo "contrib: OK!")|egrep -v "OK$" +(cd src/lib/ebus/test && make >/dev/null && ./test_filereader && ./test_data && ./test_message && ./test_symbol && echo "standard: OK!")|grep -Ev "OK$" +(cd src/lib/ebus/contrib/test && make >/dev/null && ./test_contrib && echo "contrib: OK!")|grep -Ev "OK$" diff --git a/test_coverage.sh b/test_coverage.sh index c664125ea..67344a463 100755 --- a/test_coverage.sh +++ b/test_coverage.sh @@ -107,7 +107,7 @@ $ebuspicloader -f x >/dev/null 2>/dev/null echo -e ':100800008431542CAE3401347E1484314E01961E52\n:00000001FF' > firmware.hex $ebuspicloader -f firmware.hex >/dev/null #server: -php -r 'echo "php is available";'|egrep 'php is available' +php -r 'echo "php is available";'|grep 'php is available' if [ ! "$?" = 0 ]; then echo `date` "php is not available" exit 1 @@ -370,7 +370,7 @@ function send() { while [[ ! "$status" = 0 ]] && [[ $cnt -gt 0 ]]; do sleep 5 echo `date` "check signal" - send state | egrep -q "signal acquired" + send state | grep -q "signal acquired" status=$? cnt=$((cnt - 1)) done @@ -411,7 +411,7 @@ while [ "$status" = 0 ]; do failed=1 break fi - echo $output | egrep -q "still running" + echo $output | grep -q "still running" status=$? scancnt=$(( scancnt + 1 )) done @@ -434,7 +434,7 @@ curl -s "http://localhost:8878/data/mc.5/installparam?poll=1&user=test&secret=te curl -s -T test_coverage.sh http://localhost:8878/data/ echo `date` "commands done" kill $lstpid -verify=`send info|egrep "^address 04:"` +verify=`send info|grep -E "^address 04:"` expect='address 04: slave #25, scanned "MF=153;ID=BBBBB;SW=3031;HW=3031"' if [ "x$verify" != "x$expect" ]; then echo -e `date` "error unexpected result from info command:\n expected: >$expect<\n got: >$verify<" From fe80841e782a820eee35f36025f783f78e9a19ad Mon Sep 17 00:00:00 2001 From: John Date: Sat, 20 Jan 2024 17:39:38 +0100 Subject: [PATCH 216/345] update links, use enhanced prefix in docs --- .github/ISSUE_TEMPLATE/bug_report.yml | 24 ++++++++++-------------- README.md | 5 +++-- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index c72e0b320..c600af34b 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -8,7 +8,7 @@ body: id: description attributes: label: Description - description: A clear and concise description of what the bug is. Anything related to actual message definitions in [ebusd-configuration](https://github.com/john30/ebusd-configuration), serial bridge in [ebusd-esp](https://github.com/john30/ebusd-esp), or [eBUS adapter v3/v2](https://github.com/eBUS/adapter) do not belong here! Use the appropriate repository for those. + description: A clear and concise description of what the bug is. Anything related to actual message definitions in [ebusd-configuration](https://github.com/john30/ebusd-configuration), [eBUS Adapter Shield v5](https://github.com/john30/ebusd-esp32), serial bridge in [ebusd-esp](https://github.com/john30/ebusd-esp), or [eBUS adapter v3/v2](https://github.com/eBUS/adapter) do not belong here! Use the appropriate repository for those. placeholder: e.g. during startup, an error message as described below is reported instead of... validations: required: true @@ -97,19 +97,15 @@ body: label: Hardware interface description: the eBUS hardware interface in use options: - - adapter 5 via USB - - adapter 5 via WiFi - - adapter 5 via Ethernet - - adapter 5 via Raspberry GPIO - - adapter 3.1 USB - - adapter 3.1 WiFi - - adapter 3.1 Ethernet - - adapter 3.1 RPi - - adapter 3.0 USB - - adapter 3.0 WiFi - - adapter 3.0 Ethernet - - adapter 3.0 RPi - - adapter 2 + - Adapter Shield v5 via USB + - Adapter Shield v5 via WiFi + - Adapter Shield v5 via Ethernet + - Adapter Shield v5 via Raspberry GPIO + - Adapter v3 USB + - Adapter v3 WiFi + - Adapter v3 Ethernet + - Adapter v3 RPi + - Adapter v2 - other validations: required: true diff --git a/README.md b/README.md index 1a031119a..7111f8553 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ The main features of the daemon are: * TCP * UDP * enhanced ebusd protocol allowing arbitration to be done directly by the hardware, e.g. for recent - * [ebus adapter 5](https://adapter.ebusd.eu/v5/), or + * [eBUS Adapter Shield](https://adapter.ebusd.eu/v5/), or * [ebusd-esp firmware](https://github.com/john30/ebusd-esp/) * actively send messages to and receive answers from the eBUS * passively listen to messages sent on the eBUS @@ -35,6 +35,7 @@ The main features of the daemon are: * automatically check for updates of daemon and configuration files * pick preferred language for translatable message configuration parts * grab all messages on the eBUS and provide decoding hints + * send arbitrary messages from hex input or inject those * log messages and problems to a log file * capture messages or sent/received bytes to a log file as text * dump received bytes to binary files for later playback/analysis @@ -97,7 +98,7 @@ Docker image A multi-architecture Docker image using the config web service for retrieving the latest message configuration files is available on the hub. You can use it like this: > docker pull john30/ebusd -> docker run -it --rm --device=/dev/ttyUSB0 -p 8888 john30/ebusd +> docker run -it --rm --device=ens:/dev/ttyUSB0 -p 8888 john30/ebusd For more details, see [Docker Readme](https://github.com/john30/ebusd/blob/master/contrib/docker/README.md). From 5c9153f3b7bd78aad2466b179d2892444930cf6e Mon Sep 17 00:00:00 2001 From: John Date: Sat, 20 Jan 2024 17:40:40 +0100 Subject: [PATCH 217/345] use enhanced prefix in docs --- contrib/docker/README.md | 12 ++++++------ contrib/docker/docker-compose.example.yaml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/contrib/docker/README.md b/contrib/docker/README.md index a707fb575..92ef21e78 100644 --- a/contrib/docker/README.md +++ b/contrib/docker/README.md @@ -29,16 +29,16 @@ automatically from the latest source on the git repository. Run the following co Running interactively --------------------- -To run an ebusd container interactively, e.g. on serial device /dev/ttyUSB1, use the following command: -> docker run --rm -it --device=/dev/ttyUSB1:/dev/ttyUSB0 -p 8888 john30/ebusd +To run an ebusd container interactively, e.g. on enhanced serial device /dev/ttyUSB1, use the following command: +> docker run --rm -it --device=/dev/ttyUSB1:/dev/ttyUSB0 -p 8888 john30/ebusd -d ens:/dev/ttyUSB0 This will show the ebusd output directly in the terminal. Running in background --------------------- -To start an ebusd container and have it run in the background, e.g. on serial device /dev/ttyUSB1, use the following command: -> docker run -d --name=ebusd --device=/dev/ttyUSB1:/dev/ttyUSB0 -p 8888 john30/ebusd +To start an ebusd container and have it run in the background, e.g. on enhanced serial device /dev/ttyUSB1, use the following command: +> docker run -d --name=ebusd --device=/dev/ttyUSB1:/dev/ttyUSB0 -p 8888 john30/ebusd -d ens:/dev/ttyUSB0 The container has the name "ebusd", so you can use that when querying docker about the container. @@ -62,14 +62,14 @@ Running with MQTT broker ------------------------ To start an ebusd container in the background and have it connect to your MQTT broker, use the following command while replacing "BROKERHOST" with your MQTT broker host name or IP address: -> docker run -d --name=ebusd --device=/dev/ttyUSB0 -p 8888 john30/ebusd --scanconfig -d /dev/ttyUSB0 --mqttport=1883 --mqtthost=BROKERHOST +> docker run -d --name=ebusd --device=/dev/ttyUSB0 -p 8888 john30/ebusd --scanconfig -d ens:/dev/ttyUSB0 --mqttport=1883 --mqtthost=BROKERHOST Use of environment variables ---------------------------- Instead of passing arguments (at the end of docker run) to ebusd, almost all (long) arguments can also be passed as environment variables with the prefix `EBUSD_`, e.g. the following line can be used instead of the last example above: -> docker run -d --name=ebusd --device=/dev/ttyUSB0 -p 8888 -e EBUSD_SCANCONFIG= -e EBUSD_DEVICE=/dev/ttyUSB0 -e EBUSD_MQTTPORT=1883 -e EBUSD_MQTTHOST=BROKERHOST john30/ebusd +> docker run -d --name=ebusd --device=/dev/ttyUSB0 -p 8888 -e EBUSD_SCANCONFIG= -e EBUSD_DEVICE=ens:/dev/ttyUSB0 -e EBUSD_MQTTPORT=1883 -e EBUSD_MQTTHOST=BROKERHOST john30/ebusd This eases use of e.g. "docker-compose.yaml" files like [the example docker-compose file](https://github.com/john30/ebusd/blob/master/contrib/docker/docker-compose.example.yaml) also describing each available environment variable in it. diff --git a/contrib/docker/docker-compose.example.yaml b/contrib/docker/docker-compose.example.yaml index 61f099eb3..95f3669f4 100644 --- a/contrib/docker/docker-compose.example.yaml +++ b/contrib/docker/docker-compose.example.yaml @@ -16,7 +16,7 @@ services: # Use DEV as eBUS device ("enh:DEVICE" or "enh:IP:PORT" for enhanced device, "ens:DEVICE" for enhanced high speed # serial device, "DEVICE" for serial device, or "[udp:]IP:PORT" for network device) - EBUSD_DEVICE: "/dev/ttyUSB0" + EBUSD_DEVICE: "ens:/dev/ttyUSB0" # Skip serial eBUS device test #EBUSD_NODEVICECHECK: "" # Only read from device, never write to it From 3a91fe83849a959b08fe2ff4ed8c0dd606ace51c Mon Sep 17 00:00:00 2001 From: John Date: Sat, 20 Jan 2024 17:44:42 +0100 Subject: [PATCH 218/345] fix previous commit --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7111f8553..45987ceb4 100644 --- a/README.md +++ b/README.md @@ -95,10 +95,10 @@ the latest configuration files** that are reflected by the configuration reposit Docker image ------------ -A multi-architecture Docker image using the config web service for retrieving the latest message configuration files is available on the hub. +A multi-architecture Docker image using the config web service for retrieving the latest message configuration files is available on the hub. You can use it like this: > docker pull john30/ebusd -> docker run -it --rm --device=ens:/dev/ttyUSB0 -p 8888 john30/ebusd +> docker run -it --rm --device=/dev/ttyUSB0 -p 8888 john30/ebusd -d ens:/dev/ttyUSB0 For more details, see [Docker Readme](https://github.com/john30/ebusd/blob/master/contrib/docker/README.md). From 64b2b2ea01ddcdee67261a806b4bddffe71b2b62 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 21 Jan 2024 08:46:50 +0100 Subject: [PATCH 219/345] missed commit --- src/lib/ebus/transport.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/ebus/transport.cpp b/src/lib/ebus/transport.cpp index 2d4483b84..5d883ec31 100644 --- a/src/lib/ebus/transport.cpp +++ b/src/lib/ebus/transport.cpp @@ -280,7 +280,6 @@ result_t SerialTransport::openInternal() { cfsetspeed(&newSettings, m_speed ? (m_speed > 1 ? B115200 : B9600) : B2400); #else cfsetispeed(&newSettings, m_speed ? (m_speed > 1 ? B115200 : B9600) : B2400); - cfsetospeed(&newSettings, m_enhancedLevel ? (m_enhancedLevel >= el_speed ? B115200 : B9600) : B2400); #endif newSettings.c_cflag |= (CS8 | CLOCAL | CREAD); newSettings.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // non-canonical mode From 64727a4a83b4770c37e9ba782da9d91830396490 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 27 Jan 2024 10:11:42 +0100 Subject: [PATCH 220/345] fix DTM type with recent dates (fixes #1172) --- ChangeLog.md | 2 ++ src/lib/ebus/datatype.cpp | 3 ++- src/lib/ebus/test/test_data.cpp | 3 ++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index cd0bc7ced..41afaafcc 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,6 +1,8 @@ # next (tbd) ## Bug Fixes * fix conditional messages not being sent to message definition in MQTT integration and not being used in KNX group association +* fix CSV dump of config files on command line +* fix DTM type with recent dates ## Features * add "inject" command diff --git a/src/lib/ebus/datatype.cpp b/src/lib/ebus/datatype.cpp index c301aab6b..1d6a93e14 100755 --- a/src/lib/ebus/datatype.cpp +++ b/src/lib/ebus/datatype.cpp @@ -591,7 +591,8 @@ result_t DateTimeDataType::writeSymbols(size_t offset, size_t length, istringstr if (result != RESULT_OK) { return result; // invalid time part } - if ((i == 0 && value > 24) || (i > 0 && (last == 24 && value > 0) )) { + if ((i == (m_hasDate ? 2 : 0) && value > 24) + || (i > (m_hasDate ? 2 : 0) && (last == 24 && value > 0) )) { return RESULT_ERR_OUT_OF_RANGE; // invalid time part } if (hasFlag(SPE)) { // minutes since midnight diff --git a/src/lib/ebus/test/test_data.cpp b/src/lib/ebus/test/test_data.cpp index 44f7d8e0c..02d29b683 100755 --- a/src/lib/ebus/test/test_data.cpp +++ b/src/lib/ebus/test/test_data.cpp @@ -174,7 +174,8 @@ int main() { {"x,,day", "", "10fe0700020000", "00", "Rw"}, {"x,,dtm", "01.01.2009 00:00", "10fe07000400000000", "00", ""}, {"x,,dtm", "31.12.2099 23:59", "10fe0700041f4eda02", "00", ""}, - {"x,,dtm", "16.12.2020 16:51", "10fe07000453f85f00", "00", ""}, + {"x,,dtm", "16.12.2024 16:51", "10fe07000473128000", "00", ""}, + {"x,,dtm", "24.12.2025 16:51", "10fe07000493448800", "00", ""}, {"x,,bti", "21:04:58", "10fe070003580421", "00", ""}, {"x,,bti", "00:00:00", "10fe070003000000", "00", ""}, {"x,,bti", "23:59:59", "10fe070003595923", "00", ""}, From 2ea20ed8860ffba11c716e1b601160d86c5693d2 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 27 Jan 2024 10:35:57 +0100 Subject: [PATCH 221/345] add config path to verbose info command (closes #1087) --- src/ebusd/mainloop.cpp | 3 +++ src/ebusd/scan.h | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index 6ad18c761..79869fb12 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -1881,6 +1881,9 @@ result_t MainLoop::executeInfo(const vector& args, const string& user, o << "conditional: " << m_messages->sizeConditional() << "\n" << "poll: " << m_messages->sizePoll() << "\n" << "update: " << m_messages->sizePassive(); + if (verbose) { + *ostream << "\nconfig path: " << m_scanHelper->getConfigPath(); + } m_busHandler->formatSeenInfo(ostream); return RESULT_OK; } diff --git a/src/ebusd/scan.h b/src/ebusd/scan.h index 6cb4d6c3c..99d28fe92 100644 --- a/src/ebusd/scan.h +++ b/src/ebusd/scan.h @@ -66,6 +66,11 @@ class ScanHelper : public Resolver { */ virtual ~ScanHelper(); + /** + * @return the (optionally corrected) config path for retrieving configuration files from. + */ + const string getConfigPath() const { return m_configPath; } + /** * Try to connect to the specified server. * @param host the host name to connect to. From f21adf62cebea7f9f96da6b543651ffd90124ccc Mon Sep 17 00:00:00 2001 From: John Date: Sat, 3 Feb 2024 13:19:22 +0100 Subject: [PATCH 222/345] more abstraction, better naming --- src/lib/ebus/CMakeLists.txt | 3 +- src/lib/ebus/Makefile.am | 3 +- src/lib/ebus/device.h | 203 +--------------- src/lib/ebus/device_enhanced.h | 93 +++++++ src/lib/ebus/{device.cpp => device_trans.cpp} | 103 +++----- src/lib/ebus/device_trans.h | 228 ++++++++++++++++++ src/lib/ebus/protocol.cpp | 7 +- src/lib/ebus/protocol_direct.h | 7 +- 8 files changed, 384 insertions(+), 263 deletions(-) create mode 100755 src/lib/ebus/device_enhanced.h rename src/lib/ebus/{device.cpp => device_trans.cpp} (88%) create mode 100755 src/lib/ebus/device_trans.h diff --git a/src/lib/ebus/CMakeLists.txt b/src/lib/ebus/CMakeLists.txt index e3f3b0970..dc617ed9b 100644 --- a/src/lib/ebus/CMakeLists.txt +++ b/src/lib/ebus/CMakeLists.txt @@ -6,7 +6,8 @@ set(libebus_a_SOURCES filereader.h filereader.cpp datatype.h datatype.cpp data.h data.cpp - device.h device.cpp + device.h device_enhanced.h + device_trans.h device_trans.cpp transport.h transport.cpp protocol.h protocol.cpp protocol_direct.h protocol_direct.cpp diff --git a/src/lib/ebus/Makefile.am b/src/lib/ebus/Makefile.am index 40882f0a5..cc4f386a9 100644 --- a/src/lib/ebus/Makefile.am +++ b/src/lib/ebus/Makefile.am @@ -10,7 +10,8 @@ libebus_a_SOURCES = \ filereader.h filereader.cpp \ datatype.h datatype.cpp \ data.h data.cpp \ - device.h device.cpp \ + device.h device_enhanced.h \ + device_trans.h device_trans.cpp \ transport.h transport.cpp \ protocol.h protocol.cpp \ protocol_direct.h protocol_direct.cpp \ diff --git a/src/lib/ebus/device.h b/src/lib/ebus/device.h index 8da116a76..a3cbe5855 100755 --- a/src/lib/ebus/device.h +++ b/src/lib/ebus/device.h @@ -22,7 +22,6 @@ #include #include #include "lib/ebus/result.h" -#include "lib/ebus/transport.h" #include "lib/ebus/symbol.h" namespace ebusd { @@ -33,10 +32,9 @@ namespace ebusd { * A @a Device allows to send and receive data to/from a local or remote eBUS * device while optionally dumping the data to a file and/or forwarding it to * a logging function. - * The data transport itself is handled by a @a Transport instance. */ -/** the arbitration state handled by @a CharDevice. */ +/** the arbitration state handled by @a Device. */ enum ArbitrationState { as_none, //!< no arbitration in process as_start, //!< arbitration start requested @@ -77,14 +75,13 @@ class DeviceListener { /** * The base class for accessing an eBUS. */ -class Device : public TransportListener { +class Device { protected: /** * Construct a new instance. - * @param transport the @a Transport to use. */ - explicit Device(Transport* transport) - : m_transport(transport), m_listener(nullptr) { + Device() + : m_listener(nullptr) { } public: @@ -92,17 +89,13 @@ class Device : public TransportListener { * Destructor. */ virtual ~Device() { - if (m_transport) { - delete m_transport; - m_transport = nullptr; - } } /** * Get the device name. * @return the device name (e.g. "/dev/ttyUSB0" for serial, "127.0.0.1:1234" for network). */ - const char* getName() const { return m_transport->getName(); } + virtual const char* getName() const = 0; // abstract /** * Set the @a DeviceListener. @@ -116,13 +109,7 @@ class Device : public TransportListener { * @param verbose whether to add verbose infos. * @param prefix true for the synchronously retrievable prefix, false for the potentially asynchronous suffix. */ - virtual void formatInfo(ostringstream* output, bool verbose, bool prefix) { - if (prefix) { - *output << m_transport->getName() << ", " << m_transport->getTransportInfo(); - } else if (!m_transport->isValid()) { - *output << ", invalid"; - } - } + virtual void formatInfo(ostringstream* output, bool verbose, bool prefix) = 0; // abstract /** * Format device infos in JSON format. @@ -135,50 +122,18 @@ class Device : public TransportListener { */ virtual bool supportsUpdateCheck() const { return false; } - // @copydoc - virtual result_t notifyTransportStatus(bool opened) { - m_listener->notifyDeviceStatus(!opened, opened ? "transport opened" : "transport closed"); - return RESULT_OK; - } - - // @copydoc - virtual void notifyTransportMessage(bool error, const char* message) { - m_listener->notifyDeviceStatus(error, message); - } - /** * Open the file descriptor. * @return the @a result_t code. */ - virtual result_t open() { return m_transport->open(); } + virtual result_t open() = 0; // abstract /** * Return whether the device is opened and available. * @return whether the device is opened and available. */ - virtual bool isValid() { return m_transport->isValid(); } - - protected: - /** the @a Transport to use. */ - Transport* m_transport; - - /** the @a DeviceListener, or nullptr. */ - DeviceListener* m_listener; -}; - - -class CharDevice : public Device { - protected: - /** - * Construct a new instance. - * @param transport the @a Transport to use. - */ - explicit CharDevice(Transport* transport) - : Device(transport), m_arbitrationMaster(SYN), m_arbitrationCheck(0) { - transport->setListener(this); - } + virtual bool isValid() = 0; // abstract - public: /** * Write a single byte to the device. * @param value the byte value to write. @@ -202,156 +157,24 @@ class CharDevice : public Device { * @param masterAddress the master address, or @a SYN to cancel a previous arbitration request. * @return the result_t code. */ - virtual result_t startArbitration(symbol_t masterAddress); + virtual result_t startArbitration(symbol_t masterAddress) = 0; // abstract /** * Return whether the device is currently in arbitration. * @return true when the device is currently in arbitration. */ - virtual bool isArbitrating() const { return m_arbitrationMaster != SYN; } + virtual bool isArbitrating() const = 0; // abstract /** * Cancel a running arbitration. * @param arbitrationState the reference in which @a as_error is stored when cancelled. * @return true if it was cancelled, false if not. */ - virtual bool cancelRunningArbitration(ArbitrationState* arbitrationState); + virtual bool cancelRunningArbitration(ArbitrationState* arbitrationState) = 0; // abstract protected: - /** the arbitration master address to send when in arbitration, or @a SYN. */ - symbol_t m_arbitrationMaster; - - /** >0 when in arbitration and the next received symbol needs to be checked against the sent master address, - * incremented with each received SYN when arbitration was not performed as expected and needs to be stopped. */ - size_t m_arbitrationCheck; -}; - - -class PlainCharDevice : public CharDevice { - public: - /** - * Construct a new instance. - * @param transport the @a Transport to use. - */ - explicit PlainCharDevice(Transport* transport) - : CharDevice(transport) { - } - - // @copydoc - result_t send(symbol_t value) override; - - // @copydoc - result_t recv(unsigned int timeout, symbol_t* value, ArbitrationState* arbitrationState) override; -}; - - -class EnhancedCharDevice : public CharDevice { - public: - /** - * Construct a new instance. - * @param transport the @a Transport to use. - */ - explicit EnhancedCharDevice(Transport* transport) - : CharDevice(transport), m_resetRequested(false), - m_extraFatures(0), m_infoReqTime(0), m_infoLen(0), m_infoPos(0), m_enhInfoIsWifi(false) { - } - - // @copydoc - void formatInfo(ostringstream* output, bool verbose, bool prefix) override; - - // @copydoc - void formatInfoJson(ostringstream* output) const override; - - // @copydoc - result_t send(symbol_t value) override; - - // @copydoc - result_t recv(unsigned int timeout, symbol_t* value, ArbitrationState* arbitrationState) override; - - // @copydoc - result_t startArbitration(symbol_t masterAddress) override; - - // @copydoc - virtual result_t notifyTransportStatus(bool opened); - - // @copydoc - bool supportsUpdateCheck() const override { return m_extraFatures & 0x01; } - - /** - * Check for a running extra infos request, wait for it to complete, - * and then send a new request for extra infos to enhanced device. - * @param infoId the ID of the info to request. - * @param wait true to wait for a running request to complete, false to send right away. - * @return @a RESULT_OK on success, or an error code otherwise. - */ - result_t requestEnhancedInfo(symbol_t infoId, bool wait = true); - - /** - * Get the enhanced device version. - * @return @a a string with the version infos, or empty. - */ - string getEnhancedVersion() const { return m_enhInfoVersion; } - - /** - * Retrieve/update all extra infos from an enhanced device. - * @return @a a string with the extra infos, or empty. - */ - string getEnhancedInfos(); - - private: - /** - * Cancel a running arbitration. - * @param arbitrationState the reference in which @a as_error is stored when cancelled. - * @return true if it was cancelled, false if not. - */ - bool cancelRunningArbitration(ArbitrationState* arbitrationState); - - /** - * Handle the already buffered enhanced data. - * @param value the reference in which the read byte value is stored. - * @param arbitrationState the variable in which to store the current/received arbitration state. - * @return the @a result_t code, especially RESULT_CONTINE if the value was set and more data is available immediately. - */ - result_t handleEnhancedBufferedData(const uint8_t* data, size_t len, symbol_t* value, - ArbitrationState* arbitrationState); - - /** - * Called when reception of an info ID was completed. - */ - void notifyInfoRetrieved(); - - /** whether the reset of the device was already requested. */ - bool m_resetRequested; - - /** the extra features supported by the device. */ - symbol_t m_extraFatures; - - /** the time of the last info request. */ - time_t m_infoReqTime; - - /** the info buffer expected length. */ - size_t m_infoLen; - - /** the info buffer write position. */ - size_t m_infoPos; - - /** the info buffer. */ - symbol_t m_infoBuf[16+1]; - - /** a string describing the enhanced device version. */ - string m_enhInfoVersion; - - /** whether the device is known to be connected via WIFI. */ - bool m_enhInfoIsWifi; - - /** a string describing the enhanced device temperature. */ - string m_enhInfoTemperature; - - /** a string describing the enhanced device supply voltage. */ - string m_enhInfoSupplyVoltage; - - /** a string describing the enhanced device bus voltage. */ - string m_enhInfoBusVoltage; + /** the @a DeviceListener, or nullptr. */ + DeviceListener* m_listener; }; } // namespace ebusd diff --git a/src/lib/ebus/device_enhanced.h b/src/lib/ebus/device_enhanced.h new file mode 100755 index 000000000..fe59b6b0c --- /dev/null +++ b/src/lib/ebus/device_enhanced.h @@ -0,0 +1,93 @@ +/* + * ebusd - daemon for communication with eBUS heating systems. + * Copyright (C) 2015-2023 John Baier + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LIB_EBUS_DEVICE_ENHANCED_H_ +#define LIB_EBUS_DEVICE_ENHANCED_H_ + +#include +#include +#include "lib/ebus/result.h" +#include "lib/ebus/symbol.h" + +namespace ebusd { + +/** @file lib/ebus/device_enhanced.h + * Enhanced protocol definitions for @a Device instances. + */ + +// ebusd enhanced protocol IDs: +#define ENH_REQ_INIT ((uint8_t)0x0) +#define ENH_RES_RESETTED ((uint8_t)0x0) +#define ENH_REQ_SEND ((uint8_t)0x1) +#define ENH_RES_RECEIVED ((uint8_t)0x1) +#define ENH_REQ_START ((uint8_t)0x2) +#define ENH_RES_STARTED ((uint8_t)0x2) +#define ENH_REQ_INFO ((uint8_t)0x3) +#define ENH_RES_INFO ((uint8_t)0x3) +#define ENH_RES_FAILED ((uint8_t)0xa) +#define ENH_RES_ERROR_EBUS ((uint8_t)0xb) +#define ENH_RES_ERROR_HOST ((uint8_t)0xc) + +// ebusd enhanced error codes for the ENH_RES_ERROR_* responses +#define ENH_ERR_FRAMING ((uint8_t)0x00) +#define ENH_ERR_OVERRUN ((uint8_t)0x01) + +#define ENH_BYTE_FLAG ((uint8_t)0x80) +#define ENH_BYTE_MASK ((uint8_t)0xc0) +#define ENH_BYTE1 ((uint8_t)0xc0) +#define ENH_BYTE2 ((uint8_t)0x80) +#define makeEnhancedByte1(cmd, data) (uint8_t)(ENH_BYTE1 | ((cmd) << 2) | (((data)&0xc0) >> 6)) +#define makeEnhancedByte2(cmd, data) (uint8_t)(ENH_BYTE2 | ((data)&0x3f)) +#define makeEnhancedSequence(cmd, data) {makeEnhancedByte1(cmd, data), makeEnhancedByte2(cmd, data)} + + +/** + * Interface for an enhanced @a Device. + */ +class EnhancedDeviceInterface { + public: + /** + * Destructor. + */ + virtual ~EnhancedDeviceInterface() {} + + /** + * Check for a running extra infos request, wait for it to complete, + * and then send a new request for extra infos to enhanced device. + * @param infoId the ID of the info to request. + * @param wait true to wait for a running request to complete, false to send right away. + * @return @a RESULT_OK on success, or an error code otherwise. + */ + virtual result_t requestEnhancedInfo(symbol_t infoId, bool wait = true) = 0; // abstract + + /** + * Get the enhanced device version. + * @return @a a string with the version infos, or empty. + */ + virtual string getEnhancedVersion() const = 0; // abstract + + /** + * Retrieve/update all extra infos from an enhanced device. + * @return @a a string with the extra infos, or empty. + */ + virtual string getEnhancedInfos() = 0; // abstract +}; + +} // namespace ebusd + +#endif // LIB_EBUS_DEVICE_ENHANCED_H_ diff --git a/src/lib/ebus/device.cpp b/src/lib/ebus/device_trans.cpp similarity index 88% rename from src/lib/ebus/device.cpp rename to src/lib/ebus/device_trans.cpp index 55494bd15..97e6fa2b4 100755 --- a/src/lib/ebus/device.cpp +++ b/src/lib/ebus/device_trans.cpp @@ -20,7 +20,7 @@ # include #endif -#include "lib/ebus/device.h" +#include "lib/ebus/device_trans.h" #include #include #include @@ -35,33 +35,33 @@ using std::setw; using std::setprecision; using std::fixed; -// ebusd enhanced protocol IDs: -#define ENH_REQ_INIT ((uint8_t)0x0) -#define ENH_RES_RESETTED ((uint8_t)0x0) -#define ENH_REQ_SEND ((uint8_t)0x1) -#define ENH_RES_RECEIVED ((uint8_t)0x1) -#define ENH_REQ_START ((uint8_t)0x2) -#define ENH_RES_STARTED ((uint8_t)0x2) -#define ENH_REQ_INFO ((uint8_t)0x3) -#define ENH_RES_INFO ((uint8_t)0x3) -#define ENH_RES_FAILED ((uint8_t)0xa) -#define ENH_RES_ERROR_EBUS ((uint8_t)0xb) -#define ENH_RES_ERROR_HOST ((uint8_t)0xc) -// ebusd enhanced error codes for the ENH_RES_ERROR_* responses -#define ENH_ERR_FRAMING ((uint8_t)0x00) -#define ENH_ERR_OVERRUN ((uint8_t)0x01) -#define ENH_BYTE_FLAG ((uint8_t)0x80) -#define ENH_BYTE_MASK ((uint8_t)0xc0) -#define ENH_BYTE1 ((uint8_t)0xc0) -#define ENH_BYTE2 ((uint8_t)0x80) -#define makeEnhancedByte1(cmd, data) (uint8_t)(ENH_BYTE1 | ((cmd) << 2) | (((data)&0xc0) >> 6)) -#define makeEnhancedByte2(cmd, data) (uint8_t)(ENH_BYTE2 | ((data)&0x3f)) -#define makeEnhancedSequence(cmd, data) {makeEnhancedByte1(cmd, data), makeEnhancedByte2(cmd, data)} +result_t BaseDevice::startArbitration(symbol_t masterAddress) { + if (m_arbitrationCheck) { + if (masterAddress != SYN) { + return RESULT_ERR_ARB_RUNNING; // should not occur + } + return RESULT_OK; + } + m_arbitrationMaster = masterAddress; + return RESULT_OK; +} +bool BaseDevice::cancelRunningArbitration(ArbitrationState* arbitrationState) { + if (m_arbitrationMaster == SYN) { + return false; + } + if (arbitrationState) { + *arbitrationState = as_error; + } + m_arbitrationMaster = SYN; + m_arbitrationCheck = 0; + return true; +} -result_t PlainCharDevice::send(symbol_t value) { + +result_t PlainDevice::send(symbol_t value) { result_t result = m_transport->write(&value, 1); if (result == RESULT_OK && m_listener != nullptr) { m_listener->notifyDeviceData(&value, 1, false); @@ -69,7 +69,7 @@ result_t PlainCharDevice::send(symbol_t value) { return result; } -result_t PlainCharDevice::recv(unsigned int timeout, symbol_t* value, ArbitrationState* arbitrationState) { +result_t PlainDevice::recv(unsigned int timeout, symbol_t* value, ArbitrationState* arbitrationState) { if (m_arbitrationMaster != SYN && arbitrationState) { *arbitrationState = as_running; } @@ -133,32 +133,9 @@ result_t PlainCharDevice::recv(unsigned int timeout, symbol_t* value, Arbitratio return result; } -result_t CharDevice::startArbitration(symbol_t masterAddress) { - if (m_arbitrationCheck) { - if (masterAddress != SYN) { - return RESULT_ERR_ARB_RUNNING; // should not occur - } - return RESULT_OK; - } - m_arbitrationMaster = masterAddress; - return RESULT_OK; -} - -bool CharDevice::cancelRunningArbitration(ArbitrationState* arbitrationState) { - if (m_arbitrationMaster == SYN) { - return false; - } - if (arbitrationState) { - *arbitrationState = as_error; - } - m_arbitrationMaster = SYN; - m_arbitrationCheck = 0; - return true; -} - -void EnhancedCharDevice::formatInfo(ostringstream* ostream, bool verbose, bool prefix) { - CharDevice::formatInfo(ostream, verbose, prefix); +void EnhancedDevice::formatInfo(ostringstream* ostream, bool verbose, bool prefix) { + BaseDevice::formatInfo(ostream, verbose, prefix); if (prefix) { *ostream << ", enhanced"; return; @@ -179,14 +156,14 @@ void EnhancedCharDevice::formatInfo(ostringstream* ostream, bool verbose, bool p } } -void EnhancedCharDevice::formatInfoJson(ostringstream* ostream) const { +void EnhancedDevice::formatInfoJson(ostringstream* ostream) const { string ver = getEnhancedVersion(); if (!ver.empty()) { *ostream << ",\"dv\":\"" << ver << "\""; } } -result_t EnhancedCharDevice::requestEnhancedInfo(symbol_t infoId, bool wait) { +result_t EnhancedDevice::requestEnhancedInfo(symbol_t infoId, bool wait) { if (m_extraFatures == 0) { return RESULT_ERR_INVALID_ARG; } @@ -228,7 +205,7 @@ result_t EnhancedCharDevice::requestEnhancedInfo(symbol_t infoId, bool wait) { return result; } -string EnhancedCharDevice::getEnhancedInfos() { +string EnhancedDevice::getEnhancedInfos() { if (m_extraFatures == 0) { return ""; } @@ -281,7 +258,7 @@ string EnhancedCharDevice::getEnhancedInfos() { + m_enhInfoBusVoltage; } -result_t EnhancedCharDevice::send(symbol_t value) { +result_t EnhancedDevice::send(symbol_t value) { uint8_t buf[] = makeEnhancedSequence(ENH_REQ_SEND, value); result_t result = m_transport->write(buf, 2); if (result == RESULT_OK && m_listener != nullptr) { @@ -290,7 +267,7 @@ result_t EnhancedCharDevice::send(symbol_t value) { return result; } -result_t EnhancedCharDevice::recv(unsigned int timeout, symbol_t* value, ArbitrationState* arbitrationState) { +result_t EnhancedDevice::recv(unsigned int timeout, symbol_t* value, ArbitrationState* arbitrationState) { if (arbitrationState && m_arbitrationMaster != SYN) { *arbitrationState = as_running; } @@ -322,7 +299,7 @@ result_t EnhancedCharDevice::recv(unsigned int timeout, symbol_t* value, Arbitra return result; } -result_t EnhancedCharDevice::startArbitration(symbol_t masterAddress) { +result_t EnhancedDevice::startArbitration(symbol_t masterAddress) { if (m_arbitrationCheck) { if (masterAddress != SYN) { return RESULT_ERR_ARB_RUNNING; // should not occur @@ -345,16 +322,16 @@ result_t EnhancedCharDevice::startArbitration(symbol_t masterAddress) { return RESULT_OK; } -bool EnhancedCharDevice::cancelRunningArbitration(ArbitrationState* arbitrationState) { - if (!CharDevice::cancelRunningArbitration(arbitrationState)) { +bool EnhancedDevice::cancelRunningArbitration(ArbitrationState* arbitrationState) { + if (!BaseDevice::cancelRunningArbitration(arbitrationState)) { return false; } symbol_t buf[2] = makeEnhancedSequence(ENH_REQ_START, SYN); return m_transport->write(buf, 2) == RESULT_OK; } -result_t EnhancedCharDevice::notifyTransportStatus(bool opened) { - result_t result = CharDevice::notifyTransportStatus(opened); // always OK +result_t EnhancedDevice::notifyTransportStatus(bool opened) { + result_t result = BaseDevice::notifyTransportStatus(opened); // always OK if (opened) { symbol_t buf[2] = makeEnhancedSequence(ENH_REQ_INIT, 0x01); // extra feature: info result = m_transport->write(buf, 2); @@ -377,7 +354,7 @@ result_t EnhancedCharDevice::notifyTransportStatus(bool opened) { return result; } -result_t EnhancedCharDevice::handleEnhancedBufferedData(const uint8_t* data, size_t len, +result_t EnhancedDevice::handleEnhancedBufferedData(const uint8_t* data, size_t len, symbol_t* value, ArbitrationState* arbitrationState) { bool valueSet = false; bool sent = false; @@ -533,7 +510,7 @@ symbol_t* value, ArbitrationState* arbitrationState) { return more ? RESULT_CONTINUE : valueSet ? RESULT_OK : RESULT_ERR_TIMEOUT; } -void EnhancedCharDevice::notifyInfoRetrieved() { +void EnhancedDevice::notifyInfoRetrieved() { symbol_t id = m_infoBuf[0]; symbol_t* data = m_infoBuf+1; size_t len = m_infoLen-1; @@ -559,7 +536,7 @@ void EnhancedCharDevice::notifyInfoRetrieved() { stream << "firmware " << m_enhInfoVersion; if (len >= 5) { stream << ", jumpers 0x" << setw(2) << static_cast(data[4]); - m_enhInfoIsWifi = (data[4]&0x08)!=0; + m_enhInfoIsWifi = (data[4]&0x08) != 0; } stream << setfill(' '); // reset break; diff --git a/src/lib/ebus/device_trans.h b/src/lib/ebus/device_trans.h new file mode 100755 index 000000000..5868d4fe9 --- /dev/null +++ b/src/lib/ebus/device_trans.h @@ -0,0 +1,228 @@ +/* + * ebusd - daemon for communication with eBUS heating systems. + * Copyright (C) 2015-2023 John Baier + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LIB_EBUS_DEVICE_TRANS_H_ +#define LIB_EBUS_DEVICE_TRANS_H_ + +#include +#include +#include "lib/ebus/device.h" +#include "lib/ebus/device_enhanced.h" +#include "lib/ebus/transport.h" +#include "lib/ebus/result.h" +#include "lib/ebus/symbol.h" + +namespace ebusd { + +/** @file lib/ebus/device_trans.h + * Classes providing access to the eBUS via a @a Transport instance. + */ + +/** + * The base class for accessing an eBUS via a @a Transport instance. + */ +class BaseDevice : public Device, public TransportListener { + protected: + /** + * Construct a new instance. + * @param transport the @a Transport to use. + */ + explicit BaseDevice(Transport* transport) + : Device(), m_transport(transport), + m_arbitrationMaster(SYN), m_arbitrationCheck(0) { + } + + public: + /** + * Destructor. + */ + virtual ~BaseDevice() { + if (m_transport) { + delete m_transport; + m_transport = nullptr; + } + } + + // @copydoc + virtual const char* getName() const { return m_transport->getName(); } + + // @copydoc + virtual void formatInfo(ostringstream* output, bool verbose, bool prefix) { + if (prefix) { + *output << m_transport->getName() << ", " << m_transport->getTransportInfo(); + } else if (!m_transport->isValid()) { + *output << ", invalid"; + } + } + + // @copydoc + virtual void formatInfoJson(ostringstream* output) const {} + + // @copydoc + virtual result_t notifyTransportStatus(bool opened) { + m_listener->notifyDeviceStatus(!opened, opened ? "transport opened" : "transport closed"); + return RESULT_OK; + } + + // @copydoc + virtual void notifyTransportMessage(bool error, const char* message) { + m_listener->notifyDeviceStatus(error, message); + } + + // @copydoc + virtual result_t open() { return m_transport->open(); } + + // @copydoc + virtual bool isValid() { return m_transport->isValid(); } + + // @copydoc + virtual result_t startArbitration(symbol_t masterAddress); + + // @copydoc + virtual bool isArbitrating() const { return m_arbitrationMaster != SYN; } + + // @copydoc + virtual bool cancelRunningArbitration(ArbitrationState* arbitrationState); + + protected: + /** the @a Transport to use. */ + Transport* m_transport; + + /** the arbitration master address to send when in arbitration, or @a SYN. */ + symbol_t m_arbitrationMaster; + + /** >0 when in arbitration and the next received symbol needs to be checked against the sent master address, + * incremented with each received SYN when arbitration was not performed as expected and needs to be stopped. */ + size_t m_arbitrationCheck; +}; + + +class PlainDevice : public BaseDevice { + public: + /** + * Construct a new instance. + * @param transport the @a Transport to use. + */ + explicit PlainDevice(Transport* transport) + : BaseDevice(transport) { + } + + // @copydoc + result_t send(symbol_t value) override; + + // @copydoc + result_t recv(unsigned int timeout, symbol_t* value, ArbitrationState* arbitrationState) override; +}; + + +class EnhancedDevice : public BaseDevice, public EnhancedDeviceInterface { + public: + /** + * Construct a new instance. + * @param transport the @a Transport to use. + */ + explicit EnhancedDevice(Transport* transport) + : BaseDevice(transport), EnhancedDeviceInterface(), m_resetRequested(false), + m_extraFatures(0), m_infoReqTime(0), m_infoLen(0), m_infoPos(0), m_enhInfoIsWifi(false) { + } + + // @copydoc + void formatInfo(ostringstream* output, bool verbose, bool prefix) override; + + // @copydoc + void formatInfoJson(ostringstream* output) const override; + + // @copydoc + result_t send(symbol_t value) override; + + // @copydoc + result_t recv(unsigned int timeout, symbol_t* value, ArbitrationState* arbitrationState) override; + + // @copydoc + result_t startArbitration(symbol_t masterAddress) override; + + // @copydoc + virtual result_t notifyTransportStatus(bool opened); + + // @copydoc + bool supportsUpdateCheck() const override { return m_extraFatures & 0x01; } + + // @copydoc + virtual result_t requestEnhancedInfo(symbol_t infoId, bool wait = true); + + // @copydoc + virtual string getEnhancedVersion() const { return m_enhInfoVersion; } + + // @copydoc + virtual string getEnhancedInfos(); + + // @copydoc + virtual bool cancelRunningArbitration(ArbitrationState* arbitrationState); + + private: + /** + * Handle the already buffered enhanced data. + * @param value the reference in which the read byte value is stored. + * @param arbitrationState the variable in which to store the current/received arbitration state. + * @return the @a result_t code, especially RESULT_CONTINE if the value was set and more data is available immediately. + */ + result_t handleEnhancedBufferedData(const uint8_t* data, size_t len, symbol_t* value, + ArbitrationState* arbitrationState); + + /** + * Called when reception of an info ID was completed. + */ + void notifyInfoRetrieved(); + + /** whether the reset of the device was already requested. */ + bool m_resetRequested; + + /** the extra features supported by the device. */ + symbol_t m_extraFatures; + + /** the time of the last info request. */ + time_t m_infoReqTime; + + /** the info buffer expected length. */ + size_t m_infoLen; + + /** the info buffer write position. */ + size_t m_infoPos; + + /** the info buffer. */ + symbol_t m_infoBuf[16+1]; + + /** a string describing the enhanced device version. */ + string m_enhInfoVersion; + + /** whether the device is known to be connected via WIFI. */ + bool m_enhInfoIsWifi; + + /** a string describing the enhanced device temperature. */ + string m_enhInfoTemperature; + + /** a string describing the enhanced device supply voltage. */ + string m_enhInfoSupplyVoltage; + + /** a string describing the enhanced device bus voltage. */ + string m_enhInfoBusVoltage; +}; + +} // namespace ebusd + +#endif // LIB_EBUS_DEVICE_TRANS_H_ diff --git a/src/lib/ebus/protocol.cpp b/src/lib/ebus/protocol.cpp index 3d605f672..51932c59f 100644 --- a/src/lib/ebus/protocol.cpp +++ b/src/lib/ebus/protocol.cpp @@ -22,6 +22,7 @@ #include #include +#include "lib/ebus/device_trans.h" #include "lib/ebus/protocol.h" #include "lib/ebus/protocol_direct.h" #include "lib/utils/log.h" @@ -97,11 +98,11 @@ ProtocolHandler* ProtocolHandler::create(const ebus_protocol_config_t config, // support ens:/dev/, enh:/dev/, and /dev/ transport = new SerialTransport(name, config.extraLatency, !config.noDeviceCheck, speed); } - CharDevice* device; + Device* device; if (enhanced) { - device = new EnhancedCharDevice(transport); + device = new EnhancedDevice(transport); } else { - device = new PlainCharDevice(transport); + device = new PlainDevice(transport); } return new DirectProtocolHandler(config, device, listener); } diff --git a/src/lib/ebus/protocol_direct.h b/src/lib/ebus/protocol_direct.h index 984b5513b..8db3f5cee 100755 --- a/src/lib/ebus/protocol_direct.h +++ b/src/lib/ebus/protocol_direct.h @@ -65,8 +65,8 @@ class DirectProtocolHandler : public ProtocolHandler { * @param listener the @a ProtocolListener. */ DirectProtocolHandler(const ebus_protocol_config_t config, - CharDevice* device, ProtocolListener* listener) - : ProtocolHandler(config, device, listener), m_device(device), + Device* device, ProtocolListener* listener) + : ProtocolHandler(config, device, listener), m_lockCount(config.lockCount <= 3 ? 3 : config.lockCount), m_remainLockCount(config.lockCount == 0 ? 1 : 0), m_generateSynInterval(config.generateSyn ? 10*getMasterNumber(config.ownAddress)+SYN_TIMEOUT : 0), @@ -146,9 +146,6 @@ class DirectProtocolHandler : public ProtocolHandler { */ void messageCompleted(); - /** the @a CharDevice instance for accessing the bus. */ - CharDevice* m_device; - /** the number of AUTO-SYN symbols before sending is allowed after lost arbitration. */ unsigned int m_lockCount; From 5bf0ab8eaafe76a772fb2363a578618b2d949ca9 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 4 Feb 2024 09:36:20 +0100 Subject: [PATCH 223/345] updated year --- make_debian.sh | 2 +- src/ebusd/bushandler.cpp | 2 +- src/ebusd/bushandler.h | 2 +- src/ebusd/datahandler.cpp | 2 +- src/ebusd/datahandler.h | 2 +- src/ebusd/knxhandler.cpp | 2 +- src/ebusd/knxhandler.h | 2 +- src/ebusd/main.cpp | 2 +- src/ebusd/main.h | 2 +- src/ebusd/main_args.cpp | 2 +- src/ebusd/mainloop.cpp | 2 +- src/ebusd/mainloop.h | 2 +- src/ebusd/mqttclient.cpp | 2 +- src/ebusd/mqttclient.h | 2 +- src/ebusd/mqttclient_mosquitto.cpp | 2 +- src/ebusd/mqttclient_mosquitto.h | 2 +- src/ebusd/mqtthandler.cpp | 2 +- src/ebusd/mqtthandler.h | 2 +- src/ebusd/network.cpp | 2 +- src/ebusd/network.h | 2 +- src/ebusd/request.cpp | 2 +- src/ebusd/request.h | 2 +- src/ebusd/scan.cpp | 2 +- src/ebusd/scan.h | 2 +- src/lib/ebus/contrib/contrib.cpp | 2 +- src/lib/ebus/contrib/contrib.h | 2 +- src/lib/ebus/contrib/tem.cpp | 2 +- src/lib/ebus/contrib/tem.h | 2 +- src/lib/ebus/contrib/test/test_tem.cpp | 2 +- src/lib/ebus/data.cpp | 2 +- src/lib/ebus/data.h | 2 +- src/lib/ebus/datatype.cpp | 2 +- src/lib/ebus/datatype.h | 2 +- src/lib/ebus/device.h | 2 +- src/lib/ebus/device_enhanced.h | 2 +- src/lib/ebus/device_trans.cpp | 2 +- src/lib/ebus/device_trans.h | 2 +- src/lib/ebus/filereader.cpp | 2 +- src/lib/ebus/filereader.h | 2 +- src/lib/ebus/message.cpp | 2 +- src/lib/ebus/message.h | 2 +- src/lib/ebus/protocol.cpp | 2 +- src/lib/ebus/protocol.h | 2 +- src/lib/ebus/protocol_direct.cpp | 2 +- src/lib/ebus/protocol_direct.h | 2 +- src/lib/ebus/result.cpp | 2 +- src/lib/ebus/result.h | 2 +- src/lib/ebus/stringhelper.cpp | 2 +- src/lib/ebus/stringhelper.h | 2 +- src/lib/ebus/symbol.cpp | 2 +- src/lib/ebus/symbol.h | 2 +- src/lib/ebus/test/test_data.cpp | 2 +- src/lib/ebus/test/test_device.cpp | 2 +- src/lib/ebus/test/test_filereader.cpp | 2 +- src/lib/ebus/test/test_message.cpp | 2 +- src/lib/ebus/test/test_symbol.cpp | 2 +- src/lib/ebus/transport.cpp | 2 +- src/lib/ebus/transport.h | 2 +- src/lib/knx/knx.cpp | 2 +- src/lib/knx/knx.h | 2 +- src/lib/knx/knxd.h | 2 +- src/lib/knx/knxnet.h | 2 +- src/lib/utils/arg.cpp | 2 +- src/lib/utils/arg.h | 2 +- src/lib/utils/clock.cpp | 2 +- src/lib/utils/clock.h | 2 +- src/lib/utils/httpclient.cpp | 2 +- src/lib/utils/httpclient.h | 2 +- src/lib/utils/log.cpp | 2 +- src/lib/utils/log.h | 2 +- src/lib/utils/notify.h | 2 +- src/lib/utils/queue.h | 2 +- src/lib/utils/rotatefile.cpp | 2 +- src/lib/utils/rotatefile.h | 2 +- src/lib/utils/tcpsocket.cpp | 2 +- src/lib/utils/tcpsocket.h | 2 +- src/lib/utils/thread.cpp | 2 +- src/lib/utils/thread.h | 2 +- src/tools/ebusctl.cpp | 2 +- src/tools/ebusfeed.cpp | 2 +- src/tools/ebuspicloader.cpp | 2 +- 81 files changed, 81 insertions(+), 81 deletions(-) diff --git a/make_debian.sh b/make_debian.sh index 13a8ec17a..a0376cc11 100755 --- a/make_debian.sh +++ b/make_debian.sh @@ -1,6 +1,6 @@ #!/bin/sh # ebusd - daemon for communication with eBUS heating systems. -# Copyright (C) 2014-2022 John Baier +# Copyright (C) 2014-2024 John Baier # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/bushandler.cpp b/src/ebusd/bushandler.cpp index 74c93dbd5..88edb0cfc 100644 --- a/src/ebusd/bushandler.cpp +++ b/src/ebusd/bushandler.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier + * Copyright (C) 2014-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/bushandler.h b/src/ebusd/bushandler.h index 7bc185db7..c7cda7c98 100755 --- a/src/ebusd/bushandler.h +++ b/src/ebusd/bushandler.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier + * Copyright (C) 2014-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/datahandler.cpp b/src/ebusd/datahandler.cpp index 37fa33bf6..324105dc0 100755 --- a/src/ebusd/datahandler.cpp +++ b/src/ebusd/datahandler.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2016-2023 John Baier + * Copyright (C) 2016-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/datahandler.h b/src/ebusd/datahandler.h index c3c03a751..8b5a0dce2 100755 --- a/src/ebusd/datahandler.h +++ b/src/ebusd/datahandler.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2016-2023 John Baier + * Copyright (C) 2016-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/knxhandler.cpp b/src/ebusd/knxhandler.cpp index 4a912a566..a777df4ed 100644 --- a/src/ebusd/knxhandler.cpp +++ b/src/ebusd/knxhandler.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2022 John Baier + * Copyright (C) 2022-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/knxhandler.h b/src/ebusd/knxhandler.h index fcfcebc6d..156ae2206 100644 --- a/src/ebusd/knxhandler.h +++ b/src/ebusd/knxhandler.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2022 John Baier + * Copyright (C) 2022-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index 427aeb89f..6e5237852 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier + * Copyright (C) 2014-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/main.h b/src/ebusd/main.h index c8cdca63f..250747e5e 100755 --- a/src/ebusd/main.h +++ b/src/ebusd/main.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier + * Copyright (C) 2014-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/main_args.cpp b/src/ebusd/main_args.cpp index 230681c53..1fad13b1e 100755 --- a/src/ebusd/main_args.cpp +++ b/src/ebusd/main_args.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2023 John Baier + * Copyright (C) 2023-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index 79869fb12..b0bd5a303 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier + * Copyright (C) 2014-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/mainloop.h b/src/ebusd/mainloop.h index 3703a79ef..8b1bd281a 100644 --- a/src/ebusd/mainloop.h +++ b/src/ebusd/mainloop.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier + * Copyright (C) 2014-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/mqttclient.cpp b/src/ebusd/mqttclient.cpp index 2d9c127f1..7e60cb5c3 100644 --- a/src/ebusd/mqttclient.cpp +++ b/src/ebusd/mqttclient.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2023 John Baier + * Copyright (C) 2023-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/mqttclient.h b/src/ebusd/mqttclient.h index 965647ba6..551211003 100755 --- a/src/ebusd/mqttclient.h +++ b/src/ebusd/mqttclient.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2023 John Baier + * Copyright (C) 2023-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/mqttclient_mosquitto.cpp b/src/ebusd/mqttclient_mosquitto.cpp index 903496571..1c1a0a1ca 100755 --- a/src/ebusd/mqttclient_mosquitto.cpp +++ b/src/ebusd/mqttclient_mosquitto.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2023 John Baier + * Copyright (C) 2023-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/mqttclient_mosquitto.h b/src/ebusd/mqttclient_mosquitto.h index e08ca5eba..9098bd8c6 100755 --- a/src/ebusd/mqttclient_mosquitto.h +++ b/src/ebusd/mqttclient_mosquitto.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2023 John Baier + * Copyright (C) 2023-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/mqtthandler.cpp b/src/ebusd/mqtthandler.cpp index 20ec1f79b..5412b10cd 100755 --- a/src/ebusd/mqtthandler.cpp +++ b/src/ebusd/mqtthandler.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2016-2023 John Baier + * Copyright (C) 2016-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/mqtthandler.h b/src/ebusd/mqtthandler.h index db20f6dbc..432329ea6 100755 --- a/src/ebusd/mqtthandler.h +++ b/src/ebusd/mqtthandler.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2016-2023 John Baier + * Copyright (C) 2016-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/network.cpp b/src/ebusd/network.cpp index 31cc445d6..b60f38ed0 100644 --- a/src/ebusd/network.cpp +++ b/src/ebusd/network.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier , Roland Jax 2012-2014 + * Copyright (C) 2014-2024 John Baier , Roland Jax 2012-2014 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/network.h b/src/ebusd/network.h index 9f21735cc..2c8891eb3 100644 --- a/src/ebusd/network.h +++ b/src/ebusd/network.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier , Roland Jax 2012-2014 + * Copyright (C) 2014-2024 John Baier , Roland Jax 2012-2014 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/request.cpp b/src/ebusd/request.cpp index 383742a46..16739eeb6 100644 --- a/src/ebusd/request.cpp +++ b/src/ebusd/request.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2023 John Baier + * Copyright (C) 2023-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/request.h b/src/ebusd/request.h index 7bab1248b..f27828256 100644 --- a/src/ebusd/request.h +++ b/src/ebusd/request.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2023 John Baier + * Copyright (C) 2023-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/scan.cpp b/src/ebusd/scan.cpp index f3ee1fc88..fda44b6d9 100644 --- a/src/ebusd/scan.cpp +++ b/src/ebusd/scan.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier + * Copyright (C) 2014-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/scan.h b/src/ebusd/scan.h index 99d28fe92..754c1e5fe 100644 --- a/src/ebusd/scan.h +++ b/src/ebusd/scan.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier + * Copyright (C) 2014-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/contrib/contrib.cpp b/src/lib/ebus/contrib/contrib.cpp index 6cbdd077b..d7ebb6cc6 100755 --- a/src/lib/ebus/contrib/contrib.cpp +++ b/src/lib/ebus/contrib/contrib.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2016-2023 John Baier + * Copyright (C) 2016-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/contrib/contrib.h b/src/lib/ebus/contrib/contrib.h index 53790c125..db16a2b19 100755 --- a/src/lib/ebus/contrib/contrib.h +++ b/src/lib/ebus/contrib/contrib.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2016-2023 John Baier + * Copyright (C) 2016-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/contrib/tem.cpp b/src/lib/ebus/contrib/tem.cpp index e757c44e1..f1dacc59b 100755 --- a/src/lib/ebus/contrib/tem.cpp +++ b/src/lib/ebus/contrib/tem.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2016-2023 John Baier + * Copyright (C) 2016-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/contrib/tem.h b/src/lib/ebus/contrib/tem.h index 495ff5820..e46a8cb7a 100755 --- a/src/lib/ebus/contrib/tem.h +++ b/src/lib/ebus/contrib/tem.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2016-2023 John Baier + * Copyright (C) 2016-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/contrib/test/test_tem.cpp b/src/lib/ebus/contrib/test/test_tem.cpp index 656c8e906..84fc8fa8c 100755 --- a/src/lib/ebus/contrib/test/test_tem.cpp +++ b/src/lib/ebus/contrib/test/test_tem.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2016-2023 John Baier + * Copyright (C) 2016-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/data.cpp b/src/lib/ebus/data.cpp index 8defc41f9..7fbbacf7e 100644 --- a/src/lib/ebus/data.cpp +++ b/src/lib/ebus/data.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier + * Copyright (C) 2014-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/data.h b/src/lib/ebus/data.h index 810c34884..007877f81 100755 --- a/src/lib/ebus/data.h +++ b/src/lib/ebus/data.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier + * Copyright (C) 2014-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/datatype.cpp b/src/lib/ebus/datatype.cpp index 1d6a93e14..021ec65ef 100755 --- a/src/lib/ebus/datatype.cpp +++ b/src/lib/ebus/datatype.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier + * Copyright (C) 2014-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/datatype.h b/src/lib/ebus/datatype.h index e3bf9cba4..8b6395ba4 100755 --- a/src/lib/ebus/datatype.h +++ b/src/lib/ebus/datatype.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier + * Copyright (C) 2014-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/device.h b/src/lib/ebus/device.h index a3cbe5855..8d023ccc4 100755 --- a/src/lib/ebus/device.h +++ b/src/lib/ebus/device.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2015-2023 John Baier + * Copyright (C) 2015-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/device_enhanced.h b/src/lib/ebus/device_enhanced.h index fe59b6b0c..d1042f58b 100755 --- a/src/lib/ebus/device_enhanced.h +++ b/src/lib/ebus/device_enhanced.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2015-2023 John Baier + * Copyright (C) 2015-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/device_trans.cpp b/src/lib/ebus/device_trans.cpp index 97e6fa2b4..7d451f849 100755 --- a/src/lib/ebus/device_trans.cpp +++ b/src/lib/ebus/device_trans.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2015-2023 John Baier + * Copyright (C) 2015-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/device_trans.h b/src/lib/ebus/device_trans.h index 5868d4fe9..c516135dd 100755 --- a/src/lib/ebus/device_trans.h +++ b/src/lib/ebus/device_trans.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2015-2023 John Baier + * Copyright (C) 2015-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/filereader.cpp b/src/lib/ebus/filereader.cpp index 98072b290..a0be0022b 100755 --- a/src/lib/ebus/filereader.cpp +++ b/src/lib/ebus/filereader.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier + * Copyright (C) 2014-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/filereader.h b/src/lib/ebus/filereader.h index 906e1fde0..026972389 100755 --- a/src/lib/ebus/filereader.h +++ b/src/lib/ebus/filereader.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier + * Copyright (C) 2014-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/message.cpp b/src/lib/ebus/message.cpp index f7faa0519..3afb6862f 100644 --- a/src/lib/ebus/message.cpp +++ b/src/lib/ebus/message.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier + * Copyright (C) 2014-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/message.h b/src/lib/ebus/message.h index 21e8118bd..5875e66b7 100644 --- a/src/lib/ebus/message.h +++ b/src/lib/ebus/message.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier + * Copyright (C) 2014-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/protocol.cpp b/src/lib/ebus/protocol.cpp index 51932c59f..376be8101 100644 --- a/src/lib/ebus/protocol.cpp +++ b/src/lib/ebus/protocol.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier + * Copyright (C) 2014-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/protocol.h b/src/lib/ebus/protocol.h index 540945b8d..08bb49264 100755 --- a/src/lib/ebus/protocol.h +++ b/src/lib/ebus/protocol.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier + * Copyright (C) 2014-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/protocol_direct.cpp b/src/lib/ebus/protocol_direct.cpp index 372b3a662..67daec639 100644 --- a/src/lib/ebus/protocol_direct.cpp +++ b/src/lib/ebus/protocol_direct.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier + * Copyright (C) 2014-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/protocol_direct.h b/src/lib/ebus/protocol_direct.h index 8db3f5cee..0abcfc624 100755 --- a/src/lib/ebus/protocol_direct.h +++ b/src/lib/ebus/protocol_direct.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier + * Copyright (C) 2014-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/result.cpp b/src/lib/ebus/result.cpp index 5e1a3fa04..929b25ddf 100755 --- a/src/lib/ebus/result.cpp +++ b/src/lib/ebus/result.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier + * Copyright (C) 2014-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/result.h b/src/lib/ebus/result.h index bbdc3856a..96a733419 100755 --- a/src/lib/ebus/result.h +++ b/src/lib/ebus/result.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier + * Copyright (C) 2014-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/stringhelper.cpp b/src/lib/ebus/stringhelper.cpp index 22b27ad5c..a3e18301e 100644 --- a/src/lib/ebus/stringhelper.cpp +++ b/src/lib/ebus/stringhelper.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2022-2023 John Baier + * Copyright (C) 2022-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/stringhelper.h b/src/lib/ebus/stringhelper.h index 01561ee65..a7576f685 100644 --- a/src/lib/ebus/stringhelper.h +++ b/src/lib/ebus/stringhelper.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2022-2023 John Baier + * Copyright (C) 2022-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/symbol.cpp b/src/lib/ebus/symbol.cpp index d055ec5dc..92ef757bb 100755 --- a/src/lib/ebus/symbol.cpp +++ b/src/lib/ebus/symbol.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier + * Copyright (C) 2014-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/symbol.h b/src/lib/ebus/symbol.h index d0680517f..997df7024 100755 --- a/src/lib/ebus/symbol.h +++ b/src/lib/ebus/symbol.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier + * Copyright (C) 2014-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/test/test_data.cpp b/src/lib/ebus/test/test_data.cpp index 02d29b683..fc44392a8 100755 --- a/src/lib/ebus/test/test_data.cpp +++ b/src/lib/ebus/test/test_data.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier + * Copyright (C) 2014-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/test/test_device.cpp b/src/lib/ebus/test/test_device.cpp index f4290788f..0aa69b9aa 100755 --- a/src/lib/ebus/test/test_device.cpp +++ b/src/lib/ebus/test/test_device.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier + * Copyright (C) 2014-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/test/test_filereader.cpp b/src/lib/ebus/test/test_filereader.cpp index 30b6d1c42..72bf2eb49 100755 --- a/src/lib/ebus/test/test_filereader.cpp +++ b/src/lib/ebus/test/test_filereader.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier + * Copyright (C) 2014-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/test/test_message.cpp b/src/lib/ebus/test/test_message.cpp index da9029cf1..694919f23 100644 --- a/src/lib/ebus/test/test_message.cpp +++ b/src/lib/ebus/test/test_message.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier + * Copyright (C) 2014-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/test/test_symbol.cpp b/src/lib/ebus/test/test_symbol.cpp index 85aca5318..d0fd8ac77 100755 --- a/src/lib/ebus/test/test_symbol.cpp +++ b/src/lib/ebus/test/test_symbol.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier + * Copyright (C) 2014-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/transport.cpp b/src/lib/ebus/transport.cpp index 5d883ec31..60932ff61 100644 --- a/src/lib/ebus/transport.cpp +++ b/src/lib/ebus/transport.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2023 John Baier + * Copyright (C) 2023-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/transport.h b/src/lib/ebus/transport.h index ba3a83243..9a8542245 100755 --- a/src/lib/ebus/transport.h +++ b/src/lib/ebus/transport.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2023 John Baier + * Copyright (C) 2023-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/knx/knx.cpp b/src/lib/knx/knx.cpp index f47df2ec8..e894ab46f 100644 --- a/src/lib/knx/knx.cpp +++ b/src/lib/knx/knx.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2022-2023 John Baier + * Copyright (C) 2022-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/knx/knx.h b/src/lib/knx/knx.h index ea376fde0..aaf059a0d 100644 --- a/src/lib/knx/knx.h +++ b/src/lib/knx/knx.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2022-2023 John Baier + * Copyright (C) 2022-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/knx/knxd.h b/src/lib/knx/knxd.h index d4ac4aa73..97385c66b 100644 --- a/src/lib/knx/knxd.h +++ b/src/lib/knx/knxd.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2022-2023 John Baier + * Copyright (C) 2022-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/knx/knxnet.h b/src/lib/knx/knxnet.h index 73ce156f3..aefe77c1c 100644 --- a/src/lib/knx/knxnet.h +++ b/src/lib/knx/knxnet.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2022-2023 John Baier + * Copyright (C) 2022-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/arg.cpp b/src/lib/utils/arg.cpp index 957d62b11..afb972b8a 100755 --- a/src/lib/utils/arg.cpp +++ b/src/lib/utils/arg.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2023 John Baier + * Copyright (C) 2023-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/arg.h b/src/lib/utils/arg.h index 2cc98b2de..bb3d4e22d 100755 --- a/src/lib/utils/arg.h +++ b/src/lib/utils/arg.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2023 John Baier + * Copyright (C) 2023-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/clock.cpp b/src/lib/utils/clock.cpp index d1d355934..fc6991729 100755 --- a/src/lib/utils/clock.cpp +++ b/src/lib/utils/clock.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2015-2023 John Baier + * Copyright (C) 2015-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/clock.h b/src/lib/utils/clock.h index bab709693..0f3649c94 100755 --- a/src/lib/utils/clock.h +++ b/src/lib/utils/clock.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2015-2023 John Baier + * Copyright (C) 2015-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/httpclient.cpp b/src/lib/utils/httpclient.cpp index 48b9f6614..72c3a8568 100755 --- a/src/lib/utils/httpclient.cpp +++ b/src/lib/utils/httpclient.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2018-2023 John Baier + * Copyright (C) 2018-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/httpclient.h b/src/lib/utils/httpclient.h index f5645eac5..52f8de709 100755 --- a/src/lib/utils/httpclient.h +++ b/src/lib/utils/httpclient.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2018-2023 John Baier + * Copyright (C) 2018-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/log.cpp b/src/lib/utils/log.cpp index 8601c8a75..ed03f6def 100755 --- a/src/lib/utils/log.cpp +++ b/src/lib/utils/log.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier + * Copyright (C) 2014-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/log.h b/src/lib/utils/log.h index 9de57cb6a..1a4d78ba1 100755 --- a/src/lib/utils/log.h +++ b/src/lib/utils/log.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier + * Copyright (C) 2014-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/notify.h b/src/lib/utils/notify.h index ba2fc85ae..267bfb94c 100755 --- a/src/lib/utils/notify.h +++ b/src/lib/utils/notify.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier , Roland Jax 2012-2014 + * Copyright (C) 2014-2024 John Baier , Roland Jax 2012-2014 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/queue.h b/src/lib/utils/queue.h index b41bad26b..5b1e5d143 100755 --- a/src/lib/utils/queue.h +++ b/src/lib/utils/queue.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier + * Copyright (C) 2014-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/rotatefile.cpp b/src/lib/utils/rotatefile.cpp index 23535756a..7aa557af6 100755 --- a/src/lib/utils/rotatefile.cpp +++ b/src/lib/utils/rotatefile.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2016-2023 John Baier + * Copyright (C) 2016-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/rotatefile.h b/src/lib/utils/rotatefile.h index bd54b23ea..1903f8507 100755 --- a/src/lib/utils/rotatefile.h +++ b/src/lib/utils/rotatefile.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2016-2023 John Baier + * Copyright (C) 2016-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/tcpsocket.cpp b/src/lib/utils/tcpsocket.cpp index 4a1afa462..d3eae6124 100755 --- a/src/lib/utils/tcpsocket.cpp +++ b/src/lib/utils/tcpsocket.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2015-2023 John Baier , Roland Jax 2012-2014 + * Copyright (C) 2015-2024 John Baier , Roland Jax 2012-2014 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/tcpsocket.h b/src/lib/utils/tcpsocket.h index eec6c2ba6..429b487cf 100755 --- a/src/lib/utils/tcpsocket.h +++ b/src/lib/utils/tcpsocket.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier , Roland Jax 2012-2014 + * Copyright (C) 2014-2024 John Baier , Roland Jax 2012-2014 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/thread.cpp b/src/lib/utils/thread.cpp index 737087bfa..c734cd32b 100755 --- a/src/lib/utils/thread.cpp +++ b/src/lib/utils/thread.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier , Roland Jax 2012-2014 + * Copyright (C) 2014-2024 John Baier , Roland Jax 2012-2014 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/thread.h b/src/lib/utils/thread.h index 33675d52e..b39b65fb8 100755 --- a/src/lib/utils/thread.h +++ b/src/lib/utils/thread.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier , Roland Jax 2012-2014 + * Copyright (C) 2014-2024 John Baier , Roland Jax 2012-2014 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/tools/ebusctl.cpp b/src/tools/ebusctl.cpp index 95b240d39..23d1dd4bc 100755 --- a/src/tools/ebusctl.cpp +++ b/src/tools/ebusctl.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier , Roland Jax 2012-2014 + * Copyright (C) 2014-2024 John Baier , Roland Jax 2012-2014 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/tools/ebusfeed.cpp b/src/tools/ebusfeed.cpp index fbc89339a..80ab506b6 100755 --- a/src/tools/ebusfeed.cpp +++ b/src/tools/ebusfeed.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2023 John Baier , Roland Jax 2012-2014 + * Copyright (C) 2014-2024 John Baier , Roland Jax 2012-2014 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/tools/ebuspicloader.cpp b/src/tools/ebuspicloader.cpp index ab59bb642..6ebb03629 100644 --- a/src/tools/ebuspicloader.cpp +++ b/src/tools/ebuspicloader.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2020-2023 John Baier + * Copyright (C) 2020-2024 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by From 8298eaa40112235b5d246003d70e3a35fb59023e Mon Sep 17 00:00:00 2001 From: John Date: Sun, 4 Feb 2024 10:33:21 +0100 Subject: [PATCH 224/345] make method const --- src/lib/ebus/symbol.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/ebus/symbol.h b/src/lib/ebus/symbol.h index 997df7024..e3d0f489c 100755 --- a/src/lib/ebus/symbol.h +++ b/src/lib/ebus/symbol.h @@ -330,7 +330,7 @@ class SymbolString { * Return whether the byte sequence is complete with regard to the header and length field. * @return true if the sequence is complete. */ - bool isComplete() { + bool isComplete() const { size_t lengthOffset = (m_isMaster ? 4 : 0); if (m_data.size() < lengthOffset + 1) { return false; From fa5c95958746b5c39185a141e86e20c624d0213a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 04:12:24 +0000 Subject: [PATCH 225/345] Bump codecov/codecov-action from 3 to 4 Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3 to 4. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v3...v4) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/coverage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index cb671fb0c..b0c3b9c54 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -40,7 +40,7 @@ jobs: run: ./test_coverage.sh - name: push result - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: name: codecov-umbrella fail_ci_if_error: true From 660e496216d88c66a8afa7996bc3ba43bf4baf29 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Feb 2024 04:20:16 +0000 Subject: [PATCH 226/345] Bump proudust/gh-describe from 1.6.0 to 2.0.0 Bumps [proudust/gh-describe](https://github.com/proudust/gh-describe) from 1.6.0 to 2.0.0. - [Release notes](https://github.com/proudust/gh-describe/releases) - [Commits](https://github.com/proudust/gh-describe/compare/v1.6.0...v2.0.0) --- updated-dependencies: - dependency-name: proudust/gh-describe dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yml | 2 +- .github/workflows/preparerelease.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ad4f539ca..05711904b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,7 +33,7 @@ jobs: - name: gh-describe id: gittag - uses: proudust/gh-describe@v1.6.0 + uses: proudust/gh-describe@v2.0.0 - name: set up QEMU uses: docker/setup-qemu-action@v3 diff --git a/.github/workflows/preparerelease.yml b/.github/workflows/preparerelease.yml index d2e75f782..fc7495f83 100644 --- a/.github/workflows/preparerelease.yml +++ b/.github/workflows/preparerelease.yml @@ -40,7 +40,7 @@ jobs: - name: gh-describe id: gittag - uses: proudust/gh-describe@v1.6.0 + uses: proudust/gh-describe@v2.0.0 - name: set up QEMU uses: docker/setup-qemu-action@v3 From ce8dd9620594656397ce62fff39a57745443f37d Mon Sep 17 00:00:00 2001 From: John Date: Sat, 17 Feb 2024 12:26:48 +0100 Subject: [PATCH 227/345] add missed listener --- src/lib/ebus/device_trans.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/ebus/device_trans.h b/src/lib/ebus/device_trans.h index c516135dd..188cc9f72 100755 --- a/src/lib/ebus/device_trans.h +++ b/src/lib/ebus/device_trans.h @@ -45,6 +45,7 @@ class BaseDevice : public Device, public TransportListener { explicit BaseDevice(Transport* transport) : Device(), m_transport(transport), m_arbitrationMaster(SYN), m_arbitrationCheck(0) { + transport->setListener(this); } public: From 6d49fd707af9a8ab4f023c6f754ae245969ee064 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 24 Feb 2024 11:53:58 +0100 Subject: [PATCH 228/345] don't skip initial data on tcp --- src/lib/ebus/transport.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/ebus/transport.cpp b/src/lib/ebus/transport.cpp index 60932ff61..fb40ede6d 100644 --- a/src/lib/ebus/transport.cpp +++ b/src/lib/ebus/transport.cpp @@ -328,13 +328,13 @@ result_t NetworkTransport::openInternal() { if (m_fd < 0) { return RESULT_ERR_GENERIC_IO; } - if (!m_udp) { - usleep(25000); // wait 25ms for potential initial garbage - } int cnt; symbol_t buf[MTU]; int ioerr; while ((ioerr=ioctl(m_fd, FIONREAD, &cnt)) >= 0 && cnt > 1) { + if (!m_udp) { + break; // no need to skip anything on a fresh TCP connection + } // skip buffered input ssize_t read = ::read(m_fd, &buf, MTU); if (read <= 0) { From 3d623659b17e83897509fbc7cf9c04c92028a663 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 24 Feb 2024 13:11:42 +0100 Subject: [PATCH 229/345] allow device to start with reset message, spelling --- src/lib/ebus/device_trans.cpp | 28 +++++++++++++++++++++------- src/lib/ebus/device_trans.h | 11 +++++++---- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/lib/ebus/device_trans.cpp b/src/lib/ebus/device_trans.cpp index 7d451f849..1a855bac2 100755 --- a/src/lib/ebus/device_trans.cpp +++ b/src/lib/ebus/device_trans.cpp @@ -134,6 +134,9 @@ result_t PlainDevice::recv(unsigned int timeout, symbol_t* value, ArbitrationSta } +/** the features requested. */ +#define REQUEST_FEATURES 0x01 + void EnhancedDevice::formatInfo(ostringstream* ostream, bool verbose, bool prefix) { BaseDevice::formatInfo(ostream, verbose, prefix); if (prefix) { @@ -164,7 +167,7 @@ void EnhancedDevice::formatInfoJson(ostringstream* ostream) const { } result_t EnhancedDevice::requestEnhancedInfo(symbol_t infoId, bool wait) { - if (m_extraFatures == 0) { + if (m_extraFeatures == 0) { return RESULT_ERR_INVALID_ARG; } if (wait) { @@ -206,7 +209,7 @@ result_t EnhancedDevice::requestEnhancedInfo(symbol_t infoId, bool wait) { } string EnhancedDevice::getEnhancedInfos() { - if (m_extraFatures == 0) { + if (m_extraFeatures == 0) { return ""; } result_t res; @@ -333,15 +336,17 @@ bool EnhancedDevice::cancelRunningArbitration(ArbitrationState* arbitrationState result_t EnhancedDevice::notifyTransportStatus(bool opened) { result_t result = BaseDevice::notifyTransportStatus(opened); // always OK if (opened) { - symbol_t buf[2] = makeEnhancedSequence(ENH_REQ_INIT, 0x01); // extra feature: info + symbol_t buf[2] = makeEnhancedSequence(ENH_REQ_INIT, REQUEST_FEATURES); // extra feature: info result = m_transport->write(buf, 2); if (result != RESULT_OK) { return result; } + m_resetTime = time(NULL); m_resetRequested = true; } else { // reset state - m_extraFatures = 0; + m_resetTime = 0; + m_extraFeatures = 0; m_infoLen = 0; m_enhInfoVersion = ""; m_enhInfoIsWifi = false; @@ -439,13 +444,22 @@ symbol_t* value, ArbitrationState* arbitrationState) { m_enhInfoSupplyVoltage = ""; m_enhInfoBusVoltage = ""; m_infoLen = 0; - m_extraFatures = data; + if (!m_resetRequested && m_resetTime+3 >= time(NULL)) { + if (data == m_extraFeatures) { + // skip explicit response to init request + valueSet = false; + break; + } + // response to init request had different feature flags + m_resetRequested = true; + } + m_extraFeatures = data; if (m_listener != nullptr) { - m_listener->notifyDeviceStatus(false, (m_extraFatures&0x01) ? "reset, supports info" : "reset"); + m_listener->notifyDeviceStatus(false, (m_extraFeatures&0x01) ? "reset, supports info" : "reset"); } if (m_resetRequested) { m_resetRequested = false; - if (m_extraFatures&0x01) { + if (m_extraFeatures&0x01) { requestEnhancedInfo(0, false); // request version, ignore result } valueSet = false; diff --git a/src/lib/ebus/device_trans.h b/src/lib/ebus/device_trans.h index 188cc9f72..2a69034ba 100755 --- a/src/lib/ebus/device_trans.h +++ b/src/lib/ebus/device_trans.h @@ -138,8 +138,8 @@ class EnhancedDevice : public BaseDevice, public EnhancedDeviceInterface { * @param transport the @a Transport to use. */ explicit EnhancedDevice(Transport* transport) - : BaseDevice(transport), EnhancedDeviceInterface(), m_resetRequested(false), - m_extraFatures(0), m_infoReqTime(0), m_infoLen(0), m_infoPos(0), m_enhInfoIsWifi(false) { + : BaseDevice(transport), EnhancedDeviceInterface(), m_resetTime(0), m_resetRequested(false), + m_extraFeatures(0), m_infoReqTime(0), m_infoLen(0), m_infoPos(0), m_enhInfoIsWifi(false) { } // @copydoc @@ -161,7 +161,7 @@ class EnhancedDevice : public BaseDevice, public EnhancedDeviceInterface { virtual result_t notifyTransportStatus(bool opened); // @copydoc - bool supportsUpdateCheck() const override { return m_extraFatures & 0x01; } + bool supportsUpdateCheck() const override { return m_extraFeatures & 0x01; } // @copydoc virtual result_t requestEnhancedInfo(symbol_t infoId, bool wait = true); @@ -190,11 +190,14 @@ class EnhancedDevice : public BaseDevice, public EnhancedDeviceInterface { */ void notifyInfoRetrieved(); + /** the time when the transport was resetted. */ + time_t m_resetTime; + /** whether the reset of the device was already requested. */ bool m_resetRequested; /** the extra features supported by the device. */ - symbol_t m_extraFatures; + symbol_t m_extraFeatures; /** the time of the last info request. */ time_t m_infoReqTime; From d6ea68db9424a71febdd8bccd6f940f6f090f576 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 24 Feb 2024 13:25:07 +0100 Subject: [PATCH 230/345] simplify log facility handling --- src/ebusd/main.cpp | 2 +- src/lib/utils/log.cpp | 8 ++------ src/lib/utils/log.h | 5 +---- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index 6e5237852..9d39b8a77 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -256,7 +256,7 @@ int main(int argc, char* argv[], char* envp[]) { } if (s_opt.logAreas != -1 || s_opt.logLevel != ll_COUNT) { - setFacilitiesLogLevel(LF_ALL, ll_none); + setFacilitiesLogLevel(1< Date: Fri, 8 Mar 2024 07:18:55 +0100 Subject: [PATCH 231/345] add option to answer request to non-own addresses (part of #42) --- src/ebusd/bushandler.cpp | 57 ++++++-------- src/ebusd/bushandler.h | 6 +- src/ebusd/main.cpp | 10 +++ src/ebusd/mainloop.cpp | 90 ++++++++++++++++++++- src/ebusd/mainloop.h | 11 ++- src/lib/ebus/protocol.h | 51 ++++++++---- src/lib/ebus/protocol_direct.cpp | 130 ++++++++++++++++++++++++------- src/lib/ebus/protocol_direct.h | 32 ++++++++ 8 files changed, 299 insertions(+), 88 deletions(-) diff --git a/src/ebusd/bushandler.cpp b/src/ebusd/bushandler.cpp index 88edb0cfc..20757f4f2 100644 --- a/src/ebusd/bushandler.cpp +++ b/src/ebusd/bushandler.cpp @@ -32,9 +32,6 @@ using std::setfill; using std::setw; using std::endl; -// the string used for answering to a scan request (07h 04h) -#define SCAN_ANSWER ("ebusd.eu;" PACKAGE_NAME ";" SCAN_VERSION ";100") - result_t PollRequest::prepare(symbol_t ownMasterAddress) { istringstream input; @@ -378,33 +375,11 @@ void BusHandler::notifyProtocolStatus(ProtocolState state, result_t result) { } } -result_t BusHandler::notifyProtocolAnswer(const MasterSymbolString& command, SlaveSymbolString* response) { - Message* message = m_messages->find(command); - if (message == nullptr) { - message = m_messages->find(command, true); - if (message != nullptr && message->getSrcAddress() != SYN) { - message = nullptr; - } - } - if (message == nullptr || message->isWrite()) { - // don't know this request or definition has wrong direction, deny - return RESULT_ERR_INVALID_ARG; - } - istringstream input; // TODO create input from database of internal variables - if (message == m_messages->getScanMessage() - || message == m_messages->getScanMessage(m_protocol->getOwnMasterAddress())) { - input.str(SCAN_ANSWER); - } - // build response and store in m_response for sending back to requesting master - return message->prepareSlave(&input, response); -} - - void BusHandler::notifyProtocolSeenAddress(symbol_t address) { m_seenAddresses[address] |= SEEN; } -void BusHandler::notifyProtocolMessage(bool sent, const MasterSymbolString& command, +void BusHandler::notifyProtocolMessage(MessageDirection direction, const MasterSymbolString& command, const SlaveSymbolString& response) { symbol_t srcAddress = command[0], dstAddress = command[1]; bool master = isMaster(dstAddress); @@ -465,7 +440,21 @@ void BusHandler::notifyProtocolMessage(bool sent, const MasterSymbolString& comm } m_grabbedMessages[key].setLastData(command, response); } - const char* prefix = sent ? "sent" : "received"; + if (direction == md_answer) { + size_t idLen = command.getDataSize(); + if (master && idLen >= response.size()) { + // build MS auto-answer from MM with same ID + SlaveSymbolString answer; + answer.push_back(0); // room for length + idLen -= response.size(); + for (size_t pos = idLen; pos < response.size(); pos++) { + answer.push_back(command.dataAt(pos)); + } + m_protocol->setAnswer(SYN, command[1], command[2], command[3], command.data() + 5, idLen, answer); + // TODO could use loaded messages for identifying MM/MS message pair + } + } + const char* prefix = direction == md_answer ? "answered" : direction == md_send ? "sent" : "received"; if (message == nullptr) { if (dstAddress == BROADCAST || master) { logNotice(lf_update, "%s unknown %s cmd: %s", prefix, master ? "MM" : "BC", command.getStr().c_str()); @@ -493,7 +482,7 @@ void BusHandler::notifyProtocolMessage(bool sent, const MasterSymbolString& comm string data = output.str(); if (m_protocol->isOwnAddress(dstAddress)) { logNotice(lf_update, "%s %s self-update %s %s QQ=%2.2x: %s", prefix, mode, circuit.c_str(), name.c_str(), - srcAddress, data.c_str()); // TODO store in database of internal variables + srcAddress, data.c_str()); } else if (message->getDstAddress() == SYN) { // any destination if (message->getSrcAddress() == SYN) { // any destination and any source logNotice(lf_update, "%s %s %s %s QQ=%2.2x ZZ=%2.2x: %s", prefix, mode, circuit.c_str(), name.c_str(), @@ -676,12 +665,12 @@ void BusHandler::formatSeenInfo(ostringstream* output) const { } if (ownAddress) { *output << ", ebusd"; - if (m_protocol->isAnswering()) { - *output << " (answering)"; - } - if (m_protocol->isAddressConflict(address)) { - *output << ", conflict"; - } + } + if (m_protocol->hasAnswer(address)) { + *output << " (answering)"; + } + if (ownAddress && m_protocol->isAddressConflict(address)) { + *output << ", conflict"; } if ((m_seenAddresses[address]&SCAN_DONE) != 0) { *output << ", scanned"; diff --git a/src/ebusd/bushandler.h b/src/ebusd/bushandler.h index c7cda7c98..ddf253baf 100755 --- a/src/ebusd/bushandler.h +++ b/src/ebusd/bushandler.h @@ -398,14 +398,12 @@ class BusHandler : public ProtocolListener { // @copydoc void notifyProtocolStatus(ProtocolState state, result_t result) override; - // @copydoc - result_t notifyProtocolAnswer(const MasterSymbolString& master, SlaveSymbolString* slave) override; - // @copydoc void notifyProtocolSeenAddress(symbol_t address) override; // @copydoc - void notifyProtocolMessage(bool sent, const MasterSymbolString& master, const SlaveSymbolString& slave) override; + void notifyProtocolMessage(MessageDirection direction, const MasterSymbolString& master, + const SlaveSymbolString& slave) override; private: /** diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index 9d39b8a77..10f044191 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -415,6 +415,16 @@ int main(int argc, char* argv[], char* envp[]) { return EINVAL; } s_busHandler->setProtocol(s_protocol); + if (s_opt.answer) { + istringstream input; + input.str("ebusd.eu;" PACKAGE_NAME ";" SCAN_VERSION ";100"); + Message* message = s_messageMap->getScanMessage(); + SlaveSymbolString response; + if (message && message->prepareSlave(&input, &response) == RESULT_OK) { + s_protocol->setAnswer(SYN, s_protocol->getOwnSlaveAddress(), message->getPrimaryCommand(), + message->getSecondaryCommand(), nullptr, 0, response); + } + } if (!s_opt.foreground) { if (!setLogFile(s_opt.logFile)) { diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index b0bd5a303..6854fbf5c 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -484,6 +484,13 @@ result_t MainLoop::decodeRequest(Request* req, bool* connected, RequestMode* req *ostream << "ERR: command not enabled"; return RESULT_OK; } + if (cmd == "ANSWER") { + if (m_enableHex && !m_protocol->isReadOnly()) { + return executeAnswer(args, ostream); + } + *ostream << "ERR: command not enabled"; + return RESULT_OK; + } if (cmd == "F" || cmd == "FIND") { return executeFind(args, getUserLevels(*user), ostream); } @@ -1170,7 +1177,7 @@ result_t MainLoop::executeInject(const vector& args, ostringstream* ostr if (!m_scanHelper->parseMessage(args[argPos++], false, &master, &slave)) { return RESULT_ERR_INVALID_ARG; } - m_busHandler->notifyProtocolMessage(false, master, slave); + m_busHandler->notifyProtocolMessage(md_recv, master, slave); return RESULT_OK; } *ostream << "usage: inject QQZZPBSBNN[DD]*/[NN[DD]*]\n" @@ -1183,6 +1190,84 @@ result_t MainLoop::executeInject(const vector& args, ostringstream* ostr return RESULT_OK; } +result_t MainLoop::executeAnswer(const vector& args, ostringstream* ostream) { + size_t argPos = 1; + symbol_t srcAddress = SYN; + symbol_t dstAddress = SYN; + bool master = false; + while (args.size() > argPos && args[argPos][0] == '-') { + if (args[argPos] == "-s" && argPos + 1 < args.size()) { + result_t ret; + argPos++; + 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; + } else if (args[argPos] == "-d" && argPos + 1 < args.size()) { + result_t ret; + argPos++; + symbol_t address = (symbol_t)parseInt(args[argPos].c_str(), 16, 0, 0xff, &ret); + if (ret != RESULT_OK || !isValidAddress(address)) { + return RESULT_ERR_INVALID_ADDR; + } + dstAddress = address; + } else if (args[argPos] == "-m") { + master = true; + } else { + argPos = 0; // print usage + break; + } + argPos++; + } + MasterSymbolString id; + if (argPos > 0 && argPos < args.size()) { + result_t ret = id.parseHex(args[argPos++]); + if (ret != RESULT_OK) { + return ret; + } + if (id.size() < 2 || id.size() > 6) { + return RESULT_ERR_INVALID_POS; + } + } + SlaveSymbolString answer; + if (argPos > 0 && argPos < args.size()) { + answer.push_back(0); // room for length byte + result_t ret = answer.parseHex(args[argPos++]); + if (ret != RESULT_OK) { + return ret; + } + if (answer.size() > 16) { + return RESULT_ERR_INVALID_POS; + } + answer[0] = (symbol_t)(answer.size()-1); + } + if (argPos < args.size()) { + argPos = 0; // print usage + } + if (argPos <= 1) { + *ostream << "usage: answer [-m] [-s QQ] [-d ZZ] PBSB[ID]* [DD]*\n" + " Answer to a message from the bus.\n" + " -m destination is a master\n" + " -s QQ source address to limit to\n" + " -d ZZ override destination address (instead of own address)\n" + " PB SB primary/secondary command byte\n" + " ID further ID bytes\n" + " DD data bytes (only length used with -m)"; + return RESULT_OK; + } + if (isMaster(dstAddress)) { + master = true; + } else if (dstAddress == SYN) { + dstAddress = master ? m_address : getSlaveAddress(m_address); + } + if (!m_protocol->setAnswer(srcAddress, dstAddress, id[0], id[1], id.data()+2 + , id.size()-2, answer)) { + return RESULT_ERR_INVALID_ARG; + } + return RESULT_OK; +} + result_t MainLoop::executeDirect(const vector& args, RequestMode* reqMode, ostringstream* ostream) { if (reqMode->listenMode != lm_direct) { if (args.size() == 1) { @@ -1915,12 +2000,13 @@ result_t MainLoop::executeHelp(ostringstream* ostream) { " listen|l Listen for updates: listen [-v|-V] [-n|-N] [-u|-U] [stop]\n" " hex Send hex data: hex [-s QQ] [-n] ZZPBSB[NN][DD]* (if enabled)\n" " inject Inject hex data: inject QQZZPBSBNN[DD]*/[NN[DD]*] (if enabled)\n" + " answer Answer a message: answer [-m] [-s QQ] [-d ZZ] PBSB[ID]* [DD]* (if enabled)\n" " direct Enter direct mode\n" " state|s Report bus state\n" " info|i Report information about the daemon, configuration, seen participants, and the device.\n" " grab|g Grab messages: grab [stop]\n" " Report the messages: grab result [all|decode]\n" - " define Define new message: define [-r] DEFINITION\n" + " define Define new message: define [-r] DEFINITION (if enabled)\n" " decode|d Decode field(s): decode [-v|-V] [-n|-N] DEFINITION DD[DD]*\n" " encode|e Encode field(s): encode DEFINITION VALUE[;VALUE]*\n" " scan Scan slaves: scan [full|ZZ]\n" diff --git a/src/ebusd/mainloop.h b/src/ebusd/mainloop.h index 8b1bd281a..599df1e79 100644 --- a/src/ebusd/mainloop.h +++ b/src/ebusd/mainloop.h @@ -224,6 +224,15 @@ class MainLoop : public Thread { */ result_t executeDirect(const vector& args, RequestMode* reqMode, ostringstream* ostream); + /** + * Execute the answer command. + * @param args the arguments passed to the command (starting with the command itself), or empty for help. + * @param reqMode the @a RequestMode to use and update. + * @param ostream the @a ostringstream to format the result string to. + * @return the result code. + */ + result_t executeAnswer(const vector& args, ostringstream* ostream); + /** * Execute the find command. * @param args the arguments passed to the command (starting with the command itself), or empty for help. @@ -403,7 +412,7 @@ class MainLoop : public Thread { /** true when the poll interval is non zero. */ const bool m_polling; - /** whether to enable the hex command. */ + /** whether to enable the hex, inject, and answer commands. */ const bool m_enableHex; /** the MessageMap for handling newly defined messages for testing (if enabled), or nullptr. */ diff --git a/src/lib/ebus/protocol.h b/src/lib/ebus/protocol.h index 08bb49264..f5fbdcec9 100755 --- a/src/lib/ebus/protocol.h +++ b/src/lib/ebus/protocol.h @@ -204,6 +204,13 @@ class ActiveBusRequest : public BusRequest { }; +/** the possible message directions. */ +enum MessageDirection { + md_recv, //!< message received from bus + md_send, //!< message sent to bus + md_answer, //!< answered to a message received from bus +}; + /** * Interface for listening to eBUS protocol data. */ @@ -229,22 +236,13 @@ class ProtocolListener { /** * Listener method that is called when a message was sent or received. - * @param sent true when the master part was actively sent, false if the whole message - * was received only. - * @param master the @a MasterSymbolString received. - * @param slave the @a SlaveSymbolString received. + * @param direction the message direction. + * @param master the @a MasterSymbolString received/sent. + * @param slave the @a SlaveSymbolString received/sent or the answer passed to @a ProtocolHandler::setAnswer() with + * the the length of the data part following the ID as master. */ - virtual void notifyProtocolMessage(bool sent, const MasterSymbolString& master, + virtual void notifyProtocolMessage(MessageDirection direction, const MasterSymbolString& master, const SlaveSymbolString& slave) = 0; // abstract - - /** - * Listener method that is called when in answer mode and a message targeting ourself was received. - * @param master the @a MasterSymbolString received. - * @param slave the @a SlaveSymbolString for writing the response to. - * @return @a RESULT_OK on success, or an error code. - */ - virtual result_t notifyProtocolAnswer(const MasterSymbolString& master, - SlaveSymbolString* slave) = 0; // abstract }; @@ -350,9 +348,28 @@ class ProtocolHandler : public WaitThread, public DeviceListener { symbol_t getOwnSlaveAddress() const { return m_ownSlaveAddress; } /** - * @return @p true if answering queries for the own master/slave address (if not readonly). + * @return @p true if answering queries (if not readonly). + */ + virtual bool isAnswering() const { return false; } + + /** + * Add a message to be answered. + * @param srcAddress the source address to limit to, or @a SYN for any. + * @param dstAddress the destination address (either master or slave address). + * @param pb the primary ID byte. + * @param sb the secondary ID byte. + * @param id optional further ID bytes. + * @param idLen the length of the further ID bytes (maximum 4). + * @param answer the sequence to respond when addressed as slave or the length of the data part following the ID as master. + * @return @p true on success, @p false on error (e.g. invalid address, read only, or too long id). + */ + virtual bool setAnswer(symbol_t srcAddress, symbol_t dstAddress, symbol_t pb, symbol_t sb, const symbol_t* id, + size_t idLen, const SlaveSymbolString& answer) { return false; } + + /** + * @return @p true if an answer was set for the destination address. */ - bool isAnswering() const { return m_config.answer; } + virtual bool hasAnswer(symbol_t dstAddress) const { return false; } /** * @param address the address to check. @@ -520,7 +537,7 @@ class ProtocolHandler : public WaitThread, public DeviceListener { */ virtual bool addSeenAddress(symbol_t address); - /** the client configuration to use. */ + /** the configuration to use. */ const ebus_protocol_config_t m_config; /** the @a Device instance for accessing the bus. */ diff --git a/src/lib/ebus/protocol_direct.cpp b/src/lib/ebus/protocol_direct.cpp index 67daec639..d9fa0e305 100644 --- a/src/lib/ebus/protocol_direct.cpp +++ b/src/lib/ebus/protocol_direct.cpp @@ -232,21 +232,21 @@ struct timespec* sentTime) { break; case bs_sendCmdAck: - if (m_config.answer) { + if (m_currentAnswering) { sendSymbol = m_crcValid ? ACK : NAK; sending = true; } break; case bs_sendRes: - if (m_config.answer) { + if (m_currentAnswering) { sendSymbol = m_response[m_nextSendPos]; // unescaped response sending = true; } break; case bs_sendResCrc: - if (m_config.answer) { + if (m_currentAnswering) { sendSymbol = m_crc; sending = true; } @@ -511,20 +511,10 @@ struct timespec* sentTime) { } return setState(bs_skip, RESULT_ERR_CRC); } - if (m_config.answer) { - symbol_t dstAddress = m_command[1]; - if (dstAddress == m_ownMasterAddress || dstAddress == m_ownSlaveAddress) { - if (m_crcValid) { - addSeenAddress(m_command[0]); - m_currentAnswering = true; - return setState(bs_sendCmdAck, result); - } - return setState(bs_sendCmdAck, RESULT_ERR_CRC); - } - } if (m_crcValid) { addSeenAddress(m_command[0]); - return setState(bs_recvCmdAck, result); + m_currentAnswering = getAnswer(); + return setState(m_currentAnswering ? bs_sendCmdAck : bs_recvCmdAck, result); } if (m_repeat) { return setState(bs_skip, RESULT_ERR_CRC); @@ -646,7 +636,7 @@ struct timespec* sentTime) { return setState(bs_sendSyn, result); case bs_sendCmdAck: - if (!sending || !m_config.answer) { + if (!sending || !m_currentAnswering) { return setState(bs_skip, RESULT_ERR_INVALID_ARG); } if (!m_crcValid) { @@ -658,25 +648,18 @@ struct timespec* sentTime) { } return setState(bs_skip, RESULT_ERR_ACK); } + // response to send was already prepared during bs_recvCmdCrc in m_response if (isMaster(m_command[1])) { - messageCompleted(); // TODO decode command and store value into database of internal variables + messageCompleted(); return setState(bs_skip, result); } m_nextSendPos = 0; m_repeat = false; - // build response and store in m_response for sending back to requesting master - m_response.clear(); - { - result_t result = m_listener->notifyProtocolAnswer(m_command, &m_response); - if (result != RESULT_OK) { - return setState(bs_skip, result); - } - } return setState(bs_sendRes, result); case bs_sendRes: - if (!sending || !m_config.answer) { + if (!sending || !m_currentAnswering) { return setState(bs_skip, RESULT_ERR_INVALID_ARG); } m_nextSendPos++; @@ -687,7 +670,7 @@ struct timespec* sentTime) { return result; case bs_sendResCrc: - if (!sending || !m_config.answer) { + if (!sending || !m_currentAnswering) { return setState(bs_skip, RESULT_ERR_INVALID_ARG); } return setState(bs_recvResAck, result); @@ -801,7 +784,6 @@ bool DirectProtocolHandler::addSeenAddress(symbol_t address) { } void DirectProtocolHandler::messageCompleted() { - const char* prefix = m_currentRequest ? "sent" : "received"; // do an explicit copy here in case being called by another thread const MasterSymbolString command(m_currentRequest ? m_currentRequest->getMaster() : m_command); const SlaveSymbolString response(m_response); @@ -810,17 +792,105 @@ void DirectProtocolHandler::messageCompleted() { logError(lf_bus, "invalid self-addressed message from %2.2x", srcAddress); return; } - if (!m_currentAnswering) { + if (!m_currentAnswering || (dstAddress != m_ownMasterAddress && dstAddress != m_ownSlaveAddress)) { + // also add given answers to list of seen addresses addSeenAddress(dstAddress); } + const char* prefix = m_currentAnswering ? "answered" : m_currentRequest ? "sent" : "received"; + MessageDirection direction = m_currentAnswering ? md_answer : m_currentRequest ? md_send : md_recv; bool master = isMaster(dstAddress); if (dstAddress == BROADCAST || master) { logInfo(lf_update, "%s %s cmd: %s", prefix, master ? "MM" : "BC", command.getStr().c_str()); } else { logInfo(lf_update, "%s MS cmd: %s / %s", prefix, command.getStr().c_str(), response.getStr().c_str()); } - m_listener->notifyProtocolMessage(m_currentRequest != nullptr, command, response); + m_listener->notifyProtocolMessage(direction, command, response); +} + +uint64_t DirectProtocolHandler::createAnswerKey(symbol_t srcAddress, symbol_t dstAddress, symbol_t pb, symbol_t sb, + const symbol_t* id, size_t idLen) { + uint64_t key = (uint64_t)idLen << (8 * 7 + 5); + key |= (uint64_t)getMasterNumber(srcAddress) << (8 * 7); // 0..25 + key |= (uint64_t)dstAddress << (8 * 6); + key |= (uint64_t)pb << (8 * 5); + key |= (uint64_t)sb << (8 * 4); + int exp = 3; + for (size_t pos = 0; pos < idLen; pos++) { + key |= (uint64_t)id[pos] << (8 * exp--); + } + return key; +} + +bool DirectProtocolHandler::setAnswer(symbol_t srcAddress, symbol_t dstAddress, symbol_t pb, symbol_t sb, + const symbol_t* id, size_t idLen, const SlaveSymbolString& answer) { + if (!m_config.answer || (!id && idLen > 0) || idLen > 4 || !isValidAddress(dstAddress, false) + || (srcAddress != SYN && !isMaster(srcAddress))) { + return false; + } + if (isMaster(dstAddress)) { + if (answer.size() > 7) { + return false; + } + // answer used here only for having the expected length of the MM data tail + } else { + if (!answer.isComplete()) { + return false; + } + } + uint64_t key = createAnswerKey(srcAddress, dstAddress, pb, sb, id, idLen); + m_answerByKey[key] = answer; + return true; +} + +bool DirectProtocolHandler::hasAnswer(symbol_t dstAddress) const { + if (m_answerByKey.empty()) { + return false; + } + for (auto const &answer : m_answerByKey) { + if ((answer.first >> (8 * 6)) == dstAddress) { + return true; + } + } + return false; +} + +bool DirectProtocolHandler::getAnswer() { + if (m_answerByKey.empty()) { + return false; + } + // walk through the stored answers to find the longest match + m_response.clear(); + size_t len = m_command[4]; + bool master = isMaster(m_command[1]); + uint64_t key = createAnswerKey(m_command[0], m_command[1], m_command[2], m_command[3], m_command.data()+5, len); + do { + auto it = m_answerByKey.find(key); + if (it == m_answerByKey.end()) { + it = m_answerByKey.find(key&~(0x1fLL << (8 * 7))); // without specific src + } + if (it != m_answerByKey.end()) { + // found the answer + if (master) { + if (len+it->second.size() == m_command[4]) { + m_response = it->second; // copied for having the data size only + return true; + } + // data length mismatch, find shorter one + } else { + m_response = it->second; + return true; + } + break; + } + if (len == 0) { + break; + } + // reduce the key + len--; + key = (key&~(0x07LL << (8 * 7 + 5))&~(0xffLL << (8 * (3-len)))) | (len << (8 * 7 + 5)); + } while (true); + return false; } } // namespace ebusd diff --git a/src/lib/ebus/protocol_direct.h b/src/lib/ebus/protocol_direct.h index 0abcfc624..93dfb0422 100755 --- a/src/lib/ebus/protocol_direct.h +++ b/src/lib/ebus/protocol_direct.h @@ -19,6 +19,7 @@ #ifndef LIB_EBUS_PROTOCOL_DIRECT_H_ #define LIB_EBUS_PROTOCOL_DIRECT_H_ +#include #include "lib/ebus/protocol.h" namespace ebusd { @@ -107,6 +108,15 @@ class DirectProtocolHandler : public ProtocolHandler { // @copydoc bool hasSignal() const override { return m_state != bs_noSignal; } + // @copydoc + bool isAnswering() const override { return !m_answerByKey.empty(); } + + // @copydoc + bool setAnswer(symbol_t srcAddress, symbol_t dstAddress, symbol_t pb, symbol_t sb, + const symbol_t* id, size_t idLen, const SlaveSymbolString& answer) override; + + // @copydoc + bool hasAnswer(symbol_t dstAddress) const override; private: /** @@ -146,6 +156,25 @@ class DirectProtocolHandler : public ProtocolHandler { */ void messageCompleted(); + /** + * Create a key for storing an answer. + * @param srcAddress the source address, or @a SYN for any. + * @param dstAddress the destination address. + * @param pb the primary ID byte. + * @param sb the secondary ID byte. + * @param id optional further ID bytes. + * @param idLen the length of the further ID bytes. + * @return a key for storing an answer. + */ + uint64_t createAnswerKey(symbol_t srcAddress, symbol_t dstAddress, symbol_t pb, symbol_t sb, + const symbol_t* id, size_t idLen); + + /** + * Build the answer to the currently received message and store in @a m_response for sending back to requestor. + * @return @p true on success, @p false if the message is not supposed to be answered. + */ + bool getAnswer(); + /** the number of AUTO-SYN symbols before sending is allowed after lost arbitration. */ unsigned int m_lockCount; @@ -161,6 +190,9 @@ class DirectProtocolHandler : public ProtocolHandler { /** the currently handled BusRequest, or nullptr. */ BusRequest* m_currentRequest; + /** the answers to give by key. */ + std::map m_answerByKey; + /** whether currently answering a request from another participant. */ bool m_currentAnswering; From 393c9f81e5aa2cda5d0c126b8f093293ff53ada5 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 9 Mar 2024 10:10:16 +0100 Subject: [PATCH 232/345] make port in device string optional, updated help, formatting --- src/ebusd/main.cpp | 2 +- src/ebusd/main.h | 4 ++-- src/ebusd/main_args.cpp | 8 ++++---- src/ebusd/mainloop.cpp | 3 +-- src/lib/ebus/protocol.cpp | 23 ++++++++++++----------- src/lib/ebus/protocol.h | 2 +- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index 10f044191..202c2396c 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -256,7 +256,7 @@ int main(int argc, char* argv[], char* envp[]) { } if (s_opt.logAreas != -1 || s_opt.logLevel != ll_COUNT) { - setFacilitiesLogLevel(1<& args, ostringstream* ostr } else if (dstAddress == SYN) { dstAddress = master ? m_address : getSlaveAddress(m_address); } - if (!m_protocol->setAnswer(srcAddress, dstAddress, id[0], id[1], id.data()+2 - , id.size()-2, answer)) { + if (!m_protocol->setAnswer(srcAddress, dstAddress, id[0], id[1], id.data()+2, id.size()-2, answer)) { return RESULT_ERR_INVALID_ARG; } return RESULT_OK; diff --git a/src/lib/ebus/protocol.cpp b/src/lib/ebus/protocol.cpp index 376be8101..1d80aee48 100644 --- a/src/lib/ebus/protocol.cpp +++ b/src/lib/ebus/protocol.cpp @@ -70,27 +70,28 @@ ProtocolHandler* ProtocolHandler::create(const ebus_protocol_config_t config, } } Transport* transport; - if (strchr(name, '/') == nullptr && strchr(name, ':') != nullptr) { + if (strchr(name, '/') == nullptr || strchr(name, ':') != nullptr) { char* in = strdup(name); bool udp = false; char* addrpos = in; char* portpos = strchr(addrpos, ':'); - // support tcp:: and udp:: + // support tcp:[:] and udp:[:] if (portpos == addrpos+3 && (strncmp(addrpos, "tcp", 3) == 0 || (udp=(strncmp(addrpos, "udp", 3) == 0)))) { addrpos += 4; portpos = strchr(addrpos, ':'); } + uint16_t port; if (portpos == nullptr) { - free(in); - return nullptr; // invalid protocol or missing port - } - result_t result = RESULT_OK; - uint16_t port = (uint16_t)parseInt(portpos+1, 10, 1, 65535, &result); - if (result != RESULT_OK) { - free(in); - return nullptr; // invalid port + port = 9999; + } else { + result_t result = RESULT_OK; + port = (uint16_t)parseInt(portpos+1, 10, 1, 65535, &result); + if (result != RESULT_OK) { + free(in); + return nullptr; // invalid port + } + *portpos = 0; } - *portpos = 0; char* hostOrIp = strdup(addrpos); free(in); transport = new NetworkTransport(name, config.extraLatency, hostOrIp, port, udp); diff --git a/src/lib/ebus/protocol.h b/src/lib/ebus/protocol.h index f5fbdcec9..85ddb1010 100755 --- a/src/lib/ebus/protocol.h +++ b/src/lib/ebus/protocol.h @@ -56,7 +56,7 @@ namespace ebusd { /** settings for the eBUS protocol handler. */ typedef struct ebus_protocol_config { - /** eBUS device string (serial device or [udp:]ip:port) with optional protocol prefix (enh: or ens:). */ + /** eBUS device string (serial device or [udp:]ip[:port]) with optional protocol prefix (enh: or ens:). */ const char* device; /** whether to skip serial eBUS device test. */ bool noDeviceCheck; From f26c48eb49a5de4e821aa04a32e5e86e5879a948 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 9 Mar 2024 10:11:41 +0100 Subject: [PATCH 233/345] fix m-m auto-answer response length and message search --- src/ebusd/bushandler.cpp | 10 ++++++---- src/ebusd/mainloop.cpp | 2 +- src/lib/ebus/protocol_direct.cpp | 3 +-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/ebusd/bushandler.cpp b/src/ebusd/bushandler.cpp index 20757f4f2..93d24ef51 100644 --- a/src/ebusd/bushandler.cpp +++ b/src/ebusd/bushandler.cpp @@ -442,15 +442,17 @@ void BusHandler::notifyProtocolMessage(MessageDirection direction, const MasterS } if (direction == md_answer) { size_t idLen = command.getDataSize(); - if (master && idLen >= response.size()) { + size_t resLen = response.getDataSize(); + if (master && idLen >= resLen) { // build MS auto-answer from MM with same ID SlaveSymbolString answer; answer.push_back(0); // room for length - idLen -= response.size(); - for (size_t pos = idLen; pos < response.size(); pos++) { + idLen -= resLen; + for (size_t pos = idLen; pos < resLen; pos++) { answer.push_back(command.dataAt(pos)); } - m_protocol->setAnswer(SYN, command[1], command[2], command[3], command.data() + 5, idLen, answer); + answer.adjustHeader(); + m_protocol->setAnswer(SYN, getSlaveAddress(dstAddress), command[2], command[3], command.data() + 5, idLen, answer); // TODO could use loaded messages for identifying MM/MS message pair } } diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index 0b0063904..b7d0be1a2 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -1240,7 +1240,7 @@ result_t MainLoop::executeAnswer(const vector& args, ostringstream* ostr if (answer.size() > 16) { return RESULT_ERR_INVALID_POS; } - answer[0] = (symbol_t)(answer.size()-1); + answer.adjustHeader(); } if (argPos < args.size()) { argPos = 0; // print usage diff --git a/src/lib/ebus/protocol_direct.cpp b/src/lib/ebus/protocol_direct.cpp index d9fa0e305..24e97386c 100644 --- a/src/lib/ebus/protocol_direct.cpp +++ b/src/lib/ebus/protocol_direct.cpp @@ -872,7 +872,7 @@ bool DirectProtocolHandler::getAnswer() { if (it != m_answerByKey.end()) { // found the answer if (master) { - if (len+it->second.size() == m_command[4]) { + if (len+it->second.getDataSize() == m_command[4]) { m_response = it->second; // copied for having the data size only return true; } @@ -881,7 +881,6 @@ bool DirectProtocolHandler::getAnswer() { m_response = it->second; return true; } - break; } if (len == 0) { break; From c53ca684f8b90db93d3163964a9dc63be3a6590c Mon Sep 17 00:00:00 2001 From: John Date: Sat, 9 Mar 2024 10:52:20 +0100 Subject: [PATCH 234/345] check answer command enabled more thoroughly, fix blank answer, more coverage --- src/ebusd/mainloop.cpp | 6 +++--- test_coverage.sh | 7 ++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index b7d0be1a2..323832508 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -485,7 +485,7 @@ result_t MainLoop::decodeRequest(Request* req, bool* connected, RequestMode* req return RESULT_OK; } if (cmd == "ANSWER") { - if (m_enableHex && !m_protocol->isReadOnly()) { + if (m_enableHex && m_protocol->isAnswering()) { return executeAnswer(args, ostream); } *ostream << "ERR: command not enabled"; @@ -1231,8 +1231,8 @@ result_t MainLoop::executeAnswer(const vector& args, ostringstream* ostr } } SlaveSymbolString answer; + answer.push_back(0); // room for length byte if (argPos > 0 && argPos < args.size()) { - answer.push_back(0); // room for length byte result_t ret = answer.parseHex(args[argPos++]); if (ret != RESULT_OK) { return ret; @@ -1240,8 +1240,8 @@ result_t MainLoop::executeAnswer(const vector& args, ostringstream* ostr if (answer.size() > 16) { return RESULT_ERR_INVALID_POS; } - answer.adjustHeader(); } + answer.adjustHeader(); if (argPos < args.size()) { argPos = 0; // print usage } diff --git a/test_coverage.sh b/test_coverage.sh index 67344a463..0e2a56d24 100755 --- a/test_coverage.sh +++ b/test_coverage.sh @@ -247,7 +247,7 @@ elif [[ -n "$1" ]]; then pid=$(ps -C ebusd -o pid=) done else - $ebusd -d tcp:127.0.0.1:8876 --initsend --latency 10 -n -c "$PWD/contrib/etc/ebusd" --pollinterval=10 -s --scanretries=0 -a 31 --acquireretries 3 --answer --generatesyn --receivetimeout 40000 --sendretries 1 --enablehex --htmlpath "$PWD/contrib/html" --httpport 8878 --pidfile "$PWD/ebusd.pid" --localhost -p 8877 -l "$PWD/ebusd.log" --logareas all --loglevel debug --lograwdata=bytes --lograwdatafile "$PWD/ebusd.raw" --lograwdatasize 1 --dumpfile "$PWD/ebusd.dump" --dumpsize 100 -D --scanconfig --aclfile="$PWD/acl.csv" --mqttport=1883 --enablehex --enabledefine + $ebusd -d tcp:127.0.0.1:8876 --initsend --latency 10 -n -c "$PWD/contrib/etc/ebusd" --pollinterval=10 -s --scanretries=0 -a 31 --acquireretries 3 --answer --generatesyn --receivetimeout 40000 --sendretries 1 --enablehex --htmlpath "$PWD/contrib/html" --httpport 8878 --pidfile "$PWD/ebusd.pid" --localhost -p 8877 -l "$PWD/ebusd.log" --logareas all --loglevel debug --lograwdata=bytes --lograwdatafile "$PWD/ebusd.raw" --lograwdatasize 1 --dumpfile "$PWD/ebusd.dump" --dumpsize 100 -D --scanconfig --aclfile="$PWD/acl.csv" --mqttport=1883 --enablehex --enabledefine --answer sleep 3 pid=`head -n 1 "$PWD/ebusd.pid"` fi @@ -336,6 +336,11 @@ a test testpass w -c mc.5 -d 53 installparam 123 hex fe070400 hex 53070400 +hex -s f0 -n fe07fe0102 +inject 3115b509030d1e00/02a85b +answer -d 50 0704 b5454850303003277201 +answer 0705 010203 +answer -s ff -m 0706 ab dump grab result grab result all From 1ac29d6c3e74cc69ba5c8de3b7973dedacb7505f Mon Sep 17 00:00:00 2001 From: John Date: Sat, 9 Mar 2024 11:07:12 +0100 Subject: [PATCH 235/345] add token --- .github/workflows/coverage.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index b0c3b9c54..0d5111acd 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -43,6 +43,7 @@ jobs: uses: codecov/codecov-action@v4 with: name: codecov-umbrella + token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true verbose: true gcov: true From 38a6b7bae9237d02c6c82b55aa377a17090dc421 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 9 Mar 2024 14:09:04 +0100 Subject: [PATCH 236/345] align with wiki, updated --- ChangeLog.md | 2 ++ README.md | 16 +++++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 41afaafcc..1b6e097dc 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -5,6 +5,8 @@ * fix DTM type with recent dates ## Features * add "inject" command +* add config path to verbose "info" command +* add "answer" command # 23.3 (2023-12-26) diff --git a/README.md b/README.md index 45987ceb4..accbb4c2e 100644 --- a/README.md +++ b/README.md @@ -24,16 +24,18 @@ The main features of the daemon are: * TCP * UDP * enhanced ebusd protocol allowing arbitration to be done directly by the hardware, e.g. for recent - * [eBUS Adapter Shield](https://adapter.ebusd.eu/v5/), or + * [eBUS Adapter Shield](https://adapter.ebusd.eu/v5/), + * [adapter v3.1](https://adapter.ebusd.eu/v31)/[v3.0](https://adapter.ebusd.eu/v3), or * [ebusd-esp firmware](https://github.com/john30/ebusd-esp/) - * actively send messages to and receive answers from the eBUS + * actively send messages to and receive answers from the eBUS * passively listen to messages sent on the eBUS + * answer to messages received from the eBUS * regularly poll for messages * cache all messages - * scan for bus participants and automatically pick matching message configuration files from config web service at ebusd.eu (or alternatively local files) - * parse messages to human readable values and vice versa via message configuration files - * automatically check for updates of daemon and configuration files - * pick preferred language for translatable message configuration parts + * scan for bus participants and automatically pick matching message definition files from config web service at ebusd.eu (or alternatively local files) + * parse messages to human readable values and vice versa via message definition files + * automatically check for updates of daemon and message definition files + * pick preferred language for translatable message definition parts * grab all messages on the eBUS and provide decoding hints * send arbitrary messages from hex input or inject those * log messages and problems to a log file @@ -43,7 +45,7 @@ The main features of the daemon are: * provide a rudimentary HTML interface * format messages and data in [JSON on dedicated HTTP port](https://github.com/john30/ebusd/wiki/3.2.-HTTP-client) * publish received data to [MQTT topics](https://github.com/john30/ebusd/wiki/3.3.-MQTT-client) and vice versa (if authorized) - * announce [message definitions and status by MQTT](https://github.com/john30/ebusd/wiki/MQTT-integration) to e.g. integrate with [Home Assistant](https://www.home-assistant.io/) using [MQTT Discovery](https://www.home-assistant.io/docs/mqtt/discovery/) + * announce [message definitions and status by MQTT](https://github.com/john30/ebusd/wiki/MQTT-integration) to e.g. integrate with [Home Assistant](https://www.home-assistant.io/) using [MQTT Discovery](https://www.home-assistant.io/integrations/mqtt#mqtt-discovery) * support MQTT publish to [Azure IoT hub](https://docs.microsoft.com/en-us/azure/iot-hub/) (see [MQTT integration](https://github.com/john30/ebusd/wiki/MQTT-integration)) * act as a [KNX device](https://github.com/john30/ebusd/wiki/3.4.-KNX-device) by publishing received data to KNX groups and answer to read/write requests from KNX, i.e. build an eBUS-KNX bridge * [user authentication](https://github.com/john30/ebusd/wiki/3.1.-TCP-client-commands#auth) via [ACL file](https://github.com/john30/ebusd/wiki/2.-Run#daemon-options) for access control to certain messages From 866a71f042032756134ffd73a5cc7c910b5bf268 Mon Sep 17 00:00:00 2001 From: John Date: Wed, 27 Mar 2024 06:48:12 +0100 Subject: [PATCH 237/345] fix setting log facilities --- src/ebusd/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index 202c2396c..d362e239c 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -256,7 +256,7 @@ int main(int argc, char* argv[], char* envp[]) { } if (s_opt.logAreas != -1 || s_opt.logLevel != ll_COUNT) { - setFacilitiesLogLevel(1 << ll_COUNT, ll_none); + setFacilitiesLogLevel(1 << lf_COUNT, ll_none); setFacilitiesLogLevel(s_opt.logAreas, s_opt.logLevel); } From 033586c63c00594047d75825a38888a1e5097a26 Mon Sep 17 00:00:00 2001 From: John Date: Wed, 27 Mar 2024 06:54:59 +0100 Subject: [PATCH 238/345] fix missed fd_set --- src/lib/ebus/transport.cpp | 1 + src/lib/utils/tcpsocket.cpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/lib/ebus/transport.cpp b/src/lib/ebus/transport.cpp index fb40ede6d..2a2ec4ccf 100644 --- a/src/lib/ebus/transport.cpp +++ b/src/lib/ebus/transport.cpp @@ -176,6 +176,7 @@ result_t FileTransport::read(unsigned int timeout, const uint8_t** data, size_t* FD_ZERO(&readfds); FD_ZERO(&exceptfds); FD_SET(m_fd, &readfds); + FD_SET(m_fd, &exceptfds); ret = pselect(m_fd + 1, &readfds, nullptr, &exceptfds, &tdiff, nullptr); if (ret >= 1 && FD_ISSET(m_fd, &exceptfds)) { diff --git a/src/lib/utils/tcpsocket.cpp b/src/lib/utils/tcpsocket.cpp index d3eae6124..19ef1a4a3 100755 --- a/src/lib/utils/tcpsocket.cpp +++ b/src/lib/utils/tcpsocket.cpp @@ -160,6 +160,8 @@ int tcpKeepAliveInterval) { FD_ZERO(&writefds); FD_ZERO(&exceptfds); FD_SET(sfd, &readfds); + FD_SET(sfd, &writefds); + FD_SET(sfd, &exceptfds); #ifdef HAVE_PSELECT ret = pselect(sfd + 1, &readfds, &writefds, &exceptfds, &tdiff, nullptr); #else From 60088b143ea21a7e29c4e4eac15de420d6b94408 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 29 Mar 2024 08:55:07 +0100 Subject: [PATCH 239/345] fix for potentially missed updates (#1226) --- src/ebusd/knxhandler.cpp | 11 ++++++++--- src/ebusd/mqtthandler.cpp | 11 ++++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/ebusd/knxhandler.cpp b/src/ebusd/knxhandler.cpp index a777df4ed..da2690e1d 100644 --- a/src/ebusd/knxhandler.cpp +++ b/src/ebusd/knxhandler.cpp @@ -899,6 +899,7 @@ void KnxHandler::run() { if (!m_updatedMessages.empty()) { m_messages->lock(); if (m_con->isConnected()) { + time_t maxUpdates = 0; for (auto it = m_updatedMessages.begin(); it != m_updatedMessages.end(); ) { const vector* messages = m_messages->getByKey(it->first); if (!messages) { @@ -906,16 +907,20 @@ void KnxHandler::run() { continue; } for (const auto& message : *messages) { - if (message->getLastChangeTime() <= 0) { + time_t changeTime = message->getLastChangeTime(); + if (changeTime <= 0) { continue; } + if (changeTime > lastUpdates && changeTime > maxUpdates) { + maxUpdates = changeTime; + } const auto mit = m_subscribedMessages.find(message->getKey()); if (mit == m_subscribedMessages.cend()) { continue; } if (!(message->getDataHandlerState()&2)) { message->setDataHandlerState(2, true); // first update still needed - } else if (message->getLastChangeTime() <= lastUpdates) { + } else if (changeTime <= lastUpdates) { continue; } for (auto destFlags : mit->second) { @@ -936,7 +941,7 @@ void KnxHandler::run() { } it = m_updatedMessages.erase(it); } - time(&lastUpdates); + lastUpdates = maxUpdates == 0 || lastUpdates > maxUpdates ? now : maxUpdates + 1; } else { m_updatedMessages.clear(); } diff --git a/src/ebusd/mqtthandler.cpp b/src/ebusd/mqtthandler.cpp index 5412b10cd..6e1f17c1b 100755 --- a/src/ebusd/mqtthandler.cpp +++ b/src/ebusd/mqtthandler.cpp @@ -1068,22 +1068,27 @@ void MqttHandler::run() { if (!m_updatedMessages.empty()) { m_messages->lock(); if (m_connected) { + time_t maxUpdates = 0; for (auto it = m_updatedMessages.begin(); it != m_updatedMessages.end(); ) { const vector* messages = m_messages->getByKey(it->first); if (messages) { for (const auto& message : *messages) { - if (message->getLastChangeTime() > 0 && message->isAvailable() - && (!g_onlyChanges || message->getLastChangeTime() > lastUpdates)) { + time_t changeTime = message->getLastChangeTime(); + if (changeTime > 0 && message->isAvailable() + && (!g_onlyChanges || changeTime > lastUpdates)) { updates.str(""); updates.clear(); updates << dec; publishMessage(message, &updates); } + if (changeTime > lastUpdates && changeTime > maxUpdates) { + maxUpdates = changeTime; + } } } it = m_updatedMessages.erase(it); } - time(&lastUpdates); + lastUpdates = maxUpdates == 0 || lastUpdates > maxUpdates ? now : maxUpdates + 1; } else { m_updatedMessages.clear(); } From 29e02322c0872cab8fd307fe12985e4f962b38b5 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 29 Mar 2024 13:31:16 +0100 Subject: [PATCH 240/345] fix condition range parse (#1244) --- src/lib/ebus/message.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib/ebus/message.cpp b/src/lib/ebus/message.cpp index 3afb6862f..07513df9c 100644 --- a/src/lib/ebus/message.cpp +++ b/src/lib/ebus/message.cpp @@ -1411,8 +1411,10 @@ result_t splitValues(const string& valueList, vector* valueRanges) valueRanges->push_back(0); } bool inclusive = str[1] == '='; - unsigned int val = parseInt(str.substr(inclusive?2:1).c_str(), 10, inclusive?0:1, - inclusive?UINT_MAX:(UINT_MAX-1), &result); + unsigned int val = parseInt(str.substr(inclusive?2:1).c_str(), 10, + inclusive || !upto ? 0 : 1, + inclusive || upto ? UINT_MAX : (UINT_MAX-1), + &result); if (result != RESULT_OK) { return result; } From d9628fabea2e2df679d44f2989ac0906ba0f4668 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 29 Mar 2024 13:32:24 +0100 Subject: [PATCH 241/345] const name spelling --- src/lib/ebus/message.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/ebus/message.cpp b/src/lib/ebus/message.cpp index 07513df9c..a76f519f9 100644 --- a/src/lib/ebus/message.cpp +++ b/src/lib/ebus/message.cpp @@ -70,11 +70,11 @@ using std::endl; #define POLL_PRIORITY_CONDITION 5 /** the field name constant for the message level. */ -static const char* FIELNAME_LEVEL = "level"; +static const char* FIELDNAME_LEVEL = "level";// /** the known full length field names. */ static const char* knownFieldNamesFull[] = { - "type", "circuit", FIELNAME_LEVEL, "name", "comment", "qq", "zz", "pbsb", "id", "fields", + "type", "circuit", FIELDNAME_LEVEL, "name", "comment", "qq", "zz", "pbsb", "id", "fields", }; /** the known full length field names. */ @@ -867,7 +867,7 @@ void Message::dump(const vector* fieldNames, bool withConditions, Output bool first = true; if (fieldNames == nullptr) { for (const auto& fieldName : knownFieldNamesFull) { - if (fieldName == FIELNAME_LEVEL) { + if (fieldName == FIELDNAME_LEVEL) { continue; // access level not included in default dump format } if (first) { From c70d8ffccfd02a4f317b723e206a78e55a54599d Mon Sep 17 00:00:00 2001 From: John Date: Wed, 10 Apr 2024 07:29:11 +0200 Subject: [PATCH 242/345] track changes only per data sink (#1226) --- src/ebusd/datahandler.cpp | 5 ++++- src/ebusd/datahandler.h | 9 +++++++-- src/ebusd/knxhandler.cpp | 14 ++------------ src/ebusd/mainloop.cpp | 3 ++- src/ebusd/mqtthandler.cpp | 13 ++++--------- 5 files changed, 19 insertions(+), 25 deletions(-) diff --git a/src/ebusd/datahandler.cpp b/src/ebusd/datahandler.cpp index 324105dc0..864ee4599 100755 --- a/src/ebusd/datahandler.cpp +++ b/src/ebusd/datahandler.cpp @@ -75,8 +75,11 @@ bool datahandler_register(UserInfo* userInfo, BusHandler* busHandler, MessageMap return success; } -void DataSink::notifyUpdate(Message* message) { +void DataSink::notifyUpdate(Message* message, bool changed) { if (message && message->hasLevel(m_levels)) { + if (m_changedOnly && !changed) { + return; + } m_updatedMessages[message->getKey()]++; } } diff --git a/src/ebusd/datahandler.h b/src/ebusd/datahandler.h index 8b5a0dce2..9e93096ed 100755 --- a/src/ebusd/datahandler.h +++ b/src/ebusd/datahandler.h @@ -143,8 +143,9 @@ class DataSink : virtual public DataHandler { * Constructor. * @param userInfo the @a UserInfo instance. * @param user the user name for determining the allowed access levels (fall back to default levels). + * @param changedOnly whether to handle changed messages only in the updates. */ - DataSink(const UserInfo* userInfo, const string& user) { + DataSink(const UserInfo* userInfo, const string& user, bool changedOnly) : m_changedOnly(changedOnly) { m_levels = userInfo->getLevels(userInfo->hasUser(user) ? user : ""); } @@ -159,8 +160,9 @@ class DataSink : virtual public DataHandler { /** * Notify the sink of an updated @a Message (not necessarily changed though). * @param message the updated @a Message. + * @param changed whether the message data changed since the last notification. */ - virtual void notifyUpdate(Message* message); + virtual void notifyUpdate(Message* message, bool changed); /** * Notify the sink of the latest update check result. @@ -178,6 +180,9 @@ class DataSink : virtual public DataHandler { /** the allowed access levels. */ string m_levels; + /** whether to handle changed messages only in the updates. */ + bool m_changedOnly; + /** a map of updated @p Message keys. */ map m_updatedMessages; }; diff --git a/src/ebusd/knxhandler.cpp b/src/ebusd/knxhandler.cpp index da2690e1d..2bca8694d 100644 --- a/src/ebusd/knxhandler.cpp +++ b/src/ebusd/knxhandler.cpp @@ -163,7 +163,7 @@ bool knxhandler_register(UserInfo* userInfo, BusHandler* busHandler, MessageMap* } KnxHandler::KnxHandler(UserInfo* userInfo, BusHandler* busHandler, MessageMap* messages) - : DataSink(userInfo, "knx"), DataSource(busHandler), WaitThread(), m_messages(messages), + : DataSink(userInfo, "knx", true), DataSource(busHandler), WaitThread(), m_messages(messages), m_start(0), m_lastUpdateCheckResult("."), m_lastScanStatus(SCAN_STATUS_NONE), m_scanFinishReceived(false), m_lastErrorLogTime(0) { m_con = KnxConnection::create(g_url); @@ -709,7 +709,7 @@ void KnxHandler::handleGroupTelegram(knx_addr_t src, knx_addr_t dest, int len, c #define UPTIME_INTERVAL 3600 void KnxHandler::run() { - time_t lastTaskRun, now, lastSignal = 0, lastUptime = 0, lastUpdates = 0; + time_t lastTaskRun, now, lastSignal = 0, lastUptime = 0; bool signal = false; result_t result = RESULT_OK; time(&now); @@ -899,7 +899,6 @@ void KnxHandler::run() { if (!m_updatedMessages.empty()) { m_messages->lock(); if (m_con->isConnected()) { - time_t maxUpdates = 0; for (auto it = m_updatedMessages.begin(); it != m_updatedMessages.end(); ) { const vector* messages = m_messages->getByKey(it->first); if (!messages) { @@ -911,18 +910,10 @@ void KnxHandler::run() { if (changeTime <= 0) { continue; } - if (changeTime > lastUpdates && changeTime > maxUpdates) { - maxUpdates = changeTime; - } const auto mit = m_subscribedMessages.find(message->getKey()); if (mit == m_subscribedMessages.cend()) { continue; } - if (!(message->getDataHandlerState()&2)) { - message->setDataHandlerState(2, true); // first update still needed - } else if (changeTime <= lastUpdates) { - continue; - } for (auto destFlags : mit->second) { auto sit = m_subscribedGroups.find(destFlags); if (sit == m_subscribedGroups.end()) { @@ -941,7 +932,6 @@ void KnxHandler::run() { } it = m_updatedMessages.erase(it); } - lastUpdates = maxUpdates == 0 || lastUpdates > maxUpdates ? now : maxUpdates + 1; } else { m_updatedMessages.clear(); } diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index 323832508..66f2bd6e3 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -352,8 +352,9 @@ void MainLoop::run() { m_messages->lock(); m_messages->findAll("", "", "*", false, true, true, true, true, true, sinkSince, now, false, &messages); for (const auto message : messages) { + bool changed = message->getLastChangeTime() >= sinkSince; for (const auto dataSink : dataSinks) { - dataSink->notifyUpdate(message); + dataSink->notifyUpdate(message, changed); } } m_messages->unlock(); diff --git a/src/ebusd/mqtthandler.cpp b/src/ebusd/mqtthandler.cpp index 6e1f17c1b..b32046558 100755 --- a/src/ebusd/mqtthandler.cpp +++ b/src/ebusd/mqtthandler.cpp @@ -368,7 +368,8 @@ string removeTrailingNonTopicPart(const string& str) { } MqttHandler::MqttHandler(UserInfo* userInfo, BusHandler* busHandler, MessageMap* messages) - : DataSink(userInfo, "mqtt"), DataSource(busHandler), WaitThread(), m_messages(messages), m_connected(false), + : DataSink(userInfo, "mqtt", g_onlyChanges), DataSource(busHandler), WaitThread(), + m_messages(messages), m_connected(false), m_lastUpdateCheckResult("."), m_lastScanStatus(SCAN_STATUS_NONE) { m_definitionsSince = 0; m_client = nullptr; @@ -696,7 +697,7 @@ void splitFields(const string& str, vector* row) { } void MqttHandler::run() { - time_t lastTaskRun, now, start, lastSignal = 0, lastUpdates = 0; + time_t lastTaskRun, now, start, lastSignal = 0; bool signal = false; bool globalHasName = m_globalTopic.has("name"); string signalTopic = m_globalTopic.get("", "signal"); @@ -1068,27 +1069,21 @@ void MqttHandler::run() { if (!m_updatedMessages.empty()) { m_messages->lock(); if (m_connected) { - time_t maxUpdates = 0; for (auto it = m_updatedMessages.begin(); it != m_updatedMessages.end(); ) { const vector* messages = m_messages->getByKey(it->first); if (messages) { for (const auto& message : *messages) { time_t changeTime = message->getLastChangeTime(); - if (changeTime > 0 && message->isAvailable() - && (!g_onlyChanges || changeTime > lastUpdates)) { + if (changeTime > 0 && message->isAvailable()) { updates.str(""); updates.clear(); updates << dec; publishMessage(message, &updates); } - if (changeTime > lastUpdates && changeTime > maxUpdates) { - maxUpdates = changeTime; - } } } it = m_updatedMessages.erase(it); } - lastUpdates = maxUpdates == 0 || lastUpdates > maxUpdates ? now : maxUpdates + 1; } else { m_updatedMessages.clear(); } From 9b476a67b5e7b79ca37a1c3d4f065a997368d0f7 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 5 May 2024 15:08:00 +0200 Subject: [PATCH 243/345] allow commands being injected as well --- src/ebusd/main.cpp | 32 ++++++++++++++++++++++---------- src/ebusd/main.h | 6 +++--- src/ebusd/main_args.cpp | 14 +++++--------- src/ebusd/mainloop.h | 3 ++- 4 files changed, 32 insertions(+), 23 deletions(-) diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index d362e239c..b97b29bdc 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -248,13 +248,6 @@ int main(int argc, char* argv[], char* envp[]) { return EINVAL; } - if (s_opt.injectCount > 0) { - if (!s_opt.injectMessages && !(s_opt.checkConfig && s_opt.scanConfig)) { - fprintf(stderr, "invalid inject arguments"); - return EINVAL; - } - } - if (s_opt.logAreas != -1 || s_opt.logLevel != ll_COUNT) { setFacilitiesLogLevel(1 << lf_COUNT, ll_none); setFacilitiesLogLevel(s_opt.logAreas, s_opt.logLevel); @@ -473,14 +466,33 @@ int main(int argc, char* argv[], char* envp[]) { s_scanHelper->loadConfigFiles(!s_opt.scanConfig); // start the MainLoop - if (s_opt.injectMessages) { + if (s_opt.injectCommands) { int scanAdrCount = 0; bool scanAddresses[256] = {}; for (int arg_index = argc - s_opt.injectCount; arg_index < argc; arg_index++) { - // add each passed message + string arg = argv[arg_index]; + if (arg.empty()) { + continue; + } + if (arg.find_first_of(' ') != string::npos || arg.find_first_of('/') == string::npos) { + RequestImpl req(false); + req.add(argv[arg_index]); + bool connected; + RequestMode reqMode; + string user; + bool reload; + ostringstream ostream; + result_t ret = s_mainLoop->decodeRequest(&req, &connected, &reqMode, &user, &reload, &ostream); + if (ret != RESULT_OK) { + string output = ostream.str(); + logError(lf_main, "executing command %s failed: %d", argv[arg_index], output.c_str()); + } + continue; + } + // old style inject message MasterSymbolString master; SlaveSymbolString slave; - if (!s_scanHelper->parseMessage(argv[arg_index], false, &master, &slave)) { + if (!s_scanHelper->parseMessage(arg, false, &master, &slave)) { continue; } s_protocol->injectMessage(master, slave); diff --git a/src/ebusd/main.h b/src/ebusd/main.h index 0747f2556..0137514fd 100755 --- a/src/ebusd/main.h +++ b/src/ebusd/main.h @@ -60,9 +60,9 @@ typedef struct options { OutputFormat dumpConfig; //!< dump config files, then stop const char* dumpConfigTo; //!< file to dump config to unsigned int pollInterval; //!< poll interval in seconds, 0 to disable [5] - bool injectMessages; //!< inject remaining arguments as already seen messages - bool stopAfterInject; //!< only inject messages once, then stop - int injectCount; //!< number of message arguments to inject, or 0 + bool injectCommands; //!< inject remaining arguments as commands or already seen messages + bool stopAfterInject; //!< only inject arguments once, then stop + int injectCount; //!< number of arguments to inject, or 0 #ifdef HAVE_SSL const char* caFile; //!< the CA file to use (uses defaults if neither caFile nor caPath are set), or "#" for insecure const char* caPath; //!< the path with CA files to use (uses defaults if neither caFile nor caPath are set) diff --git a/src/ebusd/main_args.cpp b/src/ebusd/main_args.cpp index 78f6590f6..6c92d73c3 100755 --- a/src/ebusd/main_args.cpp +++ b/src/ebusd/main_args.cpp @@ -54,7 +54,7 @@ static const options_t s_default_opt = { .dumpConfig = OF_NONE, .dumpConfigTo = nullptr, .pollInterval = 5, - .injectMessages = false, + .injectCommands = false, .stopAfterInject = false, .injectCount = 0, #ifdef HAVE_SSL @@ -170,9 +170,9 @@ static const argDef argDefs[] = { "Check and dump config files in FORMAT (\"json\" or \"csv\"), then stop"}, {"dumpconfigto", O_DMPCTO, "FILE", 0, "Dump config files to FILE"}, {"pollinterval", O_POLINT, "SEC", 0, "Poll for data every SEC seconds (0=disable) [5]"}, - {"inject", 'i', "stop", af_optional, "Inject remaining arguments as already seen messages (e.g. " + {"inject", 'i', "stop", af_optional, "Inject remaining arguments as commands or already seen messages (e.g. " "\"FF08070400/0AB5454850303003277201\"), optionally stop afterwards"}, - {nullptr, O_INJPOS, "INJECT", af_optional|af_multiple, "Message(s) to inject (if --inject was given)"}, + {nullptr, O_INJPOS, "INJECT", af_optional|af_multiple, "Commands and/or messages to inject (if --inject was given)"}, #ifdef HAVE_SSL {"cafile", O_CAFILE, "FILE", 0, "Use CA FILE for checking certificates (uses defaults," " \"#\" for insecure)"}, @@ -343,7 +343,7 @@ static int parse_opt(int key, char *arg, const argParseOpt *parseOpt, struct opt opt->pollInterval = value; break; case 'i': // --inject[=stop] - opt->injectMessages = true; + opt->injectCommands = true; opt->stopAfterInject = arg && strcmp("stop", arg) == 0; break; #ifdef HAVE_SSL @@ -595,7 +595,7 @@ static int parse_opt(int key, char *arg, const argParseOpt *parseOpt, struct opt default: if (key >= O_INJPOS) { // INJECT - if (!opt->injectMessages || !arg || !arg[0]) { + if (!opt->injectCommands || !arg || !arg[0]) { return ESRCH; } opt->injectCount++; @@ -614,10 +614,6 @@ static int parse_opt(int key, char *arg, const argParseOpt *parseOpt, struct opt argParseError(parseOpt, "scanconfig without polling may lead to invalid files included for certain products!"); return EINVAL; } - if (opt->injectMessages && (opt->checkConfig || opt->dumpConfig)) { - argParseError(parseOpt, "cannot combine inject with checkconfig/dumpconfig"); - return EINVAL; - } return 0; } diff --git a/src/ebusd/mainloop.h b/src/ebusd/mainloop.h index 599df1e79..856ecfb62 100644 --- a/src/ebusd/mainloop.h +++ b/src/ebusd/mainloop.h @@ -128,7 +128,7 @@ class MainLoop : public Thread { void run() override; - private: + public: /** * Decode and execute client request. * @param req the @a Request to decode. @@ -142,6 +142,7 @@ class MainLoop : public Thread { result_t decodeRequest(Request* req, bool* connected, RequestMode* reqMode, string* user, bool* reload, ostringstream* ostream); + private: /** * Parse the hex master message from the remaining arguments. * @param args the arguments passed to the command. From c084dd2cbb3411883bd1b048585724a93e1aff83 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 5 May 2024 15:16:53 +0200 Subject: [PATCH 244/345] add enum for predefined symbols, enhance getStr() --- src/ebusd/mainloop.cpp | 8 ++++---- src/lib/ebus/message.cpp | 2 +- src/lib/ebus/symbol.cpp | 11 +++++++++-- src/lib/ebus/symbol.h | 33 ++++++++++++++++++--------------- 4 files changed, 32 insertions(+), 22 deletions(-) diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index 66f2bd6e3..924b7a18b 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -108,7 +108,7 @@ MainLoop::MainLoop(const struct options& opt, BusHandler* busHandler, : Thread(), m_busHandler(busHandler), m_protocol(busHandler->getProtocol()), m_reconnectCount(0), m_userList(opt.accessLevel), m_messages(messages), m_scanHelper(scanHelper), m_address(opt.address), m_scanConfig(opt.scanConfig), - m_initialScan(opt.readOnly ? ESC : opt.initialScan), m_scanRetries(opt.scanRetries), + m_initialScan(opt.readOnly ? (symbol_t)ESC : opt.initialScan), m_scanRetries(opt.scanRetries), m_scanStatus(SCAN_STATUS_NONE), m_polling(opt.pollInterval > 0), m_enableHex(opt.enableHex), m_shutdown(false), m_runUpdateCheck(opt.updateCheck), m_httpClient(), m_requestQueue(requestQueue) { if (opt.aclFile[0]) { @@ -676,7 +676,7 @@ result_t MainLoop::executeRead(const vector& args, const string& levels, if (dest) { dstAddress = address; } else { - srcAddress = address == m_address ? SYN : address; + srcAddress = address == m_address ? (symbol_t)SYN : address; } } else if (args[argPos] == "-p") { argPos++; @@ -933,7 +933,7 @@ result_t MainLoop::executeWrite(const vector& args, const string levels, if (dest) { dstAddress = address; } else { - srcAddress = address == m_address ? SYN : address; + srcAddress = address == m_address ? (symbol_t)SYN : address; } } else if (args[argPos] == "-c") { argPos++; @@ -1110,7 +1110,7 @@ result_t MainLoop::parseHexAndSend(const vector& args, size_t& argPos, b if (ret != RESULT_OK || !isValidAddress(address, false) || !isMaster(address)) { return RESULT_ERR_INVALID_ADDR; } - srcAddress = address == m_address ? SYN : address; + srcAddress = address == m_address ? (symbol_t)SYN : address; } else if (args[argPos] == "-n") { autoLength = true; } else { diff --git a/src/lib/ebus/message.cpp b/src/lib/ebus/message.cpp index a76f519f9..2c496ce65 100644 --- a/src/lib/ebus/message.cpp +++ b/src/lib/ebus/message.cpp @@ -200,7 +200,7 @@ uint64_t Message::createKey(const MasterSymbolString& master, size_t maxIdLength } uint64_t key = (uint64_t)idLength << (8 * 7 + 5); key |= (uint64_t)getMasterNumber(master[0]) << (8 * 7); // QQ address for passive message - key |= (uint64_t)(anyDestination ? SYN : master[1]) << (8 * 6); // ZZ address + key |= (uint64_t)(anyDestination ? (symbol_t)SYN : master[1]) << (8 * 6); // ZZ address key |= (uint64_t)master[2] << (8 * 5); // PB key |= (uint64_t)master[3] << (8 * 4); // SB int exp = 3; diff --git a/src/lib/ebus/symbol.cpp b/src/lib/ebus/symbol.cpp index 92ef757bb..3b0e96f78 100755 --- a/src/lib/ebus/symbol.cpp +++ b/src/lib/ebus/symbol.cpp @@ -145,14 +145,21 @@ result_t SymbolString::parseHexEscaped(const string& str) { return inEscape ? RESULT_ERR_ESC : RESULT_OK; } -const string SymbolString::getStr(size_t skipFirstSymbols) const { +const string SymbolString::getStr(size_t skipFirstSymbols, size_t maxLength, bool withLength) const { ostringstream sstr; + if (maxLength == 0) { + maxLength = m_data.size(); + } + size_t lengthOffset = withLength ? 254 : (m_isMaster ? 4 : 0); for (size_t i = 0; i < m_data.size(); i++) { if (skipFirstSymbols > 0) { skipFirstSymbols--; - } else { + } else if (i != lengthOffset) { sstr << nouppercase << setw(2) << hex << setfill('0') << static_cast(m_data[i]); + if (--maxLength == 0) { + break; + } } } return sstr.str(); diff --git a/src/lib/ebus/symbol.h b/src/lib/ebus/symbol.h index e3d0f489c..43f73693f 100755 --- a/src/lib/ebus/symbol.h +++ b/src/lib/ebus/symbol.h @@ -72,20 +72,21 @@ using std::ostringstream; /** the base type for symbols sent to/from the eBUS. */ typedef unsigned char symbol_t; -/** escape symbol, either followed by 0x00 for the value 0xA9, or 0x01 for the value 0xAA. */ -#define ESC ((symbol_t)0xA9) - -/** synchronization symbol. */ -#define SYN ((symbol_t)0xAA) - -/** positive acknowledge symbol. */ -#define ACK ((symbol_t)0x00) - -/** negative acknowledge symbol. */ -#define NAK ((symbol_t)0xFF) - -/** the broadcast destination address. */ -#define BROADCAST ((symbol_t)0xFE) +/** + * List of predefined eBUS symbols. + */ +enum PredefinedSymbol : symbol_t { + /** escape symbol, either followed by 0x00 for the value 0xA9, or 0x01 for the value 0xAA. */ + ESC = 0xA9, + /** synchronization symbol. */ + SYN = 0xAA, + /** positive acknowledge symbol. */ + ACK = 0x00, + /** negative acknowledge symbol. */ + NAK = 0xFF, + /** the broadcast destination address. */ + BROADCAST = 0xFE, +}; /** * Parse an unsigned int value. @@ -156,9 +157,11 @@ class SymbolString { /** * Return the symbols as hex string. * @param skipFirstSymbols the number of first symbols to skip. + * @param maxLength the maximum number of symbols to include (or 0 for all). + * @param withLength whether to include the NN length field. * @return the symbols as hex string. */ - const string getStr(size_t skipFirstSymbols = 0) const; + const string getStr(size_t skipFirstSymbols = 0, size_t maxLength = 0, bool withLength = true) const; /** * Dump the data in JSON format to the output. From decbd400bb7b6540e998dca877676ef6da158de5 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 7 May 2024 21:31:27 +0200 Subject: [PATCH 245/345] prepare for cdn --- src/ebusd/scan.cpp | 7 ++++++ src/lib/utils/httpclient.cpp | 46 ++++++++++++++++++++++-------------- 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/src/ebusd/scan.cpp b/src/ebusd/scan.cpp index fda44b6d9..2a0186546 100644 --- a/src/ebusd/scan.cpp +++ b/src/ebusd/scan.cpp @@ -82,6 +82,13 @@ result_t ScanHelper::collectConfigFiles(const string& relPath, const string& pre if (!m_configHttpClient->get(uri, "", &names)) { return RESULT_ERR_NOTFOUND; } + } else if (!json && names[0]=='<') { // html + uri = m_configUriPrefix + relPathWithSlash + "index.json"; + json = true; + logDebug(lf_main, "trying index.json"); + if (!m_configHttpClient->get(uri, "", &names, nullptr, nullptr, &json)) { + return RESULT_ERR_NOTFOUND; + } } istringstream stream(names); string name; diff --git a/src/lib/utils/httpclient.cpp b/src/lib/utils/httpclient.cpp index 72c3a8568..cdaf0e1a1 100755 --- a/src/lib/utils/httpclient.cpp +++ b/src/lib/utils/httpclient.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #ifdef HAVE_SSL #if OPENSSL_VERSION_NUMBER < 0x10101000L #include @@ -301,10 +302,12 @@ SSLSocket* SSLSocket::connect(const string& host, const uint16_t& port, bool htt break; } if (strcmp(peerName, hostname) != 0) { - char* dotpos = NULL; - if (peerName[0] == '*' && peerName[1] == '.' && (dotpos=strchr((char*)hostname, '.')) + char* dotpos = strchr((char*)hostname, '.'); + if (peerName[0] == '*' && peerName[1] == '.' && dotpos && strcmp(peerName+2, dotpos+1) == 0) { // wildcard matches + } else if (dotpos && strcmp(peerName, dotpos+1) == 0) { + // hostname matches } else if (isError("subject", 1, 0)) { break; } @@ -513,15 +516,16 @@ bool* repeatable, time_t* time, bool* jsonString) { return false; } string headers = result.substr(0, pos+2); // including final \r\n + transform(headers.begin(), headers.end(), headers.begin(), ::tolower); const char* hdrs = headers.c_str(); *response = result.substr(pos+4); #if defined(HAVE_TIME_H) && defined(HAVE_TIMEGM) if (time) { - pos = headers.find("\r\nLast-Modified: "); - if (pos != string::npos && headers.substr(pos+42, 4) == " GMT") { + pos = headers.find("\r\nlast-modified: "); + if (pos != string::npos && headers.substr(pos+42, 4) == " gmt") { // Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT struct tm t; - pos += strlen("\r\nLast-Modified: ") + 5; + pos += strlen("\r\nlast-modified: ") + 5; char* strEnd = nullptr; t.tm_mday = static_cast(strtol(hdrs + pos, &strEnd, 10)); if (strEnd != hdrs + pos + 2 || t.tm_mday < 1 || t.tm_mday > 31) { @@ -556,28 +560,34 @@ bool* repeatable, time_t* time, bool* jsonString) { } } #endif - pos = headers.find("\r\nContent-Length: "); - if (pos == string::npos) { + bool isJson = headers.find("\r\ncontent-type: application/json") != string::npos; + pos = headers.find("\r\ncontent-length: "); + bool noLength = pos == string::npos; + if (noLength && !isJson) { disconnect(); + if (jsonString) { + *jsonString = false; + } return true; } char* strEnd = nullptr; - unsigned long length = strtoul(hdrs + pos + strlen("\r\nContent-Length: "), &strEnd, 10); - if (strEnd == nullptr || *strEnd != '\r') { - disconnect(); - *response = "invalid content length "; - return false; - } - bool isJson = headers.find("\r\nContent-Type: application/json") != string::npos; - if (pos == string::npos) { - disconnect(); - return true; + unsigned long length = 4*1024; // default max length + if (!noLength) { + length = strtoul(hdrs + pos + strlen("\r\ncontent-length: "), &strEnd, 10); + if (strEnd == nullptr || *strEnd != '\r') { + disconnect(); + *response = "invalid content length "; + return false; + } } pos = readUntil("", length, response); disconnect(); - if (pos != length) { + if (noLength ? pos<1 : pos != length) { return false; } + if (noLength) { + length = pos; + } if (jsonString && isJson && *jsonString && length >= 2 && response->at(0) == '"') { // check for inline conversion of JSON to string expecting a single string to de-escape pos = length; From 92e0faf352dbcd5f38cdf4d45ea7ac1df34e0453 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 26 May 2024 16:39:31 +0200 Subject: [PATCH 246/345] spelling --- src/lib/ebus/symbol.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/ebus/symbol.h b/src/lib/ebus/symbol.h index 43f73693f..a1523cadd 100755 --- a/src/lib/ebus/symbol.h +++ b/src/lib/ebus/symbol.h @@ -286,7 +286,7 @@ class SymbolString { } /** - * Return the calculated number of data bytes DD (nnot yet revealed in the length field). + * Return the calculated number of data bytes DD (not yet revealed in the length field). * @return the calculated number of data bytes DD. */ size_t getCalculatedDataSize() const { From d45c5a779f8699109f1852a79504d07c67637087 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 26 May 2024 16:39:54 +0200 Subject: [PATCH 247/345] remove invalid notification --- src/lib/ebus/transport.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/ebus/transport.cpp b/src/lib/ebus/transport.cpp index 2a2ec4ccf..ecb29a5bd 100644 --- a/src/lib/ebus/transport.cpp +++ b/src/lib/ebus/transport.cpp @@ -91,8 +91,8 @@ result_t FileTransport::open() { } else { result = openInternal(); } - if (m_listener != nullptr) { - result = m_listener->notifyTransportStatus(result == RESULT_OK); + if (m_listener != nullptr && result == RESULT_OK) { + result = m_listener->notifyTransportStatus(true); } if (result != RESULT_OK) { close(); From 08515d1167c24f0801fb755a780764291717e810 Mon Sep 17 00:00:00 2001 From: John Date: Thu, 27 Jun 2024 18:34:45 +0200 Subject: [PATCH 248/345] unify decodeLastData usage, add raw logging to read cmd --- src/ebusd/bushandler.cpp | 14 ++++---- src/ebusd/mainloop.cpp | 33 ++++++++++++------- src/ebusd/mqtthandler.cpp | 4 +-- src/lib/ebus/data.cpp | 36 ++++++++++++++++++-- src/lib/ebus/data.h | 11 +++++++ src/lib/ebus/datatype.h | 3 ++ src/lib/ebus/message.cpp | 53 ++++++++++++------------------ src/lib/ebus/message.h | 23 +++---------- src/lib/ebus/test/test_message.cpp | 31 ++++++++++++++--- 9 files changed, 131 insertions(+), 77 deletions(-) diff --git a/src/ebusd/bushandler.cpp b/src/ebusd/bushandler.cpp index 93d24ef51..36ab84804 100644 --- a/src/ebusd/bushandler.cpp +++ b/src/ebusd/bushandler.cpp @@ -101,7 +101,7 @@ bool ScanRequest::notify(result_t result, const SlaveSymbolString& slave) { } if (result == RESULT_OK) { ostringstream output; - result = m_message->decodeLastData(true, nullptr, -1, OF_NONE, &output); // decode data + result = m_message->decodeLastData(pt_any, true, nullptr, -1, OF_NONE, &output); // decode data string str = output.str(); m_busHandler->setScanResult(dstAddress, m_notifyIndex+m_index, str); } @@ -403,7 +403,7 @@ void BusHandler::notifyProtocolMessage(MessageDirection direction, const MasterS result = message->storeLastData(0, idData); if (result == RESULT_OK) { ostringstream output; - result = message->decodeLastData(true, nullptr, -1, OF_NONE, &output); + result = message->decodeLastData(pt_any, true, nullptr, -1, OF_NONE, &output); if (result == RESULT_OK) { string str = output.str(); setScanResult(slaveAddress, 0, str); @@ -420,7 +420,7 @@ void BusHandler::notifyProtocolMessage(MessageDirection direction, const MasterS result_t result = message->storeLastData(command, response); if (result == RESULT_OK) { ostringstream output; - result = message->decodeLastData(true, nullptr, -1, OF_NONE, &output); + result = message->decodeLastData(pt_any, true, nullptr, -1, OF_NONE, &output); if (result == RESULT_OK) { string str = output.str(); setScanResult(dstAddress, 0, str); @@ -475,7 +475,7 @@ void BusHandler::notifyProtocolMessage(MessageDirection direction, const MasterS result_t result = message->storeLastData(command, response); ostringstream output; if (result == RESULT_OK) { - result = message->decodeLastData(false, nullptr, -1, OF_NONE, &output); + result = message->decodeLastData(pt_any, false, nullptr, -1, OF_NONE, &output); } if (result < RESULT_OK) { logError(lf_update, "unable to parse %s %s %s from %s / %s: %s", mode, circuit.c_str(), name.c_str(), @@ -639,7 +639,7 @@ void BusHandler::formatScanResult(ostringstream* output) const { *output << endl; } *output << hex << setw(2) << setfill('0') << static_cast(slave); - message->decodeLastData(true, nullptr, -1, OF_NONE, output); + message->decodeLastData(pt_any, true, nullptr, -1, OF_NONE, output); } } } @@ -680,7 +680,7 @@ void BusHandler::formatSeenInfo(ostringstream* output) const { if (message != nullptr && message->getLastUpdateTime() > 0) { // add detailed scan info: Manufacturer ID SW HW *output << " \""; - result_t result = message->decodeLastData(false, nullptr, -1, OF_NAMES, output); + result_t result = message->decodeLastData(pt_any, false, nullptr, -1, OF_NAMES, output); if (result != RESULT_OK) { *output << "\" error: " << getResultCode(result); } else { @@ -759,7 +759,7 @@ void BusHandler::formatUpdateInfo(ostringstream* output) const { Message* message = m_messages->getScanMessage(address); if (message != nullptr && message->getLastUpdateTime() > 0) { // add detailed scan info: Manufacturer ID SW HW - message->decodeLastData(true, nullptr, -1, OF_NAMES|OF_NUMERIC|OF_JSON|OF_SHORT, output); + message->decodeLastData(pt_any, true, nullptr, -1, OF_NAMES|OF_NUMERIC|OF_JSON|OF_SHORT, output); } } const vector& loadedFiles = m_messages->getLoadedFiles(address); diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index 924b7a18b..5c31433d6 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -402,7 +402,7 @@ void MainLoop::run() { m_messages->findAll("", "", levels, false, true, true, true, true, true, since, now, true, &messages); for (const auto message : messages) { ostream << message->getCircuit() << " " << message->getName() << " = " << dec; - message->decodeLastData(false, nullptr, -1, reqMode.format, &ostream); + message->decodeLastData(pt_any, false, nullptr, -1, reqMode.format, &ostream); ostream << endl; } } @@ -648,8 +648,15 @@ result_t MainLoop::executeRead(const vector& args, const string& levels, } } else if (args[argPos] == "-vv") { verbosity |= VERBOSITY_2; - } else if (args[argPos] == "-vvv" || args[argPos] == "-V") { + } else if (args[argPos] == "-vvv") { verbosity |= VERBOSITY_3; + } else if (args[argPos] == "-V") { + if ((verbosity & VERBOSITY_4) == VERBOSITY_4) { + verbosity |= OF_RAWDATA; + } + verbosity |= VERBOSITY_4; + } else if (args[argPos] == "-VV") { + verbosity |= VERBOSITY_4 | OF_RAWDATA; } else if (args[argPos] == "-n") { verbosity = (verbosity & ~OF_VALUENAME) | OF_NUMERIC; } else if (args[argPos] == "-N") { @@ -723,7 +730,7 @@ result_t MainLoop::executeRead(const vector& args, const string& levels, " -d ZZ override destination address ZZ\n" " -p PRIO set the message poll priority (1-9)\n" " -v increase verbosity (include names/units/comments)\n" - " -V be very verbose (include names, units, and comments)\n" + " -V be very verbose (all attributes, plus raw data if given more than once)\n" " -n use numeric value of value=name pairs\n" " -N use numeric and named value of value=name pairs\n" " -i VALUE read additional message parameters from VALUE\n" @@ -785,7 +792,7 @@ result_t MainLoop::executeRead(const vector& args, const string& levels, ret = message->storeLastData(master, slave); ostringstream result; if (ret == RESULT_OK) { - ret = message->decodeLastData(false, nullptr, -1, OF_NONE, &result); + ret = message->decodeLastData(pt_any, false, nullptr, -1, OF_NONE, &result); } if (ret >= RESULT_OK) { logInfo(lf_main, "read hex %s %s cache update: %s", message->getCircuit().c_str(), message->getName().c_str(), @@ -855,7 +862,7 @@ result_t MainLoop::executeRead(const vector& args, const string& levels, if (verbosity & OF_NAMES) { *ostream << cacheMessage->getCircuit() << " " << cacheMessage->getName() << " "; } - ret = cacheMessage->decodeLastData(false, fieldIndex == -2 ? nullptr : fieldName.c_str(), fieldIndex, verbosity, + ret = cacheMessage->decodeLastData(pt_any, false, fieldIndex == -2 ? nullptr : fieldName.c_str(), fieldIndex, verbosity, ostream); if (ret != RESULT_OK) { if (ret < RESULT_OK) { @@ -888,7 +895,7 @@ result_t MainLoop::executeRead(const vector& args, const string& levels, if (verbosity & OF_NAMES) { *ostream << message->getCircuit() << " " << message->getName() << " "; } - ret = message->decodeLastData(false, false, fieldIndex == -2 ? nullptr : fieldName.c_str(), fieldIndex, verbosity, + ret = message->decodeLastData(pt_any, false, fieldIndex == -2 ? nullptr : fieldName.c_str(), fieldIndex, verbosity, ostream); if (ret < RESULT_OK) { logError(lf_main, "read %s %s: decode %s", message->getCircuit().c_str(), message->getName().c_str(), @@ -1009,7 +1016,7 @@ result_t MainLoop::executeWrite(const vector& args, const string levels, ret = message->storeLastData(master, slave); ostringstream result; if (ret == RESULT_OK) { - ret = message->decodeLastData(false, nullptr, -1, OF_NONE, &result); + ret = message->decodeLastData(pt_any, false, nullptr, -1, OF_NONE, &result); } if (ret >= RESULT_OK) { logInfo(lf_main, "write hex %s %s cache update: %s", message->getCircuit().c_str(), @@ -1080,7 +1087,7 @@ result_t MainLoop::executeWrite(const vector& args, const string levels, return RESULT_OK; } - ret = message->decodeLastData(false, false, nullptr, -1, OF_NONE, ostream); // decode data + ret = message->decodeLastData(pt_any, false, nullptr, -1, OF_NONE, ostream); // decode data if (ret >= RESULT_OK && ostream->str().empty()) { logNotice(lf_main, "write %s %s: decode %s", message->getCircuit().c_str(), message->getName().c_str(), getResultCode(ret)); @@ -1487,7 +1494,7 @@ result_t MainLoop::executeFind(const vector& args, const string& levels, } else if (hexFormat) { *ostream << message->getLastMasterData().getStr() << " / " << message->getLastSlaveData().getStr(); } else { - result_t ret = message->decodeLastData(false, nullptr, -1, verbosity, ostream); + result_t ret = message->decodeLastData(pt_any, false, nullptr, -1, verbosity, ostream); if (ret != RESULT_OK) { *ostream << " (" << getResultCode(ret) << " for " << message->getLastMasterData().getStr() @@ -2045,7 +2052,7 @@ result_t MainLoop::executeGet(const vector& args, bool* connected, ostri circuit = uri.substr(6, pos - 6); name = uri.substr(pos + 1); } - bool required = false, full = false, withWrite = false, raw = false; + bool required = false, full = false, withWrite = false; bool withDefinition = false; string newDefinition; OutputFormat verbosity = OF_NAMES; @@ -2099,7 +2106,9 @@ result_t MainLoop::executeGet(const vector& args, bool* connected, ostri } else if (qname == "write") { withWrite = parseBoolQuery(value); } else if (qname == "raw") { - raw = parseBoolQuery(value); + if (parseBoolQuery(value)) { + verbosity |= OF_RAWDATA; + } } else if (qname == "def") { withDefinition = parseBoolQuery(value); } else if (qname == "define") { @@ -2191,7 +2200,7 @@ result_t MainLoop::executeGet(const vector& args, bool* connected, ostri Message* next = *(it+1); same = next->getCircuit() == lastCircuit && next->getName() == name; } - message->decodeJson(!first, same, true, raw, verbosity, ostream); + message->decodeJson(!first, same, true, verbosity, ostream); lastName = name; first = false; } diff --git a/src/ebusd/mqtthandler.cpp b/src/ebusd/mqtthandler.cpp index b32046558..56ff1cabc 100755 --- a/src/ebusd/mqtthandler.cpp +++ b/src/ebusd/mqtthandler.cpp @@ -1178,7 +1178,7 @@ void MqttHandler::publishMessage(const Message* message, ostringstream* updates, } else if (m_staticTopic) { *updates << message->getCircuit() << UI_FIELD_SEPARATOR << message->getName() << UI_FIELD_SEPARATOR; } - result_t result = message->decodeLastData(false, nullptr, -1, outputFormat, updates); + result_t result = message->decodeLastData(pt_any, false, nullptr, -1, outputFormat, updates); if (result == RESULT_EMPTY) { publishEmptyTopic(getTopic(message)); // alternatively: , json ? "null" : ""); return; @@ -1206,7 +1206,7 @@ void MqttHandler::publishMessage(const Message* message, ostringstream* updates, publishEmptyTopic(getTopic(message, "", name)); // alternatively: , json ? "null" : ""); continue; } - result_t result = message->decodeLastData(false, nullptr, index, outputFormat, updates); + result_t result = message->decodeLastData(pt_any, false, nullptr, index, outputFormat, updates); if (result != RESULT_OK) { logOtherError("mqtt", "decode %s %s %s: %s", message->getCircuit().c_str(), message->getName().c_str(), name.c_str(), getResultCode(result)); diff --git a/src/lib/ebus/data.cpp b/src/lib/ebus/data.cpp index 7fbbacf7e..35a25805c 100644 --- a/src/lib/ebus/data.cpp +++ b/src/lib/ebus/data.cpp @@ -412,6 +412,31 @@ const char* DataField::getDayName(int day) { return dayNames[day]; } +bool DataField::addRaw(size_t offset, size_t length, const SymbolString& input, bool isJson, ostream* output) { + size_t size = input.getDataSize(); + if (offset >= size) { + return false; + } + if (isJson) { + *output << ", \"raw\": ["; + for (size_t pos = 0; pos < length && offset+pos < size; pos++) { + if (pos > 0) { + *output << ", "; + } + *output << dec << static_cast(input.dataAt(offset+pos)); + } + *output << "]"; + } else { + *output << "["; + for (size_t pos = 0; pos < length && offset+pos < size; pos++) { + *output << setw(2) << hex + << setfill('0') << static_cast(input.dataAt(offset+pos)); + } + *output << "]"; + } + return true; +} + result_t SingleDataField::create(const string& name, const map& attributes, const DataType* dataType, PartType partType, size_t length, int divisor, const string& constantValue, @@ -547,7 +572,8 @@ result_t SingleDataField::read(const SymbolString& data, size_t offset, return RESULT_EMPTY; } bool shortFormat = outputFormat & OF_SHORT; - if (outputFormat & OF_JSON) { + bool isJson = outputFormat & OF_JSON; + if (isJson) { if (leadingSeparator) { *output << ","; } @@ -578,6 +604,9 @@ result_t SingleDataField::read(const SymbolString& data, size_t offset, } } + if (!shortFormat && (outputFormat & OF_RAWDATA) && !isJson) { + addRaw(offset, m_length, data, isJson, output); + } result_t result = readSymbols(data, offset, outputFormat, output); if (result != RESULT_OK) { return result; @@ -585,7 +614,10 @@ result_t SingleDataField::read(const SymbolString& data, size_t offset, if (!shortFormat) { appendAttributes(outputFormat, output); } - if (!shortFormat && (outputFormat & OF_JSON)) { + if (!shortFormat && isJson) { + if (outputFormat & OF_RAWDATA) { + addRaw(offset, m_length, data, isJson, output); + } *output << "}"; } return RESULT_OK; diff --git a/src/lib/ebus/data.h b/src/lib/ebus/data.h index 007877f81..e922529cb 100755 --- a/src/lib/ebus/data.h +++ b/src/lib/ebus/data.h @@ -239,6 +239,17 @@ class DataField : public AttributedItem { */ static const char* getDayName(int day); + /** + * Add raw data to the output (excluding the length field). + * @param offset the offset in the data part of the @a SymbolString. + * @param length the maximum number of symbols to dump. + * @param input the @a SymbolString to dump from. + * @param isJson true for JSON format, false for text. + * @param output the ostream to append the raw data to. + * @return true when something was added to the output. + */ + static bool addRaw(size_t offset, size_t length, const SymbolString& input, bool isJson, ostream* output); + /** * Returns the length of this field (or contained fields) in bytes. * @param partType the message part of the contained fields to limit the length calculation to. diff --git a/src/lib/ebus/datatype.h b/src/lib/ebus/datatype.h index 8b6395ba4..013d2f702 100755 --- a/src/lib/ebus/datatype.h +++ b/src/lib/ebus/datatype.h @@ -110,6 +110,9 @@ enum OutputFormat : OutputFormatBaseType { /** bit flag for @a OutputFormat: include message/field definition. */ OF_DEFINITION = 1 << 8, + + /** bit flag for @a OutputFormat: include raw data. */ + OF_RAWDATA = 1 << 9, }; constexpr inline enum OutputFormat operator| (enum OutputFormat self, enum OutputFormat other) { diff --git a/src/lib/ebus/message.cpp b/src/lib/ebus/message.cpp index 2c496ce65..a3cb3fe75 100644 --- a/src/lib/ebus/message.cpp +++ b/src/lib/ebus/message.cpp @@ -769,36 +769,25 @@ result_t Message::storeLastData(size_t index, const SlaveSymbolString& data) { return RESULT_OK; } -result_t Message::decodeLastData(bool master, bool leadingSeparator, const char* fieldName, - ssize_t fieldIndex, OutputFormat outputFormat, ostream* output) const { - result_t result; - if (master) { - result = m_data->read(m_lastMasterData, getIdLength(), leadingSeparator, fieldName, fieldIndex, - outputFormat, -1, output); - } else { - result = m_data->read(m_lastSlaveData, 0, leadingSeparator, fieldName, fieldIndex, - outputFormat, -1, output); - } - if (result < RESULT_OK) { - return result; - } - if (result == RESULT_EMPTY && (fieldName != nullptr || fieldIndex >= 0)) { - return RESULT_ERR_NOTFOUND; - } - return result; -} - -result_t Message::decodeLastData(bool leadingSeparator, const char* fieldName, +result_t Message::decodeLastData(PartType part, bool leadingSeparator, const char* fieldName, ssize_t fieldIndex, const OutputFormat outputFormat, ostream* output) const { + if ((outputFormat & OF_RAWDATA) && !(outputFormat & OF_JSON)) { + *output << "[" << m_lastMasterData.getStr(2, 0, false) + << "/" << m_lastSlaveData.getStr(0, 0, false) + << "] "; + } ostream::pos_type startPos = output->tellp(); - result_t result = m_data->read(m_lastMasterData, getIdLength(), leadingSeparator, fieldName, fieldIndex, - outputFormat, -1, output); - if (result < RESULT_OK) { - return result; + result_t result = RESULT_EMPTY; + bool skipSlaveData = part == pt_masterData; + if (part == pt_any || skipSlaveData) { + result = m_data->read(m_lastMasterData, getIdLength(), leadingSeparator, fieldName, fieldIndex, + outputFormat, -1, output); + if (result < RESULT_OK) { + return result; + } } bool empty = result == RESULT_EMPTY; - bool skipSlaveData = false; - if (fieldIndex >= 0) { + if (!skipSlaveData && fieldIndex >= 0) { fieldIndex -= m_data->getCount(pt_masterData, fieldName); if (fieldIndex < 0) { skipSlaveData = true; @@ -953,7 +942,7 @@ void Message::dumpField(const string& fieldName, bool withConditions, OutputForm dumpAttribute(false, outputFormat, fieldName, output); } -void Message::decodeJson(bool leadingSeparator, bool appendDirectionCondition, bool withData, bool addRaw, +void Message::decodeJson(bool leadingSeparator, bool appendDirectionCondition, bool withData, OutputFormat outputFormat, ostringstream* output) const { outputFormat |= OF_JSON; if (leadingSeparator) { @@ -1003,14 +992,14 @@ void Message::decodeJson(bool leadingSeparator, bool appendDirectionCondition, b } appendAttributes(outputFormat, output); if (hasData) { - if (addRaw) { + if (outputFormat & OF_RAWDATA) { m_lastMasterData.dumpJson(true, output); m_lastSlaveData.dumpJson(true, output); *output << dec; } size_t pos = (size_t)output->tellp(); *output << ",\n \"fields\": {"; - result_t dret = decodeLastData(false, nullptr, -1, outputFormat, output); + result_t dret = decodeLastData(pt_any, false, nullptr, -1, outputFormat, output); if (dret == RESULT_OK) { *output << "\n }"; } else { @@ -1694,7 +1683,7 @@ void SimpleNumericCondition::dumpValuesJson(ostream* output) const { bool SimpleStringCondition::checkValue(const Message* message, const string& field) { ostringstream output; - result_t result = message->decodeLastData(false, field.length() == 0 ? nullptr : field.c_str(), -1, OF_NONE, &output); + result_t result = message->decodeLastData(pt_any, false, field.length() == 0 ? nullptr : field.c_str(), -1, OF_NONE, &output); if (result == RESULT_OK) { string value = output.str(); for (size_t i = 0; i < m_values.size(); i++) { @@ -2931,7 +2920,7 @@ void MessageMap::dump(bool withConditions, OutputFormat outputFormat, ostream* o } if (isJson) { ostringstream str; - message->decodeJson(false, false, false, false, outputFormat, &str); + message->decodeJson(false, false, false, outputFormat, &str); string add = str.str(); size_t pos = add.find('{'); *output << " {\n \"circuit\": \"" << message->getCircuit() << "\", " << add.substr(pos+1); @@ -2952,7 +2941,7 @@ void MessageMap::dump(bool withConditions, OutputFormat outputFormat, ostream* o } if (isJson) { ostringstream str; - message->decodeJson(!wasFirst, true, false, false, outputFormat, &str); + message->decodeJson(!wasFirst, true, false, outputFormat, &str); *output << str.str(); } else { message->dump(nullptr, withConditions, outputFormat, output); diff --git a/src/lib/ebus/message.h b/src/lib/ebus/message.h index 5875e66b7..ca862ee9d 100644 --- a/src/lib/ebus/message.h +++ b/src/lib/ebus/message.h @@ -478,8 +478,8 @@ class Message : public AttributedItem { virtual result_t storeLastData(size_t index, const SlaveSymbolString& data); /** - * Decode the value from the last stored master or slave data. - * @param master true for decoding the master data, false for slave. + * Decode value(s) from the last stored data. + * @param part the part to decode. * @param leadingSeparator whether to prepend a separator before the formatted value. * @param fieldName the optional name of a field to limit the output to. * @param fieldIndex the optional index of the field to limit the output to (either named or overall), or -1. @@ -487,20 +487,8 @@ class Message : public AttributedItem { * @param output the @a ostream to append the formatted value to. * @return @a RESULT_OK on success, or an error code. */ - virtual result_t decodeLastData(bool master, bool leadingSeparator, const char* fieldName, - ssize_t fieldIndex, OutputFormat outputFormat, ostream* output) const; - - /** - * Decode the value from the last stored master and slave data. - * @param leadingSeparator whether to prepend a separator before the formatted value. - * @param fieldName the optional name of a field to limit the output to. - * @param fieldIndex the optional index of the field to limit the output to (either named or overall), or -1. - * @param outputFormat the @a OutputFormat options to use. - * @param output the @a ostream to append the formatted value to. - * @return @a RESULT_OK on success, or an error code. - */ - virtual result_t decodeLastData(bool leadingSeparator, const char* fieldName, - ssize_t fieldIndex, OutputFormat outputFormat, ostream* output) const; + virtual result_t decodeLastData(PartType part, bool leadingSeparator, const char* fieldName, + ssize_t fieldIndex, const OutputFormat outputFormat, ostream* output) const; /** * Decode a particular numeric field value from the last stored data. @@ -599,11 +587,10 @@ class Message : public AttributedItem { * @param leadingSeparator whether to prepend a separator before the first value. * @param appendDirectionCondition whether to append the direction and condition to the name key. * @param withData whether to add the last data as well. - * @param addRaw whether to add the raw symbols as well. * @param outputFormat the @a OutputFormat options to use. * @param output the @a ostringstream to append the decoded value(s) to. */ - virtual void decodeJson(bool leadingSeparator, bool appendDirectionCondition, bool withData, bool addRaw, + virtual void decodeJson(bool leadingSeparator, bool appendDirectionCondition, bool withData, OutputFormat outputFormat, ostringstream* output) const; protected: diff --git a/src/lib/ebus/test/test_message.cpp b/src/lib/ebus/test/test_message.cpp index 694919f23..323ae23c2 100644 --- a/src/lib/ebus/test/test_message.cpp +++ b/src/lib/ebus/test/test_message.cpp @@ -184,6 +184,7 @@ int main() { {"", "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", "r,cirCIRCUITcuit,naNAMEme,comCOMMENTment,ff,75,b509,0d0100,field,s,UCH,,,: [b5090d0100/2a] field=[2a]42", "ff75b509030d0100", "012a", "DNr"}, {"r,CIRCUIT,NAME,COMMENT,,,,0100,field,,UCH", " \"naNAMEme\": {\n" " \"name\": \"naNAMEme\",\n" @@ -194,13 +195,32 @@ int main() { " \"zz\": 117,\n" " \"id\": [181, 9, 13, 1, 0],\n" " \"fields\": {\n" - " \"0\": {\"name\": \"field\", \"value\": 42}\n" + " \"field\": {\"value\": 42}\n" " },\n" " \"fielddefs\": [\n" " { \"name\": \"field\", \"slave\": true, \"type\": \"UCH\", \"isbits\": false, \"length\": 1, \"unit\": \"\", \"comment\": \"\"}\n" " ]\n" " }: \n" " \"field\": {\"value\": 42}", "ff75b509030d0100", "012a", "jN"}, + {"r,CIRCUIT,NAME,COMMENT,,,,0100,field,,UCH", + " \"naNAMEme\": {\n" + " \"name\": \"naNAMEme\",\n" + " \"passive\": false,\n" + " \"write\": false,\n" + " \"lastup\": *,\n" + " \"qq\": 255,\n" + " \"zz\": 117,\n" + " \"id\": [181, 9, 13, 1, 0],\n" + " \"master\": [255, 117, 181, 9, 3, 13, 1, 0],\n" + " \"slave\": [1, 42],\n" + " \"fields\": {\n" + " \"field\": {\"value\": 42, \"raw\": [42]}\n" + " },\n" + " \"fielddefs\": [\n" + " { \"name\": \"field\", \"slave\": true, \"type\": \"UCH\", \"isbits\": false, \"length\": 1, \"unit\": \"\", \"comment\": \"\"}\n" + " ]\n" + " }: \n" + " \"field\": {\"value\": 42, \"raw\": [42]}", "ff75b509030d0100", "012a", "jNr"}, }; templates = new DataFieldTemplates(); unsigned int lineNo = 0; @@ -235,6 +255,10 @@ int main() { bool decodeVerbose = flags.find('D') != string::npos || flags.find('J') != string::npos; bool withMessageDump = flags.find('N') != string::npos; bool decode = decodeJson || decodeVerbose || (flags.find('d') != string::npos); + OutputFormat verbosity = (decodeVerbose?OF_NAMES|OF_UNITS|OF_COMMENTS:OF_NONE)|(decodeJson?OF_NAMES|OF_JSON:OF_NONE); + if (flags.find('r') != string::npos) { + verbosity |= OF_RAWDATA; + } bool failedPrepare = flags.find('p') != string::npos; bool failedPrepareMatch = flags.find('P') != string::npos; bool multi = flags.find('*') != string::npos; @@ -408,7 +432,7 @@ int main() { ostringstream output; if (withMessageDump) { if (decodeJson) { - message->decodeJson(false, false, true, false, OF_JSON|OF_DEFINITION, &output); + message->decodeJson(false, false, true, verbosity|OF_DEFINITION, &output); string str = output.str(); size_t start = str.find("\"lastup\": "); if (start != string::npos) { @@ -425,8 +449,7 @@ int main() { } output << ": "; } - result = message->decodeLastData(false, nullptr, -1, - (decodeVerbose?OF_NAMES|OF_UNITS|OF_COMMENTS:OF_NONE)|(decodeJson?OF_NAMES|OF_JSON:OF_NONE), &output); + result = message->decodeLastData(pt_any, false, nullptr, -1, verbosity, &output); if (result != RESULT_OK) { cout << " \"" << check[2] << "\" / \"" << check[3] << "\": decode error " << (message->isWrite() ? "write: " : "read: ") << getResultCode(result) << endl; From d5f448a15e098bb00b092766d1b7653abab54412 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 28 Jun 2024 18:06:00 +0200 Subject: [PATCH 249/345] unify decode output result+log, fix special broadcast write output --- src/ebusd/mainloop.cpp | 49 +++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index 5c31433d6..6e6f554c5 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -864,11 +864,18 @@ result_t MainLoop::executeRead(const vector& args, const string& levels, } ret = cacheMessage->decodeLastData(pt_any, false, fieldIndex == -2 ? nullptr : fieldName.c_str(), fieldIndex, verbosity, ostream); - if (ret != RESULT_OK) { - if (ret < RESULT_OK) { - logError(lf_main, "read %s %s cached: %s", cacheMessage->getCircuit().c_str(), - cacheMessage->getName().c_str(), getResultCode(ret)); + if (ret < RESULT_OK) { + logError(lf_main, "read %s %s cached: decode %s", cacheMessage->getCircuit().c_str(), + cacheMessage->getName().c_str(), getResultCode(ret)); + const auto str = ostream->str(); + ostream->str(""); + *ostream << getResultCode(ret) << " in decode"; + if (!str.empty()) { + *ostream << ": " << str; } + return RESULT_OK; + } + if (ret > RESULT_OK) { return ret; } logInfo(lf_main, "read %s %s cached: %s", cacheMessage->getCircuit().c_str(), cacheMessage->getName().c_str(), @@ -900,8 +907,12 @@ result_t MainLoop::executeRead(const vector& args, const string& levels, if (ret < RESULT_OK) { logError(lf_main, "read %s %s: decode %s", message->getCircuit().c_str(), message->getName().c_str(), getResultCode(ret)); + const auto str = ostream->str(); ostream->str(""); *ostream << getResultCode(ret) << " in decode"; + if (!str.empty()) { + *ostream << ": " << str; + } return RESULT_OK; } if (ret > RESULT_OK) { @@ -1077,32 +1088,26 @@ result_t MainLoop::executeWrite(const vector& args, const string levels, getResultCode(ret)); return ret; } - dstAddress = message->getLastMasterData().dataAt(1); - if (dstAddress == BROADCAST || isMaster(dstAddress)) { - logNotice(lf_main, "write %s %s: %s", message->getCircuit().c_str(), message->getName().c_str(), - getResultCode(ret)); - if (dstAddress == BROADCAST) { - *ostream << "done broadcast"; - } - return RESULT_OK; - } + dstAddress = message->getLastMasterData()[1]; ret = message->decodeLastData(pt_any, false, nullptr, -1, OF_NONE, ostream); // decode data - if (ret >= RESULT_OK && ostream->str().empty()) { - logNotice(lf_main, "write %s %s: decode %s", message->getCircuit().c_str(), message->getName().c_str(), - getResultCode(ret)); - return RESULT_OK; - } - if (ret != RESULT_OK) { + if (ret < RESULT_OK) { logError(lf_main, "write %s %s: decode %s", message->getCircuit().c_str(), message->getName().c_str(), getResultCode(ret)); ostream->str(""); *ostream << getResultCode(ret) << " in decode"; return RESULT_OK; } - logNotice(lf_main, "write %s %s: %s", message->getCircuit().c_str(), message->getName().c_str(), - ostream->str().c_str()); - return RESULT_OK; + if (dstAddress == BROADCAST && ostream->tellp() == 0) { + if (ret == RESULT_OK) { + *ostream << getResultCode(ret) << " "; + } + *ostream << "broadcast"; + } + string code = ret == RESULT_OK ? "" : (string(getResultCode(ret)) + " "); + logNotice(lf_main, "write %s %s: %s%s", message->getCircuit().c_str(), message->getName().c_str(), + code.c_str(), ostream->str().c_str()); + return ret; } result_t MainLoop::parseHexAndSend(const vector& args, size_t& argPos, bool isDirectMode, From 6e59fd2c87e7e82ce08d84d2ee6bea49d7c5efae Mon Sep 17 00:00:00 2001 From: John Date: Fri, 28 Jun 2024 18:07:03 +0200 Subject: [PATCH 250/345] add verbosity to write cmd --- src/ebusd/mainloop.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index 6e6f554c5..c867563be 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -925,11 +925,19 @@ result_t MainLoop::executeRead(const vector& args, const string& levels, result_t MainLoop::executeWrite(const vector& args, const string levels, ostringstream* ostream) { size_t argPos = 1; bool hex = false, newDefinition = false; + OutputFormat verbosity = OF_NONE; string circuit; symbol_t srcAddress = SYN, dstAddress = SYN; while (args.size() > argPos && args[argPos][0] == '-') { if (args[argPos] == "-h") { hex = true; + } else if (args[argPos] == "-V") { + if ((verbosity & VERBOSITY_4) == VERBOSITY_4) { + verbosity |= OF_RAWDATA; + } + verbosity |= VERBOSITY_4; + } else if (args[argPos] == "-VV") { + verbosity |= VERBOSITY_4 | OF_RAWDATA; } else if (args[argPos] == "-def") { if (!m_newlyDefinedMessages) { *ostream << "ERR: option not enabled"; @@ -982,6 +990,7 @@ result_t MainLoop::executeWrite(const vector& args, const string levels, " -s QQ override source address QQ\n" " -d ZZ override destination address ZZ\n" " -c CIRCUIT CIRCUIT of the message to send\n" + " -V be very verbose (all attributes, plus raw data if given more than once)\n" " NAME NAME of the message to send\n" " VALUE a single field VALUE\n" " -def write with explicit message definition (only if enabled):\n" @@ -1027,7 +1036,7 @@ result_t MainLoop::executeWrite(const vector& args, const string levels, ret = message->storeLastData(master, slave); ostringstream result; if (ret == RESULT_OK) { - ret = message->decodeLastData(pt_any, false, nullptr, -1, OF_NONE, &result); + ret = message->decodeLastData(pt_any, false, nullptr, -1, verbosity, &result); } if (ret >= RESULT_OK) { logInfo(lf_main, "write hex %s %s cache update: %s", message->getCircuit().c_str(), @@ -1090,7 +1099,7 @@ result_t MainLoop::executeWrite(const vector& args, const string levels, } dstAddress = message->getLastMasterData()[1]; - ret = message->decodeLastData(pt_any, false, nullptr, -1, OF_NONE, ostream); // decode data + ret = message->decodeLastData(pt_any, false, nullptr, -1, verbosity, ostream); // decode data if (ret < RESULT_OK) { logError(lf_main, "write %s %s: decode %s", message->getCircuit().c_str(), message->getName().c_str(), getResultCode(ret)); From a8362bd087a16ee67a523bb0167ba1c4a62e91de Mon Sep 17 00:00:00 2001 From: John Date: Fri, 28 Jun 2024 18:11:40 +0200 Subject: [PATCH 251/345] keep generated decode output and append it to error prefix on error --- src/ebusd/mainloop.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index c867563be..14a8a0878 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -382,10 +382,15 @@ void MainLoop::run() { scanRetry = 0; // restart scan counting } if (!req->isHttp() && (ostream.tellp() == 0 || result != RESULT_OK)) { - if (reqMode.listenMode != lm_direct) { - ostream.str(""); + string suffix; + if (result == RESULT_EMPTY && ostream.tellp() > 0) { + suffix = ostream.str(); } + ostream.str(""); ostream << getResultCode(result); + if (!suffix.empty()) { + ostream << " " << suffix; + } } const auto resp = ostream.str(); req->log(&resp); From 20982e5e3ec87944bdec1922be4fdbec1430b49b Mon Sep 17 00:00:00 2001 From: John Date: Fri, 28 Jun 2024 18:41:44 +0200 Subject: [PATCH 252/345] allow define in write direction if given more than once --- src/ebusd/mainloop.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index 14a8a0878..c7ebe7331 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -613,7 +613,7 @@ result_t MainLoop::executeAuth(const vector& args, string* user, ostring result_t MainLoop::executeRead(const vector& args, const string& levels, ostringstream* ostream) { size_t argPos = 1; - bool hex = false, newDefinition = false; + bool hex = false, newDefinition = false, writeDirection = false; OutputFormat verbosity = OF_NONE; time_t maxAge = 5*60; string circuit, params; @@ -627,6 +627,9 @@ result_t MainLoop::executeRead(const vector& args, const string& levels, *ostream << "ERR: option not enabled"; return RESULT_OK; } + if (newDefinition) { + writeDirection = true; + } newDefinition = true; } else if (args[argPos] == "-f") { maxAge = 0; @@ -742,7 +745,7 @@ result_t MainLoop::executeRead(const vector& 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 (only if enabled):\n" + " -def read with explicit message definition (only if enabled, allow write direction if given more than once):\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" @@ -842,9 +845,9 @@ result_t MainLoop::executeRead(const vector& args, const string& levels, return RESULT_OK; } deque messages; - m_newlyDefinedMessages->findAll("", "", levels, false, true, false, false, true, false, 0, 0, false, &messages); + m_newlyDefinedMessages->findAll("", "", levels, false, true, writeDirection, false, true, false, 0, 0, false, &messages); if (messages.empty()) { - *ostream << "ERR: bad definition: no read message"; + *ostream << "ERR: bad definition: no read" << (writeDirection?"/write":"") << " message"; return RESULT_OK; } message = *messages.begin(); From 6a2fced0ccab1de4eb95aaa304f8db411e33575b Mon Sep 17 00:00:00 2001 From: John Date: Fri, 28 Jun 2024 19:04:13 +0200 Subject: [PATCH 253/345] updated --- ChangeLog.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index 1b6e097dc..82bb284b9 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -3,10 +3,15 @@ * fix conditional messages not being sent to message definition in MQTT integration and not being used in KNX group association * fix CSV dump of config files on command line * fix DTM type with recent dates +* fix for some updated messages not appearing on KNX or MQTT +* fix for parsing certain condition ranges ## Features * add "inject" command * add config path to verbose "info" command * add "answer" command +* add option to inject start-up commands +* add verbose raw data option to "read" and "write" commands +* add option to allow write direction in "read" command when inline defining a new message # 23.3 (2023-12-26) From f8e8b23dfe4769446b42b88648ca58a684fb61ab Mon Sep 17 00:00:00 2001 From: John Date: Fri, 28 Jun 2024 19:19:47 +0200 Subject: [PATCH 254/345] fix for reload not starting the scan again (closes #1283) --- src/ebusd/bushandler.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ebusd/bushandler.cpp b/src/ebusd/bushandler.cpp index 36ab84804..b37d05ef7 100644 --- a/src/ebusd/bushandler.cpp +++ b/src/ebusd/bushandler.cpp @@ -317,6 +317,7 @@ bool GrabbedMessage::dump(bool unknown, MessageMap* messages, bool first, Output void BusHandler::clear() { m_protocol->clear(); m_scanResults.clear(); + memset(m_seenAddresses, 0, sizeof(m_seenAddresses)); } result_t BusHandler::readFromBus(Message* message, const string& inputStr, symbol_t dstAddress, From b22a1135f5fa4bb2186ae4d8185b9f76fe521cec Mon Sep 17 00:00:00 2001 From: John Date: Sun, 7 Jul 2024 19:31:57 +0200 Subject: [PATCH 255/345] switch to gh pages config service --- contrib/config/README.md | 10 +++++----- src/ebusd/main.cpp | 34 ++++++++++++++++++++-------------- src/ebusd/main.h | 7 +++++-- src/ebusd/main_args.cpp | 7 ------- 4 files changed, 30 insertions(+), 28 deletions(-) diff --git a/contrib/config/README.md b/contrib/config/README.md index 2ea3d4b31..903075685 100644 --- a/contrib/config/README.md +++ b/contrib/config/README.md @@ -1,8 +1,8 @@ +## former ebusd.eu config webservice -ebusd.eu config webservice -========================== - -This is the code of the webservice at https://cfg.ebusd.eu/ that allows ebusd to download the needed CSV configuration +This is the code of the former webservice at https://cfg.ebusd.eu/ that allowed 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=https://cfg.ebusd.eu/` commandline option. +Meanwhile the service was moved to using github pages at `https://ebus.github.io/`. + +The code here is kept for those who want to serve the old style configuration on their own. diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index b97b29bdc..1f7c7a927 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -40,11 +40,8 @@ namespace ebusd { using std::cout; -/** the previous config path part to rewrite to the current one. */ -#define PREVIOUS_CONFIG_PATH_SUFFIX "://ebusd.eu/config/" - -/** the second previous config path part to rewrite to the current one. */ -#define PREVIOUS_CONFIG_PATH_SUFFIX2 "://cfg.ebusd.eu/" +/** the previous config path suffixes to rewrite to the current one. */ +#define PREVIOUS_CONFIG_PATH_SUFFIXES {"ebusd.eu/config/", "cfg.ebusd.eu/"} /** the opened PID file, or nullptr. */ static FILE* s_pidFile = nullptr; @@ -255,7 +252,7 @@ int main(int argc, char* argv[], char* envp[]) { const string lang = MappedFileReader::normalizeLanguage( s_opt.preferLanguage == nullptr || !s_opt.preferLanguage[0] ? "" : s_opt.preferLanguage); - string configLocalPrefix, configUriPrefix; + string configLocalPrefix, configUriPrefix, configLangQuery; #ifdef HAVE_SSL HttpClient::initialize(s_opt.caFile, s_opt.caPath); #else // HAVE_SSL @@ -270,14 +267,16 @@ int main(int argc, char* argv[], char* envp[]) { logWrite(lf_main, ll_error, "invalid configpath without scanconfig"); // force logging on exit return EINVAL; } - size_t pos = configPath.find(PREVIOUS_CONFIG_PATH_SUFFIX); - if (pos != string::npos) { - string newPath = configPath.substr(0, pos) + CONFIG_PATH_SUFFIX - + configPath.substr(pos+strlen(PREVIOUS_CONFIG_PATH_SUFFIX)); - logNotice(lf_main, "replaced old configPath %s with new one: %s", s_opt.configPath, newPath.c_str()); - configPath = newPath; + for (auto prevSuffix : PREVIOUS_CONFIG_PATH_SUFFIXES) { + size_t pos = configPath.find(prevSuffix); + if (pos == string::npos || (pos >= 3 && configPath.substr(pos-3, 3) != "://")) { + continue; + } + configPath = CONFIG_PATH; + logNotice(lf_main, "replaced old configPath %s with new one: %s", s_opt.configPath, configPath.c_str()); + break; } - uint16_t configPort = 80; + uint16_t configPort = 443; string proto, configHost; if (!HttpClient::parseUrl(configPath, &proto, &configHost, &configPort, &configUriPrefix)) { #ifndef HAVE_SSL @@ -289,6 +288,13 @@ int main(int argc, char* argv[], char* envp[]) { logWrite(lf_main, ll_error, "invalid configPath URL"); // force logging on exit return EINVAL; } + if (configHost == CONFIG_HOST) { + string suffix = (lang != "en" ? "de" : lang) + "/"; + configUriPrefix += suffix; + configPath += suffix; // only for informational purposes + } else { + configLangQuery = lang.empty() ? lang : "?l=" + lang; + } configHttpClient = new HttpClient(); if ( !configHttpClient->connect(configHost, configPort, proto == "https", PACKAGE_NAME "/" PACKAGE_VERSION) @@ -306,7 +312,7 @@ int main(int argc, char* argv[], char* envp[]) { s_messageMap = new MessageMap(s_opt.checkConfig, lang); s_scanHelper = new ScanHelper(s_messageMap, configPath, configLocalPrefix, configUriPrefix, - lang.empty() ? lang : "?l=" + lang, configHttpClient, s_opt.checkConfig); + configLangQuery, configHttpClient, s_opt.checkConfig); s_messageMap->setResolver(s_scanHelper); if (s_opt.checkConfig) { logNotice(lf_main, PACKAGE_STRING "." REVISION " performing configuration check..."); diff --git a/src/ebusd/main.h b/src/ebusd/main.h index 0137514fd..7bf4dbb87 100755 --- a/src/ebusd/main.h +++ b/src/ebusd/main.h @@ -32,8 +32,11 @@ namespace ebusd { * The main entry method doing all the startup handling. */ -/** the config path part behind the scheme (scheme without "://"). */ -#define CONFIG_PATH_SUFFIX "://cfg.ebusd.eu/" +/** the default host of the configuration files. */ +#define CONFIG_HOST "ebus.github.io" + +/** the default location of the configuration files (without language suffix). */ +#define CONFIG_PATH "https://" CONFIG_HOST "/" /** A structure holding all program options. */ typedef struct options { diff --git a/src/ebusd/main_args.cpp b/src/ebusd/main_args.cpp index 6c92d73c3..236e72956 100755 --- a/src/ebusd/main_args.cpp +++ b/src/ebusd/main_args.cpp @@ -29,13 +29,6 @@ namespace ebusd { -/** the default path of the configuration files. */ -#ifdef HAVE_SSL -#define CONFIG_PATH "https" CONFIG_PATH_SUFFIX -#else // HAVE_SSL -#define CONFIG_PATH "http" CONFIG_PATH_SUFFIX -#endif // HAVE_SSL - /** the default program options. */ static const options_t s_default_opt = { .device = "/dev/ttyUSB0", From ef4d68930f03c88dad97fb381f2d2ec2f3a4f063 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 7 Jul 2024 19:36:00 +0200 Subject: [PATCH 256/345] support dumping whole subdir from gh pages --- src/ebusd/main.cpp | 2 +- src/ebusd/main_args.cpp | 4 ++-- src/ebusd/scan.cpp | 7 +++++++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index 1f7c7a927..bf8b83ee3 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -263,7 +263,7 @@ int main(int argc, char* argv[], char* envp[]) { if (configPath.find("://") == string::npos) { configLocalPrefix = s_opt.configPath; } else { - if (!s_opt.scanConfig) { + if (!s_opt.scanConfig && s_opt.dumpConfig == OF_NONE) { logWrite(lf_main, ll_error, "invalid configpath without scanconfig"); // force logging on exit return EINVAL; } diff --git a/src/ebusd/main_args.cpp b/src/ebusd/main_args.cpp index 236e72956..38f426abe 100755 --- a/src/ebusd/main_args.cpp +++ b/src/ebusd/main_args.cpp @@ -148,7 +148,7 @@ static const argDef argDefs[] = { {"configpath", 'c', "PATH", 0, "Read CSV config files from PATH (local folder or HTTPS URL) [" CONFIG_PATH "]"}, {"scanconfig", 's', "ADDR", af_optional, "Pick CSV config files matching initial scan ADDR: " - "empty for broadcast ident message (default when configpath is not given), " + "empty for broadcast ident message (default when neither configpath nor dumpconfig is not given), " "\"none\" for no initial scan message, " "\"full\" for full scan, " "a single hex address to scan, or " @@ -667,7 +667,7 @@ int parse_main_args(int argc, char* argv[], char* envp[], options_t *opt) { return ret; } - if (!opt->readOnly && !opt->scanConfigOrPathSet) { + if (!opt->readOnly && !opt->scanConfigOrPathSet && opt->dumpConfig == OF_NONE) { opt->scanConfig = true; opt->initialScan = BROADCAST; } diff --git a/src/ebusd/scan.cpp b/src/ebusd/scan.cpp index 2a0186546..3ae6a5160 100644 --- a/src/ebusd/scan.cpp +++ b/src/ebusd/scan.cpp @@ -102,6 +102,13 @@ result_t ScanHelper::collectConfigFiles(const string& relPath, const string& pre } continue; } + if (name.back() == '/') { + // directory + if (dirs != nullptr) { + dirs->push_back(relPathWithSlash + name.substr(0, name.length() - 1)); + } + continue; + } if (prefix.length() == 0 ? (!ignoreAddressPrefix || name.length() < 3 || name.find_first_of('.') != 2) : (name.length() >= prefix.length() && name.substr(0, prefix.length()) == prefix)) { files->push_back(relPathWithSlash + name); From b5899fc37848b006e0967d19b163680f78838888 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 7 Jul 2024 19:36:25 +0200 Subject: [PATCH 257/345] updated --- ChangeLog.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index 82bb284b9..bb116bdaa 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -5,6 +5,8 @@ * fix DTM type with recent dates * fix for some updated messages not appearing on KNX or MQTT * fix for parsing certain condition ranges +* fix for "reload" command not starting the scan again + ## Features * add "inject" command * add config path to verbose "info" command @@ -13,6 +15,9 @@ * add verbose raw data option to "read" and "write" commands * add option to allow write direction in "read" command when inline defining a new message +## Breaking Changes +* change default config path to https://ebus.github.io/ serving files generated from new TypeSpec message definition sources + # 23.3 (2023-12-26) ## Bug Fixes From 0efcdf10a41de9adc4ec6311d0a6c11f75a699ff Mon Sep 17 00:00:00 2001 From: John Date: Mon, 8 Jul 2024 07:51:56 +0200 Subject: [PATCH 258/345] support cdn redirection --- contrib/updatecheck/calcversions.sh | 6 ++++- contrib/updatecheck/index.php | 2 +- contrib/updatecheck/prepend.inc | 41 ++++++++++++++++------------- src/ebusd/bushandler.cpp | 2 +- src/ebusd/main.cpp | 27 +++++++++++++++++-- src/ebusd/main_args.cpp | 2 +- src/ebusd/mainloop.cpp | 4 +++ src/ebusd/scan.h | 5 ++++ 8 files changed, 64 insertions(+), 25 deletions(-) diff --git a/contrib/updatecheck/calcversions.sh b/contrib/updatecheck/calcversions.sh index 42ffb2cb3..1c160f7d8 100755 --- a/contrib/updatecheck/calcversions.sh +++ b/contrib/updatecheck/calcversions.sh @@ -2,12 +2,13 @@ version=`head -n 1 ../../VERSION` revision=`git describe --always` echo "ebusd=${version},${revision}" > versions.txt -echo "ebusd=${version},${revision}" > oldversions.txt +# cp versions.txt > oldversions.txt devver=`curl -s https://adapter.ebusd.eu/v31/firmware/ChangeLog|grep "Version "|head -n 1|sed -e 's#.*]*>##' -e 's#<.*##' -e 's# ##'` devbl=`curl -s https://adapter.ebusd.eu/v31/firmware/ChangeLog|grep "Bootloader version"|head -n 1|sed -e 's#.*]*>##' -e 's#<.*##' -e 's# ##'` echo "device=${devver},${devbl}" >> versions.txt devver=`curl -s https://adapter.ebusd.eu/v5/ChangeLog|grep "Version "|head -n 1|sed -e 's#.*]*>##' -e 's#<.*##' -e 's# ##'` echo "device=${devver},${devver}" >> versions.txt +cp versions.txt cdnversions.txt files=`find config/de -type f -or -type l` ../../src/lib/ebus/test/test_filereader $files|sed -e 's#^config/de/##' -e 's#^\([^ ]*\) #\1=#' -e 's# #,#g'|sort >> versions.txt files=`find config/en -type f -or -type l` @@ -17,3 +18,6 @@ for filever in $(../../src/lib/ebus/test/test_filereader $files|sed -e 's#^confi sed -i -e "s#^$file=\(.*\)\$#$file=\1,$ver#" versions.txt done #./oldtest_filereader $files|sed -e 's#^config/##' -e 's#^\([^ ]*\) #\1=#' -e 's# #,#g'|sort >> oldversions.txt +curl -sS https://ebus.github.io/en/versions.json -o veren.json +curl -sS https://ebus.github.io/de/versions.json -o verde.json +node -e 'fs=require("fs");e=JSON.parse(fs.readFileSync("veren.json","utf-8"));d=JSON.parse(fs.readFileSync("verde.json","utf-8"));console.log(Object.entries(e).map(([k,v])=>{w=d[k];return `${k}=${v.hash},${v.size},${v.mtime},${w.hash},${w.size},${w.mtime}`;}).join("\n"))' >> cdnversions.txt diff --git a/contrib/updatecheck/index.php b/contrib/updatecheck/index.php index 74a10897e..372f3e625 100644 --- a/contrib/updatecheck/index.php +++ b/contrib/updatecheck/index.php @@ -5,7 +5,7 @@ $r = @file_get_contents('php://input'); header('Content-Type: text/plain'); $r = @json_decode($r, true); - echo checkUpdate(@$r['v'], @$r['r'], @$r['a'], @$r['dv'], @$r['l'], @$r['lc']); + echo checkUpdate(@$r['v'], @$r['r'], @$r['a'], @$r['dv'], @$r['l'], @$r['lc'], @$r['cp']); exit; } readVersions(); diff --git a/contrib/updatecheck/prepend.inc b/contrib/updatecheck/prepend.inc index 16f4792ef..1af94c2f0 100644 --- a/contrib/updatecheck/prepend.inc +++ b/contrib/updatecheck/prepend.inc @@ -1,31 +1,34 @@ 1) { - $key = $val[0]; - $val = explode(',', $val[1]); - if ($key==='device' && count($val)===2 && $val[0]===$val[1]) { - $key = 'devicesame'; - } - $versions[$key] = $val; - } - }; - array_walk($v, $func); + $v = @file_get_contents($prefix.'versions.txt'); + if (!$v) { + return false; } + $v = explode("\n", $v); + $func = function($val, $k) { + global $versions; + $val = explode('=', $val); + if (count($val)>1) { + $key = $val[0]; + $val = explode(',', $val[1]); + if ($key==='device' && count($val)===2 && $val[0]===$val[1]) { + $key = 'devicesame'; + } + $versions[$key] = $val; + } + }; + array_walk($v, $func); + return true; } -function checkUpdate($ebusdVersion, $ebusdRelease, $architecture, $deviceVersion, $loadedFiles, $language) { +function checkUpdate($ebusdVersion, $ebusdRelease, $architecture, $deviceVersion, $loadedFiles, $language, $cdnPath) { if (!$ebusdVersion) { return 'invalid request'; } - readVersions($architecture && substr($architecture, 0, 3)!=='arm' && (((float)$ebusdVersion)<3.3 || ($ebusdVersion==='3.3' && ($ebusdRelease==='v3.3' || (strtok($ebusdRelease, '-')==='v3.3') && strtok('-')<18)))); + $old = $architecture && substr($architecture, 0, 3)!=='arm' && (((float)$ebusdVersion)<3.3 || ($ebusdVersion==='3.3' && ($ebusdRelease==='v3.3' || (strtok($ebusdRelease, '-')==='v3.3') && strtok('-')<18))); + readVersions($cdnPath ? 'cdn' : ($old ? 'old' : '')) || ($cdnPath && readVersions()); global $versions; $ret = 'unknown'; if ($ebusdVersion==$versions['ebusd'][0] && $ebusdRelease==$versions['ebusd'][1]) { diff --git a/src/ebusd/bushandler.cpp b/src/ebusd/bushandler.cpp index b37d05ef7..5f31976e0 100644 --- a/src/ebusd/bushandler.cpp +++ b/src/ebusd/bushandler.cpp @@ -742,7 +742,7 @@ void BusHandler::formatUpdateInfo(ostringstream* output) const { } unsigned char address = 0; for (int index = 0; index < 256; index++, address++) { - bool ownAddress = !m_protocol->isOwnAddress(address); + bool ownAddress = m_protocol->isOwnAddress(address); if (!isValidAddress(address, false) || ((m_seenAddresses[address]&SEEN) == 0 && !ownAddress)) { continue; } diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index bf8b83ee3..8fd380fac 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -288,8 +288,10 @@ int main(int argc, char* argv[], char* envp[]) { logWrite(lf_main, ll_error, "invalid configPath URL"); // force logging on exit return EINVAL; } - if (configHost == CONFIG_HOST) { - string suffix = (lang != "en" ? "de" : lang) + "/"; + bool isCdn = configHost == CONFIG_HOST; + string suffix; + if (isCdn) { + suffix = (lang != "en" ? "de" : lang) + "/"; configUriPrefix += suffix; configPath += suffix; // only for informational purposes } else { @@ -306,6 +308,27 @@ int main(int argc, char* argv[], char* envp[]) { cleanup(); return EINVAL; } + if (isCdn) { + // check load balancing redirection + string redirPath; + uint16_t redirPort = 443; + string redirProto, redirHost, redirUriPrefix; + bool repeat = false; + bool json = true; + if (configHttpClient->get("/redirect.json", "", &redirPath, nullptr, nullptr, &json) && !json + && HttpClient::parseUrl(redirPath, &redirProto, &redirHost, &redirPort, &redirUriPrefix) + && redirHost != configHost) { + configHttpClient->disconnect(); + ebusd::HttpClient* redirClient = new HttpClient(); + if (redirClient->connect(redirHost, redirPort, redirProto == "https", PACKAGE_NAME "/" PACKAGE_VERSION)) { + configUriPrefix = redirUriPrefix + suffix; + configPath = redirPath + suffix; // only for informational purposes + delete configHttpClient; + configHttpClient = redirClient; + logNotice(lf_main, "configPath URL redirected to %s", configPath.c_str()); + } + } + } logInfo(lf_main, "configPath URL is valid"); configHttpClient->disconnect(); } diff --git a/src/ebusd/main_args.cpp b/src/ebusd/main_args.cpp index 38f426abe..009724e94 100755 --- a/src/ebusd/main_args.cpp +++ b/src/ebusd/main_args.cpp @@ -157,7 +157,7 @@ static const argDef argDefs[] = { "arguments for checking a particular scan configuration, e.g. \"FF08070400/0AB5454850303003277201\"."}, {"scanretries", O_SCNRET, "COUNT", 0, "Retry scanning devices COUNT times [5]"}, {"configlang", O_CFGLNG, "LANG", 0, - "Prefer LANG in multilingual configuration files [system default language]"}, + "Prefer LANG in multilingual configuration files [system default language, DE as fallback]"}, {"checkconfig", O_CHKCFG, nullptr, 0, "Check config files, then stop"}, {"dumpconfig", O_DMPCFG, "FORMAT", af_optional, "Check and dump config files in FORMAT (\"json\" or \"csv\"), then stop"}, diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index c7ebe7331..2c55af91e 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -321,6 +321,10 @@ void MainLoop::run() { << ",\"a\":\"other\"" #endif << ",\"u\":" << (now-start); + const string configPathCDN = m_scanHelper->getConfigPathCDN(); + if (!configPathCDN.empty()) { + ostr << ",\"cp\":\"" << configPathCDN << "\""; + } m_protocol->formatInfoJson(&ostr); if (m_reconnectCount) { ostr << ",\"rc\":" << m_reconnectCount; diff --git a/src/ebusd/scan.h b/src/ebusd/scan.h index 754c1e5fe..1c68866d0 100644 --- a/src/ebusd/scan.h +++ b/src/ebusd/scan.h @@ -71,6 +71,11 @@ class ScanHelper : public Resolver { */ const string getConfigPath() const { return m_configPath; } + /** + * @return the config path when pointing to CDN, empty otherwise. + */ + const string getConfigPathCDN() const { return m_configUriPrefix.empty() ? "" : m_configPath; } + /** * Try to connect to the specified server. * @param host the host name to connect to. From 0b49f9d6da7d17a5e97a0819d3b5f3d630dc84c8 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 9 Jul 2024 08:02:54 +0200 Subject: [PATCH 259/345] fix "as" casing --- contrib/docker/Dockerfile | 4 ++-- contrib/docker/Dockerfile.release | 4 ++-- contrib/docker/Dockerfile.template | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/contrib/docker/Dockerfile b/contrib/docker/Dockerfile index 842081add..64679fc7b 100755 --- a/contrib/docker/Dockerfile +++ b/contrib/docker/Dockerfile @@ -1,6 +1,6 @@ ARG BASE_IMAGE -FROM $BASE_IMAGE as build +FROM $BASE_IMAGE AS build RUN apt-get update && apt-get install -y \ knxd-dev knxd- libmosquitto-dev libssl-dev libstdc++6 libc6 libgcc1 \ @@ -25,7 +25,7 @@ RUN RUNTEST=full GIT_REVISION=$GIT_REVISION ./make_debian.sh --with-knxd -FROM $BASE_IMAGE-slim as image +FROM $BASE_IMAGE-slim AS image RUN apt-get update && apt-get install -y \ knxd-dev knxd- libmosquitto1 libssl1.1 ca-certificates libstdc++6 libc6 libgcc1 \ diff --git a/contrib/docker/Dockerfile.release b/contrib/docker/Dockerfile.release index e63142d33..2ae411728 100644 --- a/contrib/docker/Dockerfile.release +++ b/contrib/docker/Dockerfile.release @@ -1,6 +1,6 @@ ARG BASE_IMAGE -FROM $BASE_IMAGE as build +FROM $BASE_IMAGE AS build RUN apt-get update && apt-get install -y \ libmosquitto-dev libssl-dev libstdc++6 libc6 libgcc1 \ @@ -25,7 +25,7 @@ RUN GIT_REVISION=$GIT_REVISION ./make_debian.sh -FROM $BASE_IMAGE-slim as image +FROM $BASE_IMAGE-slim AS image RUN apt-get update && apt-get install -y \ libmosquitto1 libssl1.1 ca-certificates libstdc++6 libc6 libgcc1 \ diff --git a/contrib/docker/Dockerfile.template b/contrib/docker/Dockerfile.template index 5f0a717f2..7d384f64f 100644 --- a/contrib/docker/Dockerfile.template +++ b/contrib/docker/Dockerfile.template @@ -1,6 +1,6 @@ ARG BASE_IMAGE -FROM $BASE_IMAGE as build +FROM $BASE_IMAGE AS build RUN apt-get update && apt-get install -y \ %EBUSD_EXTRAPKGS%libmosquitto-dev libssl-dev libstdc++6 libc6 libgcc1 \ @@ -25,7 +25,7 @@ RUN %EBUSD_MAKE% %EBUSD_UPLOAD_LINES% -FROM $BASE_IMAGE-slim as image +FROM $BASE_IMAGE-slim AS image RUN apt-get update && apt-get install -y \ %EBUSD_EXTRAPKGS%libmosquitto1 libssl1.1 ca-certificates libstdc++6 libc6 libgcc1 \ From efcb0f32f2f45e5fccf958fb65a914db0b35c957 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 9 Jul 2024 19:53:39 +0200 Subject: [PATCH 260/345] allow test lang as well --- src/ebusd/main.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index 8fd380fac..e5ee6c301 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -291,7 +291,7 @@ int main(int argc, char* argv[], char* envp[]) { bool isCdn = configHost == CONFIG_HOST; string suffix; if (isCdn) { - suffix = (lang != "en" ? "de" : lang) + "/"; + suffix = (lang != "en" && lang != "tt" ? "de" : lang) + "/"; configUriPrefix += suffix; configPath += suffix; // only for informational purposes } else { @@ -313,7 +313,6 @@ int main(int argc, char* argv[], char* envp[]) { string redirPath; uint16_t redirPort = 443; string redirProto, redirHost, redirUriPrefix; - bool repeat = false; bool json = true; if (configHttpClient->get("/redirect.json", "", &redirPath, nullptr, nullptr, &json) && !json && HttpClient::parseUrl(redirPath, &redirProto, &redirHost, &redirPort, &redirUriPrefix) From d6e790b89f8104aaff8f786723441dbcb4b5168b Mon Sep 17 00:00:00 2001 From: John Date: Wed, 10 Jul 2024 21:04:16 +0200 Subject: [PATCH 261/345] fix de/en order --- contrib/updatecheck/calcversions.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/updatecheck/calcversions.sh b/contrib/updatecheck/calcversions.sh index 1c160f7d8..a1333eb1a 100755 --- a/contrib/updatecheck/calcversions.sh +++ b/contrib/updatecheck/calcversions.sh @@ -20,4 +20,4 @@ done #./oldtest_filereader $files|sed -e 's#^config/##' -e 's#^\([^ ]*\) #\1=#' -e 's# #,#g'|sort >> oldversions.txt curl -sS https://ebus.github.io/en/versions.json -o veren.json curl -sS https://ebus.github.io/de/versions.json -o verde.json -node -e 'fs=require("fs");e=JSON.parse(fs.readFileSync("veren.json","utf-8"));d=JSON.parse(fs.readFileSync("verde.json","utf-8"));console.log(Object.entries(e).map(([k,v])=>{w=d[k];return `${k}=${v.hash},${v.size},${v.mtime},${w.hash},${w.size},${w.mtime}`;}).join("\n"))' >> cdnversions.txt +node -e 'fs=require("fs");e=JSON.parse(fs.readFileSync("veren.json","utf-8"));d=JSON.parse(fs.readFileSync("verde.json","utf-8"));console.log(Object.entries(d).map(([k,de])=>{en=e[k];return `${k}=${de.hash},${de.size},${de.mtime},${en.hash},${en.size},${en.mtime}`;}).join("\n"))'|sort >> cdnversions.txt From ad364c68001045c6dd85f5ac95bcfc6636fa04f2 Mon Sep 17 00:00:00 2001 From: John Date: Thu, 11 Jul 2024 20:39:09 +0200 Subject: [PATCH 262/345] fix extra trailing newline (fixes #1304) --- contrib/updatecheck/index.php | 2 +- contrib/updatecheck/prepend.inc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/updatecheck/index.php b/contrib/updatecheck/index.php index 372f3e625..ec6bc51e7 100644 --- a/contrib/updatecheck/index.php +++ b/contrib/updatecheck/index.php @@ -48,4 +48,4 @@ array_walk($versions, $func); ?>
FileGermanEnglish
$k

- + \ No newline at end of file diff --git a/contrib/updatecheck/prepend.inc b/contrib/updatecheck/prepend.inc index 1af94c2f0..5ce705eb2 100644 --- a/contrib/updatecheck/prepend.inc +++ b/contrib/updatecheck/prepend.inc @@ -68,4 +68,4 @@ function checkUpdate($ebusdVersion, $ebusdRelease, $architecture, $deviceVersion } return $ret.$configs; } -?> +?> \ No newline at end of file From 9c3371c4356f7e8c54a914d74bcc8d7d9a60cbf9 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 13 Jul 2024 06:31:09 +0200 Subject: [PATCH 263/345] report unexpected env vars as notice only (closes #1291) --- src/ebusd/main_args.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ebusd/main_args.cpp b/src/ebusd/main_args.cpp index 009724e94..b3b56724d 100755 --- a/src/ebusd/main_args.cpp +++ b/src/ebusd/main_args.cpp @@ -658,7 +658,7 @@ int parse_main_args(int argc, char* argv[], char* envp[], options_t *opt) { logWrite(lf_main, ll_error, "invalid argument in env: %s", *env); // force logging on exit return EINVAL; } - logWrite(lf_main, ll_error, "invalid/unknown argument in env (ignored): %s", *env); // force logging + logWrite(lf_main, ll_notice, "invalid/unknown argument in env (ignored): %s", *env); // force logging } } From f3baf85df1926e67d88054fe237de1f5a58ec730 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 13 Jul 2024 07:03:15 +0200 Subject: [PATCH 264/345] doc for wildcard (related to #1218) --- contrib/etc/ebusd/mqtt-hassio.cfg | 3 ++- contrib/etc/ebusd/mqtt-integration.cfg | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/contrib/etc/ebusd/mqtt-hassio.cfg b/contrib/etc/ebusd/mqtt-hassio.cfg index ab000d8bb..178bfc07e 100644 --- a/contrib/etc/ebusd/mqtt-hassio.cfg +++ b/contrib/etc/ebusd/mqtt-hassio.cfg @@ -90,7 +90,8 @@ # - "|" allows defining alternatives, e.g. "a|b" matches "but" as well as "all". # - "^" matches the beginning of the input, e.g. "^al" matches "al" but not "hal". # - "$" matches the end of the input, e.g. "al$" matches "hal" but not "all". -# - "*" matches a single arbitrary length wildcard part in the middle, e.g. "^a*l$" matches "all" but not "always". +# - "*" matches a single arbitrary length wildcard part in the middle (can only be used once per filter/alternative), +# e.g. "^a*l$" matches "all" but not "always". # include only messages having data sent at least once (only checked for passive or read messages, not for active write) # when set to 1. If set to >1, then all messages passing the other filter criteria (including active read messages) will diff --git a/contrib/etc/ebusd/mqtt-integration.cfg b/contrib/etc/ebusd/mqtt-integration.cfg index d87a287e7..5b8c4fdb9 100644 --- a/contrib/etc/ebusd/mqtt-integration.cfg +++ b/contrib/etc/ebusd/mqtt-integration.cfg @@ -84,7 +84,8 @@ # - "|" allows defining alternatives, e.g. "a|b" matches "but" as well as "all". # - "^" matches the beginning of the input, e.g. "^al" matches "al" but not "hal". # - "$" matches the end of the input, e.g. "al$" matches "hal" but not "all". -# - "*" matches a single arbitrary length wildcard part in the middle, e.g. "^a*l$" matches "all" but not "always". +# - "*" matches a single arbitrary length wildcard part in the middle (can only be used once per filter/alternative), +# e.g. "^a*l$" matches "all" but not "always". # include only messages having data sent at least once (only checked for passive or read messages, not for active write) # when set to 1. If set to >1, then all messages passing the other filter criteria (including active read messages) will From d117acd933bd0dc03557e8d199e3c75aa12c5b79 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 13 Jul 2024 07:06:06 +0200 Subject: [PATCH 265/345] add duration class for uptime (closes #1264) --- contrib/etc/ebusd/mqtt-hassio.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/etc/ebusd/mqtt-hassio.cfg b/contrib/etc/ebusd/mqtt-hassio.cfg index 178bfc07e..9c438ee54 100644 --- a/contrib/etc/ebusd/mqtt-hassio.cfg +++ b/contrib/etc/ebusd/mqtt-hassio.cfg @@ -380,6 +380,7 @@ def_global_running-payload = %global_prefix, "device_class":"running"%global_boolean_suffix def_global_version-topic = def_global_uptime-payload = %global_prefix, + "device_class":"duration", "state_class":"total_increasing", "unit_of_measurement":"s" } From e703e9b7055cd0d7b89cf614e66405b4a740ad1f Mon Sep 17 00:00:00 2001 From: John Date: Sat, 13 Jul 2024 10:09:32 +0200 Subject: [PATCH 266/345] assert names are valid identifier with little exceptions only (closes #1302) --- src/lib/ebus/data.cpp | 50 ++++++++++++++++++++++++++++++++++------ src/lib/ebus/data.h | 16 +++++++++++++ src/lib/ebus/message.cpp | 8 +++++++ 3 files changed, 67 insertions(+), 7 deletions(-) diff --git a/src/lib/ebus/data.cpp b/src/lib/ebus/data.cpp index 35a25805c..50011c574 100644 --- a/src/lib/ebus/data.cpp +++ b/src/lib/ebus/data.cpp @@ -191,6 +191,34 @@ string AttributedItem::getAttribute(const string& name) const { } +bool isValidIdentifierChar(char ch, bool first, bool allowFirstDigit) { + return ((ch >= '0' && ch <= '9') && (!first || allowFirstDigit)) + || (ch >= 'a' && ch <= 'z') + || (ch >= 'A' && ch <= 'Z') + || ch == '_' || ch == '$' + // todo '.' is the only excuse for now and should be removed some day + || (ch == '.' && !first); +} + +bool DataField::checkIdentifier(const string& name, bool allowFirstDigit) { + for (size_t i = 0; i < name.size(); i++) { + char ch = name[i]; + if (!isValidIdentifierChar(ch, i==0, allowFirstDigit)) { + return false; + } + } + return true; +} + +void DataField::normalizeIdentifier(string& name, bool allowFirstDigit) { + for (size_t i = 0; i < name.size(); i++) { + char ch = name[i]; + if (!isValidIdentifierChar(ch, i==0, allowFirstDigit)) { + name[i] = '_'; + } + } +} + result_t DataField::create(bool isWriteMessage, bool isTemplate, bool isBroadcastOrMasterDestination, size_t maxFieldLength, const DataFieldTemplates* templates, vector< map >* rows, string* errorDescription, const DataField** returnField) { @@ -345,6 +373,9 @@ result_t DataField::create(bool isWriteMessage, bool isTemplate, bool isBroadcas if (!dataType) { result = RESULT_ERR_NOTFOUND; *errorDescription = "field type "+typeName+" in field "+formatInt(fieldIndex); + } else if (firstType && !name.empty() && !DataField::checkIdentifier(name)) { + *errorDescription = "field name "+name; + result = RESULT_ERR_INVALID_ARG; } else { SingleDataField* add = nullptr; result = SingleDataField::create(firstType ? name : "", row, dataType, partType, length, divisor, @@ -370,14 +401,19 @@ result_t DataField::create(bool isWriteMessage, bool isTemplate, bool isBroadcas } else { fieldName = (firstType && lastType) ? name : ""; } - if (lastType) { - result = templ->derive(fieldName, partType, divisor, values, &row, &fields); + if (!fieldName.empty() && !DataField::checkIdentifier(fieldName)) { + *errorDescription = "field name "+fieldName; + result = RESULT_ERR_INVALID_ARG; } else { - map attrs = row; // don't let DataField::derive() consume the row - result = templ->derive(fieldName, partType, divisor, values, &attrs, &fields); - } - if (result != RESULT_OK) { - *errorDescription = "derive field "+fieldName+" in field "+formatInt(fieldIndex); + if (lastType) { + result = templ->derive(fieldName, partType, divisor, values, &row, &fields); + } else { + map attrs = row; // don't let DataField::derive() consume the row + result = templ->derive(fieldName, partType, divisor, values, &attrs, &fields); + } + if (result != RESULT_OK) { + *errorDescription = "derive field "+fieldName+" in field "+formatInt(fieldIndex); + } } } if (firstType && !lastType) { diff --git a/src/lib/ebus/data.h b/src/lib/ebus/data.h index e922529cb..f36fc0588 100755 --- a/src/lib/ebus/data.h +++ b/src/lib/ebus/data.h @@ -215,6 +215,22 @@ class DataField : public AttributedItem { */ virtual bool isList() const { return false; } + /** + * Check if the given name is a valid identifier. + * @param name the name to check (and optionally normalize). + * @param allowFirstDigit whether to additionally allow the name to start with a digit. + * @param normalize whether to replace invalid characters with an underscore. + * @return true if the name is valid (or was normalized), false if invalid. + */ + static bool checkIdentifier(const string& name, bool allowFirstDigit = false); + + /** + * Normalize the given name to be a valid identifier. + * @param name the name to check and normalize. + * @param allowFirstDigit whether to additionally allow the name to start with a digit. + */ + static void normalizeIdentifier(string& name, bool allowFirstDigit = false); + /** * Factory method for creating new instances. * @param isWriteMessage whether the field is part of a write message (default false). diff --git a/src/lib/ebus/message.cpp b/src/lib/ebus/message.cpp index a3cb3fe75..ab74a915d 100644 --- a/src/lib/ebus/message.cpp +++ b/src/lib/ebus/message.cpp @@ -301,11 +301,19 @@ result_t Message::create(const string& filename, const DataFieldTemplates* templ *errorDescription = "circuit"; return RESULT_ERR_MISSING_ARG; // empty circuit } + if (!DataField::checkIdentifier(circuit, true)) { + *errorDescription = "circuit name "+circuit; + return RESULT_ERR_INVALID_ARG; // invalid circuit name + } string name = getDefault(pluck("name", row), defaults, "name", true, true); // name if (name.empty()) { *errorDescription = "name"; return RESULT_ERR_MISSING_ARG; // empty name } + if (!DataField::checkIdentifier(name)) { + *errorDescription = "name "+name; + return RESULT_ERR_INVALID_ARG; // invalid message name + } string comment = getDefault(pluck("comment", row), defaults, "comment", true); // [comment] if (!comment.empty()) { (*row)["comment"] = comment; From c5668a0d59292aa57a1ca9390eb4ca1b902ede5a Mon Sep 17 00:00:00 2001 From: John Date: Sat, 13 Jul 2024 10:19:03 +0200 Subject: [PATCH 267/345] adjust tests to hardened id check --- src/lib/ebus/test/test_message.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/lib/ebus/test/test_message.cpp b/src/lib/ebus/test/test_message.cpp index 323ae23c2..ea5ceb446 100644 --- a/src/lib/ebus/test/test_message.cpp +++ b/src/lib/ebus/test/test_message.cpp @@ -103,15 +103,15 @@ int main() { {"tempsensor,temp;sensor,,Temperatursensor", "", "", "", "template"}, {"tempsensorc,temp;sensorc,,Temperatursensor", "", "", "", "template"}, {"r,cir,Status01,VL/RL/AussenTemp/VLWW/SpeicherTemp/Status,,08,B511,01,,,temp1;temp1;temp2;temp1;temp1;pumpstate", "28.0;24.0;4.938;35.0;41.0;4", "ff08b5110101", "093830f00446520400ff", "d"}, - {"r,message circuit,message name,message comment,,25,B509,0d2800,,,tempsensor", "temp=-14.00 Temperatursensor [Temperatur];sensor=ok [Fühlerstatus]", "ff25b509030d2800", "0320ff00", "D"}, - {"r,message circuit,message name,message comment,,25,B509,0d2800,,,tempsensor,,field unit,field comment", "temp=-14.00 field unit [field comment];sensor=ok [Fühlerstatus]", "ff25b509030d2800", "0320ff00", "D"}, - {"r,message circuit,message name,message comment,,25,B509,0d2800,,,tempsensor,,field unit,field comment", "\n \"temp\": {\"value\": -14.00},\n \"sensor\": {\"value\": \"ok\"}", "ff25b509030d2800", "0320ff00", "j"}, - {"r,message circuit,message name,message comment,,25,B509,0d2800,,,tempsensor,,field unit,field comment", "\n \"temp\": {\"value\": -14.00, \"unit\": \"field unit\", \"comment\": \"field comment\"},\n" " \"sensor\": {\"value\": \"ok\", \"comment\": \"Fühlerstatus\"}", "ff25b509030d2800", "0320ff00", "J"}, - {"r,message circuit,message name,\"message, comment\",,25,B509,0d2800,,,tempsensor,,field unit,\"field, comment\"", "\n \"temp\": {\"value\": -14.00, \"unit\": \"field unit\", \"comment\": \"field, comment\"},\n" " \"sensor\": {\"value\": \"ok\", \"comment\": \"Fühlerstatus\"}", "ff25b509030d2800", "0320ff00", "J"}, - {"r,message circuit,message name,\"message\"\",\"\" comment\",,25,B509,0d2800,,,tempsensor,,field unit,\"field\"\",\"\" comment\"", "\n \"temp\": {\"value\": -14.00, \"unit\": \"field unit\", \"comment\": \"field',' comment\"},\n" " \"sensor\": {\"value\": \"ok\", \"comment\": \"Fühlerstatus\"}", "ff25b509030d2800", "0320ff00", "J"}, - {"r,message circuit,message name,message comment,,25,B509,0d2800,,,temp,,field unit,field comment,,,sensor", "temp=-14.00 field unit [field comment];sensor=ok [Fühlerstatus]", "ff25b509030d2800", "0320ff00", "D"}, - {"r,message circuit,message name,message comment,,25,B509,0d2800,,,temp,,field unit,\"field\"\",\"\" comment\",,,sensor", "temp=-14.00 field unit [field\",\" comment];sensor=ok [Fühlerstatus]", "ff25b509030d2800", "0320ff00", "D"}, - {"r,message circuit,message name,message comment,,25,B509,0d2800,,,D2C,,°C,Temperatur,,,sensor", "\n \"0\": {\"name\": \"\", \"value\": -14.00},\n \"1\": {\"name\": \"sensor\", \"value\": \"ok\"}", "ff25b509030d2800", "0320ff00", "j"}, + {"r,message_circuit,message_name,message comment,,25,B509,0d2800,,,tempsensor", "temp=-14.00 Temperatursensor [Temperatur];sensor=ok [Fühlerstatus]", "ff25b509030d2800", "0320ff00", "D"}, + {"r,message_circuit,message_name,message comment,,25,B509,0d2800,,,tempsensor,,field unit,field comment", "temp=-14.00 field unit [field comment];sensor=ok [Fühlerstatus]", "ff25b509030d2800", "0320ff00", "D"}, + {"r,message_circuit,message_name,message comment,,25,B509,0d2800,,,tempsensor,,field unit,field comment", "\n \"temp\": {\"value\": -14.00},\n \"sensor\": {\"value\": \"ok\"}", "ff25b509030d2800", "0320ff00", "j"}, + {"r,message_circuit,message_name,message comment,,25,B509,0d2800,,,tempsensor,,field unit,field comment", "\n \"temp\": {\"value\": -14.00, \"unit\": \"field unit\", \"comment\": \"field comment\"},\n" " \"sensor\": {\"value\": \"ok\", \"comment\": \"Fühlerstatus\"}", "ff25b509030d2800", "0320ff00", "J"}, + {"r,message_circuit,message_name,\"message, comment\",,25,B509,0d2800,,,tempsensor,,field unit,\"field, comment\"", "\n \"temp\": {\"value\": -14.00, \"unit\": \"field unit\", \"comment\": \"field, comment\"},\n" " \"sensor\": {\"value\": \"ok\", \"comment\": \"Fühlerstatus\"}", "ff25b509030d2800", "0320ff00", "J"}, + {"r,message_circuit,message_name,\"message\"\",\"\" comment\",,25,B509,0d2800,,,tempsensor,,field unit,\"field\"\",\"\" comment\"", "\n \"temp\": {\"value\": -14.00, \"unit\": \"field unit\", \"comment\": \"field',' comment\"},\n" " \"sensor\": {\"value\": \"ok\", \"comment\": \"Fühlerstatus\"}", "ff25b509030d2800", "0320ff00", "J"}, + {"r,message_circuit,message_name,message comment,,25,B509,0d2800,,,temp,,field unit,field comment,,,sensor", "temp=-14.00 field unit [field comment];sensor=ok [Fühlerstatus]", "ff25b509030d2800", "0320ff00", "D"}, + {"r,message_circuit,message_name,message comment,,25,B509,0d2800,,,temp,,field unit,\"field\"\",\"\" comment\",,,sensor", "temp=-14.00 field unit [field\",\" comment];sensor=ok [Fühlerstatus]", "ff25b509030d2800", "0320ff00", "D"}, + {"r,message_circuit,message_name,message comment,,25,B509,0d2800,,,D2C,,°C,Temperatur,,,sensor", "\n \"0\": {\"name\": \"\", \"value\": -14.00},\n \"1\": {\"name\": \"sensor\", \"value\": \"ok\"}", "ff25b509030d2800", "0320ff00", "j"}, {"r,cir,name,,,25,B509,0d2800,,,tempsensorc", "-14.00", "ff25b509030d2800", "0320ff55", ""}, {"r,cir,name,,,25,B509,0d28,,m,sensorc,,,,,,temp", "-14.00", "ff25b509030d2855", "0220ff", ""}, {"u,cir,first,,,fe,0700,,x,,bda", "26.10.2014", "fffe07000426100714", "00", "p"}, From 492418b2b1209dc641e56fe1eb775d25ec2fd6cb Mon Sep 17 00:00:00 2001 From: John Date: Sat, 13 Jul 2024 10:26:13 +0200 Subject: [PATCH 268/345] updated [skip ci] --- ChangeLog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ChangeLog.md b/ChangeLog.md index bb116bdaa..11223c045 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -17,6 +17,7 @@ ## Breaking Changes * change default config path to https://ebus.github.io/ serving files generated from new TypeSpec message definition sources +* change validation of identifiers to no longer accept unusual characters # 23.3 (2023-12-26) From f6402f386a4865d680b53a60bc6f2f151d5b66b1 Mon Sep 17 00:00:00 2001 From: john30 Date: Wed, 11 Sep 2024 07:38:37 +0200 Subject: [PATCH 269/345] add mdns discovery and make it the default --- ChangeLog.md | 2 + README.md | 5 +- src/ebusd/main.cpp | 47 +++- src/ebusd/main.h | 2 +- src/ebusd/main_args.cpp | 9 +- src/lib/ebus/transport.cpp | 2 +- src/lib/knx/knxnet.h | 99 +------ src/lib/utils/tcpsocket.cpp | 520 +++++++++++++++++++++++++++++++----- src/lib/utils/tcpsocket.h | 53 +++- src/tools/ebuspicloader.cpp | 2 +- 10 files changed, 572 insertions(+), 169 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index bb116bdaa..74dca1311 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -14,9 +14,11 @@ * add option to inject start-up commands * add verbose raw data option to "read" and "write" commands * add option to allow write direction in "read" command when inline defining a new message +* add option to discover device via mDNS ## Breaking Changes * change default config path to https://ebus.github.io/ serving files generated from new TypeSpec message definition sources +* change default device connection to be resolved automatically via mDNS # 23.3 (2023-12-26) diff --git a/README.md b/README.md index accbb4c2e..a555939be 100644 --- a/README.md +++ b/README.md @@ -24,10 +24,11 @@ The main features of the daemon are: * TCP * UDP * enhanced ebusd protocol allowing arbitration to be done directly by the hardware, e.g. for recent - * [eBUS Adapter Shield](https://adapter.ebusd.eu/v5/), + * [eBUS Adapter Shields C6](https://adapter.ebusd.eu/v5-c6/) and [v5](https://adapter.ebusd.eu/v5/), * [adapter v3.1](https://adapter.ebusd.eu/v31)/[v3.0](https://adapter.ebusd.eu/v3), or * [ebusd-esp firmware](https://github.com/john30/ebusd-esp/) - * actively send messages to and receive answers from the eBUS + * auto-discover device connection via mDNS + * actively send messages to and receive answers from the eBUS * passively listen to messages sent on the eBUS * answer to messages received from the eBUS * regularly poll for messages diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index e5ee6c301..e85686fd0 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -23,6 +23,7 @@ #include "ebusd/main.h" #include #include +#include #include #include #include @@ -34,6 +35,7 @@ #include "ebusd/network.h" #include "lib/utils/log.h" #include "lib/utils/httpclient.h" +#include "lib/utils/tcpsocket.h" #include "ebusd/scan.h" namespace ebusd { @@ -411,11 +413,52 @@ int main(int argc, char* argv[], char* envp[]) { return overallResult == RESULT_OK ? EXIT_SUCCESS : EXIT_FAILURE; } + const char* device = s_opt.device; + if (!s_opt.checkConfig && strncmp(device, "mdns:", 5) == 0) { + // auto discovery + mdns_oneshot_t address; + #define MAX_ADDRESSES 10 + mdns_oneshot_t addresses[MAX_ADDRESSES]; + size_t otherCount = MAX_ADDRESSES; + logWrite(lf_main, ll_notice, "discovering device from \"%s\"", device); + int ret = 0; + for (int i=0; i < 3 && ret == 0; i++) { // 3*up to 5 seconds = 15 seconds max + ret = resolveMdnsOneShot(device+5, &address, addresses, &otherCount); + } + if (ret < 0) { + logWrite(lf_main, ll_error, "unable to discover device \"%s\", error %d", device, ret); + cleanup(); + return EINVAL; + } + const char *ip; + for (size_t pos=0; pos < otherCount; pos++) { + mdns_oneshot_t *addr = addresses+pos; + ip = inet_ntoa(addr->address); + logWrite(lf_main, ll_info, "discovered another device with ID %s and device string %s:%s", + addr->id, addr->proto, ip); + } + if (ret == 0) { + logWrite(lf_main, ll_error, "unable to discover device \"%s\", %s found", device, otherCount ? "ID not" : "none"); + cleanup(); + return EINVAL; + } + if (ret > 1) { + logWrite(lf_main, ll_notice, + "found several devices from \"%s\", better limit to the desired one using e.g. \"mdns:%s\"", device, + address.id); + } + ip = inet_ntoa(address.address); + char *mdnsDevice = reinterpret_cast(malloc(4*4+3+1)); // ens:xxx.xxx.xxx.xxx + snprintf(mdnsDevice, 4*4+3+1, "%s:%s", address.proto, ip); + device = mdnsDevice; + logWrite(lf_main, ll_notice, "using discovered device with ID %s and device string %s", address.id, device); + } + s_busHandler = new BusHandler(s_messageMap, s_scanHelper, s_opt.pollInterval); // create the protocol and open the device ebus_protocol_config_t config = { - .device = s_opt.device, + .device = device, .noDeviceCheck = s_opt.noDeviceCheck, .readOnly = s_opt.readOnly, .extraLatency = s_opt.extraLatency, @@ -431,7 +474,7 @@ int main(int argc, char* argv[], char* envp[]) { }; s_protocol = ProtocolHandler::create(config, s_busHandler); if (s_protocol == nullptr) { - logWrite(lf_main, ll_error, "unable to create protocol/device %s", s_opt.device); // force logging on exit + logWrite(lf_main, ll_error, "unable to create protocol/device %s", config.device); // force logging on exit cleanup(); return EINVAL; } diff --git a/src/ebusd/main.h b/src/ebusd/main.h index 7bf4dbb87..9ab60efdb 100755 --- a/src/ebusd/main.h +++ b/src/ebusd/main.h @@ -40,7 +40,7 @@ namespace ebusd { /** A structure holding all program options. */ typedef struct options { - const char* device; //!< eBUS device (serial device or [udp:]ip[:port]) [/dev/ttyUSB0] + const char* device; //!< eBUS device (serial device or mdns:[id] or [udp:]ip[:port]) [mdns:] bool noDeviceCheck; //!< skip serial eBUS device test bool readOnly; //!< read-only access to the device bool initialSend; //!< send an initial escape symbol after connecting device diff --git a/src/ebusd/main_args.cpp b/src/ebusd/main_args.cpp index b3b56724d..2afce0f7d 100755 --- a/src/ebusd/main_args.cpp +++ b/src/ebusd/main_args.cpp @@ -31,7 +31,7 @@ namespace ebusd { /** the default program options. */ static const options_t s_default_opt = { - .device = "/dev/ttyUSB0", + .device = "mdns:", .noDeviceCheck = false, .readOnly = false, .initialSend = false, @@ -134,11 +134,14 @@ static string s_configPath = CONFIG_PATH; static const argDef argDefs[] = { {nullptr, 0, nullptr, 0, "Device options:"}, {"device", 'd', "DEV", 0, "Use DEV as eBUS device (" + "\"mdns:\" for auto discovery via mDNS (" + "optional suffix with specific HW ID as well as specific IP interface after '@', " + "otherwise: " "prefix \"ens:\" for enhanced high speed device or " "\"enh:\" for enhanced device, with " "\"IP[:PORT]\" for network device or " "\"DEVICE\" for serial device" - ") [/dev/ttyUSB0]"}, + ") [mdns:]"}, {"nodevicecheck", 'n', nullptr, 0, "Skip serial eBUS device test"}, {"readonly", 'r', nullptr, 0, "Only read from device, never write to it"}, {"initsend", O_INISND, nullptr, 0, "Send an initial escape symbol after connecting device"}, @@ -232,7 +235,7 @@ static int parse_opt(int key, char *arg, const argParseOpt *parseOpt, struct opt switch (key) { // Device options: - case 'd': // --device=/dev/ttyUSB0 + case 'd': // --device=mdns: if (arg == nullptr || arg[0] == 0) { argParseError(parseOpt, "invalid device"); return EINVAL; diff --git a/src/lib/ebus/transport.cpp b/src/lib/ebus/transport.cpp index ecb29a5bd..e95221627 100644 --- a/src/lib/ebus/transport.cpp +++ b/src/lib/ebus/transport.cpp @@ -325,7 +325,7 @@ void SerialTransport::checkDevice() { } result_t NetworkTransport::openInternal() { - m_fd = socketConnect(m_hostOrIp, m_port, m_udp, nullptr, 5, 2); // wait up to 5 seconds for established connection + m_fd = socketConnect(m_hostOrIp, m_port, m_udp ? IPPROTO_UDP : 0, nullptr, 5, 2); // wait up to 5 seconds for established connection if (m_fd < 0) { return RESULT_ERR_GENERIC_IO; } diff --git a/src/lib/knx/knxnet.h b/src/lib/knx/knxnet.h index aefe77c1c..5041e59be 100644 --- a/src/lib/knx/knxnet.h +++ b/src/lib/knx/knxnet.h @@ -40,6 +40,7 @@ #include #include #include "lib/knx/knx.h" +#include "lib/utils/tcpsocket.h" namespace ebusd { @@ -228,7 +229,7 @@ typedef struct __attribute__ ((packed)) { #define SYSTEM_MULTICAST_PORT 3671 // the default system multicast address 224.0.23.12 -#define SYSTEM_MULTICAST_IP 0xe000170c +#define SYSTEM_MULTICAST_IP_STR "224.0.23.12" #define LAST_FRAME_TIMEOUT 2 @@ -400,96 +401,17 @@ class KnxNetConnection : public KnxConnection { // @copydoc const char* open() override { close(); - int ret; - struct in_addr mcast = {}; - mcast.s_addr = htonl(SYSTEM_MULTICAST_IP); - m_interface.s_addr = INADDR_ANY; - m_port = SYSTEM_MULTICAST_PORT; - if (m_url && m_url[0]) { // non-empty - string urlStr = m_url; // "[mcast][@intf]" for non-default 224.0.23.12:3671) - if (!urlStr.empty()) { - auto pos = urlStr.find('@'); - if (pos != string::npos) { - string intfStr = urlStr.substr(pos+1); - const char* intfCstr = intfStr.c_str(); - ret = inet_aton(intfCstr, &m_interface); - if (ret == 0) { - return "intf addr"; - } - urlStr = urlStr.substr(0, pos); - } - } - if (!urlStr.empty()) { - const char *mcastStr = urlStr.c_str(); - ret = inet_aton(mcastStr, &mcast); - if (ret == 0) { - return "multicast addr"; - } - } - } - - sockaddr_in address = {}; - address.sin_family = AF_INET; - address.sin_port = htons(m_port); - address.sin_addr.s_addr = INADDR_ANY; - - int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + int fd = socketConnect(m_url && m_url[0] ? m_url : SYSTEM_MULTICAST_IP_STR, + SYSTEM_MULTICAST_PORT, IPPROTO_UDP, nullptr, 0x02); if (fd < 0) { return "create socket"; } - // set non-blocking - ret = fcntl(fd, F_SETFL, O_NONBLOCK); - if (ret != 0) { + if (fcntl(fd, F_SETFL, O_NONBLOCK) != 0) { ::close(fd); return "non-blocking"; } - // set reuse address option - int optint = 1; - ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optint, sizeof(optint)); - if (ret != 0) { - ::close(fd); - return "reuse"; - } - - // allow multiple processes using the same port for multicast on the same host - unsigned char optchar = 1; - ret = setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &optchar, sizeof(optchar)); - if (ret != 0) { - ::close(fd); - return "mcast loop"; - } - - if (m_interface.s_addr != INADDR_ANY) { - // set outgoing interface to other than default (determined by routing table) - ret = setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &m_interface, sizeof(m_interface)); - if (ret != 0) { - ::close(fd); - return "mcast intf"; - } - } - - // bind for incoming multicast - ret = bind(fd, (struct sockaddr*) &address, sizeof(address)); - if (ret != 0) { - ::close(fd); - return "bind socket"; - } - - // set the target address for later use by sendto() - m_multicast = address; - m_multicast.sin_addr = mcast; - - // join the multicast inbound - ip_mreq req = {}; - req.imr_multiaddr = mcast; - req.imr_interface = m_interface; - if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &req, sizeof(req)) < 0) { - ::close(fd); - return "join multicast"; - } - m_sock = fd; return nullptr; } @@ -674,7 +596,7 @@ class KnxNetConnection : public KnxConnection { } d[0] = tpci; logTelegram(true, c, l, d); - ssize_t sent = sendto(m_sock, buf, totalLen, MSG_NOSIGNAL, (sockaddr*)&m_multicast, sizeof(m_multicast)); + ssize_t sent = ::send(m_sock, buf, totalLen, MSG_NOSIGNAL); if (sent < 0) { return "send error"; } @@ -711,15 +633,6 @@ class KnxNetConnection : public KnxConnection { /** the URL to connect to. */ const char* m_url; - /** the multicast address to join. */ - struct sockaddr_in m_multicast; - - /** the port to listen to. */ - in_port_t m_port; - - /** the optional interface address to bind to. */ - struct in_addr m_interface; - /** the socket if connected, or 0. */ int m_sock; diff --git a/src/lib/utils/tcpsocket.cpp b/src/lib/utils/tcpsocket.cpp index 19ef1a4a3..2114b07b4 100755 --- a/src/lib/utils/tcpsocket.cpp +++ b/src/lib/utils/tcpsocket.cpp @@ -46,38 +46,91 @@ bool TCPSocket::isValid() { return fcntl(m_sfd, F_GETFL) != -1; } +bool parseIp(const char* server, struct in_addr *sin_addr) { + if (inet_aton(server, sin_addr) == 1) { + return true; + } + struct hostent* he = gethostbyname(server); + if (he == nullptr) { + return false; + } + memcpy(sin_addr, he->h_addr_list[0], he->h_length); + return true; +} -int socketConnect(const char* server, uint16_t port, bool udp, socketaddress* storeAddress, int tcpConnectTimeout, -int tcpKeepAliveInterval) { +int socketConnect(const char* server, uint16_t port, int udpProto, socketaddress* storeAddress, +int tcpConnToUdpOptions, int tcpKeepAliveInterval, struct in_addr* storeIntf) { socketaddress localAddress; socketaddress* address = storeAddress ? storeAddress : &localAddress; memset(reinterpret_cast(address), 0, sizeof(*address)); - if (inet_aton(server, &address->sin_addr) == 0) { - struct hostent* he = gethostbyname(server); - if (he == nullptr) { + // parse "address[@intf]" + const char* pos = strchr(server, '@'); + struct in_addr intf; + intf.s_addr = INADDR_ANY; + if (pos) { + char* str = strdupa(server); + char* ifa = strchr(str, '@'); + ifa[0] = 0; + ifa++; + if (!str[0] || !parseIp(str, &address->sin_addr)) { return -1; } - memcpy(&address->sin_addr, he->h_addr_list[0], he->h_length); + if (!parseIp(ifa, &intf)) { + return -1; + } + } else if (!parseIp(server, &address->sin_addr)) { + return -1; + } + if (storeIntf) { + *storeIntf = intf; } address->sin_family = AF_INET; address->sin_port = (in_port_t)htons(port); - int sfd = socket(AF_INET, udp ? SOCK_DGRAM : SOCK_STREAM, 0); + int sfd = socket(AF_INET, udpProto ? SOCK_DGRAM : SOCK_STREAM, udpProto); if (sfd < 0) { - return -1; + return -2; } - int ret; - if (udp) { + int ret = 0; + if (udpProto) { + #define RET(chk, next) if (ret >= 0) { ret = chk; if (ret < 0) ret = next;} struct sockaddr_in bindAddress = *address; - bindAddress.sin_addr.s_addr = INADDR_ANY; - ret = bind(sfd, (struct sockaddr*)&bindAddress, sizeof(bindAddress)); - if (ret >= 0) { - ret = ::connect(sfd, (struct sockaddr*)address, sizeof(*address)); + // allow multiple processes using the same port for multicast on the same host + int optint = 1; + RET(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &optint, sizeof(optint)), -3); +#ifdef SO_REUSEPORT + RET(setsockopt(sfd, SOL_SOCKET, SO_REUSEPORT, &optint, sizeof(optint)), -3); +#endif + bool isMcast = IN_MULTICAST(ntohl(address->sin_addr.s_addr)); + if (isMcast) { + // loop-back sent multicast packets + unsigned char optchar = 1; + RET(setsockopt(sfd, IPPROTO_IP, IP_MULTICAST_LOOP, &optchar, sizeof(optchar)), -3); + if (ret >= 0) { + // join the multicast inbound + ip_mreq req = {}; + req.imr_multiaddr = address->sin_addr; + req.imr_interface = intf; + RET(setsockopt(sfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &req, sizeof(req)), -7); + } + if (ret >= 0 && intf.s_addr != INADDR_ANY) { + // set outgoing interface to other than default (determined by routing table) + RET(setsockopt(sfd, IPPROTO_IP, IP_MULTICAST_IF, &intf, sizeof(intf)), -3); + } } - if (ret < 0) { - close(sfd); - return -1; + bindAddress.sin_addr = intf; + if (!(tcpConnToUdpOptions&0x01)) { + bindAddress.sin_port = 0; // do not bind to same source port for outgoing packets + } + RET(bind(sfd, (struct sockaddr*)&bindAddress, sizeof(bindAddress)), -4); + if (tcpConnToUdpOptions&0x02) { + // set the default target address for later use by send() + RET(::connect(sfd, (struct sockaddr*)address, sizeof(*address)), -5); + if (ret < 0) { + close(sfd); + return ret; + } } return sfd; } @@ -85,7 +138,7 @@ int tcpKeepAliveInterval) { ret = setsockopt(sfd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&value), sizeof(value)); if (ret < 0) { close(sfd); - return -1; + return -3; } if (tcpKeepAliveInterval > 0) { value = 1; @@ -124,64 +177,78 @@ int tcpKeepAliveInterval) { } #endif } - if (tcpConnectTimeout > 0 && fcntl(sfd, F_SETFL, O_NONBLOCK) < 0) { // set non-blocking + if (tcpConnToUdpOptions > 0 && fcntl(sfd, F_SETFL, O_NONBLOCK) < 0) { // set non-blocking close(sfd); - return -1; + return -4; } ret = ::connect(sfd, (struct sockaddr*)address, sizeof(*address)); if (ret != 0) { - if (ret < 0 && (tcpConnectTimeout <= 0 || errno != EINPROGRESS)) { + if (ret < 0 && (tcpConnToUdpOptions <= 0 || errno != EINPROGRESS)) { close(sfd); - return -1; + return -5; + } + if (tcpConnToUdpOptions > 0) { + ret = socketPoll(sfd, POLLIN|POLLOUT, tcpConnToUdpOptions); + if (ret <= 0) { + close(sfd); + return -6; + } + if (fcntl(sfd, F_SETFL, 0) < 0) { // set blocking again + close(sfd); + return -4; + } } - if (tcpConnectTimeout > 0) { + } + return sfd; +} + +int socketPoll(int sfd, int which, int timeoutSeconds) { + int ret; #if defined(HAVE_PPOLL) || defined(HAVE_PSELECT) - struct timespec tdiff; - tdiff.tv_sec = tcpConnectTimeout; - tdiff.tv_nsec = 0; + struct timespec tdiff; + tdiff.tv_sec = timeoutSeconds; + tdiff.tv_nsec = 0; #else - struct timeval tdiff; - tdiff.tv_sec = tcpConnectTimeout; - tdiff.tv_usec = 0; + struct timeval tdiff; + tdiff.tv_sec = timeoutSeconds; + tdiff.tv_usec = 0; #endif #ifdef HAVE_PPOLL - nfds_t nfds = 1; - struct pollfd fds[nfds]; - memset(fds, 0, sizeof(fds)); - fds[0].fd = sfd; - fds[0].events = POLLIN|POLLOUT; - ret = ppoll(fds, nfds, &tdiff, nullptr); - if (ret == 1 && fds[0].revents & POLLERR) { - ret = -1; - } + nfds_t nfds = 1; + struct pollfd fds[nfds]; + memset(fds, 0, sizeof(fds)); + fds[0].fd = sfd; + fds[0].events = which; + ret = ppoll(fds, nfds, &tdiff, nullptr); + if (ret >= 1 && fds[0].revents & POLLERR) { + ret = -1; + } else if (ret >= 1) { + ret = fds[0].revents; + } #else - fd_set readfds, writefds, exceptfds; - FD_ZERO(&readfds); - FD_ZERO(&writefds); - FD_ZERO(&exceptfds); - FD_SET(sfd, &readfds); - FD_SET(sfd, &writefds); - FD_SET(sfd, &exceptfds); + fd_set readfds, writefds, exceptfds; + FD_ZERO(&readfds); + FD_ZERO(&writefds); + FD_ZERO(&exceptfds); + if (which & POLLIN) { + FD_SET(sfd, &readfds); + } + if (which & POLLOUT) { + FD_SET(sfd, &writefds); + } + FD_SET(sfd, &exceptfds); #ifdef HAVE_PSELECT - ret = pselect(sfd + 1, &readfds, &writefds, &exceptfds, &tdiff, nullptr); + ret = pselect(sfd + 1, &readfds, &writefds, &exceptfds, &tdiff, nullptr); #else - ret = select(sfd + 1, &readfds, &writefds, &exceptfds, &tdiff); -#endif - if (ret >= 1 && FD_ISSET(sfd, &exceptfds)) { - ret = -1; - } + ret = select(sfd + 1, &readfds, &writefds, &exceptfds, &tdiff); #endif - if (ret == -1 || ret == 0) { - close(sfd); - return -1; - } - if (fcntl(sfd, F_SETFL, 0) < 0) { // set blocking again - close(sfd); - return -1; - } - } + if (ret >= 1 && FD_ISSET(sfd, &exceptfds)) { + ret = -1; + } else if (ret >= 1) { + ret = (FD_ISSET(sfd, &readfds) ? POLLIN : 0) | (FD_ISSET(sfd, &writefds) ? POLLOUT : 0); } - return sfd; +#endif + return ret; } @@ -243,4 +310,335 @@ TCPSocket* TCPServer::newSocket() { return new TCPSocket(sfd, &address); } +size_t readNameRecursive(uint8_t *data, size_t len, size_t pos, size_t maxPos, int maxDepth, char* str, size_t slen, + size_t* spos) { + size_t nlen = data[pos++]; + if ((nlen&0xc0) == 0xc0) { + // pointer + size_t p = ((nlen&0x3f) << 8) | data[pos]; + if (p >= len || maxDepth < 1) { + return 0; + } + readNameRecursive(data, len, p, len, maxDepth-1, str, slen, spos); + return 2; + } + if (!nlen) { + return 1; + } + if (pos+nlen > maxPos || *spos+1+nlen > slen) { + return 0; + } + if (*spos > 0) { + str[*spos] = '.'; + *spos += 1; + } + memcpy(str+*spos, data+pos, nlen); + *spos += nlen; + pos += nlen; + size_t add; + if (pos >= maxPos || maxDepth < 1) { + add = 0; + } else { + add = readNameRecursive(data, len, pos, maxPos, maxDepth-1, str, slen, spos); + if (add == 0) { + return 0; + } + } + return 1+nlen+add; +} + +size_t readName(uint8_t *data, size_t len, size_t pos, size_t maxPos, char* str, size_t slen, size_t* spos) { + return readNameRecursive(data, len, pos, maxPos, 4, str, slen, spos); +} + +typedef struct __attribute__ ((packed)) { + uint16_t id; + struct { +#if __BYTE_ORDER == __BIG_ENDIAN + bool qr: 1; // 0=query, 1=answer + uint8_t opcode: 4; // 0=standard query, 1=inverse query, 2=status request + bool aa: 1; // authoritive answer + bool tc: 1; // truncation + bool rd: 1; // recursion desired +#else + bool rd: 1; // recursion desired + bool tc: 1; // truncation + bool aa: 1; // authoritive answer + uint8_t opcode: 4; // 0=standard query, 1=inverse query, 2=status request + bool qr: 1; // 0=query, 1=answer +#endif + }; + struct { +#if __BYTE_ORDER == __BIG_ENDIAN + bool ra: 1; // recursion available + uint8_t z: 3; // zero + uint8_t rcode: 4; // response code: 0=OK +#else + uint8_t rcode: 4; // response code: 0=OK + uint8_t z: 3; // zero + bool ra: 1; // recursion available +#endif + }; + uint16_t qdCount; // question section entry count + uint16_t anCount; // answer section entry count + uint16_t nsCount; // name server section entry count + uint16_t arCount; // additional records section entry count +} dns_query_t; + +typedef struct __attribute__ ((packed)) { + uint8_t len; + // unsigned char *name; +} dns_qname_t; + +typedef struct __attribute__ ((packed)) { + dns_qname_t qname; + uint16_t qtype; + uint16_t qclass; // top bit used for unicast-response +} dns_question_t; + +#define DNS_TYPE_A 0x01 +#define DNS_TYPE_PTR 0x0c +#define DNS_TYPE_TXT 0x10 +#define DNS_TYPE_SRV 0x21 +#define DNS_CLASS_AA 0x01 + +typedef struct __attribute__ ((packed)) { + dns_qname_t aname; + uint16_t atype; + uint16_t aclass; + uint32_t ttl; + uint16_t rdLength; + // uint8_t *rData; +} dns_answer_t; + +typedef struct __attribute__ ((packed)) { + uint16_t priority; + uint16_t weight; + uint16_t port; + dns_qname_t target; +} dns_rr_srv_t; + +int resolveMdnsOneShot(const char* url, mdns_oneshot_t *result, mdns_oneshot_t *moreResults, size_t *moreCount) { + memset(result, 0, sizeof(mdns_oneshot_t)); + socketaddress address; + const char* pos = strchr(url, '@'); + string limitId = string(url); + string device = "224.0.0.251"; + if (pos) { + limitId = limitId.substr(0, pos-url); + device += string(pos); + } + int sock = socketConnect(device.c_str(), 5353, IPPROTO_UDP, &address); + if (sock < 0) { + return -1; + } + + uint8_t record[1500]; + memset(record, 0, sizeof(record)); + dns_query_t *dnsr = reinterpret_cast(record); + dnsr->qdCount = htons(1); + size_t len = sizeof(dns_query_t); + dns_question_t *q = reinterpret_cast(reinterpret_cast(dnsr)+len); + const uint8_t serviceName[] = { + 0x06, 0x5f, 0x65, 0x62, 0x75, 0x73, 0x64, // _ebusd + 0x04, 0x5f, 0x74, 0x63, 0x70, // _tcp + 0x05, 0x6c, 0x6f, 0x63, 0x61, 0x6c, // local + 0x00 + }; + memcpy(&q->qname.len, serviceName, sizeof(serviceName)); + len += sizeof(serviceName)-1; // -1 for final empty qname + q = reinterpret_cast(reinterpret_cast(dnsr)+len); + q->qtype = htons(DNS_TYPE_PTR); + q->qclass = htons( + 0x8000 | // unicast response bit + DNS_CLASS_AA); + len += sizeof(dns_question_t); + ssize_t done = sendto(sock, record, len, 0, reinterpret_cast(&address), sizeof(address)); +#ifdef DEBUG_MDNS + printf("mdns: sent %ld, err %d\n", done, errno); +#endif + fcntl(sock, F_SETFL, O_NONBLOCK); + bool found = false, foundMore = false; + size_t moreRemain = moreResults && moreCount && *moreCount > 0 ? *moreCount : 0; + if (moreRemain > 0) { + *moreCount = 0; + } +#ifdef DEBUG_MDNS + socketaddress aaddr; + socklen_t aaddrlen = 0; +#endif + for (int i=0; i < (found ? 3 : 5); i++) { // up to 5 seconds, at least 3 seconds + int ret = socketPoll(sock, POLLIN, 1); + done = 0; + if (ret > 0 && (ret&POLLIN)) { +#ifdef DEBUG_MDNS + aaddrlen = sizeof(aaddr); + done = recvfrom(sock, record, sizeof(record), 0, reinterpret_cast(&aaddr), &aaddrlen); +#else + done = recv(sock, record, sizeof(record), 0); +#endif + } + if (done == 0 || (done < 0 && errno == EAGAIN) || done < sizeof(dns_query_t)) { + continue; + } + dnsr = reinterpret_cast(record); + // todo length check +#ifdef DEBUG_MDNS + printf("mdns: got %d from %2.2x:%d, q=%d, an=%d, ns=%d, ar=%d\n", done, aaddr.sin_addr.s_addr, + ntohs(aaddr.sin_port), ntohs(dnsr->qdCount), ntohs(dnsr->anCount), ntohs(dnsr->nsCount), + ntohs(dnsr->arCount)); +#endif + if (dnsr->qdCount || done < sizeof(dns_query_t)+sizeof(serviceName)+4*sizeof(dns_answer_t)+(26+2)+4+1+1+ + sizeof(dns_rr_srv_t)+(2+1+sizeof(mdns_oneshot_t::id)-1+1+5+1+sizeof(mdns_oneshot_t::proto)-1)+4 + // "eBUS Adapter Shield xxxxxx", "id=xxxxxxxxxxxx.proto=ens" + ) { + continue; + } + int anCnt = ntohs(dnsr->anCount); + int arCnt = ntohs(dnsr->arCount); + if (anCnt < 1 || dnsr->nsCount || arCnt < 1) { + continue; + } + len = sizeof(dns_query_t); + char name[256]; + bool validPort = false; + struct in_addr validAddress; + validAddress.s_addr = INADDR_ANY; + char id[sizeof(mdns_oneshot_t::id)] = {0}; + char proto[sizeof(mdns_oneshot_t::proto)] = {0}; + for (int i=0; i < anCnt+arCnt && len < done; i++) { + dns_answer_t *a = reinterpret_cast(reinterpret_cast(dnsr)+len); + if (i == 0) { + if (memcmp(&a->aname.len, serviceName, sizeof(serviceName)) != 0) { +#ifdef DEBUG_MDNS + printf("mdns: an 0 mismatch\n"); +#endif + anCnt = 0; + break; // skip this one + } +#ifdef DEBUG_MDNS + printf("mdns: an 0 match\n"); +#endif + len += sizeof(serviceName)-1; // -1 for final empty qname + } else { + // read name + size_t pos = 0; + size_t nlen = readName(record, done, len, done, name, sizeof(name), &pos); + if (nlen == 0) { + anCnt = 0; + break; // skip this one + } + len += nlen-1; // -1 for final empty qname / right pointer for below + name[pos] = 0; +#ifdef DEBUG_MDNS + printf("mdns: a%c %d name=%s\n", i >= anCnt ? 'r' : 'n', i >= anCnt ? i-anCnt : i, name); +#endif + } + a = reinterpret_cast(reinterpret_cast(dnsr)+len); + int atype = ntohs(a->atype); + int aclass = ntohs(a->aclass); +#ifdef DEBUG_MDNS + printf(" atype %d, aclass %d\n", atype, aclass); +#endif + if (i == 0 && (atype != DNS_TYPE_PTR + || aclass != DNS_CLASS_AA)) { + anCnt = 0; + break; // skip this one + } + len += sizeof(dns_answer_t); + int rdLen = ntohs(a->rdLength); +#ifdef DEBUG_MDNS + printf(" rd %d @%2.2x = ", rdLen, len); + for (int i=0; i < rdLen && len+i < done; i++) { + printf("%2.2x ", reinterpret_cast(dnsr)[len+i]); + } + printf("\n"); +#endif + if (atype == DNS_TYPE_PTR || atype == DNS_TYPE_TXT) { + size_t pos = 0; + if (readName(record, done, len, len+rdLen, name, sizeof(name), &pos) == 0) { + anCnt = 0; + break; // skip this one + } + name[pos] = 0; +#ifdef DEBUG_MDNS + printf(" %s=%s\n", (atype == DNS_TYPE_TXT) ? "txt" : "ptr", name); +#endif + if (atype == DNS_TYPE_TXT && name[0]) { + char* sep = strchr(name, '='); + char* sep2; + if (sep && strncmp(name, "id", sep-name) == 0) { + sep2 = strchr(name, '.'); + if (sep2-sep-1 == sizeof(mdns_oneshot_t::id)-1) { + memcpy(id, sep+1, sizeof(mdns_oneshot_t::id)-1); + } else { + sep = nullptr; + } + sep = sep ? strchr(sep2+1, '=') : nullptr; + } + if (sep && strncmp(sep2+1, "proto", sep-sep2-1) == 0 && ( + pos == sep+1+sizeof(mdns_oneshot_t::proto)-1-name + || strchr(sep+1, '.') == sep+1+sizeof(mdns_oneshot_t::proto)-1)) { + memcpy(proto, sep+1, sizeof(mdns_oneshot_t::proto)-1); + } + } + } else if (atype == DNS_TYPE_SRV && rdLen >= sizeof(dns_rr_srv_t)) { + dns_rr_srv_t *srv = reinterpret_cast(record+len); + size_t pos = 0; + if (readName(record, done, len+sizeof(dns_rr_srv_t)-1, len+rdLen, name, sizeof(name), &pos) == 0) { + anCnt = 0; + break; // skip this one + } + name[pos] = 0; + validPort = ntohs(srv->port) == 9999; +#ifdef DEBUG_MDNS + printf(" srv port %d target %s\n", ntohs(srv->port), name); +#endif + } else if (atype == DNS_TYPE_A) { + // ipv4 address +#ifdef DEBUG_MDNS + printf(" address %d.%d.%d.%d\n", record[len], record[len+1], record[len+2], record[len+3]); +#endif + memcpy(reinterpret_cast(&validAddress.s_addr), record+len, 4); + } + len += rdLen; + } + if (!anCnt) { + continue; + } + if (validPort && validAddress.s_addr != INADDR_ANY && validAddress.s_addr != INADDR_NONE && proto[0]) { + mdns_oneshot_t *storeTo; + if (!found && (!limitId.length() || limitId.compare(id) == 0)) { + storeTo = result; + found = true; + if (limitId.length()) { + break; // found the desired one, no need to wait for another + } + } else if (found && strcmp(id, result->id) == 0) { + // skip duplicate answer + continue; + } else { + foundMore = !limitId.length(); + if (moreRemain > 0) { + storeTo = moreResults++; + moreRemain--; + (*moreCount)++; + } else if (!found) { + continue; + } else { + break; + } + } + storeTo->address = validAddress; + strncpy(storeTo->id, id, sizeof(mdns_oneshot_t::id)); + strncpy(storeTo->proto, proto, sizeof(mdns_oneshot_t::proto)); + if (found && moreRemain == 0) { + break; + } + } + } + close(sock); + return found ? foundMore ? 2 : 1 : 0; +} + } // namespace ebusd diff --git a/src/lib/utils/tcpsocket.h b/src/lib/utils/tcpsocket.h index 429b487cf..139ad1962 100755 --- a/src/lib/utils/tcpsocket.h +++ b/src/lib/utils/tcpsocket.h @@ -25,6 +25,11 @@ #include #include #include +#ifdef __FreeBSD__ + #include +#else + #include +#endif /** typedef for referencing @a sockaddr_in within namespace. */ typedef struct sockaddr_in socketaddress; @@ -43,16 +48,29 @@ using std::string; /** * Connect a TCP or UDP socket. - * @param server the server name or ip address to connect to. + * @param server the server name or ip address to connect to, optionally + * followed by "@intf" to bind to a certain interface address. * @param port the port number. - * @param udp true for UDP, false for TCP. + * @param udpProto the protocol to use for UDP (e.g. IPPROTO_UDP), or 0 for TCP. * @param storeAddress optional pointer to where the socket address will be stored. - * @param tcpConnectTimeout the TCP connect timeout in seconds, or 0. + * @param tcpConnectTimeoutUdpOptions the connect timeout in seconds for TCP (or 0), + * or a bit set of options for UDP (0x01 for binding to the same source port, + * 0x02 for connecting to the target address). * @param tcpKeepAliveInterval optional interval in seconds for sending TCP keepalive. + * @param storeIntf optional pointer to where the interface address will be stored. * @return the connected socket file descriptor on success, or -1 on error. */ -int socketConnect(const char* server, uint16_t port, bool udp, socketaddress* storeAddress = nullptr, -int tcpConnectTimeout = 0, int tcpKeepAliveInterval = 0); +int socketConnect(const char* server, uint16_t port, int udpProto, socketaddress* storeAddress = nullptr, +int tcpConnectTimeoutUdpOptions = 0, int tcpKeepAliveInterval = 0, struct in_addr* storeIntf = nullptr); + +/** + * Poll a socket. + * @param sfd the socket file descriptor. + * @param which the set of bits of the event(s) to wait for (e.g. POLLIN and/or POLLOUT). + * @param timeoutSeconds the poll timeout in seconds. + * @return a set of bits indicating the received event (e.g. POLLIN and/or POLLOUT), or -1 on error. + */ +int socketPoll(int sfd, int which, int timeoutSeconds); /** @@ -198,6 +216,31 @@ class TCPServer { bool m_listening; }; +/** + * Structure for resolving device address via mDNS one-shot query. + */ +typedef struct __attribute__ ((packed)) { + /** the device IP address. */ + struct in_addr address; + /** the device ID. */ + char id[6*2+1]; + /** the announced ebusd protocol. */ + char proto[3+1]; +} mdns_oneshot_t; + +/** + * Use an mDNS one-shot query to resolve an eBUS device. + * @param url the desired ID (or empty) followed by an optional host interface IP to use after an '@' sign. + * @param result pointer to an mdns_oneshot_t structure to store the result in. + * @param moreRequests optional pointer to further results not matching the desired ID. + * @param moreCount optional pointer to the size of the moreRequests argument that will be updated with the number of + * further results found upon success. + * @return 1 on success, 2 when another device was found, 0 when no device was found or no found device matched the + * desired ID, or less than 0 on error. + */ +int resolveMdnsOneShot(const char* url, mdns_oneshot_t *result, + mdns_oneshot_t *moreResults = nullptr, size_t *moreCount = nullptr); + } // namespace ebusd #endif // LIB_UTILS_TCPSOCKET_H_ diff --git a/src/tools/ebuspicloader.cpp b/src/tools/ebuspicloader.cpp index 6ebb03629..7822d8110 100644 --- a/src/tools/ebuspicloader.cpp +++ b/src/tools/ebuspicloader.cpp @@ -807,7 +807,7 @@ int openSerial(std::string port) { int openNet(std::string host, uint16_t port) { // open network port - int fd = socketConnect(host.c_str(), port, false, nullptr, 5); + int fd = socketConnect(host.c_str(), port, 0, nullptr, 5); if (fd < 0) { std::cerr << "unable to open " << host << std::endl; return -1; From 59edf1a21fa5167cdc3ce67a31c27bc7a95595c8 Mon Sep 17 00:00:00 2001 From: john30 Date: Thu, 12 Sep 2024 07:24:34 +0200 Subject: [PATCH 270/345] fix early break --- src/lib/utils/tcpsocket.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/lib/utils/tcpsocket.cpp b/src/lib/utils/tcpsocket.cpp index 2114b07b4..bafea0743 100755 --- a/src/lib/utils/tcpsocket.cpp +++ b/src/lib/utils/tcpsocket.cpp @@ -611,9 +611,6 @@ int resolveMdnsOneShot(const char* url, mdns_oneshot_t *result, mdns_oneshot_t * if (!found && (!limitId.length() || limitId.compare(id) == 0)) { storeTo = result; found = true; - if (limitId.length()) { - break; // found the desired one, no need to wait for another - } } else if (found && strcmp(id, result->id) == 0) { // skip duplicate answer continue; @@ -632,8 +629,8 @@ int resolveMdnsOneShot(const char* url, mdns_oneshot_t *result, mdns_oneshot_t * storeTo->address = validAddress; strncpy(storeTo->id, id, sizeof(mdns_oneshot_t::id)); strncpy(storeTo->proto, proto, sizeof(mdns_oneshot_t::proto)); - if (found && moreRemain == 0) { - break; + if (found && (limitId.length() || !moreRemain)) { + break; // found the desired one or no more space left for others } } } From 981b26751e93930dffd858ff3a7b147a07c7de27 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 1 Oct 2024 06:04:12 +0200 Subject: [PATCH 271/345] better helpt text --- src/ebusd/main_args.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/ebusd/main_args.cpp b/src/ebusd/main_args.cpp index 2afce0f7d..f71498b5d 100755 --- a/src/ebusd/main_args.cpp +++ b/src/ebusd/main_args.cpp @@ -133,15 +133,14 @@ static string s_configPath = CONFIG_PATH; /** the definition of the known program arguments. */ static const argDef argDefs[] = { {nullptr, 0, nullptr, 0, "Device options:"}, - {"device", 'd', "DEV", 0, "Use DEV as eBUS device (" - "\"mdns:\" for auto discovery via mDNS (" - "optional suffix with specific HW ID as well as specific IP interface after '@', " - "otherwise: " - "prefix \"ens:\" for enhanced high speed device or " - "\"enh:\" for enhanced device, with " - "\"IP[:PORT]\" for network device or " - "\"DEVICE\" for serial device" - ") [mdns:]"}, + {"device", 'd', "DEV", 0, "Use DEV as eBUS device [mdns:]\n" + "- \"mdns:\" for auto discovery via mDNS with optional suffix \"[ID][@INTF]\" for using a specific" + " hardware ID and/or IP interface INTF for the discovery (only for eBUS Adapter Shield), or\n" + "- prefix \"ens:\" for enhanced high speed device,\n" + "- prefix \"enh:\" for enhanced device, or\n" + "- no prefix for plain device, and\n" + "- suffix \"IP[:PORT]\" for network device, or\n" + "- suffix \"DEVICE\" for serial device"}, {"nodevicecheck", 'n', nullptr, 0, "Skip serial eBUS device test"}, {"readonly", 'r', nullptr, 0, "Only read from device, never write to it"}, {"initsend", O_INISND, nullptr, 0, "Send an initial escape symbol after connecting device"}, From 441a3f64574907ae490a3900ec053ffa8f759826 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 1 Oct 2024 06:08:51 +0200 Subject: [PATCH 272/345] include passive with new def in read+write --- src/ebusd/mainloop.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index 2c55af91e..7a3635dbd 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -849,7 +849,7 @@ result_t MainLoop::executeRead(const vector& args, const string& levels, return RESULT_OK; } deque messages; - m_newlyDefinedMessages->findAll("", "", levels, false, true, writeDirection, false, true, false, 0, 0, false, &messages); + m_newlyDefinedMessages->findAll("", "", levels, false, true, writeDirection, writeDirection, true, false, 0, 0, false, &messages); if (messages.empty()) { *ostream << "ERR: bad definition: no read" << (writeDirection?"/write":"") << " message"; return RESULT_OK; @@ -1086,7 +1086,7 @@ result_t MainLoop::executeWrite(const vector& args, const string levels, return RESULT_OK; } deque messages; - m_newlyDefinedMessages->findAll("", "", levels, false, false, true, false, true, false, 0, 0, false, &messages); + m_newlyDefinedMessages->findAll("", "", levels, false, false, true, true, true, false, 0, 0, false, &messages); if (messages.empty()) { *ostream << "ERR: bad definition: no write message"; return RESULT_OK; From 5d8c9735d227f731cc7e045877d93850b73fc6c6 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 1 Oct 2024 06:44:52 +0200 Subject: [PATCH 273/345] formatting, lint --- src/ebusd/bushandler.cpp | 3 ++- src/ebusd/knxhandler.cpp | 6 ++++-- src/ebusd/main_args.cpp | 9 +++++---- src/ebusd/mainloop.cpp | 10 ++++++---- src/ebusd/mqtthandler.cpp | 3 ++- src/ebusd/scan.cpp | 2 +- src/lib/ebus/data.cpp | 4 ++-- src/lib/ebus/device_trans.cpp | 2 +- src/lib/ebus/message.cpp | 5 +++-- src/lib/ebus/transport.cpp | 3 ++- src/lib/ebus/transport.h | 2 +- src/lib/utils/httpclient.cpp | 4 ++-- 12 files changed, 31 insertions(+), 22 deletions(-) diff --git a/src/ebusd/bushandler.cpp b/src/ebusd/bushandler.cpp index 5f31976e0..5dc2e0609 100644 --- a/src/ebusd/bushandler.cpp +++ b/src/ebusd/bushandler.cpp @@ -453,7 +453,8 @@ void BusHandler::notifyProtocolMessage(MessageDirection direction, const MasterS answer.push_back(command.dataAt(pos)); } answer.adjustHeader(); - m_protocol->setAnswer(SYN, getSlaveAddress(dstAddress), command[2], command[3], command.data() + 5, idLen, answer); + m_protocol->setAnswer(SYN, getSlaveAddress(dstAddress), command[2], command[3], command.data() + 5, idLen, + answer); // TODO could use loaded messages for identifying MM/MS message pair } } diff --git a/src/ebusd/knxhandler.cpp b/src/ebusd/knxhandler.cpp index 2bca8694d..d160f18dc 100644 --- a/src/ebusd/knxhandler.cpp +++ b/src/ebusd/knxhandler.cpp @@ -771,10 +771,12 @@ void KnxHandler::run() { continue; // not usable in absence of destination address } if (message->getCreateTime() <= definitionsSince // only newer defined - && (!message->isConditional() || message->getAvailableSinceTime() <= definitionsSince)) { // unless conditional + && (!message->isConditional() // unless conditional + || message->getAvailableSinceTime() <= definitionsSince)) { continue; } - logOtherDebug("knx", "checking association to %s %s", message->getCircuit().c_str(), message->getName().c_str()); + logOtherDebug("knx", "checking association to %s %s", message->getCircuit().c_str(), + message->getName().c_str()); bool isWrite = message->isWrite() && !message->isPassive(); // from KNX perspective ssize_t fieldCount = static_cast(message->getFieldCount()); if (isWrite && fieldCount > 1) { diff --git a/src/ebusd/main_args.cpp b/src/ebusd/main_args.cpp index f71498b5d..29e555dbb 100755 --- a/src/ebusd/main_args.cpp +++ b/src/ebusd/main_args.cpp @@ -165,12 +165,13 @@ static const argDef argDefs[] = { "Check and dump config files in FORMAT (\"json\" or \"csv\"), then stop"}, {"dumpconfigto", O_DMPCTO, "FILE", 0, "Dump config files to FILE"}, {"pollinterval", O_POLINT, "SEC", 0, "Poll for data every SEC seconds (0=disable) [5]"}, - {"inject", 'i', "stop", af_optional, "Inject remaining arguments as commands or already seen messages (e.g. " - "\"FF08070400/0AB5454850303003277201\"), optionally stop afterwards"}, - {nullptr, O_INJPOS, "INJECT", af_optional|af_multiple, "Commands and/or messages to inject (if --inject was given)"}, + {"inject", 'i', "stop", af_optional, "Inject remaining arguments as commands or already seen messages " + "(e.g. \"FF08070400/0AB5454850303003277201\"), optionally stop afterwards"}, + {nullptr, O_INJPOS, "INJECT", af_optional|af_multiple, "Commands and/or messages to inject " + "(if --inject was given)"}, #ifdef HAVE_SSL {"cafile", O_CAFILE, "FILE", 0, "Use CA FILE for checking certificates (uses defaults," - " \"#\" for insecure)"}, + " \"#\" for insecure)"}, {"capath", O_CAPATH, "PATH", 0, "Use CA PATH for checking certificates (uses defaults)"}, #endif // HAVE_SSL diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index 7a3635dbd..419b8f45e 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -749,7 +749,8 @@ result_t MainLoop::executeRead(const vector& 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 (only if enabled, allow write direction if given more than once):\n" + " -def read with explicit message definition (only if enabled, allow write direction if given more" + " than once):\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" @@ -849,7 +850,8 @@ result_t MainLoop::executeRead(const vector& args, const string& levels, return RESULT_OK; } deque messages; - m_newlyDefinedMessages->findAll("", "", levels, false, true, writeDirection, writeDirection, true, false, 0, 0, false, &messages); + m_newlyDefinedMessages->findAll("", "", levels, false, true, writeDirection, writeDirection, true, false, 0, 0, + false, &messages); if (messages.empty()) { *ostream << "ERR: bad definition: no read" << (writeDirection?"/write":"") << " message"; return RESULT_OK; @@ -874,8 +876,8 @@ result_t MainLoop::executeRead(const vector& args, const string& levels, if (verbosity & OF_NAMES) { *ostream << cacheMessage->getCircuit() << " " << cacheMessage->getName() << " "; } - ret = cacheMessage->decodeLastData(pt_any, false, fieldIndex == -2 ? nullptr : fieldName.c_str(), fieldIndex, verbosity, - ostream); + ret = cacheMessage->decodeLastData(pt_any, false, fieldIndex == -2 ? nullptr : fieldName.c_str(), fieldIndex, + verbosity, ostream); if (ret < RESULT_OK) { logError(lf_main, "read %s %s cached: decode %s", cacheMessage->getCircuit().c_str(), cacheMessage->getName().c_str(), getResultCode(ret)); diff --git a/src/ebusd/mqtthandler.cpp b/src/ebusd/mqtthandler.cpp index 56ff1cabc..acc6ec5ba 100755 --- a/src/ebusd/mqtthandler.cpp +++ b/src/ebusd/mqtthandler.cpp @@ -818,7 +818,8 @@ void MqttHandler::run() { } message->setDataHandlerState(1, true); } else if (message->getCreateTime() <= m_definitionsSince // only newer defined - && (!message->isConditional() || message->getAvailableSinceTime() <= m_definitionsSince)) { // unless conditional + && (!message->isConditional() // unless conditional + || message->getAvailableSinceTime() <= m_definitionsSince)) { continue; } if (!FileReader::matches(message->getCircuit(), filterCircuit, true, true) diff --git a/src/ebusd/scan.cpp b/src/ebusd/scan.cpp index 3ae6a5160..cb7228847 100644 --- a/src/ebusd/scan.cpp +++ b/src/ebusd/scan.cpp @@ -82,7 +82,7 @@ result_t ScanHelper::collectConfigFiles(const string& relPath, const string& pre if (!m_configHttpClient->get(uri, "", &names)) { return RESULT_ERR_NOTFOUND; } - } else if (!json && names[0]=='<') { // html + } else if (!json && names[0] == '<') { // html uri = m_configUriPrefix + relPathWithSlash + "index.json"; json = true; logDebug(lf_main, "trying index.json"); diff --git a/src/lib/ebus/data.cpp b/src/lib/ebus/data.cpp index 50011c574..ca7c36d58 100644 --- a/src/lib/ebus/data.cpp +++ b/src/lib/ebus/data.cpp @@ -203,7 +203,7 @@ bool isValidIdentifierChar(char ch, bool first, bool allowFirstDigit) { bool DataField::checkIdentifier(const string& name, bool allowFirstDigit) { for (size_t i = 0; i < name.size(); i++) { char ch = name[i]; - if (!isValidIdentifierChar(ch, i==0, allowFirstDigit)) { + if (!isValidIdentifierChar(ch, i == 0, allowFirstDigit)) { return false; } } @@ -213,7 +213,7 @@ bool DataField::checkIdentifier(const string& name, bool allowFirstDigit) { void DataField::normalizeIdentifier(string& name, bool allowFirstDigit) { for (size_t i = 0; i < name.size(); i++) { char ch = name[i]; - if (!isValidIdentifierChar(ch, i==0, allowFirstDigit)) { + if (!isValidIdentifierChar(ch, i == 0, allowFirstDigit)) { name[i] = '_'; } } diff --git a/src/lib/ebus/device_trans.cpp b/src/lib/ebus/device_trans.cpp index 1a855bac2..61a2ba781 100755 --- a/src/lib/ebus/device_trans.cpp +++ b/src/lib/ebus/device_trans.cpp @@ -616,7 +616,7 @@ void EnhancedDevice::notifyInfoRetrieved() { case 0x0107: stream << "rssi "; if (data[0]) { - stream << static_cast(((int8_t*)data)[0]) << " dBm"; + stream << static_cast(reinterpret_cast(data)[0]) << " dBm"; } else { stream << "unknown"; } diff --git a/src/lib/ebus/message.cpp b/src/lib/ebus/message.cpp index ab74a915d..5d2812260 100644 --- a/src/lib/ebus/message.cpp +++ b/src/lib/ebus/message.cpp @@ -70,7 +70,7 @@ using std::endl; #define POLL_PRIORITY_CONDITION 5 /** the field name constant for the message level. */ -static const char* FIELDNAME_LEVEL = "level";// +static const char* FIELDNAME_LEVEL = "level"; /** the known full length field names. */ static const char* knownFieldNamesFull[] = { @@ -1691,7 +1691,8 @@ void SimpleNumericCondition::dumpValuesJson(ostream* output) const { bool SimpleStringCondition::checkValue(const Message* message, const string& field) { ostringstream output; - result_t result = message->decodeLastData(pt_any, false, field.length() == 0 ? nullptr : field.c_str(), -1, OF_NONE, &output); + result_t result = message->decodeLastData(pt_any, false, field.length() == 0 ? nullptr : field.c_str(), -1, + OF_NONE, &output); if (result == RESULT_OK) { string value = output.str(); for (size_t i = 0; i < m_values.size(); i++) { diff --git a/src/lib/ebus/transport.cpp b/src/lib/ebus/transport.cpp index e95221627..c9cb1bc8c 100644 --- a/src/lib/ebus/transport.cpp +++ b/src/lib/ebus/transport.cpp @@ -325,7 +325,8 @@ void SerialTransport::checkDevice() { } result_t NetworkTransport::openInternal() { - m_fd = socketConnect(m_hostOrIp, m_port, m_udp ? IPPROTO_UDP : 0, nullptr, 5, 2); // wait up to 5 seconds for established connection + // wait up to 5 seconds for established connection + m_fd = socketConnect(m_hostOrIp, m_port, m_udp ? IPPROTO_UDP : 0, nullptr, 5, 2); if (m_fd < 0) { return RESULT_ERR_GENERIC_IO; } diff --git a/src/lib/ebus/transport.h b/src/lib/ebus/transport.h index 9a8542245..3c2e1f80f 100755 --- a/src/lib/ebus/transport.h +++ b/src/lib/ebus/transport.h @@ -300,7 +300,7 @@ class NetworkTransport : public FileTransport { */ ~NetworkTransport() override { if (m_hostOrIp) { - free((void*)m_hostOrIp); + free(const_cast(m_hostOrIp)); m_hostOrIp = nullptr; } } diff --git a/src/lib/utils/httpclient.cpp b/src/lib/utils/httpclient.cpp index cdaf0e1a1..b1648c1d8 100755 --- a/src/lib/utils/httpclient.cpp +++ b/src/lib/utils/httpclient.cpp @@ -302,7 +302,7 @@ SSLSocket* SSLSocket::connect(const string& host, const uint16_t& port, bool htt break; } if (strcmp(peerName, hostname) != 0) { - char* dotpos = strchr((char*)hostname, '.'); + char* dotpos = strchr(const_cast(hostname), '.'); if (peerName[0] == '*' && peerName[1] == '.' && dotpos && strcmp(peerName+2, dotpos+1) == 0) { // wildcard matches @@ -582,7 +582,7 @@ bool* repeatable, time_t* time, bool* jsonString) { } pos = readUntil("", length, response); disconnect(); - if (noLength ? pos<1 : pos != length) { + if (noLength ? pos < 1 : pos != length) { return false; } if (noLength) { From 85855ea74d8024ad26d0b4f6da046294c6f0523d Mon Sep 17 00:00:00 2001 From: John Date: Tue, 1 Oct 2024 07:00:27 +0200 Subject: [PATCH 274/345] more logging --- src/ebusd/main.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index e85686fd0..35f182a0c 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -420,9 +420,10 @@ int main(int argc, char* argv[], char* envp[]) { #define MAX_ADDRESSES 10 mdns_oneshot_t addresses[MAX_ADDRESSES]; size_t otherCount = MAX_ADDRESSES; - logWrite(lf_main, ll_notice, "discovering device from \"%s\"", device); int ret = 0; - for (int i=0; i < 3 && ret == 0; i++) { // 3*up to 5 seconds = 15 seconds max + #define MAX_MDNS_RUNS 3 + for (int i=0; i < MAX_MDNS_RUNS && ret == 0; i++) { // 3*up to 5 seconds = 15 seconds max + logWrite(lf_main, ll_notice, "discovering device from \"%s\", try %d/%d", device, i+1, MAX_MDNS_RUNS); ret = resolveMdnsOneShot(device+5, &address, addresses, &otherCount); } if (ret < 0) { From 1a8de228d5fb0ebc2084cc6a757608d51d88ca2f Mon Sep 17 00:00:00 2001 From: John Date: Tue, 1 Oct 2024 07:10:35 +0200 Subject: [PATCH 275/345] add dedicated log level for device (closes #1280) --- src/ebusd/main_args.cpp | 4 ++-- src/lib/ebus/protocol.cpp | 4 ++-- src/lib/utils/log.cpp | 3 ++- src/lib/utils/log.h | 1 + 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/ebusd/main_args.cpp b/src/ebusd/main_args.cpp index 29e555dbb..12bb26461 100755 --- a/src/ebusd/main_args.cpp +++ b/src/ebusd/main_args.cpp @@ -203,8 +203,8 @@ static const argDef argDefs[] = { PACKAGE_LOGFILE "]"}, {"log", O_LOG, "AREAS:LEVEL", 0, "Only write log for matching AREA(S) up to LEVEL" " (alternative to --logareas/--logevel, may be used multiple times) [all:notice]"}, - {"logareas", O_LOGARE, "AREAS", 0, "Only write log for matching AREA(S): main|network|bus|update|other" - "|all [all]"}, + {"logareas", O_LOGARE, "AREAS", 0, "Only write log for matching AREA(S): main|network|bus|device|update" + "|other|all [all]"}, {"loglevel", O_LOGLEV, "LEVEL", 0, "Only write log up to LEVEL: error|notice|info|debug" " [notice]"}, diff --git a/src/lib/ebus/protocol.cpp b/src/lib/ebus/protocol.cpp index 1d80aee48..f74450a96 100644 --- a/src/lib/ebus/protocol.cpp +++ b/src/lib/ebus/protocol.cpp @@ -184,9 +184,9 @@ void ProtocolHandler::notifyDeviceData(const symbol_t* data, size_t len, bool re void ProtocolHandler::notifyDeviceStatus(bool error, const char* message) { if (error) { - logError(lf_bus, "device status: %s", message); + logError(lf_device, message); } else { - logNotice(lf_bus, "device status: %s", message); + logNotice(lf_device, message); } } diff --git a/src/lib/utils/log.cpp b/src/lib/utils/log.cpp index 5e04099c9..89273c316 100755 --- a/src/lib/utils/log.cpp +++ b/src/lib/utils/log.cpp @@ -39,6 +39,7 @@ static const char *s_facilityNames[] = { "main", "network", "bus", + "device", "update", "other", "all", @@ -68,7 +69,7 @@ static const int s_syslogLevels[] = { #endif /** the current log level by log facility. */ -static LogLevel s_facilityLogLevel[] = { ll_notice, ll_notice, ll_notice, ll_notice, ll_notice, }; +static LogLevel s_facilityLogLevel[] = { ll_notice, ll_notice, ll_notice, ll_notice, ll_notice, ll_notice, }; /** the current log FILE, or nullptr if closed or syslog is used. */ static FILE* s_logFile = stdout; diff --git a/src/lib/utils/log.h b/src/lib/utils/log.h index 221fc1144..d011e477b 100755 --- a/src/lib/utils/log.h +++ b/src/lib/utils/log.h @@ -28,6 +28,7 @@ enum LogFacility { lf_main = 0, //!< main loop lf_network, //!< network related lf_bus, //!< eBUS related + lf_device, //!< device related lf_update, //!< updates found while listening to the bus lf_other, //!< all other log facilities lf_COUNT = 5 //!< number of available log facilities and flag for setting all From 5d5543ea1bccf01d69f9e2373f81dee910ece825 Mon Sep 17 00:00:00 2001 From: john30 Date: Tue, 1 Oct 2024 08:34:12 +0200 Subject: [PATCH 276/345] fix previous commit --- src/lib/utils/log.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/utils/log.h b/src/lib/utils/log.h index d011e477b..01116ee5d 100755 --- a/src/lib/utils/log.h +++ b/src/lib/utils/log.h @@ -31,7 +31,7 @@ enum LogFacility { lf_device, //!< device related lf_update, //!< updates found while listening to the bus lf_other, //!< all other log facilities - lf_COUNT = 5 //!< number of available log facilities and flag for setting all + lf_COUNT = 6 //!< number of available log facilities and flag for setting all }; /** the available log levels. */ From 1511db2e271ecf022805128f0a153a878d1bbafe Mon Sep 17 00:00:00 2001 From: John Date: Wed, 2 Oct 2024 06:59:13 +0200 Subject: [PATCH 277/345] fix datetime mapping --- src/ebusd/mqtthandler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ebusd/mqtthandler.cpp b/src/ebusd/mqtthandler.cpp index acc6ec5ba..303118e9c 100755 --- a/src/ebusd/mqtthandler.cpp +++ b/src/ebusd/mqtthandler.cpp @@ -902,7 +902,7 @@ void MqttHandler::run() { } else if (dataType->hasFlag(DAT)) { auto dt = dynamic_cast(dataType); if (dt->hasDate()) { - typeStr = dt->hasDate() ? "datetime" : "date"; + typeStr = dt->hasTime() ? "datetime" : "date"; } else { typeStr = "time"; } From eebb2ef3f532248193060617670700c9ff7d360c Mon Sep 17 00:00:00 2001 From: John Date: Wed, 2 Oct 2024 07:02:31 +0200 Subject: [PATCH 278/345] allow extending existing vars --- src/ebusd/mqtthandler.cpp | 3 ++- src/lib/ebus/stringhelper.cpp | 14 ++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/ebusd/mqtthandler.cpp b/src/ebusd/mqtthandler.cpp index 303118e9c..39539f93c 100755 --- a/src/ebusd/mqtthandler.cpp +++ b/src/ebusd/mqtthandler.cpp @@ -71,7 +71,8 @@ static const argDef g_mqtt_argDefs[] = { {"mqttretain", O_RETA, nullptr, 0, "Retain all topics instead of only selected global ones"}, {"mqttqos", O_PQOS, "QOS", 0, "Set the QoS value for all topics (0-2) [0]"}, {"mqttint", O_INTF, "FILE", 0, "Read MQTT integration settings from FILE (no default)"}, - {"mqttvar", O_IVAR, "NAME=VALUE[,...]", 0, "Add variable(s) to the read MQTT integration settings"}, + {"mqttvar", O_IVAR, "NAME[+]=VALUE[,...]", 0, "Add variable(s) to the read MQTT integration settings " + "(append to already existing value with \"NAME+=VALUE\")"}, {"mqttjson", O_JSON, "short", af_optional, "Publish in JSON format instead of strings, optionally in short (value directly below field key)"}, {"mqttverbose", O_VERB, nullptr, 0, "Publish all available attributes"}, diff --git a/src/lib/ebus/stringhelper.cpp b/src/lib/ebus/stringhelper.cpp index a3e18301e..6a80a328e 100644 --- a/src/lib/ebus/stringhelper.cpp +++ b/src/lib/ebus/stringhelper.cpp @@ -373,16 +373,14 @@ void StringReplacers::parseLine(const string& line) { if (pos == string::npos || pos == 0) { return; } - bool emptyIfMissing = false; - string key; - if (line[pos-1] == '?') { - emptyIfMissing = true; - key = line.substr(0, pos-1); - } else { - key = line.substr(0, pos); - } + bool emptyIfMissing = line[pos-1] == '?'; + bool append = !emptyIfMissing && line[pos-1] == '+'; + string key = line.substr(0, (emptyIfMissing || append) ? pos-1 : pos); FileReader::trim(&key); string value = line.substr(pos+1); + if (append) { + value = get(key).str() + value; + } FileReader::trim(&value); if (value.find('%') == string::npos) { set(key, value); // constant value From 6d0511a174decfb631aac655c389f7721a2ef5ff Mon Sep 17 00:00:00 2001 From: John Date: Wed, 2 Oct 2024 07:28:45 +0200 Subject: [PATCH 279/345] add date+datetime mapping (closes #1120 and #1134) --- contrib/etc/ebusd/mqtt-hassio.cfg | 35 ++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/contrib/etc/ebusd/mqtt-hassio.cfg b/contrib/etc/ebusd/mqtt-hassio.cfg index 9c438ee54..e78823508 100644 --- a/contrib/etc/ebusd/mqtt-hassio.cfg +++ b/contrib/etc/ebusd/mqtt-hassio.cfg @@ -148,10 +148,11 @@ circuit_part = { type_map-number = number type_map-list = string type_map-time = time -# HA integration: skip string/date/datetime types completely +type_map-date = date +type_map-datetime = datetime + +# HA integration: skip string types completely type_map-string = -type_map-date = -type_map-datetime = # field type switch designator, see below. # HA integration: this is used to set several variable values depending on the field type, message name, field name, and unit. @@ -222,13 +223,23 @@ type_switch-time = sensor,,,time = from,|to,|time2,|timer sensor,,,time3 = time, +# HA integration: the mapping list for (potentially) writable string entities containing a date value by field type, name, message, and unit. +type_switch-w-date = + text,,,date = + +type_switch-date = + sensor,,, = + +# HA integration: the mapping list for (potentially) writable string entities containing a datetime value by field type, name, message, and unit. +type_switch-w-datetime = + text,,,datetime = + +type_switch-datetime = + sensor,,, = + # HA integration: currently unused mapping lists for non-numeric/non-binary entities. #type_switch-string = # sensor,, = -#type_switch-date = -# sensor,,measurement = -#type_switch-datetime = -# sensor, = # HA integration: optional variable with the entity device class for numbers type_class_number ?= , @@ -299,6 +310,16 @@ type_part-texttime3 = , "command_topic":"%topic/set", "pattern": "^[012][0-9]:[0-5][0-9]:[0-5][0-9]$"%state_class +# HA integration: %type_part variable for date %type_topic +type_part-textdate = , + "command_topic":"%topic/set", + "pattern": "^[0-3][0-9].[01][0-9].20[0-3][0-9]$"%state_class + +# HA integration: %type_part variable for datetime %type_topic +type_part-textdatetime = , + "command_topic":"%topic/set", + "pattern": "^[0-3][0-9].[01][0-9].20[0-3][0-9] [012][0-9]:[0-5][0-9]$"%state_class + # optional format string for converting a fields value list into %field_values. # "$value" and "$text" are being replaced by the corresponding part. field_values-entry = "$text" From 32aa1e387423f7d491c1beeb5b8f422c70db28a6 Mon Sep 17 00:00:00 2001 From: John Date: Thu, 3 Oct 2024 23:00:50 +0200 Subject: [PATCH 280/345] workaround for badly typed strdupa on alpine --- src/lib/utils/tcpsocket.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/utils/tcpsocket.cpp b/src/lib/utils/tcpsocket.cpp index bafea0743..5dc56d446 100755 --- a/src/lib/utils/tcpsocket.cpp +++ b/src/lib/utils/tcpsocket.cpp @@ -69,7 +69,7 @@ int tcpConnToUdpOptions, int tcpKeepAliveInterval, struct in_addr* storeIntf) { struct in_addr intf; intf.s_addr = INADDR_ANY; if (pos) { - char* str = strdupa(server); + char* str = (char*)strdupa(server); // (char*) needed for wrong return type on e.g. Alpine char* ifa = strchr(str, '@'); ifa[0] = 0; ifa++; From 6e552c39456c4be1ddcadc61bf227aee2603f4af Mon Sep 17 00:00:00 2001 From: John Date: Sat, 5 Oct 2024 08:34:39 +0200 Subject: [PATCH 281/345] updated --- ChangeLog.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index add9cdb7e..c9f730caa 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -6,6 +6,7 @@ * fix for some updated messages not appearing on KNX or MQTT * fix for parsing certain condition ranges * fix for "reload" command not starting the scan again +* fix datetime type mapping in MQTT ## Features * add "inject" command @@ -15,6 +16,9 @@ * add verbose raw data option to "read" and "write" commands * add option to allow write direction in "read" command when inline defining a new message * add option to discover device via mDNS +* add dedicated log level for device messages +* add option to extend MQTT variables from env/cmdline +* add date+datetime to Home Assistant MQTT discovery integration ## Breaking Changes * change default config path to https://ebus.github.io/ serving files generated from new TypeSpec message definition sources From 8c5c6c4cef231514fc5baebf268b421366bcc497 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 5 Oct 2024 09:07:07 +0200 Subject: [PATCH 282/345] enhance device update check --- contrib/etc/ebusd/mqtt-hassio.cfg | 2 +- contrib/updatecheck/calcversions.sh | 6 ++---- contrib/updatecheck/index.php | 4 ++-- contrib/updatecheck/prepend.inc | 11 ++++++++--- src/lib/ebus/device.h | 2 +- src/lib/ebus/device_trans.cpp | 25 ++++++++++++++++++++++--- src/lib/ebus/device_trans.h | 16 +++++++++++++--- 7 files changed, 49 insertions(+), 17 deletions(-) diff --git a/contrib/etc/ebusd/mqtt-hassio.cfg b/contrib/etc/ebusd/mqtt-hassio.cfg index e78823508..d71f93d10 100644 --- a/contrib/etc/ebusd/mqtt-hassio.cfg +++ b/contrib/etc/ebusd/mqtt-hassio.cfg @@ -428,7 +428,7 @@ def_global_updatecheck_device-payload = { }, "state_topic":"%topic", "name":"%name", - "value_template":"{%% set my_new = value_json|truncate(255)|regex_replace(find='^[^,]*|, device firmware |,.*| available',replace='') %%}{%% set my_cur = 'old' %%}{%% if my_new == '' %%}{%% set my_new = 'current' %%}{%% set my_cur = 'current' %%}{%% endif %%}{{ {'installed_version':my_cur,'latest_version':my_new,'entity_picture':'https://adapter.ebusd.eu/favicon.ico','release_url':'https://adapter.ebusd.eu/firmware/ChangeLog'} | tojson }}" + "value_template":"{%% set my_new = value_json|truncate(255)|regex_replace(find='^[^,]*|, device firmware |,.*| available',replace='') %%}{%% set my_ds = 'v31/firmware/' %%}{%% if my_new is search('up to date') %%}{%% set my_ds = my_new|regex_replace(find=' .*',replace='/') %%}{%% set my_new = 'current' %%}{%% set my_cur = 'current' %%}{%% else %%}{%% set my_cur = 'old' %%}{%% if my_new is search(' ') %%}{%% set my_ds = my_new|regex_replace(find=' .*',replace='/') %%}{%% set my_new = my_new|regex_replace(find='.* ',replace='') %%}{%% else %%}{%% if my_new == '' %%}{%% set my_new = 'current' %%}{%% set my_cur = 'current' %%}{%% endif %%}{%% endif %%}{%% endif %%}{{ {'installed_version':my_cur,'latest_version':my_new,'entity_picture':'https://adapter.ebusd.eu/'+my_ds+'favicon.ico','release_url':'https://adapter.ebusd.eu/'+my_ds+'ChangeLog'} | tojson }}" } # the topic and payload to listen to in order to republish all config messages. diff --git a/contrib/updatecheck/calcversions.sh b/contrib/updatecheck/calcversions.sh index a1333eb1a..fc485e24a 100755 --- a/contrib/updatecheck/calcversions.sh +++ b/contrib/updatecheck/calcversions.sh @@ -2,12 +2,11 @@ version=`head -n 1 ../../VERSION` revision=`git describe --always` echo "ebusd=${version},${revision}" > versions.txt -# cp versions.txt > oldversions.txt devver=`curl -s https://adapter.ebusd.eu/v31/firmware/ChangeLog|grep "Version "|head -n 1|sed -e 's#.*]*>##' -e 's#<.*##' -e 's# ##'` devbl=`curl -s https://adapter.ebusd.eu/v31/firmware/ChangeLog|grep "Bootloader version"|head -n 1|sed -e 's#.*]*>##' -e 's#<.*##' -e 's# ##'` echo "device=${devver},${devbl}" >> versions.txt -devver=`curl -s https://adapter.ebusd.eu/v5/ChangeLog|grep "Version "|head -n 1|sed -e 's#.*]*>##' -e 's#<.*##' -e 's# ##'` -echo "device=${devver},${devver}" >> versions.txt +devver=`curl -s https://adapter.ebusd.eu/v5/ChangeLog|grep "Version "|head -n 1|sed -e 's#.*id="\([^"]*\)".*]*>\([^<]*\)<.*#\2,\2,\1#'` +echo "device=${devver}" >> versions.txt cp versions.txt cdnversions.txt files=`find config/de -type f -or -type l` ../../src/lib/ebus/test/test_filereader $files|sed -e 's#^config/de/##' -e 's#^\([^ ]*\) #\1=#' -e 's# #,#g'|sort >> versions.txt @@ -17,7 +16,6 @@ for filever in $(../../src/lib/ebus/test/test_filereader $files|sed -e 's#^confi ver=${filever#*=} sed -i -e "s#^$file=\(.*\)\$#$file=\1,$ver#" versions.txt done -#./oldtest_filereader $files|sed -e 's#^config/##' -e 's#^\([^ ]*\) #\1=#' -e 's# #,#g'|sort >> oldversions.txt curl -sS https://ebus.github.io/en/versions.json -o veren.json curl -sS https://ebus.github.io/de/versions.json -o verde.json node -e 'fs=require("fs");e=JSON.parse(fs.readFileSync("veren.json","utf-8"));d=JSON.parse(fs.readFileSync("verde.json","utf-8"));console.log(Object.entries(d).map(([k,de])=>{en=e[k];return `${k}=${de.hash},${de.size},${de.mtime},${en.hash},${en.size},${en.mtime}`;}).join("\n"))'|sort >> cdnversions.txt diff --git a/contrib/updatecheck/index.php b/contrib/updatecheck/index.php index ec6bc51e7..d40cfa5f0 100644 --- a/contrib/updatecheck/index.php +++ b/contrib/updatecheck/index.php @@ -5,7 +5,7 @@ $r = @file_get_contents('php://input'); header('Content-Type: text/plain'); $r = @json_decode($r, true); - echo checkUpdate(@$r['v'], @$r['r'], @$r['a'], @$r['dv'], @$r['l'], @$r['lc'], @$r['cp']); + echo checkUpdate(@$r['v'], @$r['r'], @$r['a'], @$r['dv'], @$r['di'], @$r['l'], @$r['lc'], @$r['cp']); exit; } readVersions(); @@ -23,7 +23,7 @@ } ?>

latest ebusd version:

-

latest device firmware: v5=, v3*=

+

latest device firmware: v5*=, v3*=

config files:1) { $key = $val[0]; $val = explode(',', $val[1]); - if ($key==='device' && count($val)===2 && $val[0]===$val[1]) { + if ($key==='device' && count($val)>=2 && $val[0]===$val[1]) { $key = 'devicesame'; } $versions[$key] = $val; @@ -23,7 +23,7 @@ function readVersions($prefix='') { array_walk($v, $func); return true; } -function checkUpdate($ebusdVersion, $ebusdRelease, $architecture, $deviceVersion, $loadedFiles, $language, $cdnPath) { +function checkUpdate($ebusdVersion, $ebusdRelease, $architecture, $deviceVersion, $deviceId, $loadedFiles, $language, $cdnPath) { if (!$ebusdVersion) { return 'invalid request'; } @@ -43,7 +43,12 @@ function checkUpdate($ebusdVersion, $ebusdRelease, $architecture, $deviceVersion $ver = strtok('.'); if ($ver) { $key = ($ver===strtok('.')) ? 'devicesame' : 'device'; - if ($ver!==$versions[$key][0]) { + $devTypes = array('00'=>'v5', '01'=>'v5-c6', '00n'=>'v5', '01n'=>''); + $devType = $key==='devicesame' && strlen($deviceId)===9*2 && substr($deviceId,-2)==='00' ? $devTypes[substr($deviceId,-4,2).(substr($deviceId,-6,2)==='00'?'n':'')] : ''; + if ($devType) { + $devVer = $versions[$key][0]; + $ret .= ', device firmware '.$devType.' '.$devVer.($ver===$devVer ? ' up to date' : ' available'); + } else if ($ver!==$versions[$key][0]) { $ret .= ', device firmware '.$versions[$key][0].' available'; } } diff --git a/src/lib/ebus/device.h b/src/lib/ebus/device.h index 8d023ccc4..646eeec72 100755 --- a/src/lib/ebus/device.h +++ b/src/lib/ebus/device.h @@ -115,7 +115,7 @@ class Device { * Format device infos in JSON format. * @param output the @a ostringstream to append the infos to. */ - virtual void formatInfoJson(ostringstream* output) const {} + virtual void formatInfoJson(ostringstream* output) {} /** * @return whether the device supports checking for version updates. diff --git a/src/lib/ebus/device_trans.cpp b/src/lib/ebus/device_trans.cpp index 61a2ba781..458050527 100755 --- a/src/lib/ebus/device_trans.cpp +++ b/src/lib/ebus/device_trans.cpp @@ -159,10 +159,21 @@ void EnhancedDevice::formatInfo(ostringstream* ostream, bool verbose, bool prefi } } -void EnhancedDevice::formatInfoJson(ostringstream* ostream) const { +void EnhancedDevice::formatInfoJson(ostringstream* ostream) { string ver = getEnhancedVersion(); if (!ver.empty()) { *ostream << ",\"dv\":\"" << ver << "\""; + if (m_enhInfoIdRequestNeeded) { + if (!m_enhInfoIdRequested) { + result_t ret = requestEnhancedInfo(1); + if (ret == RESULT_OK) { + requestEnhancedInfo(0xff); + } + } + if (!m_enhInfoId.empty()) { + *ostream << ",\"di\":\"" << m_enhInfoId << "\""; + } + } } } @@ -197,6 +208,7 @@ result_t EnhancedDevice::requestEnhancedInfo(symbol_t infoId, bool wait) { uint8_t buf[] = makeEnhancedSequence(ENH_REQ_INFO, infoId); result_t result = m_transport->write(buf, 2); if (result == RESULT_OK) { + m_enhInfoIdRequested = m_enhInfoIdRequested || (infoId == 1); m_infoBuf[0] = infoId; m_infoLen = 1; m_infoPos = 1; @@ -350,6 +362,9 @@ result_t EnhancedDevice::notifyTransportStatus(bool opened) { m_infoLen = 0; m_enhInfoVersion = ""; m_enhInfoIsWifi = false; + m_enhInfoIdRequestNeeded = false; + m_enhInfoIdRequested = false; + m_enhInfoId = ""; m_enhInfoTemperature = ""; m_enhInfoSupplyVoltage = ""; m_enhInfoBusVoltage = ""; @@ -545,6 +560,7 @@ void EnhancedDevice::notifyInfoRetrieved() { stream << "[" << setfill('0') << setw(2) << hex << static_cast(data[6]) << setw(2) << static_cast(data[7]) << "]"; } + m_enhInfoIdRequestNeeded = len >= 8 && data[0] == data[5] && data[2] == data[6] && data[3] == data[7]; m_enhInfoVersion = stream.str(); stream.str(" "); stream << "firmware " << m_enhInfoVersion; @@ -557,10 +573,13 @@ void EnhancedDevice::notifyInfoRetrieved() { case 0x0901: case 0x0802: case 0x0302: - stream << (id == 1 ? "ID" : "config"); + stream << (id == 1 ? "ID " : "config "); stream << hex << setfill('0'); for (size_t pos = 0; pos < len; pos++) { - stream << " " << setw(2) << static_cast(data[pos]); + stream << setw(2) << static_cast(data[pos]); + } + if (id == 1 && data[8] == 0) { + m_enhInfoId = stream.str().substr(3); } if (id == 2 && (data[2]&0x3f) != 0x3f) { // non-default arbitration delay diff --git a/src/lib/ebus/device_trans.h b/src/lib/ebus/device_trans.h index 2a69034ba..de40e1e04 100755 --- a/src/lib/ebus/device_trans.h +++ b/src/lib/ebus/device_trans.h @@ -72,7 +72,7 @@ class BaseDevice : public Device, public TransportListener { } // @copydoc - virtual void formatInfoJson(ostringstream* output) const {} + virtual void formatInfoJson(ostringstream* output) {} // @copydoc virtual result_t notifyTransportStatus(bool opened) { @@ -139,14 +139,15 @@ class EnhancedDevice : public BaseDevice, public EnhancedDeviceInterface { */ explicit EnhancedDevice(Transport* transport) : BaseDevice(transport), EnhancedDeviceInterface(), m_resetTime(0), m_resetRequested(false), - m_extraFeatures(0), m_infoReqTime(0), m_infoLen(0), m_infoPos(0), m_enhInfoIsWifi(false) { + m_extraFeatures(0), m_infoReqTime(0), m_infoLen(0), m_infoPos(0), m_enhInfoIsWifi(false), + m_enhInfoIdRequestNeeded(false), m_enhInfoIdRequested(false) { } // @copydoc void formatInfo(ostringstream* output, bool verbose, bool prefix) override; // @copydoc - void formatInfoJson(ostringstream* output) const override; + void formatInfoJson(ostringstream* output) override; // @copydoc result_t send(symbol_t value) override; @@ -217,6 +218,15 @@ class EnhancedDevice : public BaseDevice, public EnhancedDeviceInterface { /** whether the device is known to be connected via WIFI. */ bool m_enhInfoIsWifi; + /** whether the device ID request is needed. */ + bool m_enhInfoIdRequestNeeded; + + /** whether the device ID was already requested. */ + bool m_enhInfoIdRequested; + + /** a string with the ID of the enhanced device. */ + string m_enhInfoId; + /** a string describing the enhanced device temperature. */ string m_enhInfoTemperature; From 254517f9db70e16dd851a0951e770bea7fdbcf8e Mon Sep 17 00:00:00 2001 From: John Date: Sat, 5 Oct 2024 09:12:19 +0200 Subject: [PATCH 283/345] better comments, adhere to cdn normalization --- ChangeLog.md | 2 +- contrib/etc/ebusd/mqtt-hassio.cfg | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index c9f730caa..ee866c304 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -18,7 +18,7 @@ * add option to discover device via mDNS * add dedicated log level for device messages * add option to extend MQTT variables from env/cmdline -* add date+datetime to Home Assistant MQTT discovery integration +* add date+datetime mapping and better device update check to Home Assistant MQTT discovery integration ## Breaking Changes * change default config path to https://ebus.github.io/ serving files generated from new TypeSpec message definition sources diff --git a/contrib/etc/ebusd/mqtt-hassio.cfg b/contrib/etc/ebusd/mqtt-hassio.cfg index d71f93d10..e6bf4770c 100644 --- a/contrib/etc/ebusd/mqtt-hassio.cfg +++ b/contrib/etc/ebusd/mqtt-hassio.cfg @@ -155,11 +155,11 @@ type_map-datetime = datetime type_map-string = # field type switch designator, see below. -# HA integration: this is used to set several variable values depending on the field type, message name, field name, and unit. +# HA integration: this is used to set several variable values depending on the field type, message name+field name, and unit. type_switch-by = %name%field,%unit # field type switch variables names to use in addition to %type_switch in case of multiple keys (separated by comma). -# HA integration: var names for topic/class/state/sub values mapped from field type, message name, field name, and unit. +# HA integration: var names for topic/class/state/sub values mapped from field type, message name+field name, and unit. type_switch-names = type_topic,type_class,type_state,type_sub # field type switch for each field type and optionally direction (between dash before the field type) available as @@ -169,7 +169,7 @@ type_switch-names = type_topic,type_class,type_state,type_sub # when the wildcard string in the right part matched the "type_switch-by" value. The list is traversed from top to # bottom stopping at the first match. If direction specific definitions exist, these are traversed first. If no line # matches at all, the variable(s) are set to the empty string. -# HA integration: the mapping list for (potentially) writable number entities by field type, name, message, and unit. +# HA integration: the mapping list for (potentially) writable number entities field type, message name+field name, and unit. type_switch-w-number = number,temperature, = temp|,°C$ number,temperature, = temp|,K$ @@ -177,21 +177,21 @@ type_switch-w-number = number,power_factor, = power*%% number,power, = power|,kW$|,W$ number,voltage, = volt|,V$ - number,current, = current,|,A$ + number,current, = current,|currentvalue,|,A$ number,pressure, = bar$ number,gas, = gas*/min$ number,humidity, = humid*%%$ - number,, = curve, + number,, = curve,|curvevalue, -# HA integration: the mapping list for numeric sensor entities by field type, name, message, and unit. +# HA integration: the mapping list for numeric sensor entities field type, message name+field name, and unit. type_switch-number = - sensor,,total_increasing = poweron|count, + sensor,,total_increasing = poweron|count,|countvalue, sensor,temperature,measurement = temp|,°C$ sensor,temperature,measurement = temp|,K$ sensor,power_factor,measurement = power*%% sensor,power,measurement = power|,kW$|,W$ sensor,voltage,measurement = volt|,V$ - sensor,current,measurement = current,|,A$ + sensor,current,measurement = current,|currentvalue,|,A$ sensor,,measurement = integral|,°min$ sensor,energy,total_increasing = energy|,Wh$ sensor,yield,total_increasing = total*,Wh$ @@ -202,35 +202,35 @@ type_switch-number = sensor,humidity,measurement = humid*%%$ sensor,, = -# HA integration: the mapping list for (potentially) writable binary switch entities by field type, name, message, and unit. +# HA integration: the mapping list for (potentially) writable binary switch entities field type, message name+field name, and unit. type_switch-w-list = switch,, = onoff switch,,,yesno = yesno select,, = -# HA integration: the mapping list for rather binary sensor entities by field type, name, message, and unit. +# HA integration: the mapping list for rather binary sensor entities field type, message name+field name, and unit. type_switch-list = binary_sensor,,measurement = onoff binary_sensor,,measurement,yesno = yesno sensor,,,list = -# HA integration: the mapping list for (potentially) writable string entities containing a time value by field type, name, message, and unit. +# HA integration: the mapping list for (potentially) writable string entities containing a time value field type, message name+field name, and unit. type_switch-w-time = text,,,time = from,|to,|time2,|timer - text,,,time3 = time, + text,,,time3 = time,|timevalue, type_switch-time = sensor,,,time = from,|to,|time2,|timer - sensor,,,time3 = time, + sensor,,,time3 = time,|timevalue, -# HA integration: the mapping list for (potentially) writable string entities containing a date value by field type, name, message, and unit. +# HA integration: the mapping list for (potentially) writable string entities containing a date value field type, message name+field name, and unit. type_switch-w-date = text,,,date = type_switch-date = sensor,,, = -# HA integration: the mapping list for (potentially) writable string entities containing a datetime value by field type, name, message, and unit. +# HA integration: the mapping list for (potentially) writable string entities containing a datetime value field type, message name+field name, and unit. type_switch-w-datetime = text,,,datetime = From 3a9ee44728446cee4607062e36a050658b749515 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 22 Oct 2024 07:53:04 +0200 Subject: [PATCH 284/345] stop with error on multiple non-matching mdns responses --- src/ebusd/main.cpp | 9 +++++++-- src/lib/utils/tcpsocket.cpp | 29 +++++++++++++++++++---------- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index 35f182a0c..43437de52 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -444,9 +444,14 @@ int main(int argc, char* argv[], char* envp[]) { return EINVAL; } if (ret > 1) { - logWrite(lf_main, ll_notice, - "found several devices from \"%s\", better limit to the desired one using e.g. \"mdns:%s\"", device, + ip = inet_ntoa(address.address); + logWrite(lf_main, ll_info, "discovered another device with ID %s and device string %s:%s", + address.id, address.proto, ip); + logWrite(lf_main, ll_error, + "found several devices from \"%s\". use e.g. \"--device=mdns:%s\" to limit it to the desired one", device, address.id); + cleanup(); + return EINVAL; } ip = inet_ntoa(address.address); char *mdnsDevice = reinterpret_cast(malloc(4*4+3+1)); // ens:xxx.xxx.xxx.xxx diff --git a/src/lib/utils/tcpsocket.cpp b/src/lib/utils/tcpsocket.cpp index 5dc56d446..b01e656bb 100755 --- a/src/lib/utils/tcpsocket.cpp +++ b/src/lib/utils/tcpsocket.cpp @@ -453,9 +453,9 @@ int resolveMdnsOneShot(const char* url, mdns_oneshot_t *result, mdns_oneshot_t * 0x8000 | // unicast response bit DNS_CLASS_AA); len += sizeof(dns_question_t); - ssize_t done = sendto(sock, record, len, 0, reinterpret_cast(&address), sizeof(address)); + ssize_t ret = sendto(sock, record, len, 0, reinterpret_cast(&address), sizeof(address)); #ifdef DEBUG_MDNS - printf("mdns: sent %ld, err %d\n", done, errno); + printf("mdns: sent %ld, err %d\n", ret, errno); #endif fcntl(sock, F_SETFL, O_NONBLOCK); bool found = false, foundMore = false; @@ -463,22 +463,31 @@ int resolveMdnsOneShot(const char* url, mdns_oneshot_t *result, mdns_oneshot_t * if (moreRemain > 0) { *moreCount = 0; } + size_t done = 0; #ifdef DEBUG_MDNS socketaddress aaddr; socklen_t aaddrlen = 0; #endif for (int i=0; i < (found ? 3 : 5); i++) { // up to 5 seconds, at least 3 seconds - int ret = socketPoll(sock, POLLIN, 1); + ret = socketPoll(sock, POLLIN, 1); done = 0; if (ret > 0 && (ret&POLLIN)) { #ifdef DEBUG_MDNS aaddrlen = sizeof(aaddr); - done = recvfrom(sock, record, sizeof(record), 0, reinterpret_cast(&aaddr), &aaddrlen); + ret = recvfrom(sock, record, sizeof(record), 0, reinterpret_cast(&aaddr), &aaddrlen); #else - done = recv(sock, record, sizeof(record), 0); + ret = recv(sock, record, sizeof(record), 0); #endif } - if (done == 0 || (done < 0 && errno == EAGAIN) || done < sizeof(dns_query_t)) { + if (ret < 0) { + if (errno == EAGAIN) { + continue; + } + close(sock); + return -1; + } + done = (size_t)ret; + if (done < sizeof(dns_query_t)) { continue; } dnsr = reinterpret_cast(record); @@ -494,8 +503,8 @@ int resolveMdnsOneShot(const char* url, mdns_oneshot_t *result, mdns_oneshot_t * ) { continue; } - int anCnt = ntohs(dnsr->anCount); - int arCnt = ntohs(dnsr->arCount); + uint16_t anCnt = ntohs(dnsr->anCount); + uint16_t arCnt = ntohs(dnsr->arCount); if (anCnt < 1 || dnsr->nsCount || arCnt < 1) { continue; } @@ -546,7 +555,7 @@ int resolveMdnsOneShot(const char* url, mdns_oneshot_t *result, mdns_oneshot_t * break; // skip this one } len += sizeof(dns_answer_t); - int rdLen = ntohs(a->rdLength); + uint16_t rdLen = ntohs(a->rdLength); #ifdef DEBUG_MDNS printf(" rd %d @%2.2x = ", rdLen, len); for (int i=0; i < rdLen && len+i < done; i++) { @@ -577,7 +586,7 @@ int resolveMdnsOneShot(const char* url, mdns_oneshot_t *result, mdns_oneshot_t * sep = sep ? strchr(sep2+1, '=') : nullptr; } if (sep && strncmp(sep2+1, "proto", sep-sep2-1) == 0 && ( - pos == sep+1+sizeof(mdns_oneshot_t::proto)-1-name + pos == (size_t)(sep-name)+1+sizeof(mdns_oneshot_t::proto)-1 || strchr(sep+1, '.') == sep+1+sizeof(mdns_oneshot_t::proto)-1)) { memcpy(proto, sep+1, sizeof(mdns_oneshot_t::proto)-1); } From 0dd3a802694bc47c01e86c773273751d87764461 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 26 Oct 2024 07:10:42 +0200 Subject: [PATCH 285/345] Bump proudust/gh-describe from 2.0.0 to 2.1.0 (#1356) Bumps [proudust/gh-describe](https://github.com/proudust/gh-describe) from 2.0.0 to 2.1.0. - [Release notes](https://github.com/proudust/gh-describe/releases) - [Commits](https://github.com/proudust/gh-describe/compare/v2.0.0...v2.1.0) --- updated-dependencies: - dependency-name: proudust/gh-describe dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- .github/workflows/preparerelease.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 05711904b..f14379a7a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,7 +33,7 @@ jobs: - name: gh-describe id: gittag - uses: proudust/gh-describe@v2.0.0 + uses: proudust/gh-describe@v2.1.0 - name: set up QEMU uses: docker/setup-qemu-action@v3 diff --git a/.github/workflows/preparerelease.yml b/.github/workflows/preparerelease.yml index fc7495f83..847dc54bd 100644 --- a/.github/workflows/preparerelease.yml +++ b/.github/workflows/preparerelease.yml @@ -40,7 +40,7 @@ jobs: - name: gh-describe id: gittag - uses: proudust/gh-describe@v2.0.0 + uses: proudust/gh-describe@v2.1.0 - name: set up QEMU uses: docker/setup-qemu-action@v3 From 619f15d73090530d72bbc8d856bdcaf1d24028d2 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 26 Oct 2024 08:18:29 +0200 Subject: [PATCH 286/345] remove field name from HA discovery for single-field messages --- ChangeLog.md | 2 +- contrib/etc/ebusd/mqtt-hassio.cfg | 3 ++- src/ebusd/mqtthandler.cpp | 4 +++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index ee866c304..ee9dd613d 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -18,7 +18,7 @@ * add option to discover device via mDNS * add dedicated log level for device messages * add option to extend MQTT variables from env/cmdline -* add date+datetime mapping and better device update check to Home Assistant MQTT discovery integration +* add date+datetime mapping, better device update check, and removed single-field-message field names to Home Assistant MQTT discovery integration ## Breaking Changes * change default config path to https://ebus.github.io/ serving files generated from new TypeSpec message definition sources diff --git a/contrib/etc/ebusd/mqtt-hassio.cfg b/contrib/etc/ebusd/mqtt-hassio.cfg index e6bf4770c..921e08c29 100644 --- a/contrib/etc/ebusd/mqtt-hassio.cfg +++ b/contrib/etc/ebusd/mqtt-hassio.cfg @@ -52,6 +52,7 @@ # %field the field name and key for JSON objects (equals the field index if no name is defined or the names are not # unique in the message). # %fieldname the field name (and nothing else like in %field, might be empty though!) +# %fieldnamemult the field name unless there is a single field only (similar to %fieldname) # %type the field type (one of "number", "list", "string", "date", "time", or "datetime"). # %basetype the base data type ID (e.g. "UCH"). # %comment the field comment (if any). @@ -350,7 +351,7 @@ definition-topic ?= %haprefix/%type_topic/%{TOPIC}_%FIELD/config # HA integration: this is the config topic payload for HA's MQTT discovery. definition-payload = { "unique_id":"%{TOPIC}_%FIELD", - "name":"%name %fieldname", + "name":"%name %fieldnamemult", "device":%circuit_part, "value_template":"{{value_json[\"%field\"].value}}", "state_topic":"%topic"%field_payload diff --git a/src/ebusd/mqtthandler.cpp b/src/ebusd/mqtthandler.cpp index 39539f93c..312d33c3a 100755 --- a/src/ebusd/mqtthandler.cpp +++ b/src/ebusd/mqtthandler.cpp @@ -924,7 +924,9 @@ void MqttHandler::run() { StringReplacers values = msgValues; // need a copy here as the contents are manipulated values.set("index", static_cast(index)); values.set("field", fieldName); - values.set("fieldname", field->getName(-1)); + string fieldNameNonUnique = field->getName(-1); + values.set("fieldname", fieldNameNonUnique); + values.set("fieldnamemult", fieldCount == 1 ? "" : fieldNameNonUnique); values.set("type", typeStr); values.set("type_map", str); values.set("basetype", dataType->getId()); From 21f2d1def7a4dc1179434d4f67e8324c5cabb75a Mon Sep 17 00:00:00 2001 From: John Date: Sun, 27 Oct 2024 10:03:33 +0100 Subject: [PATCH 287/345] another try to make alpine compile --- src/lib/utils/tcpsocket.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/utils/tcpsocket.cpp b/src/lib/utils/tcpsocket.cpp index b01e656bb..6c7ac9cd5 100755 --- a/src/lib/utils/tcpsocket.cpp +++ b/src/lib/utils/tcpsocket.cpp @@ -69,7 +69,7 @@ int tcpConnToUdpOptions, int tcpKeepAliveInterval, struct in_addr* storeIntf) { struct in_addr intf; intf.s_addr = INADDR_ANY; if (pos) { - char* str = (char*)strdupa(server); // (char*) needed for wrong return type on e.g. Alpine + char* str = reinterpret_cast(strdupa(server)); // (char*) needed for wrong return type on e.g. Alpine char* ifa = strchr(str, '@'); ifa[0] = 0; ifa++; From d2d0f5a9b994d7743fbd9b89e8991244d91b8b15 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 27 Oct 2024 10:13:57 +0100 Subject: [PATCH 288/345] better workaround for alpine --- src/lib/utils/tcpsocket.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/lib/utils/tcpsocket.cpp b/src/lib/utils/tcpsocket.cpp index 6c7ac9cd5..01a94dbfe 100755 --- a/src/lib/utils/tcpsocket.cpp +++ b/src/lib/utils/tcpsocket.cpp @@ -69,16 +69,21 @@ int tcpConnToUdpOptions, int tcpKeepAliveInterval, struct in_addr* storeIntf) { struct in_addr intf; intf.s_addr = INADDR_ANY; if (pos) { - char* str = reinterpret_cast(strdupa(server)); // (char*) needed for wrong return type on e.g. Alpine + size_t len = strlen(server)+1; // workaround for e.g. Alpine with wrong return type on strdupa() + char* str = reinterpret_cast(malloc(len)); + strcpy(str, server); char* ifa = strchr(str, '@'); ifa[0] = 0; ifa++; if (!str[0] || !parseIp(str, &address->sin_addr)) { + free(str); return -1; } if (!parseIp(ifa, &intf)) { + free(str); return -1; } + free(str); } else if (!parseIp(server, &address->sin_addr)) { return -1; } From c7fd412ec8fe9ed406e88e50902f423b46e0d29e Mon Sep 17 00:00:00 2001 From: john30 Date: Sun, 27 Oct 2024 10:23:54 +0100 Subject: [PATCH 289/345] updated version to 24.1 --- .github/ISSUE_TEMPLATE/bug_report.yml | 1 + ChangeLog.md | 4 ++-- VERSION | 2 +- contrib/alpine/APKBUILD | 2 +- contrib/alpine/APKBUILD.git | 2 +- contrib/archlinux/PKGBUILD | 2 +- contrib/archlinux/PKGBUILD.git | 2 +- contrib/html/openapi.yaml | 2 +- 8 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index c600af34b..5d5679c52 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,7 @@ body: description: the ebusd version in use options: - current source from git + - '24.1' - '23.3' - '23.2' - '23.1' diff --git a/ChangeLog.md b/ChangeLog.md index ee9dd613d..d4c754db7 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,4 +1,4 @@ -# next (tbd) +# 24.1 (2024-10-27) ## Bug Fixes * fix conditional messages not being sent to message definition in MQTT integration and not being used in KNX group association * fix CSV dump of config files on command line @@ -18,7 +18,7 @@ * add option to discover device via mDNS * add dedicated log level for device messages * add option to extend MQTT variables from env/cmdline -* add date+datetime mapping, better device update check, and removed single-field-message field names to Home Assistant MQTT discovery integration +* add date+datetime mapping, better device update check, and remove single-field-message field names in Home Assistant MQTT discovery integration ## Breaking Changes * change default config path to https://ebus.github.io/ serving files generated from new TypeSpec message definition sources diff --git a/VERSION b/VERSION index 99730abcf..27b21ff38 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -23.3 \ No newline at end of file +24.1 \ No newline at end of file diff --git a/contrib/alpine/APKBUILD b/contrib/alpine/APKBUILD index 38b93cc59..4338f9a66 100644 --- a/contrib/alpine/APKBUILD +++ b/contrib/alpine/APKBUILD @@ -1,7 +1,7 @@ # Contributor: Tim # Maintainer: John pkgname=ebusd -pkgver=23.3 +pkgver=24.1 pkgrel=0 pkgdesc="Daemon for communication with eBUS heating systems" url="https://github.com/john30/ebusd" diff --git a/contrib/alpine/APKBUILD.git b/contrib/alpine/APKBUILD.git index 45a020a71..997352111 100644 --- a/contrib/alpine/APKBUILD.git +++ b/contrib/alpine/APKBUILD.git @@ -1,6 +1,6 @@ # Maintainer: John pkgname=ebusd-git -pkgver=23.3 +pkgver=24.1 pkgrel=0 pkgdesc="Daemon for communication with eBUS heating systems" url="https://github.com/john30/ebusd" diff --git a/contrib/archlinux/PKGBUILD b/contrib/archlinux/PKGBUILD index 857a30b43..45455f9bd 100644 --- a/contrib/archlinux/PKGBUILD +++ b/contrib/archlinux/PKGBUILD @@ -2,7 +2,7 @@ # Contributor: Milan Knizek # Usage: makepkg pkgname=ebusd -pkgver=23.3 +pkgver=24.1 pkgrel=1 pkgdesc="ebusd, the daemon for communication with eBUS heating systems." arch=('i686' 'x86_64' 'armv6h' 'armv7h' 'aarch64') diff --git a/contrib/archlinux/PKGBUILD.git b/contrib/archlinux/PKGBUILD.git index 131483d98..a0694946c 100644 --- a/contrib/archlinux/PKGBUILD.git +++ b/contrib/archlinux/PKGBUILD.git @@ -3,7 +3,7 @@ # Usage: makepkg -p PKGBUILD.git pkgname=ebusd-git _gitname=ebusd -pkgver=23.3 +pkgver=24.1 pkgrel=1 pkgdesc="ebusd, the daemon for communication with eBUS heating systems." arch=('i686' 'x86_64' 'armv6h' 'armv7h' 'aarch64') diff --git a/contrib/html/openapi.yaml b/contrib/html/openapi.yaml index 26ec93a57..c69aee470 100644 --- a/contrib/html/openapi.yaml +++ b/contrib/html/openapi.yaml @@ -2,7 +2,7 @@ openapi: 3.1.0 info: title: ebusd-http description: The API that ebusd provides on HTTP port. - version: "23.3" + version: "24.1" servers: - url: http://127.0.0.1:8080/ paths: From 029aa27676f87a6c5b58bfb3f8630788c1cd881d Mon Sep 17 00:00:00 2001 From: john30 Date: Sun, 27 Oct 2024 12:42:07 +0100 Subject: [PATCH 290/345] updated to 24.1 --- contrib/alpine/APKBUILD | 2 +- contrib/archlinux/PKGBUILD | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/alpine/APKBUILD b/contrib/alpine/APKBUILD index 4338f9a66..836645687 100644 --- a/contrib/alpine/APKBUILD +++ b/contrib/alpine/APKBUILD @@ -27,5 +27,5 @@ package() { } sha512sums=" -acf155d36e99db1c9c2ff04eabbfddba8493f2566b6691f609c86af0e42e3cb0594618fd51e874e475cfc7b9c742d1e010099f38e19c21f52c953ebcfb0f2ea2 ebusd-23.3.tar.gz +a4ab5e21f345894c29b7af84f46f93ac4a3ee658d69ca7fb002d52233e8c041043df328212cbeae4f01220e7a6bf4ec8a26ad3757eb0cf6da157d237f5b6b0b6 ebusd-24.1.tar.gz " diff --git a/contrib/archlinux/PKGBUILD b/contrib/archlinux/PKGBUILD index 45455f9bd..4ef1f8214 100644 --- a/contrib/archlinux/PKGBUILD +++ b/contrib/archlinux/PKGBUILD @@ -41,4 +41,4 @@ package() { install -m 0644 contrib/etc/ebusd/mqtt-integration.cfg "${pkgdir}/etc/ebusd/mqtt-integration.cfg" } # update md5sums: updpkgsums -md5sums=('4702250aff5be67ac38ba81e1acae231') +md5sums=('ec614447defed756e586fdf4fd73647a') From c20399b6b3467aadfdd0e67348a749d98ced02ca Mon Sep 17 00:00:00 2001 From: John Date: Sun, 27 Oct 2024 12:58:05 +0100 Subject: [PATCH 291/345] wording --- src/ebusd/main_args.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ebusd/main_args.cpp b/src/ebusd/main_args.cpp index 12bb26461..2f2f17e2b 100755 --- a/src/ebusd/main_args.cpp +++ b/src/ebusd/main_args.cpp @@ -150,7 +150,7 @@ static const argDef argDefs[] = { {"configpath", 'c', "PATH", 0, "Read CSV config files from PATH (local folder or HTTPS URL) [" CONFIG_PATH "]"}, {"scanconfig", 's', "ADDR", af_optional, "Pick CSV config files matching initial scan ADDR: " - "empty for broadcast ident message (default when neither configpath nor dumpconfig is not given), " + "empty for broadcast ident message (default when neither configpath nor dumpconfig is given), " "\"none\" for no initial scan message, " "\"full\" for full scan, " "a single hex address to scan, or " From 46996c83a29657ca6f1151ad12c004b788d5bb3f Mon Sep 17 00:00:00 2001 From: John Date: Wed, 30 Oct 2024 21:04:40 +0100 Subject: [PATCH 292/345] add mdns hint --- contrib/docker/README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/contrib/docker/README.md b/contrib/docker/README.md index 92ef21e78..6b7983641 100644 --- a/contrib/docker/README.md +++ b/contrib/docker/README.md @@ -50,7 +50,12 @@ Using a network device ---------------------- When using a network device, the "--device" argument to docker can be omitted, but the device information has to be passed on to ebusd: -> docker run --rm -it -p 8888 john30/ebusd --scanconfig -d 192.168.178.123:10000 --latency=20 +> docker run --rm -it -p 8888 john30/ebusd --scanconfig -d ens:192.168.178.123 --latency=20 + +If mDNS device discovery is supposed to be used, then the container needs to run on the host network instead of the default bridge network, +as multicast traffic is usually only routed via the host network, i.e. use "--network=host" as additional argument. +Then the device argument can be omitted (as long as there is only one mDNS discoverable device on the net), e.g.: +> docker run --rm -it -p 8888 --network=host john30/ebusd --scanconfig --latency=20 Note: the required "-f" (foreground) argument is passed as environment variable and does not need to be specified anymore. From eeb8f2ecc0ec76d353d5219ee4762ac4ec12d1a0 Mon Sep 17 00:00:00 2001 From: John Date: Wed, 30 Oct 2024 21:05:52 +0100 Subject: [PATCH 293/345] install in /usr/bin and not /bin --- contrib/alpine/APKBUILD | 2 +- contrib/alpine/APKBUILD.git | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/alpine/APKBUILD b/contrib/alpine/APKBUILD index 836645687..fa94dad70 100644 --- a/contrib/alpine/APKBUILD +++ b/contrib/alpine/APKBUILD @@ -23,7 +23,7 @@ check() { } package() { - DESTDIR="$pkgdir" cmake --install build + DESTDIR="$pkgdir" cmake --install build --prefix /usr } sha512sums=" diff --git a/contrib/alpine/APKBUILD.git b/contrib/alpine/APKBUILD.git index 997352111..763cd9bad 100644 --- a/contrib/alpine/APKBUILD.git +++ b/contrib/alpine/APKBUILD.git @@ -29,7 +29,7 @@ check() { } package() { - DESTDIR="$pkgdir" cmake --install build + DESTDIR="$pkgdir" cmake --install build --prefix /usr } sha512sums=" From b315cb575a2fc4f7f691f2bcfd6b2e0306cb2121 Mon Sep 17 00:00:00 2001 From: John Date: Thu, 31 Oct 2024 07:49:45 +0100 Subject: [PATCH 294/345] formatting --- src/ebusd/knxhandler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ebusd/knxhandler.cpp b/src/ebusd/knxhandler.cpp index d160f18dc..ba7e53690 100644 --- a/src/ebusd/knxhandler.cpp +++ b/src/ebusd/knxhandler.cpp @@ -62,7 +62,7 @@ static const argDef g_knx_argDefs[] = { {"knxrage", O_AGR, "SEC", 0, "Maximum age in seconds for using the last value of read messages (0=disable)" " [5]"}, {"knxwage", O_AGW, "SEC", 0, "Maximum age in seconds for using the last value for reads on write messages" - " (0=disable), [99999999]"}, + " (0=disable) [99999999]"}, {"knxint", O_INT, "FILE", 0, "Read KNX integration settings from FILE [/etc/ebusd/knx.cfg]"}, {"knxvar", O_VAR, "NAME=VALUE[,...]", 0, "Add variable(s) to the read KNX integration settings"}, From bcea04b757a2f159c9a7a520ef8d4de1b0cd11d6 Mon Sep 17 00:00:00 2001 From: John Date: Thu, 31 Oct 2024 08:09:44 +0100 Subject: [PATCH 295/345] aligned with help text, missed special cases for env arg parsing --- contrib/docker/docker-compose.example.yaml | 41 ++++++++++++++-------- src/ebusd/main_args.cpp | 7 ++-- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/contrib/docker/docker-compose.example.yaml b/contrib/docker/docker-compose.example.yaml index 95f3669f4..7c76777af 100644 --- a/contrib/docker/docker-compose.example.yaml +++ b/contrib/docker/docker-compose.example.yaml @@ -14,8 +14,15 @@ services: environment: # Device options: - # Use DEV as eBUS device ("enh:DEVICE" or "enh:IP:PORT" for enhanced device, "ens:DEVICE" for enhanced high speed - # serial device, "DEVICE" for serial device, or "[udp:]IP:PORT" for network device) + # Use DEV as eBUS device: + # - "mdns:" for auto discovery via mDNS with optional suffix "[ID][@INTF]" for using a specific hardware ID + # and/or IP interface INTF for the discovery (only for eBUS Adapter Shield; on docker, the network device + # needs to support multicast routing e.g. like the host network), or + # - prefix "ens:" for enhanced high speed device, + # - prefix "enh:" for enhanced device, or + # - no prefix for plain device, and + # - suffix "IP[:PORT]" for network device, or + # - suffix "DEVICE" for serial device EBUSD_DEVICE: "ens:/dev/ttyUSB0" # Skip serial eBUS device test #EBUSD_NODEVICECHECK: "" @@ -30,9 +37,15 @@ services: # Read CSV config files from PATH (local folder or HTTPS URL) #EBUSD_CONFIGPATH: "/path/to/local/configs" - # Pick CSV config files matching initial scan (ADDR="none" or empty for no initial scan message, "full" for full - # scan, or a single hex address to scan, default is broadcast ident message). + # Pick CSV config files matching initial scan. + # - empty for broadcast ident message (default when EBUSD_CONFIGPATH is not given), + # - "none" for no initial scan message, + # - "full" for full scan, + # - a single hex address to scan, or + # - "off" for not picking CSV files by scan result (default when EBUSD_CONFIGPATH is given). EBUSD_SCANCONFIG: "" + # Retry scanning devices COUNT times + #EBUSD_SCANRETRIES: 5 # Prefer LANG in multilingual configuration files #EBUSD_CONFIGLANG: "en" # Poll for data every SEC seconds (0=disable) @@ -44,8 +57,8 @@ services: # eBUS options: - # Use ADDR as own bus address - #EBUSD_ADDRESS: ff + # Use hex ADDR as own master bus address + #EBUSD_ADDRESS: "ff" # Actively answer to requests from other masters #EBUSD_ANSWER: "" # Stop bus acquisition after MSEC ms @@ -67,7 +80,7 @@ services: #EBUSD_ACCESSLEVEL: "*" # Read access control list from FILE #EBUSD_ACLFILE: "/path/to/aclfile" - # Enable hex command + # Enable hex/inject/answer commands #EBUSD_ENABLEHEX: "" # Enable define command #EBUSD_ENABLEDEFINE: "" @@ -88,12 +101,12 @@ services: # Write log to FILE (only for daemon, empty string for using syslog) #EBUSD_LOGFILE: "/var/log/ebusd.log" - # Only write log for matching AREA(S) below or equal to LEVEL (alternative to EBUSD_LOGAREAS/EBUSD_LOGLEVEL, may - # be used multiple times) + # Only write log for matching AREA(S) up to LEVEL (alternative to EBUSD_LOGAREAS/EBUSD_LOGLEVEL, may be used + # multiple times) #EBUSD_LOG: "all:notice" - # Only write log for matching AREA(S): main|network|bus|update|other|all + # Only write log for matching AREA(S): main|network|bus|device|update|other|all #EBUSD_LOGAREAS: "all" - # Only write log below or equal to LEVEL: error|notice|info|debug + # Only write log up to LEVEL: error|notice|info|debug #EBUSD_LOGLEVEL: "notice" # Raw logging options: @@ -138,8 +151,8 @@ services: #EBUSD_MQTTQOS: 0 # Read MQTT integration settings from FILE (no default) #EBUSD_MQTTINT: "/etc/ebusd/mqtt-hassio.cfg" - # Add variable(s) to the read MQTT integration settings - #EBUSD_MQTTVAR: "key=value" + # Add variable(s) to the read MQTT integration settings (append to already existing value with "NAME+=VALUE") + #EBUSD_MQTTVAR: "key[+]=value[,...]" # Publish in JSON format instead of strings, optionally in short (value directly below field key) #EBUSD_MQTTJSON: "" # Publish all available attributes @@ -169,7 +182,7 @@ services: #EBUSD_KNXURL: "" # Maximum age in seconds for using the last value of read messages (0=disable) #EBUSD_KNXRAGE: 30 - # Maximum age in seconds for using the last value for reads on write messages (0=disable), + # Maximum age in seconds for using the last value for reads on write messages (0=disable) #EBUSD_KNXWAGE: 7200 # Read KNX integration settings from FILE #EBUSD_KNXINT: "/etc/ebusd/knx.cfg" diff --git a/src/ebusd/main_args.cpp b/src/ebusd/main_args.cpp index 2f2f17e2b..caf5e4bba 100755 --- a/src/ebusd/main_args.cpp +++ b/src/ebusd/main_args.cpp @@ -135,7 +135,8 @@ static const argDef argDefs[] = { {nullptr, 0, nullptr, 0, "Device options:"}, {"device", 'd', "DEV", 0, "Use DEV as eBUS device [mdns:]\n" "- \"mdns:\" for auto discovery via mDNS with optional suffix \"[ID][@INTF]\" for using a specific" - " hardware ID and/or IP interface INTF for the discovery (only for eBUS Adapter Shield), or\n" + " hardware ID and/or IP interface INTF for the discovery (only for eBUS Adapter Shield;" + " on docker, the network device needs to support multicast routing e.g. like the host network), or\n" "- prefix \"ens:\" for enhanced high speed device,\n" "- prefix \"enh:\" for enhanced device, or\n" "- no prefix for plain device, and\n" @@ -650,7 +651,9 @@ int parse_main_args(int argc, char* argv[], char* envp[], options_t *opt) { char* envargv[] = {argv[0], envname, pos+1}; int cnt = pos[1] ? 2 : 1; if (pos[1] && strlen(*env) < sizeof(envname)-3 - && (strcmp(envopt, "scanconfig") == 0 || strcmp(envopt, "lograwdata") == 0)) { + && (strcmp(envopt, "scanconfig") == 0 || strcmp(envopt, "lograwdata") == 0 + || strcmp(envopt, "mqttjson") == 0 || strcmp(envopt, "knxurl") == 0 + )) { // only really special case: af_optional with non-empty arg needs to use "=" syntax cnt = 1; strcat(envopt, pos); From 8d2986c58f762fccb716dad753f281f71abe388a Mon Sep 17 00:00:00 2001 From: John Date: Mon, 11 Nov 2024 20:36:56 +0100 Subject: [PATCH 296/345] fix for symlinks with colon (fixes #1374) --- src/lib/ebus/protocol.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/ebus/protocol.cpp b/src/lib/ebus/protocol.cpp index f74450a96..70123479d 100644 --- a/src/lib/ebus/protocol.cpp +++ b/src/lib/ebus/protocol.cpp @@ -70,7 +70,8 @@ ProtocolHandler* ProtocolHandler::create(const ebus_protocol_config_t config, } } Transport* transport; - if (strchr(name, '/') == nullptr || strchr(name, ':') != nullptr) { + // symlink device name may contain colon, so only check for absence of slash only + if (strchr(name, '/') == nullptr) { char* in = strdup(name); bool udp = false; char* addrpos = in; @@ -97,6 +98,7 @@ ProtocolHandler* ProtocolHandler::create(const ebus_protocol_config_t config, transport = new NetworkTransport(name, config.extraLatency, hostOrIp, port, udp); } else { // support ens:/dev/, enh:/dev/, and /dev/ + // as well as symlinks like /dev/serial/by-id/...Espressif_00:01:02:03... transport = new SerialTransport(name, config.extraLatency, !config.noDeviceCheck, speed); } Device* device; From 52af010493f5bfe9dc9e233de08a07378e65ffef Mon Sep 17 00:00:00 2001 From: John Date: Wed, 13 Nov 2024 07:14:59 +0100 Subject: [PATCH 297/345] fix read/write response on tcp client --- src/ebusd/mainloop.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index 419b8f45e..7de1313b0 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -805,7 +805,7 @@ result_t MainLoop::executeRead(const vector& args, const string& levels, ret = message->storeLastData(master, slave); ostringstream result; if (ret == RESULT_OK) { - ret = message->decodeLastData(pt_any, false, nullptr, -1, OF_NONE, &result); + ret = message->decodeLastData(pt_slaveData, false, nullptr, -1, OF_NONE, &result); } if (ret >= RESULT_OK) { logInfo(lf_main, "read hex %s %s cache update: %s", message->getCircuit().c_str(), message->getName().c_str(), @@ -876,7 +876,7 @@ result_t MainLoop::executeRead(const vector& args, const string& levels, if (verbosity & OF_NAMES) { *ostream << cacheMessage->getCircuit() << " " << cacheMessage->getName() << " "; } - ret = cacheMessage->decodeLastData(pt_any, false, fieldIndex == -2 ? nullptr : fieldName.c_str(), fieldIndex, + ret = cacheMessage->decodeLastData(pt_slaveData, false, fieldIndex == -2 ? nullptr : fieldName.c_str(), fieldIndex, verbosity, ostream); if (ret < RESULT_OK) { logError(lf_main, "read %s %s cached: decode %s", cacheMessage->getCircuit().c_str(), @@ -916,7 +916,7 @@ result_t MainLoop::executeRead(const vector& args, const string& levels, if (verbosity & OF_NAMES) { *ostream << message->getCircuit() << " " << message->getName() << " "; } - ret = message->decodeLastData(pt_any, false, fieldIndex == -2 ? nullptr : fieldName.c_str(), fieldIndex, verbosity, + ret = message->decodeLastData(pt_slaveData, false, fieldIndex == -2 ? nullptr : fieldName.c_str(), fieldIndex, verbosity, ostream); if (ret < RESULT_OK) { logError(lf_main, "read %s %s: decode %s", message->getCircuit().c_str(), message->getName().c_str(), @@ -1050,7 +1050,7 @@ result_t MainLoop::executeWrite(const vector& args, const string levels, ret = message->storeLastData(master, slave); ostringstream result; if (ret == RESULT_OK) { - ret = message->decodeLastData(pt_any, false, nullptr, -1, verbosity, &result); + ret = message->decodeLastData(pt_slaveData, false, nullptr, -1, verbosity, &result); } if (ret >= RESULT_OK) { logInfo(lf_main, "write hex %s %s cache update: %s", message->getCircuit().c_str(), @@ -1113,7 +1113,7 @@ result_t MainLoop::executeWrite(const vector& args, const string levels, } dstAddress = message->getLastMasterData()[1]; - ret = message->decodeLastData(pt_any, false, nullptr, -1, verbosity, ostream); // decode data + ret = message->decodeLastData(pt_slaveData, false, nullptr, -1, verbosity, ostream); // decode data if (ret < RESULT_OK) { logError(lf_main, "write %s %s: decode %s", message->getCircuit().c_str(), message->getName().c_str(), getResultCode(ret)); From 2790860607d162ed06f03a8e8ac2e60ecceda7de Mon Sep 17 00:00:00 2001 From: John Date: Tue, 26 Nov 2024 20:39:21 +0100 Subject: [PATCH 298/345] updated [akip ci] --- ChangeLog.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index d4c754db7..e3a6e2e70 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,3 +1,9 @@ +# next (tbd) +## Bug Fixes +* fix for device string symlink with colon +* fix "read" and "write" command response + + # 24.1 (2024-10-27) ## Bug Fixes * fix conditional messages not being sent to message definition in MQTT integration and not being used in KNX group association From 85a3eef0a6f2a2b5fd36d1e1f6ff88563f951bd2 Mon Sep 17 00:00:00 2001 From: John Date: Thu, 12 Dec 2024 21:56:38 +0100 Subject: [PATCH 299/345] fix dump of divisor --- ChangeLog.md | 1 + src/ebusd/bushandler.cpp | 2 +- src/ebusd/main.cpp | 2 +- src/ebusd/mainloop.cpp | 2 +- src/lib/ebus/data.cpp | 2 +- src/lib/ebus/datatype.cpp | 32 +++++++++++++++--------------- src/lib/ebus/datatype.h | 18 +++++++++++------ src/lib/ebus/test/test_message.cpp | 2 ++ 8 files changed, 35 insertions(+), 26 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index e3a6e2e70..83ac7ccda 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -2,6 +2,7 @@ ## Bug Fixes * fix for device string symlink with colon * fix "read" and "write" command response +* fix dump of divisor # 24.1 (2024-10-27) diff --git a/src/ebusd/bushandler.cpp b/src/ebusd/bushandler.cpp index 5dc2e0609..2db31899b 100644 --- a/src/ebusd/bushandler.cpp +++ b/src/ebusd/bushandler.cpp @@ -191,7 +191,7 @@ bool decodeType(const DataType* type, const SymbolString& input, size_t length, first = false; *output << endl << " "; ostringstream::pos_type cnt = output->tellp(); - type->dump(OF_NONE, length, false, output); + type->dump(OF_NONE, length, ad_none, output); cnt = output->tellp() - cnt; while (cnt < 5) { *output << " "; diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index 43437de52..b5edc5647 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -394,7 +394,7 @@ int main(int argc, char* argv[], char* envp[]) { } if (s_opt.dumpConfig & OF_JSON) { *out << "{\"datatypes\":["; - DataTypeList::getInstance()->dump(s_opt.dumpConfig, true, out); + DataTypeList::getInstance()->dump(s_opt.dumpConfig, out); *out << "],\"templates\":["; const auto tmpl = s_scanHelper->getTemplates(""); tmpl->dump(s_opt.dumpConfig, out); diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index 7de1313b0..ab2f66b3c 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -2279,7 +2279,7 @@ result_t MainLoop::executeGet(const vector& args, bool* connected, ostri if (uri == "/datatypes") { *ostream << "["; OutputFormat verbosity = OF_NAMES|OF_JSON|OF_ALL_ATTRS; - DataTypeList::getInstance()->dump(verbosity, true, ostream); + DataTypeList::getInstance()->dump(verbosity, ostream); *ostream << "\n]"; type = 6; *connected = false; diff --git a/src/lib/ebus/data.cpp b/src/lib/ebus/data.cpp index ca7c36d58..9f2361664 100644 --- a/src/lib/ebus/data.cpp +++ b/src/lib/ebus/data.cpp @@ -555,7 +555,7 @@ void SingleDataField::dumpPrefix(bool prependFieldSeparator, OutputFormat output } *output << FIELD_SEPARATOR; } - m_dataType->dump(outputFormat, m_length, true, output); + m_dataType->dump(outputFormat, m_length, ad_normal, output); } void SingleDataField::dumpSuffix(OutputFormat outputFormat, ostream* output) const { diff --git a/src/lib/ebus/datatype.cpp b/src/lib/ebus/datatype.cpp index 021ec65ef..3f1d6b125 100755 --- a/src/lib/ebus/datatype.cpp +++ b/src/lib/ebus/datatype.cpp @@ -124,7 +124,7 @@ uint16_t floatToUint16(float value) { return static_cast((shift << 11) | (negative ? 0x8000 | (0x800-sig) : sig)); } -bool DataType::dump(OutputFormat outputFormat, size_t length, bool appendDivisor, ostream* output) const { +bool DataType::dump(OutputFormat outputFormat, size_t length, AppendDivisor appendDivisor, ostream* output) const { if (outputFormat & OF_JSON) { *output << "\"type\": \"" << m_id << "\", \"isbits\": " << (getBitCount() < 8 ? "true" : "false"); @@ -149,7 +149,7 @@ bool DataType::dump(OutputFormat outputFormat, size_t length, bool appendDivisor *output << static_cast(length); } } - if (appendDivisor) { + if (appendDivisor != ad_none) { *output << FIELD_SEPARATOR; } } @@ -157,7 +157,7 @@ bool DataType::dump(OutputFormat outputFormat, size_t length, bool appendDivisor } -bool StringDataType::dump(OutputFormat outputFormat, size_t length, bool appendDivisor, ostream* output) const { +bool StringDataType::dump(OutputFormat outputFormat, size_t length, AppendDivisor appendDivisor, ostream* output) const { DataType::dump(outputFormat, length, appendDivisor, output); if ((outputFormat & OF_JSON) && (outputFormat & OF_ALL_ATTRS)) { *output << ", \"result\": \"" << (isIgnored() ? "void" : "string") << "\""; @@ -300,7 +300,7 @@ result_t StringDataType::writeSymbols(size_t offset, size_t length, istringstrea } -bool DateTimeDataType::dump(OutputFormat outputFormat, size_t length, bool appendDivisor, ostream* output) const { +bool DateTimeDataType::dump(OutputFormat outputFormat, size_t length, AppendDivisor appendDivisor, ostream* output) const { DataType::dump(outputFormat, length, appendDivisor, output); if ((outputFormat & OF_JSON) && (outputFormat & OF_ALL_ATTRS)) { *output << ", \"result\": \"" << (hasDate() ? hasTime() ? "datetime" : "date" : "time") << "\""; @@ -672,7 +672,7 @@ size_t NumberDataType::calcPrecision(int divisor) { return precision; } -bool NumberDataType::dump(OutputFormat outputFormat, size_t length, bool appendDivisor, ostream* output) const { +bool NumberDataType::dump(OutputFormat outputFormat, size_t length, AppendDivisor appendDivisor, ostream* output) const { if (m_bitCount < 8) { DataType::dump(outputFormat, m_bitCount, appendDivisor, output); } else { @@ -681,11 +681,17 @@ bool NumberDataType::dump(OutputFormat outputFormat, size_t length, bool appendD if ((outputFormat & OF_JSON) && (outputFormat & OF_ALL_ATTRS)) { *output << ", \"result\": \"number\""; } - if (!appendDivisor) { + if (appendDivisor == ad_none) { return false; } bool ret = false; - if (m_baseType) { + if (appendDivisor == ad_full && m_divisor != 1) { + if (outputFormat & OF_JSON) { + *output << ", \"divisor\": "; + } + *output << m_divisor; + ret = true; + } else if (m_baseType) { if (m_baseType->m_divisor != m_divisor) { if (outputFormat & OF_JSON) { *output << ", \"divisor\": "; @@ -693,12 +699,6 @@ bool NumberDataType::dump(OutputFormat outputFormat, size_t length, bool appendD *output << (m_divisor / m_baseType->m_divisor); ret = true; } - } else if (m_divisor != 1) { - if (outputFormat & OF_JSON) { - *output << ", \"divisor\": "; - } - *output << m_divisor; - ret = true; } if (ret && (outputFormat & OF_JSON) && (outputFormat & OF_ALL_ATTRS)) { *output << ", \"precision\": " << static_cast(getPrecision()); @@ -1317,7 +1317,7 @@ DataTypeList* DataTypeList::getInstance() { return &s_instance; } -void DataTypeList::dump(OutputFormat outputFormat, bool appendDivisor, ostream* output) const { +void DataTypeList::dump(OutputFormat outputFormat, ostream* output) const { bool json = outputFormat & OF_JSON; string sep = "\n"; for (const auto &it : m_typesById) { @@ -1329,9 +1329,9 @@ void DataTypeList::dump(OutputFormat outputFormat, bool appendDivisor, ostream* *output << sep << " {"; } if ((dataType->getBitCount() % 8) != 0) { - dataType->dump(outputFormat, dataType->getBitCount(), appendDivisor, output); + dataType->dump(outputFormat, dataType->getBitCount(), ad_full, output); } else { - dataType->dump(outputFormat, dataType->getBitCount() / 8, appendDivisor, output); + dataType->dump(outputFormat, dataType->getBitCount() / 8, ad_full, output); } if (json) { *output << "}"; diff --git a/src/lib/ebus/datatype.h b/src/lib/ebus/datatype.h index 013d2f702..f6bd1a23a 100755 --- a/src/lib/ebus/datatype.h +++ b/src/lib/ebus/datatype.h @@ -137,6 +137,13 @@ constexpr inline enum OutputFormat operator~ (enum OutputFormat self) { return (enum OutputFormat)(~(OutputFormatBaseType)self); } +/** whether divisor should be appended to a dump. */ +enum AppendDivisor { + ad_none, //!< no dump of divisor + ad_normal, //!< regular dump of divisor (i.e. not for base types) + ad_full, //!< full dump of divisor (i.e. also for base types) +}; + /** the message part in which a data field is stored. */ enum PartType { pt_any, //!< stored in any data (master or slave) @@ -286,7 +293,7 @@ class DataType { * @param output the @a ostream to dump to. * @return true when a non-default divisor was written to the output. */ - virtual bool dump(OutputFormat outputFormat, size_t length, bool appendDivisor, ostream* output) const; + virtual bool dump(OutputFormat outputFormat, size_t length, AppendDivisor appendDivisor, ostream* output) const; /** * Internal method for reading the numeric raw value from a @a SymbolString. @@ -363,7 +370,7 @@ class StringDataType : public DataType { virtual ~StringDataType() {} // @copydoc - bool dump(OutputFormat outputFormat, size_t length, bool appendDivisor, ostream* output) const override; + bool dump(OutputFormat outputFormat, size_t length, AppendDivisor appendDivisor, ostream* output) const override; // @copydoc result_t readRawValue(size_t offset, size_t length, const SymbolString& input, @@ -410,7 +417,7 @@ class DateTimeDataType : public DataType { virtual ~DateTimeDataType() {} // @copydoc - bool dump(OutputFormat outputFormat, size_t length, bool appendDivisor, ostream* output) const override; + bool dump(OutputFormat outputFormat, size_t length, AppendDivisor appendDivisor, ostream* output) const override; /** * @return true if date part is present. @@ -503,7 +510,7 @@ class NumberDataType : public DataType { static size_t calcPrecision(int divisor); // @copydoc - bool dump(OutputFormat outputFormat, size_t length, bool appendDivisor, ostream* output) const override; + bool dump(OutputFormat outputFormat, size_t length, AppendDivisor appendDivisor, ostream* output) const override; /** * Derive a new @a NumberDataType from this. @@ -658,10 +665,9 @@ class DataTypeList { /** * Dump the type list optionally including the divisor to the output. * @param outputFormat the @a OutputFormat options. - * @param appendDivisor whether to append the divisor (if available). * @param output the @a ostream to dump to. */ - void dump(OutputFormat outputFormat, bool appendDivisor, ostream* output) const; + void dump(OutputFormat outputFormat, ostream* output) const; /** * Removes all @a DataType instances. diff --git a/src/lib/ebus/test/test_message.cpp b/src/lib/ebus/test/test_message.cpp index ea5ceb446..dda4ee90a 100644 --- a/src/lib/ebus/test/test_message.cpp +++ b/src/lib/ebus/test/test_message.cpp @@ -221,6 +221,8 @@ int main() { " ]\n" " }: \n" " \"field\": {\"value\": 42, \"raw\": [42]}", "ff75b509030d0100", "012a", "jNr"}, + {"r,CIRCUIT,NAME,COMMENT,,,,0100,field,,temp", "r,cirCIRCUITcuit,naNAMEme,comCOMMENTment,ff,75,b509,0d0100,field,s,D2C,,°C,Temperatur: field=42.00 °C [Temperatur]", "ff75b509030d0100", "02a002", "DN"}, + {"r,CIRCUIT,NAME,COMMENT,,,,0100,field,,D2C,,°C,Temperatur", "r,cirCIRCUITcuit,naNAMEme,comCOMMENTment,ff,75,b509,0d0100,field,s,D2C,,°C,Temperatur: field=42.00 °C [Temperatur]", "ff75b509030d0100", "02a002", "DN"}, }; templates = new DataFieldTemplates(); unsigned int lineNo = 0; From 1fb10f0c664c185055d36ec2d04f26a916174d92 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 22 Dec 2024 08:16:15 +0100 Subject: [PATCH 300/345] corrected+enhanced docs --- src/lib/ebus/contrib/tem.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/lib/ebus/contrib/tem.cpp b/src/lib/ebus/contrib/tem.cpp index f1dacc59b..ff1e22b16 100755 --- a/src/lib/ebus/contrib/tem.cpp +++ b/src/lib/ebus/contrib/tem.cpp @@ -72,9 +72,11 @@ result_t TemParamDataType::readSymbols(size_t offset, size_t length, const Symbo } int grp = 0, num = 0; if (input.isMaster()) { - grp = (value & 0x1f); // grp in bits 0...5 - num = ((value >> 8) & 0x7f); // num in bits 8...13 + // bits are distributed like this in 2 bytes: xNNN NNNN xxxG GGGG + grp = (value & 0x1f); // grp in bits 0...4 + num = ((value >> 8) & 0x7f); // num in bits 8...14 } else { + // bits are distributed like this in 2 bytes: xxxx GGGG GNNN NNNN grp = ((value >> 7) & 0x1f); // grp in bits 7...11 num = (value & 0x7f); // num in bits 0...6 } @@ -126,8 +128,10 @@ result_t TemParamDataType::writeSymbols(const size_t offset, const size_t length return RESULT_ERR_OUT_OF_RANGE; // value out of range } if (output->isMaster()) { - value = grp | (num << 8); // grp in bits 0...5, num in bits 8...13 + // bits are distributed like this in 2 bytes: xNNN NNNN xxxG GGGG + value = grp | (num << 8); // grp in bits 0...4, num in bits 8...14 } else { + // bits are distributed like this in 2 bytes: xxxx GGGG GNNN NNNN value = (grp << 7) | num; // grp in bits 7...11, num in bits 0...6 } } From da3a7045c2b4f1f0c09ab999ae207934d39a94aa Mon Sep 17 00:00:00 2001 From: John Date: Tue, 24 Dec 2024 11:48:32 +0100 Subject: [PATCH 301/345] enhance encode+decode commands --- src/ebusd/mainloop.cpp | 52 ++++++++++++++++++++++++++++++------------ src/lib/ebus/data.h | 8 +++++-- 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index ab2f66b3c..c10191c00 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -1719,6 +1719,7 @@ result_t MainLoop::executeDefine(const vector& args, ostringstream* ostr result_t MainLoop::executeDecode(const vector& args, ostringstream* ostream) { size_t argPos = 1; OutputFormat verbosity = OF_NONE; + bool toMaster = false; while (args.size() > argPos && args[argPos][0] == '-') { if (args[argPos] == "-v") { if ((verbosity & VERBOSITY_3) == VERBOSITY_0) { @@ -1732,6 +1733,8 @@ result_t MainLoop::executeDecode(const vector& args, ostringstream* ostr verbosity |= VERBOSITY_2; } else if (args[argPos] == "-vvv" || args[argPos] == "-V") { verbosity |= VERBOSITY_3; + } else if (args[argPos] == "-m") { + toMaster = true; } else if (args[argPos] == "-n") { verbosity = (verbosity & ~OF_VALUENAME) | OF_NUMERIC; } else if (args[argPos] == "-N") { @@ -1748,10 +1751,11 @@ result_t MainLoop::executeDecode(const vector& args, ostringstream* ostr if (argPos == 0 || args.size() != argPos + 2) { *ostream << - "usage: decode [-v|-V] [-n|-N] DEFINITION DD[DD]*\n" + "usage: decode [-v|-V] [-m] [-n|-N] DEFINITION DD[DD]*\n" " Decode field(s) by definition and hex data.\n" " -v increase verbosity (include names/units/comments)\n" " -V be very verbose (include names, units, and comments)\n" + " -m target a master instead of a slave\n" " -n use numeric value of value=name pairs\n" " -N use numeric and named value of value=name pairs\n" " DEFINITION field definition (type,divisor/values,unit,comment,...)\n" @@ -1764,28 +1768,45 @@ result_t MainLoop::executeDecode(const vector& args, ostringstream* ostr istringstream defstr("#\n" + args[argPos]); // ensure first line is not used for determining col names string errorDescription; DataFieldTemplates* templates = m_scanHelper->getTemplates("*"); - LoadableDataFieldSet fields("", templates); + LoadableDataFieldSet fields("", templates, toMaster); result_t ret = fields.readFromStream(&defstr, "temporary", now, true, nullptr, &errorDescription); if (ret != RESULT_OK) { return ret; } - SlaveSymbolString slave; - slave.push_back(0); // dummy length - ret = slave.parseHex(args[argPos+1]); + SlaveSymbolString sdata; + MasterSymbolString mdata; + SymbolString* data = toMaster ? &mdata : (SymbolString*)&sdata; + data->adjustHeader(); + ret = data->parseHex(args[argPos+1]); if (ret != RESULT_OK) { return ret; } - slave.adjustHeader(); - return fields.read(slave, 0, false, nullptr, -1, verbosity, -1, ostream); + data->adjustHeader(); + return fields.read(*data, 0, false, nullptr, -1, verbosity, -1, ostream); } result_t MainLoop::executeEncode(const vector& args, ostringstream* ostream) { size_t argPos = 1; - if (argPos == 0 || args.size() != argPos + 2) { + bool toMaster = false; + while (args.size() > argPos && args[argPos][0] == '-') { + if (args[argPos] == "-m") { + toMaster = true; + } else { + argPos = 0; // print usage + break; + } + argPos++; + } + if (args.size() != argPos + 2) { + argPos = 0; // print usage + } + + if (argPos == 0) { *ostream << - "usage: encode DEFINITION VALUE[;VALUE]*\n" + "usage: encode [-m] DEFINITION VALUE[;VALUE]*\n" " Encode field(s) by definition and decoded value(s).\n" + " -m target a master instead of a slave\n" " DEFINITION field definition (type,divisor/values,unit,comment,...)\n" " VALUE single field VALUE to encode"; return RESULT_OK; @@ -1796,18 +1817,21 @@ result_t MainLoop::executeEncode(const vector& args, ostringstream* ostr istringstream defstr("#\n" + args[argPos]); // ensure first line is not used for determining col names string errorDescription; DataFieldTemplates* templates = m_scanHelper->getTemplates("*"); - LoadableDataFieldSet fields("", templates); + LoadableDataFieldSet fields("", templates, toMaster); result_t ret = fields.readFromStream(&defstr, "temporary", now, true, nullptr, &errorDescription); if (ret != RESULT_OK) { return ret; } istringstream datastr(args[argPos+1]); - SlaveSymbolString slave; - ret = fields.write(UI_FIELD_SEPARATOR, 0, &datastr, &slave, nullptr); + SlaveSymbolString sdata; + MasterSymbolString mdata; + SymbolString* data = toMaster ? &mdata : (SymbolString*)&sdata; + data->adjustHeader(); + ret = fields.write(UI_FIELD_SEPARATOR, 0, &datastr, data, nullptr); if (ret != RESULT_OK) { return ret; } - *ostream << slave.getStr(1); + *ostream << data->getStr(toMaster ? 5 : 1); return ret; } @@ -2375,7 +2399,7 @@ result_t MainLoop::executeGet(const vector& args, bool* connected, ostri istringstream defstr("#\n" + def); // ensure first line is not used for determining col names string errorDescription; DataFieldTemplates* templates = m_scanHelper->getTemplates("*"); - LoadableDataFieldSet fields("", templates); + LoadableDataFieldSet fields("", templates, false); ret = fields.readFromStream(&defstr, "temporary", now, true, nullptr, &errorDescription); if (ret == RESULT_OK && fields.size()) { SlaveSymbolString slave; diff --git a/src/lib/ebus/data.h b/src/lib/ebus/data.h index f36fc0588..ed540d612 100755 --- a/src/lib/ebus/data.h +++ b/src/lib/ebus/data.h @@ -792,9 +792,10 @@ class LoadableDataFieldSet : public DataFieldSet, public MappedFileReader { * Constructs a new instance. * @param name the field name. * @param templates the @a DataFieldTemplates instance to use. + * @param isWrite true for a write message, false for read. */ - LoadableDataFieldSet(const string& name, DataFieldTemplates* templates) - : DataFieldSet(name, vector()), MappedFileReader(false), m_templates(templates) { + LoadableDataFieldSet(const string& name, DataFieldTemplates* templates, bool isWrite) + : DataFieldSet(name, vector()), MappedFileReader(false), m_templates(templates), m_isWrite(isWrite) { } // @copydoc @@ -807,6 +808,9 @@ class LoadableDataFieldSet : public DataFieldSet, public MappedFileReader { private: /** the @a DataFieldTemplates instance to use. */ DataFieldTemplates* m_templates; + + /** true for a write message, false for read. */ + bool m_isWrite; }; From 692ed8a6102ed86fdf6d525fbaf44aa182d5a37b Mon Sep 17 00:00:00 2001 From: John Date: Wed, 25 Dec 2024 10:38:44 +0100 Subject: [PATCH 302/345] add argFind and use it for more generic special args handling from env, improve help text --- src/ebusd/main_args.cpp | 25 +++++++++++++++---------- src/lib/utils/arg.cpp | 26 ++++++++++++++++++++++++++ src/lib/utils/arg.h | 15 ++++++++++++--- 3 files changed, 53 insertions(+), 13 deletions(-) diff --git a/src/ebusd/main_args.cpp b/src/ebusd/main_args.cpp index caf5e4bba..250fb7d63 100755 --- a/src/ebusd/main_args.cpp +++ b/src/ebusd/main_args.cpp @@ -130,6 +130,8 @@ static string s_configPath = CONFIG_PATH; #define O_DMPFLU (O_DMPSIZ-1) #define O_INJPOS 0x100 +#define ARG_NO_ENV (af_max << 1) + /** the definition of the known program arguments. */ static const argDef argDefs[] = { {nullptr, 0, nullptr, 0, "Device options:"}, @@ -156,17 +158,17 @@ static const argDef argDefs[] = { "\"full\" for full scan, " "a single hex address to scan, or " "\"off\" for not picking CSV files by scan result (default when configpath is given).\n" - "If combined with --checkconfig, you can add scan message data as " + "If combined with --checkconfig and --inject, you can add scan message data as " "arguments for checking a particular scan configuration, e.g. \"FF08070400/0AB5454850303003277201\"."}, {"scanretries", O_SCNRET, "COUNT", 0, "Retry scanning devices COUNT times [5]"}, {"configlang", O_CFGLNG, "LANG", 0, "Prefer LANG in multilingual configuration files [system default language, DE as fallback]"}, - {"checkconfig", O_CHKCFG, nullptr, 0, "Check config files, then stop"}, - {"dumpconfig", O_DMPCFG, "FORMAT", af_optional, + {"checkconfig", O_CHKCFG, nullptr, ARG_NO_ENV, "Check config files, then stop"}, + {"dumpconfig", O_DMPCFG, "FORMAT", af_optional|ARG_NO_ENV, "Check and dump config files in FORMAT (\"json\" or \"csv\"), then stop"}, {"dumpconfigto", O_DMPCTO, "FILE", 0, "Dump config files to FILE"}, {"pollinterval", O_POLINT, "SEC", 0, "Poll for data every SEC seconds (0=disable) [5]"}, - {"inject", 'i', "stop", af_optional, "Inject remaining arguments as commands or already seen messages " + {"inject", 'i', "stop", af_optional|ARG_NO_ENV, "Inject remaining arguments as commands or already seen messages " "(e.g. \"FF08070400/0AB5454850303003277201\"), optionally stop afterwards"}, {nullptr, O_INJPOS, "INJECT", af_optional|af_multiple, "Commands and/or messages to inject " "(if --inject was given)"}, @@ -642,18 +644,21 @@ int parse_main_args(int argc, char* argv[], char* envp[], options_t *opt) { } envopt[len] = 0; if (strcmp(envopt, "version") == 0 || strcmp(envopt, "image") == 0 || strcmp(envopt, "arch") == 0 - || strcmp(envopt, "opts") == 0 || strcmp(envopt, "inject") == 0 - || strcmp(envopt, "checkconfig") == 0 || strncmp(envopt, "dumpconfig", 10) == 0 + || strcmp(envopt, "opts") == 0 ) { - // ignore those defined in Dockerfile, EBUSD_OPTS, those with final args, and interactive ones + // ignore those defined in Dockerfile, EBUSD_OPTS + continue; + } + const argDef* found = argFind(&parseOpt, envopt); + if (found && found->flags & ARG_NO_ENV) { + // ignore those with final args and interactive ones continue; } char* envargv[] = {argv[0], envname, pos+1}; int cnt = pos[1] ? 2 : 1; if (pos[1] && strlen(*env) < sizeof(envname)-3 - && (strcmp(envopt, "scanconfig") == 0 || strcmp(envopt, "lograwdata") == 0 - || strcmp(envopt, "mqttjson") == 0 || strcmp(envopt, "knxurl") == 0 - )) { + && found && found->flags & af_optional + ) { // only really special case: af_optional with non-empty arg needs to use "=" syntax cnt = 1; strcat(envopt, pos); diff --git a/src/lib/utils/arg.cpp b/src/lib/utils/arg.cpp index afb972b8a..53e52ecff 100755 --- a/src/lib/utils/arg.cpp +++ b/src/lib/utils/arg.cpp @@ -398,4 +398,30 @@ void argHelp(const char* name, const argParseOpt *parseOpt) { fflush(stdout); } +const argDef* argFindIn(const argDef *argDefs, const char* name) { + for (const argDef *arg = argDefs; arg && arg->help; arg++) { + if (arg->name && strcmp(name, arg->name) == 0) { // long option + return arg; + } + if (name[2] == 0 && arg->key >= '?' && name[0] == arg->key) { // short option + return arg; + } + } + return nullptr; +} + +const argDef* argFind(const argParseOpt *parseOpt, const char* name) { + const argDef* found = argFindIn(parseOpt->argDefs, name); + if (found) { + return found; + } + for (const argParseChildOpt *child = parseOpt->childOpts; child && child->argDefs; child++) { + found = argFindIn(child->argDefs, name); + if (found) { + return found; + } + } + return nullptr; +} + } // namespace ebusd diff --git a/src/lib/utils/arg.h b/src/lib/utils/arg.h index bb3d4e22d..c7f136ddb 100755 --- a/src/lib/utils/arg.h +++ b/src/lib/utils/arg.h @@ -25,10 +25,11 @@ namespace ebusd { /** the available arg flags. */ enum ArgFlag { - af_optional = 1<<0, //!< optional argument value - af_multiple = 1<<1, //!< may appear multiple times (only allowed for last positional) - af_noHelp = 1<<2, //!< do not include -?/--help option + af_optional = 1<<0, //!< optional argument value + af_multiple = 1<<1, //!< may appear multiple times (only allowed for last positional) + af_noHelp = 1<<2, //!< do not include -?/--help option af_noVersion = 1<<3, //!< do not include -V/--version option + af_max = 1<<3, //!< maximum defined flag value }; /** Definition of a single argument. */ @@ -87,6 +88,14 @@ int argParse(const argParseOpt *parseOpt, int argc, char **argv, void *userArg); */ void argHelp(const char* name, const argParseOpt *parseOpt); +/** + * Find the argument with the given name. + * @param parseOpt pointer to the @a argParseOpt structure. + * @param name the name of the argument, either short or long. + * @return a pointer to the found @a argDef, or nullptr. + */ +const argDef* argFind(const argParseOpt *parseOpt, const char* name); + /** * Convenience macro to print an error message to stderr. */ From 776cf0581224351a08cbe20a1d73b23a56afd6a3 Mon Sep 17 00:00:00 2001 From: John Date: Wed, 25 Dec 2024 15:22:57 +0100 Subject: [PATCH 303/345] missed commit --- src/lib/ebus/data.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/ebus/data.cpp b/src/lib/ebus/data.cpp index 9f2361664..14a10cf7c 100644 --- a/src/lib/ebus/data.cpp +++ b/src/lib/ebus/data.cpp @@ -1319,7 +1319,7 @@ result_t LoadableDataFieldSet::getFieldMap(const string& preferLanguage, vector< result_t LoadableDataFieldSet::addFromFile(const string& filename, unsigned int lineNo, map* row, vector< map >* subRows, string* errorDescription, bool replace) { const DataField* field = nullptr; - result_t result = DataField::create(false, false, false, MAX_POS, m_templates, subRows, errorDescription, &field); + result_t result = DataField::create(m_isWrite, false, false, MAX_POS, m_templates, subRows, errorDescription, &field); if (result != RESULT_OK) { return result; } From c42d06dc05cc3503f383ad90f192ad799e6977ec Mon Sep 17 00:00:00 2001 From: John Date: Sat, 28 Dec 2024 14:43:13 +0100 Subject: [PATCH 304/345] fix max value on some signed number types --- src/lib/ebus/datatype.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib/ebus/datatype.cpp b/src/lib/ebus/datatype.cpp index 3f1d6b125..59a62ce12 100755 --- a/src/lib/ebus/datatype.cpp +++ b/src/lib/ebus/datatype.cpp @@ -1292,17 +1292,17 @@ DataTypeList::DataTypeList() { // unsigned 3 bytes int, 0 - 16777214, big endian add(new NumberDataType("U3R", 24, REV, 0xffffff, 0, 0xfffffe, 1)); // signed 3 bytes int, -8388607 - +8388607, little endian - add(new NumberDataType("S3N", 24, SIG, 0x800000, 0x800001, 0xffffff, 1)); + add(new NumberDataType("S3N", 24, SIG, 0x800000, 0x800001, 0x7fffff, 1)); // signed 3 bytes int, -8388607 - +8388607, big endian - add(new NumberDataType("S3R", 24, SIG|REV, 0x800000, 0x800001, 0xffffff, 1)); + add(new NumberDataType("S3R", 24, SIG|REV, 0x800000, 0x800001, 0x7fffff, 1)); // unsigned integer, 0 - 4294967294, little endian add(new NumberDataType("ULG", 32, 0, 0xffffffff, 0, 0xfffffffe, 1)); // unsigned integer, 0 - 4294967294, big endian add(new NumberDataType("ULR", 32, REV, 0xffffffff, 0, 0xfffffffe, 1)); // signed integer, -2147483647 - +2147483647, little endian - add(new NumberDataType("SLG", 32, SIG, 0x80000000, 0x80000001, 0xffffffff, 1)); + add(new NumberDataType("SLG", 32, SIG, 0x80000000, 0x80000001, 0x7fffffff, 1)); // signed integer, -2147483647 - +2147483647, big endian - add(new NumberDataType("SLR", 32, SIG|REV, 0x80000000, 0x80000001, 0xffffffff, 1)); + add(new NumberDataType("SLR", 32, SIG|REV, 0x80000000, 0x80000001, 0x7fffffff, 1)); add(new NumberDataType("BI0", 7, ADJ|REQ, 0, 0, 1)); // bit 0 (up to 7 bits until bit 6) add(new NumberDataType("BI1", 7, ADJ|REQ, 0, 1, 1)); // bit 1 (up to 7 bits until bit 7) add(new NumberDataType("BI2", 6, ADJ|REQ, 0, 2, 1)); // bit 2 (up to 6 bits until bit 7) From d25551e3f9f69a1e656d5c83ade23b37455bd06c Mon Sep 17 00:00:00 2001 From: John Date: Sun, 29 Dec 2024 07:39:21 +0100 Subject: [PATCH 305/345] fix knx socket options (closes #1422) --- src/lib/knx/knxnet.h | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/lib/knx/knxnet.h b/src/lib/knx/knxnet.h index 5041e59be..4b0cc1336 100644 --- a/src/lib/knx/knxnet.h +++ b/src/lib/knx/knxnet.h @@ -402,7 +402,9 @@ class KnxNetConnection : public KnxConnection { const char* open() override { close(); int fd = socketConnect(m_url && m_url[0] ? m_url : SYSTEM_MULTICAST_IP_STR, - SYSTEM_MULTICAST_PORT, IPPROTO_UDP, nullptr, 0x02); + SYSTEM_MULTICAST_PORT, IPPROTO_UDP, &m_multicast, + // do not use connect() as it will limit incoming to the mcast src which is not the case + 0x01); if (fd < 0) { return "create socket"; } @@ -596,7 +598,7 @@ class KnxNetConnection : public KnxConnection { } d[0] = tpci; logTelegram(true, c, l, d); - ssize_t sent = ::send(m_sock, buf, totalLen, MSG_NOSIGNAL); + ssize_t sent = sendto(m_sock, buf, totalLen, MSG_NOSIGNAL, (sockaddr*)&m_multicast, sizeof(m_multicast)); if (sent < 0) { return "send error"; } @@ -633,6 +635,9 @@ class KnxNetConnection : public KnxConnection { /** the URL to connect to. */ const char* m_url; + /** the multicast address to send to. */ + struct sockaddr_in m_multicast; + /** the socket if connected, or 0. */ int m_sock; From 58e6ec86f0f5c79d92de8dcd45fbefd2a54c79bf Mon Sep 17 00:00:00 2001 From: John Date: Tue, 31 Dec 2024 07:27:10 +0100 Subject: [PATCH 306/345] parse partial URLs as well --- src/lib/knx/knxnet.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/knx/knxnet.h b/src/lib/knx/knxnet.h index 4b0cc1336..b002f741a 100644 --- a/src/lib/knx/knxnet.h +++ b/src/lib/knx/knxnet.h @@ -401,7 +401,11 @@ class KnxNetConnection : public KnxConnection { // @copydoc const char* open() override { close(); - int fd = socketConnect(m_url && m_url[0] ? m_url : SYSTEM_MULTICAST_IP_STR, + string url = m_url && m_url[0] ? m_url : SYSTEM_MULTICAST_IP_STR; + if (m_url[0] == '@') { + url = SYSTEM_MULTICAST_IP_STR+url; + } + int fd = socketConnect(url.c_str(), SYSTEM_MULTICAST_PORT, IPPROTO_UDP, &m_multicast, // do not use connect() as it will limit incoming to the mcast src which is not the case 0x01); From 6f16175e67270912d08f90fa989aa5f4da8167a8 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 31 Dec 2024 07:29:44 +0100 Subject: [PATCH 307/345] add option to parse partial to parseInt() --- src/lib/ebus/symbol.cpp | 6 +++--- src/lib/ebus/symbol.h | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/lib/ebus/symbol.cpp b/src/lib/ebus/symbol.cpp index 3b0e96f78..ae4588c15 100755 --- a/src/lib/ebus/symbol.cpp +++ b/src/lib/ebus/symbol.cpp @@ -56,12 +56,12 @@ static const symbol_t CRC_LOOKUP_TABLE[] = { unsigned int parseInt(const char* str, int base, unsigned int minValue, unsigned int maxValue, - result_t* result, size_t* length) { + result_t* result, size_t* length, bool allowIncomplete) { char* strEnd = nullptr; unsigned long ret = strtoul(str, &strEnd, base); - if (strEnd == nullptr || strEnd == str || *strEnd != 0) { + if (strEnd == nullptr || strEnd == str || (!allowIncomplete && *strEnd != 0)) { *result = RESULT_ERR_INVALID_NUM; // invalid value return 0; } @@ -83,7 +83,7 @@ int parseSignedInt(const char* str, int base, int minValue, int maxValue, long ret = strtol(str, &strEnd, base); - if (strEnd == nullptr || (!allowIncomplete && *strEnd != 0)) { + if (strEnd == nullptr || strEnd == str || (!allowIncomplete && *strEnd != 0)) { *result = RESULT_ERR_INVALID_NUM; // invalid value return 0; } diff --git a/src/lib/ebus/symbol.h b/src/lib/ebus/symbol.h index a1523cadd..4324ea5f1 100755 --- a/src/lib/ebus/symbol.h +++ b/src/lib/ebus/symbol.h @@ -91,20 +91,21 @@ enum PredefinedSymbol : symbol_t { /** * Parse an unsigned int value. * @param str the string to parse. - * @param base the numerical base. + * @param base the numerical base (or 0 to determine from the prefix). * @param minValue the minimum resulting value. * @param maxValue the maximum resulting value. * @param result the variable in which to store an error code when parsing failed or the value is out of bounds. * @param length the optional variable in which to store the number of read characters. + * @param allowIncomplete true to allow parsing less than the complete string. * @return the parsed value. */ unsigned int parseInt(const char* str, int base, unsigned int minValue, unsigned int maxValue, - result_t* result, size_t* length = nullptr); + result_t* result, size_t* length = nullptr, bool allowIncomplete = false); /** * Parse a signed int value. * @param str the string to parse. - * @param base the numerical base. + * @param base the numerical base (or 0 to determine from the prefix). * @param minValue the minimum resulting value. * @param maxValue the maximum resulting value. * @param result the variable in which to store an error code when parsing failed or the value is out of bounds. From 2da8516c5081e0541ef696fc25312b8bf18dcd73 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 31 Dec 2024 08:08:34 +0100 Subject: [PATCH 308/345] updated --- ChangeLog.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index 83ac7ccda..1c645b021 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -3,6 +3,11 @@ * fix for device string symlink with colon * fix "read" and "write" command response * fix dump of divisor +* fix max value for S3N, S3N, SLG, and SLR types +* fix socket options for KNXnet/IP integration + +## Features +* add "-m" option to "encode" and "decode" commands # 24.1 (2024-10-27) From 5d4d75d631719ba4a433e84fa3a5fd9a077b5474 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 31 Dec 2024 08:37:06 +0100 Subject: [PATCH 309/345] add output for executed commands --- ChangeLog.md | 1 + src/ebusd/main.cpp | 17 +++++++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 1c645b021..55fd6022d 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -8,6 +8,7 @@ ## Features * add "-m" option to "encode" and "decode" commands +* add output for commands executed with "--inject=stop" # 24.1 (2024-10-27) diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index b5edc5647..5b24a0fe1 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -554,15 +554,20 @@ int main(int argc, char* argv[], char* envp[]) { if (arg.find_first_of(' ') != string::npos || arg.find_first_of('/') == string::npos) { RequestImpl req(false); req.add(argv[arg_index]); - bool connected; - RequestMode reqMode; + bool connected = true; + RequestMode reqMode = {}; string user; - bool reload; + bool reload = false; ostringstream ostream; result_t ret = s_mainLoop->decodeRequest(&req, &connected, &reqMode, &user, &reload, &ostream); - if (ret != RESULT_OK) { - string output = ostream.str(); - logError(lf_main, "executing command %s failed: %d", argv[arg_index], output.c_str()); + string output = ostream.str(); + if (ret != RESULT_OK || output.substr(0, 3) == "ERR" || output.substr(0, 5) == "usage") { + if (output.empty()) { + output = getResultCode(ret); + } + logError(lf_main, "executing command \"%s\" failed: %s", argv[arg_index], output.c_str()); + } else if (s_opt.stopAfterInject) { + logNotice(lf_main, "executed command \"%s\": %s", argv[arg_index], output.c_str()); } continue; } From 3eb51e7cc8c453e5d0619da87ca081b943b5aa6a Mon Sep 17 00:00:00 2001 From: John Date: Tue, 31 Dec 2024 08:43:28 +0100 Subject: [PATCH 310/345] fix packages --- .github/workflows/coverage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 0d5111acd..fef11f918 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -18,7 +18,7 @@ jobs: uses: actions/checkout@v4 - name: packages - run: sudo apt-get update && sudo apt-get install -y libmosquitto1 libmosquitto-dev libssl1.1 libssl-dev + run: sudo apt-get update && sudo apt-get install -y libmosquitto1 libmosquitto-dev libssl3 libssl-dev - name: build run: cmake -Dcoverage=1 -DBUILD_TESTING=1 . && make From edcb01955eb539979d5c9f7c389e947fcd56b504 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 31 Dec 2024 18:23:55 +0100 Subject: [PATCH 311/345] add secondary replacement value 0 for date types (closes #1391) --- src/lib/ebus/datatype.cpp | 6 +++--- src/lib/ebus/datatype.h | 3 +++ src/lib/ebus/test/test_data.cpp | 5 +++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/lib/ebus/datatype.cpp b/src/lib/ebus/datatype.cpp index 59a62ce12..6a5c62ff2 100755 --- a/src/lib/ebus/datatype.cpp +++ b/src/lib/ebus/datatype.cpp @@ -345,11 +345,11 @@ result_t DateTimeDataType::readSymbols(size_t offset, size_t length, const Symbo } switch (type) { case 2: // date only - if (!hasFlag(REQ) && symbol == m_replacement) { + if (!hasFlag(REQ) && (symbol == m_replacement || (!hasFlag(REZ) && symbol == 0))) { if (i + 1 != length) { *output << NULL_VALUE << "."; break; - } else if (last == m_replacement) { + } else if (last == m_replacement || (!hasFlag(REZ) && last == 0)) { if (length == 2) { // number of days since 01.01.1900 *output << NULL_VALUE << "."; } @@ -1228,7 +1228,7 @@ DataTypeList::DataTypeList() { // date, 01.01.2000 - 31.12.2099 (0x01,0x01,0x00 - 0x1f,0x0c,0x63, replacement 0xff) add(new DateTimeDataType("HDA:3", 24, 0, 0xff, true, false, 0)); // date, days since 01.01.1900, 01.01.1900 - 06.06.2079 (0x00,0x00 - 0xff,0xff) - add(new DateTimeDataType("DAY", 16, 0, 0xff, true, false, 0)); + add(new DateTimeDataType("DAY", 16, REZ, 0xff, true, false, 0)); // date+time in minutes since 01.01.2009, 01.01.2009 - 31.12.2099 (0x00,0x00,0x00,0x00 - 0x02,0xda,0x4e,0x1f) add(new DateTimeDataType("DTM", 32, REQ, 0x100, true, true, 0)); // time in BCD, 00:00:00 - 23:59:59 (0x00,0x00,0x00 - 0x59,0x59,0x23) diff --git a/src/lib/ebus/datatype.h b/src/lib/ebus/datatype.h index f6bd1a23a..c5e77a469 100755 --- a/src/lib/ebus/datatype.h +++ b/src/lib/ebus/datatype.h @@ -194,6 +194,9 @@ enum PartType { /** bit flag for @a DataType: stored duplicate for backwards compatibility, not to be traversed in lists any more. */ #define DUP 0x2000 +/** bit flag for @a DataType: special marker for non-tolerated secondary replacement value of zero (date only). */ +#define REZ 0x4000 + /** * Parse a float value from the 32 bit representation (IEEE 754). * @param value the 32 bit representation of the float value. diff --git a/src/lib/ebus/test/test_data.cpp b/src/lib/ebus/test/test_data.cpp index fc44392a8..1c09989d6 100755 --- a/src/lib/ebus/test/test_data.cpp +++ b/src/lib/ebus/test/test_data.cpp @@ -143,11 +143,13 @@ int main() { {"x,,bda", "20.02.2021", "10fe07000420020621", "00", ""}, // Saturday {"x,,bda", "31.12.2099", "10fe07000431120499", "00", ""}, // Thursday {"x,,bda", "-.-.-", "10fe070004ffff00ff", "00", ""}, + {"x,,bda", "-.-.-", "10fe07000400000000", "00", "W"}, {"x,,bda", "", "10fe07000432100014", "00", "rw"}, {"x,,bda:3", "26.10.2014", "10fe070003261014", "00", ""}, {"x,,bda:3", "01.01.2000", "10fe070003010100", "00", ""}, {"x,,bda:3", "31.12.2099", "10fe070003311299", "00", ""}, {"x,,bda:3", "-.-.-", "10fe070003ffffff", "00", ""}, + {"x,,bda:3", "-.-.-", "10fe070003000000", "00", "W"}, {"x,,bda:3", "", "10fe070003321299", "00", "rw"}, {"x,,bda,2", "", "", "", "c"}, {"x,,bdz", "26.10.2014", "10fe07000426100614", "00", ""}, // Sunday @@ -155,16 +157,19 @@ int main() { {"x,,bdz", "20.02.2021", "10fe07000420020521", "00", ""}, // Saturday {"x,,bdz", "31.12.2099", "10fe07000431120399", "00", ""}, // Thursday {"x,,bdz", "-.-.-", "10fe070004ffff00ff", "00", ""}, + {"x,,bdz", "-.-.-", "10fe07000400000000", "00", "W"}, {"x,,bdz", "", "10fe07000432100014", "00", "rw"}, {"x,,hda", "26.10.2014", "10fe0700041a0a070e", "00", ""}, // Sunday {"x,,hda", "01.01.2000", "10fe07000401010600", "00", ""}, // Saturday {"x,,hda", "31.12.2099", "10fe0700041f0c0463", "00", ""}, // Thursday {"x,,hda", "-.-.-", "10fe070004ffff00ff", "00", ""}, + {"x,,hda", "-.-.-", "10fe07000400000000", "00", "W"}, {"x,,hda", "", "10fe070004200c0463", "00", "rw"}, {"x,,hda:3", "26.10.2014", "10fe0700031a0a0e", "00", ""}, {"x,,hda:3", "01.01.2000", "10fe070003010100", "00", ""}, {"x,,hda:3", "31.12.2099", "10fe0700031f0c63", "00", ""}, {"x,,hda:3", "-.-.-", "10fe070003ffffff", "00", ""}, + {"x,,hda:3", "-.-.-", "10fe070003000000", "00", "W"}, {"x,,hda:3", "", "10fe070003200c63", "00", "rw"}, {"x,,hda,2", "", "", "", "c"}, {"x,,day", "26.10.2014", "10fe070002d0a3", "00", ""}, From fab1dd1b1f0c8ad4e90acebb6f34c22d6d8875ac Mon Sep 17 00:00:00 2001 From: John Date: Wed, 1 Jan 2025 16:30:48 +0100 Subject: [PATCH 312/345] fix constant value encoding in json --- src/lib/ebus/data.cpp | 5 ++++- src/lib/ebus/test/test_data.cpp | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/lib/ebus/data.cpp b/src/lib/ebus/data.cpp index 14a10cf7c..5bdb38469 100644 --- a/src/lib/ebus/data.cpp +++ b/src/lib/ebus/data.cpp @@ -927,13 +927,16 @@ result_t ConstantDataField::readSymbols(const SymbolString& input, size_t offset if (result != RESULT_OK) { return result; } - if (m_verify) { string value = coutput.str(); FileReader::trim(&value); + if (m_verify) { if (value != m_value) { return RESULT_ERR_OUT_OF_RANGE; } } + if (outputFormat & OF_JSON) { + *output << value; + } return RESULT_OK; } diff --git a/src/lib/ebus/test/test_data.cpp b/src/lib/ebus/test/test_data.cpp index 1c09989d6..1cfb56268 100755 --- a/src/lib/ebus/test/test_data.cpp +++ b/src/lib/ebus/test/test_data.cpp @@ -118,6 +118,8 @@ int main() { {"x,,str:10,==dummy", "", "10fe07000a48616c6c6f2044752120", "00", "rW"}, {"x,,str:10,=dummy", "", "10fe07000a64756d6d792020202020", "00", ""}, {"x,,str:10,==dummy", "", "10fe07000a64756d6d792020202020", "00", ""}, + {",,str:5,=dummy", "\n \"0\": {\"name\": \"\", \"value\": \"dummy\"}", "10fe07000a64756d6d792020202020", "00", "j"}, + {",,str:5,==\"dummy\"","\n \"0\": {\"name\": \"\", \"value\": \"dummy\"}", "10fe07000a64756d6d792020202020", "00", "j"}, {"x,,nts:10", "Hallo, Du!", "10fe07000a48616c6c6f2c20447521", "00", ""}, {"x,,nts:10", "Hallo, Du!", "10fe07000a48616c6c6f2c20447521", "00", ""}, {"x,,nts:10", "Hallo, Du", "10fe07000a48616c6c6f2c20447500", "00", ""}, From 88b039e58ece53bd025b588479527d3a7bcf4007 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 6 Jan 2025 19:48:52 +0100 Subject: [PATCH 313/345] simplify value list parsing --- src/lib/ebus/data.cpp | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/lib/ebus/data.cpp b/src/lib/ebus/data.cpp index 5bdb38469..368569784 100644 --- a/src/lib/ebus/data.cpp +++ b/src/lib/ebus/data.cpp @@ -309,27 +309,22 @@ result_t DataField::create(bool isWriteMessage, bool isTemplate, bool isBroadcas while (getline(stream, token, VALUE_SEPARATOR)) { FileReader::trim(&token); const char* str = token.c_str(); - char* strEnd = nullptr; - unsigned long id; - if (strncasecmp(str, "0x", 2) == 0) { - str += 2; - id = strtoul(str, &strEnd, 16); // hexadecimal - } else { - id = strtoul(str, &strEnd, 10); // decimal - } - if (strEnd == nullptr || strEnd == str || id > MAX_VALUE) { + size_t len = 0; + unsigned int id = parseInt(str, 0, 0, MAX_VALUE, &result, &len, true); + if (result != RESULT_OK) { *errorDescription = "value "+token+" in field "+formatInt(fieldIndex); result = RESULT_ERR_INVALID_LIST; break; } + str += len; // remove blanks around '=' sign - while (*strEnd == ' ') strEnd++; - if (*strEnd != '=') { + while (*str == ' ') str++; + if (*str != '=') { *errorDescription = "value "+token+" in field "+formatInt(fieldIndex); result = RESULT_ERR_INVALID_LIST; break; } - token = string(strEnd + 1); + token = string(str + 1); FileReader::trim(&token); values[(unsigned int)id] = token; } From e795ce604aca700b2350e734a59ebc3ca0cfea4d Mon Sep 17 00:00:00 2001 From: John Date: Mon, 6 Jan 2025 22:08:49 +0100 Subject: [PATCH 314/345] add value range and step support, fix value list validation --- src/lib/ebus/contrib/tem.cpp | 5 +- src/lib/ebus/data.cpp | 70 ++++++- src/lib/ebus/datatype.cpp | 353 +++++++++++++++++++++----------- src/lib/ebus/datatype.h | 59 +++++- src/lib/ebus/test/test_data.cpp | 68 +++++- 5 files changed, 417 insertions(+), 138 deletions(-) diff --git a/src/lib/ebus/contrib/tem.cpp b/src/lib/ebus/contrib/tem.cpp index ff1e22b16..991905cd9 100755 --- a/src/lib/ebus/contrib/tem.cpp +++ b/src/lib/ebus/contrib/tem.cpp @@ -135,8 +135,9 @@ result_t TemParamDataType::writeSymbols(const size_t offset, const size_t length value = (grp << 7) | num; // grp in bits 7...11, num in bits 0...6 } } - if (value < getMinValue() || value > getMaxValue()) { - return RESULT_ERR_OUT_OF_RANGE; // value out of range + result_t ret = checkValueRange(value); + if (ret != RESULT_OK) { + return ret; } return writeRawValue(value, offset, length, output, usedLength); } diff --git a/src/lib/ebus/data.cpp b/src/lib/ebus/data.cpp index 368569784..2f745ad7a 100644 --- a/src/lib/ebus/data.cpp +++ b/src/lib/ebus/data.cpp @@ -273,6 +273,7 @@ result_t DataField::create(bool isWriteMessage, bool isTemplate, bool isBroadcas string divisorStr = pluck("divisor", &row); string valuesStr = pluck("values", &row); + string rangeStr = pluck("range", &row); if (divisorStr.empty() && valuesStr.empty()) { divisorStr = pluck("divisor/values", &row); // [divisor|values] if (divisorStr.find('=') != string::npos) { @@ -365,6 +366,57 @@ result_t DataField::create(bool isWriteMessage, bool isTemplate, bool isBroadcas } transform(typeName.begin(), typeName.end(), typeName.begin(), ::toupper); const DataType* dataType = DataTypeList::getInstance()->get(typeName, length == REMAIN_LEN ? 0 : length); + if (dataType && dataType->isNumeric() && !rangeStr.empty()) { + const NumberDataType* numType = reinterpret_cast(dataType); + if (divisor != 1 && divisor != 0) { + result = numType->derive(divisor, numType->getBitCount(), &numType); + divisor = 1; + } + // either from-to or from-to:step + size_t sepPos = rangeStr.find('-', 1); + string part = rangeStr.substr(0, sepPos); + FileReader::trim(&part); + unsigned int from; + if (result == RESULT_OK) { + result = numType->parseInput(part, &from); + } + unsigned int to = from; + unsigned int inc = 0; + if (result == RESULT_OK) { + part = rangeStr.substr(sepPos+1); + FileReader::trim(&part); + sepPos = part.find(':'); + if (sepPos != string::npos) { + string incStr = part.substr(sepPos + 1); + FileReader::trim(&incStr); + part = part.substr(0, sepPos); + FileReader::trim(&part); + result = numType->parseInput(incStr, &inc); + } + if (result == RESULT_OK) { + result = numType->parseInput(part, &to); + } + } + float ffrom = 0, fto = 0; + if (result == RESULT_OK) { + result = numType->getFloatFromRawValue(from, &ffrom); + } + if (result == RESULT_OK) { + result = numType->getFloatFromRawValue(to, &fto); + } + if (result == RESULT_OK && ffrom > fto) { + result = RESULT_ERR_INVALID_LIST; + } + if (result == RESULT_OK) { + result = numType->derive(from, to, inc, &numType); + }; + if (result != RESULT_OK) { + *errorDescription = "\""+rangeStr+"\" in field "+formatInt(fieldIndex); + result = RESULT_ERR_OUT_OF_RANGE; + break; + } + dataType = numType; + } if (!dataType) { result = RESULT_ERR_NOTFOUND; *errorDescription = "field type "+typeName+" in field "+formatInt(fieldIndex); @@ -512,8 +564,11 @@ result_t SingleDataField::create(const string& name, const map& *returnField = new SingleDataField(name, attributes, numType, partType, byteCount); return RESULT_OK; } - if (values->begin()->first < numType->getMinValue() || values->rbegin()->first > numType->getMaxValue()) { - return RESULT_ERR_OUT_OF_RANGE; + for (auto& it : *values) { + result_t ret = numType->checkValueRange(it.first); + if (ret != RESULT_OK) { + return ret; + } } *returnField = new ValueListDataField(name, attributes, numType, partType, byteCount, *values); return RESULT_OK; @@ -775,8 +830,11 @@ result_t ValueListDataField::derive(const string& name, PartType partType, int d } const NumberDataType* num = reinterpret_cast(m_dataType); if (!values.empty()) { - if (values.begin()->first < num->getMinValue() || values.rbegin()->first > num->getMaxValue()) { - return RESULT_ERR_INVALID_ARG; // cannot use divisor != 1 for value list field + for (auto& it : values) { + result_t ret = num->checkValueRange(it.first); + if (ret != RESULT_OK) { + return RESULT_ERR_INVALID_ARG; + } } fields->push_back(new ValueListDataField(useName, *attributes, num, partType, m_length, values)); @@ -922,8 +980,8 @@ result_t ConstantDataField::readSymbols(const SymbolString& input, size_t offset if (result != RESULT_OK) { return result; } - string value = coutput.str(); - FileReader::trim(&value); + string value = coutput.str(); + FileReader::trim(&value); if (m_verify) { if (value != m_value) { return RESULT_ERR_OUT_OF_RANGE; diff --git a/src/lib/ebus/datatype.cpp b/src/lib/ebus/datatype.cpp index 6a5c62ff2..a617bb398 100755 --- a/src/lib/ebus/datatype.cpp +++ b/src/lib/ebus/datatype.cpp @@ -700,8 +700,16 @@ bool NumberDataType::dump(OutputFormat outputFormat, size_t length, AppendDiviso ret = true; } } - if (ret && (outputFormat & OF_JSON) && (outputFormat & OF_ALL_ATTRS)) { - *output << ", \"precision\": " << static_cast(getPrecision()); + if ((outputFormat & OF_JSON) && (outputFormat & OF_ALL_ATTRS)) { + if (ret) { + *output << ", \"precision\": " << static_cast(getPrecision()); + } + *output << ", \"min\": "; + getMinMax(false, OF_JSON, output); + *output << ", \"max\": "; + getMinMax(true, OF_JSON, output); + *output << ", \"step\": "; + getStep(OF_JSON, output); } return ret; } @@ -747,23 +755,119 @@ result_t NumberDataType::derive(int divisor, size_t bitCount, const NumberDataTy } else { return RESULT_ERR_INVALID_ARG; } + ostringstream str; + str << m_id << ',' << static_cast(bitCount) << ',' << static_cast(divisor); + string key = str.str(); + *derived = static_cast(DataTypeList::getInstance()->get(key)); + if (*derived == nullptr) { + if (m_bitCount < 8) { + *derived = new NumberDataType(m_id, bitCount, m_flags, m_replacement, + m_firstBit, divisor, m_baseType ? m_baseType : this); + } else { + *derived = new NumberDataType(m_id, bitCount, m_flags, m_replacement, + m_minValue, m_maxValue, divisor, m_baseType ? m_baseType : this); + } + DataTypeList::getInstance()->add(*derived, key); + } + return RESULT_OK; +} + +result_t NumberDataType::derive(unsigned int min, unsigned int max, unsigned int inc, const NumberDataType** derived) +const { if (m_bitCount < 8) { - *derived = new NumberDataType(m_id, bitCount, m_flags, m_replacement, - m_firstBit, divisor, m_baseType ? m_baseType : this); - } else { - *derived = new NumberDataType(m_id, bitCount, m_flags, m_replacement, - m_minValue, m_maxValue, divisor, m_baseType ? m_baseType : this); + return RESULT_ERR_INVALID_ARG; + } + if (min == m_minValue && max == m_maxValue && (inc == 0 || inc == m_incValue)) { + *derived = this; + return RESULT_OK; + } + if (checkValueRange(min) != RESULT_OK || checkValueRange(max) != RESULT_OK) { + return RESULT_ERR_OUT_OF_RANGE; + } + ostringstream str; + str << m_id << ',' << static_cast(m_bitCount) << ',' << static_cast(m_divisor) + << ',' << static_cast(min)<< ',' << static_cast(max)<< ',' << static_cast(inc); + string key = str.str(); + *derived = static_cast(DataTypeList::getInstance()->get(key)); + if (*derived == nullptr) { + *derived = new NumberDataType(m_id, m_bitCount, m_flags, m_replacement, + min, max, inc, m_divisor, m_baseType ? m_baseType : this); + DataTypeList::getInstance()->add(*derived, key); } - DataTypeList::getInstance()->addCleanup(*derived); return RESULT_OK; } result_t NumberDataType::getMinMax(bool getMax, const OutputFormat outputFormat, ostream* output) const { - return readFromRawValue(getMax ? m_maxValue : m_minValue, outputFormat, output); + return readFromRawValue(getMax ? m_maxValue : m_minValue, outputFormat, output, true); } result_t NumberDataType::getStep(const OutputFormat outputFormat, ostream* output) const { - return readFromRawValue(hasFlag(EXP) ? floatToUint(1.0f) : 1, outputFormat, output); + return readFromRawValue(m_incValue ? m_incValue : hasFlag(EXP) ? floatToUint(1.0f) : 1, outputFormat, output, true); +} + +result_t NumberDataType::checkValueRange(unsigned int value, bool* pnegative) const { + bool negative; + if (hasFlag(SIG)) { // signed value + unsigned int negBit = 1 << (m_bitCount - 1); + negative = (value & negBit) != 0; + if (hasFlag(EXP)) { + float fval = uintToFloat(value, negative); + if (!isfinite(fval)) { + return RESULT_EMPTY; + } + float cval = uintToFloat(m_minValue, (m_minValue & negBit) != 0); + if (!isfinite(cval)) { + return RESULT_EMPTY; + } + if (fval < cval) { + return RESULT_ERR_OUT_OF_RANGE; + } + cval = uintToFloat(m_maxValue, (m_maxValue & negBit) != 0); + if (!isfinite(cval)) { + return RESULT_EMPTY; + } + if (fval > cval) { + return RESULT_ERR_OUT_OF_RANGE; + } + } else { + if (m_minValue & negBit) { + // negative min + if (negative && value < m_minValue) { + // e.g. SCH val=0xfc=-4 min=0xff=-1 + return RESULT_ERR_OUT_OF_RANGE; + } + } else { + // positive min + if (negative || value < m_minValue) { + // e.g. SCH val=0xfc=-4 min=0x01=+1 + // e.g. SCH val=0x00=0 min=0x01=+1 + return RESULT_ERR_OUT_OF_RANGE; + } + } + if (m_maxValue & negBit) { + // negative max + if (!negative || value > m_maxValue) { + // e.g. SCH val=0x00=0 max=0xff=-1 + // e.g. SCH val=0xff=-1 max=0xfe=-2 + return RESULT_ERR_OUT_OF_RANGE; + } + } else { + // positive max + if (!negative && value > m_maxValue) { + // e.g. SCH val=0x04=+4 max=0x01=+1 + return RESULT_ERR_OUT_OF_RANGE; + } + } + } + } else if (value < m_minValue || value > m_maxValue) { + return RESULT_ERR_OUT_OF_RANGE; + } else { + negative = false; + } + if (pnegative) { + *pnegative = negative; + } + return RESULT_OK; } result_t NumberDataType::readRawValue(size_t offset, size_t length, const SymbolString& input, @@ -830,22 +934,10 @@ result_t NumberDataType::getFloatFromRawValue(unsigned int value, float* output) return RESULT_EMPTY; } - bool negative; - if (hasFlag(SIG)) { // signed value - negative = (value & (1 << (m_bitCount - 1))) != 0; - if (!hasFlag(EXP)) { - if (negative) { // negative signed value - if (value < m_minValue) { - return RESULT_ERR_OUT_OF_RANGE; // value out of range - } - } else if (value > m_maxValue) { - return RESULT_ERR_OUT_OF_RANGE; // value out of range - } - } - } else if (value < m_minValue || value > m_maxValue) { - return RESULT_ERR_OUT_OF_RANGE; // value out of range - } else { - negative = false; + bool negative = false; + result_t ret = checkValueRange(value, &negative); + if (ret != RESULT_OK) { + return ret; } int signedValue; if (m_bitCount == 32) { @@ -865,7 +957,7 @@ result_t NumberDataType::getFloatFromRawValue(unsigned int value, float* output) val /= static_cast(m_divisor); } } - *output = static_cast(val); + *output = val; return RESULT_OK; } if (!negative) { @@ -895,7 +987,7 @@ result_t NumberDataType::getFloatFromRawValue(unsigned int value, float* output) } result_t NumberDataType::readFromRawValue(unsigned int value, - OutputFormat outputFormat, ostream* output) const { + OutputFormat outputFormat, ostream* output, bool skipRangeCheck) const { size_t length = (m_bitCount < 8) ? 1 : (m_bitCount/8); // initialize output *output << setw(0) << std::resetiosflags(output->flags()) << dec << std::skipws << setprecision(6); @@ -909,22 +1001,10 @@ result_t NumberDataType::readFromRawValue(unsigned int value, return RESULT_OK; } - bool negative; - if (hasFlag(SIG)) { // signed value - negative = (value & (1 << (m_bitCount - 1))) != 0; - if (!hasFlag(EXP)) { - if (negative) { // negative signed value - if (value < m_minValue) { - return RESULT_ERR_OUT_OF_RANGE; // value out of range - } - } else if (value > m_maxValue) { - return RESULT_ERR_OUT_OF_RANGE; // value out of range - } - } - } else if (value < m_minValue || value > m_maxValue) { - return RESULT_ERR_OUT_OF_RANGE; // value out of range - } else { - negative = false; + bool negative = false; + result_t ret = checkValueRange(value, &negative); + if (!skipRangeCheck && ret != RESULT_OK) { + return ret; } int signedValue; if (m_bitCount == 32) { @@ -1055,7 +1135,7 @@ result_t NumberDataType::getRawValueFromFloat(float val, unsigned int* output) c } else { if (m_divisor == 1) { if (hasFlag(SIG)) { - long signedValue = static_cast(val); // TODO static_c? + long signedValue = static_cast(val); if (signedValue < 0 && m_bitCount != 32) { value = (unsigned int)(signedValue + (1 << m_bitCount)); } else { @@ -1091,106 +1171,108 @@ result_t NumberDataType::getRawValueFromFloat(float val, unsigned int* output) c value = (unsigned int)dvalue; } } - - if (hasFlag(SIG)) { // signed value - if ((value & (1 << (m_bitCount - 1))) != 0) { // negative signed value - if (value < m_minValue) { - return RESULT_ERR_OUT_OF_RANGE; // value out of range - } - } else if (value > m_maxValue) { - return RESULT_ERR_OUT_OF_RANGE; // value out of range - } - } else if (value < m_minValue || value > m_maxValue) { - return RESULT_ERR_OUT_OF_RANGE; // value out of range - } + } + result_t ret = checkValueRange(value); + if (ret != RESULT_OK) { + return ret; } *output = value; return RESULT_OK; } -result_t NumberDataType::writeSymbols(size_t offset, size_t length, istringstream* input, - SymbolString* output, size_t* usedLength) const { +result_t NumberDataType::parseInput(const string inputStr, unsigned int* parsedValue) const { unsigned int value; - const string inputStr = input->str(); if (!hasFlag(REQ) && (isIgnored() || inputStr == NULL_VALUE)) { value = m_replacement; // replacement value } else if (inputStr.empty()) { return RESULT_ERR_EOF; // input too short - } else if (hasFlag(EXP)) { // IEEE 754 binary32 - const char* str = inputStr.c_str(); - char* strEnd = nullptr; - double dvalue = strtod(str, &strEnd); - if (strEnd == nullptr || strEnd == str || *strEnd != 0) { - return RESULT_ERR_INVALID_NUM; // invalid value - } - if (m_divisor < 0) { - dvalue /= -m_divisor; - } else if (m_divisor > 1) { - dvalue *= m_divisor; - } - value = floatToUint(static_cast(dvalue)); - if (value == 0xffffffff) { - return RESULT_ERR_INVALID_NUM; - } } else { - const char* str = inputStr.c_str(); - char* strEnd = nullptr; - if (m_divisor == 1) { - if (hasFlag(SIG)) { - long signedValue = strtol(str, &strEnd, 10); - if (signedValue < 0 && m_bitCount != 32) { - value = (unsigned int)(signedValue + (1 << m_bitCount)); - } else { - value = (unsigned int)signedValue; - } - } else { - value = (unsigned int)strtoul(str, &strEnd, 10); - } - if (strEnd == nullptr || strEnd == str || (*strEnd != 0 && *strEnd != '.')) { - return RESULT_ERR_INVALID_NUM; // invalid value - } - } else { + if (hasFlag(EXP)) { // IEEE 754 binary32 + const char* str = inputStr.c_str(); + char* strEnd = nullptr; double dvalue = strtod(str, &strEnd); - if (strEnd == nullptr || strEnd == str || *strEnd != 0) { + if (errno == ERANGE || strEnd == nullptr || strEnd == str || *strEnd != 0) { return RESULT_ERR_INVALID_NUM; // invalid value } if (m_divisor < 0) { - dvalue = round(dvalue / -m_divisor); - } else { - dvalue = round(dvalue * m_divisor); + dvalue /= -m_divisor; + } else if (m_divisor > 1) { + dvalue *= m_divisor; } - if (hasFlag(SIG)) { - if (dvalue < -exp2((8 * static_cast(length)) - 1) - || dvalue >= exp2((8 * static_cast(length)) - 1)) { - return RESULT_ERR_OUT_OF_RANGE; // value out of range - } - if (dvalue < 0 && m_bitCount != 32) { - value = static_cast(dvalue + (1 << m_bitCount)); + value = floatToUint(static_cast(dvalue)); + if (value == 0xffffffff) { + return RESULT_ERR_INVALID_NUM; + } + } else { + unsigned int maxBit = m_bitCount != 32 ? 1 << m_bitCount : 0; + const char* str = inputStr.c_str(); + char* strEnd = nullptr; + if (m_divisor == 1) { + if (hasFlag(SIG)) { + long signedValue = strtol(str, &strEnd, 0); + if (errno == ERANGE || (maxBit && (signedValue < -(maxBit/2L) || signedValue >= maxBit/2L))) { + return RESULT_ERR_OUT_OF_RANGE; // value out of range + } + if (signedValue < 0 && m_bitCount != 32) { + value = (unsigned int)(signedValue + maxBit); + } else { + value = (unsigned int)signedValue; + } } else { - value = static_cast(dvalue); + value = (unsigned int)strtoul(str, &strEnd, 0); + if (errno == ERANGE || (maxBit && value >= maxBit)) { + return RESULT_ERR_OUT_OF_RANGE; + } + } + if (strEnd == nullptr || strEnd == str || (*strEnd != 0 && *strEnd != '.')) { + return RESULT_ERR_INVALID_NUM; // invalid value } } else { - if (dvalue < 0.0 || dvalue >= exp2(8 * static_cast(length))) { - return RESULT_ERR_OUT_OF_RANGE; // value out of range + double dvalue = strtod(str, &strEnd); + if (errno == ERANGE || strEnd == nullptr || strEnd == str || *strEnd != 0) { + return RESULT_ERR_INVALID_NUM; // invalid value } - value = (unsigned int)dvalue; - } - } - - if (hasFlag(SIG)) { // signed value - if ((value & (1 << (m_bitCount - 1))) != 0) { // negative signed value - if (value < m_minValue) { - return RESULT_ERR_OUT_OF_RANGE; // value out of range + if (m_divisor < 0) { + dvalue = round(dvalue / -m_divisor); + } else { + dvalue = round(dvalue * m_divisor); + } + if (hasFlag(SIG)) { + double max = exp2(m_bitCount - 1); + if (dvalue < -max || dvalue >= max) { + return RESULT_ERR_OUT_OF_RANGE; // value out of range + } + if (dvalue < 0 && m_bitCount != 32) { + value = static_cast(dvalue + (1 << m_bitCount)); + } else { + value = static_cast(dvalue); + } + } else { + if (dvalue < 0.0 || dvalue >= exp2(m_bitCount)) { + return RESULT_ERR_OUT_OF_RANGE; // value out of range + } + value = (unsigned int)dvalue; } - } else if (value > m_maxValue) { - return RESULT_ERR_OUT_OF_RANGE; // value out of range } - } else if (value < m_minValue || value > m_maxValue) { - return RESULT_ERR_OUT_OF_RANGE; // value out of range + } + result_t ret = checkValueRange(value); + if (ret != RESULT_OK) { + return ret; } } + *parsedValue = value; + return RESULT_OK; +} +result_t NumberDataType::writeSymbols(size_t offset, size_t length, istringstream* input, + SymbolString* output, size_t* usedLength) const { + unsigned int value; + const string inputStr = input->str(); + result_t ret = parseInput(inputStr, &value); + if (ret != RESULT_OK) { + return ret; + } return writeRawValue(value, offset, length, output, usedLength); } @@ -1207,6 +1289,7 @@ DataTypeList::DataTypeList() { // unsigned decimal in BCD, 0000 - 9999 (fixed length) add(new NumberDataType("PIN", 16, FIX|BCD|REV, 0xffff, 0, 0x9999, 1)); add(new NumberDataType("UCH", 8, 0, 0xff, 0, 0xfe, 1)); // unsigned integer, 0 - 254 + add(new NumberDataType("U1L", 8, REQ, 0, 0, 0xff, 1)); // unsigned 1-byte, 0 - 255 (no replacement) add(new StringDataType("IGN", MAX_LEN*8, IGN|ADJ, 0)); // >= 1 byte ignored data // >= 1 byte character string filled up with 0x00 (null terminated string) add(new StringDataType("NTS", MAX_LEN*8, ADJ, 0)); @@ -1264,6 +1347,7 @@ DataTypeList::DataTypeList() { add(new NumberDataType("HCD:2", 16, HCD|BCD|REQ, 0, 0, 9999, 1)); // unsigned decimal in HCD, 0 - 9999 add(new NumberDataType("HCD:3", 24, HCD|BCD|REQ, 0, 0, 999999, 1)); // unsigned decimal in HCD, 0 - 999999 add(new NumberDataType("SCH", 8, SIG, 0x80, 0x81, 0x7f, 1)); // signed integer, -127 - +127 + add(new NumberDataType("S1L", 8, SIG|REQ, 0, 0x80, 0x7f, 1)); // signed integer, -128 - +127 (no replacement) add(new NumberDataType("D1B", 8, SIG, 0x80, 0x81, 0x7f, 1)); // signed integer, -127 - +127 // unsigned number (fraction 1/2), 0 - 100 (0x00 - 0xc8, replacement 0xff) add(new NumberDataType("D1C", 8, 0, 0xff, 0x00, 0xc8, 2)); @@ -1283,26 +1367,50 @@ DataTypeList::DataTypeList() { add(new NumberDataType("UIN", 16, 0, 0xffff, 0, 0xfffe, 1)); // unsigned integer, 0 - 65534, big endian add(new NumberDataType("UIR", 16, REV, 0xffff, 0, 0xfffe, 1)); + // unsigned integer, 0 - 65535, little endian (no replacement) + add(new NumberDataType("U2L", 16, REQ, 0, 0, 0xffff, 1)); + // unsigned integer, 0 - 65535, big endian (no replacement) + add(new NumberDataType("U2B", 16, REQ|REV, 0, 0, 0xffff, 1)); // signed integer, -32767 - +32767, little endian add(new NumberDataType("SIN", 16, SIG, 0x8000, 0x8001, 0x7fff, 1)); // signed integer, -32767 - +32767, big endian add(new NumberDataType("SIR", 16, SIG|REV, 0x8000, 0x8001, 0x7fff, 1)); + // signed integer, -32768 - +32767, little endian (no replacement) + add(new NumberDataType("S2L", 16, SIG|REQ, 0, 0x8000, 0x7fff, 1)); + // signed integer, -32768 - +32767, big endian (no replacement) + add(new NumberDataType("S2B", 16, SIG|REQ|REV, 0, 0x8000, 0x7fff, 1)); // unsigned 3 bytes int, 0 - 16777214, little endian add(new NumberDataType("U3N", 24, 0, 0xffffff, 0, 0xfffffe, 1)); // unsigned 3 bytes int, 0 - 16777214, big endian add(new NumberDataType("U3R", 24, REV, 0xffffff, 0, 0xfffffe, 1)); + // unsigned 3 bytes int, 0 - 16777215, little endian (no replacement) + add(new NumberDataType("U3L", 24, REQ, 0, 0, 0xffffff, 1)); + // unsigned 3 bytes int, 0 - 16777215, big endian (no replacement) + add(new NumberDataType("U3B", 24, REQ|REV, 0, 0, 0xffffff, 1)); // signed 3 bytes int, -8388607 - +8388607, little endian add(new NumberDataType("S3N", 24, SIG, 0x800000, 0x800001, 0x7fffff, 1)); // signed 3 bytes int, -8388607 - +8388607, big endian add(new NumberDataType("S3R", 24, SIG|REV, 0x800000, 0x800001, 0x7fffff, 1)); + // signed 3 bytes int, -8388608 - +8388607, little endian (no replacement) + add(new NumberDataType("S3L", 24, SIG|REQ, 0, 0x800000, 0x7fffff, 1)); + // signed 3 bytes int, -8388608 - +8388607, big endian (no replacement) + add(new NumberDataType("S3B", 24, SIG|REQ|REV, 0, 0x800000, 0x7fffff, 1)); // unsigned integer, 0 - 4294967294, little endian add(new NumberDataType("ULG", 32, 0, 0xffffffff, 0, 0xfffffffe, 1)); // unsigned integer, 0 - 4294967294, big endian add(new NumberDataType("ULR", 32, REV, 0xffffffff, 0, 0xfffffffe, 1)); // signed integer, -2147483647 - +2147483647, little endian + // unsigned integer, 0 - 4294967295, little endian (no replacement) + add(new NumberDataType("U4L", 32, REQ, 0, 0, 0xffffffff, 1)); + // unsigned integer, 0 - 4294967295, big endian (no replacement) + add(new NumberDataType("U4B", 32, REQ|REV, 0, 0, 0xffffffff, 1)); add(new NumberDataType("SLG", 32, SIG, 0x80000000, 0x80000001, 0x7fffffff, 1)); // signed integer, -2147483647 - +2147483647, big endian add(new NumberDataType("SLR", 32, SIG|REV, 0x80000000, 0x80000001, 0x7fffffff, 1)); + // signed integer, -2147483648 - +2147483647, little endian (no replacement) + add(new NumberDataType("S4L", 32, SIG|REQ, 0, 0x80000000, 0x7fffffff, 1)); + // signed integer, -2147483648 - +2147483647, big endian (no replacement) + add(new NumberDataType("S4B", 32, SIG|REQ|REV, 0, 0x80000000, 0x7fffffff, 1)); add(new NumberDataType("BI0", 7, ADJ|REQ, 0, 0, 1)); // bit 0 (up to 7 bits until bit 6) add(new NumberDataType("BI1", 7, ADJ|REQ, 0, 1, 1)); // bit 1 (up to 7 bits until bit 7) add(new NumberDataType("BI2", 6, ADJ|REQ, 0, 2, 1)); // bit 2 (up to 6 bits until bit 7) @@ -1350,11 +1458,12 @@ void DataTypeList::clear() { m_typesById.clear(); } -result_t DataTypeList::add(const DataType* dataType) { - if (m_typesById.find(dataType->getId()) != m_typesById.end()) { +result_t DataTypeList::add(const DataType* dataType, const string derivedKey) { + string key = derivedKey.empty() ? dataType->getId() : derivedKey; + if (m_typesById.find(key) != m_typesById.end()) { return RESULT_ERR_DUPLICATE_NAME; // duplicate key } - m_typesById[dataType->getId()] = dataType; + m_typesById[key] = dataType; m_cleanupTypes.push_back(dataType); return RESULT_OK; } diff --git a/src/lib/ebus/datatype.h b/src/lib/ebus/datatype.h index c5e77a469..9fb2f3a52 100755 --- a/src/lib/ebus/datatype.h +++ b/src/lib/ebus/datatype.h @@ -481,7 +481,25 @@ class NumberDataType : public DataType { NumberDataType(const string& id, size_t bitCount, uint16_t flags, unsigned int replacement, unsigned int minValue, unsigned int maxValue, int divisor, const NumberDataType* baseType = nullptr) - : DataType(id, bitCount, flags|NUM, replacement), m_minValue(minValue), m_maxValue(maxValue), + : DataType(id, bitCount, flags|NUM, replacement), m_minValue(minValue), m_maxValue(maxValue), m_incValue(0), + m_divisor(divisor == 0 ? 1 : divisor), m_precision(calcPrecision(divisor)), m_firstBit(0), m_baseType(baseType) {} + + /** + * Constructs a new instance for multiple of 8 bits with increment value. + * @param id the type identifier. + * @param bitCount the number of bits (maximum length if #ADJ flag is set). + * @param flags the combination of flags (like #BCD). + * @param replacement the replacement value (no replacement if equal to minValue). + * @param minValue the minimum raw value. + * @param maxValue the maximum raw value. + * @param incValue the smallest step value for increment/decrement, or 0 for auto. + * @param divisor the divisor (negative for reciprocal). + * @param baseType the base @a NumberDataType for derived instances, or nullptr. + */ + NumberDataType(const string& id, size_t bitCount, uint16_t flags, unsigned int replacement, + unsigned int minValue, unsigned int maxValue, unsigned int incValue, int divisor, + const NumberDataType* baseType = nullptr) + : DataType(id, bitCount, flags|NUM, replacement), m_minValue(minValue), m_maxValue(maxValue), m_incValue(incValue), m_divisor(divisor == 0 ? 1 : divisor), m_precision(calcPrecision(divisor)), m_firstBit(0), m_baseType(baseType) {} /** @@ -496,7 +514,7 @@ class NumberDataType : public DataType { */ NumberDataType(const string& id, size_t bitCount, uint16_t flags, unsigned int replacement, int16_t firstBit, int divisor, const NumberDataType* baseType = nullptr) - : DataType(id, bitCount, flags|NUM, replacement), m_minValue(0), m_maxValue((1 << bitCount)-1), + : DataType(id, bitCount, flags|NUM, replacement), m_minValue(0), m_maxValue((1 << bitCount)-1), m_incValue(0), m_divisor(divisor == 0 ? 1 : divisor), m_precision(0), m_firstBit(firstBit), m_baseType(baseType) {} /** @@ -527,6 +545,18 @@ class NumberDataType : public DataType { */ virtual result_t derive(int divisor, size_t bitCount, const NumberDataType** derived) const; + /** + * Derive a new @a NumberDataType from this. + * @param min the minimum raw value. + * @param max the minimum raw value. + * @param inc the smallest step value for increment/decrement, or 0 to keep the current increment (or calculate + * automatically). + * @param derived the derived @a NumberDataType, or this if derivation is + * not necessary. + * @return @a RESULT_OK on success, or an error code. + */ + virtual result_t derive(unsigned int min, unsigned int max, unsigned int inc, const NumberDataType** derived) const; + /** * @return the minimum raw value. */ @@ -546,6 +576,14 @@ class NumberDataType : public DataType { */ result_t getMinMax(bool getMax, const OutputFormat outputFormat, ostream* output) const; + /** + * Check the value against the minimum and maximum value. + * @param value the raw value. + * @param negative optional variable in which to store the negative flag. + * @return @a RESULT_OK on success, or an error code. + */ + result_t checkValueRange(unsigned int value, bool* negative = nullptr) const; + /** * Get the smallest step value for increment/decrement. * @param outputFormat the @a OutputFormat options to use. @@ -598,10 +636,19 @@ class NumberDataType : public DataType { * @param value the numeric raw value. * @param outputFormat the @a OutputFormat options to use. * @param output the ostream to append the formatted value to. + * @param skipRangeCheck whether to skip the value range check. * @return @a RESULT_OK on success, or an error code. */ result_t readFromRawValue(unsigned int value, - OutputFormat outputFormat, ostream* output) const; + OutputFormat outputFormat, ostream* output, bool skipRangeCheck = false) const; + + /** + * Internal method for parsing an input string to the coorresponding raw value. + * @param inputStr the input string to parse the formatted value from. + * @param parsedValue the variable in which to store the parsed raw value. + * @return @a RESULT_OK on success, or an error code. + */ + result_t parseInput(const string inputStr, unsigned int* parsedValue) const; /** * Internal method for writing the numeric raw value to a @a SymbolString. @@ -628,6 +675,9 @@ class NumberDataType : public DataType { /** the maximum raw value. */ const unsigned int m_maxValue; + /** the smallest step value for increment/decrement, or 0 for auto. */ + const unsigned int m_incValue; + /** the divisor (negative for reciprocal). */ const int m_divisor; @@ -680,10 +730,11 @@ class DataTypeList { /** * Adds a @a DataType instance to this map. * @param dataType the @a DataType instance to add. + * @param derivedKey optional speicla key for derived instances. * @return @a RESULT_OK on success, or an error code. * Note: the caller may not free the added instance on success. */ - result_t add(const DataType* dataType); + result_t add(const DataType* dataType, const string derivedKey = ""); /** * Adds a @a DataType instance for later cleanup. diff --git a/src/lib/ebus/test/test_data.cpp b/src/lib/ebus/test/test_data.cpp index 1cfb56268..6fce4b918 100755 --- a/src/lib/ebus/test/test_data.cpp +++ b/src/lib/ebus/test/test_data.cpp @@ -53,15 +53,18 @@ void verify(bool expectFailMatch, string type, string input, class TestReader : public MappedFileReader { public: - TestReader(DataFieldTemplates* templates, bool isSet, bool isMasterDest) + TestReader(DataFieldTemplates* templates, bool isSet, bool isMasterDest, bool withRange) : MappedFileReader::MappedFileReader(true), m_templates(templates), m_isSet(isSet), m_isMasterDest(isMasterDest), - m_fields(nullptr) {} + m_withRange(withRange), m_fields(nullptr) {} result_t getFieldMap(const string& preferLanguage, vector* row, string* errorDescription) const override { if (row->empty()) { row->push_back("*name"); row->push_back("part"); row->push_back("type"); row->push_back("divisor/values"); + if (m_withRange) { + row->push_back("range"); + } row->push_back("unit"); row->push_back("comment"); return RESULT_OK; @@ -86,6 +89,7 @@ class TestReader : public MappedFileReader { const DataFieldTemplates* m_templates; const bool m_isSet; const bool m_isMasterDest; + const bool m_withRange; public: const DataField* m_fields; }; @@ -101,7 +105,7 @@ int main() { // entry: definition, decoded value, master data, slave data, flags // definition: name,part,type[:len][,[divisor|values][,[unit][,[comment]]]] unsigned int baseLine = __LINE__+1; - string checks[][5] = { + string checks[][6] = { {"x,,ign:10", "", "10fe07000a00000000000000000000", "00", ""}, {"x,,ign:*", "", "10fe07000a00000000000000000000", "00", "W"}, {"x,,ign,2", "", "", "", "c"}, @@ -344,6 +348,13 @@ int main() { {"x,,uch,==48", "", "10feffff01ab", "00", "rW"}, {"x,,uch,=48", "", "10feffff0130", "00", ""}, {"x,,uch,==48", "", "10feffff0130", "00", ""}, + {"x,,uch,,1-3", "2", "10feffff0102", "00", "-"}, + {"x,,uch,,1-3", "4", "10feffff0102", "00", "-Rw:ERR: argument value out of valid range"}, + {"x,,uch,,1-3", "2", "10feffff0104", "00", "-rW:ERR: argument value out of valid range"}, + {"x,,uch,,0x1-0x3", "4","10feffff0102", "00", "-Rw:ERR: argument value out of valid range"}, + {"x,,uch,,0x1-0x3", "2","10feffff0104", "00", "-rW:ERR: argument value out of valid range"}, + {"x,,uch", "\n \"x\": {\"value\": 2}", "10feffff0102", "00", "-jV", "\n { \"name\": \"x\", \"slave\": false, \"type\": \"UCH\", \"isbits\": false, \"isadjustable\": false, \"isignored\": false, \"isreverse\": false, \"length\": 1, \"result\": \"number\", \"min\": 0, \"max\": 254, \"step\": 1, \"unit\": \"\", \"comment\": \"\"}"}, + {"x,,uch,,1-3:2", "\n \"x\": {\"value\": 2}", "10feffff0102", "00", "-jV", "\n { \"name\": \"x\", \"slave\": false, \"type\": \"UCH\", \"isbits\": false, \"isadjustable\": false, \"isignored\": false, \"isreverse\": false, \"length\": 1, \"result\": \"number\", \"min\": 1, \"max\": 3, \"step\": 2, \"unit\": \"\", \"comment\": \"\"}"}, {"x,,sch", "-90", "10feffff01a6", "00", ""}, {"x,,sch", "0", "10feffff0100", "00", ""}, {"x,,sch", "-1", "10feffff01ff", "00", ""}, @@ -352,6 +363,15 @@ int main() { {"x,,sch", "127", "10feffff017f", "00", ""}, {"x,,sch,10", "-9.0", "10feffff01a6", "00", ""}, {"x,,sch,-10", "-900", "10feffff01a6", "00", ""}, + {"x,,sch,,1-3", "2", "10feffff0102", "00", "-"}, + {"x,,sch,,1-500", "-", "10feffff0180", "00", "-c"}, + {"x,,sch,,-130-1", "-", "10feffff0180", "00", "-c"}, + {"x,,sch,,-127-127", "-", "10feffff0180", "00", "-"}, + {"x,,sch,,-127-128", "-", "10feffff0180", "00", "-c"}, + {"x,,sch,,-128-127", "-", "10feffff0180", "00", "-c"}, + {"x,,sch,,1-3", "4", "10feffff0102", "00", "-Rw:ERR: argument value out of valid range"}, + {"x,,sch,,1-3", "2", "10feffff0104", "00", "-rW:ERR: argument value out of valid range"}, + {"x,,sch,,-3--1", "-4", "10feffff01fe", "00", "-Rw:ERR: argument value out of valid range"}, {"x,,d1b", "-90", "10feffff01a6", "00", ""}, {"x,,d1b", "0", "10feffff0100", "00", ""}, {"x,,d1b", "-1", "10feffff01ff", "00", ""}, @@ -429,6 +449,12 @@ int main() { {"x,,flt", "-", "10feffff020080", "00", ""}, {"x,,flt", "-32.767", "10feffff020180", "00", ""}, {"x,,flt", "32.767", "10feffff02ff7f", "00", ""}, + {"x,,flt,,1-3", "2.000", "10feffff02d007", "00", "-"}, + {"x,,flt,,1-3", "4.000", "10feffff02d007", "00", "-Rw:ERR: argument value out of valid range"}, + {"x,,flt,,1-3", "2.000", "10feffff02a00f", "00", "-rW:ERR: argument value out of valid range"}, + {"x,,flt,,-3--1", "-4", "10feffff0230f8", "00", "-Rw:ERR: argument value out of valid range"}, // -4:60f0, -2:30f8 + {"x,,flt,,-3.1--1.0", "-4", "10feffff0230f8", "00", "-Rw:ERR: argument value out of valid range"}, + {"x,,flt,,-3.1--1.0", "-2", "10feffff0260f0", "00", "-rW:ERR: argument value out of valid range"}, {"x,,flr", "-0.090", "10feffff02ffa6", "00", ""}, {"x,,flr", "0.000", "10feffff020000", "00", ""}, {"x,,flr", "-0.001", "10feffff02ffff", "00", ""}, @@ -445,6 +471,11 @@ int main() { {"x,,exp", "0.25", "10feffff040000803e", "00", ""}, {"x,,exp", "0.95", "10feffff043333733f", "00", ""}, {"x,,exp", "0.65", "10feffff046666263f", "00", ""}, + {"x,,exp", "0.065", "10feffff04b81e853d", "00", ""}, + {"x,,exp,,0-0.65", "0.65", "10feffff046666263f", "00", "-"}, + {"x,,exp,,0-0.5", "0.65", "10feffff046666263f", "00", "-rw:ERR: argument value out of valid range"}, + {"x,,exp,10,0-0.065", "0.0650000", "10feffff046666263f", "00", "-"}, + {"x,,exp,10,0-0.05", "0.0650000", "10feffff046666263f", "00", "-rw:ERR: argument value out of valid range"}, {"x,,exr", "-0.09", "10feffff04bdb851ec", "00", ""}, {"x,,exr", "0.0", "10feffff0400000000", "00", ""}, {"x,,exr", "-0.001", "10feffff04ba83126f", "00", ""}, @@ -577,6 +608,12 @@ int main() { continue; } string flags = check[4]; + size_t colon = flags.find(':'); + string errStr; + if (colon != string::npos) { + errStr = flags.substr(colon+1); + flags = flags.substr(0, colon); + } bool isSet = flags.find('s') != string::npos; bool testFields = flags.find('F') != string::npos; bool failedCreate = flags.find('c') != string::npos; @@ -584,6 +621,7 @@ int main() { bool failedReadMatch = flags.find('R') != string::npos; bool failedWrite = flags.find('w') != string::npos; bool failedWriteMatch = flags.find('W') != string::npos; + string withDump = check[5]; // optional const char* findName = flags.find('I') == string::npos ? nullptr : "x"; ssize_t findIndex = -1; if (flags.find('i') != string::npos) { @@ -599,6 +637,9 @@ int main() { if (flags.find("vvv") != string::npos) { verbosity |= OF_COMMENTS; } + if (flags.find("V") != string::npos) { + verbosity |= OF_NAMES|OF_UNITS|OF_COMMENTS|OF_ALL_ATTRS; + } if (flags.find('j') != string::npos) { verbosity |= OF_JSON; } @@ -620,7 +661,8 @@ int main() { } continue; } - TestReader reader{templates, isSet, mstr[1] == BROADCAST || isMaster(mstr[1])}; + bool withRange = flags.find('-') != string::npos; + TestReader reader{templates, isSet, mstr[1] == BROADCAST || isMaster(mstr[1]), withRange}; lineNo = 0; dummystr.clear(); dummystr.str("#"); @@ -638,6 +680,9 @@ int main() { if (result == RESULT_OK) { cout << "\"" << check[0] << "\": failed create error: unexpectedly succeeded" << endl; error = true; + } else if (!errStr.empty() && errorDescription != errStr) { + cout << "\"" << check[0] << "\": failed create error: unexpected result \"" << errorDescription << "\" instead of \"" << errStr << "\"" << endl; + error = true; } else { cout << "\"" << check[0] << "\": failed create OK" << endl; } @@ -680,6 +725,10 @@ int main() { cout << " failed read " << fields->getName(-1) << " >" << check[2] << " " << check[3] << "< error: unexpectedly succeeded" << endl; error = true; + } else if (!errStr.empty() && getResultCode(result) != errStr) { + cout << " failed read " << fields->getName(-1) << " >" << check[2] << " " << check[3] + << "< error: unexpected result \""; + cout << getResultCode(result) << "\" instead of \"" << errStr << "\"" << endl; } else { cout << " failed read " << fields->getName(-1) << " >" << check[2] << " " << check[3] << "< OK" << endl; @@ -744,6 +793,10 @@ int main() { cout << " failed write " << fields->getName(-1) << " >" << expectStr << "< error: unexpectedly succeeded" << endl; error = true; + } else if (!errStr.empty() && getResultCode(result) != errStr) { + cout << " failed write " << fields->getName(-1) << " >" + << "< error: unexpected result \""; + cout << getResultCode(result) << "\" instead of \"" << errStr << "\"" << endl; } else { cout << " failed write " << fields->getName(-1) << " >" << expectStr << "< OK" << endl; @@ -760,6 +813,13 @@ int main() { writeMstr.getStr() + " " + writeSstr.getStr()); } } + if (!withDump.empty()) { + output.clear(); + output.str(""); + fields->dump(false, verbosity, &output); + bool match = output.str() == withDump; + verify(false, "dump", withDump, match, withDump, output.str()); + } delete fields; fields = nullptr; } From 2b3aa086733b25dcef8007a27ab764a5028d879f Mon Sep 17 00:00:00 2001 From: John Date: Tue, 7 Jan 2025 08:21:26 +0100 Subject: [PATCH 315/345] fix previous commit for i386, optimize comparison --- src/lib/ebus/datatype.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/lib/ebus/datatype.cpp b/src/lib/ebus/datatype.cpp index a617bb398..62940b37c 100755 --- a/src/lib/ebus/datatype.cpp +++ b/src/lib/ebus/datatype.cpp @@ -1205,23 +1205,24 @@ result_t NumberDataType::parseInput(const string inputStr, unsigned int* parsedV return RESULT_ERR_INVALID_NUM; } } else { - unsigned int maxBit = m_bitCount != 32 ? 1 << m_bitCount : 0; const char* str = inputStr.c_str(); char* strEnd = nullptr; if (m_divisor == 1) { if (hasFlag(SIG)) { long signedValue = strtol(str, &strEnd, 0); - if (errno == ERANGE || (maxBit && (signedValue < -(maxBit/2L) || signedValue >= maxBit/2L))) { + if (errno == ERANGE + || (m_bitCount != 32 && (signedValue < 0L ? (signedValue < -(1L << (m_bitCount - 1))) : (signedValue >= (1L << (m_bitCount - 1))))) + ) { return RESULT_ERR_OUT_OF_RANGE; // value out of range } if (signedValue < 0 && m_bitCount != 32) { - value = (unsigned int)(signedValue + maxBit); + value = (unsigned int)(signedValue + (1L << m_bitCount)); } else { value = (unsigned int)signedValue; } } else { value = (unsigned int)strtoul(str, &strEnd, 0); - if (errno == ERANGE || (maxBit && value >= maxBit)) { + if (errno == ERANGE || (m_bitCount != 32 && value >= (1U << m_bitCount))) { return RESULT_ERR_OUT_OF_RANGE; } } @@ -1240,10 +1241,10 @@ result_t NumberDataType::parseInput(const string inputStr, unsigned int* parsedV } if (hasFlag(SIG)) { double max = exp2(m_bitCount - 1); - if (dvalue < -max || dvalue >= max) { + if (dvalue < 0.0 ? (dvalue < -max) : (dvalue >= max)) { return RESULT_ERR_OUT_OF_RANGE; // value out of range } - if (dvalue < 0 && m_bitCount != 32) { + if (dvalue < 0.0 && m_bitCount != 32) { value = static_cast(dvalue + (1 << m_bitCount)); } else { value = static_cast(dvalue); From 0445e4412e06409e5c1ee1b78ca0597dc065adcf Mon Sep 17 00:00:00 2001 From: John Date: Thu, 9 Jan 2025 20:44:34 +0100 Subject: [PATCH 316/345] fix parsing unexpected mdns txt response --- src/lib/utils/tcpsocket.cpp | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/lib/utils/tcpsocket.cpp b/src/lib/utils/tcpsocket.cpp index 01a94dbfe..243ab79a8 100755 --- a/src/lib/utils/tcpsocket.cpp +++ b/src/lib/utils/tcpsocket.cpp @@ -579,21 +579,31 @@ int resolveMdnsOneShot(const char* url, mdns_oneshot_t *result, mdns_oneshot_t * printf(" %s=%s\n", (atype == DNS_TYPE_TXT) ? "txt" : "ptr", name); #endif if (atype == DNS_TYPE_TXT && name[0]) { + // parse id=xxxxxxxxxxxx[.proto=xxx] char* sep = strchr(name, '='); char* sep2; - if (sep && strncmp(name, "id", sep-name) == 0) { - sep2 = strchr(name, '.'); + if (sep && sep-name == 2 && strncmp(name, "id", 2) == 0) { + sep2 = strchr(sep+1, '.'); + if (!sep2) { + sep2 = name + pos; + } if (sep2-sep-1 == sizeof(mdns_oneshot_t::id)-1) { memcpy(id, sep+1, sizeof(mdns_oneshot_t::id)-1); } else { sep = nullptr; } - sep = sep ? strchr(sep2+1, '=') : nullptr; + sep = sep && sep2 < name + pos ? strchr(sep2+1, '=') : nullptr; + } else { + sep2 = name - 1; } - if (sep && strncmp(sep2+1, "proto", sep-sep2-1) == 0 && ( - pos == (size_t)(sep-name)+1+sizeof(mdns_oneshot_t::proto)-1 - || strchr(sep+1, '.') == sep+1+sizeof(mdns_oneshot_t::proto)-1)) { - memcpy(proto, sep+1, sizeof(mdns_oneshot_t::proto)-1); + if (sep && sep-sep2-1 == 5 && strncmp(sep2+1, "proto", 5) == 0) { + sep2 = strchr(sep+1, '.'); + if (!sep2) { + sep2 = name + pos; + } + if (sep2-sep-1 == sizeof(mdns_oneshot_t::proto)-1) { + memcpy(proto, sep+1, sizeof(mdns_oneshot_t::proto)-1); + } } } } else if (atype == DNS_TYPE_SRV && rdLen >= sizeof(dns_rr_srv_t)) { From 34552eab61d3ed605bf99cb1324783115fd11bab Mon Sep 17 00:00:00 2001 From: John Date: Sun, 12 Jan 2025 10:11:04 +0100 Subject: [PATCH 317/345] fix ignored message id key for NN>3, fix longer message key check (fixes #1436), add log message on exceeded max length --- src/ebusd/main.cpp | 5 ++++- src/lib/ebus/message.cpp | 15 +++++++++------ src/lib/ebus/message.h | 11 ++++------- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index 5b24a0fe1..de478d47a 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -408,7 +408,10 @@ int main(int argc, char* argv[], char* envp[]) { fout.close(); } } - + if (overallResult == RESULT_OK && s_messageMap->getMaxIdLength() > 7) { + logNotice(lf_main, "max message ID length exceeded"); + overallResult = RESULT_CONTINUE; + } cleanup(); return overallResult == RESULT_OK ? EXIT_SUCCESS : EXIT_FAILURE; } diff --git a/src/lib/ebus/message.cpp b/src/lib/ebus/message.cpp index 5d2812260..956a70bbf 100644 --- a/src/lib/ebus/message.cpp +++ b/src/lib/ebus/message.cpp @@ -180,7 +180,7 @@ uint64_t Message::createKey(const vector& id, bool isWrite, bool isPas int exp = 5; for (const auto it : id) { key ^= (uint64_t)it << (8 * exp--); - if (exp == 0) { + if (exp < 0) { exp = 3; } } @@ -206,7 +206,7 @@ uint64_t Message::createKey(const MasterSymbolString& master, size_t maxIdLength int exp = 3; for (size_t i = 0; i < idLength; i++) { key ^= (uint64_t)master.dataAt(i) << (8 * exp--); - if (exp == 0) { + if (exp < 0) { exp = 3; } } @@ -2712,13 +2712,12 @@ Message* MessageMap::find(const MasterSymbolString& master, bool anyDestination, if (anyDestination && master.size() >= 5 && master[4] == 0 && master[2] == 0x07 && master[3] == 0x04) { return m_scanMessage; } - uint64_t baseKey = Message::createKey(master, - anyDestination || master[1] != BROADCAST ? m_maxIdLength : m_maxBroadcastIdLength, anyDestination); + size_t maxIdLength = anyDestination || master[1] != BROADCAST ? m_maxIdLength : m_maxBroadcastIdLength; + uint64_t baseKey = Message::createKey(master, maxIdLength, 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; if (idLength == maxIdLength) { @@ -2728,7 +2727,7 @@ Message* MessageMap::find(const MasterSymbolString& master, bool anyDestination, int exp = 3; for (size_t i = 0; i < idLength; i++) { key ^= (uint64_t)master.dataAt(i) << (8 * exp--); - if (exp == 0) { + if (exp < 0) { exp = 3; } } @@ -2907,6 +2906,10 @@ void MessageMap::dump(bool withConditions, OutputFormat outputFormat, ostream* o *output << (m_addAll ? "[" : "{"); } else { Message::dumpHeader(nullptr, output); + if (m_addAll) { + *output << endl << "# max ID length: " << static_cast(m_maxIdLength) + << " (broadcast only: " << static_cast(m_maxBroadcastIdLength) << ")"; + } } if (!(outputFormat & OF_SHORT)) { *output << endl; diff --git a/src/lib/ebus/message.h b/src/lib/ebus/message.h index ca862ee9d..b0a303b7e 100644 --- a/src/lib/ebus/message.h +++ b/src/lib/ebus/message.h @@ -158,13 +158,6 @@ class Message : public AttributedItem { */ static uint64_t createKey(symbol_t pb, symbol_t sb, bool broadcast); - /** - * Get the length field from the key. - * @param key the key. - * @return the length field from the key. - */ - static size_t getKeyLength(uint64_t key) { return (size_t)(key >> (8 * 7 + 5)); } - /** * Parse an ID part from the input @a string. * @param input the input @a string, hex digits optionally separated by space. @@ -1651,6 +1644,10 @@ class MessageMap : public MappedFileReader { */ void dump(bool withConditions, OutputFormat outputFormat, ostream* output) const; + /** + * @return the maximum ID length used by any of the known @a Message instances. + */ + size_t getMaxIdLength() const { return m_maxIdLength; } private: /** empty vector for @a getLoadedFiles(). */ From 4ebecb10e25542881a6460cd038c006560ad4af9 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 12 Jan 2025 22:04:42 +0100 Subject: [PATCH 318/345] add dump with id key --- src/ebusd/main_args.cpp | 6 ++++-- src/lib/ebus/message.cpp | 3 +++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/ebusd/main_args.cpp b/src/ebusd/main_args.cpp index 250fb7d63..d68037067 100755 --- a/src/ebusd/main_args.cpp +++ b/src/ebusd/main_args.cpp @@ -165,7 +165,7 @@ static const argDef argDefs[] = { "Prefer LANG in multilingual configuration files [system default language, DE as fallback]"}, {"checkconfig", O_CHKCFG, nullptr, ARG_NO_ENV, "Check config files, then stop"}, {"dumpconfig", O_DMPCFG, "FORMAT", af_optional|ARG_NO_ENV, - "Check and dump config files in FORMAT (\"json\" or \"csv\"), then stop"}, + "Check and dump config files in FORMAT (\"json\", \"csv\", or \"csvall\" for CSV with all attributes), then stop"}, {"dumpconfigto", O_DMPCTO, "FILE", 0, "Dump config files to FILE"}, {"pollinterval", O_POLINT, "SEC", 0, "Poll for data every SEC seconds (0=disable) [5]"}, {"inject", 'i', "stop", af_optional|ARG_NO_ENV, "Inject remaining arguments as commands or already seen messages " @@ -314,10 +314,12 @@ static int parse_opt(int key, char *arg, const argParseOpt *parseOpt, struct opt case O_CHKCFG: // --checkconfig opt->checkConfig = true; break; - case O_DMPCFG: // --dumpconfig[=json|csv] + case O_DMPCFG: // --dumpconfig[=json|csv|csvall] if (!arg || arg[0] == 0 || strcmp("csv", arg) == 0) { // no further flags opt->dumpConfig = OF_DEFINITION; + } else if (strcmp("csvall", arg) == 0) { + opt->dumpConfig = OF_DEFINITION | OF_ALL_ATTRS; } else if (strcmp("json", arg) == 0) { opt->dumpConfig = OF_DEFINITION | OF_NAMES | OF_UNITS | OF_COMMENTS | OF_VALUENAME | OF_ALL_ATTRS | OF_JSON; } else { diff --git a/src/lib/ebus/message.cpp b/src/lib/ebus/message.cpp index 956a70bbf..ad84cbddf 100644 --- a/src/lib/ebus/message.cpp +++ b/src/lib/ebus/message.cpp @@ -941,6 +941,9 @@ void Message::dumpField(const string& fieldName, bool withConditions, OutputForm for (auto it = m_id.begin()+2; it < m_id.end(); it++) { *output << hex << setw(2) << setfill('0') << static_cast(*it); } + if (outputFormat & OF_ALL_ATTRS) { + *output << "=" << hex << setw(0) << setfill('0') << static_cast(m_key); + } return; } if (fieldName == "fields") { From a8f876d48b8c42e3551e0c6d62c87cfb47bcdebf Mon Sep 17 00:00:00 2001 From: John Date: Tue, 21 Jan 2025 21:03:54 +0100 Subject: [PATCH 319/345] reduce to cdnversions only --- contrib/updatecheck/calcversions.sh | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/contrib/updatecheck/calcversions.sh b/contrib/updatecheck/calcversions.sh index fc485e24a..3b0a646e1 100755 --- a/contrib/updatecheck/calcversions.sh +++ b/contrib/updatecheck/calcversions.sh @@ -1,21 +1,12 @@ #!/bin/sh version=`head -n 1 ../../VERSION` revision=`git describe --always` -echo "ebusd=${version},${revision}" > versions.txt +echo "ebusd=${version},${revision}" > cdnversions.txt devver=`curl -s https://adapter.ebusd.eu/v31/firmware/ChangeLog|grep "Version "|head -n 1|sed -e 's#.*]*>##' -e 's#<.*##' -e 's# ##'` devbl=`curl -s https://adapter.ebusd.eu/v31/firmware/ChangeLog|grep "Bootloader version"|head -n 1|sed -e 's#.*]*>##' -e 's#<.*##' -e 's# ##'` -echo "device=${devver},${devbl}" >> versions.txt +echo "device=${devver},${devbl}" >> cdnversions.txt devver=`curl -s https://adapter.ebusd.eu/v5/ChangeLog|grep "Version "|head -n 1|sed -e 's#.*id="\([^"]*\)".*]*>\([^<]*\)<.*#\2,\2,\1#'` -echo "device=${devver}" >> versions.txt -cp versions.txt cdnversions.txt -files=`find config/de -type f -or -type l` -../../src/lib/ebus/test/test_filereader $files|sed -e 's#^config/de/##' -e 's#^\([^ ]*\) #\1=#' -e 's# #,#g'|sort >> versions.txt -files=`find config/en -type f -or -type l` -for filever in $(../../src/lib/ebus/test/test_filereader $files|sed -e 's#^config/en/##' -e 's#^\([^ ]*\) #\1=#' -e 's# #,#g'); do - file=${filever%%=*} - ver=${filever#*=} - sed -i -e "s#^$file=\(.*\)\$#$file=\1,$ver#" versions.txt -done +echo "device=${devver}" >> cdnversions.txt curl -sS https://ebus.github.io/en/versions.json -o veren.json curl -sS https://ebus.github.io/de/versions.json -o verde.json node -e 'fs=require("fs");e=JSON.parse(fs.readFileSync("veren.json","utf-8"));d=JSON.parse(fs.readFileSync("verde.json","utf-8"));console.log(Object.entries(d).map(([k,de])=>{en=e[k];return `${k}=${de.hash},${de.size},${de.mtime},${en.hash},${en.size},${en.mtime}`;}).join("\n"))'|sort >> cdnversions.txt From 1a2c810fdc40f9c46ccfca92449174d16c93c5c6 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 26 Jan 2025 07:53:43 +0100 Subject: [PATCH 320/345] avoid poll on scan messages --- src/lib/ebus/message.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/ebus/message.cpp b/src/lib/ebus/message.cpp index ad84cbddf..aabb04dbe 100644 --- a/src/lib/ebus/message.cpp +++ b/src/lib/ebus/message.cpp @@ -111,7 +111,7 @@ Message::Message(const string& filename, const string& circuit, const string& le m_pollPriority(pollPriority), m_usedByCondition(false), m_isScanMessage(false), m_condition(condition), m_availableSinceTime(0), m_dataHandlerState(0), m_lastUpdateTime(0), m_lastChangeTime(0), m_pollOrder(0), m_lastPollTime(0) { - if (circuit == "scan") { + if (strcasecmp(circuit.c_str(), "scan") == 0) { setScanMessage(); m_pollPriority = 0; } From 1965639f0781c847d7719b0ad3425fd90155a919 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 26 Jan 2025 20:37:18 +0100 Subject: [PATCH 321/345] add getter to grabbed message --- src/ebusd/bushandler.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ebusd/bushandler.h b/src/ebusd/bushandler.h index ddf253baf..b27bac3cc 100755 --- a/src/ebusd/bushandler.h +++ b/src/ebusd/bushandler.h @@ -211,6 +211,12 @@ class GrabbedMessage { */ MasterSymbolString& getLastMasterData() { return m_lastMaster; } + /** + * Get the last @a SlaveSymbolString. + * @return the last @a SlaveSymbolString. + */ + SlaveSymbolString& getLastSlaveData() { return m_lastSlave; } + /** * Dump the last received data and message count to the output. * @param unknown whether to dump only if this message is unknown. From d8b2ae1bc89b4c63191f5200c5ab50425ac7160d Mon Sep 17 00:00:00 2001 From: John Date: Fri, 21 Feb 2025 06:37:03 +0100 Subject: [PATCH 322/345] fix constant field dump --- src/lib/ebus/data.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/ebus/data.cpp b/src/lib/ebus/data.cpp index 2f745ad7a..0deb83203 100644 --- a/src/lib/ebus/data.cpp +++ b/src/lib/ebus/data.cpp @@ -965,7 +965,7 @@ void ConstantDataField::dump(bool prependFieldSeparator, OutputFormat outputForm dumpPrefix(prependFieldSeparator, outputFormat, output); // no divisor appended since it is not allowed for ConstantDataField if (outputFormat & OF_JSON) { - appendJson(false, "value", m_value, true, output); + appendJson(true, "value", m_value, true, output); *output << ", \"verify\": " << (m_verify ? "true" : "false"); } else { *output << (m_verify?"==":"=") << m_value; From b7a65fc875366ed3fff126065c033b952739e5ea Mon Sep 17 00:00:00 2001 From: John Date: Sun, 13 Apr 2025 16:06:46 +0200 Subject: [PATCH 323/345] treat write message as read for mqtt when excluded by config (fixes #1113) --- src/ebusd/mqtthandler.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/ebusd/mqtthandler.cpp b/src/ebusd/mqtthandler.cpp index 312d33c3a..a97767639 100755 --- a/src/ebusd/mqtthandler.cpp +++ b/src/ebusd/mqtthandler.cpp @@ -793,13 +793,17 @@ void MqttHandler::run() { bool includeActiveWrite = FileReader::matches("w", filterDirection, true, true); for (const auto& message : messages) { bool checkPollAdjust = false; + bool isWrite = message->isWrite(); + bool isPassive = message->isPassive(); + // at least treat as passive read if write direction is excluded + bool treatAsPassiveRead = !includeActiveWrite && isWrite; if (filterSeen > 0) { if (message->getLastUpdateTime() == 0) { - if (message->isPassive()) { + if (isPassive || treatAsPassiveRead) { // only wait for data on passive messages continue; // no data ever } - if (!message->isWrite()) { + if (!isWrite) { // only wait for data on read messages or set their poll prio if (filterSeen > 1 && (!message->getPollPriority() || message->getPollPriority() > filterSeen) && (filterPriority == 0 || filterSeen <= filterPriority) @@ -830,7 +834,7 @@ void MqttHandler::run() { || !FileReader::matches(message->getLevel(), filterLevel, true, true)) { continue; } - const string direction = directionNames[(message->isWrite() ? 2 : 0) + (message->isPassive() ? 1 : 0)]; + const string direction = treatAsPassiveRead ? "uw" : directionNames[(isWrite ? 2 : 0) + (isPassive ? 1 : 0)]; if (!FileReader::matches(direction, filterDirection, true, true)) { continue; } @@ -844,9 +848,9 @@ void MqttHandler::run() { continue; } if (includeActiveWrite) { - if (message->isWrite()) { + if (isWrite) { bool skipMultiFieldWrite = (!m_hasDefinitionFieldsPayload || m_publishByField) - && !message->isPassive() && message->getFieldCount() > 1; + && !isPassive && message->getFieldCount() > 1; if (skipMultiFieldWrite) { // multi-field message is not writable when publishing by field or combining // multiple fields in one definition, so skip it From 5823d4fc529dbf91c85f54a7175beaaaddc8dbe1 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 13 Apr 2025 16:22:02 +0200 Subject: [PATCH 324/345] fix outsidetemp def --- contrib/etc/ebusd/broadcast.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/etc/ebusd/broadcast.csv b/contrib/etc/ebusd/broadcast.csv index faef2cf1e..b4f69a71d 100644 --- a/contrib/etc/ebusd/broadcast.csv +++ b/contrib/etc/ebusd/broadcast.csv @@ -2,7 +2,7 @@ *r,broadcast,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, *b,broadcast,,,,FE,,,,,,,,,,,,,,,,,,,,,,,,,, *w,broadcast,,,,FE,,,,,,,,,,,,,,,,,,,,,,,,,, -b,,datetime,date/time,,,0700,,outsidetemp,,D2B,,,°C,time,,BTI,,,,date,,BDA,,,,,,,,, +b,,datetime,date/time,,,0700,,outsidetemp,,D2B,,°C,,time,,BTI,,,,date,,BDA,,,,,,,,, r;b,,id,identification,,,0704,,manufacturer,,UCH,0x06=Dungs;0x0f=FH Ostfalia;0x10=TEM;0x11=Lamberti;0x14=CEB;0x15=Landis-Staefa;0x16=FERRO;0x17=MONDIAL;0x18=Wikon;0x19=Wolf;0x20=RAWE;0x30=Satronic;0x40=ENCON;0x50=Kromschröder;0x60=Eberle;0x65=EBV;0x75=Grässlin;0x85=ebm-papst;0x95=SIG;0xa5=Theben;0xa7=Thermowatt;0xb5=Vaillant;0xc0=Toby;0xc5=Weishaupt;0xfd=ebusd.eu,,device manufacturer,id,,STR:5,,,device id,software,,PIN,,,software version,hardware,,PIN,,,hardware version w,,queryexistence,Inquiry of existence,,,07FE,,,,,,,,,,,,,,,,,,,,,,,,, b,,signoflife,sign of life,,,07FF,,,,,,,,,,,,,,,,,,,,,,,,, From f27464f3cd52997142c8c6c6e29c636d83cb8aae Mon Sep 17 00:00:00 2001 From: John Date: Sun, 13 Apr 2025 17:02:20 +0200 Subject: [PATCH 325/345] add ignore case option to match() --- src/lib/ebus/stringhelper.cpp | 14 +++++++++++--- src/lib/ebus/stringhelper.h | 3 ++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/lib/ebus/stringhelper.cpp b/src/lib/ebus/stringhelper.cpp index 6a80a328e..3227e1dea 100644 --- a/src/lib/ebus/stringhelper.cpp +++ b/src/lib/ebus/stringhelper.cpp @@ -306,8 +306,12 @@ bool StringReplacer::checkMatchability() const { return true; } -ssize_t StringReplacer::match(const string& str, string* circuit, string* name, string* field, - const string& separator) const { +ssize_t StringReplacer::match(const string& strIn, string* circuit, string* name, string* field, + const string& separator, bool ignoreCase) const { + string str = strIn; + if (ignoreCase) { + FileReader::tolower(&str); + } size_t last = 0; size_t count = m_parts.size(); size_t idx; @@ -323,7 +327,11 @@ ssize_t StringReplacer::match(const string& str, string* circuit, string* name, } string value; if (idx+1 < count) { - size_t pos = str.find(m_parts[idx+1].first, last); + string chk = m_parts[idx+1].first; + if (ignoreCase) { + FileReader::tolower(&chk); + } + size_t pos = str.find(chk, last); if (pos == string::npos) { // next part not found, consume the rest and mark incomplete value = str.substr(last); diff --git a/src/lib/ebus/stringhelper.h b/src/lib/ebus/stringhelper.h index a7576f685..b31063e75 100644 --- a/src/lib/ebus/stringhelper.h +++ b/src/lib/ebus/stringhelper.h @@ -151,9 +151,10 @@ class StringReplacer { * @param name pointer to the string receiving the message name if present. * @param field pointer to the string receiving the field name if present. * @param separator the separator expected in the extra non-matched non-field parts (default slash). + * @param ignoreCase true to ignore case. * @return the index of the last unmatched part, or the negative index minus one for extra non-matched non-field parts. */ - ssize_t match(const string& str, string* circuit, string* name, string* field, const string& separator = "/") const; + ssize_t match(const string& str, string* circuit, string* name, string* field, const string& separator = "/", bool ignoreCase = false) const; private: /** From 177e4740671dfcb08d4d6e70b876feed137501c1 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 13 Apr 2025 17:10:47 +0200 Subject: [PATCH 326/345] updated --- ChangeLog.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index 55fd6022d..dcf6fe0d4 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -5,10 +5,18 @@ * fix dump of divisor * fix max value for S3N, S3N, SLG, and SLR types * fix socket options for KNXnet/IP integration +* fix constant encoding in json +* fix parsing unexpected mDNS response +* fix longer message key and check +* fix unnecessary poll on scan messages ## Features * add "-m" option to "encode" and "decode" commands * add output for commands executed with "--inject=stop" +* add secondary replacement value for date types +* add value range and step support for numeric types +* add numeric base types without replacement value (e.g. "U1L", "S1L", "U2L", "S2L", "U2B", "S2B", etc.) +* add also include write messages as read-only ones in MQTT definition topic if writes are excluded # 24.1 (2024-10-27) From 5c61f472f21997fa1c36ecd2990dad7ce01e4bc1 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 13 Apr 2025 17:25:37 +0200 Subject: [PATCH 327/345] fix knxd inclusion --- contrib/docker/Dockerfile | 4 ++-- contrib/docker/update.sh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/docker/Dockerfile b/contrib/docker/Dockerfile index 64679fc7b..3ba4f3d89 100755 --- a/contrib/docker/Dockerfile +++ b/contrib/docker/Dockerfile @@ -3,7 +3,7 @@ ARG BASE_IMAGE FROM $BASE_IMAGE AS build RUN apt-get update && apt-get install -y \ - knxd-dev knxd- libmosquitto-dev libssl-dev libstdc++6 libc6 libgcc1 \ + knxd-dev knxd libmosquitto-dev libssl-dev libstdc++6 libc6 libgcc1 \ curl \ autoconf automake g++ make git \ && rm -rf /var/lib/apt/lists/* @@ -28,7 +28,7 @@ RUN RUNTEST=full GIT_REVISION=$GIT_REVISION ./make_debian.sh --with-knxd FROM $BASE_IMAGE-slim AS image RUN apt-get update && apt-get install -y \ - knxd-dev knxd- libmosquitto1 libssl1.1 ca-certificates libstdc++6 libc6 libgcc1 \ + knxd-dev knxd libmosquitto1 libssl1.1 ca-certificates libstdc++6 libc6 libgcc1 \ && rm -rf /var/lib/apt/lists/* LABEL maintainer="ebusd@ebusd.eu" diff --git a/contrib/docker/update.sh b/contrib/docker/update.sh index 529b47e6e..914055b4f 100755 --- a/contrib/docker/update.sh +++ b/contrib/docker/update.sh @@ -17,7 +17,7 @@ function replaceTemplate () { # devel update version_variant='-devel' make='RUNTEST=full GIT_REVISION=\$GIT_REVISION ./make_debian.sh --with-knxd' -extrapkgs='knxd-dev knxd- ' +extrapkgs='knxd-dev knxd ' copydeb='COPY --from=build /build/ebusd-*_mqtt1.deb ebusd.deb' debsrc='ebusd.deb \&\& rm -f ebusd.deb' copyentry='COPY --from=build /build/contrib/docker/docker-entrypoint.sh /' From ff340bed11a6778c659dacc71ff5a336c8880616 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 13 Apr 2025 18:09:42 +0200 Subject: [PATCH 328/345] formatting --- src/ebusd/mainloop.cpp | 4 ++-- src/lib/ebus/data.cpp | 2 +- src/lib/ebus/data.h | 4 ++-- src/lib/ebus/datatype.cpp | 14 +++++++++----- src/lib/ebus/stringhelper.h | 3 ++- 5 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index c10191c00..7835e8afc 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -916,8 +916,8 @@ result_t MainLoop::executeRead(const vector& args, const string& levels, if (verbosity & OF_NAMES) { *ostream << message->getCircuit() << " " << message->getName() << " "; } - ret = message->decodeLastData(pt_slaveData, false, fieldIndex == -2 ? nullptr : fieldName.c_str(), fieldIndex, verbosity, - ostream); + ret = message->decodeLastData(pt_slaveData, false, fieldIndex == -2 ? nullptr : fieldName.c_str(), fieldIndex, + verbosity, ostream); if (ret < RESULT_OK) { logError(lf_main, "read %s %s: decode %s", message->getCircuit().c_str(), message->getName().c_str(), getResultCode(ret)); diff --git a/src/lib/ebus/data.cpp b/src/lib/ebus/data.cpp index 0deb83203..2a0c3661c 100644 --- a/src/lib/ebus/data.cpp +++ b/src/lib/ebus/data.cpp @@ -409,7 +409,7 @@ result_t DataField::create(bool isWriteMessage, bool isTemplate, bool isBroadcas } if (result == RESULT_OK) { result = numType->derive(from, to, inc, &numType); - }; + } if (result != RESULT_OK) { *errorDescription = "\""+rangeStr+"\" in field "+formatInt(fieldIndex); result = RESULT_ERR_OUT_OF_RANGE; diff --git a/src/lib/ebus/data.h b/src/lib/ebus/data.h index ed540d612..3697a7453 100755 --- a/src/lib/ebus/data.h +++ b/src/lib/ebus/data.h @@ -795,8 +795,8 @@ class LoadableDataFieldSet : public DataFieldSet, public MappedFileReader { * @param isWrite true for a write message, false for read. */ LoadableDataFieldSet(const string& name, DataFieldTemplates* templates, bool isWrite) - : DataFieldSet(name, vector()), MappedFileReader(false), m_templates(templates), m_isWrite(isWrite) { - } + : DataFieldSet(name, vector()), MappedFileReader(false), m_templates(templates), + m_isWrite(isWrite) {} // @copydoc result_t getFieldMap(const string& preferLanguage, vector* row, string* errorDescription) const override; diff --git a/src/lib/ebus/datatype.cpp b/src/lib/ebus/datatype.cpp index 62940b37c..494c66413 100755 --- a/src/lib/ebus/datatype.cpp +++ b/src/lib/ebus/datatype.cpp @@ -157,7 +157,8 @@ bool DataType::dump(OutputFormat outputFormat, size_t length, AppendDivisor appe } -bool StringDataType::dump(OutputFormat outputFormat, size_t length, AppendDivisor appendDivisor, ostream* output) const { +bool StringDataType::dump(OutputFormat outputFormat, size_t length, AppendDivisor appendDivisor, ostream* output +) const { DataType::dump(outputFormat, length, appendDivisor, output); if ((outputFormat & OF_JSON) && (outputFormat & OF_ALL_ATTRS)) { *output << ", \"result\": \"" << (isIgnored() ? "void" : "string") << "\""; @@ -300,7 +301,8 @@ result_t StringDataType::writeSymbols(size_t offset, size_t length, istringstrea } -bool DateTimeDataType::dump(OutputFormat outputFormat, size_t length, AppendDivisor appendDivisor, ostream* output) const { +bool DateTimeDataType::dump(OutputFormat outputFormat, size_t length, AppendDivisor appendDivisor, ostream* output +) const { DataType::dump(outputFormat, length, appendDivisor, output); if ((outputFormat & OF_JSON) && (outputFormat & OF_ALL_ATTRS)) { *output << ", \"result\": \"" << (hasDate() ? hasTime() ? "datetime" : "date" : "time") << "\""; @@ -672,7 +674,8 @@ size_t NumberDataType::calcPrecision(int divisor) { return precision; } -bool NumberDataType::dump(OutputFormat outputFormat, size_t length, AppendDivisor appendDivisor, ostream* output) const { +bool NumberDataType::dump(OutputFormat outputFormat, size_t length, AppendDivisor appendDivisor, ostream* output +) const { if (m_bitCount < 8) { DataType::dump(outputFormat, m_bitCount, appendDivisor, output); } else { @@ -1211,8 +1214,9 @@ result_t NumberDataType::parseInput(const string inputStr, unsigned int* parsedV if (hasFlag(SIG)) { long signedValue = strtol(str, &strEnd, 0); if (errno == ERANGE - || (m_bitCount != 32 && (signedValue < 0L ? (signedValue < -(1L << (m_bitCount - 1))) : (signedValue >= (1L << (m_bitCount - 1))))) - ) { + || (m_bitCount != 32 && (signedValue < 0L ? (signedValue < -(1L << (m_bitCount - 1))) + : (signedValue >= (1L << (m_bitCount - 1))) + ))) { return RESULT_ERR_OUT_OF_RANGE; // value out of range } if (signedValue < 0 && m_bitCount != 32) { diff --git a/src/lib/ebus/stringhelper.h b/src/lib/ebus/stringhelper.h index b31063e75..9cf46bc1c 100644 --- a/src/lib/ebus/stringhelper.h +++ b/src/lib/ebus/stringhelper.h @@ -154,7 +154,8 @@ class StringReplacer { * @param ignoreCase true to ignore case. * @return the index of the last unmatched part, or the negative index minus one for extra non-matched non-field parts. */ - ssize_t match(const string& str, string* circuit, string* name, string* field, const string& separator = "/", bool ignoreCase = false) const; + ssize_t match(const string& str, string* circuit, string* name, string* field, const string& separator = "/", + bool ignoreCase = false) const; private: /** From f1818cfa3669f5a34f6c98e21fdd4954adcb7bdf Mon Sep 17 00:00:00 2001 From: john30 Date: Sun, 13 Apr 2025 18:14:35 +0200 Subject: [PATCH 329/345] updated version to 25.1 --- .github/ISSUE_TEMPLATE/bug_report.yml | 1 + ChangeLog.md | 2 +- VERSION | 2 +- contrib/alpine/APKBUILD | 2 +- contrib/alpine/APKBUILD.git | 2 +- contrib/archlinux/PKGBUILD | 2 +- contrib/archlinux/PKGBUILD.git | 2 +- contrib/html/openapi.yaml | 2 +- 8 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 5d5679c52..262b30929 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,7 @@ body: description: the ebusd version in use options: - current source from git + - '25.1' - '24.1' - '23.3' - '23.2' diff --git a/ChangeLog.md b/ChangeLog.md index dcf6fe0d4..591edd563 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,4 +1,4 @@ -# next (tbd) +# 25.1 (2025-04-13) ## Bug Fixes * fix for device string symlink with colon * fix "read" and "write" command response diff --git a/VERSION b/VERSION index 27b21ff38..d41368806 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -24.1 \ No newline at end of file +25.1 \ No newline at end of file diff --git a/contrib/alpine/APKBUILD b/contrib/alpine/APKBUILD index fa94dad70..4b9703b03 100644 --- a/contrib/alpine/APKBUILD +++ b/contrib/alpine/APKBUILD @@ -1,7 +1,7 @@ # Contributor: Tim # Maintainer: John pkgname=ebusd -pkgver=24.1 +pkgver=25.1 pkgrel=0 pkgdesc="Daemon for communication with eBUS heating systems" url="https://github.com/john30/ebusd" diff --git a/contrib/alpine/APKBUILD.git b/contrib/alpine/APKBUILD.git index 763cd9bad..1e38304bb 100644 --- a/contrib/alpine/APKBUILD.git +++ b/contrib/alpine/APKBUILD.git @@ -1,6 +1,6 @@ # Maintainer: John pkgname=ebusd-git -pkgver=24.1 +pkgver=25.1 pkgrel=0 pkgdesc="Daemon for communication with eBUS heating systems" url="https://github.com/john30/ebusd" diff --git a/contrib/archlinux/PKGBUILD b/contrib/archlinux/PKGBUILD index 4ef1f8214..d33b4fb09 100644 --- a/contrib/archlinux/PKGBUILD +++ b/contrib/archlinux/PKGBUILD @@ -2,7 +2,7 @@ # Contributor: Milan Knizek # Usage: makepkg pkgname=ebusd -pkgver=24.1 +pkgver=25.1 pkgrel=1 pkgdesc="ebusd, the daemon for communication with eBUS heating systems." arch=('i686' 'x86_64' 'armv6h' 'armv7h' 'aarch64') diff --git a/contrib/archlinux/PKGBUILD.git b/contrib/archlinux/PKGBUILD.git index a0694946c..533bbc805 100644 --- a/contrib/archlinux/PKGBUILD.git +++ b/contrib/archlinux/PKGBUILD.git @@ -3,7 +3,7 @@ # Usage: makepkg -p PKGBUILD.git pkgname=ebusd-git _gitname=ebusd -pkgver=24.1 +pkgver=25.1 pkgrel=1 pkgdesc="ebusd, the daemon for communication with eBUS heating systems." arch=('i686' 'x86_64' 'armv6h' 'armv7h' 'aarch64') diff --git a/contrib/html/openapi.yaml b/contrib/html/openapi.yaml index c69aee470..8336e5c92 100644 --- a/contrib/html/openapi.yaml +++ b/contrib/html/openapi.yaml @@ -2,7 +2,7 @@ openapi: 3.1.0 info: title: ebusd-http description: The API that ebusd provides on HTTP port. - version: "24.1" + version: "25.1" servers: - url: http://127.0.0.1:8080/ paths: From 4732f51fdb922ebe0b71dca76723626e1f133447 Mon Sep 17 00:00:00 2001 From: john30 Date: Sun, 13 Apr 2025 19:47:53 +0200 Subject: [PATCH 330/345] updated to 25.1 --- contrib/alpine/APKBUILD | 2 +- contrib/archlinux/PKGBUILD | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/alpine/APKBUILD b/contrib/alpine/APKBUILD index 4b9703b03..61b31f79e 100644 --- a/contrib/alpine/APKBUILD +++ b/contrib/alpine/APKBUILD @@ -27,5 +27,5 @@ package() { } sha512sums=" -a4ab5e21f345894c29b7af84f46f93ac4a3ee658d69ca7fb002d52233e8c041043df328212cbeae4f01220e7a6bf4ec8a26ad3757eb0cf6da157d237f5b6b0b6 ebusd-24.1.tar.gz +ff69ee0b36b0e2ad9e44d090ba9507f51430ebb66e79948a44f8c719dbaf00e03a47c791e8f9e305b7dcb8e4fe87a7f00fb8f05c8a7c89e9a1b524e257576d7c ebusd-25.1.tar.gz " diff --git a/contrib/archlinux/PKGBUILD b/contrib/archlinux/PKGBUILD index d33b4fb09..9909193a5 100644 --- a/contrib/archlinux/PKGBUILD +++ b/contrib/archlinux/PKGBUILD @@ -41,4 +41,4 @@ package() { install -m 0644 contrib/etc/ebusd/mqtt-integration.cfg "${pkgdir}/etc/ebusd/mqtt-integration.cfg" } # update md5sums: updpkgsums -md5sums=('ec614447defed756e586fdf4fd73647a') +md5sums=('213a0ab21600798de5642cc7a0518c76') From ef6631f95c6479afa2753ca428c088a5111fec1c Mon Sep 17 00:00:00 2001 From: John Date: Mon, 14 Apr 2025 07:42:15 +0200 Subject: [PATCH 331/345] update copyright year [noci] --- make_debian.sh | 2 +- src/ebusd/bushandler.cpp | 2 +- src/ebusd/bushandler.h | 2 +- src/ebusd/datahandler.cpp | 2 +- src/ebusd/datahandler.h | 2 +- src/ebusd/knxhandler.cpp | 2 +- src/ebusd/knxhandler.h | 2 +- src/ebusd/main.cpp | 2 +- src/ebusd/main.h | 2 +- src/ebusd/main_args.cpp | 2 +- src/ebusd/mainloop.cpp | 2 +- src/ebusd/mainloop.h | 2 +- src/ebusd/mqttclient.cpp | 2 +- src/ebusd/mqttclient.h | 2 +- src/ebusd/mqttclient_mosquitto.cpp | 2 +- src/ebusd/mqttclient_mosquitto.h | 2 +- src/ebusd/mqtthandler.cpp | 2 +- src/ebusd/mqtthandler.h | 2 +- src/ebusd/network.cpp | 2 +- src/ebusd/network.h | 2 +- src/ebusd/request.cpp | 2 +- src/ebusd/request.h | 2 +- src/ebusd/scan.cpp | 2 +- src/ebusd/scan.h | 2 +- src/lib/ebus/contrib/contrib.cpp | 2 +- src/lib/ebus/contrib/contrib.h | 2 +- src/lib/ebus/contrib/tem.cpp | 2 +- src/lib/ebus/contrib/tem.h | 2 +- src/lib/ebus/contrib/test/test_tem.cpp | 2 +- src/lib/ebus/data.cpp | 2 +- src/lib/ebus/data.h | 2 +- src/lib/ebus/datatype.cpp | 2 +- src/lib/ebus/datatype.h | 2 +- src/lib/ebus/device.h | 2 +- src/lib/ebus/device_enhanced.h | 2 +- src/lib/ebus/device_trans.cpp | 2 +- src/lib/ebus/device_trans.h | 2 +- src/lib/ebus/filereader.cpp | 2 +- src/lib/ebus/filereader.h | 2 +- src/lib/ebus/message.cpp | 2 +- src/lib/ebus/message.h | 2 +- src/lib/ebus/protocol.cpp | 2 +- src/lib/ebus/protocol.h | 2 +- src/lib/ebus/protocol_direct.cpp | 2 +- src/lib/ebus/protocol_direct.h | 2 +- src/lib/ebus/result.cpp | 2 +- src/lib/ebus/result.h | 2 +- src/lib/ebus/stringhelper.cpp | 2 +- src/lib/ebus/stringhelper.h | 2 +- src/lib/ebus/symbol.cpp | 2 +- src/lib/ebus/symbol.h | 2 +- src/lib/ebus/test/test_data.cpp | 2 +- src/lib/ebus/test/test_device.cpp | 2 +- src/lib/ebus/test/test_filereader.cpp | 2 +- src/lib/ebus/test/test_message.cpp | 2 +- src/lib/ebus/test/test_symbol.cpp | 2 +- src/lib/ebus/transport.cpp | 2 +- src/lib/ebus/transport.h | 2 +- src/lib/knx/knx.cpp | 2 +- src/lib/knx/knx.h | 2 +- src/lib/knx/knxd.h | 2 +- src/lib/knx/knxnet.h | 2 +- src/lib/utils/arg.cpp | 2 +- src/lib/utils/arg.h | 2 +- src/lib/utils/clock.cpp | 2 +- src/lib/utils/clock.h | 2 +- src/lib/utils/httpclient.cpp | 2 +- src/lib/utils/httpclient.h | 2 +- src/lib/utils/log.cpp | 2 +- src/lib/utils/log.h | 2 +- src/lib/utils/notify.h | 2 +- src/lib/utils/queue.h | 2 +- src/lib/utils/rotatefile.cpp | 2 +- src/lib/utils/rotatefile.h | 2 +- src/lib/utils/tcpsocket.cpp | 2 +- src/lib/utils/tcpsocket.h | 2 +- src/lib/utils/thread.cpp | 2 +- src/lib/utils/thread.h | 2 +- src/tools/ebusctl.cpp | 2 +- src/tools/ebusfeed.cpp | 2 +- src/tools/ebuspicloader.cpp | 2 +- 81 files changed, 81 insertions(+), 81 deletions(-) diff --git a/make_debian.sh b/make_debian.sh index a0376cc11..bbe00965b 100755 --- a/make_debian.sh +++ b/make_debian.sh @@ -1,6 +1,6 @@ #!/bin/sh # ebusd - daemon for communication with eBUS heating systems. -# Copyright (C) 2014-2024 John Baier +# Copyright (C) 2014-2025 John Baier # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/bushandler.cpp b/src/ebusd/bushandler.cpp index 2db31899b..a24595125 100644 --- a/src/ebusd/bushandler.cpp +++ b/src/ebusd/bushandler.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier + * Copyright (C) 2014-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/bushandler.h b/src/ebusd/bushandler.h index b27bac3cc..ecff82da8 100755 --- a/src/ebusd/bushandler.h +++ b/src/ebusd/bushandler.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier + * Copyright (C) 2014-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/datahandler.cpp b/src/ebusd/datahandler.cpp index 864ee4599..d3c82b3a9 100755 --- a/src/ebusd/datahandler.cpp +++ b/src/ebusd/datahandler.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2016-2024 John Baier + * Copyright (C) 2016-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/datahandler.h b/src/ebusd/datahandler.h index 9e93096ed..ff977eda8 100755 --- a/src/ebusd/datahandler.h +++ b/src/ebusd/datahandler.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2016-2024 John Baier + * Copyright (C) 2016-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/knxhandler.cpp b/src/ebusd/knxhandler.cpp index ba7e53690..cdeecaac2 100644 --- a/src/ebusd/knxhandler.cpp +++ b/src/ebusd/knxhandler.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2022-2024 John Baier + * Copyright (C) 2022-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/knxhandler.h b/src/ebusd/knxhandler.h index 156ae2206..26c23338d 100644 --- a/src/ebusd/knxhandler.h +++ b/src/ebusd/knxhandler.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2022-2024 John Baier + * Copyright (C) 2022-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index de478d47a..c5f449b05 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier + * Copyright (C) 2014-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/main.h b/src/ebusd/main.h index 9ab60efdb..1060befe3 100755 --- a/src/ebusd/main.h +++ b/src/ebusd/main.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier + * Copyright (C) 2014-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/main_args.cpp b/src/ebusd/main_args.cpp index d68037067..1ca40a350 100755 --- a/src/ebusd/main_args.cpp +++ b/src/ebusd/main_args.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2023-2024 John Baier + * Copyright (C) 2023-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index 7835e8afc..4feef2d5e 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier + * Copyright (C) 2014-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/mainloop.h b/src/ebusd/mainloop.h index 856ecfb62..142ad89c2 100644 --- a/src/ebusd/mainloop.h +++ b/src/ebusd/mainloop.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier + * Copyright (C) 2014-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/mqttclient.cpp b/src/ebusd/mqttclient.cpp index 7e60cb5c3..3cefb1a12 100644 --- a/src/ebusd/mqttclient.cpp +++ b/src/ebusd/mqttclient.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2023-2024 John Baier + * Copyright (C) 2023-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/mqttclient.h b/src/ebusd/mqttclient.h index 551211003..9ac00aad6 100755 --- a/src/ebusd/mqttclient.h +++ b/src/ebusd/mqttclient.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2023-2024 John Baier + * Copyright (C) 2023-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/mqttclient_mosquitto.cpp b/src/ebusd/mqttclient_mosquitto.cpp index 1c1a0a1ca..852a0a975 100755 --- a/src/ebusd/mqttclient_mosquitto.cpp +++ b/src/ebusd/mqttclient_mosquitto.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2023-2024 John Baier + * Copyright (C) 2023-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/mqttclient_mosquitto.h b/src/ebusd/mqttclient_mosquitto.h index 9098bd8c6..447b22c1b 100755 --- a/src/ebusd/mqttclient_mosquitto.h +++ b/src/ebusd/mqttclient_mosquitto.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2023-2024 John Baier + * Copyright (C) 2023-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/mqtthandler.cpp b/src/ebusd/mqtthandler.cpp index a97767639..8d2f47208 100755 --- a/src/ebusd/mqtthandler.cpp +++ b/src/ebusd/mqtthandler.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2016-2024 John Baier + * Copyright (C) 2016-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/mqtthandler.h b/src/ebusd/mqtthandler.h index 432329ea6..3e15ce333 100755 --- a/src/ebusd/mqtthandler.h +++ b/src/ebusd/mqtthandler.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2016-2024 John Baier + * Copyright (C) 2016-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/network.cpp b/src/ebusd/network.cpp index b60f38ed0..c816f098e 100644 --- a/src/ebusd/network.cpp +++ b/src/ebusd/network.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier , Roland Jax 2012-2014 + * Copyright (C) 2014-2025 John Baier , Roland Jax 2012-2014 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/network.h b/src/ebusd/network.h index 2c8891eb3..d6bf375d7 100644 --- a/src/ebusd/network.h +++ b/src/ebusd/network.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier , Roland Jax 2012-2014 + * Copyright (C) 2014-2025 John Baier , Roland Jax 2012-2014 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/request.cpp b/src/ebusd/request.cpp index 16739eeb6..f960c2104 100644 --- a/src/ebusd/request.cpp +++ b/src/ebusd/request.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2023-2024 John Baier + * Copyright (C) 2023-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/request.h b/src/ebusd/request.h index f27828256..27c0d9bd5 100644 --- a/src/ebusd/request.h +++ b/src/ebusd/request.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2023-2024 John Baier + * Copyright (C) 2023-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/scan.cpp b/src/ebusd/scan.cpp index cb7228847..387913ef9 100644 --- a/src/ebusd/scan.cpp +++ b/src/ebusd/scan.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier + * Copyright (C) 2014-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/ebusd/scan.h b/src/ebusd/scan.h index 1c68866d0..413a5974a 100644 --- a/src/ebusd/scan.h +++ b/src/ebusd/scan.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier + * Copyright (C) 2014-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/contrib/contrib.cpp b/src/lib/ebus/contrib/contrib.cpp index d7ebb6cc6..6aaaaecdb 100755 --- a/src/lib/ebus/contrib/contrib.cpp +++ b/src/lib/ebus/contrib/contrib.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2016-2024 John Baier + * Copyright (C) 2016-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/contrib/contrib.h b/src/lib/ebus/contrib/contrib.h index db16a2b19..f8a25a478 100755 --- a/src/lib/ebus/contrib/contrib.h +++ b/src/lib/ebus/contrib/contrib.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2016-2024 John Baier + * Copyright (C) 2016-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/contrib/tem.cpp b/src/lib/ebus/contrib/tem.cpp index 991905cd9..3a4f1f8fe 100755 --- a/src/lib/ebus/contrib/tem.cpp +++ b/src/lib/ebus/contrib/tem.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2016-2024 John Baier + * Copyright (C) 2016-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/contrib/tem.h b/src/lib/ebus/contrib/tem.h index e46a8cb7a..08bd2b84d 100755 --- a/src/lib/ebus/contrib/tem.h +++ b/src/lib/ebus/contrib/tem.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2016-2024 John Baier + * Copyright (C) 2016-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/contrib/test/test_tem.cpp b/src/lib/ebus/contrib/test/test_tem.cpp index 84fc8fa8c..887c31a10 100755 --- a/src/lib/ebus/contrib/test/test_tem.cpp +++ b/src/lib/ebus/contrib/test/test_tem.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2016-2024 John Baier + * Copyright (C) 2016-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/data.cpp b/src/lib/ebus/data.cpp index 2a0c3661c..3d142a26b 100644 --- a/src/lib/ebus/data.cpp +++ b/src/lib/ebus/data.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier + * Copyright (C) 2014-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/data.h b/src/lib/ebus/data.h index 3697a7453..3cfbc175f 100755 --- a/src/lib/ebus/data.h +++ b/src/lib/ebus/data.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier + * Copyright (C) 2014-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/datatype.cpp b/src/lib/ebus/datatype.cpp index 494c66413..93eb9e1d4 100755 --- a/src/lib/ebus/datatype.cpp +++ b/src/lib/ebus/datatype.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier + * Copyright (C) 2014-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/datatype.h b/src/lib/ebus/datatype.h index 9fb2f3a52..6d6c97df0 100755 --- a/src/lib/ebus/datatype.h +++ b/src/lib/ebus/datatype.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier + * Copyright (C) 2014-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/device.h b/src/lib/ebus/device.h index 646eeec72..037d794ea 100755 --- a/src/lib/ebus/device.h +++ b/src/lib/ebus/device.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2015-2024 John Baier + * Copyright (C) 2015-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/device_enhanced.h b/src/lib/ebus/device_enhanced.h index d1042f58b..969b2345f 100755 --- a/src/lib/ebus/device_enhanced.h +++ b/src/lib/ebus/device_enhanced.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2015-2024 John Baier + * Copyright (C) 2015-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/device_trans.cpp b/src/lib/ebus/device_trans.cpp index 458050527..d23a09958 100755 --- a/src/lib/ebus/device_trans.cpp +++ b/src/lib/ebus/device_trans.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2015-2024 John Baier + * Copyright (C) 2015-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/device_trans.h b/src/lib/ebus/device_trans.h index de40e1e04..c3b9a01ea 100755 --- a/src/lib/ebus/device_trans.h +++ b/src/lib/ebus/device_trans.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2015-2024 John Baier + * Copyright (C) 2015-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/filereader.cpp b/src/lib/ebus/filereader.cpp index a0be0022b..e2526286b 100755 --- a/src/lib/ebus/filereader.cpp +++ b/src/lib/ebus/filereader.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier + * Copyright (C) 2014-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/filereader.h b/src/lib/ebus/filereader.h index 026972389..ca155f9ae 100755 --- a/src/lib/ebus/filereader.h +++ b/src/lib/ebus/filereader.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier + * Copyright (C) 2014-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/message.cpp b/src/lib/ebus/message.cpp index aabb04dbe..6c72aba15 100644 --- a/src/lib/ebus/message.cpp +++ b/src/lib/ebus/message.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier + * Copyright (C) 2014-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/message.h b/src/lib/ebus/message.h index b0a303b7e..fe94d2df0 100644 --- a/src/lib/ebus/message.h +++ b/src/lib/ebus/message.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier + * Copyright (C) 2014-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/protocol.cpp b/src/lib/ebus/protocol.cpp index 70123479d..c4502b763 100644 --- a/src/lib/ebus/protocol.cpp +++ b/src/lib/ebus/protocol.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier + * Copyright (C) 2014-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/protocol.h b/src/lib/ebus/protocol.h index 85ddb1010..0a58dfec1 100755 --- a/src/lib/ebus/protocol.h +++ b/src/lib/ebus/protocol.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier + * Copyright (C) 2014-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/protocol_direct.cpp b/src/lib/ebus/protocol_direct.cpp index 24e97386c..2fc78243f 100644 --- a/src/lib/ebus/protocol_direct.cpp +++ b/src/lib/ebus/protocol_direct.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier + * Copyright (C) 2014-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/protocol_direct.h b/src/lib/ebus/protocol_direct.h index 93dfb0422..ba24a46ca 100755 --- a/src/lib/ebus/protocol_direct.h +++ b/src/lib/ebus/protocol_direct.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier + * Copyright (C) 2014-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/result.cpp b/src/lib/ebus/result.cpp index 929b25ddf..690e43366 100755 --- a/src/lib/ebus/result.cpp +++ b/src/lib/ebus/result.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier + * Copyright (C) 2014-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/result.h b/src/lib/ebus/result.h index 96a733419..b867bd1e3 100755 --- a/src/lib/ebus/result.h +++ b/src/lib/ebus/result.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier + * Copyright (C) 2014-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/stringhelper.cpp b/src/lib/ebus/stringhelper.cpp index 3227e1dea..bbb60e304 100644 --- a/src/lib/ebus/stringhelper.cpp +++ b/src/lib/ebus/stringhelper.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2022-2024 John Baier + * Copyright (C) 2022-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/stringhelper.h b/src/lib/ebus/stringhelper.h index 9cf46bc1c..0b5a5fdf5 100644 --- a/src/lib/ebus/stringhelper.h +++ b/src/lib/ebus/stringhelper.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2022-2024 John Baier + * Copyright (C) 2022-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/symbol.cpp b/src/lib/ebus/symbol.cpp index ae4588c15..e5c188560 100755 --- a/src/lib/ebus/symbol.cpp +++ b/src/lib/ebus/symbol.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier + * Copyright (C) 2014-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/symbol.h b/src/lib/ebus/symbol.h index 4324ea5f1..81180b8fc 100755 --- a/src/lib/ebus/symbol.h +++ b/src/lib/ebus/symbol.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier + * Copyright (C) 2014-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/test/test_data.cpp b/src/lib/ebus/test/test_data.cpp index 6fce4b918..cab3fbfa1 100755 --- a/src/lib/ebus/test/test_data.cpp +++ b/src/lib/ebus/test/test_data.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier + * Copyright (C) 2014-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/test/test_device.cpp b/src/lib/ebus/test/test_device.cpp index 0aa69b9aa..c70255347 100755 --- a/src/lib/ebus/test/test_device.cpp +++ b/src/lib/ebus/test/test_device.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier + * Copyright (C) 2014-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/test/test_filereader.cpp b/src/lib/ebus/test/test_filereader.cpp index 72bf2eb49..4bd3164d7 100755 --- a/src/lib/ebus/test/test_filereader.cpp +++ b/src/lib/ebus/test/test_filereader.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier + * Copyright (C) 2014-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/test/test_message.cpp b/src/lib/ebus/test/test_message.cpp index dda4ee90a..648744713 100644 --- a/src/lib/ebus/test/test_message.cpp +++ b/src/lib/ebus/test/test_message.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier + * Copyright (C) 2014-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/test/test_symbol.cpp b/src/lib/ebus/test/test_symbol.cpp index d0fd8ac77..dc8f1fbd8 100755 --- a/src/lib/ebus/test/test_symbol.cpp +++ b/src/lib/ebus/test/test_symbol.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier + * Copyright (C) 2014-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/transport.cpp b/src/lib/ebus/transport.cpp index c9cb1bc8c..8488012f0 100644 --- a/src/lib/ebus/transport.cpp +++ b/src/lib/ebus/transport.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2023-2024 John Baier + * Copyright (C) 2023-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/ebus/transport.h b/src/lib/ebus/transport.h index 3c2e1f80f..66ae1125b 100755 --- a/src/lib/ebus/transport.h +++ b/src/lib/ebus/transport.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2023-2024 John Baier + * Copyright (C) 2023-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/knx/knx.cpp b/src/lib/knx/knx.cpp index e894ab46f..1729aca99 100644 --- a/src/lib/knx/knx.cpp +++ b/src/lib/knx/knx.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2022-2024 John Baier + * Copyright (C) 2022-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/knx/knx.h b/src/lib/knx/knx.h index aaf059a0d..d711dd3b3 100644 --- a/src/lib/knx/knx.h +++ b/src/lib/knx/knx.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2022-2024 John Baier + * Copyright (C) 2022-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/knx/knxd.h b/src/lib/knx/knxd.h index 97385c66b..50211cb87 100644 --- a/src/lib/knx/knxd.h +++ b/src/lib/knx/knxd.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2022-2024 John Baier + * Copyright (C) 2022-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/knx/knxnet.h b/src/lib/knx/knxnet.h index b002f741a..c6d4424de 100644 --- a/src/lib/knx/knxnet.h +++ b/src/lib/knx/knxnet.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2022-2024 John Baier + * Copyright (C) 2022-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/arg.cpp b/src/lib/utils/arg.cpp index 53e52ecff..ef9a2f565 100755 --- a/src/lib/utils/arg.cpp +++ b/src/lib/utils/arg.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2023-2024 John Baier + * Copyright (C) 2023-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/arg.h b/src/lib/utils/arg.h index c7f136ddb..ec70d53b8 100755 --- a/src/lib/utils/arg.h +++ b/src/lib/utils/arg.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2023-2024 John Baier + * Copyright (C) 2023-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/clock.cpp b/src/lib/utils/clock.cpp index fc6991729..35f9b05e1 100755 --- a/src/lib/utils/clock.cpp +++ b/src/lib/utils/clock.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2015-2024 John Baier + * Copyright (C) 2015-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/clock.h b/src/lib/utils/clock.h index 0f3649c94..691286bba 100755 --- a/src/lib/utils/clock.h +++ b/src/lib/utils/clock.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2015-2024 John Baier + * Copyright (C) 2015-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/httpclient.cpp b/src/lib/utils/httpclient.cpp index b1648c1d8..356ac97dd 100755 --- a/src/lib/utils/httpclient.cpp +++ b/src/lib/utils/httpclient.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2018-2024 John Baier + * Copyright (C) 2018-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/httpclient.h b/src/lib/utils/httpclient.h index 52f8de709..96998bd86 100755 --- a/src/lib/utils/httpclient.h +++ b/src/lib/utils/httpclient.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2018-2024 John Baier + * Copyright (C) 2018-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/log.cpp b/src/lib/utils/log.cpp index 89273c316..65ee42f0f 100755 --- a/src/lib/utils/log.cpp +++ b/src/lib/utils/log.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier + * Copyright (C) 2014-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/log.h b/src/lib/utils/log.h index 01116ee5d..116a9b9ad 100755 --- a/src/lib/utils/log.h +++ b/src/lib/utils/log.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier + * Copyright (C) 2014-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/notify.h b/src/lib/utils/notify.h index 267bfb94c..1eb216211 100755 --- a/src/lib/utils/notify.h +++ b/src/lib/utils/notify.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier , Roland Jax 2012-2014 + * Copyright (C) 2014-2025 John Baier , Roland Jax 2012-2014 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/queue.h b/src/lib/utils/queue.h index 5b1e5d143..415312845 100755 --- a/src/lib/utils/queue.h +++ b/src/lib/utils/queue.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier + * Copyright (C) 2014-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/rotatefile.cpp b/src/lib/utils/rotatefile.cpp index 7aa557af6..636a890f7 100755 --- a/src/lib/utils/rotatefile.cpp +++ b/src/lib/utils/rotatefile.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2016-2024 John Baier + * Copyright (C) 2016-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/rotatefile.h b/src/lib/utils/rotatefile.h index 1903f8507..ccdd80b5e 100755 --- a/src/lib/utils/rotatefile.h +++ b/src/lib/utils/rotatefile.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2016-2024 John Baier + * Copyright (C) 2016-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/tcpsocket.cpp b/src/lib/utils/tcpsocket.cpp index 243ab79a8..f1b42f710 100755 --- a/src/lib/utils/tcpsocket.cpp +++ b/src/lib/utils/tcpsocket.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2015-2024 John Baier , Roland Jax 2012-2014 + * Copyright (C) 2015-2025 John Baier , Roland Jax 2012-2014 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/tcpsocket.h b/src/lib/utils/tcpsocket.h index 139ad1962..aaebbd21d 100755 --- a/src/lib/utils/tcpsocket.h +++ b/src/lib/utils/tcpsocket.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier , Roland Jax 2012-2014 + * Copyright (C) 2014-2025 John Baier , Roland Jax 2012-2014 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/thread.cpp b/src/lib/utils/thread.cpp index c734cd32b..c1567161b 100755 --- a/src/lib/utils/thread.cpp +++ b/src/lib/utils/thread.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier , Roland Jax 2012-2014 + * Copyright (C) 2014-2025 John Baier , Roland Jax 2012-2014 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/lib/utils/thread.h b/src/lib/utils/thread.h index b39b65fb8..5d58f5c1e 100755 --- a/src/lib/utils/thread.h +++ b/src/lib/utils/thread.h @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier , Roland Jax 2012-2014 + * Copyright (C) 2014-2025 John Baier , Roland Jax 2012-2014 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/tools/ebusctl.cpp b/src/tools/ebusctl.cpp index 23d1dd4bc..d2b4113b9 100755 --- a/src/tools/ebusctl.cpp +++ b/src/tools/ebusctl.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier , Roland Jax 2012-2014 + * Copyright (C) 2014-2025 John Baier , Roland Jax 2012-2014 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/tools/ebusfeed.cpp b/src/tools/ebusfeed.cpp index 80ab506b6..a9f93fef2 100755 --- a/src/tools/ebusfeed.cpp +++ b/src/tools/ebusfeed.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2014-2024 John Baier , Roland Jax 2012-2014 + * Copyright (C) 2014-2025 John Baier , Roland Jax 2012-2014 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/tools/ebuspicloader.cpp b/src/tools/ebuspicloader.cpp index 7822d8110..f39dbb10b 100644 --- a/src/tools/ebuspicloader.cpp +++ b/src/tools/ebuspicloader.cpp @@ -1,6 +1,6 @@ /* * ebusd - daemon for communication with eBUS heating systems. - * Copyright (C) 2020-2024 John Baier + * Copyright (C) 2020-2025 John Baier * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by From c3fc48681631b5e419ad36998ea8ef89788a78fa Mon Sep 17 00:00:00 2001 From: John Date: Mon, 14 Apr 2025 07:53:38 +0200 Subject: [PATCH 332/345] add ebus adapter stick [noci] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a555939be..1010c3c0a 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ The main features of the daemon are: * TCP * UDP * enhanced ebusd protocol allowing arbitration to be done directly by the hardware, e.g. for recent - * [eBUS Adapter Shields C6](https://adapter.ebusd.eu/v5-c6/) and [v5](https://adapter.ebusd.eu/v5/), + * [eBUS Adapter Shields C6](https://adapter.ebusd.eu/v5-c6/), [Stick](https://adapter.ebusd.eu/v5-c6/stick.en.html), and [v5](https://adapter.ebusd.eu/v5/), * [adapter v3.1](https://adapter.ebusd.eu/v31)/[v3.0](https://adapter.ebusd.eu/v3), or * [ebusd-esp firmware](https://github.com/john30/ebusd-esp/) * auto-discover device connection via mDNS From 32dc4086bbd58196088bfadc06540bb16b2ebd93 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 14 Apr 2025 20:26:10 +0200 Subject: [PATCH 333/345] align with help --- contrib/docker/docker-compose.example.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/docker/docker-compose.example.yaml b/contrib/docker/docker-compose.example.yaml index 7c76777af..5b094c362 100644 --- a/contrib/docker/docker-compose.example.yaml +++ b/contrib/docker/docker-compose.example.yaml @@ -152,7 +152,7 @@ services: # Read MQTT integration settings from FILE (no default) #EBUSD_MQTTINT: "/etc/ebusd/mqtt-hassio.cfg" # Add variable(s) to the read MQTT integration settings (append to already existing value with "NAME+=VALUE") - #EBUSD_MQTTVAR: "key[+]=value[,...]" + #EBUSD_MQTTVAR: "name[+]=value[,...]" # Publish in JSON format instead of strings, optionally in short (value directly below field key) #EBUSD_MQTTJSON: "" # Publish all available attributes From 395fcc7c8af7dd3401ee4df181ed30446924ef31 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 17 May 2025 15:48:16 +0200 Subject: [PATCH 334/345] some more tests --- src/lib/ebus/test/test_data.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/lib/ebus/test/test_data.cpp b/src/lib/ebus/test/test_data.cpp index cab3fbfa1..7842d5c99 100755 --- a/src/lib/ebus/test/test_data.cpp +++ b/src/lib/ebus/test/test_data.cpp @@ -355,6 +355,9 @@ int main() { {"x,,uch,,0x1-0x3", "2","10feffff0104", "00", "-rW:ERR: argument value out of valid range"}, {"x,,uch", "\n \"x\": {\"value\": 2}", "10feffff0102", "00", "-jV", "\n { \"name\": \"x\", \"slave\": false, \"type\": \"UCH\", \"isbits\": false, \"isadjustable\": false, \"isignored\": false, \"isreverse\": false, \"length\": 1, \"result\": \"number\", \"min\": 0, \"max\": 254, \"step\": 1, \"unit\": \"\", \"comment\": \"\"}"}, {"x,,uch,,1-3:2", "\n \"x\": {\"value\": 2}", "10feffff0102", "00", "-jV", "\n { \"name\": \"x\", \"slave\": false, \"type\": \"UCH\", \"isbits\": false, \"isadjustable\": false, \"isignored\": false, \"isreverse\": false, \"length\": 1, \"result\": \"number\", \"min\": 1, \"max\": 3, \"step\": 2, \"unit\": \"\", \"comment\": \"\"}"}, + {"x,,u1l", "0", "10feffff0100", "00", ""}, + {"x,,u1l", "255", "10feffff01ff", "00", ""}, + {"x,,u1l", "-", "10feffff01ff", "00", "Rw"}, {"x,,sch", "-90", "10feffff01a6", "00", ""}, {"x,,sch", "0", "10feffff0100", "00", ""}, {"x,,sch", "-1", "10feffff01ff", "00", ""}, @@ -372,6 +375,10 @@ int main() { {"x,,sch,,1-3", "4", "10feffff0102", "00", "-Rw:ERR: argument value out of valid range"}, {"x,,sch,,1-3", "2", "10feffff0104", "00", "-rW:ERR: argument value out of valid range"}, {"x,,sch,,-3--1", "-4", "10feffff01fe", "00", "-Rw:ERR: argument value out of valid range"}, + {"x,,s1l", "0", "10feffff0100", "00", ""}, + {"x,,s1l", "127", "10feffff017f", "00", ""}, + {"x,,s1l", "-128", "10feffff0180", "00", ""}, + {"x,,s1l", "-", "10feffff01ff", "00", "Rw"}, {"x,,d1b", "-90", "10feffff01a6", "00", ""}, {"x,,d1b", "0", "10feffff0100", "00", ""}, {"x,,d1b", "-1", "10feffff01ff", "00", ""}, From 1474af4e94485a414cae1eff7513257ec94563de Mon Sep 17 00:00:00 2001 From: John Date: Sat, 17 May 2025 16:12:26 +0200 Subject: [PATCH 335/345] add isLink to openFile() --- src/lib/ebus/filereader.cpp | 5 ++++- src/lib/ebus/filereader.h | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lib/ebus/filereader.cpp b/src/lib/ebus/filereader.cpp index e2526286b..d971ba6c5 100755 --- a/src/lib/ebus/filereader.cpp +++ b/src/lib/ebus/filereader.cpp @@ -34,7 +34,7 @@ using std::cout; using std::endl; -istream* FileReader::openFile(const string& filename, string* errorDescription, time_t* time) { +istream* FileReader::openFile(const string& filename, string* errorDescription, time_t* time, bool *isLink) { struct stat st; if (stat(filename.c_str(), &st) != 0) { *errorDescription = filename; @@ -54,6 +54,9 @@ istream* FileReader::openFile(const string& filename, string* errorDescription, if (time) { *time = st.st_mtime; } + if (isLink) { + *isLink = lstat(filename.c_str(), &st) == 0 && S_ISLNK(st.st_mode); + } return stream; } diff --git a/src/lib/ebus/filereader.h b/src/lib/ebus/filereader.h index ca155f9ae..b0ff26acd 100755 --- a/src/lib/ebus/filereader.h +++ b/src/lib/ebus/filereader.h @@ -84,9 +84,10 @@ class FileReader { * @param filename the name of the file being read. * @param errorDescription a string in which to store the error description in case of error. * @param time optional pointer to a @a time_t value for storing the modification time of the file, or nullptr. + * @param isLink optional pointer for strong whether the file is a link, or nullptr. * @return the opened @a istream on success, or nullptr on error. */ - static istream* openFile(const string& filename, string* errorDescription, time_t* time = nullptr); + static istream* openFile(const string& filename, string* errorDescription, time_t* time = nullptr, bool *isLink = nullptr); /** * Read the definitions from a stream. From 1e3af90e7af2c842a6eaf3556de1ecb33177937a Mon Sep 17 00:00:00 2001 From: John Date: Sat, 17 May 2025 16:16:17 +0200 Subject: [PATCH 336/345] add AddAttributes interface --- src/lib/ebus/message.cpp | 10 +++++++--- src/lib/ebus/message.h | 33 +++++++++++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/lib/ebus/message.cpp b/src/lib/ebus/message.cpp index 6c72aba15..ffdb462b4 100644 --- a/src/lib/ebus/message.cpp +++ b/src/lib/ebus/message.cpp @@ -954,7 +954,8 @@ void Message::dumpField(const string& fieldName, bool withConditions, OutputForm } void Message::decodeJson(bool leadingSeparator, bool appendDirectionCondition, bool withData, - OutputFormat outputFormat, ostringstream* output) const { + OutputFormat outputFormat, ostringstream* output, + AddAttributes* addAttrs) const { outputFormat |= OF_JSON; if (leadingSeparator) { *output << ",\n"; @@ -986,6 +987,9 @@ void Message::decodeJson(bool leadingSeparator, bool appendDirectionCondition, b *output << ",\n \"condition\": "; m_condition->dumpJson(output); } + if (addAttrs) { + addAttrs->addAttrsTo(this, output); + } } if (withData) { *output << ",\n \"lastup\": " << setw(0) << dec << getLastUpdateTime(); @@ -2902,7 +2906,7 @@ Message* MessageMap::getNextPoll() { return ret; } -void MessageMap::dump(bool withConditions, OutputFormat outputFormat, ostream* output) const { +void MessageMap::dump(bool withConditions, OutputFormat outputFormat, ostream* output, AddAttributes* addAttrs) const { bool first = true; bool isJson = (outputFormat & OF_JSON) != 0; if (isJson) { @@ -2935,7 +2939,7 @@ void MessageMap::dump(bool withConditions, OutputFormat outputFormat, ostream* o } if (isJson) { ostringstream str; - message->decodeJson(false, false, false, outputFormat, &str); + message->decodeJson(false, false, false, outputFormat, &str, addAttrs); string add = str.str(); size_t pos = add.find('{'); *output << " {\n \"circuit\": \"" << message->getCircuit() << "\", " << add.substr(pos+1); diff --git a/src/lib/ebus/message.h b/src/lib/ebus/message.h index fe94d2df0..8aa89b245 100644 --- a/src/lib/ebus/message.h +++ b/src/lib/ebus/message.h @@ -64,6 +64,7 @@ using std::deque; class Condition; class SimpleCondition; class CombinedCondition; +class AddAttributes; class MessageMap; @@ -582,9 +583,11 @@ class Message : public AttributedItem { * @param withData whether to add the last data as well. * @param outputFormat the @a OutputFormat options to use. * @param output the @a ostringstream to append the decoded value(s) to. + * @param addAttrs an @a AddAttributes instance to use as well, or nullptr. */ virtual void decodeJson(bool leadingSeparator, bool appendDirectionCondition, bool withData, - OutputFormat outputFormat, ostringstream* output) const; + OutputFormat outputFormat, ostringstream* output, + AddAttributes* addAttrs = nullptr) const; protected: /** @@ -1318,6 +1321,30 @@ class Resolver { }; +/** + * Interface for adding instance specific message attributes. + */ +class AddAttributes { + public: + /** + * Constructor. + */ + AddAttributes() {} + + /** + * Destructor. + */ + virtual ~AddAttributes() {} + + /** + * Append attributes for the @a Message specific to this instance as JSON to the output. + * @param message the @a Message instance. + * @param output the @a ostream to append the attributes to. + */ + virtual void addAttrsTo(const Message* message, ostream* output) const { } +}; + + /** * Holds a map of all known @a Message instances. */ @@ -1641,8 +1668,10 @@ class MessageMap : public MappedFileReader { * @param withConditions whether to include the optional conditions prefix. * @param outputFormat the @a OutputFormat options. * @param output the @a ostream to append the formatted messages to. + * @param addAttrs an @a AddAttributes instance to use as well, or nullptr. */ - void dump(bool withConditions, OutputFormat outputFormat, ostream* output) const; + void dump(bool withConditions, OutputFormat outputFormat, ostream* output, + AddAttributes* addAttrs = nullptr) const; /** * @return the maximum ID length used by any of the known @a Message instances. From 83e2527809db46058f1a8f7e0c9080a15f09fad7 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 17 May 2025 16:26:51 +0200 Subject: [PATCH 337/345] extract some parts of run() --- src/ebusd/mqtthandler.cpp | 295 ++++++++++++++++++++------------------ src/ebusd/mqtthandler.h | 23 +++ 2 files changed, 178 insertions(+), 140 deletions(-) diff --git a/src/ebusd/mqtthandler.cpp b/src/ebusd/mqtthandler.cpp index 8d2f47208..024d3e92d 100755 --- a/src/ebusd/mqtthandler.cpp +++ b/src/ebusd/mqtthandler.cpp @@ -473,6 +473,9 @@ MqttHandler::MqttHandler(UserInfo* userInfo, BusHandler* busHandler, MessageMap* } } } + if (!m_typeSwitches.empty()) { + splitFields(m_replacers["type_switch-names"], &m_typeSwitchNames); + } m_hasDefinitionTopic = !m_replacers.get("definition-topic", true, false).empty(); m_hasDefinitionFieldsPayload = m_replacers.uses("fields_payload"); m_subscribeConfigRestartTopic = m_replacers.get("config_restart-topic", false, false); @@ -708,7 +711,6 @@ void MqttHandler::run() { unsigned int filterSeen = 0; string filterCircuit, filterNonCircuit, filterName, filterNonName, filterField, filterNonField, filterLevel, filterDirection; - vector typeSwitchNames; if (m_hasDefinitionTopic) { result_t result = RESULT_OK; filterPriority = parseInt(m_replacers["filter-priority"].c_str(), 10, 0, 9, &result); @@ -735,9 +737,6 @@ void MqttHandler::run() { FileReader::tolower(&filterLevel); filterDirection = m_replacers["filter-direction"]; FileReader::tolower(&filterDirection); - if (!m_typeSwitches.empty()) { - splitFields(m_replacers["type_switch-names"], &typeSwitchNames); - } } time(&now); start = lastTaskRun = now; @@ -871,16 +870,7 @@ void MqttHandler::run() { } } StringReplacers msgValues = m_replacers; // need a copy here as the contents are manipulated - msgValues.set("circuit", message->getCircuit()); - msgValues.set("name", message->getName()); - msgValues.set("priority", static_cast(message->getPollPriority())); - msgValues.set("level", message->getLevel()); - msgValues.set("direction", direction); - msgValues.set("messagecomment", message->getAttribute("comment")); - msgValues.reduce(true); - string str = msgValues.get("direction_map-"+direction, false, false); - msgValues.set("direction_map", str); - msgValues.reduce(true); + prepareDefinition(message, direction, &msgValues); ostringstream fields; size_t fieldCount = message->getFieldCount(); for (size_t index = 0; index < fieldCount; index++) { @@ -896,134 +886,10 @@ void MqttHandler::run() { || (!filterNonField.empty() && FileReader::matches(fieldName, filterNonField, true, true))) { continue; } - const DataType* dataType = field->getDataType(); - string typeStr; - if (dataType->isNumeric()) { - if (field->isList()) { - typeStr = "list"; - } else { - typeStr = "number"; - } - } else if (dataType->hasFlag(DAT)) { - auto dt = dynamic_cast(dataType); - if (dt->hasDate()) { - typeStr = dt->hasTime() ? "datetime" : "date"; - } else { - typeStr = "time"; - } - } else { - typeStr = "string"; - } - ostr.str(""); - ostr << "type_map-" << direction << "-" << typeStr; - str = msgValues.get(ostr.str(), false, false); - if (str.empty()) { - ostr.str(""); - ostr << "type_map-" << typeStr; - str = msgValues.get(ostr.str(), false, false); - } - if (str.empty()) { + StringReplacers values; + if (!prepareDefinition(direction, msgValues, fieldCount, index, fieldName, field, &values)) { continue; } - StringReplacers values = msgValues; // need a copy here as the contents are manipulated - values.set("index", static_cast(index)); - values.set("field", fieldName); - string fieldNameNonUnique = field->getName(-1); - values.set("fieldname", fieldNameNonUnique); - values.set("fieldnamemult", fieldCount == 1 ? "" : fieldNameNonUnique); - values.set("type", typeStr); - values.set("type_map", str); - values.set("basetype", dataType->getId()); - values.set("comment", field->getAttribute("comment")); - values.set("unit", field->getAttribute("unit")); - if (dataType->isNumeric() && !dataType->hasFlag(EXP)) { - auto dt = dynamic_cast(dataType); - ostr.str(""); - if (dt->getMinMax(false, g_publishFormat, &ostr) == RESULT_OK) { - values.set("min", ostr.str()); - ostr.str(""); - } - if (dt->getMinMax(true, g_publishFormat, &ostr) == RESULT_OK) { - values.set("max", ostr.str()); - ostr.str(""); - } - if (dt->getStep(g_publishFormat, &ostr) != RESULT_OK) { - // fallback method, when smallest number didn't work - int divisor = dt->getDivisor(); - float step = 1.0f; - if (divisor > 1) { - step /= static_cast(divisor); - } else if (divisor < 0) { - step *= static_cast(-divisor); - } - ostr << static_cast(step); - } - values.set("step", ostr.str()); - } - if (dataType->isNumeric() && field->isList() && !values["field_values-entry"].empty()) { - auto vl = (dynamic_cast(field))->getList(); - string entryFormat = values["field_values-entry"]; - string::size_type pos = -1; - while ((pos = entryFormat.find('$', pos+1)) != string::npos) { - if (entryFormat.substr(pos+1, 4) == "text" || entryFormat.substr(pos+1, 5) == "value") { - entryFormat.replace(pos, 1, "%"); - } - } - entryFormat.replace(0, 0, "entry = "); - string result = values["field_values-prefix"]; - bool first = true; - for (const auto& it : vl) { - StringReplacers entry; - entry.parseLine(entryFormat); - entry.set("value", it.first); - entry.set("text", it.second); - entry.reduce(); - if (first) { - first = false; - } else { - result += values["field_values-separator"]; - } - result += entry.get("entry", false, false); - } - result += values["field_values-suffix"]; - values.set("field_values", result); - } - if (!m_typeSwitches.empty()) { - values.reduce(true); - str = values.get("type_switch-by", false, false); - string typeSwitch; - for (int i = 0; i < 2; i++) { - ostr.str(""); - if (i == 0) { - ostr << direction << '-'; - } - ostr << typeStr; - const string key = ostr.str(); - for (auto const &check : m_typeSwitches[key]) { - if (FileReader::matches(str, check.second, true, true)) { - typeSwitch = check.first; - i = 2; // early exit - break; - } - } - } - values.set("type_switch", typeSwitch); - if (!typeSwitchNames.empty()) { - vector strs; - splitFields(typeSwitch, &strs); - for (size_t pos = 0; pos < typeSwitchNames.size(); pos++) { - values.set(typeSwitchNames[pos], pos < strs.size() ? strs[pos] : ""); - } - } - } - values.reduce(true); - string typePartSuffix = values["type_part-by"]; - if (typePartSuffix.empty()) { - typePartSuffix = typeStr; - } - str = values.get("type_part-" + typePartSuffix, false, false); - values.set("type_part", str); - values.reduce(); if (m_hasDefinitionFieldsPayload) { string value = values["field_payload"]; if (!value.empty()) { @@ -1107,6 +973,155 @@ void MqttHandler::run() { } } +void MqttHandler::prepareDefinition(const Message* message, const string& direction, StringReplacers* msgValues) const { + msgValues->set("circuit", message->getCircuit()); + msgValues->set("name", message->getName()); + msgValues->set("priority", static_cast(message->getPollPriority())); + msgValues->set("level", message->getLevel()); + msgValues->set("direction", direction); + msgValues->set("messagecomment", message->getAttribute("comment")); + msgValues->reduce(true); + string str = msgValues->get("direction_map-"+direction, false, false); + msgValues->set("direction_map", str); + msgValues->reduce(true); +} + +bool MqttHandler::prepareDefinition(const string& direction, const StringReplacers& msgValues, size_t fieldCount, size_t index, const string& fieldName, const SingleDataField* field, StringReplacers* values) const { + const DataType* dataType = field->getDataType(); + string typeStr; + if (dataType->isNumeric()) { + if (field->isList()) { + typeStr = "list"; + } else { + typeStr = "number"; + } + } else if (dataType->hasFlag(DAT)) { + auto dt = dynamic_cast(dataType); + if (dt->hasDate()) { + typeStr = dt->hasTime() ? "datetime" : "date"; + } else { + typeStr = "time"; + } + } else { + typeStr = "string"; + } + ostringstream ostr; + ostr << "type_map-" << direction << "-" << typeStr; + string str = msgValues.get(ostr.str(), false, false); + if (str.empty()) { + ostr.str(""); + ostr << "type_map-" << typeStr; + str = msgValues.get(ostr.str(), false, false); + } + if (str.empty()) { + return false; + } + *values = msgValues; // need a copy here as the contents are manipulated + values->set("index", static_cast(index)); + values->set("field", fieldName); + string fieldNameNonUnique = field->getName(-1); + values->set("fieldname", fieldNameNonUnique); + values->set("fieldnamemult", fieldCount == 1 ? "" : fieldNameNonUnique); + values->set("type", typeStr); + values->set("type_map", str); + values->set("basetype", dataType->getId()); + values->set("comment", field->getAttribute("comment")); + values->set("unit", field->getAttribute("unit")); + if (dataType->isNumeric() && !dataType->hasFlag(EXP)) { + auto dt = dynamic_cast(dataType); + ostr.str(""); + if (dt->getMinMax(false, g_publishFormat, &ostr) == RESULT_OK) { + values->set("min", ostr.str()); + ostr.str(""); + } + if (dt->getMinMax(true, g_publishFormat, &ostr) == RESULT_OK) { + values->set("max", ostr.str()); + ostr.str(""); + } + if (dt->getStep(g_publishFormat, &ostr) != RESULT_OK) { + // fallback method, when smallest number didn't work + int divisor = dt->getDivisor(); + float step = 1.0f; + if (divisor > 1) { + step /= static_cast(divisor); + } else if (divisor < 0) { + step *= static_cast(-divisor); + } + ostr << static_cast(step); + } + values->set("step", ostr.str()); + } + if (dataType->isNumeric() && field->isList() && !(*values)["field_values-entry"].empty()) { + auto vl = (dynamic_cast(field))->getList(); + string entryFormat = (*values)["field_values-entry"]; + string::size_type pos = -1; + while ((pos = entryFormat.find('$', pos+1)) != string::npos) { + if (entryFormat.substr(pos+1, 4) == "text" || entryFormat.substr(pos+1, 5) == "value") { + entryFormat.replace(pos, 1, "%"); + } + } + entryFormat.replace(0, 0, "entry = "); + string result = (*values)["field_values-prefix"]; + bool first = true; + for (const auto& it : vl) { + StringReplacers entry; + entry.parseLine(entryFormat); + entry.set("value", it.first); + entry.set("text", it.second); + entry.reduce(); + if (first) { + first = false; + } else { + result += (*values)["field_values-separator"]; + } + result += entry.get("entry", false, false); + } + result += (*values)["field_values-suffix"]; + values->set("field_values", result); + } + if (!m_typeSwitches.empty()) { + values->reduce(true); + str = values->get("type_switch-by", false, false); + string typeSwitch; + for (int i = 0; i < 2; i++) { + ostr.str(""); + if (i == 0) { + ostr << direction << '-'; + } + ostr << typeStr; + const string key = ostr.str(); + auto const tsIt = m_typeSwitches.find(key); + if (tsIt != m_typeSwitches.cend()) { + for (auto const &check : tsIt->second) { + if (FileReader::matches(str, check.second, true, true)) { + typeSwitch = check.first; + i = 2; // early exit + break; + } + } + } + } + values->set("type_switch", typeSwitch); + if (!m_typeSwitchNames.empty()) { + vector strs; + splitFields(typeSwitch, &strs); + for (size_t pos = 0; pos < m_typeSwitchNames.size(); pos++) { + values->set(m_typeSwitchNames[pos], pos < strs.size() ? strs[pos] : ""); + } + } + } + values->reduce(true); + string typePartSuffix = (*values)["type_part-by"]; + if (typePartSuffix.empty()) { + typePartSuffix = typeStr; + } + str = values->get("type_part-" + typePartSuffix, false, false); + values->set("type_part", str); + values->reduce(); + return true; +} + + void MqttHandler::publishDefinition(StringReplacers values, const string& prefix, const string& topic, const string& circuit, const string& name, const string& fallbackPrefix) { bool reduce = false; diff --git a/src/ebusd/mqtthandler.h b/src/ebusd/mqtthandler.h index 3e15ce333..8bbf8259f 100755 --- a/src/ebusd/mqtthandler.h +++ b/src/ebusd/mqtthandler.h @@ -95,6 +95,26 @@ class MqttHandler : public DataSink, public DataSource, public WaitThread, publi void notifyScanStatus(scanStatus_t scanStatus) override; protected: + /** + * Prepare the message part of a definition topic. + * @param message the @a Message instance to prepare for. + * @param direction the direction string. + * @param msgValues the values with the message specification. + */ + void prepareDefinition(const Message* message, const string& direction, StringReplacers* msgValues) const; + + /** + * Prepare the field part of a definition topic. + * @param direction the direction string. + * @param msgValues the prepared values from the message specification. + * @param fieldCount the total field count (non-ignored only). + * @param index the field index. + * @param field the @a SingleDataField instance. + * @param values the values to update on success. + * @return true if the values were updated successfully, false otherwise. + */ + bool prepareDefinition(const string& direction, const StringReplacers& msgValues, size_t fieldCount, size_t index, const string& fieldName, const SingleDataField* field, StringReplacers* values) const; + // @copydoc void run() override; @@ -184,6 +204,9 @@ class MqttHandler : public DataSink, public DataSource, public WaitThread, publi /** map of type name to a list of pairs of wildcard string and mapped value. */ map>> m_typeSwitches; + /** prepared list of type switch names. */ + vector m_typeSwitchNames; + /** the subscribed configuration restart topic, or empty. */ string m_subscribeConfigRestartTopic; From d02e3b1ca523c416281cc4987b1c837ad93c2cf6 Mon Sep 17 00:00:00 2001 From: John Date: Wed, 21 May 2025 19:34:25 +0200 Subject: [PATCH 338/345] add missed min/max/step --- contrib/html/openapi.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/contrib/html/openapi.yaml b/contrib/html/openapi.yaml index 8336e5c92..89fdc178b 100644 --- a/contrib/html/openapi.yaml +++ b/contrib/html/openapi.yaml @@ -664,6 +664,15 @@ components: precision: type: number description: the precision (number of fraction digits) when divisor is >1. + min: + type: number + description: the minimum allowed value. + max: + type: number + description: the maximum allowed value. + step: + type: number + description: the smallest step value. required: - type - isbits From b0e75d2127549ca81758aa4c1f44349d46e8876f Mon Sep 17 00:00:00 2001 From: John Date: Wed, 21 May 2025 19:38:00 +0200 Subject: [PATCH 339/345] add cleanup once only, fix comment, more tests --- src/lib/ebus/datatype.cpp | 6 ++++-- src/lib/ebus/test/test_data.cpp | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/lib/ebus/datatype.cpp b/src/lib/ebus/datatype.cpp index 93eb9e1d4..c62f3c6b0 100755 --- a/src/lib/ebus/datatype.cpp +++ b/src/lib/ebus/datatype.cpp @@ -1404,11 +1404,11 @@ DataTypeList::DataTypeList() { add(new NumberDataType("ULG", 32, 0, 0xffffffff, 0, 0xfffffffe, 1)); // unsigned integer, 0 - 4294967294, big endian add(new NumberDataType("ULR", 32, REV, 0xffffffff, 0, 0xfffffffe, 1)); - // signed integer, -2147483647 - +2147483647, little endian // unsigned integer, 0 - 4294967295, little endian (no replacement) add(new NumberDataType("U4L", 32, REQ, 0, 0, 0xffffffff, 1)); // unsigned integer, 0 - 4294967295, big endian (no replacement) add(new NumberDataType("U4B", 32, REQ|REV, 0, 0, 0xffffffff, 1)); + // signed integer, -2147483647 - +2147483647, little endian add(new NumberDataType("SLG", 32, SIG, 0x80000000, 0x80000001, 0x7fffffff, 1)); // signed integer, -2147483647 - +2147483647, big endian add(new NumberDataType("SLR", 32, SIG|REV, 0x80000000, 0x80000001, 0x7fffffff, 1)); @@ -1469,7 +1469,9 @@ result_t DataTypeList::add(const DataType* dataType, const string derivedKey) { return RESULT_ERR_DUPLICATE_NAME; // duplicate key } m_typesById[key] = dataType; - m_cleanupTypes.push_back(dataType); + if (std::find(m_cleanupTypes.begin(), m_cleanupTypes.end(), dataType) == m_cleanupTypes.end()) { + m_cleanupTypes.push_back(dataType); + } return RESULT_OK; } diff --git a/src/lib/ebus/test/test_data.cpp b/src/lib/ebus/test/test_data.cpp index 7842d5c99..9082ef840 100755 --- a/src/lib/ebus/test/test_data.cpp +++ b/src/lib/ebus/test/test_data.cpp @@ -422,6 +422,22 @@ int main() { {"x,,sir", "32767", "10feffff027fff", "00", ""}, {"x,,sir,10", "-9.0", "10feffff02ffa6", "00", ""}, {"x,,sir,-10", "-900", "10feffff02ffa6", "00", ""}, + {"x,,s2l", "-90", "10feffff02a6ff", "00", ""}, + {"x,,s2l", "0", "10feffff020000", "00", ""}, + {"x,,s2l", "-1", "10feffff02ffff", "00", ""}, + {"x,,s2l", "-32768", "10feffff020080", "00", ""}, + {"x,,s2l", "-32767", "10feffff020180", "00", ""}, + {"x,,s2l", "32767", "10feffff02ff7f", "00", ""}, + {"x,,s2l,10", "-9.0", "10feffff02a6ff", "00", ""}, + {"x,,s2l,-10", "-900", "10feffff02a6ff", "00", ""}, + {"x,,s2b", "-90", "10feffff02ffa6", "00", ""}, + {"x,,s2b", "0", "10feffff020000", "00", ""}, + {"x,,s2b", "-1", "10feffff02ffff", "00", ""}, + {"x,,s2b", "-32768", "10feffff028000", "00", ""}, + {"x,,s2b", "-32767", "10feffff028001", "00", ""}, + {"x,,s2b", "32767", "10feffff027fff", "00", ""}, + {"x,,s2b,10", "-9.0", "10feffff02ffa6", "00", ""}, + {"x,,s2b,-10", "-900", "10feffff02ffa6", "00", ""}, {"x,,u3n", "38", "10feffff03260000", "00", ""}, {"x,,u3n", "0", "10feffff03000000", "00", ""}, {"x,,u3n", "16777214", "10feffff03feffff", "00", ""}, From d72aa81b9fa491a355826ac9964059f858b2ab9a Mon Sep 17 00:00:00 2001 From: John Date: Thu, 22 May 2025 06:43:21 +0200 Subject: [PATCH 340/345] remove outdated webervice --- README.md | 6 ++-- contrib/config/README.md | 8 ----- contrib/config/index.php | 69 ---------------------------------------- contrib/docker/README.md | 2 +- src/ebusd/main_args.cpp | 2 +- 5 files changed, 5 insertions(+), 82 deletions(-) delete mode 100644 contrib/config/README.md delete mode 100644 contrib/config/index.php diff --git a/README.md b/README.md index 1010c3c0a..1a1ea09b4 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ The main features of the daemon are: * answer to messages received from the eBUS * regularly poll for messages * cache all messages - * scan for bus participants and automatically pick matching message definition files from config web service at ebusd.eu (or alternatively local files) + * scan for bus participants and automatically pick matching message definition files from config CDN at [ebus.github.io](https://ebus.github.io/) or from local files * parse messages to human readable values and vice versa via message definition files * automatically check for updates of daemon and message definition files * pick preferred language for translatable message definition parts @@ -90,8 +90,8 @@ Configuration ------------- The most important part of each ebusd installation is the message configuration. -Starting with version 3.2, **ebusd by default uses the config web service at ebusd.eu to retrieve -the latest configuration files** that are reflected by the configuration repository (follow the "latest" symlink there): +Starting with version 3.2, **ebusd by default uses the config web service to retrieve +the latest configuration files** that are reflected by the configuration repository: > https://github.com/john30/ebusd-configuration diff --git a/contrib/config/README.md b/contrib/config/README.md deleted file mode 100644 index 903075685..000000000 --- a/contrib/config/README.md +++ /dev/null @@ -1,8 +0,0 @@ -## former ebusd.eu config webservice - -This is the code of the former webservice at https://cfg.ebusd.eu/ that allowed ebusd to download the needed CSV configuration -files instead of having to install the ebusd-configuration package. - -Meanwhile the service was moved to using github pages at `https://ebus.github.io/`. - -The code here is kept for those who want to serve the old style configuration on their own. diff --git a/contrib/config/index.php b/contrib/config/index.php deleted file mode 100644 index c51504442..000000000 --- a/contrib/config/index.php +++ /dev/null @@ -1,69 +0,0 @@ - diff --git a/contrib/docker/README.md b/contrib/docker/README.md index 6b7983641..dfe681744 100644 --- a/contrib/docker/README.md +++ b/contrib/docker/README.md @@ -3,7 +3,7 @@ ebusd Docker image An [ebusd](https://github.com/john30/ebusd/) Docker image is available on the [Docker Hub](https://hub.docker.com/r/john30/ebusd/) and is able to download the latest released -[configuration files](https://github.com/john30/ebusd-configuration/) from a [dedicated webservice](https://cfg.ebusd.eu/). +[configuration files](https://github.com/john30/ebusd-configuration/) from a [dedicated webservice](https://ebus.github.io/). It allows running ebusd without actually installing (or even building) it on the host. You might even be able to run it on a non-Linux operating system, which is at least known to diff --git a/src/ebusd/main_args.cpp b/src/ebusd/main_args.cpp index 1ca40a350..baca6a2d9 100755 --- a/src/ebusd/main_args.cpp +++ b/src/ebusd/main_args.cpp @@ -264,7 +264,7 @@ static int parse_opt(int key, char *arg, const argParseOpt *parseOpt, struct opt break; // Message configuration options: - case 'c': // --configpath=https://cfg.ebusd.eu/ + case 'c': // --configpath=https://ebus.github.io/ if (arg == nullptr || arg[0] == 0 || strcmp("/", arg) == 0) { argParseError(parseOpt, "invalid configpath"); return EINVAL; From f5a4662bde98e2ce380385e9d5cdfbf834048fa0 Mon Sep 17 00:00:00 2001 From: john30 Date: Sun, 10 Aug 2025 13:09:49 +0200 Subject: [PATCH 341/345] fix #1553 --- src/lib/utils/arg.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/utils/arg.cpp b/src/lib/utils/arg.cpp index ef9a2f565..61b9bca73 100755 --- a/src/lib/utils/arg.cpp +++ b/src/lib/utils/arg.cpp @@ -223,6 +223,7 @@ int argParse(const argParseOpt *parseOpt, int argc, char **argv, void* userArg) } free(longOpts); free(shortChars); + free(shortIndexes); free(shortOpts); return ret; } From 8750d44e8c96c1a33fe7fa09225f6cd63b9cb5bb Mon Sep 17 00:00:00 2001 From: john30 Date: Sat, 27 Sep 2025 16:48:44 +0200 Subject: [PATCH 342/345] fix read from cache for passive+write messages in TCP client (closes #1555, #1566) --- src/ebusd/mainloop.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index 4feef2d5e..2af372029 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -876,8 +876,9 @@ result_t MainLoop::executeRead(const vector& args, const string& levels, if (verbosity & OF_NAMES) { *ostream << cacheMessage->getCircuit() << " " << cacheMessage->getName() << " "; } - ret = cacheMessage->decodeLastData(pt_slaveData, false, fieldIndex == -2 ? nullptr : fieldName.c_str(), fieldIndex, - verbosity, ostream); + ret = cacheMessage->decodeLastData( + hasCache && (cacheMessage->isWrite() || cacheMessage->isPassive()) ? pt_any : pt_slaveData, + false, fieldIndex == -2 ? nullptr : fieldName.c_str(), fieldIndex, verbosity, ostream); if (ret < RESULT_OK) { logError(lf_main, "read %s %s cached: decode %s", cacheMessage->getCircuit().c_str(), cacheMessage->getName().c_str(), getResultCode(ret)); From c68b281397c6c3c8ee94ba6d65ac568f506f5924 Mon Sep 17 00:00:00 2001 From: john30 Date: Sat, 27 Sep 2025 17:46:24 +0200 Subject: [PATCH 343/345] prefer more concrete file name over less (fixes #1592) --- src/ebusd/scan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ebusd/scan.cpp b/src/ebusd/scan.cpp index 387913ef9..28ebbbe6a 100644 --- a/src/ebusd/scan.cpp +++ b/src/ebusd/scan.cpp @@ -481,7 +481,7 @@ result_t ScanHelper::loadScanConfigFile(symbol_t address, string* relativeFile) } match += remain.length(); } - if (match >= bestMatch) { + if (match > bestMatch || (match == bestMatch && name.length() > best.length())) { bestMatch = match; best = name; bestDefaults = defaults; From 9b511cf73d45439b08449b3b5b0cbc9bb5e6cc26 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 28 Sep 2025 14:35:39 +0200 Subject: [PATCH 344/345] allow several rounds of executing instructions to e.g. support load within load --- src/lib/ebus/message.cpp | 111 ++++++++++++++++++++------------------- 1 file changed, 58 insertions(+), 53 deletions(-) diff --git a/src/lib/ebus/message.cpp b/src/lib/ebus/message.cpp index ffdb462b4..7ab2fb67e 100644 --- a/src/lib/ebus/message.cpp +++ b/src/lib/ebus/message.cpp @@ -2490,67 +2490,72 @@ result_t MessageMap::resolveCondition(void (*readMessageFunc)(Message* message), result_t MessageMap::executeInstructions(void (*readMessageFunc)(Message* message), ostringstream* log) { result_t overallResult = RESULT_OK; - vector remove; - for (auto& it : m_instructions) { - auto instructions = it.second; - bool removeSingletons = false; - vector remain; - for (const auto instruction : instructions) { - if (!m_addAll && removeSingletons && instruction->isSingleton()) { - delete instruction; - continue; - } - Condition* condition = instruction->getCondition(); - bool execute = m_addAll || condition == nullptr; - if (!execute) { - string errorDescription; - result_t result = resolveCondition(instruction->isSingleton()?readMessageFunc:nullptr, condition, - &errorDescription); - if (result != RESULT_OK) { - overallResult = result; - *log << "error resolving condition for \""; - instruction->getDestination(log); - *log << "\": " << getResultCode(result); - if (!errorDescription.empty()) { - *log << " " << errorDescription; - } - } else if (condition->isTrue()) { - execute = true; + size_t maxRounds = 3, cntBefore, cntAfter; + do { + cntBefore = cntAfter = m_instructions.size(); + vector remove; + for (auto& it : m_instructions) { + auto instructions = it.second; + bool removeSingletons = false; + vector remain; + for (const auto instruction : instructions) { + if (!m_addAll && removeSingletons && instruction->isSingleton()) { + delete instruction; + continue; } - } - if (execute) { - if (!m_addAll && instruction->isSingleton()) { - removeSingletons = true; + Condition* condition = instruction->getCondition(); + bool execute = m_addAll || condition == nullptr; + if (!execute) { + string errorDescription; + result_t result = resolveCondition(instruction->isSingleton()?readMessageFunc:nullptr, condition, + &errorDescription); + if (result != RESULT_OK) { + overallResult = result; + *log << "error resolving condition for \""; + instruction->getDestination(log); + *log << "\": " << getResultCode(result); + if (!errorDescription.empty()) { + *log << " " << errorDescription; + } + } else if (condition->isTrue()) { + execute = true; + } } - result_t result = instruction->execute(this, log); - if (result != RESULT_OK) { - overallResult = result; + if (execute) { + if (!m_addAll && instruction->isSingleton()) { + removeSingletons = true; + } + result_t result = instruction->execute(this, log); + if (result != RESULT_OK) { + overallResult = result; + } + delete instruction; + } else { + remain.push_back(instruction); } - delete instruction; - } else { - remain.push_back(instruction); } - } - if (removeSingletons && !remain.empty()) { - instructions = remain; - remain.clear(); - for (const auto instruction : instructions) { - if (!instruction->isSingleton()) { - remain.push_back(instruction); - continue; + if (removeSingletons && !remain.empty()) { + instructions = remain; + remain.clear(); + for (const auto instruction : instructions) { + if (!instruction->isSingleton()) { + remain.push_back(instruction); + continue; + } + delete instruction; } - delete instruction; + } + if (remain.empty()) { + remove.push_back(it.first); + } else { + it.second = remain; } } - if (remain.empty()) { - remove.push_back(it.first); - } else { - it.second = remain; + cntAfter = m_instructions.size(); + for (const auto& it : remove) { + m_instructions.erase(it); } - } - for (const auto& it : remove) { - m_instructions.erase(it); - } + } while (overallResult == RESULT_OK && cntAfter>cntBefore && --maxRounds>0); return overallResult; } From 255f176861ac9e41ba983183ffc90333ed2ad135 Mon Sep 17 00:00:00 2001 From: John Date: Sun, 28 Sep 2025 14:37:03 +0200 Subject: [PATCH 345/345] add newer interfaces --- .github/ISSUE_TEMPLATE/bug_report.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 262b30929..01cea8776 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -99,10 +99,10 @@ body: label: Hardware interface description: the eBUS hardware interface in use options: - - Adapter Shield v5 via USB - - Adapter Shield v5 via WiFi - - Adapter Shield v5 via Ethernet - - Adapter Shield v5 via Raspberry GPIO + - Adapter Shield v5/C6/Stick via USB + - Adapter Shield v5/C6/Stick via WiFi + - Adapter Shield v5/C6/Stick via Ethernet + - Adapter Shield v5/C6 via Raspberry GPIO - Adapter v3 USB - Adapter v3 WiFi - Adapter v3 Ethernet
FileGermanEnglish