From c3dc90914c27d8b3a0e8c8e64b620cb59de3af84 Mon Sep 17 00:00:00 2001 From: John Date: Sat, 3 Sep 2022 11:05:04 +0200 Subject: [PATCH] abstract from knxd --- CMakeLists.txt | 25 +++++--- Makefile.am | 8 ++- config.h.cmake | 5 +- configure.ac | 18 ++++-- src/ebusd/CMakeLists.txt | 3 +- src/ebusd/Makefile.am | 8 ++- src/ebusd/knxhandler.cpp | 98 +++++++++++++++-------------- src/ebusd/knxhandler.h | 19 +++--- src/lib/ebus/datatype.cpp | 7 +-- src/lib/knx/CMakeLists.txt | 17 +++++ src/lib/knx/Makefile.am | 18 ++++++ src/lib/knx/knx.cpp | 37 +++++++++++ src/lib/knx/knx.h | 123 +++++++++++++++++++++++++++++++++++++ src/lib/knx/knxd.h | 102 ++++++++++++++++++++++++++++++ 14 files changed, 407 insertions(+), 81 deletions(-) create mode 100644 src/lib/knx/CMakeLists.txt create mode 100644 src/lib/knx/Makefile.am create mode 100644 src/lib/knx/knx.cpp create mode 100644 src/lib/knx/knx.h create mode 100644 src/lib/knx/knxd.h diff --git a/CMakeLists.txt b/CMakeLists.txt index e773179f..98b41955 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -103,14 +103,24 @@ if(HAVE_MQTT) endif(mqtt STREQUAL ON) endif(HAVE_MQTT) -find_library(HAVE_KNX eibclient) +option(knx "disable support for KNX handling." ON) +if(knx STREQUAL ON) + message(STATUS "KNX enabled") + set(HAVE_KNX 1) +else(knx STREQUAL ON) + unset(HAVE_KNX) +endif(knx STREQUAL ON) + if(HAVE_KNX) - option(knx "disable support for KNX handling." ON) - if(knx STREQUAL ON) - message(STATUS "KNX enabled") - else(knx STREQUAL ON) - unset(HAVE_KNX) - endif(knx STREQUAL ON) + find_library(HAVE_KNXD eibclient) + if(HAVE_KNXD) + option(knxd "enable support for KNX handling via knxd." OFF) + if(knxd STREQUAL ON) + message(STATUS "KNX via knxd enabled") + else(knxd STREQUAL ON) + set(HAVE_KNXD 0) + endif(knxd STREQUAL ON) + endif(HAVE_KNXD) endif(HAVE_KNX) find_library(HAVE_SSL ssl) @@ -169,6 +179,7 @@ endif(BUILD_TESTING) add_subdirectory(src/ebusd) add_subdirectory(src/lib/utils) add_subdirectory(src/lib/ebus) +add_subdirectory(src/lib/knx) add_subdirectory(src/tools) if(EXISTS "${ROOT}/etc/debian_version") diff --git a/Makefile.am b/Makefile.am index f2488188..1fffdb1f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2,7 +2,13 @@ ACLOCAL_AMFLAGS = -I m4 SUBDIRS = docs \ src/lib/utils \ - src/lib/ebus \ + src/lib/ebus + +if KNX +SUBDIRS += src/lib/knx +endif + +SUBDIRS += \ src/ebusd \ src/tools diff --git a/config.h.cmake b/config.h.cmake index 0163f24d..5bfd4250 100755 --- a/config.h.cmake +++ b/config.h.cmake @@ -7,9 +7,12 @@ /* Defined if MQTT handling is enabled. */ #cmakedefine HAVE_MQTT -/* Defined if KNX is enabled. */ +/* Defined if KNX handling is enabled. */ #cmakedefine HAVE_KNX +/* Defined if KNX handling via knxd is enabled. */ +#cmakedefine HAVE_KNXD + /* Defined if SSL is enabled. */ #cmakedefine HAVE_SSL diff --git a/configure.ac b/configure.ac index ad354eaf..cbd705bf 100755 --- a/configure.ac +++ b/configure.ac @@ -65,13 +65,18 @@ AM_CONDITIONAL([MQTT], [test "x$with_mqtt" != "xno"]) AC_ARG_WITH(knx, AS_HELP_STRING([--without-knx], [disable support for KNX handling]), [], [with_knx=yes]) if test "x$with_knx" != "xno"; then - AC_CHECK_LIB([eibclient], [EIBSocketURL], - [AC_DEFINE_UNQUOTED(HAVE_KNX, [1], [Defined if KNX handling is enabled.]) - EXTRA_LIBS+=" -leibclient"], - [AC_MSG_RESULT([Could not find EIBSocketURL in libeibclient.]) - with_knx="no"]) + AC_DEFINE_UNQUOTED(HAVE_KNX, [1], [Defined if KNX handling is enabled.]) + AC_ARG_WITH(knxd, AS_HELP_STRING([--with-knxd], [enable support for KNX handling via knxd]), [ + AS_IF([test "x$with_knxd" == "xyes"], + [AC_CHECK_LIB([eibclient], [EIBSocketURL], + [AC_DEFINE_UNQUOTED(HAVE_KNXD, [1], [Defined if KNX handling via knxd is enabled.])], + [AC_MSG_RESULT([Could not find EIBSocketURL in libeibclient.]) + with_knxd="no"]) + ]) + ], []) fi AM_CONDITIONAL([KNX], [test "x$with_knx" != "xno"]) +AM_CONDITIONAL([KNXD], [test "x$with_knxd" == "xyes"]) AC_ARG_WITH(ssl, AS_HELP_STRING([--without-ssl], [disable support for SSL]), [], [with_ssl=yes]) if test "x$with_ssl" != "xno"; then @@ -141,6 +146,9 @@ AM_COND_IF([CONTRIB], [AC_CONFIG_FILES([ src/lib/ebus/contrib/Makefile src/lib/ebus/contrib/test/Makefile ])]) +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.]) diff --git a/src/ebusd/CMakeLists.txt b/src/ebusd/CMakeLists.txt index b766b23b..325663c2 100644 --- a/src/ebusd/CMakeLists.txt +++ b/src/ebusd/CMakeLists.txt @@ -15,7 +15,8 @@ endif(HAVE_MQTT) if(HAVE_KNX) set(ebusd_SOURCES ${ebusd_SOURCES} knxhandler.cpp knxhandler.h) - set(ebusd_LIBS ${ebusd_LIBS} eibclient) + set(ebusd_LIBS ${ebusd_LIBS} knx) + include_directories(../lib/knx) endif(HAVE_KNX) if(HAVE_SSL) diff --git a/src/ebusd/Makefile.am b/src/ebusd/Makefile.am index 7d655646..1b4af122 100644 --- a/src/ebusd/Makefile.am +++ b/src/ebusd/Makefile.am @@ -16,15 +16,17 @@ ebusd_SOURCES = \ if MQTT ebusd_SOURCES += mqtthandler.cpp mqtthandler.h endif -if KNX -ebusd_SOURCES += knxhandler.cpp knxhandler.h -endif ebusd_LDADD = ../lib/utils/libutils.a \ ../lib/ebus/libebus.a \ -lpthread \ @EXTRA_LIBS@ +if KNX +ebusd_SOURCES += knxhandler.cpp knxhandler.h +ebusd_LDADD += ../lib/knx/libknx.a +endif + if SSL ebusd_LDADD += -lssl -lcrypto endif diff --git a/src/ebusd/knxhandler.cpp b/src/ebusd/knxhandler.cpp index dcf681f7..2db5e7e6 100644 --- a/src/ebusd/knxhandler.cpp +++ b/src/ebusd/knxhandler.cpp @@ -184,7 +184,7 @@ KnxHandler::KnxHandler(UserInfo* userInfo, BusHandler* busHandler, MessageMap* m logOtherError("knx", "invalid assignment %s to %s", key.c_str(), val.c_str()); continue; } - auto dest = static_cast(v << 11); + auto dest = static_cast(v << 11); if (pos2 == string::npos) { // 2 level v = parseInt(val.substr(pos+1).c_str(), 10, 0, 0x7ff, &res); @@ -192,7 +192,7 @@ KnxHandler::KnxHandler(UserInfo* userInfo, BusHandler* busHandler, MessageMap* m logOtherError("knx", "invalid 2-level assignment %s to %s", key.c_str(), val.c_str()); continue; } - dest |= static_cast(v); + dest |= static_cast(v); } else { // 3 level v = parseInt(val.substr(pos+1, pos2).c_str(), 10, 0, 0x07, &res); @@ -200,13 +200,13 @@ KnxHandler::KnxHandler(UserInfo* userInfo, BusHandler* busHandler, MessageMap* m logOtherError("knx", "invalid 3-level assignment %s to %s", key.c_str(), val.c_str()); continue; } - dest |= static_cast(v << 8); + dest |= static_cast(v << 8); v = parseInt(val.substr(pos+1, pos2).c_str(), 10, 0, 0xff, &res); if (res != RESULT_OK) { logOtherError("knx", "invalid 3-level assignment %s to %s", key.c_str(), val.c_str()); continue; } - dest |= static_cast(v); + dest |= static_cast(v); } if (key.substr(0, 7) != "global/") { messageCnt++; @@ -248,7 +248,7 @@ KnxHandler::KnxHandler(UserInfo* userInfo, BusHandler* busHandler, MessageMap* m KnxHandler::~KnxHandler() { join(); if (m_con) { - EIBClose(m_con); + delete m_con; m_con = nullptr; } } @@ -348,7 +348,10 @@ float int16ToFloat(uint16_t val) { return static_cast(sig * exp2(exp) * (negative ? -0.01 : 0.01)); } -result_t KnxHandler::sendGroupValue(eibaddr_t dest, apci_t apci, dtlf_t& lengthFlag, unsigned int value, const SingleDataField *field) const { +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()) { + return RESULT_EMPTY; + } uint8_t data[] = {0, 0, 0, 0, 0, 0}; data[0] = static_cast(apci>>8); data[1] = static_cast(apci&0xff); @@ -408,7 +411,8 @@ result_t KnxHandler::sendGroupValue(eibaddr_t dest, apci_t apci, dtlf_t& lengthF return RESULT_ERR_INVALID_NUM; } len += lengthFlag.length; - if (EIBSendGroup(m_con, dest, len, data) < 0) { + const char* err = m_con->sendGroup(dest, len, data); + if (err) { logOtherError("knx", "unable to send %s, dest %4.4x, len %d", apci==APCI_GROUPVALUE_WRITE ? "write" : apci==APCI_GROUPVALUE_READ ? "read" : "response", dest, len); @@ -421,7 +425,7 @@ result_t KnxHandler::sendGroupValue(eibaddr_t dest, apci_t apci, dtlf_t& lengthF } void KnxHandler::sendGlobalValue(global_t index, unsigned int value, bool response) { - if (!m_con) { + if (!m_con || !m_con->isConnected() || !m_con->getAddress()) { return; } const auto vit = m_subscribedGlobals.find(index); @@ -432,18 +436,18 @@ void KnxHandler::sendGlobalValue(global_t index, unsigned int value, bool respon if (git == m_subscribedGroups.end()) { return; } - sendGroupValue(static_cast(vit->second&0xffff), + sendGroupValue(static_cast(vit->second&0xffff), response ? APCI_GROUPVALUE_RESPONSE : APCI_GROUPVALUE_WRITE, git->second.lengthFlag, value); } -result_t KnxHandler::receiveTelegram(int maxlen, uint8_t *buf, int *recvlen, - eibaddr_t *src, eibaddr_t *dest) { +result_t KnxHandler::receiveTelegram(int maxlen, knx_transfer_t* typ, uint8_t *buf, int *recvlen, + knx_addr_t *src, knx_addr_t *dest) { struct timespec tdiff = { .tv_sec = 2, .tv_nsec = 0, }; - int fd = EIB_Poll_FD(m_con); + int fd = m_con->getPollFd(); #ifdef HAVE_PPOLL nfds_t nfds = 1; struct pollfd fds[nfds]; @@ -482,28 +486,17 @@ result_t KnxHandler::receiveTelegram(int maxlen, uint8_t *buf, int *recvlen, newData = FD_ISSET(fd, &readfds); #endif #endif - int len = EIB_Poll_Complete(m_con); - if (len == -1) { - // read failed - return RESULT_ERR_GENERIC_IO; - } if (!newData) { // timeout return RESULT_ERR_TIMEOUT; } - len = EIBGetGroup_Src(m_con, maxlen, buf, src, dest); - if (len < 0) { - return RESULT_ERR_GENERIC_IO; - } - if (len < 2) { - return RESULT_ERR_GENERIC_IO; - } - *recvlen = len; - return RESULT_OK; + *typ = m_con->getPollData(maxlen, buf, recvlen, src, dest); + return *typ == KNX_TRANSFER_NONE ? RESULT_EMPTY : RESULT_OK; } /* -void printResponse(eibaddr_t src, eibaddr_t dest, int len, const uint8_t *data) { +void printResponse(knx_addr_t src, knx_addr_t dest, int len, const uint8_t *data) { + int tctrl = data[0]>>2; int apci = ((data[0]&0x03)<<8) | data[1]; if ((apci & APCI_GROUPVALUE_READ_MASK) == 0) { apci &= ~APCI_GROUPVALUE_READ_MASK; @@ -518,17 +511,22 @@ void printResponse(eibaddr_t src, eibaddr_t dest, int len, const uint8_t *data) if (len>5) { value = (value<<8) | data[5]; // up to 32 bits } - logOtherDebug("knx", "recv from %4.4x to %4.4x, %s (%d), len %d", src, dest, + logOtherDebug("knx", "recv from %4.4x to %4.4x, %s (0x%3.3x, tctrl 0x%2.2x), len %d", src, dest, apci==APCI_GROUPVALUE_WRITE ? "write" : apci==APCI_GROUPVALUE_READ ? "read" : apci==APCI_GROUPVALUE_RESPONSE ? "response" : "other", - apci, len); + apci, tctrl, len); } - */ +*/ -void KnxHandler::handleReceivedTelegram(eibaddr_t src, eibaddr_t dest, int len, const uint8_t *data) { +void KnxHandler::handleReceivedTelegram(knx_transfer_t typ, knx_addr_t src, knx_addr_t dest, int len, const uint8_t *data) { + if (typ != KNX_TRANSFER_GROUP) { + logOtherNotice("knx", "skipping non-group PDU %3.3x", typ); + return; + } int apci = ((data[0]&0x03)<<8) | data[1]; - if ((apci & APCI_GROUPVALUE_READ_MASK) == 0) { - apci &= ~APCI_GROUPVALUE_READ_MASK; + int groupReadWriteApci = apci & APCI_GROUPVALUE_READ_WRITE_MASK; + if (groupReadWriteApci == APCI_GROUPVALUE_WRITE || groupReadWriteApci == APCI_GROUPVALUE_READ) { + apci = groupReadWriteApci; } bool isWrite = apci==APCI_GROUPVALUE_WRITE; if (apci!=APCI_GROUPVALUE_READ && !isWrite) { @@ -638,7 +636,8 @@ void KnxHandler::handleReceivedTelegram(eibaddr_t src, eibaddr_t dest, int len, fval = int16ToFloat(static_cast(value)); } else if (lengthFlag.length == 4) { // convert from IEEE 754 - fval = uintToFloat(value); + bool negative = (value & (1u << 31)) != 0; + fval = uintToFloat(value, negative); } else { logOtherNotice("knx", "unable to decode write request from %4.4x to %4.4x for %s/%s/%s, value %d", src, dest, circuit.c_str(), name.c_str(), fieldName.c_str(), value); @@ -703,24 +702,22 @@ void KnxHandler::run() { int len = 0; time_t definitionsSince = 0; while (isRunning()) { - bool wasConnected = m_con != nullptr; + bool wasConnected = m_con != nullptr && m_con->isConnected(); bool needsWait = true; if (!m_con) { - m_con = EIBSocketURL(g_url); - const char* err = nullptr; - if (!m_con) { - err = "open error"; - } else if (EIBOpen_GroupSocket(m_con, 0) < 0) { - err = "open group error"; - EIBClose_sync(m_con); - m_con = nullptr; - } else { + m_con = KnxConnection::create(); + const char* err = m_con->open(g_url); + if (!err) { m_lastErrorLogTime = 0; logOtherNotice("knx", "connected"); sendGlobalValue(GLOBAL_VERSION, VERSION_INT); sendGlobalValue(GLOBAL_RUNNING, 1); } if (err) { + if (m_con) { + delete m_con; + m_con = nullptr; + } time(&now); if (now > m_lastErrorLogTime + 10) { // log at most every 10 seconds m_lastErrorLogTime = now; @@ -792,7 +789,7 @@ void KnxHandler::run() { continue; } // store association - eibaddr_t dest = git->second; + knx_addr_t dest = git->second; auto subKey = static_cast(dest | (isWrite ? FLAG_WRITE : FLAG_READ)); auto sit = m_subscribedGroups.find(subKey); if (sit != m_subscribedGroups.cend()) { @@ -866,19 +863,20 @@ void KnxHandler::run() { } } if (m_con) { - eibaddr_t src, dest; + 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(8, data, &len, &src, &dest); + res = receiveTelegram(sizeof(data), &typ, data, &len, &src, &dest); if (res != RESULT_OK) { if (res == RESULT_ERR_GENERIC_IO) { - EIBClose_sync(m_con); + delete m_con; m_con = nullptr; } } else { needsWait = false; - handleReceivedTelegram(src, dest, len, data); + handleReceivedTelegram(typ, src, dest, len, data); } } while (res == RESULT_OK); } @@ -913,7 +911,7 @@ void KnxHandler::run() { if (!field || field->isIgnored()) { continue; } - eibaddr_t dest = destFlags&0xffff; + knx_addr_t dest = destFlags&0xffff; unsigned int value = 0; result = message->decodeLastDataNumField(nullptr, index, &value); sendGroupValue(dest, APCI_GROUPVALUE_WRITE, sit->second.lengthFlag, value, field); diff --git a/src/ebusd/knxhandler.h b/src/ebusd/knxhandler.h index 928dd7c5..95932323 100644 --- a/src/ebusd/knxhandler.h +++ b/src/ebusd/knxhandler.h @@ -19,7 +19,6 @@ #ifndef EBUSD_KNXHANDLER_H_ #define EBUSD_KNXHANDLER_H_ -#include #include #include #include @@ -29,11 +28,12 @@ #include "ebusd/bushandler.h" #include "lib/ebus/message.h" #include "lib/ebus/stringhelper.h" +#include "lib/knx/knx.h" namespace ebusd { /** @file ebusd/knxhandler.h - * A data handler enabling KNX support via knx. + * A data handler enabling KNX support. */ using std::map; @@ -65,7 +65,7 @@ enum apci_t { APCI_GROUPVALUE_WRITE = 0x080, //!< A_GroupValue_Write-PDU }; -#define APCI_GROUPVALUE_READ_MASK 0x3c0 +#define APCI_GROUPVALUE_READ_WRITE_MASK 0x3c0 #define FLAG_READ 0x400000 #define FLAG_WRITE 0x800000 @@ -144,7 +144,7 @@ class KnxHandler : public DataSink, public DataSource, public WaitThread { * @param field the message field or nullptr for non field related. * @return the result code. */ - result_t sendGroupValue(eibaddr_t dest, apci_t apci, dtlf_t& lengthFlag, unsigned int value, const SingleDataField *field = nullptr) const; + result_t sendGroupValue(knx_addr_t dest, apci_t apci, dtlf_t& lengthFlag, unsigned int value, const SingleDataField *field = nullptr) const; /** * Send a global value to the registered group address. @@ -164,16 +164,17 @@ class KnxHandler : public DataSink, public DataSource, public WaitThread { * @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, uint8_t *buf, int *recvlen, eibaddr_t *src, eibaddr_t *dest); + result_t receiveTelegram(int maxlen, knx_transfer_t* typ, uint8_t *buf, int *recvlen, knx_addr_t *src, knx_addr_t *dest); /** * Handle a received KNX telegram. + * @param typ the poll data type. * @param src the source address. * @param dest the destination group address. * @param len the telegram length (starting with ovctet 6). * @param data the telegram data buffer. */ - void handleReceivedTelegram(eibaddr_t src, eibaddr_t dest, int len, const uint8_t *data); + void handleReceivedTelegram(knx_transfer_t typ, knx_addr_t src, knx_addr_t dest, int len, const uint8_t *data); protected: // @copydoc @@ -188,7 +189,7 @@ class KnxHandler : public DataSink, public DataSource, public WaitThread { StringReplacers m_replacers; /** the group address for relevant message fields before being subscribed to by "circuit/message/field" name. */ - map m_messageFieldGroupAddress; + map m_messageFieldGroupAddress; /** * the group addresses that need to be responded to. @@ -209,8 +210,8 @@ class KnxHandler : public DataSink, public DataSource, public WaitThread { /** the time the run thread was entered. */ time_t m_start; - /** the knx structure if initialized, or nullptr. */ - EIBConnection* m_con; + /** the knx connection if initialized, or nullptr. */ + KnxConnection* m_con; /** the last update check result. */ string m_lastUpdateCheckResult; diff --git a/src/lib/ebus/datatype.cpp b/src/lib/ebus/datatype.cpp index fd39f400..d321bd4d 100755 --- a/src/lib/ebus/datatype.cpp +++ b/src/lib/ebus/datatype.cpp @@ -794,7 +794,6 @@ result_t NumberDataType::readSymbols(size_t offset, size_t length, const SymbolS } result_t NumberDataType::getFloatFromRawValue(unsigned int value, float* output) const { - int signedValue; if (!hasFlag(REQ) && value == m_replacement) { return RESULT_EMPTY; } @@ -814,9 +813,10 @@ result_t NumberDataType::getFloatFromRawValue(unsigned int value, float* output) } else { negative = false; } + int signedValue; if (m_bitCount == 32) { if (hasFlag(EXP)) { // IEEE 754 binary32 - float val = uintToFloat(value); + float val = uintToFloat(value, negative); if (val != val) { // !isnan(val) return RESULT_EMPTY; } @@ -830,7 +830,6 @@ result_t NumberDataType::getFloatFromRawValue(unsigned int value, float* output) *output = static_cast(val); return RESULT_OK; } - // less than 32 bit if (!negative) { if (m_divisor < 0) { *output = static_cast(value) * static_cast(-m_divisor); @@ -860,7 +859,6 @@ result_t NumberDataType::getFloatFromRawValue(unsigned int value, float* output) result_t NumberDataType::readFromRawValue(unsigned int value, OutputFormat outputFormat, ostream* output) const { size_t length = (m_bitCount < 8) ? 1 : (m_bitCount/8); - int signedValue; // initialize output *output << setw(0) << std::resetiosflags(output->flags()) << dec << std::skipws << setprecision(6); @@ -888,6 +886,7 @@ result_t NumberDataType::readFromRawValue(unsigned int value, } else { negative = false; } + int signedValue; if (m_bitCount == 32) { if (hasFlag(EXP)) { // IEEE 754 binary32 float val = uintToFloat(value, negative); diff --git a/src/lib/knx/CMakeLists.txt b/src/lib/knx/CMakeLists.txt new file mode 100644 index 00000000..15863be2 --- /dev/null +++ b/src/lib/knx/CMakeLists.txt @@ -0,0 +1,17 @@ +add_definitions(-Wconversion -Wno-unused-parameter) + +set(libknx_a_SOURCES + knx.h knx.cpp +) + +set(libknx_a_LIBS +) + +if(HAVE_KNXD) + set(libknx_a_SOURCES ${libknx_a_SOURCES} knxd.h) + set(libknx_a_LIBS ${libknx_a_LIBS} eibclient) +endif(HAVE_KNXD) + +add_library(knx ${libknx_a_SOURCES}) + +target_link_libraries(knx ${libknx_a_LIBS}) \ No newline at end of file diff --git a/src/lib/knx/Makefile.am b/src/lib/knx/Makefile.am new file mode 100644 index 00000000..5475c4cf --- /dev/null +++ b/src/lib/knx/Makefile.am @@ -0,0 +1,18 @@ +AM_CXXFLAGS = -I$(top_srcdir)/src \ + -isystem$(top_srcdir) \ + -Wno-conversion + +noinst_LIBRARIES = libknx.a + +libknx_a_SOURCES = \ + knx.h knx.cpp + +if KNXD +libknx_a_SOURCES += knxd.h +libknx_a_LIBADD = -leibclient +else +libknx_a_SOURCES += knxnet.h +endif + +distclean-local: + -rm -f Makefile.in diff --git a/src/lib/knx/knx.cpp b/src/lib/knx/knx.cpp new file mode 100644 index 00000000..72615759 --- /dev/null +++ b/src/lib/knx/knx.cpp @@ -0,0 +1,37 @@ +/* + * ebusd - daemon for communication with eBUS heating systems. + * Copyright (C) 2022 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/knx/knx.h" + +#ifdef HAVE_KNXD +#include "lib/knx/knxd.h" +#else +#include "lib/knx/knxnet.h" +#endif + +KnxConnection* KnxConnection::create() { +#ifdef HAVE_KNXD + return new KnxdConnection(); +#else + return new KnxNetConnection(); +#endif +} diff --git a/src/lib/knx/knx.h b/src/lib/knx/knx.h new file mode 100644 index 00000000..cded4758 --- /dev/null +++ b/src/lib/knx/knx.h @@ -0,0 +1,123 @@ +/* + * ebusd - daemon for communication with eBUS heating systems. + * Copyright (C) 2022 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_KNX_KNX_H_ +#define LIB_KNX_KNX_H_ + +#include + +// base KNX address type (group or individual) +typedef uint16_t knx_addr_t; + +// the transfer types (lower 8 bits of transport control field with sequence=0, plus bit 8 with address type) +enum knx_transfer_t { + KNX_TRANSFER_NONE = -1, // no transfer available + KNX_TRANSFER_GROUP = 0x100, // data group or broadcast PDU + KNX_TRANSFER_TAG_GROUP = 0x104, // data tag group PDU + KNX_TRANSFER_INDIVIDUAL = 0x000, // data individual PDU + KNX_TRANSFER_CONNECTED = 0x040, // data connected PDU + KNX_TRANSFER_CONNECT = 0x080, // connect PDU + KNX_TRANSFER_DISCONNECT = 0x081, // disconnect PDU + KNX_TRANSFER_ACK = 0x0c2, // ACK PDU + KNX_TRANSFER_NAK = 0x0c3, // NAK PDU +}; + + +/** + * An abstract KNX connection. + */ +class KnxConnection { + public: + /** + * Construct a new instance. + */ + KnxConnection() {} + + /** + * Destructor. + */ + virtual ~KnxConnection() {} + + /** + * Create a new KnxConnection. + * @param url the URL to connect to. + * @return the new KnxConnection, or @a nullptr on error. + */ + static KnxConnection* create(); + + /** + * Open a connection to the specified URL. + * @param url the URL to connect to. + * @return nullptr on success, or an error message. + */ + virtual const char* open(const char* url) = 0; + + /** + * @return true if connected, false otherwise. + */ + virtual bool isConnected() const = 0; + + /** + * Close the connection. + */ + virtual void close() = 0; + + /** + * @return the file descriptor for polling. + */ + virtual int getPollFd() const = 0; + + /** + * Get the available data (after the file descriptor was checked for availability). + * @param size the size of the data buffer. + * @param data the data buffer to copy the data to. + * @param len pointer to store the actual data length to. + * @param src optional pointer to store the source address (if any, depending on poll data type). + * @param dst optional pointer to store the destination address (if any, depending on poll data type). + * @return the polled transfer data type. + */ + virtual knx_transfer_t getPollData(int size, uint8_t* data, int* len, knx_addr_t* src, knx_addr_t* dst) = 0; + + /** + * Send a group APDU. + * @param dst the destination address. + * @param len the APDU length. + * @param data the APDU data buffer. + * @return nullptr on success, or an error message. + */ + virtual const char* sendGroup(knx_addr_t dst, int len, const uint8_t* data) = 0; + + /** + * @return true if connection allows programming via ETS. + */ + virtual bool isProgrammable() { return false; }; + + /** + * @return the individual address, or 0 if not programmed yet, or any non-zero value if not programmable. + */ + virtual knx_addr_t getAddress() { return 0xffff; }; + + /** + * @param address the individual address to set. + */ + virtual void setAddress(knx_addr_t address) { + // default implementation does nothing + } +}; + +#endif // LIB_KNX_KNX_H_ \ No newline at end of file diff --git a/src/lib/knx/knxd.h b/src/lib/knx/knxd.h new file mode 100644 index 00000000..729ddc02 --- /dev/null +++ b/src/lib/knx/knxd.h @@ -0,0 +1,102 @@ +/* + * ebusd - daemon for communication with eBUS heating systems. + * Copyright (C) 2022 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_KNX_KNXD_H_ +#define LIB_KNX_KNXD_H_ + +#include +#include "lib/knx/knx.h" + +class KnxdConnection : public KnxConnection { + public: + /** + * Construct a new instance. + */ + KnxdConnection() + : KnxConnection(), m_con(nullptr) {} + + /** + * Destructor. + */ + virtual ~KnxdConnection() { + close(); + } + + // @copydoc + const char* open(const char* url) override { + close(); + m_con = EIBSocketURL(url); + if (!m_con) { + return "open error"; + } + if (EIBOpen_GroupSocket(m_con, 0) < 0) { + EIBClose_sync(m_con); + m_con = nullptr; + return "open group error"; + } + return nullptr; + } + + // @copydoc + bool isConnected() const override { + return m_con != nullptr; + } + + void close() override { + if (m_con) { + EIBClose_sync(m_con); + m_con = nullptr; + } + } + + // @copydoc + int getPollFd() const override { + return EIB_Poll_FD(m_con); + } + + // @copydoc + knx_transfer_t getPollData(int size, uint8_t* data, int* len, knx_addr_t* src, knx_addr_t* dst) override { + int ret = EIB_Poll_Complete(m_con); + if (ret == -1) { + // read failed + return KNX_TRANSFER_NONE; + } + ret = EIBGetGroup_Src(m_con, size, data, src, dst); + if (ret < 2) { + return KNX_TRANSFER_NONE; + } + if (len) { + *len = ret; + } + return KNX_TRANSFER_GROUP; + } + + // @copydoc + const char* sendGroup(knx_addr_t dst, int len, const uint8_t* data) override { + if (EIBSendGroup(m_con, dst, len, data) < 0) { + return "send error"; + } + return nullptr; + } + + private: + /** the knx structure if connected, or nullptr. */ + EIBConnection* m_con; +}; + +#endif // LIB_KNX_KNXD_H_ \ No newline at end of file