From efe2c46d329ee13f348d215a3326e9b4c0bbc9c0 Mon Sep 17 00:00:00 2001 From: Waldemar Porscha Date: Tue, 16 Jan 2024 00:39:26 +0100 Subject: [PATCH 01/44] New implementation of Uninitialized state for group object - old implementation is still there for compatibility reasons - new implementation reflects the case "uninitialized while transmitting" - Just states "Updated", "WriteRequest" and "Ok" remove uninitialized state --- src/knx/group_object.cpp | 29 ++++++++++++++++++++--------- src/knx/group_object.h | 19 +++++++++++++++++-- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/src/knx/group_object.cpp b/src/knx/group_object.cpp index 6898ba5..cf3ad24 100644 --- a/src/knx/group_object.cpp +++ b/src/knx/group_object.cpp @@ -12,7 +12,8 @@ GroupObjectTableObject* GroupObject::_table = 0; GroupObject::GroupObject() { _data = 0; - _commFlag = Uninitialized; + _commFlagEx.uninitialized = true; + _commFlagEx.commFlag = Uninitialized; _dataLength = 0; #ifndef SMALL_GROUPOBJECT _updateHandler = 0; @@ -22,7 +23,7 @@ GroupObject::GroupObject() GroupObject::GroupObject(const GroupObject& other) { _data = new uint8_t[other._dataLength]; - _commFlag = other._commFlag; + _commFlagEx = other._commFlagEx; _dataLength = other._dataLength; _asap = other._asap; #ifndef SMALL_GROUPOBJECT @@ -75,7 +76,7 @@ bool GroupObject::readEnable() return false; // we forbid reading of new (uninitialized) go - if (_commFlag == Uninitialized) + if (_commFlagEx.uninitialized) return false; return bitRead(ntohs(_table->_tableData[_asap]), 11) > 0; @@ -157,22 +158,29 @@ size_t GroupObject::asapValueSize(uint8_t code) ComFlag GroupObject::commFlag() { - return _commFlag; + return _commFlagEx.commFlag; } void GroupObject::commFlag(ComFlag value) { - _commFlag = value; + _commFlagEx.commFlag = value; + if (value == WriteRequest || value == Updated || value == Ok) + _commFlagEx.uninitialized = false; +} + +bool GroupObject::initialized() +{ + return !_commFlagEx.uninitialized; } void GroupObject::requestObjectRead() { - _commFlag = ReadRequest; + _commFlagEx.commFlag = ReadRequest; } void GroupObject::objectWritten() { - _commFlag = WriteRequest; + _commFlagEx.commFlag = WriteRequest; } size_t GroupObject::valueSize() @@ -274,8 +282,11 @@ void GroupObject::valueNoSend(const KNXValue& value) void GroupObject::valueNoSend(const KNXValue& value, const Dpt& type) { - if (_commFlag == Uninitialized) - _commFlag = Ok; + if (_commFlagEx.uninitialized) + { + _commFlagEx.commFlag = Ok; + _commFlagEx.uninitialized = false; + } KNX_Encode_Value(value, _data, _dataLength, type); } diff --git a/src/knx/group_object.h b/src/knx/group_object.h index 88ce274..cf46307 100644 --- a/src/knx/group_object.h +++ b/src/knx/group_object.h @@ -7,7 +7,7 @@ class GroupObjectTableObject; -enum ComFlag +enum ComFlag : uint8_t { Updated = 0, //!< Group object was updated ReadRequest = 1, //!< Read was requested but was not processed @@ -18,6 +18,16 @@ enum ComFlag Uninitialized = 6 //!< uninitialized Group Object, its value is not valid }; +// extended ComFlag: Uninitialized it not handled correctly as ComFlag +// it might be in state Transmitting during a ReadRequest on startup while value is still not valid +// we use MSB to store Uninitialized and keep the size of GroupObject the same saving memory ressources +// the old Uninitialized handling is still there for compatibility reasons. +struct ComFlagEx +{ + bool uninitialized : 1; + ComFlag commFlag : 7; +}; + class GroupObject; #ifndef HAS_FUNCTIONAL @@ -96,6 +106,11 @@ class GroupObject */ void commFlag(ComFlag value); + /** + * Check if the group object contains a valid value assigned from bus or from application program + */ + bool initialized(); + /** * Request the read of a communication object. Calling this function triggers the * sending of a read-group-value telegram, to read the value of the communication @@ -236,7 +251,7 @@ class GroupObject size_t asapValueSize(uint8_t code); size_t goSize(); uint16_t _asap = 0; - ComFlag _commFlag = Uninitialized; + ComFlagEx _commFlagEx; uint8_t* _data = 0; uint8_t _dataLength = 0; #ifndef SMALL_GROUPOBJECT From c6368db8a3d17c78fd97f7e93012de9b111f8697 Mon Sep 17 00:00:00 2001 From: Waldemar Porscha Date: Thu, 18 Jan 2024 22:43:31 +0100 Subject: [PATCH 02/44] Finalize uninitialized handling --- src/knx/group_object.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/knx/group_object.cpp b/src/knx/group_object.cpp index cf3ad24..30420e3 100644 --- a/src/knx/group_object.cpp +++ b/src/knx/group_object.cpp @@ -175,12 +175,12 @@ bool GroupObject::initialized() void GroupObject::requestObjectRead() { - _commFlagEx.commFlag = ReadRequest; + commFlag(ReadRequest); } void GroupObject::objectWritten() { - _commFlagEx.commFlag = WriteRequest; + commFlag(WriteRequest); } size_t GroupObject::valueSize() @@ -283,10 +283,7 @@ void GroupObject::valueNoSend(const KNXValue& value) void GroupObject::valueNoSend(const KNXValue& value, const Dpt& type) { if (_commFlagEx.uninitialized) - { - _commFlagEx.commFlag = Ok; - _commFlagEx.uninitialized = false; - } + commFlag(Ok); KNX_Encode_Value(value, _data, _dataLength, type); } From f5f45e2ce1dccdecd1a057a636fafae3752b8785 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cornelius=20K=C3=B6pp?= Date: Sun, 13 Aug 2023 22:01:45 +0200 Subject: [PATCH 03/44] Allow Setting Value of GroupObject (KO) with Checking Change after Conversion by `valueNoSendCompare(..)` * Return if Value was Changed * Always Set the First Value * Copy on Changes Only * Make Comparison Independent of Sending * Extend Doc of `valueNoSendCompare(..)`; Add Note for using `valueNoSend(..)` --- src/knx/group_object.cpp | 24 ++++++++++++++++++++++++ src/knx/group_object.h | 13 +++++++++++++ 2 files changed, 37 insertions(+) diff --git a/src/knx/group_object.cpp b/src/knx/group_object.cpp index 30420e3..80865f4 100644 --- a/src/knx/group_object.cpp +++ b/src/knx/group_object.cpp @@ -287,3 +287,27 @@ void GroupObject::valueNoSend(const KNXValue& value, const Dpt& type) KNX_Encode_Value(value, _data, _dataLength, type); } + +bool GroupObject::valueNoSendCompare(const KNXValue& value, const Dpt& type) +{ + if (_commFlag == Uninitialized) + { + // always set first value + this->valueNoSend(value, type); + return true; + } + else + { + // convert new value to given dtp + uint8_t newData[_dataLength]; + memset(newData, 0, _dataLength); + KNX_Encode_Value(value, newData, _dataLength, type); + + // check for change in converted value / update value on change only + const bool dataChanged = memcmp(_data, newData, _dataLength); + if (dataChanged) + memcpy(_data, newData, _dataLength); + + return dataChanged; + } +} \ No newline at end of file diff --git a/src/knx/group_object.h b/src/knx/group_object.h index cf46307..38b8291 100644 --- a/src/knx/group_object.h +++ b/src/knx/group_object.h @@ -181,6 +181,19 @@ class GroupObject * The parameters must fit the group object. Otherwise it will stay unchanged. */ void valueNoSend(const KNXValue& value, const Dpt& type); + + /** + * Check if the value (after conversion to dpt) will differ from current value of the group object and update if necessary. + * Use this method only, when the value change is relevant, otherwise valueNoSend(const KNXValue&, const Dpt&) will do the same (without overhead for comparing) + * @param value the value the group object is set to + * @param type the datapoint type used for the conversion. + * + * The parameters must fit the group object. Otherwise it will stay unchanged. + * + * @returns true if the value of the group object has changed + */ + bool valueNoSendCompare(const KNXValue& value, const Dpt& type); + /** * set the current value of the group object. * @param value the value the group object is set to From 1f426e9203dcb4dd25c891a6321d64df20fb8431 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cornelius=20K=C3=B6pp?= Date: Mon, 22 Jan 2024 20:47:59 +0100 Subject: [PATCH 04/44] Update for New Uninitialized-Handling --- src/knx/group_object.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/knx/group_object.cpp b/src/knx/group_object.cpp index 80865f4..0abbfc0 100644 --- a/src/knx/group_object.cpp +++ b/src/knx/group_object.cpp @@ -290,7 +290,7 @@ void GroupObject::valueNoSend(const KNXValue& value, const Dpt& type) bool GroupObject::valueNoSendCompare(const KNXValue& value, const Dpt& type) { - if (_commFlag == Uninitialized) + if (_commFlagEx.uninitialized) { // always set first value this->valueNoSend(value, type); From defffbd5731c59b59f58b929f6186021d0776d8d Mon Sep 17 00:00:00 2001 From: Ing-Dom Date: Tue, 30 Jan 2024 10:49:26 +0100 Subject: [PATCH 05/44] fix build pipeline errors custom_hwids.py was never executed in the ci pipeline, but the missing script was just a warning. With Plattform I/O Core 6.1.11 missing scripts are errors, so the pipeline fails. --- examples/knx-usb/platformio-ci.ini | 5 +++-- examples/knx-usb/platformio.ini | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/knx-usb/platformio-ci.ini b/examples/knx-usb/platformio-ci.ini index 60cc010..dc8e232 100644 --- a/examples/knx-usb/platformio-ci.ini +++ b/examples/knx-usb/platformio-ci.ini @@ -14,8 +14,9 @@ framework = arduino ; VID must be changed to some known KNX Manufacturer ; so that the KNX USB interface gets recognized by ETS -extra_scripts = pre:custom_hwids.py -board_build.usb_product="KNX RF - USB Interface" +; not possible within ci +;extra_scripts = pre:custom_hwids.py +;board_build.usb_product="KNX RF - USB Interface" lib_deps = SPI diff --git a/examples/knx-usb/platformio.ini b/examples/knx-usb/platformio.ini index 2d7a710..b227cc3 100644 --- a/examples/knx-usb/platformio.ini +++ b/examples/knx-usb/platformio.ini @@ -28,7 +28,7 @@ board_build.usb_product="KNX RF - USB Interface" lib_deps = SPI Adafruit TinyUSB Library@0.7.1 - https://github.com/thelsing/FlashStorage.git + ;https://github.com/thelsing/FlashStorage.git knx build_flags = From 9d7c8acde66ab523b28841bd242c75c6166c4ed9 Mon Sep 17 00:00:00 2001 From: Ing-Dom Date: Tue, 13 Feb 2024 09:38:51 +0100 Subject: [PATCH 06/44] update library.properties to V2.0.0 --- library.properties | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/library.properties b/library.properties index 441ebef..980e8ea 100644 --- a/library.properties +++ b/library.properties @@ -1,10 +1,10 @@ name=knx -version=1.2.0 -author=Thomas Kunze -maintainer=Thomas Kunze +version=2.0.0 +author=Thomas Kunze et al. +maintainer=OpenKNX Team sentence=knx stack paragraph= category=Communication -url=https://github.com/thelsing/knx +url=https://github.com/OpenKNX/knx architectures=* -includes=knx.h +includes=knx.h \ No newline at end of file From cacbd9f1751e3fbf20f0ca518f0702489cec4bc8 Mon Sep 17 00:00:00 2001 From: Ing-Dom Date: Mon, 26 Feb 2024 12:40:29 +0100 Subject: [PATCH 07/44] add rp2040 build environment to knx-demo example for both example project and ci --- examples/knx-demo/platformio-ci.ini | 17 +++++++++++++++++ examples/knx-demo/platformio.ini | 23 +++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/examples/knx-demo/platformio-ci.ini b/examples/knx-demo/platformio-ci.ini index 2f0b04c..0ea2778 100644 --- a/examples/knx-demo/platformio-ci.ini +++ b/examples/knx-demo/platformio-ci.ini @@ -79,3 +79,20 @@ build_flags = -DMASK_VERSION=0x07B0 -Wno-unknown-pragmas -DUSE_DATASECURE + +;--- RP2040 ----------------------------------------------- +[env:rp2040] +framework = arduino +platform = https://github.com/maxgerhardt/platform-raspberrypi.git#182d833 +platform_packages = framework-arduinopico @ https://github.com/earlephilhower/arduino-pico/releases/download/3.6.2/rp2040-3.6.2.zip +board = rpipico +board_build.core = earlephilhower + +lib_deps = + knx + +build_flags = + -DMASK_VERSION=0x07B0 + -DKNX_FLASH_SIZE=4096 + -D PIO_FRAMEWORK_ARDUINO_ENABLE_RTTI + -Wno-unknown-pragmas diff --git a/examples/knx-demo/platformio.ini b/examples/knx-demo/platformio.ini index f47cade..0db86f5 100644 --- a/examples/knx-demo/platformio.ini +++ b/examples/knx-demo/platformio.ini @@ -146,3 +146,26 @@ build_flags = -Wno-unknown-pragmas extra_scripts = ../scripts/stm32rdu.py + + +;--- RP2040 ----------------------------------------------- +[env:rp2040] +framework = arduino +platform = https://github.com/maxgerhardt/platform-raspberrypi.git#182d833 +platform_packages = framework-arduinopico @ https://github.com/earlephilhower/arduino-pico/releases/download/3.6.2/rp2040-3.6.2.zip +board = rpipico +board_build.core = earlephilhower +; We consider that the this projects is opened within its project directory +; while working with VS Code. + + +lib_deps = + knx=file://../../../knx + +lib_ldf_mode = deep+ + +build_flags = + -DMASK_VERSION=0x07B0 + -DKNX_FLASH_SIZE=4096 + -D PIO_FRAMEWORK_ARDUINO_ENABLE_RTTI + -Wno-unknown-pragmas \ No newline at end of file From 03f55f5503759061800063814dbb5f357e281b44 Mon Sep 17 00:00:00 2001 From: Marco Scholl Date: Mon, 26 Feb 2024 23:55:38 +0100 Subject: [PATCH 08/44] Makes the data link layer accessible from outside --- src/knx/bau07B0.cpp | 3 +++ src/knx/bau07B0.h | 3 ++- src/knx/bau091A.cpp | 7 +++++++ src/knx/bau091A.h | 2 ++ src/knx/bau27B0.cpp | 4 +++- src/knx/bau27B0.h | 1 + src/knx/bau2920.cpp | 7 +++++++ src/knx/bau2920.h | 2 ++ src/knx/bau57B0.cpp | 3 +++ src/knx/bau57B0.h | 3 ++- 10 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/knx/bau07B0.cpp b/src/knx/bau07B0.cpp index c68ecb2..c30906b 100644 --- a/src/knx/bau07B0.cpp +++ b/src/knx/bau07B0.cpp @@ -151,4 +151,7 @@ bool Bau07B0::isAckRequired(uint16_t address, bool isGrpAddr) return false; } +TpUartDataLinkLayer* Bau07B0::getDataLinkLayer() { + return (TpUartDataLinkLayer*)&_dlLayer; +} #endif diff --git a/src/knx/bau07B0.h b/src/knx/bau07B0.h index b9c1e6c..b87ca6d 100644 --- a/src/knx/bau07B0.h +++ b/src/knx/bau07B0.h @@ -15,7 +15,8 @@ class Bau07B0 : public BauSystemBDevice, public ITpUartCallBacks, public DataLin void loop() override; bool enabled() override; void enabled(bool value) override; - + + TpUartDataLinkLayer* getDataLinkLayer(); protected: InterfaceObject* getInterfaceObject(uint8_t idx); InterfaceObject* getInterfaceObject(ObjectType objectType, uint8_t objectInstance); diff --git a/src/knx/bau091A.cpp b/src/knx/bau091A.cpp index 2465bc4..202315f 100644 --- a/src/knx/bau091A.cpp +++ b/src/knx/bau091A.cpp @@ -167,4 +167,11 @@ bool Bau091A::isAckRequired(uint16_t address, bool isGrpAddr) return false; } +IpDataLinkLayer* Bau091A::getPrimaryDataLinkLayer() { + return (IpDataLinkLayer*)&_dlLayerPrimary; +} + +TpUartDataLinkLayer* Bau091A::getSecondaryDataLinkLayer() { + return (TpUartDataLinkLayer*)&_dlLayerSecondary; +} #endif diff --git a/src/knx/bau091A.h b/src/knx/bau091A.h index 606fbda..59b8151 100644 --- a/src/knx/bau091A.h +++ b/src/knx/bau091A.h @@ -18,6 +18,8 @@ class Bau091A : public BauSystemBCoupler, public ITpUartCallBacks, public DataLi bool enabled() override; void enabled(bool value) override; + IpDataLinkLayer* getPrimaryDataLinkLayer(); + TpUartDataLinkLayer* getSecondaryDataLinkLayer(); protected: InterfaceObject* getInterfaceObject(uint8_t idx); InterfaceObject* getInterfaceObject(ObjectType objectType, uint8_t objectInstance); diff --git a/src/knx/bau27B0.cpp b/src/knx/bau27B0.cpp index 6180539..e11cde7 100644 --- a/src/knx/bau27B0.cpp +++ b/src/knx/bau27B0.cpp @@ -181,5 +181,7 @@ void Bau27B0::domainAddressSerialNumberReadLocalConfirm(Priority priority, HopCo { } - +RfDataLinkLayer* Bau27B0::getDataLinkLayer() { + return (RfDataLinkLayer*)&_dlLayer; +} #endif // #ifdef USE_RF diff --git a/src/knx/bau27B0.h b/src/knx/bau27B0.h index 222a848..b5d5e81 100644 --- a/src/knx/bau27B0.h +++ b/src/knx/bau27B0.h @@ -22,6 +22,7 @@ class Bau27B0 : public BauSystemBDevice bool enabled() override; void enabled(bool value) override; + RfDataLinkLayer* getDataLinkLayer(); protected: InterfaceObject* getInterfaceObject(uint8_t idx); InterfaceObject* getInterfaceObject(ObjectType objectType, uint8_t objectInstance); diff --git a/src/knx/bau2920.cpp b/src/knx/bau2920.cpp index f5e5367..3ed7827 100644 --- a/src/knx/bau2920.cpp +++ b/src/knx/bau2920.cpp @@ -154,4 +154,11 @@ void Bau2920::loop() BauSystemBCoupler::loop(); } +TpUartDataLinkLayer* Bau2920::getPrimaryDataLinkLayer() { + return (TpUartDataLinkLayer*)&_dlLayerPrimary; +} + +RfDataLinkLayer* Bau2920::getSecondaryDataLinkLayer() { + return (RfDataLinkLayer*)&_dlLayerSecondary; +} #endif diff --git a/src/knx/bau2920.h b/src/knx/bau2920.h index 43a5bc5..6c0bbcb 100644 --- a/src/knx/bau2920.h +++ b/src/knx/bau2920.h @@ -22,6 +22,8 @@ class Bau2920 : public BauSystemBCoupler bool enabled() override; void enabled(bool value) override; + TpUartDataLinkLayer* getPrimaryDataLinkLayer(); + RfDataLinkLayer* getSecondaryDataLinkLayer(); protected: InterfaceObject* getInterfaceObject(uint8_t idx); InterfaceObject* getInterfaceObject(ObjectType objectType, uint8_t objectInstance); diff --git a/src/knx/bau57B0.cpp b/src/knx/bau57B0.cpp index 75522aa..2e3b7d2 100644 --- a/src/knx/bau57B0.cpp +++ b/src/knx/bau57B0.cpp @@ -145,4 +145,7 @@ void Bau57B0::loop() #endif } +IpDataLinkLayer* Bau57B0::getDataLinkLayer() { + return (IpDataLinkLayer*)&_dlLayer; +} #endif diff --git a/src/knx/bau57B0.h b/src/knx/bau57B0.h index f6309f9..fb437a1 100644 --- a/src/knx/bau57B0.h +++ b/src/knx/bau57B0.h @@ -15,7 +15,8 @@ class Bau57B0 : public BauSystemBDevice, public DataLinkLayerCallbacks void loop() override; bool enabled() override; void enabled(bool value) override; - + + IpDataLinkLayer* getDataLinkLayer(); protected: InterfaceObject* getInterfaceObject(uint8_t idx); InterfaceObject* getInterfaceObject(ObjectType objectType, uint8_t objectInstance); From f5724c64d1366c39f9e710f682ea9c6b8bf7159f Mon Sep 17 00:00:00 2001 From: Marco Scholl Date: Tue, 27 Feb 2024 16:02:29 +0100 Subject: [PATCH 09/44] Extends the platforms with additional uart methods. --- src/arduino_platform.cpp | 5 +++++ src/arduino_platform.h | 1 + src/knx/platform.cpp | 8 ++++++++ src/knx/platform.h | 2 ++ src/rp2040_arduino_platform.cpp | 5 +++++ src/rp2040_arduino_platform.h | 2 ++ 6 files changed, 23 insertions(+) diff --git a/src/arduino_platform.cpp b/src/arduino_platform.cpp index 62ab8a9..d23d4c2 100644 --- a/src/arduino_platform.cpp +++ b/src/arduino_platform.cpp @@ -103,6 +103,11 @@ size_t ArduinoPlatform::readBytesUart(uint8_t *buffer, size_t length) return length; } +void ArduinoPlatform::flushUart() +{ + return _knxSerial->flush(); +} + #ifndef KNX_NO_SPI void ArduinoPlatform::setupSpi() { diff --git a/src/arduino_platform.h b/src/arduino_platform.h index 29d846e..9118920 100644 --- a/src/arduino_platform.h +++ b/src/arduino_platform.h @@ -25,6 +25,7 @@ class ArduinoPlatform : public Platform virtual size_t writeUart(const uint8_t* buffer, size_t size); virtual int readUart(); virtual size_t readBytesUart(uint8_t* buffer, size_t length); + virtual void flushUart(); //spi #ifndef KNX_NO_SPI diff --git a/src/knx/platform.cpp b/src/knx/platform.cpp index 545e3ad..2b398ae 100644 --- a/src/knx/platform.cpp +++ b/src/knx/platform.cpp @@ -57,6 +57,14 @@ void Platform::closeUart() void Platform::setupUart() {} +bool Platform::overflowUart() +{ + return false; +} + +void Platform::flushUart() +{} + uint32_t Platform::currentIpAddress() { return 0x01020304; diff --git a/src/knx/platform.h b/src/knx/platform.h index b9e4c5c..ddb1a67 100644 --- a/src/knx/platform.h +++ b/src/knx/platform.h @@ -62,6 +62,8 @@ class Platform virtual size_t writeUart(const uint8_t* buffer, size_t size); virtual int readUart(); virtual size_t readBytesUart(uint8_t* buffer, size_t length); + virtual bool overflowUart(); + virtual void flushUart(); // SPI virtual void setupSpi(); diff --git a/src/rp2040_arduino_platform.cpp b/src/rp2040_arduino_platform.cpp index ddc89cb..dce06e8 100644 --- a/src/rp2040_arduino_platform.cpp +++ b/src/rp2040_arduino_platform.cpp @@ -85,6 +85,11 @@ void RP2040ArduinoPlatform::knxUartPins(pin_size_t rxPin, pin_size_t txPin) _txPin = txPin; } +bool RP2040ArduinoPlatform::overflowUart() { + SerialUART* serial = dynamic_cast(_knxSerial); + return serial->overflow(); +} + void RP2040ArduinoPlatform::setupUart() { SerialUART* serial = dynamic_cast(_knxSerial); diff --git a/src/rp2040_arduino_platform.h b/src/rp2040_arduino_platform.h index b4e7f8d..4d71862 100644 --- a/src/rp2040_arduino_platform.h +++ b/src/rp2040_arduino_platform.h @@ -68,6 +68,8 @@ public: // uart void knxUartPins(pin_size_t rxPin, pin_size_t txPin); void setupUart(); + virtual bool overflowUart(); + // unique serial number uint32_t uniqueSerialNumber() override; From 6b2ac7e50bccd468deba31ebafac68be72d0f3c8 Mon Sep 17 00:00:00 2001 From: Marco Scholl Date: Thu, 28 Mar 2024 08:33:15 +0100 Subject: [PATCH 10/44] Adds dma support for rp2040 uart --- src/rp2040_arduino_platform.cpp | 290 ++++++++++++++++++++++++++------ src/rp2040_arduino_platform.h | 33 +++- 2 files changed, 266 insertions(+), 57 deletions(-) diff --git a/src/rp2040_arduino_platform.cpp b/src/rp2040_arduino_platform.cpp index dce06e8..691d768 100644 --- a/src/rp2040_arduino_platform.cpp +++ b/src/rp2040_arduino_platform.cpp @@ -4,7 +4,7 @@ Plattform for Raspberry Pi Pico and other RP2040 boards by SirSydom 2021-2022 made to work with arduino-pico - "Raspberry Pi Pico Arduino core, for all RP2040 boards" -by Earl E. Philhower III https://github.com/earlephilhower/arduino-pico +by Earl E. Philhower III https://github.com/earlephilhower/arduino-pico RTTI must be set to enabled in the board options @@ -31,20 +31,77 @@ For usage of KNX-IP you have to define either #include -//Pi Pico specific libs -#include // EEPROM emulation in flash, part of Earl E Philhowers Pi Pico Arduino support -#include // from Pico SDK -#include // from Pico SDK -#include // from Pico SDK +// Pi Pico specific libs +#include // EEPROM emulation in flash, part of Earl E Philhowers Pi Pico Arduino support +#include // from Pico SDK +#include // from Pico SDK +#include // from Pico SDK + +#ifdef USE_KNX_DMA_UART +#include +// constexpr uint32_t uartDmaTransferCount = 0b1111111111; +constexpr uint32_t uartDmaTransferCount = UINT32_MAX; +constexpr uint8_t uartDmaBufferExp = 8u; // 2**BufferExp +constexpr uint16_t uartDmaBufferSize = (1u << uartDmaBufferExp); +int8_t uartDmaChannel = -1; +volatile uint8_t __attribute__((aligned(uartDmaBufferSize))) uartDmaBuffer[uartDmaBufferSize] = {}; +volatile uint32_t uartDmaReadCount = 0; +volatile uint16_t uartDmaRestartCount = 0; +volatile uint32_t uartDmaWriteCount2 = 0; +volatile uint32_t uartDmaAvail = 0; + +// Liefert die Zahl der gelesenen Bytes seit dem DMA Transferstart +inline uint32_t uartDmaWriteCount() +{ + uartDmaWriteCount2 = uartDmaTransferCount - dma_channel_hw_addr(uartDmaChannel)->transfer_count; + return uartDmaWriteCount2; +} + +// Liefert die aktuelle Schreibposition im DMA Buffer +inline uint16_t uartDmaWriteBufferPosition() +{ + return uartDmaWriteCount() % uartDmaBufferSize; +} + +// Liefert die aktuelle Leseposition im DMA Buffer +inline uint16_t uartDmaReadBufferPosition() +{ + return uartDmaReadCount % uartDmaBufferSize; +} + +// Liefert die aktuelle Leseposition als Pointer +inline uint8_t* uartDmaReadAddr() +{ + return ((uint8_t*)uartDmaBuffer + uartDmaReadBufferPosition()); +} + +// Startet den Transfer nach Abschluss neu. +void __time_critical_func(uartDmaRestart)() +{ + // println("Restart"); + uartDmaRestartCount = uartDmaWriteBufferPosition() - uartDmaReadBufferPosition(); + + // wenn uartDmaRestartCount == 0 ist, wurde alles verarbeitet und der read count kann mit dem neustart wieder auf 0 gesetzt werden. + if (uartDmaRestartCount == 0) + { + uartDmaReadCount = 0; + } + + asm volatile("" ::: "memory"); + dma_hw->ints0 = 1u << uartDmaChannel; // clear DMA IRQ0 flag + asm volatile("" ::: "memory"); + dma_channel_set_write_addr(uartDmaChannel, uartDmaBuffer, true); +} +#endif #define FLASHPTR ((uint8_t*)XIP_BASE + KNX_FLASH_OFFSET) #ifndef USE_RP2040_EEPROM_EMULATION -#if KNX_FLASH_SIZE%4096 +#if KNX_FLASH_SIZE % 4096 #error "KNX_FLASH_SIZE must be multiple of 4096" #endif -#if KNX_FLASH_OFFSET%4096 +#if KNX_FLASH_OFFSET % 4096 #error "KNX_FLASH_OFFSET must be multiple of 4096" #endif #endif @@ -57,26 +114,27 @@ extern Wiznet5500lwIP KNX_NETIF; #endif RP2040ArduinoPlatform::RP2040ArduinoPlatform() -#ifndef KNX_NO_DEFAULT_UART +#if !defined(KNX_NO_DEFAULT_UART) && !defined(USE_KNX_DMA_UART) : ArduinoPlatform(&KNX_SERIAL) #endif { - #ifdef KNX_UART_RX_PIN +#ifdef KNX_UART_RX_PIN _rxPin = KNX_UART_RX_PIN; - #endif - #ifdef KNX_UART_TX_PIN +#endif +#ifdef KNX_UART_TX_PIN _txPin = KNX_UART_TX_PIN; - #endif - #ifndef USE_RP2040_EEPROM_EMULATION +#endif +#ifndef USE_RP2040_EEPROM_EMULATION _memoryType = Flash; - #endif +#endif } -RP2040ArduinoPlatform::RP2040ArduinoPlatform( HardwareSerial* s) : ArduinoPlatform(s) +RP2040ArduinoPlatform::RP2040ArduinoPlatform(HardwareSerial* s) + : ArduinoPlatform(s) { - #ifndef USE_RP2040_EEPROM_EMULATION +#ifndef USE_RP2040_EEPROM_EMULATION _memoryType = Flash; - #endif +#endif } void RP2040ArduinoPlatform::knxUartPins(pin_size_t rxPin, pin_size_t txPin) @@ -85,36 +143,162 @@ void RP2040ArduinoPlatform::knxUartPins(pin_size_t rxPin, pin_size_t txPin) _txPin = txPin; } -bool RP2040ArduinoPlatform::overflowUart() { +bool RP2040ArduinoPlatform::overflowUart() +{ +#ifdef USE_KNX_DMA_UART + // during dma restart + bool ret; + const uint32_t writeCount = uartDmaWriteCount(); + if (uartDmaRestartCount > 0) + ret = writeCount >= (uartDmaBufferSize - uartDmaRestartCount - 1); + else + ret = (writeCount - uartDmaReadCount) > uartDmaBufferSize; + + // if (ret) + // { + // println(uartDmaWriteBufferPosition()); + // println(uartDmaReadBufferPosition()); + // println(uartDmaWriteCount()); + // println(uartDmaReadCount); + // println(uartDmaRestartCount); + // printHex("BUF: ", (const uint8_t *)uartDmaBuffer, uartDmaBufferSize); + // println("OVERFLOW"); + // while (true) + // ; + // } + return ret; +#else SerialUART* serial = dynamic_cast(_knxSerial); return serial->overflow(); +#endif } void RP2040ArduinoPlatform::setupUart() { +#ifdef USE_KNX_DMA_UART + if (uartDmaChannel == -1) + { + // configure uart0 + gpio_set_function(_rxPin, GPIO_FUNC_UART); + gpio_set_function(_txPin, GPIO_FUNC_UART); + uart_init(KNX_DMA_UART, 19200); + uart_set_hw_flow(KNX_DMA_UART, false, false); + uart_set_format(KNX_DMA_UART, 8, 1, UART_PARITY_EVEN); + uart_set_fifo_enabled(KNX_DMA_UART, false); + + // configure uart0 + uartDmaChannel = dma_claim_unused_channel(true); // get free channel for dma + dma_channel_config dmaConfig = dma_channel_get_default_config(uartDmaChannel); + channel_config_set_transfer_data_size(&dmaConfig, DMA_SIZE_8); + channel_config_set_read_increment(&dmaConfig, false); + channel_config_set_write_increment(&dmaConfig, true); + channel_config_set_high_priority(&dmaConfig, true); + channel_config_set_ring(&dmaConfig, true, uartDmaBufferExp); + channel_config_set_dreq(&dmaConfig, KNX_DMA_UART_DREQ); + dma_channel_set_read_addr(uartDmaChannel, &uart_get_hw(uart0)->dr, false); + dma_channel_set_write_addr(uartDmaChannel, uartDmaBuffer, false); + dma_channel_set_trans_count(uartDmaChannel, uartDmaTransferCount, false); + dma_channel_set_config(uartDmaChannel, &dmaConfig, true); + dma_channel_set_irq1_enabled(uartDmaChannel, true); + // irq_add_shared_handler(KNX_DMA_IRQ, uartDmaRestart, PICO_SHARED_IRQ_HANDLER_HIGHEST_ORDER_PRIORITY); + irq_set_exclusive_handler(KNX_DMA_IRQ, uartDmaRestart); + irq_set_enabled(KNX_DMA_IRQ, true); + } +#else SerialUART* serial = dynamic_cast(_knxSerial); - if(serial) + if (serial) { if (_rxPin != UART_PIN_NOT_DEFINED) serial->setRX(_rxPin); if (_txPin != UART_PIN_NOT_DEFINED) serial->setTX(_txPin); serial->setPollingMode(); + serial->setFIFOSize(64); } _knxSerial->begin(19200, SERIAL_8E1); - while (!_knxSerial) + while (!_knxSerial) ; +#endif } +#ifdef USE_KNX_DMA_UART +int RP2040ArduinoPlatform::uartAvailable() +{ + if (uartDmaChannel == -1) + return 0; + + if (uartDmaRestartCount > 0) + { + return uartDmaRestartCount; + } + else + { + uint32_t tc = dma_channel_hw_addr(uartDmaChannel)->transfer_count; + uartDmaAvail = tc; + int test = uartDmaTransferCount - tc - uartDmaReadCount; + return test; + } +} + +int RP2040ArduinoPlatform::readUart() +{ + if (!uartAvailable()) + return -1; + + int ret = uartDmaReadAddr()[0]; + // print("< "); + // println(ret, HEX); + uartDmaReadCount++; + + if (uartDmaRestartCount > 0) + { + // process previouse buffer + uartDmaRestartCount--; + + // last char, then reset read count to start at new writer position + if (uartDmaRestartCount == 0) + uartDmaReadCount = 0; + } + + return ret; +} + +size_t RP2040ArduinoPlatform::writeUart(const uint8_t data) +{ + if (uartDmaChannel == -1) + return 0; + + // print("> "); + // println(data, HEX); + while (!uart_is_writable(uart0)) + ; + uart_putc_raw(uart0, data); + return 1; +} + +void RP2040ArduinoPlatform::closeUart() +{ + if (uartDmaChannel >= 0) + { + dma_channel_cleanup(uartDmaChannel); + irq_set_enabled(DMA_IRQ_0, false); + uart_deinit(uart0); + uartDmaChannel = -1; + uartDmaReadCount = 0; + uartDmaRestartCount = 0; + } +} +#endif + uint32_t RP2040ArduinoPlatform::uniqueSerialNumber() { - pico_unique_board_id_t id; // 64Bit unique serial number from the QSPI flash + pico_unique_board_id_t id; // 64Bit unique serial number from the QSPI flash noInterrupts(); rp2040.idleOtherCore(); - flash_get_unique_id(id.id); //pico_get_unique_board_id(&id); + flash_get_unique_id(id.id); // pico_get_unique_board_id(&id); rp2040.resumeOtherCore(); interrupts(); @@ -128,7 +312,7 @@ uint32_t RP2040ArduinoPlatform::uniqueSerialNumber() void RP2040ArduinoPlatform::restart() { println("restart"); - watchdog_reboot(0,0,0); + watchdog_reboot(0, 0, 0); } #ifdef USE_RP2040_EEPROM_EMULATION @@ -137,20 +321,20 @@ void RP2040ArduinoPlatform::restart() #ifdef USE_RP2040_LARGE_EEPROM_EMULATION -uint8_t * RP2040ArduinoPlatform::getEepromBuffer(uint32_t size) +uint8_t* RP2040ArduinoPlatform::getEepromBuffer(uint32_t size) { - if(size%4096) + if (size % 4096) { println("KNX_FLASH_SIZE must be a multiple of 4096"); fatalError(); } - - if(!_rambuff_initialized) + + if (!_rambuff_initialized) { memcpy(_rambuff, FLASHPTR, KNX_FLASH_SIZE); _rambuff_initialized = true; } - + return _rambuff; } @@ -159,10 +343,10 @@ void RP2040ArduinoPlatform::commitToEeprom() noInterrupts(); rp2040.idleOtherCore(); - //ToDo: write block-by-block to prevent writing of untouched blocks - if(memcmp(_rambuff, FLASHPTR, KNX_FLASH_SIZE)) + // ToDo: write block-by-block to prevent writing of untouched blocks + if (memcmp(_rambuff, FLASHPTR, KNX_FLASH_SIZE)) { - flash_range_erase (KNX_FLASH_OFFSET, KNX_FLASH_SIZE); + flash_range_erase(KNX_FLASH_OFFSET, KNX_FLASH_SIZE); flash_range_program(KNX_FLASH_OFFSET, _rambuff, KNX_FLASH_SIZE); } @@ -172,22 +356,22 @@ void RP2040ArduinoPlatform::commitToEeprom() #else -uint8_t * RP2040ArduinoPlatform::getEepromBuffer(uint32_t size) +uint8_t* RP2040ArduinoPlatform::getEepromBuffer(uint32_t size) { - if(size > 4096) + if (size > 4096) { println("KNX_FLASH_SIZE to big for EEPROM emulation (max. 4kB)"); fatalError(); } - - uint8_t * eepromptr = EEPROM.getDataPtr(); - if(eepromptr == nullptr) + uint8_t* eepromptr = EEPROM.getDataPtr(); + + if (eepromptr == nullptr) { EEPROM.begin(4096); eepromptr = EEPROM.getDataPtr(); } - + return eepromptr; } @@ -217,10 +401,10 @@ uint8_t* RP2040ArduinoPlatform::userFlashStart() size_t RP2040ArduinoPlatform::userFlashSizeEraseBlocks() { - if(KNX_FLASH_SIZE <= 0) + if (KNX_FLASH_SIZE <= 0) return 0; else - return ( (KNX_FLASH_SIZE - 1) / (flashPageSize() * flashEraseBlockSize())) + 1; + return ((KNX_FLASH_SIZE - 1) / (flashPageSize() * flashEraseBlockSize())) + 1; } void RP2040ArduinoPlatform::flashErase(uint16_t eraseBlockNum) @@ -228,7 +412,7 @@ void RP2040ArduinoPlatform::flashErase(uint16_t eraseBlockNum) noInterrupts(); rp2040.idleOtherCore(); - flash_range_erase (KNX_FLASH_OFFSET + eraseBlockNum * flashPageSize() * flashEraseBlockSize(), flashPageSize() * flashEraseBlockSize()); + flash_range_erase(KNX_FLASH_OFFSET + eraseBlockNum * flashPageSize() * flashEraseBlockSize(), flashPageSize() * flashEraseBlockSize()); rp2040.resumeOtherCore(); interrupts(); @@ -247,12 +431,12 @@ void RP2040ArduinoPlatform::flashWritePage(uint16_t pageNumber, uint8_t* data) void RP2040ArduinoPlatform::writeBufferedEraseBlock() { - if(_bufferedEraseblockNumber > -1 && _bufferedEraseblockDirty) + if (_bufferedEraseblockNumber > -1 && _bufferedEraseblockDirty) { noInterrupts(); rp2040.idleOtherCore(); - flash_range_erase (KNX_FLASH_OFFSET + _bufferedEraseblockNumber * flashPageSize() * flashEraseBlockSize(), flashPageSize() * flashEraseBlockSize()); + flash_range_erase(KNX_FLASH_OFFSET + _bufferedEraseblockNumber * flashPageSize() * flashEraseBlockSize(), flashPageSize() * flashEraseBlockSize()); flash_range_program(KNX_FLASH_OFFSET + _bufferedEraseblockNumber * flashPageSize() * flashEraseBlockSize(), _eraseblockBuffer, flashPageSize() * flashEraseBlockSize()); rp2040.resumeOtherCore(); @@ -281,7 +465,7 @@ void RP2040ArduinoPlatform::macAddress(uint8_t* addr) #if defined(KNX_IP_W5500) addr = KNX_NETIF.getNetIf()->hwaddr; #elif defined(KNX_IP_WIFI) - uint8_t macaddr[6] = {0,0,0,0,0,0}; + uint8_t macaddr[6] = {0, 0, 0, 0, 0, 0}; addr = KNX_NETIF.macAddress(macaddr); #elif defined(KNX_IP_GENERIC) KNX_NETIF.MACAddress(addr); @@ -294,12 +478,12 @@ void RP2040ArduinoPlatform::setupMultiCast(uint32_t addr, uint16_t port) mcastaddr = IPAddress(htonl(addr)); _port = port; uint8_t result = _udp.beginMulticast(mcastaddr, port); - (void) result; + (void)result; - #ifdef KNX_IP_GENERIC - //if(!_unicast_socket_setup) - // _unicast_socket_setup = UDP_UNICAST.begin(3671); - #endif +#ifdef KNX_IP_GENERIC +// if(!_unicast_socket_setup) +// _unicast_socket_setup = UDP_UNICAST.begin(3671); +#endif // print("Setup Mcast addr: "); // print(mcastaddr.toString().c_str()); @@ -318,7 +502,7 @@ bool RP2040ArduinoPlatform::sendBytesMultiCast(uint8_t* buffer, uint16_t len) { // printHex("<- ",buffer, len); - //ToDo: check if Ethernet is able to receive, return false if not + // ToDo: check if Ethernet is able to receive, return false if not _udp.beginPacket(mcastaddr, _port); _udp.write(buffer, len); _udp.endPacket(); @@ -353,12 +537,12 @@ int RP2040ArduinoPlatform::readBytesMultiCast(uint8_t* buffer, uint16_t maxLen) bool RP2040ArduinoPlatform::sendBytesUniCast(uint32_t addr, uint16_t port, uint8_t* buffer, uint16_t len) { IPAddress ucastaddr(htonl(addr)); - + // print("sendBytesUniCast to:"); // println(ucastaddr.toString().c_str()); #ifdef KNX_IP_GENERIC - if(!_unicast_socket_setup) + if (!_unicast_socket_setup) _unicast_socket_setup = UDP_UNICAST.begin(3671); #endif @@ -376,5 +560,3 @@ bool RP2040ArduinoPlatform::sendBytesUniCast(uint32_t addr, uint16_t port, uint8 #endif #endif - - diff --git a/src/rp2040_arduino_platform.h b/src/rp2040_arduino_platform.h index 4d71862..c02c2a7 100644 --- a/src/rp2040_arduino_platform.h +++ b/src/rp2040_arduino_platform.h @@ -58,6 +58,22 @@ #endif +#if USE_KNX_DMA_UART == 1 +#define KNX_DMA_UART uart1 +#define KNX_DMA_UART_IRQ UART1_IRQ +#define KNX_DMA_UART_DREQ DREQ_UART1_RX +#else +#define KNX_DMA_UART uart0 +#define KNX_DMA_UART_IRQ UART0_IRQ +#define KNX_DMA_UART_DREQ DREQ_UART0_RX +#endif + +#if USE_KNX_DMA_IRQ == 1 +#define KNX_DMA_IRQ DMA_IRQ_1 +#else +#define KNX_DMA_IRQ DMA_IRQ_0 +#endif + class RP2040ArduinoPlatform : public ArduinoPlatform { @@ -67,9 +83,20 @@ public: // uart void knxUartPins(pin_size_t rxPin, pin_size_t txPin); - void setupUart(); - virtual bool overflowUart(); - + void setupUart() override; + bool overflowUart() override; + #ifdef USE_KNX_DMA_UART + int uartAvailable() override; + void closeUart() override; + void knxUart( HardwareSerial* serial) override {}; + HardwareSerial* knxUart() override { return nullptr; }; + size_t writeUart(const uint8_t data) override; + size_t writeUart(const uint8_t* buffer, size_t size) override { return 0; }; + int readUart() override; + size_t readBytesUart(uint8_t* buffer, size_t length) override { return 0; }; + void flushUart() override {}; + #endif + // unique serial number uint32_t uniqueSerialNumber() override; From 1ee78a45d187836cfb838e240b8487a3df80d05f Mon Sep 17 00:00:00 2001 From: Marco Scholl Date: Thu, 28 Mar 2024 08:38:55 +0100 Subject: [PATCH 11/44] Reimplementation of the tpuart data link layer --- src/knx/tp_frame.h | 301 +++++ src/knx/tpuart_data_link_layer.cpp | 1670 ++++++++++++++++++---------- src/knx/tpuart_data_link_layer.h | 163 ++- 3 files changed, 1480 insertions(+), 654 deletions(-) create mode 100644 src/knx/tp_frame.h diff --git a/src/knx/tp_frame.h b/src/knx/tp_frame.h new file mode 100644 index 0000000..856af93 --- /dev/null +++ b/src/knx/tp_frame.h @@ -0,0 +1,301 @@ +#pragma once + +#include "cemi_frame.h" +#include +#include +#include + +// Means that the frame is invalid +#define TP_FRAME_FLAG_INVALID 0b10000000 + +// Means that the frame is an extended frame +#define TP_FRAME_FLAG_EXTENDED 0b01000000 + +// Means that the frame has been repeated +#define TP_FRAME_FLAG_REPEATED 0b00100000 + +// Means that the frame comes from the device itself +#define TP_FRAME_FLAG_ECHO 0b00010000 + +// Means that the frame is processed by this device +#define TP_FRAME_FLAG_ADDRESSED 0b00000100 + +// Means that the frame has been acked by this device. +#define TP_FRAME_FLAG_ACKING 0b00000010 + +// Means that the frame has been acked by other (Busmontior) +#define TP_FRAME_FLAG_ACKED 0b00000001 + +class TpFrame +{ + private: + uint8_t *_data; + uint16_t _size; + uint16_t _maxSize; + uint8_t _flags = 0; + + /* + * Sets a few flags based on the control byte + */ + inline void presetFlags() + { + if (isExtended()) + addFlags(TP_FRAME_FLAG_EXTENDED); + + if (isRepeated()) + addFlags(TP_FRAME_FLAG_REPEATED); + } + + public: + /* + * Convert a CemiFrame into a TpFrame + */ + TpFrame(CemiFrame &cemiFrame) + { + _size = cemiFrame.telegramLengthtTP(); + _maxSize = cemiFrame.telegramLengthtTP(); + _data = (uint8_t *)malloc(cemiFrame.telegramLengthtTP()); + cemiFrame.fillTelegramTP(_data); + presetFlags(); + } + + /* + * Create a TpFrame with a reserved space. + * Used for incoming parsing. + */ + TpFrame(uint16_t maxSize = 263) + : _maxSize(maxSize) + { + _data = new uint8_t[_maxSize]; + _size = 0; + } + + /* + * Free the data area + */ + ~TpFrame() + { + free(_data); + } + + /* + * Add a byte at end. + * Used for incoming parsing. + */ + inline void addByte(uint8_t byte) + { + if (!isFull()) + { + _data[_size] = byte; + _size++; + } + + // Read meta data for flags + if (_size == 1) + presetFlags(); + } + + /* + * Current frame size. This may differ from the actual size as long as the frame is not complete. + */ + inline uint16_t size() + { + return _size; + } + + /* + * Returns the assigned flags + */ + inline uint16_t flags() + { + return _flags; + } + + /* + * Adds one or more flags + */ + inline void addFlags(uint8_t flags) + { + _flags |= flags; + } + + /* + * Returns a pointer to the data + */ + inline uint8_t *data() + { + return _data; + } + + /* + * Returns the byte corresponding to the specified position + */ + inline uint8_t data(uint16_t pos) + { + return _data[pos]; + } + + /* + * Resets the internal values to refill the frame. + */ + inline void reset() + { + _size = 0; + _flags = 0; + // It is important to fill the _data with zeros so that the length is 0 as long as the value has not yet been read in. + memset(_data, 0x0, _maxSize); + } + + /* + * Checks whether the frame has been imported completely + */ + inline bool isFull() + { + return _size >= (_size >= 7 ? fullSize() : _maxSize); + } + + /* + * Returns is the frame exteneded or not + */ + inline bool isExtended() + { + return (_data[0] & 0xD3) == 0x10; + } + + /* + * Returns the source + * Assumes that enough data has been imported. + */ + inline uint16_t source() + { + return isExtended() ? (_data[2] << 8) + _data[3] : (_data[1] << 8) + _data[2]; + } + + inline std::string humanSource() + { + uint16_t value = source(); + char buffer[10]; + sprintf(buffer, "%02i.%02i.%03i", (value >> 12 & 0b1111), (value >> 8 & 0b1111), (value & 0b11111111)); + return buffer; + } + + inline std::string humanDestination() + { + uint16_t value = destination(); + char buffer[10]; + if (isGroupAddress()) + sprintf(buffer, "%02i/%02i/%03i", (value >> 11 & 0b1111), (value >> 8 & 0b111), (value & 0b11111111)); + else + sprintf(buffer, "%02i.%02i.%03i", (value >> 12 & 0b1111), (value >> 8 & 0b1111), (value & 0b11111111)); + + return buffer; + } + + /* + * Returns the destination + * Assumes that enough data has been imported. + */ + inline uint16_t destination() + { + return isExtended() ? (_data[4] << 8) + _data[5] : (_data[3] << 8) + _data[4]; + } + + /* + * Returns the payload size (with checksum) + * Assumes that enough data has been imported. + */ + inline uint8_t payloadSize() + { + return isExtended() ? _data[6] : _data[5] & 0b1111; + } + + /* + * Returns the header size + */ + inline uint8_t headerSize() + { + return isExtended() ? 9 : 8; + } + + /* + * Returns the frame size based on header and payload size. + * Assumes that enough data has been imported. + */ + inline uint16_t fullSize() + { + return headerSize() + payloadSize(); + } + + /* + * Returns if the destination is a group address + * Assumes that enough data has been imported. + */ + inline bool isGroupAddress() + { + return isExtended() ? (_data[1] >> 7) & 0b1 : (_data[5] >> 7) & 0b1; + } + + /* + * Calculates the size of a CemiFrame. A CemiFrame has 2 additional bytes at the beginning. + * An additional byte is added to a standard frame, as this still has to be converted into an extendend. + */ + uint16_t cemiSize() + { + return fullSize() + (isExtended() ? 2 : 3); + } + + /** + * Creates a buffer and converts the TpFrame into a CemiFrame. + * Important: After processing (i.e. also after using the CemiFrame), the reference must be released manually. + */ + uint8_t *cemiData() + { + uint8_t *cemiBuffer = (uint8_t *)malloc(cemiSize()); + + // Das CEMI erwartet die Daten im Extended format inkl. zwei zusätzlicher Bytes am Anfang. + cemiBuffer[0] = 0x29; + cemiBuffer[1] = 0x0; + cemiBuffer[2] = _data[0]; + if (isExtended()) + { + memcpy(cemiBuffer + 2, _data, fullSize()); + } + else + { + cemiBuffer[3] = _data[5] & 0xF0; + memcpy(cemiBuffer + 4, _data + 1, 4); + cemiBuffer[8] = _data[5] & 0x0F; + memcpy(cemiBuffer + 9, _data + 6, cemiBuffer[8] + 2); + } + + return cemiBuffer; + } + + /* + * Checks whether the frame is complete and valid. + */ + inline bool isValid() + { + if (!isComplete()) + return false; + + uint8_t sum = 0; + const uint16_t s = fullSize() - 1; + for (uint16_t i = 0; i < s; i++) + sum ^= _data[i]; + return _data[s] == (uint8_t)~sum; + } + + /* + * Checks whether the frame is long enough to match the length specified in the frame + */ + inline bool isComplete() + { + return _size == fullSize(); + } + + inline bool isRepeated() + { + return !(_data[0] & 0b100000); + } +}; \ No newline at end of file diff --git a/src/knx/tpuart_data_link_layer.cpp b/src/knx/tpuart_data_link_layer.cpp index 1c9f777..271ff6c 100644 --- a/src/knx/tpuart_data_link_layer.cpp +++ b/src/knx/tpuart_data_link_layer.cpp @@ -1,620 +1,908 @@ #include "config.h" #ifdef USE_TP +#pragma GCC optimize("O3") -#include "tpuart_data_link_layer.h" -#include "bits.h" -#include "platform.h" -#include "device_object.h" #include "address_table_object.h" +#include "bits.h" #include "cemi_frame.h" +#include "device_object.h" +#include "platform.h" +#include "tpuart_data_link_layer.h" -#include -#include - -// Activate trace output -//#define DBG_TRACE - -// NCN5120 -//#define NCN5120 +/* + * A new implementation of the tpuart connection. + * Author Marco Scholl + * + * To avoid misunderstandings (also for myself), this is in German, at least for the time being. + */ // services Host -> Controller : // internal commands, device specific -#define U_RESET_REQ 0x01 -#define U_STATE_REQ 0x02 -#define U_SET_BUSY_REQ 0x03 -#define U_QUIT_BUSY_REQ 0x04 -#define U_BUSMON_REQ 0x05 -#define U_SET_ADDRESS_REQ 0xF1 // different on TP-UART -#define U_SET_REPETITION_REQ 0xF2 -#define U_L_DATA_OFFSET_REQ 0x08 //-0x0C -#define U_SYSTEM_STATE 0x0D -#define U_STOP_MODE_REQ 0x0E +#define U_RESET_REQ 0x01 +#define U_STATE_REQ 0x02 +#define U_SET_BUSY_REQ 0x03 +#define U_QUIT_BUSY_REQ 0x04 +#define U_BUSMON_REQ 0x05 +#define U_SET_ADDRESS_REQ 0xF1 // different on TP-UART +#define U_L_DATA_OFFSET_REQ 0x08 //-0x0C +#define U_SYSTEM_MODE 0x0D +#define U_STOP_MODE_REQ 0x0E #define U_EXIT_STOP_MODE_REQ 0x0F -#define U_ACK_REQ 0x10 //-0x17 -#define U_ACK_REQ_NACK 0x04 -#define U_ACK_REQ_BUSY 0x02 -#define U_ACK_REQ_ADRESSED 0x01 -#define U_CONFIGURE_REQ 0x18 -#define U_INT_REG_WR_REQ 0x28 -#define U_INT_REG_RD_REQ 0x38 -#define U_POLLING_STATE_REQ 0xE0 +#define U_ACK_REQ 0x10 //-0x17 +#define U_ACK_REQ_NACK 0x04 +#define U_ACK_REQ_BUSY 0x02 +#define U_ACK_REQ_ADRESSED 0x01 +#define U_POLLING_STATE_REQ 0xE0 -//knx transmit data commands -#define U_L_DATA_START_CONT_REQ 0x80 //-0xBF -#define U_L_DATA_END_REQ 0x40 //-0x7F +// Only on NCN51xx available +#ifdef NCN5120 +#define U_CONFIGURE_REQ 0x18 +#define U_CONFIGURE_MARKER_REQ 0x1 +#define U_CONFIGURE_CRC_CCITT_REQ 0x2 +#define U_CONFIGURE_AUTO_POLLING_REQ 0x4 +#define U_SET_REPETITION_REQ 0xF2 +#else +#define U_MXRSTCNT 0x24 +#endif -//serices to host controller +// knx transmit data commands +#define U_L_DATA_START_REQ 0x80 +#define U_L_DATA_CONT_REQ 0x80 //-0xBF +#define U_L_DATA_END_REQ 0x40 //-0x7F + +// serices to host controller // DLL services (device is transparent) #define L_DATA_STANDARD_IND 0x90 #define L_DATA_EXTENDED_IND 0x10 -#define L_DATA_MASK 0xD3 -#define L_POLL_DATA_IND 0xF0 +#define L_DATA_MASK 0xD3 +#define L_POLL_DATA_IND 0xF0 // acknowledge services (device is transparent in bus monitor mode) -#define L_ACKN_IND 0x00 -#define L_ACKN_MASK 0x33 -#define L_DATA_CON 0x0B -#define L_DATA_CON_MASK 0x7F -#define SUCCESS 0x80 +#define L_ACKN_IND 0x00 +#define L_ACKN_MASK 0x33 +#define L_DATA_CON 0x0B +#define L_DATA_CON_MASK 0x7F +#define SUCCESS 0x80 // control services, device specific -#define U_RESET_IND 0x03 -#define U_STATE_IND 0x07 -#define SLAVE_COLLISION 0x80 -#define RECEIVE_ERROR 0x40 -#define TRANSMIT_ERROR 0x20 -#define PROTOCOL_ERROR 0x10 -#define TEMPERATURE_WARNING 0x08 -#define U_FRAME_STATE_IND 0x13 -#define U_FRAME_STATE_MASK 0x17 -#define PARITY_BIT_ERROR 0x80 +#define U_RESET_IND 0x03 +#define U_STATE_MASK 0x07 +#define U_STATE_IND 0x07 +#define SLAVE_COLLISION 0x80 +#define RECEIVE_ERROR 0x40 +#define TRANSMIT_ERROR 0x20 +#define PROTOCOL_ERROR 0x10 +#define TEMPERATURE_WARNING 0x08 +#define U_FRAME_STATE_IND 0x13 +#define U_FRAME_STATE_MASK 0x17 +#define PARITY_BIT_ERROR 0x80 #define CHECKSUM_LENGTH_ERROR 0x40 -#define TIMING_ERROR 0x20 -#define U_CONFIGURE_IND 0x01 -#define U_CONFIGURE_MASK 0x83 -#define AUTO_ACKNOWLEDGE 0x20 -#define AUTO_POLLING 0x10 -#define CRC_CCITT 0x80 +#define TIMING_ERROR 0x20 +#define U_CONFIGURE_IND 0x01 +#define U_CONFIGURE_MASK 0x83 +#define AUTO_ACKNOWLEDGE 0x20 +#define AUTO_POLLING 0x10 +#define CRC_CCITT 0x80 #define FRAME_END_WITH_MARKER 0x40 -#define U_FRAME_END_IND 0xCB -#define U_STOP_MODE_IND 0x2B -#define U_SYSTEM_STAT_IND 0x4B +#define U_FRAME_END_IND 0xCB +#define U_STOP_MODE_IND 0x2B +#define U_SYSTEM_STAT_IND 0x4B -//tx states -enum { - TX_IDLE, - TX_FRAME, - TX_WAIT_CONN -}; +/* + * NCN51xx Register handling + */ +// write internal registers +#define U_INT_REG_WR_REQ_WD 0x28 +#define U_INT_REG_WR_REQ_ACR0 0x29 +#define U_INT_REG_WR_REQ_ACR1 0x2A +#define U_INT_REG_WR_REQ_ASR0 0x2B +// read internal registers +#define U_INT_REG_RD_REQ_WD 0x38 +#define U_INT_REG_RD_REQ_ACR0 0x39 +#define U_INT_REG_RD_REQ_ACR1 0x3A +#define U_INT_REG_RD_REQ_ASR0 0x3B +// Analog Control Register 0 - Bit values +#define ACR0_FLAG_V20VEN 0x40 +#define ACR0_FLAG_DC2EN 0x20 +#define ACR0_FLAG_XCLKEN 0x10 +#define ACR0_FLAG_TRIGEN 0x08 +#define ACR0_FLAG_V20VCLIMIT 0x04 -//rx states -enum { - RX_WAIT_START, - RX_L_ADDR, - RX_L_DATA, - RX_WAIT_EOP -}; - -#define EOP_TIMEOUT 2 //milli seconds; end of layer-2 packet gap -#ifndef EOPR_TIMEOUT // allow to set EOPR_TIMEOUT externally -#define EOPR_TIMEOUT 8 //ms; relaxed EOP timeout; usally to trigger after NAK -#endif -#define CONFIRM_TIMEOUT 500 //milli seconds -#define RESET_TIMEOUT 100 //milli seconds -#define TX_TIMEPAUSE 0 // 0 means 1 milli seconds - -#ifndef OVERRUN_COUNT -#define OVERRUN_COUNT 7 //bytes; max. allowed bytes in receive buffer (on start) to see it as overrun -#endif - -// If this threshold is reached loop() goes into -// "hog mode" where it stays in loop() while L2 address reception -#define HOGMODE_THRESHOLD 3 // milli seconds - -void TpUartDataLinkLayer::enterRxWaitEOP() +enum { - // Flush input - while (_platform.uartAvailable()) - { - _platform.readUart(); - } - _lastByteRxTime = millis(); - _rxState = RX_WAIT_EOP; + TX_IDLE, + TX_FRAME +}; + +enum +{ + // In diesem Zustand wird auf neue Steuerbefehle gewartet. + RX_IDLE, + + // In diesem Zustand werden alle Bytes als Bytes für ein Frame betrachtet. + RX_FRAME, + + /* + * Dieser Zustand ist speziell und soll verhindern dass Framebytes als Steuerbytes betrachet werden. + * Das Ganze ist leider nötig, weil die verarbeitung nicht syncron verläuft und teilweise durch externe interrupt. + * Dadurch kann die 2,6ms ruhe nicht ermittelt werden um sicher zu sagen das ab jetzt alles wieder Steuercodes sind. + */ + RX_INVALID, + + RX_AWAITING_ACK +}; + +void printFrame(TpFrame *tpframe) +{ + print(tpframe->humanSource().c_str()); + print(" -> "); + print(tpframe->humanDestination().c_str()); + print(" ["); + print((tpframe->flags() & TP_FRAME_FLAG_INVALID) ? 'I' : '_'); // Invalid + print((tpframe->flags() & TP_FRAME_FLAG_EXTENDED) ? 'E' : '_'); // Extended + print((tpframe->flags() & TP_FRAME_FLAG_REPEATED) ? 'R' : '_'); // Repeat + print((tpframe->flags() & TP_FRAME_FLAG_ECHO) ? 'O' : '_'); // My own + print((tpframe->flags() & 0b00001000) ? 'x' : '_'); // Reserve + print((tpframe->flags() & TP_FRAME_FLAG_ADDRESSED) ? 'D' : '_'); // For me + print((tpframe->flags() & TP_FRAME_FLAG_ACKING) ? 'A' : '_'); // ACK recevied + print((tpframe->flags() & TP_FRAME_FLAG_ACKED) ? 'A' : '_'); // ACK sent + print("] "); + printHex("( ", tpframe->data(), tpframe->size(), false); + print(")"); } -void TpUartDataLinkLayer::loop() +/* + * Verarbeitet alle Bytes. + */ +void __isr __time_critical_func(TpUartDataLinkLayer::processRx)(bool isr) { - if (!_enabled) - { - if(_waitConfirmStartTime == 0) - { - if (millis() - _lastResetChipTime > 1000) - { - //reset chip every 1 seconds - _lastResetChipTime = millis(); - _enabled = resetChip(); - } - } else { - _enabled = resetChipTick(); - } - } - - if (!_enabled) + if (!isrLock()) return; - // Loop once and repeat as long we have rx data available - do { - // Signals to communicate from rx part with the tx part - uint8_t dataConnMsg = 0; // The DATA_CONN message just seen or 0 + /* + * Manche Platformen untersützen die Erkennung, ob der Hardwarebuffer übergelaufen ist. + * Theoretisch könnte man nun den Buffer verwerfen, aber dann geht ggf. noch ein gültiges Frame verloren. + * Daher wird später im loop nur eine Info ausgegben und im Byteprocessing wird "versucht" noch darauf einzugehen. + */ + if (_platform.overflowUart()) + _rxOverflow = true; -#ifdef KNX_WAIT_FOR_ADDR - // After seeing a L2 packet start, stay in loop until address bytes are - // received and the AK/NAK packet is sent - bool stayInRx = true; -#elif defined(KNX_AUTO_ADAPT) - // After seeing a L2 packet start, stay in loop until address bytes are - // received and the AK/NAK packet is sent, when last loop call delayed - // by more than HOGMODE_THRESHOLD - bool stayInRx = millis() - _lastLoopTime > HOGMODE_THRESHOLD; - _lastLoopTime = millis(); -#else - // After seeing a L2 packet start, leave loop and hope the loop - // is called early enough to do further processings - bool stayInRx = false; -#endif - - // Loop once and repeat as long we are in the receive phase for the L2 address - do { - uint8_t* buffer = _receiveBuffer + 2; - uint8_t rxByte; - switch (_rxState) - { - case RX_WAIT_START: - if (_platform.uartAvailable()) - { - if (_platform.uartAvailable() > OVERRUN_COUNT) - { - print("input buffer overrun: "); println(_platform.uartAvailable()); - enterRxWaitEOP(); - break; - } - rxByte = _platform.readUart(); -#ifdef DBG_TRACE - print(rxByte, HEX); -#endif - _lastByteRxTime = millis(); - - // Check for layer-2 packets - _RxByteCnt = 0; - _xorSum = 0; - if ((rxByte & L_DATA_MASK) == L_DATA_STANDARD_IND) - { - buffer[_RxByteCnt++] = rxByte; - _xorSum ^= rxByte; - _RxByteCnt++; //convert to L_DATA_EXTENDED - _convert = true; - _rxState = RX_L_ADDR; -#ifdef DBG_TRACE - println("RLS"); -#endif - break; - } - else if ((rxByte & L_DATA_MASK) == L_DATA_EXTENDED_IND) - { - buffer[_RxByteCnt++] = rxByte; - _xorSum ^= rxByte; - _convert = false; - _rxState = RX_L_ADDR; -#ifdef DBG_TRACE - println("RLX"); -#endif - break; - } - - // Handle all single byte packets here - else if ((rxByte & L_DATA_CON_MASK) == L_DATA_CON) - { - dataConnMsg = rxByte; - } - else if (rxByte == L_POLL_DATA_IND) - { - // not sure if this can happen - println("got L_POLL_DATA_IND"); - } - else if ((rxByte & L_ACKN_MASK) == L_ACKN_IND) - { - // this can only happen in bus monitor mode - println("got L_ACKN_IND"); - } - else if (rxByte == U_RESET_IND) - { - println("got U_RESET_IND"); - } - else if ((rxByte & U_STATE_IND) == U_STATE_IND) - { - print("got U_STATE_IND:"); - if (rxByte & 0x80) print (" SC"); - if (rxByte & 0x40) print (" RE"); - if (rxByte & 0x20) print (" TE"); - if (rxByte & 0x10) print (" PE"); - if (rxByte & 0x08) print (" TW"); - println(); - } - else if ((rxByte & U_FRAME_STATE_MASK) == U_FRAME_STATE_IND) - { - print("got U_FRAME_STATE_IND: 0x"); - print(rxByte, HEX); - println(); - } - else if ((rxByte & U_CONFIGURE_MASK) == U_CONFIGURE_IND) - { - print("got U_CONFIGURE_IND: 0x"); - print(rxByte, HEX); - println(); - } - else if (rxByte == U_FRAME_END_IND) - { - println("got U_FRAME_END_IND"); - } - else if (rxByte == U_STOP_MODE_IND) - { - println("got U_STOP_MODE_IND"); - } - else if (rxByte == U_SYSTEM_STAT_IND) - { - print("got U_SYSTEM_STAT_IND: 0x"); - while (true) - { - int tmp = _platform.readUart(); - if (tmp < 0) - continue; - - print(tmp, HEX); - break; - } - println(); - } - else - { - print("got UNEXPECTED: 0x"); - print(rxByte, HEX); - println(); - } - } - break; - case RX_L_ADDR: - if (millis() - _lastByteRxTime > EOPR_TIMEOUT) - { - _rxState = RX_WAIT_START; - println("EOPR @ RX_L_ADDR"); - break; - } - if (!_platform.uartAvailable()) - break; - _lastByteRxTime = millis(); - rxByte = _platform.readUart(); -#ifdef DBG_TRACE - print(rxByte, HEX); -#endif - buffer[_RxByteCnt++] = rxByte; - _xorSum ^= rxByte; - - if (_RxByteCnt == 7) - { - //Destination Address + payload available - //check if echo; ignore repeat bit of control byte - _isEcho = (_sendBuffer != nullptr && (!((buffer[0] ^ _sendBuffer[0]) & ~0x20) && !memcmp(buffer + _convert + 1, _sendBuffer + 1, 5))); - - //convert into Extended.ind - if (_convert) - { - buffer[1] = buffer[6] & 0xF0; - buffer[6] &= 0x0F; - } - - if (!_isEcho) - { - uint8_t c = U_ACK_REQ; - - // The bau knows everything and could either check the address table object (normal device) - // or any filter tables (coupler) to see if we are addressed. - - //check if individual or group address - bool isGroupAddress = (buffer[1] & 0x80) != 0; - uint16_t addr = getWord(buffer + 4); - - if (_cb.isAckRequired(addr, isGroupAddress)) - { - c |= U_ACK_REQ_ADRESSED; - } - - // Hint: We can send directly here, this doesn't disturb other transmissions - // We don't have to update _lastByteTxTime because after U_ACK_REQ the timing is not so tight - _platform.writeUart(c); - } - _rxState = RX_L_DATA; - } - break; - case RX_L_DATA: - if (!_platform.uartAvailable()) - break; - _lastByteRxTime = millis(); - rxByte = _platform.readUart(); -#ifdef DBG_TRACE - print(rxByte, HEX); -#endif - if (_RxByteCnt == MAX_KNX_TELEGRAM_SIZE - 2) - { - println("invalid telegram size"); - enterRxWaitEOP(); - } - else - { - buffer[_RxByteCnt++] = rxByte; - } - - if (_RxByteCnt == buffer[6] + 7 + 2) - { - //complete Frame received, payloadLength+1 for TCPI +1 for CRC - //check if crc is correct - if (rxByte == (uint8_t)(~_xorSum)) - { - if (!_isEcho) - { - _receiveBuffer[0] = 0x29; - _receiveBuffer[1] = 0; -#ifdef DBG_TRACE - unsigned long runTime = millis(); -#endif - frameBytesReceived(_receiveBuffer, _RxByteCnt + 2); -#ifdef DBG_TRACE - runTime = millis() - runTime; - if (runTime > (OVERRUN_COUNT*14)/10) - { - // complain when the runtime was long than the OVERRUN_COUNT allows - print("processing received frame took: "); print(runTime); println(" ms"); - } -#endif - } - _rxState = RX_WAIT_START; -#ifdef DBG_TRACE - println("RX_WAIT_START"); -#endif - } - else - { - println("frame with invalid crc ignored"); - enterRxWaitEOP(); - } - } - else - { - _xorSum ^= rxByte; - } - break; - case RX_WAIT_EOP: - if (millis() - _lastByteRxTime > EOP_TIMEOUT) - { - // found a gap - _rxState = RX_WAIT_START; -#ifdef DBG_TRACE - println("RX_WAIT_START"); -#endif - break; - } - if (_platform.uartAvailable()) - { - _platform.readUart(); - _lastByteRxTime = millis(); - } - break; - default: - println("invalid _rxState"); - enterRxWaitEOP(); - break; - } - } while (_rxState == RX_L_ADDR && (stayInRx || _platform.uartAvailable())); - - // Check for spurios DATA_CONN message - if (dataConnMsg && _txState != TX_WAIT_CONN) { - println("unexpected L_DATA_CON"); - } - - switch (_txState) - { - case TX_IDLE: - if (!isTxQueueEmpty()) - { - loadNextTxFrame(); - _txState = TX_FRAME; -#ifdef DBG_TRACE - println("TX_FRAME"); -#endif - } - break; - case TX_FRAME: - if (millis() - _lastByteTxTime > TX_TIMEPAUSE) - { - if (sendSingleFrameByte() == false) - { - _waitConfirmStartTime = millis(); - _txState = TX_WAIT_CONN; -#ifdef DBG_TRACE - println("TX_WAIT_CONN"); -#endif - } - else - { - _lastByteTxTime = millis(); - } - } - break; - case TX_WAIT_CONN: - if (dataConnMsg) - { - dataConBytesReceived(_receiveBuffer, _RxByteCnt + 2, (dataConnMsg & SUCCESS)); - delete[] _sendBuffer; - _sendBuffer = 0; - _sendBufferLength = 0; - _txState = TX_IDLE; - } - else if (millis() - _waitConfirmStartTime > CONFIRM_TIMEOUT) - { - println("L_DATA_CON not received within expected time"); - uint8_t cemiBuffer[MAX_KNX_TELEGRAM_SIZE]; - cemiBuffer[0] = 0x29; - cemiBuffer[1] = 0; - memcpy((cemiBuffer + 2), _sendBuffer, _sendBufferLength); - dataConBytesReceived(cemiBuffer, _sendBufferLength + 2, false); - delete[] _sendBuffer; - _sendBuffer = 0; - _sendBufferLength = 0; - _txState = TX_IDLE; -#ifdef DBG_TRACE - println("TX_IDLE"); -#endif - } - break; - } - } while (_platform.uartAvailable()); -} - -bool TpUartDataLinkLayer::sendFrame(CemiFrame& frame) -{ - if (!_enabled) + // verarbeiten daten + uint8_t counter = isr ? 10 : 255; + while (_platform.uartAvailable() && counter > 0) { - dataConReceived(frame, false); - return false; + processRxByte(); + counter--; } - addFrameTxQueue(frame); + isrUnlock(); +} + +/* + * Verarbeitet 1 eigehendes Byte (wenn vorhanden) + */ +void TpUartDataLinkLayer::processRxByte() +{ + int byte = _platform.readUart(); + + // RxBuffer empty + if (byte < 0) + return; + + if (_rxState == RX_FRAME) + { + processRxFrameByte(byte); + } + else if ((byte & L_DATA_MASK) == L_DATA_STANDARD_IND || (byte & L_DATA_MASK) == L_DATA_EXTENDED_IND) + { + /* + * Verarbeite ein vorheriges Frame falls noch vorhanden. Das dürfte normal nur im Busmonitor auftreten, weil hier noch auch ein ACK gewartet wird + */ + processRxFrameComplete(); + + _rxFrame->addByte(byte); + + // Provoziere ungültige Frames für Tests + // if (millis() % 20 == 0) + // _rxFrame->addByte(0x1); + + _rxMarker = false; + _rxState = RX_FRAME; + + /* + * Hier wird inital ein Ack ohne Addressed gesetzt. Das dient dazu falls noch ein Ack vom vorherigen Frame gesetzt wurde, + * welches aber ggf nicht rechtzeitig verarbeitet (also nach der Übertragung gesendet wurde) sich nicht auf das neue Frame auswirkt. + * Der Zustand kann beliebig oft gesendet werden. Sobald der Moment gekommen ist, dass ein Ack gesendet wird, schaut TPUart im Buffer + * was der letzte Ackstatus war und sendet diesen. + * + * Das darf man natürlich nur wenn ich nicht gerade selber sende, da man eigene Frames nicht ACKt + */ + if (_txState == TX_IDLE) + { + _platform.writeUart(U_ACK_REQ); + } + } + else if (_rxState == RX_INVALID || _rxOverflow) + { + /* + * Sobald ein Frame ungültig verarbeitet wurde oder ein unbekanntest Kommando eintrifft ist wechselt der Zustand in RX_INVALID. + * Ab jetzt muss ich davon ausgehen, dass einen Übertragungsfehler gegeben hat und die aktuellen Bytes ungültig sind. + * Gleiches gilt auch wenn ein HW Overflow erkannt wurde + * + * - Dieser Zustand wird aufgehoben wenn ein Frame "vermutlich" erkannt wurde. (if Abfrage über dieser) + * - Die Zeit des letzten Frames 3ms vorbei ist (erfolt im loop) + * - Wenn der Markermodus aktiv ist und ein U_FRAME_END_IND sauber erkannt wurde. (passiert hiert) + * + * Ansonsten macht dieser Abschnitt nichts und verwirrft die ungültigen Bytes + */ + if (markerMode()) + { + if (!_rxMarker && byte == U_FRAME_END_IND) + { + _rxMarker = true; + } + else if (_rxMarker && byte == U_FRAME_END_IND) + { + // doppeltes byte gefunden also marker zurück setzten - kein Frameende + _rxMarker = false; + } + else if (_rxMarker) + { + // frame ende gefunden. -> RX_IDLE + _rxMarker = false; + _rxState = RX_IDLE; + } + else + { + // print("RX_INVALID: "); + // println(byte, HEX); + } + } + } + else + { + if (byte == U_RESET_IND) + { + // println("U_RESET_IND"); + } + else if ((byte & U_STATE_MASK) == U_STATE_IND) + { + _tpState |= (byte ^ U_STATE_MASK); +#ifndef NCN5120 + /* + * Filtere "Protocol errors" weil auf anderen BCU wie der Siements dies gesetzt, when das timing nicht stimmt. + * Leider ist kein perfektes Timing möglich so dass dieser Fehler ignoriert wird. Hat auch keine bekannte Auswirkungen. + */ + _tpState &= 0b11101000; +#endif + } + else if ((byte & U_CONFIGURE_MASK) == U_CONFIGURE_IND) + { + // println("U_CONFIGURE_IND"); + } + else if (byte == U_STOP_MODE_IND) + { + // println("U_STOP_MODE_IND"); + } + else if ((byte & L_ACKN_MASK) == L_ACKN_IND) + { + /* + * Wenn ein Frame noch nicht geschlossen wurde und ein Ack rein kommt. + * dann setze noch das ACK. + */ + if (_rxFrame->size() > 0) + { + _rxFrame->addFlags(TP_FRAME_FLAG_ACKED); + processRxFrameComplete(); + } + // println("L_ACKN_IND"); + } + else if ((byte & L_DATA_CON_MASK) == L_DATA_CON) + { + if (_txState == TX_FRAME) + { + const bool success = ((byte ^ L_DATA_CON_MASK) >> 7); + uint8_t *cemiData = _txFrame->cemiData(); + CemiFrame cemiFrame(cemiData, _txFrame->cemiSize()); + dataConReceived(cemiFrame, success); + free(cemiData); + delete _txFrame; + _txFrame = nullptr; + _txState = TX_IDLE; + } + else + { + // Dieses Byte wurde nicht erwartet, da garnichts gesendet wurde. + _rxUnkownControlCounter++; + _rxState = RX_INVALID; + // println("L_DATA_CON"); + } + } + else if (byte == L_POLL_DATA_IND) + { + // println("L_POLL_DATA_IND"); + } + else if ((byte & U_FRAME_STATE_MASK) == U_FRAME_STATE_IND) + { + // println("U_FRAME_STATE_IND"); + } + else + { + _rxUnkownControlCounter++; + // print("Unknown Controlbyte: "); + // println(byte, HEX); + _rxState = RX_INVALID; + } + } + + _rxLastTime = millis(); +} + +/* + * Verarbeite eigehendes Byte eines Frames + */ +void TpUartDataLinkLayer::processRxFrameByte(uint8_t byte) +{ + /* + * Bei aktivem Maker muss das erste U_FRAME_END_IND ignoriert und auf ein Folgebyte gewartet werden. + * Das Folgebyte ist also ausschlaggebend wie dieses Byte zo bewerten ist. + */ + if (markerMode() && (byte == U_FRAME_END_IND && !_rxMarker)) + { + _rxMarker = true; + } + + /* + * Wenn das vorherige Byte ein U_FRAME_END_IND war, und das neue Byte ein U_FRAME_STATE_IND ist, + * dann ist der Empfang sauber abgeschlossen und das Frame kann verarbeitet werden. + */ + else if (_rxMarker && (byte & U_FRAME_STATE_MASK) == U_FRAME_STATE_IND) + { + _rxMarker = false; + processRxFrameComplete(); + + /* + * Setze den Zustand auf RX_IDLE, da durch den Marker sichergestellt ist, + * dass das Frame erfolgreich verarbeitet wurde. Nachfolgende Bytes sind also wieder sauber Steuerbefehle, + * selbst wenn das Frame aufgrund einer ungültigen Checksumme verworfen wurde (was RX_INVAID) bedeuten würde + */ + _rxState = RX_IDLE; + } + + /* + * Dies ist ein hypotetischer Fall, dass die Frames ohne Marker kommen, obwohl der MarkerModus aktiv ist. + * Hier wird der aktuelle Frame abgearbeitet und RX_INVALID gesetzt, da das aktuelle Byte hierbei nicht bearbeitet wird. + * Dieser Fall kann eintreffen wenn der Marker Modus von der TPUart nicht unterstützt wird (NCN51xx Feature). + * TODO vergleiche auf maxSize oder falls vorhanden auf länge aus telegramm -> full() + * TODO Beschriebung anpassen weil es auch bei Byteverlusten auftreten kann + */ + else if (markerMode() && _rxFrame->isFull()) + { + processRxFrameComplete(); + /* + * RX_INVALID weil theoretisch könnte das Frame als gültig verarbeitet worden sein. + * Da aber das aktuelle Byte schon "angefangen" wurde zu verarbeiten, fehlt das in der Verarbeitungskette + * und somit sind die nachfolgenden Bytes nicht verwertbar. + */ + _rxState = RX_INVALID; + } + + /* + * Wenn der Markermodus in aktiv ist, soll das Byte normal verarbeitet werden. + * Wenn der Markermodus aktiv ist, dann darf ein U_FRAME_END_IND Byte nur verarbeitet werden, wenn des vorherige Byte auch ein U_FRAME_END_IND war. + */ + else if (!markerMode() || byte != U_FRAME_END_IND || (byte == U_FRAME_END_IND && _rxMarker)) + { + // Setze den Marker wieder zurück falls aktiv + _rxMarker = false; + // Übernehme das Byte + _rxFrame->addByte(byte); + + // Wenn der Busmonitor gestartet wurde, findet keine Verarbeitung - also auch kein ACKing + if (!_monitoring) + { + // Wenn mehr als 7 bytes vorhanden kann geschaut werden ob das Frame für "mich" bestimmt ist + if (_rxFrame->size() == 7) + { + // Prüfe ob ich für das Frame zuständig bin + if (_forceAck || _cb.isAckRequired(_rxFrame->destination(), _rxFrame->isGroupAddress())) + { + /* + * Speichere die Zuständigkeit dass dieses Frame weiterverarbeitet werden soll. + * Da es keine extra Funktion außer dem isAckRequired gibt, wird das erstmal gleich behandelt. + * Eine spätere Unterscheidung (ggf für Routermodus) muss man dann schauen. + */ + + _rxFrame->addFlags(TP_FRAME_FLAG_ADDRESSED); + + // Das darf man natürlich nur wenn ich nicht gerade selber sende, da man eigene Frames nicht ACKt + if (_txState == TX_IDLE) + { + // Speichere das ein Acking erfolgen soll + _rxFrame->addFlags(TP_FRAME_FLAG_ACKING); + + // und im TPUart damit dieser das ACK schicken kann + _platform.writeUart(U_ACK_REQ | U_ACK_REQ_ADRESSED); + } + } + } + +#ifdef USE_TP_RX_QUEUE + // Prüfe nun ob die RxQueue noch Platz hat für Frame + Size (2) + Flags(1) + if (_rxFrame->size() == 8 && (_rxFrame->flags() & TP_FRAME_FLAG_ADDRESSED)) + { + if (availableInRxQueue() < (_rxFrame->size() + 3)) + { + // Nur wenn ich nicht selber sende + if (_txState == RX_IDLE) + { + _platform.writeUart(U_ACK_REQ | U_ACK_REQ_ADRESSED | U_ACK_REQ_BUSY); + } + } + } +#endif + } + } + + /* + * Wenn kein Markermodus aktiv ist, dann muss anhand des Frame geschaut werden, ob es Vollständig ist. + * isFull prüft hier ob die maxSize oder die Längenangabe des Frames überschritten wurde! + * In beiden Fällen muss das Frame verarbeitet werden. + */ + if (!markerMode() && (_rxFrame->isFull())) + { + processRxFrameComplete(); + } +} + +/* + * Verarbeitet das aktuelle Frame und prüft ob dieses vollständig und gültig (checksum) ist. + * Sollte ein Frame vollständig und gültig sein, so wird deses falls für "mich" bestimmt in die Queue gelegt und der Modus ist wieder RX_IDLE. + * Ansonsten wird das Frame als ungültig verworfen und der Zustand ist RX_INVALID, da nicht sichergestellt ist das Folgebytes wieder Steuercodes sind. + * Ausnahme im Markermodus, hier wird der Status RX_INVALID an einer anderen Stelle direkt wieder auf RX_IDLE geändert, weil + * dann ist sicher gestellt, dass das das Frame auf TP Ebene kaputt gegangen ist. + */ +void TpUartDataLinkLayer::processRxFrameComplete() +{ + // Sollte aktuell kein Frame in der Bearbeitung (size == 0) sein, dann breche ab + if (_rxFrame->size() == 0) + return; + + // Ist das Frame vollständig und gültig + if (_rxFrame->isValid()) + { + // Wenn ein Frame gesendet wurde + if (_txState == TX_FRAME) + { + // prüfe ob das Empfangen diesem entspricht + if (!memcmp(_rxFrame->data(), _txFrame->data(), _txFrame->size())) + { + // und markiere das entsprechend + // println("MATCH"); + _rxFrame->addFlags(TP_FRAME_FLAG_ECHO); + } + // Jetzt warte noch auf das L_DATA_CON + } + // wenn das frame für mich ist oder ich im busmonitor modus bin dann möchte ich es weiter verarbeiten + if (_rxFrame->flags() & TP_FRAME_FLAG_ADDRESSED || _monitoring) + { + /* + * Im Busmonitormodus muss noch auf ein Ack gewartet werden. + * Daher wird hier der Status geändert und vor dem echten abschließen zurück gesprungen. + * Sobald ein weiteres mal aufgerufen wird, (egal ob geackt oder nicht) wird das Frame geschlossen. + */ + if (_monitoring && _rxState != RX_AWAITING_ACK) + { + _rxState = RX_AWAITING_ACK; + return; + } + _rxProcessdFrameCounter++; + } + else + { + // Sonst verwerfe das Paket und gebe den Speicher frei -> da nicht in die Queue gepackt wird + _rxIgnoredFrameCounter++; + } + // Und wieder bereit für Steuercodes + _rxState = RX_IDLE; + } + else + { + /* + * Ist das Frame unvollständig oder ungültig dann wechsle in RX_INVALID Modus da ich nicht unterscheiden kann, + * ob es sich um einen TPBus Error oder ein UART Error oder ein Timming Error handelt. + */ + _rxInvalidFrameCounter++; + _rxFrame->addFlags(TP_FRAME_FLAG_INVALID); + _rxState = RX_INVALID; // ignore bytes + } + +#ifdef USE_TP_RX_QUEUE + pushRxFrameQueue(); +#else + processRxFrame(_rxFrame); +#endif + + // resete den aktuellen Framepointer + _rxFrame->reset(); +} + +/* + * Steckt das zu sendende Frame in eine Queue, da der TpUart vielleicht gerade noch nicht sende bereit ist. + */ +void TpUartDataLinkLayer::pushTxFrameQueue(TpFrame *tpFrame) +{ + knx_tx_queue_entry_t *entry = new knx_tx_queue_entry_t(tpFrame); + + if (_txFrameQueue.back == nullptr) + { + _txFrameQueue.front = _txFrameQueue.back = entry; + } + else + { + _txFrameQueue.back->next = entry; + _txFrameQueue.back = entry; + } +} + +void TpUartDataLinkLayer::setRepetitions(uint8_t nack, uint8_t busy) +{ + _repetitions = (nack & 0b111) | ((busy & 0b111) << 4); +} + +// Alias +void TpUartDataLinkLayer::setFrameRepetition(uint8_t nack, uint8_t busy) +{ + setRepetitions(nack, busy); +} + +bool TpUartDataLinkLayer::sendFrame(CemiFrame &cemiFrame) +{ + // if (!_connected) + // { + // dataConReceived(cemiFrame, false); + // return false; + // } + + TpFrame *tpFrame = new TpFrame(cemiFrame); + // printHex(" TP>: ", tpFrame->data(), tpFrame->size()); + pushTxFrameQueue(tpFrame); return true; } -bool TpUartDataLinkLayer::resetChip() +/* + * Es soll regelmäßig der Status abgefragt werden um ein Disconnect des TPUart und dessen Status zu erkennen. + * Außerdem soll regelmäßig die aktuelle Config bzw der Modus übermittelt werden, so dass nach einem Disconnect, + * der TPUart im richtigen Zustand ist. + */ +void TpUartDataLinkLayer::requestState() { - if(_waitConfirmStartTime > 0) return false; - uint8_t cmd = U_RESET_REQ; - _platform.writeUart(cmd); - - int resp = _platform.readUart(); - if (resp == U_RESET_IND) - return true; + if (_rxState != RX_IDLE) + return; - _waitConfirmStartTime = millis(); - return false; + if (_txState != TX_IDLE) + return; + + // Nur 1x pro Sekunde + if ((millis() - _lastStateRequest) < 1000) + return; + + // println("requestState"); + + // Sende Konfiguration bzw. Modus + if (_monitoring) + _platform.writeUart(U_BUSMON_REQ); + else + requestConfig(); + + // Frage status an - wenn monitoring inaktiv + if (!_monitoring) + _platform.writeUart(U_STATE_REQ); + + _lastStateRequest = millis(); } -bool TpUartDataLinkLayer::resetChipTick() -{ - int resp = _platform.readUart(); - if (resp == U_RESET_IND) - { - _waitConfirmStartTime = 0; - return true; - } - else if (millis() - _waitConfirmStartTime > RESET_TIMEOUT) - _waitConfirmStartTime = 0; - - return false; -} - -void TpUartDataLinkLayer::stopChip() +/* + * Sendet die aktuelle Config an den Chip + */ +void TpUartDataLinkLayer::requestConfig() { + // println("requestConfig"); #ifdef NCN5120 - uint8_t cmd = U_STOP_MODE_REQ; - _platform.writeUart(cmd); - while (true) + if (markerMode()) + _platform.writeUart(U_CONFIGURE_REQ | U_CONFIGURE_MARKER_REQ); +#endif + + // Abweichende Config + if (_repetitions != 0b00110011) { - int resp = _platform.readUart(); - if (resp == U_STOP_MODE_IND) - break; +#ifdef NCN5120 + _platform.writeUart(U_SET_REPETITION_REQ); + _platform.writeUart(_repetitions); + _platform.writeUart(0x0); // dummy, see NCN5120 datasheet + _platform.writeUart(0x0); // dummy, see NCN5120 datasheet +#else + _platform.writeUart(U_MXRSTCNT); + _platform.writeUart(((_repetitions & 0xF0) << 1) || (_repetitions & 0x0F)); +#endif } -#endif } -TpUartDataLinkLayer::TpUartDataLinkLayer(DeviceObject& devObj, - NetworkLayerEntity &netLayerEntity, - Platform& platform, - ITpUartCallBacks& cb, - DataLinkLayerCallbacks* dllcb) - : DataLinkLayer(devObj, netLayerEntity, platform), - _cb(cb), - _dllcb(dllcb) +/* + * Ein vereinfachter Lockmechanismus der nur auf dem gleichen Core funktionert. + * Also perfekt für ISR + */ +bool TpUartDataLinkLayer::isrLock(bool blocking /* = false */) { + if (blocking) + while (_rxProcessing) + ; + else if (_rxProcessing) + return false; + + _rxProcessing = true; + return true; } -void TpUartDataLinkLayer::frameBytesReceived(uint8_t* buffer, uint16_t length) +void TpUartDataLinkLayer::isrUnlock() { - //printHex("=>", buffer, length); -#ifdef KNX_ACTIVITYCALLBACK - if(_dllcb) - _dllcb->activity((_netIndex << KNX_ACTIVITYCALLBACK_NET) | (KNX_ACTIVITYCALLBACK_DIR_RECV << KNX_ACTIVITYCALLBACK_DIR)); -#endif - CemiFrame frame(buffer, length); - frameReceived(frame); + _rxProcessing = false; } -void TpUartDataLinkLayer::dataConBytesReceived(uint8_t* buffer, uint16_t length, bool success) +void TpUartDataLinkLayer::clearUartBuffer() { - //printHex("=>", buffer, length); - CemiFrame frame(buffer, length); - dataConReceived(frame, success); + // Clear rx queue + while (_platform.uartAvailable()) + _platform.readUart(); +} + +void TpUartDataLinkLayer::connected(bool state /* = true */) +{ + if (state) + println("TP is connected"); + else + println("TP is disconnected"); + + _connected = state; +} + +bool TpUartDataLinkLayer::reset() +{ + // println("Reset TP"); + if (!_initialized) + { + _platform.setupUart(); + _initialized = true; + } + + // Wait for isr & block isr + isrLock(true); + + // Reset + _rxIgnoredFrameCounter = 0; + _rxInvalidFrameCounter = 0; + _rxInvalidFrameCounter = 0; + _rxUnkownControlCounter = 0; + if (_txFrame != nullptr) + _txFrame->reset(); + if (_rxFrame != nullptr) + _rxFrame->reset(); + _rxState = RX_IDLE; + _txState = TX_IDLE; + _connected = false; + _stopped = false; + _monitoring = false; + _rxLastTime = 0; + + clearUartBuffer(); + + _platform.writeUart(U_RESET_REQ); + bool success = false; + + const uint32_t start = millis(); + // During startup answer took up to 2ms and normal 1ms + do + { + const int byte = _platform.readUart(); + if (byte == -1) + continue; // empty + + if (byte & U_RESET_IND) + { + success = true; + break; // next run for U_CONFIGURE_IND + } + + } while (!((millis() - start) >= 10)); + + connected(success); + if (success) + { + _lastStateRequest = 0; // Force + requestState(); + _rxLastTime = millis(); + } + + isrUnlock(); + return success; +} + +void TpUartDataLinkLayer::forceAck(bool state) +{ + _forceAck = true; +} + +void TpUartDataLinkLayer::stop(bool state) +{ + if (!_initialized) + return; + + if (state && !_stopped) + _platform.writeUart(U_STOP_MODE_REQ); + else if (!state && _stopped) + _platform.writeUart(U_EXIT_STOP_MODE_REQ); + + _stopped = state; +} + +void TpUartDataLinkLayer::requestBusy(bool state) +{ + if (state && !_busy) + _platform.writeUart(U_SET_BUSY_REQ); + else if (!state && _busy) + _platform.writeUart(U_QUIT_BUSY_REQ); + + _busy = state; +} + +void TpUartDataLinkLayer::monitor() +{ + if (!_initialized || _monitoring) + return; + + // println("busmonitor"); + _monitoring = true; + _platform.writeUart(U_BUSMON_REQ); } void TpUartDataLinkLayer::enabled(bool value) { - if (value && !_enabled) - { - _platform.setupUart(); + // After an unusual device restart, perform a reset, as the TPUart may still be in an incorrect state. + if (!_initialized) + reset(); - uint8_t cmd = U_RESET_REQ; - _platform.writeUart(cmd); - _waitConfirmStartTime = millis(); - bool flag = false; - - while (true) - { - int resp = _platform.readUart(); - if (resp == U_RESET_IND) - { - flag = true; - break; - } - else if (millis() - _waitConfirmStartTime > RESET_TIMEOUT) - { - flag = false; - break; - } - } - - if (flag) - { - _enabled = true; - print("ownaddr "); - println(_deviceObject.individualAddress(), HEX); - } - else - { - _enabled = false; - println("ERROR, TPUART not responding"); - } - return; - } - - if (!value && _enabled) - { - _enabled = false; - stopChip(); - _platform.closeUart(); - return; - } + stop(!value); } bool TpUartDataLinkLayer::enabled() const { - return _enabled; + return _initialized && _connected; +} + +/* + * Hier werden die ausgehenden Frames aus der Warteschlange genomnmen und versendet. + * Das passiert immer nur einzelnd, da nach jedem Frame, gewartet werden muss bis das Frame wieder reingekommen ist und das L_DATA_CON rein kommt. + * + */ +void TpUartDataLinkLayer::processTxQueue() +{ + // Diese Abfrage ist vorsorglich. Eigentlich sollte auch schon parallel gestartet werden können. + if (_rxState != RX_IDLE) + return; + + // + if (_txState != TX_IDLE) + return; + + if (_txFrameQueue.front != nullptr) + { + knx_tx_queue_entry_t *entry = _txFrameQueue.front; + _txFrameQueue.front = entry->next; + + if (_txFrameQueue.front == nullptr) + { + _txFrameQueue.back = nullptr; + } + + _txFrame = entry->frame; +#ifdef DEBUG_TP_FRAMES + print("Outbound: "); + printFrame(_txFrame); + println(); +#endif + _txState = TX_FRAME; + _txLastTime = millis(); + + delete entry; + processTxFrameBytes(); + } +} + +/* + * Prüfe ob ich zu lange keine Daten mehr erhalten habe und setzte den Status auf nicht verbunden. + * Im normalen Modus wird jede Sekunde der Status angefragt. Daher kann hier eine kurze Zeit gewählt werden. + * Im Monitoringmodus gibt es eigentlichen Frames, daher ist hier eine längere Zeit verwendet. + * Dennoch kommt es bei größeren Datenmengen zu vermuteten Disconnect, daher wird auch noch die RxQueue beachtet. + */ +void TpUartDataLinkLayer::checkConnected() +{ + if (!isrLock()) + return; + + const uint32_t current = millis(); + + if (_connected) + { + // 5000 instead 3000 because siemens tpuart + const uint32_t timeout = _monitoring ? 10000 : 5000; + + if ((current - _rxLastTime) > timeout) + { + connected(false); + } + } + else + { + if (_rxLastTime > 0 && (current - _rxLastTime) < 1000) + connected(); + } + + isrUnlock(); +} + +void TpUartDataLinkLayer::loop() +{ + if (!_initialized) + return; + + if (_rxOverflow) + { + println("TPUart overflow detected!"); + _rxOverflow = false; + } + + if (_tpState) + { + print("TPUart state error: "); + println(_tpState, 2); + _tpState = 0; + } + + processRx(); +#ifdef USE_TP_RX_QUEUE + processRxQueue(); +#endif + + requestState(); + processTxQueue(); + checkConnected(); + + /* + * Normal ist alles so gebaut worde, dass anhand der Bytes entschieden wird wann ein Frame "fertig" ist oder nicht. + * Dennoch gibt es Situationen, wo ein Frame nicht geschlossen wurde, weil Bytes verloren geangen sind oder wie beim Busmonitor + * bewusst auf ein Ack gewartet werden muss. + */ + // if (!_platform.uartAvailable() && _rxFrame->size() > 0 && (millis() - _rxLastTime) > 2) + // { + // processRxFrameComplete(); + // } +} + +void TpUartDataLinkLayer::rxFrameReceived(TpFrame *tpFrame) +{ + uint8_t *cemiData = tpFrame->cemiData(); + CemiFrame cemiFrame(cemiData, tpFrame->cemiSize()); + // printHex(" TP<: ", tpFrame->data(), tpFrame->size()); + // printHex(" CEMI<: ", cemiFrame.data(), cemiFrame.dataLength()); + +#ifdef KNX_ACTIVITYCALLBACK + if (_dllcb) + _dllcb->activity((_netIndex << KNX_ACTIVITYCALLBACK_NET) | (KNX_ACTIVITYCALLBACK_DIR_RECV << KNX_ACTIVITYCALLBACK_DIR)); +#endif + + frameReceived(cemiFrame); + free(cemiData); } DptMedium TpUartDataLinkLayer::mediumType() const @@ -622,94 +910,246 @@ DptMedium TpUartDataLinkLayer::mediumType() const return DptMedium::KNX_TP1; } -bool TpUartDataLinkLayer::sendSingleFrameByte() +/* + * Hiermit kann die Stromversorgung des V20V (VCC2) + */ +#ifdef NCN5120 +void TpUartDataLinkLayer::powerControl(bool state) { - uint8_t cmd[2]; + _platform.writeUart(U_INT_REG_WR_REQ_ACR0); + if (state) + _platform.writeUart(ACR0_FLAG_DC2EN | ACR0_FLAG_V20VEN | ACR0_FLAG_XCLKEN | ACR0_FLAG_V20VCLIMIT); + else + _platform.writeUart(ACR0_FLAG_XCLKEN | ACR0_FLAG_V20VCLIMIT); +} +#endif - uint8_t idx = _TxByteCnt >> 6; +bool TpUartDataLinkLayer::processTxFrameBytes() +{ + // println("processTxFrameBytes"); - if (_sendBuffer == NULL) - return false; - - if (_TxByteCnt < _sendBufferLength) + /* + * Jedes Frame muss mit einem U_L_DATA_START_REQ eingeleitet werden und jedes Folgebyte mit einem weiteren Positionsbyte (6bit). + * Da das Positionsbyte aus dem U_L_DATA_START_REQ + Position besteht und wir sowieso mit 0 starten, ist eine weitere + * Unterscheidung nicht nötig. + * + * Das letzte Byte (Checksumme) verwendet allerdings das U_L_DATA_END_REQ + Postion! + * Außerdem gibt es eine weitere Besondertheit bei Extendedframes bis 263 Bytes lang sein können reichen die 6bit nicht mehr. + * Hier muss ein U_L_DATA_OFFSET_REQ + Position (3bit) vorangestellt werden. Somit stehten 9bits für die Postion bereit. + */ + for (uint16_t i = 0; i < _txFrame->size(); i++) { - if (idx != _oldIdx) + uint8_t offset = (i >> 6); + uint8_t position = (i & 0x3F); + + if (offset) { - _oldIdx = idx; - cmd[0] = U_L_DATA_OFFSET_REQ | idx; - _platform.writeUart(cmd, 1); + // position++; + _platform.writeUart(U_L_DATA_OFFSET_REQ | offset); } - if (_TxByteCnt != _sendBufferLength - 1) - cmd[0] = U_L_DATA_START_CONT_REQ | (_TxByteCnt & 0x3F); + if (i == (_txFrame->size() - 1)) // Letztes Bytes (Checksumme) + _platform.writeUart(U_L_DATA_END_REQ | position); else - cmd[0] = U_L_DATA_END_REQ | (_TxByteCnt & 0x3F); + _platform.writeUart(U_L_DATA_START_REQ | position); - cmd[1] = _sendBuffer[_TxByteCnt]; -#ifdef DBG_TRACE - print(cmd[1], HEX); -#endif - - _platform.writeUart(cmd, 2); - _TxByteCnt++; + _platform.writeUart(_txFrame->data(i)); } - - // Check for last byte send - if (_TxByteCnt >= _sendBufferLength) - { - _TxByteCnt = 0; + #ifdef KNX_ACTIVITYCALLBACK - if(_dllcb) - _dllcb->activity((_netIndex << KNX_ACTIVITYCALLBACK_NET) | (KNX_ACTIVITYCALLBACK_DIR_SEND << KNX_ACTIVITYCALLBACK_DIR)); + if (_dllcb) + _dllcb->activity((_netIndex << KNX_ACTIVITYCALLBACK_NET) | (KNX_ACTIVITYCALLBACK_DIR_SEND << KNX_ACTIVITYCALLBACK_DIR)); #endif - return false; - } + return true; } -void TpUartDataLinkLayer::addFrameTxQueue(CemiFrame& frame) +TpUartDataLinkLayer::TpUartDataLinkLayer(DeviceObject &devObj, + NetworkLayerEntity &netLayerEntity, + Platform &platform, + ITpUartCallBacks &cb, + DataLinkLayerCallbacks *dllcb) + : DataLinkLayer(devObj, netLayerEntity, platform), + _cb(cb), + _dllcb(dllcb) { - _tx_queue_frame_t* tx_frame = new _tx_queue_frame_t; - tx_frame->length = frame.telegramLengthtTP(); - tx_frame->data = new uint8_t[tx_frame->length]; - tx_frame->next = NULL; - frame.fillTelegramTP(tx_frame->data); - - if (_tx_queue.back == NULL) - { - _tx_queue.front = _tx_queue.back = tx_frame; - } - else - { - _tx_queue.back->next = tx_frame; - _tx_queue.back = tx_frame; - } + _rxFrame = new TpFrame(MAX_KNX_TELEGRAM_SIZE); } -bool TpUartDataLinkLayer::isTxQueueEmpty() +/* + * Liefert die Anzahl der Frames, die nicht verarbeitet werden konnte. + */ +uint32_t TpUartDataLinkLayer::getRxInvalidFrameCounter() { - if (_tx_queue.front == NULL) - { - return true; - } + return _rxInvalidFrameCounter; +} + +/* + * Liefert die Anzahl der Frames, welche gültig und für das Geräte bestimmt sind + */ +uint32_t TpUartDataLinkLayer::getRxProcessdFrameCounter() +{ + return _rxProcessdFrameCounter; +} + +/* + * Liefert die Anzahl der Frames, welche gültig aber nicht f+r das Gerät bestimmt sind + */ +uint32_t TpUartDataLinkLayer::getRxIgnoredFrameCounter() +{ + return _rxIgnoredFrameCounter; +} + +/* + * Liefert die Anzahl der gezählten Steuerbytes, welche nicht erkannt wurden + */ +uint32_t TpUartDataLinkLayer::getRxUnknownControlCounter() +{ + return _rxUnkownControlCounter; +} + +bool TpUartDataLinkLayer::isConnected() +{ + return _connected; +} + +bool TpUartDataLinkLayer::isStopped() +{ + return _stopped; +} + +bool TpUartDataLinkLayer::isBusy() +{ + return _busy; +} + +bool TpUartDataLinkLayer::isMonitoring() +{ + return _monitoring; +} + +bool TpUartDataLinkLayer::markerMode() +{ + if (_monitoring) + return false; + +#ifdef NCN5120 + // return true; +#endif + return false; } -void TpUartDataLinkLayer::loadNextTxFrame() +void TpUartDataLinkLayer::processRxFrame(TpFrame *tpFrame) { - if (_tx_queue.front == NULL) + if (_monitoring) { - return; + print("Monitor: "); + printFrame(tpFrame); + println(); } - _tx_queue_frame_t* tx_frame = _tx_queue.front; - _sendBuffer = tx_frame->data; - _sendBufferLength = tx_frame->length; - _tx_queue.front = tx_frame->next; + else if (tpFrame->flags() & TP_FRAME_FLAG_INVALID) + { + print("\x1B["); + print(31); + print("m"); + print("Invalid: "); + printFrame(tpFrame); + print("\x1B["); + print(0); + println("m"); + } + else if (tpFrame->flags() & TP_FRAME_FLAG_ADDRESSED) + { +#ifdef DEBUG_TP_FRAMES + print("Inbound: "); + printFrame(tpFrame); + println(); +#endif + rxFrameReceived(tpFrame); + } +} - if (_tx_queue.front == NULL) +#ifdef USE_TP_RX_QUEUE +/* + * This method allows the processing of the incoming bytes to be handled additionally via an interrupt (ISR). + * The prerequisite is that the interrupt runs on the same core as the knx.loop! + * + * Bei einem RP2040 wo beim "erase" eines Blocks auch der ISR gesperrt sind, + * kann zwischern den Erases die Verarbeitung nachgeholt werden. So wird das Risiko von Frameverlusten deutlich minimiert. + */ +void __isr __time_critical_func(TpUartDataLinkLayer::processRxISR)() +{ + processRx(true); +} + +/* + * Steckt das empfangene Frame in eine Queue. Diese Queue ist notwendig, + * weil ein Frame optional über ein ISR empfangen werden kann und die verarbeitung noch normal im knx.loop statt finden muss. + * Außerdem ist diese Queue statisch vorallokierte, da in einem ISR keine malloc u.ä. gemacht werden kann. + */ +void TpUartDataLinkLayer::pushRxFrameQueue() +{ + if (availableInRxQueue() < (_rxFrame->size() + 3)) + return; + + // Payloadsize (2 byte) + pushByteToRxQueue(_rxFrame->size() & 0xFF); + pushByteToRxQueue(_rxFrame->size() >> 8); + // Paylodflags (1 byte) + pushByteToRxQueue(_rxFrame->flags()); + + for (size_t i = 0; i < _rxFrame->size(); i++) { - _tx_queue.back = NULL; + pushByteToRxQueue(_rxFrame->data(i)); } - delete tx_frame; + + asm volatile("" ::: "memory"); + _rxBufferCount++; +} + +void TpUartDataLinkLayer::processRxQueue() +{ + if (!isrLock()) + return; + + while (_rxBufferCount) + { + const uint16_t size = pullByteFromRxQueue() + (pullByteFromRxQueue() << 8); + TpFrame *tpFrame = new TpFrame(size); + tpFrame->addFlags(pullByteFromRxQueue()); + + for (uint16_t i = 0; i < size; i++) + { + tpFrame->addByte(pullByteFromRxQueue()); + } + + processRxFrame(tpFrame); + delete tpFrame; + asm volatile("" ::: "memory"); + _rxBufferCount--; + } + + isrUnlock(); +} + +void TpUartDataLinkLayer::pushByteToRxQueue(uint8_t byte) +{ + _rxBuffer[_rxBufferFront] = byte; + _rxBufferFront = (_rxBufferFront + 1) % MAX_RX_QUEUE_BYTES; +} + +uint8_t TpUartDataLinkLayer::pullByteFromRxQueue() +{ + uint8_t byte = _rxBuffer[_rxBufferRear]; + _rxBufferRear = (_rxBufferRear + 1) % MAX_RX_QUEUE_BYTES; + return byte; +} + +uint16_t TpUartDataLinkLayer::availableInRxQueue() +{ + return ((_rxBufferFront == _rxBufferRear) ? MAX_RX_QUEUE_BYTES : (((MAX_RX_QUEUE_BYTES - _rxBufferFront) + _rxBufferRear) % MAX_RX_QUEUE_BYTES)) - 1; } #endif + +#endif \ No newline at end of file diff --git a/src/knx/tpuart_data_link_layer.h b/src/knx/tpuart_data_link_layer.h index c2e46f3..da81aa4 100644 --- a/src/knx/tpuart_data_link_layer.h +++ b/src/knx/tpuart_data_link_layer.h @@ -3,14 +3,26 @@ #include "config.h" #ifdef USE_TP -#include #include "data_link_layer.h" +#include "tp_frame.h" +#include #define MAX_KNX_TELEGRAM_SIZE 263 +#ifndef MAX_RX_QUEUE_BYTES +#define MAX_RX_QUEUE_BYTES MAX_KNX_TELEGRAM_SIZE + 50 +#endif + +// __time_critical_func fallback +#ifndef ARDUINO_ARCH_RP2040 +#define __time_critical_func(X) X +#define __isr +#endif + +void printFrame(TpFrame* tpframe); class ITpUartCallBacks { -public: + public: virtual ~ITpUartCallBacks() = default; virtual bool isAckRequired(uint16_t address, bool isGrpAddr) = 0; }; @@ -24,56 +36,129 @@ class TpUartDataLinkLayer : public DataLinkLayer TpUartDataLinkLayer(DeviceObject& devObj, NetworkLayerEntity& netLayerEntity, Platform& platform, ITpUartCallBacks& cb, DataLinkLayerCallbacks* dllcb = nullptr); - - void loop(); void enabled(bool value); bool enabled() const; DptMedium mediumType() const override; + bool reset(); + void monitor(); + void stop(bool state); + void requestBusy(bool state); + void forceAck(bool state); + void setRepetitions(uint8_t nack, uint8_t busy); + // Alias + void setFrameRepetition(uint8_t nack, uint8_t busy); + bool isConnected(); + bool isMonitoring(); + bool isStopped(); + bool isBusy(); + +#ifdef USE_TP_RX_QUEUE + void processRxISR(); +#endif +#ifdef NCN5120 + void powerControl(bool state); +#endif + + uint32_t getRxInvalidFrameCounter(); + uint32_t getRxProcessdFrameCounter(); + uint32_t getRxIgnoredFrameCounter(); + uint32_t getRxUnknownControlCounter(); + uint8_t getMode(); private: - bool _enabled = false; - uint8_t* _sendBuffer = 0; - uint16_t _sendBufferLength = 0; - uint8_t _receiveBuffer[MAX_KNX_TELEGRAM_SIZE]; - uint8_t _txState = 0; - uint8_t _rxState = 0; - uint16_t _RxByteCnt = 0; - uint16_t _TxByteCnt = 0; - uint8_t _oldIdx = 0; - bool _isEcho = false; - bool _convert = false; - uint8_t _xorSum = 0; - uint32_t _lastByteRxTime; - uint32_t _lastByteTxTime; - uint32_t _lastLoopTime; - uint32_t _waitConfirmStartTime = 0; - uint32_t _lastResetChipTime = 0; - - struct _tx_queue_frame_t + // Frame + struct knx_tx_queue_entry_t { - uint8_t* data; - uint16_t length; - _tx_queue_frame_t* next; + TpFrame* frame; + knx_tx_queue_entry_t* next = nullptr; + + knx_tx_queue_entry_t(TpFrame* tpFrame) + : frame(tpFrame) + { + } }; - struct _tx_queue_t + // TX Queue + struct knx_tx_queue_t { - _tx_queue_frame_t* front = NULL; - _tx_queue_frame_t* back = NULL; - } _tx_queue; + knx_tx_queue_entry_t* front = nullptr; + knx_tx_queue_entry_t* back = nullptr; + } _txFrameQueue; - void addFrameTxQueue(CemiFrame& frame); - bool isTxQueueEmpty(); - void loadNextTxFrame(); - bool sendSingleFrameByte(); + TpFrame* _txFrame = nullptr; + TpFrame* _rxFrame = nullptr; + + volatile bool _stopped = false; + volatile bool _connected = false; + volatile bool _monitoring = false; + volatile bool _busy = false; + volatile bool _initialized = false; + + volatile uint32_t _stateTime = 0; + volatile uint8_t _rxState = 0; + volatile uint8_t _txState = 0; + volatile uint32_t _rxProcessdFrameCounter = 0; + volatile uint32_t _rxInvalidFrameCounter = 0; + volatile uint32_t _rxIgnoredFrameCounter = 0; + volatile uint32_t _rxUnkownControlCounter = 0; + volatile bool _rxMarker = false; + volatile bool _rxOverflow = false; + volatile uint8_t _tpState = 0x0; + volatile uint32_t _txLastTime = 0; + volatile uint32_t _rxLastTime = 0; + volatile bool _forceAck = false; + + inline bool markerMode(); + + /* + * bits + * + * 5-7 Busy (Default 11 = 3) + * 0-3 Nack (Default 11 = 3) + */ + volatile uint8_t _repetitions = 0b00110011; + + // to prevent parallel rx processing by isr (when using) + volatile bool _rxProcessing = false; + + volatile uint32_t _lastStateRequest = 0; + + // void loadNextTxFrame(); + inline bool processTxFrameBytes(); bool sendFrame(CemiFrame& frame); - void frameBytesReceived(uint8_t* buffer, uint16_t length); + void rxFrameReceived(TpFrame* frame); void dataConBytesReceived(uint8_t* buffer, uint16_t length, bool success); - void enterRxWaitEOP(); - bool resetChip(); - bool resetChipTick(); - void stopChip(); + + void processRx(bool isr = false); + void checkConnected(); + void processRxByte(); + void processTxQueue(); + void processRxFrameComplete(); + inline void processRxFrame(TpFrame* tpFrame); + void pushTxFrameQueue(TpFrame* tpFrame); + void requestState(); + void requestConfig(); + inline void processRxFrameByte(uint8_t byte); + +#ifdef USE_TP_RX_QUEUE + // Es muss ein Extended Frame rein passen + 1Byte je erlaubter ms Verzögerung + volatile uint8_t _rxBuffer[MAX_RX_QUEUE_BYTES] = {}; + volatile uint16_t _rxBufferFront = 0; + volatile uint16_t _rxBufferRear = 0; + volatile uint8_t _rxBufferCount = 0; + + void pushByteToRxQueue(uint8_t byte); + uint8_t pullByteFromRxQueue(); + uint16_t availableInRxQueue(); + void pushRxFrameQueue(); + void processRxQueue(); +#endif + + inline bool isrLock(bool blocking = false); + inline void isrUnlock(); + inline void clearUartBuffer(); + inline void connected(bool state = true); ITpUartCallBacks& _cb; DataLinkLayerCallbacks* _dllcb; From 8388c79f9f089847d84fd3ea2f3d3a2990afa862 Mon Sep 17 00:00:00 2001 From: Marco Scholl Date: Fri, 29 Mar 2024 22:31:59 +0100 Subject: [PATCH 12/44] adds gcc optimize for tp frame --- src/knx/tp_frame.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/knx/tp_frame.h b/src/knx/tp_frame.h index 856af93..32dd13f 100644 --- a/src/knx/tp_frame.h +++ b/src/knx/tp_frame.h @@ -1,4 +1,5 @@ #pragma once +#pragma GCC optimize("O3") #include "cemi_frame.h" #include From cf1b6bc7accaf47eb4386268f1a412a875e4df59 Mon Sep 17 00:00:00 2001 From: Marco Scholl Date: Fri, 29 Mar 2024 22:32:18 +0100 Subject: [PATCH 13/44] some bugfixes --- src/knx/tpuart_data_link_layer.cpp | 128 ++++++++++++++--------------- src/knx/tpuart_data_link_layer.h | 2 +- 2 files changed, 63 insertions(+), 67 deletions(-) diff --git a/src/knx/tpuart_data_link_layer.cpp b/src/knx/tpuart_data_link_layer.cpp index 271ff6c..3f8a79f 100644 --- a/src/knx/tpuart_data_link_layer.cpp +++ b/src/knx/tpuart_data_link_layer.cpp @@ -169,11 +169,9 @@ void __isr __time_critical_func(TpUartDataLinkLayer::processRx)(bool isr) _rxOverflow = true; // verarbeiten daten - uint8_t counter = isr ? 10 : 255; - while (_platform.uartAvailable() && counter > 0) + while (_platform.uartAvailable()) { processRxByte(); - counter--; } isrUnlock(); @@ -190,49 +188,28 @@ void TpUartDataLinkLayer::processRxByte() if (byte < 0) return; - if (_rxState == RX_FRAME) + /* + * Wenn ich im RX_INVALID state bin + * und das letzte Byte vor mehr als 2ms verarbeitet wurde (also pause >2ms) + * und keine weiteren Bytes vorliegen, + * dann kann ich den INVALID State verwerfen. + */ + if (_rxState == RX_INVALID && (millis() - _rxLastTime) > 2 && !_platform.uartAvailable()) { - processRxFrameByte(byte); - } - else if ((byte & L_DATA_MASK) == L_DATA_STANDARD_IND || (byte & L_DATA_MASK) == L_DATA_EXTENDED_IND) - { - /* - * Verarbeite ein vorheriges Frame falls noch vorhanden. Das dürfte normal nur im Busmonitor auftreten, weil hier noch auch ein ACK gewartet wird - */ + println("Reset RX_INVALID"); processRxFrameComplete(); - - _rxFrame->addByte(byte); - - // Provoziere ungültige Frames für Tests - // if (millis() % 20 == 0) - // _rxFrame->addByte(0x1); - - _rxMarker = false; - _rxState = RX_FRAME; - - /* - * Hier wird inital ein Ack ohne Addressed gesetzt. Das dient dazu falls noch ein Ack vom vorherigen Frame gesetzt wurde, - * welches aber ggf nicht rechtzeitig verarbeitet (also nach der Übertragung gesendet wurde) sich nicht auf das neue Frame auswirkt. - * Der Zustand kann beliebig oft gesendet werden. Sobald der Moment gekommen ist, dass ein Ack gesendet wird, schaut TPUart im Buffer - * was der letzte Ackstatus war und sendet diesen. - * - * Das darf man natürlich nur wenn ich nicht gerade selber sende, da man eigene Frames nicht ACKt - */ - if (_txState == TX_IDLE) - { - _platform.writeUart(U_ACK_REQ); - } + _rxState = RX_IDLE; } - else if (_rxState == RX_INVALID || _rxOverflow) + + if (_rxState == RX_INVALID) { /* - * Sobald ein Frame ungültig verarbeitet wurde oder ein unbekanntest Kommando eintrifft ist wechselt der Zustand in RX_INVALID. + * Sobald ein Frame ungültig verarbeitet wurde oder ein unbekanntes Kommando eintrifft, wechselt der Zustand in RX_INVALID. * Ab jetzt muss ich davon ausgehen, dass einen Übertragungsfehler gegeben hat und die aktuellen Bytes ungültig sind. - * Gleiches gilt auch wenn ein HW Overflow erkannt wurde + * Gleiches gilt auch wenn ein HW Overflow erkannt wurde. * - * - Dieser Zustand wird aufgehoben wenn ein Frame "vermutlich" erkannt wurde. (if Abfrage über dieser) - * - Die Zeit des letzten Frames 3ms vorbei ist (erfolt im loop) - * - Wenn der Markermodus aktiv ist und ein U_FRAME_END_IND sauber erkannt wurde. (passiert hiert) + * - Die Zeit des letzten Frames 3ms vorbei ist (kann aber nicht garantiert werden, wegen möglichem ISR & DMA. Hier sollte es das Problem aber nicht geben) + * - Wenn der Markermodus aktiv ist und ein U_FRAME_END_IND sauber erkannt wurde. (Wird hier geprüft) * * Ansonsten macht dieser Abschnitt nichts und verwirrft die ungültigen Bytes */ @@ -253,11 +230,38 @@ void TpUartDataLinkLayer::processRxByte() _rxMarker = false; _rxState = RX_IDLE; } - else - { - // print("RX_INVALID: "); - // println(byte, HEX); - } + } + } + else if (_rxState == RX_FRAME) + { + processRxFrameByte(byte); + } + else if ((byte & L_DATA_MASK) == L_DATA_STANDARD_IND || (byte & L_DATA_MASK) == L_DATA_EXTENDED_IND) + { + /* + * Verarbeite ein vorheriges Frame falls noch vorhanden. Das dürfte normal nur im Busmonitor auftreten, weil hier noch auch ein ACK gewartet wird + */ + processRxFrameComplete(); + _rxFrame->addByte(byte); + + // Provoziere ungültige Frames für Tests + // if (millis() % 20 == 0) + // _rxFrame->addByte(0x1); + + _rxMarker = false; + _rxState = RX_FRAME; + + /* + * Hier wird inital ein Ack ohne Addressed gesetzt. Das dient dazu falls noch ein Ack vom vorherigen Frame gesetzt wurde, + * welches aber ggf nicht rechtzeitig verarbeitet (also nach der Übertragung gesendet wurde) sich nicht auf das neue Frame auswirkt. + * Der Zustand kann beliebig oft gesendet werden. Sobald der Moment gekommen ist, dass ein Ack gesendet wird, schaut TPUart im Buffer + * was der letzte Ackstatus war und sendet diesen. + * + * Das darf man natürlich nur wenn ich nicht gerade selber sende, da man eigene Frames nicht ACKt + */ + if (_txState == TX_IDLE) + { + _platform.writeUart(U_ACK_REQ); } } else @@ -576,17 +580,17 @@ bool TpUartDataLinkLayer::sendFrame(CemiFrame &cemiFrame) * Außerdem soll regelmäßig die aktuelle Config bzw der Modus übermittelt werden, so dass nach einem Disconnect, * der TPUart im richtigen Zustand ist. */ -void TpUartDataLinkLayer::requestState() +void TpUartDataLinkLayer::requestState(bool force /* = false */) { - if (_rxState != RX_IDLE) - return; + if (!force) + { + if (!(_rxState == RX_IDLE || _rxState == RX_INVALID)) + return; - if (_txState != TX_IDLE) - return; - - // Nur 1x pro Sekunde - if ((millis() - _lastStateRequest) < 1000) - return; + // Nur 1x pro Sekunde + if ((millis() - _lastStateRequest) < 1000) + return; + } // println("requestState"); @@ -720,7 +724,7 @@ bool TpUartDataLinkLayer::reset() if (success) { _lastStateRequest = 0; // Force - requestState(); + requestState(true); _rxLastTime = millis(); } @@ -860,6 +864,7 @@ void TpUartDataLinkLayer::loop() { println("TPUart overflow detected!"); _rxOverflow = false; + _rxState = RX_INVALID; } if (_tpState) @@ -869,7 +874,7 @@ void TpUartDataLinkLayer::loop() _tpState = 0; } - processRx(); + // processRx(); #ifdef USE_TP_RX_QUEUE processRxQueue(); #endif @@ -877,16 +882,6 @@ void TpUartDataLinkLayer::loop() requestState(); processTxQueue(); checkConnected(); - - /* - * Normal ist alles so gebaut worde, dass anhand der Bytes entschieden wird wann ein Frame "fertig" ist oder nicht. - * Dennoch gibt es Situationen, wo ein Frame nicht geschlossen wurde, weil Bytes verloren geangen sind oder wie beim Busmonitor - * bewusst auf ein Ack gewartet werden muss. - */ - // if (!_platform.uartAvailable() && _rxFrame->size() > 0 && (millis() - _rxLastTime) > 2) - // { - // processRxFrameComplete(); - // } } void TpUartDataLinkLayer::rxFrameReceived(TpFrame *tpFrame) @@ -1066,7 +1061,8 @@ void TpUartDataLinkLayer::processRxFrame(TpFrame *tpFrame) printFrame(tpFrame); println(); #endif - rxFrameReceived(tpFrame); + if (!(tpFrame->flags() & TP_FRAME_FLAG_ECHO)) + rxFrameReceived(tpFrame); } } diff --git a/src/knx/tpuart_data_link_layer.h b/src/knx/tpuart_data_link_layer.h index da81aa4..bace540 100644 --- a/src/knx/tpuart_data_link_layer.h +++ b/src/knx/tpuart_data_link_layer.h @@ -137,7 +137,7 @@ class TpUartDataLinkLayer : public DataLinkLayer void processRxFrameComplete(); inline void processRxFrame(TpFrame* tpFrame); void pushTxFrameQueue(TpFrame* tpFrame); - void requestState(); + void requestState(bool force = false); void requestConfig(); inline void processRxFrameByte(uint8_t byte); From 6884734cc3347b83077e74c9cd7c32e2540f33cd Mon Sep 17 00:00:00 2001 From: Marco Scholl Date: Sat, 30 Mar 2024 06:58:18 +0100 Subject: [PATCH 14/44] fixes the missing processRx() in loop() --- src/knx/tpuart_data_link_layer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/knx/tpuart_data_link_layer.cpp b/src/knx/tpuart_data_link_layer.cpp index 3f8a79f..f712fca 100644 --- a/src/knx/tpuart_data_link_layer.cpp +++ b/src/knx/tpuart_data_link_layer.cpp @@ -874,7 +874,7 @@ void TpUartDataLinkLayer::loop() _tpState = 0; } - // processRx(); + processRx(); #ifdef USE_TP_RX_QUEUE processRxQueue(); #endif From 9d7e29d705c3b9f10280e9b504c09f3155f8742e Mon Sep 17 00:00:00 2001 From: Marco Scholl Date: Sat, 30 Mar 2024 09:52:15 +0100 Subject: [PATCH 15/44] auto format --- src/knx/tp_frame.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/knx/tp_frame.h b/src/knx/tp_frame.h index 32dd13f..6647b63 100644 --- a/src/knx/tp_frame.h +++ b/src/knx/tp_frame.h @@ -2,9 +2,9 @@ #pragma GCC optimize("O3") #include "cemi_frame.h" +#include #include #include -#include // Means that the frame is invalid #define TP_FRAME_FLAG_INVALID 0b10000000 From 7b910eba59a283f4c02b5e7975748804ad814eaf Mon Sep 17 00:00:00 2001 From: Marco Scholl Date: Thu, 4 Apr 2024 20:51:45 +0200 Subject: [PATCH 16/44] small optimizes --- src/knx/tp_frame.h | 2 +- src/knx/tpuart_data_link_layer.cpp | 56 ++++++++++++++---------------- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/src/knx/tp_frame.h b/src/knx/tp_frame.h index 6647b63..a7475c5 100644 --- a/src/knx/tp_frame.h +++ b/src/knx/tp_frame.h @@ -67,7 +67,7 @@ class TpFrame TpFrame(uint16_t maxSize = 263) : _maxSize(maxSize) { - _data = new uint8_t[_maxSize]; + _data = (uint8_t *)malloc(_maxSize); _size = 0; } diff --git a/src/knx/tpuart_data_link_layer.cpp b/src/knx/tpuart_data_link_layer.cpp index f712fca..e6af0b3 100644 --- a/src/knx/tpuart_data_link_layer.cpp +++ b/src/knx/tpuart_data_link_layer.cpp @@ -123,13 +123,10 @@ enum // In diesem Zustand werden alle Bytes als Bytes für ein Frame betrachtet. RX_FRAME, - /* - * Dieser Zustand ist speziell und soll verhindern dass Framebytes als Steuerbytes betrachet werden. - * Das Ganze ist leider nötig, weil die verarbeitung nicht syncron verläuft und teilweise durch externe interrupt. - * Dadurch kann die 2,6ms ruhe nicht ermittelt werden um sicher zu sagen das ab jetzt alles wieder Steuercodes sind. - */ + // In diesem Zustand werdem alle Bytes verworfen RX_INVALID, + // Im Monitoring wird noch auf ein ACk gewartet RX_AWAITING_ACK }; @@ -189,9 +186,9 @@ void TpUartDataLinkLayer::processRxByte() return; /* - * Wenn ich im RX_INVALID state bin + * Wenn ich im RX_INVALID Modus bin * und das letzte Byte vor mehr als 2ms verarbeitet wurde (also pause >2ms) - * und keine weiteren Bytes vorliegen, + * und keine weiteren Bytes in der Buffer vorliegen, * dann kann ich den INVALID State verwerfen. */ if (_rxState == RX_INVALID && (millis() - _rxLastTime) > 2 && !_platform.uartAvailable()) @@ -208,10 +205,10 @@ void TpUartDataLinkLayer::processRxByte() * Ab jetzt muss ich davon ausgehen, dass einen Übertragungsfehler gegeben hat und die aktuellen Bytes ungültig sind. * Gleiches gilt auch wenn ein HW Overflow erkannt wurde. * - * - Die Zeit des letzten Frames 3ms vorbei ist (kann aber nicht garantiert werden, wegen möglichem ISR & DMA. Hier sollte es das Problem aber nicht geben) + * - Die Zeit des letzten Frames 3ms vorbei ist und keine Daten mehr im Buffer sind. (Wird übermir geprüft) * - Wenn der Markermodus aktiv ist und ein U_FRAME_END_IND sauber erkannt wurde. (Wird hier geprüft) * - * Ansonsten macht dieser Abschnitt nichts und verwirrft die ungültigen Bytes + * Ansonsten macht dieser Abschnitt nichts und verwirrft damit die ungültigen Bytes */ if (markerMode()) { @@ -252,12 +249,12 @@ void TpUartDataLinkLayer::processRxByte() _rxState = RX_FRAME; /* - * Hier wird inital ein Ack ohne Addressed gesetzt. Das dient dazu falls noch ein Ack vom vorherigen Frame gesetzt wurde, - * welches aber ggf nicht rechtzeitig verarbeitet (also nach der Übertragung gesendet wurde) sich nicht auf das neue Frame auswirkt. - * Der Zustand kann beliebig oft gesendet werden. Sobald der Moment gekommen ist, dass ein Ack gesendet wird, schaut TPUart im Buffer - * was der letzte Ackstatus war und sendet diesen. + * Hier wird inital ein Ack ohne Addressed gesetzt. Das dient dazu, falls noch ein Ack vom vorherigen Frame gesetzt ist, + * zurück gesetzt wird. Das passiert wenn die Verarbeitung zu stark verzögert ist (z.B. weil kein DMA/IRQ genutzt wird). + * Das ACK kann beliebig oft gesendet werden, weil es in der BCU nur gespeichert wird und erst bei bedarf genutzt / gesendet wird. * - * Das darf man natürlich nur wenn ich nicht gerade selber sende, da man eigene Frames nicht ACKt + * Das darf man natürlich nur wenn ich nicht gerade selber sende, da man eigene Frames nicht ACKt. Ggf ignoriert die BCU dies, + * aber ich wollte hier auf sicher gehen. */ if (_txState == TX_IDLE) { @@ -266,6 +263,8 @@ void TpUartDataLinkLayer::processRxByte() } else { + // Hier werden die Commands ausgewertet, falls das noch schon passiert ist. + if (byte == U_RESET_IND) { // println("U_RESET_IND"); @@ -276,7 +275,7 @@ void TpUartDataLinkLayer::processRxByte() #ifndef NCN5120 /* * Filtere "Protocol errors" weil auf anderen BCU wie der Siements dies gesetzt, when das timing nicht stimmt. - * Leider ist kein perfektes Timing möglich so dass dieser Fehler ignoriert wird. Hat auch keine bekannte Auswirkungen. + * Leider ist kein perfektes Timing möglich, so dass dieser Fehler ignoriert werden muss. Hat auch keine bekannte Auswirkungen. */ _tpState &= 0b11101000; #endif @@ -350,7 +349,7 @@ void TpUartDataLinkLayer::processRxFrameByte(uint8_t byte) { /* * Bei aktivem Maker muss das erste U_FRAME_END_IND ignoriert und auf ein Folgebyte gewartet werden. - * Das Folgebyte ist also ausschlaggebend wie dieses Byte zo bewerten ist. + * Das Folgebyte ist also ausschlaggebend wie dieses Byte zu bewerten ist. */ if (markerMode() && (byte == U_FRAME_END_IND && !_rxMarker)) { @@ -377,9 +376,7 @@ void TpUartDataLinkLayer::processRxFrameByte(uint8_t byte) /* * Dies ist ein hypotetischer Fall, dass die Frames ohne Marker kommen, obwohl der MarkerModus aktiv ist. * Hier wird der aktuelle Frame abgearbeitet und RX_INVALID gesetzt, da das aktuelle Byte hierbei nicht bearbeitet wird. - * Dieser Fall kann eintreffen wenn der Marker Modus von der TPUart nicht unterstützt wird (NCN51xx Feature). - * TODO vergleiche auf maxSize oder falls vorhanden auf länge aus telegramm -> full() - * TODO Beschriebung anpassen weil es auch bei Byteverlusten auftreten kann + * Dieser Fall kann eintreffen wenn der Marker Modus von der TPUart nicht unterstützt wird (NCN51xx Feature) aber aktiviert wurde. */ else if (markerMode() && _rxFrame->isFull()) { @@ -469,8 +466,8 @@ void TpUartDataLinkLayer::processRxFrameByte(uint8_t byte) */ void TpUartDataLinkLayer::processRxFrameComplete() { - // Sollte aktuell kein Frame in der Bearbeitung (size == 0) sein, dann breche ab - if (_rxFrame->size() == 0) + // Sollte aktuell kein Frame in der Bearbeitung sein, dann breche ab + if (!_rxFrame->size()) return; // Ist das Frame vollständig und gültig @@ -860,6 +857,10 @@ void TpUartDataLinkLayer::loop() if (!_initialized) return; + /* + * Sollte ein overflow erkannt worden sein, so wechsle in RX_INVALID. + * Das greift aber nur im loop und nicht im ISR. Aber bei Nutzung von ISR und DMA sollte dieser Fall nie passieren. + */ if (_rxOverflow) { println("TPUart overflow detected!"); @@ -874,7 +875,7 @@ void TpUartDataLinkLayer::loop() _tpState = 0; } - processRx(); + // processRx(); #ifdef USE_TP_RX_QUEUE processRxQueue(); #endif @@ -1112,16 +1113,13 @@ void TpUartDataLinkLayer::processRxQueue() while (_rxBufferCount) { const uint16_t size = pullByteFromRxQueue() + (pullByteFromRxQueue() << 8); - TpFrame *tpFrame = new TpFrame(size); - tpFrame->addFlags(pullByteFromRxQueue()); + TpFrame tpFrame = TpFrame(size); + tpFrame.addFlags(pullByteFromRxQueue()); for (uint16_t i = 0; i < size; i++) - { - tpFrame->addByte(pullByteFromRxQueue()); - } + tpFrame.addByte(pullByteFromRxQueue()); - processRxFrame(tpFrame); - delete tpFrame; + processRxFrame(&tpFrame); asm volatile("" ::: "memory"); _rxBufferCount--; } From e65afebd3ab33bd2e426e8273467e557e67ae8fe Mon Sep 17 00:00:00 2001 From: Marco Scholl Date: Fri, 5 Apr 2024 00:04:43 +0200 Subject: [PATCH 17/44] reenable sendframe handling reenable rxProcess in loop fixes a memory leak --- src/knx/tpuart_data_link_layer.cpp | 33 ++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/knx/tpuart_data_link_layer.cpp b/src/knx/tpuart_data_link_layer.cpp index e6af0b3..479b256 100644 --- a/src/knx/tpuart_data_link_layer.cpp +++ b/src/knx/tpuart_data_link_layer.cpp @@ -560,11 +560,11 @@ void TpUartDataLinkLayer::setFrameRepetition(uint8_t nack, uint8_t busy) bool TpUartDataLinkLayer::sendFrame(CemiFrame &cemiFrame) { - // if (!_connected) - // { - // dataConReceived(cemiFrame, false); - // return false; - // } + if (!_connected || _monitoring) + { + dataConReceived(cemiFrame, false); + return false; + } TpFrame *tpFrame = new TpFrame(cemiFrame); // printHex(" TP>: ", tpFrame->data(), tpFrame->size()); @@ -686,9 +686,14 @@ bool TpUartDataLinkLayer::reset() _rxInvalidFrameCounter = 0; _rxUnkownControlCounter = 0; if (_txFrame != nullptr) - _txFrame->reset(); + { + _txFrame = nullptr; + delete _txFrame; + } if (_rxFrame != nullptr) + { _rxFrame->reset(); + } _rxState = RX_IDLE; _txState = TX_IDLE; _connected = false; @@ -792,7 +797,6 @@ void TpUartDataLinkLayer::processTxQueue() if (_rxState != RX_IDLE) return; - // if (_txState != TX_IDLE) return; @@ -806,16 +810,23 @@ void TpUartDataLinkLayer::processTxQueue() _txFrameQueue.back = nullptr; } + // free old frame + if (_txFrame != nullptr) + delete _txFrame; + + // use frame from queue and delete queue entry _txFrame = entry->frame; + delete entry; + + _txState = TX_FRAME; + _txLastTime = millis(); + #ifdef DEBUG_TP_FRAMES print("Outbound: "); printFrame(_txFrame); println(); #endif - _txState = TX_FRAME; - _txLastTime = millis(); - delete entry; processTxFrameBytes(); } } @@ -875,7 +886,7 @@ void TpUartDataLinkLayer::loop() _tpState = 0; } - // processRx(); + processRx(); #ifdef USE_TP_RX_QUEUE processRxQueue(); #endif From 1754e5387b14c08b64941be604971207dc784c9f Mon Sep 17 00:00:00 2001 From: Marco Scholl Date: Mon, 8 Apr 2024 21:21:30 +0200 Subject: [PATCH 18/44] fixes some problems with txqueue --- src/knx/tpuart_data_link_layer.cpp | 89 +++++++++++++++++++++++------- src/knx/tpuart_data_link_layer.h | 10 +++- 2 files changed, 77 insertions(+), 22 deletions(-) diff --git a/src/knx/tpuart_data_link_layer.cpp b/src/knx/tpuart_data_link_layer.cpp index 479b256..a378083 100644 --- a/src/knx/tpuart_data_link_layer.cpp +++ b/src/knx/tpuart_data_link_layer.cpp @@ -306,13 +306,7 @@ void TpUartDataLinkLayer::processRxByte() if (_txState == TX_FRAME) { const bool success = ((byte ^ L_DATA_CON_MASK) >> 7); - uint8_t *cemiData = _txFrame->cemiData(); - CemiFrame cemiFrame(cemiData, _txFrame->cemiSize()); - dataConReceived(cemiFrame, success); - free(cemiData); - delete _txFrame; - _txFrame = nullptr; - _txState = TX_IDLE; + processTxFrameComplete(success); } else { @@ -436,7 +430,7 @@ void TpUartDataLinkLayer::processRxFrameByte(uint8_t byte) if (availableInRxQueue() < (_rxFrame->size() + 3)) { // Nur wenn ich nicht selber sende - if (_txState == RX_IDLE) + if (_txState == TX_IDLE) { _platform.writeUart(U_ACK_REQ | U_ACK_REQ_ADRESSED | U_ACK_REQ_BUSY); } @@ -529,6 +523,30 @@ void TpUartDataLinkLayer::processRxFrameComplete() _rxFrame->reset(); } +void TpUartDataLinkLayer::clearTxFrame() +{ + if (_txFrame != nullptr) + { + delete _txFrame; + _txFrame = nullptr; + } +} + +void TpUartDataLinkLayer::clearTxFrameQueue() +{ +} + +void TpUartDataLinkLayer::processTxFrameComplete(bool success) +{ + uint8_t *cemiData = _txFrame->cemiData(); + CemiFrame cemiFrame(cemiData, _txFrame->cemiSize()); + dataConReceived(cemiFrame, success); + free(cemiData); + clearTxFrame(); + _txProcessdFrameCounter++; + _txState = TX_IDLE; +} + /* * Steckt das zu sendende Frame in eine Queue, da der TpUart vielleicht gerade noch nicht sende bereit ist. */ @@ -545,6 +563,14 @@ void TpUartDataLinkLayer::pushTxFrameQueue(TpFrame *tpFrame) _txFrameQueue.back->next = entry; _txFrameQueue.back = entry; } + + if (_txQueueCount > 10) + { + print("_txQueueCount:"); + print(_txQueueCount); + } + _txQueueCount++; + _txFrameCounter++; } void TpUartDataLinkLayer::setRepetitions(uint8_t nack, uint8_t busy) @@ -685,17 +711,15 @@ bool TpUartDataLinkLayer::reset() _rxInvalidFrameCounter = 0; _rxInvalidFrameCounter = 0; _rxUnkownControlCounter = 0; - if (_txFrame != nullptr) - { - _txFrame = nullptr; - delete _txFrame; - } + + clearTxFrame(); + clearTxFrameQueue(); + if (_rxFrame != nullptr) { _rxFrame->reset(); } _rxState = RX_IDLE; - _txState = TX_IDLE; _connected = false; _stopped = false; _monitoring = false; @@ -786,6 +810,17 @@ bool TpUartDataLinkLayer::enabled() const return _initialized && _connected; } +/* + * Wenn ein TxFrame gesendet wurde, wird eine Bestätigung für den Versand erwartet. + * Kam es aber zu einem ungültigen Frame oder Busdisconnect, bleibt die Bestätigung aus und der STack hängt im TX_FRAME fest. + * Daher muss nach einer kurzen Wartezeit das Warten beendet werden. + */ +void TpUartDataLinkLayer::clearOutdatedTxFrame() +{ + if (_txState == TX_FRAME && (millis() - _txLastTime) > 1000) + processTxFrameComplete(false); +} + /* * Hier werden die ausgehenden Frames aus der Warteschlange genomnmen und versendet. * Das passiert immer nur einzelnd, da nach jedem Frame, gewartet werden muss bis das Frame wieder reingekommen ist und das L_DATA_CON rein kommt. @@ -793,10 +828,6 @@ bool TpUartDataLinkLayer::enabled() const */ void TpUartDataLinkLayer::processTxQueue() { - // Diese Abfrage ist vorsorglich. Eigentlich sollte auch schon parallel gestartet werden können. - if (_rxState != RX_IDLE) - return; - if (_txState != TX_IDLE) return; @@ -810,9 +841,9 @@ void TpUartDataLinkLayer::processTxQueue() _txFrameQueue.back = nullptr; } - // free old frame - if (_txFrame != nullptr) - delete _txFrame; + _txQueueCount--; + + clearTxFrame(); // use frame from queue and delete queue entry _txFrame = entry->frame; @@ -892,6 +923,7 @@ void TpUartDataLinkLayer::loop() #endif requestState(); + clearOutdatedTxFrame(); processTxQueue(); checkConnected(); } @@ -1015,6 +1047,21 @@ uint32_t TpUartDataLinkLayer::getRxUnknownControlCounter() return _rxUnkownControlCounter; } +/* + * Liefert die Anzahl der zusendenden Frames + */ +uint32_t TpUartDataLinkLayer::getTxFrameCounter() +{ + return _txFrameCounter; +} +/* + * Liefert die Anzahl der versendeten Frames + */ +uint32_t TpUartDataLinkLayer::getTxProcessedFrameCounter() +{ + return _txProcessdFrameCounter; +} + bool TpUartDataLinkLayer::isConnected() { return _connected; diff --git a/src/knx/tpuart_data_link_layer.h b/src/knx/tpuart_data_link_layer.h index bace540..24afa1a 100644 --- a/src/knx/tpuart_data_link_layer.h +++ b/src/knx/tpuart_data_link_layer.h @@ -64,6 +64,8 @@ class TpUartDataLinkLayer : public DataLinkLayer uint32_t getRxProcessdFrameCounter(); uint32_t getRxIgnoredFrameCounter(); uint32_t getRxUnknownControlCounter(); + uint32_t getTxFrameCounter(); + uint32_t getTxProcessedFrameCounter(); uint8_t getMode(); private: @@ -95,19 +97,21 @@ class TpUartDataLinkLayer : public DataLinkLayer volatile bool _busy = false; volatile bool _initialized = false; - volatile uint32_t _stateTime = 0; volatile uint8_t _rxState = 0; volatile uint8_t _txState = 0; volatile uint32_t _rxProcessdFrameCounter = 0; volatile uint32_t _rxInvalidFrameCounter = 0; volatile uint32_t _rxIgnoredFrameCounter = 0; volatile uint32_t _rxUnkownControlCounter = 0; + volatile uint32_t _txFrameCounter = 0; + volatile uint32_t _txProcessdFrameCounter = 0; volatile bool _rxMarker = false; volatile bool _rxOverflow = false; volatile uint8_t _tpState = 0x0; volatile uint32_t _txLastTime = 0; volatile uint32_t _rxLastTime = 0; volatile bool _forceAck = false; + uint8_t _txQueueCount = 0; inline bool markerMode(); @@ -134,6 +138,7 @@ class TpUartDataLinkLayer : public DataLinkLayer void checkConnected(); void processRxByte(); void processTxQueue(); + void clearTxFrameQueue(); void processRxFrameComplete(); inline void processRxFrame(TpFrame* tpFrame); void pushTxFrameQueue(TpFrame* tpFrame); @@ -159,6 +164,9 @@ class TpUartDataLinkLayer : public DataLinkLayer inline void isrUnlock(); inline void clearUartBuffer(); inline void connected(bool state = true); + void clearTxFrame(); + void clearOutdatedTxFrame(); + void processTxFrameComplete(bool success); ITpUartCallBacks& _cb; DataLinkLayerCallbacks* _dllcb; From 97e15c7d9257f081a5280096e1679c0cb63c1425 Mon Sep 17 00:00:00 2001 From: Marco Scholl Date: Wed, 10 Apr 2024 14:36:19 +0200 Subject: [PATCH 19/44] remove some debug output. adds a tx queue size limit with warning. --- src/knx/tpuart_data_link_layer.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/knx/tpuart_data_link_layer.cpp b/src/knx/tpuart_data_link_layer.cpp index a378083..834c212 100644 --- a/src/knx/tpuart_data_link_layer.cpp +++ b/src/knx/tpuart_data_link_layer.cpp @@ -109,6 +109,8 @@ #define ACR0_FLAG_TRIGEN 0x08 #define ACR0_FLAG_V20VCLIMIT 0x04 +#define MAX_TX_QUEUE 20 + enum { TX_IDLE, @@ -193,7 +195,6 @@ void TpUartDataLinkLayer::processRxByte() */ if (_rxState == RX_INVALID && (millis() - _rxLastTime) > 2 && !_platform.uartAvailable()) { - println("Reset RX_INVALID"); processRxFrameComplete(); _rxState = RX_IDLE; } @@ -564,11 +565,6 @@ void TpUartDataLinkLayer::pushTxFrameQueue(TpFrame *tpFrame) _txFrameQueue.back = entry; } - if (_txQueueCount > 10) - { - print("_txQueueCount:"); - print(_txQueueCount); - } _txQueueCount++; _txFrameCounter++; } @@ -586,8 +582,12 @@ void TpUartDataLinkLayer::setFrameRepetition(uint8_t nack, uint8_t busy) bool TpUartDataLinkLayer::sendFrame(CemiFrame &cemiFrame) { - if (!_connected || _monitoring) + if (!_connected || _monitoring || _txQueueCount > MAX_TX_QUEUE) { + if (_txQueueCount > MAX_TX_QUEUE) + { + println("Ignore frame because transmit queue is full!"); + } dataConReceived(cemiFrame, false); return false; } From 1ef4856740dc8798aa5f0ef7e4f5d63dfe173830 Mon Sep 17 00:00:00 2001 From: Dom Date: Mon, 22 Apr 2024 13:22:15 +0200 Subject: [PATCH 20/44] Update platformio-ci.ini use newest atmelsam plattform --- examples/knx-usb/platformio-ci.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/knx-usb/platformio-ci.ini b/examples/knx-usb/platformio-ci.ini index dc8e232..6688b72 100644 --- a/examples/knx-usb/platformio-ci.ini +++ b/examples/knx-usb/platformio-ci.ini @@ -8,7 +8,7 @@ ; Please visit documentation for the other options and examples ; https://docs.platformio.org/page/projectconf.html [env:adafruit_feather_m0] -platform = atmelsam@6.0.1 +platform = atmelsam board = adafruit_feather_m0 framework = arduino From f94bd0b674b3fefec9afbab8d1ad10551086f74d Mon Sep 17 00:00:00 2001 From: Marco Scholl Date: Sun, 19 May 2024 23:52:21 +0200 Subject: [PATCH 21/44] allow to change MAX_TX_QUEUE --- src/knx/tpuart_data_link_layer.cpp | 2 -- src/knx/tpuart_data_link_layer.h | 5 +++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/knx/tpuart_data_link_layer.cpp b/src/knx/tpuart_data_link_layer.cpp index 834c212..e74df73 100644 --- a/src/knx/tpuart_data_link_layer.cpp +++ b/src/knx/tpuart_data_link_layer.cpp @@ -109,8 +109,6 @@ #define ACR0_FLAG_TRIGEN 0x08 #define ACR0_FLAG_V20VCLIMIT 0x04 -#define MAX_TX_QUEUE 20 - enum { TX_IDLE, diff --git a/src/knx/tpuart_data_link_layer.h b/src/knx/tpuart_data_link_layer.h index 24afa1a..729e7be 100644 --- a/src/knx/tpuart_data_link_layer.h +++ b/src/knx/tpuart_data_link_layer.h @@ -8,10 +8,15 @@ #include #define MAX_KNX_TELEGRAM_SIZE 263 + #ifndef MAX_RX_QUEUE_BYTES #define MAX_RX_QUEUE_BYTES MAX_KNX_TELEGRAM_SIZE + 50 #endif +#ifndef MAX_TX_QUEUE +#define MAX_TX_QUEUE 50 +#endif + // __time_critical_func fallback #ifndef ARDUINO_ARCH_RP2040 #define __time_critical_func(X) X From a330699a0e72e9ee92c586e10528799f38f25e13 Mon Sep 17 00:00:00 2001 From: Michael Geramb Date: Wed, 17 Apr 2024 19:21:27 +0200 Subject: [PATCH 22/44] Add function to group object to send a value only if it was changed. --- src/knx/group_object.cpp | 10 ++++++++++ src/knx/group_object.h | 13 +++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/knx/group_object.cpp b/src/knx/group_object.cpp index 0abbfc0..5a9ad28 100644 --- a/src/knx/group_object.cpp +++ b/src/knx/group_object.cpp @@ -310,4 +310,14 @@ bool GroupObject::valueNoSendCompare(const KNXValue& value, const Dpt& type) return dataChanged; } +} + +bool GroupObject::valueCompare(const KNXValue& value, const Dpt& type) +{ + if (valueNoSendCompare(value, type)) + { + objectWritten(); + return true; + } + return false; } \ No newline at end of file diff --git a/src/knx/group_object.h b/src/knx/group_object.h index 38b8291..02bd0c3 100644 --- a/src/knx/group_object.h +++ b/src/knx/group_object.h @@ -173,6 +173,19 @@ class GroupObject * The parameters must fit the group object. Otherwise it will stay unchanged. */ void value(const KNXValue& value, const Dpt& type); + + /** + * Check if the value (after conversion to dpt) will differ from current value of the group object and changes the state of the group object to ::WriteRequest if different. + * Use this method only, when the value should not be sent if it was not changed, otherwise value(const KNXValue&, const Dpt&) will do the same (without overhead for comparing) + * @param value the value the group object is set to + * @param type the datapoint type used for the conversion. + * + * The parameters must fit the group object. Otherwise it will stay unchanged. + * + * @returns true if the value of the group object has changed + */ + bool valueCompare(const KNXValue& value, const Dpt& type); + /** * set the current value of the group object. * @param value the value the group object is set to From 2adac5e4afbc3f54dda821e856822d7f61631ccb Mon Sep 17 00:00:00 2001 From: Marco Scholl Date: Thu, 23 May 2024 09:35:07 +0200 Subject: [PATCH 23/44] fix: resets stats --- src/knx/tpuart_data_link_layer.cpp | 22 ++++++++++++++++------ src/knx/tpuart_data_link_layer.h | 1 + 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/knx/tpuart_data_link_layer.cpp b/src/knx/tpuart_data_link_layer.cpp index e74df73..67e9e25 100644 --- a/src/knx/tpuart_data_link_layer.cpp +++ b/src/knx/tpuart_data_link_layer.cpp @@ -564,7 +564,6 @@ void TpUartDataLinkLayer::pushTxFrameQueue(TpFrame *tpFrame) } _txQueueCount++; - _txFrameCounter++; } void TpUartDataLinkLayer::setRepetitions(uint8_t nack, uint8_t busy) @@ -580,12 +579,15 @@ void TpUartDataLinkLayer::setFrameRepetition(uint8_t nack, uint8_t busy) bool TpUartDataLinkLayer::sendFrame(CemiFrame &cemiFrame) { + _txFrameCounter++; + if (!_connected || _monitoring || _txQueueCount > MAX_TX_QUEUE) { if (_txQueueCount > MAX_TX_QUEUE) { println("Ignore frame because transmit queue is full!"); } + dataConReceived(cemiFrame, false); return false; } @@ -692,6 +694,17 @@ void TpUartDataLinkLayer::connected(bool state /* = true */) _connected = state; } +void TpUartDataLinkLayer::resetStats() +{ + _rxProcessdFrameCounter = 0; + _rxIgnoredFrameCounter = 0; + _rxInvalidFrameCounter = 0; + _rxInvalidFrameCounter = 0; + _rxUnkownControlCounter = 0; + _txFrameCounter = 0; + _txProcessdFrameCounter = 0; +} + bool TpUartDataLinkLayer::reset() { // println("Reset TP"); @@ -705,11 +718,7 @@ bool TpUartDataLinkLayer::reset() isrLock(true); // Reset - _rxIgnoredFrameCounter = 0; - _rxInvalidFrameCounter = 0; - _rxInvalidFrameCounter = 0; - _rxUnkownControlCounter = 0; - + resetStats(); clearTxFrame(); clearTxFrameQueue(); @@ -792,6 +801,7 @@ void TpUartDataLinkLayer::monitor() // println("busmonitor"); _monitoring = true; _platform.writeUart(U_BUSMON_REQ); + resetStats(); } void TpUartDataLinkLayer::enabled(bool value) diff --git a/src/knx/tpuart_data_link_layer.h b/src/knx/tpuart_data_link_layer.h index 729e7be..7dd0d55 100644 --- a/src/knx/tpuart_data_link_layer.h +++ b/src/knx/tpuart_data_link_layer.h @@ -57,6 +57,7 @@ class TpUartDataLinkLayer : public DataLinkLayer bool isMonitoring(); bool isStopped(); bool isBusy(); + void resetStats(); #ifdef USE_TP_RX_QUEUE void processRxISR(); From a645575a72fe04d18b6657ca7ceca6a1dbee5ce0 Mon Sep 17 00:00:00 2001 From: Marco Scholl Date: Thu, 23 May 2024 15:38:31 +0200 Subject: [PATCH 24/44] support detect acks with busy + nack and show in monitoring --- src/knx/tp_frame.h | 13 ++++++++----- src/knx/tpuart_data_link_layer.cpp | 22 +++++++++++++++------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/knx/tp_frame.h b/src/knx/tp_frame.h index a7475c5..cd900a6 100644 --- a/src/knx/tp_frame.h +++ b/src/knx/tp_frame.h @@ -19,13 +19,16 @@ #define TP_FRAME_FLAG_ECHO 0b00010000 // Means that the frame is processed by this device -#define TP_FRAME_FLAG_ADDRESSED 0b00000100 +#define TP_FRAME_FLAG_ADDRESSED 0b00001000 -// Means that the frame has been acked by this device. -#define TP_FRAME_FLAG_ACKING 0b00000010 +// Means that the frame has been acked with BUSY +#define TP_FRAME_FLAG_ACK_BUSY 0b00000100 -// Means that the frame has been acked by other (Busmontior) -#define TP_FRAME_FLAG_ACKED 0b00000001 +// Means that the frame has been acked with NACK +#define TP_FRAME_FLAG_ACK_NACK 0b00000010 + +// Means that the frame has been acked +#define TP_FRAME_FLAG_ACK 0b00000001 class TpFrame { diff --git a/src/knx/tpuart_data_link_layer.cpp b/src/knx/tpuart_data_link_layer.cpp index 67e9e25..a011454 100644 --- a/src/knx/tpuart_data_link_layer.cpp +++ b/src/knx/tpuart_data_link_layer.cpp @@ -61,6 +61,8 @@ // acknowledge services (device is transparent in bus monitor mode) #define L_ACKN_IND 0x00 #define L_ACKN_MASK 0x33 +#define L_ACKN_BUSY_MASK 0x0C +#define L_ACKN_NACK_MASK 0xC0 #define L_DATA_CON 0x0B #define L_DATA_CON_MASK 0x7F #define SUCCESS 0x80 @@ -139,11 +141,11 @@ void printFrame(TpFrame *tpframe) print((tpframe->flags() & TP_FRAME_FLAG_INVALID) ? 'I' : '_'); // Invalid print((tpframe->flags() & TP_FRAME_FLAG_EXTENDED) ? 'E' : '_'); // Extended print((tpframe->flags() & TP_FRAME_FLAG_REPEATED) ? 'R' : '_'); // Repeat - print((tpframe->flags() & TP_FRAME_FLAG_ECHO) ? 'O' : '_'); // My own - print((tpframe->flags() & 0b00001000) ? 'x' : '_'); // Reserve - print((tpframe->flags() & TP_FRAME_FLAG_ADDRESSED) ? 'D' : '_'); // For me - print((tpframe->flags() & TP_FRAME_FLAG_ACKING) ? 'A' : '_'); // ACK recevied - print((tpframe->flags() & TP_FRAME_FLAG_ACKED) ? 'A' : '_'); // ACK sent + print((tpframe->flags() & TP_FRAME_FLAG_ECHO) ? 'T' : '_'); // Send by me + print((tpframe->flags() & TP_FRAME_FLAG_ADDRESSED) ? 'D' : '_'); // Recv for me + print((tpframe->flags() & TP_FRAME_FLAG_ACK_NACK) ? 'N' : '_'); // ACK + NACK + print((tpframe->flags() & TP_FRAME_FLAG_ACK_BUSY) ? 'B' : '_'); // ACK + BUSY + print((tpframe->flags() & TP_FRAME_FLAG_ACK) ? 'A' : '_'); // ACK print("] "); printHex("( ", tpframe->data(), tpframe->size(), false); print(")"); @@ -295,7 +297,13 @@ void TpUartDataLinkLayer::processRxByte() */ if (_rxFrame->size() > 0) { - _rxFrame->addFlags(TP_FRAME_FLAG_ACKED); + if (!(byte & L_ACKN_BUSY_MASK)) + _rxFrame->addFlags(TP_FRAME_FLAG_ACK_BUSY); + + if (!(byte & L_ACKN_NACK_MASK)) + _rxFrame->addFlags(TP_FRAME_FLAG_ACK_NACK); + + _rxFrame->addFlags(TP_FRAME_FLAG_ACK); processRxFrameComplete(); } // println("L_ACKN_IND"); @@ -414,7 +422,7 @@ void TpUartDataLinkLayer::processRxFrameByte(uint8_t byte) if (_txState == TX_IDLE) { // Speichere das ein Acking erfolgen soll - _rxFrame->addFlags(TP_FRAME_FLAG_ACKING); + _rxFrame->addFlags(TP_FRAME_FLAG_ACK); // und im TPUart damit dieser das ACK schicken kann _platform.writeUart(U_ACK_REQ | U_ACK_REQ_ADRESSED); From 6eb7af74a0d54663d2d14b34bacab8bd106d1ca8 Mon Sep 17 00:00:00 2001 From: Ing-Dom Date: Sun, 2 Jun 2024 23:45:54 +0200 Subject: [PATCH 25/44] flag also repeated telegrams as echoed / own telegrams --- src/knx/tpuart_data_link_layer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/knx/tpuart_data_link_layer.cpp b/src/knx/tpuart_data_link_layer.cpp index a011454..627daf0 100644 --- a/src/knx/tpuart_data_link_layer.cpp +++ b/src/knx/tpuart_data_link_layer.cpp @@ -477,8 +477,8 @@ void TpUartDataLinkLayer::processRxFrameComplete() // Wenn ein Frame gesendet wurde if (_txState == TX_FRAME) { - // prüfe ob das Empfangen diesem entspricht - if (!memcmp(_rxFrame->data(), _txFrame->data(), _txFrame->size())) + // prüfe ob das Empfangen diesem entspricht: Vergleich der Quelladresse und Zieladresse sowie Startbyte ohne Berücksichtigung des Retry-Bits + if(!((_rxFrame->data(0) ^ _txFrame->data(0)) & ~0x20) && _rxFrame->destination() == _txFrame->destination() && _rxFrame->source() == _txFrame->source()) { // und markiere das entsprechend // println("MATCH"); From 4f0e47f809ebcc535652b01f7e60798f96f1b336 Mon Sep 17 00:00:00 2001 From: Marco Scholl Date: Mon, 3 Jun 2024 10:36:39 +0200 Subject: [PATCH 26/44] drop unexpected udp data packet length --- src/rp2040_arduino_platform.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/rp2040_arduino_platform.cpp b/src/rp2040_arduino_platform.cpp index 691d768..96c13c9 100644 --- a/src/rp2040_arduino_platform.cpp +++ b/src/rp2040_arduino_platform.cpp @@ -517,11 +517,10 @@ int RP2040ArduinoPlatform::readBytesMultiCast(uint8_t* buffer, uint16_t maxLen) if (len > maxLen) { - print("udp buffer to small. was "); - print(maxLen); - print(", needed "); - println(len); - fatalError(); + println("Unexpected UDP data packet length - drop packet"); + for (size_t i = 0; i < len; i++) + _udp.read(); + return 0; } _udp.read(buffer, len); From acf4a0b6e4d2a0b713c7661fd2f203828223b4c3 Mon Sep 17 00:00:00 2001 From: Mike <45664417+thewhobox@users.noreply.github.com> Date: Mon, 3 Jun 2024 13:03:41 +0200 Subject: [PATCH 27/44] remove annoying file --- .vscode/extensions.json | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 .vscode/extensions.json diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index 080e70d..0000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - // See http://go.microsoft.com/fwlink/?LinkId=827846 - // for the documentation about the extensions.json format - "recommendations": [ - "platformio.platformio-ide" - ], - "unwantedRecommendations": [ - "ms-vscode.cpptools-extension-pack" - ] -} From e69f63611a27eac41d48c8fa1d5a0e1e8755374d Mon Sep 17 00:00:00 2001 From: Ing-Dom Date: Wed, 3 Jul 2024 11:27:25 +0200 Subject: [PATCH 28/44] Release V2.1.0 --- README.md | 57 ++++++++++++---------------------------------- library.json | 23 +++++++++++++++++++ library.properties | 4 ++-- 3 files changed, 40 insertions(+), 44 deletions(-) create mode 100644 library.json diff --git a/README.md b/README.md index 953cfa8..8f84371 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,26 @@ # knx +This is a fork of the thelsing/knx stack from Thomas Kunze for and by the OpenKNX Team. + +While we did not remove support for any plattform, the testing focus is on RP2040 (main), ESP32 (experimental) and SAMD21(deprecated). + This projects provides a knx-device stack for arduino (ESP8266, ESP32, SAMD21, RP2040, STM32), CC1310 and linux. (more are quite easy to add) It implements most of System-B specification and can be configured with ETS. The necessary knxprod-files can be generated with the [Kaenx-Creator](https://github.com/OpenKNX/Kaenx-Creator) tool. -For ESP8266 and ESP32 [WifiManager](https://github.com/tzapu/WiFiManager) is used to configure wifi. -Don't forget to reset ESP8266 manually (disconnect power) after flashing. The reboot doen't work during configuration with ETS otherwise. +## Usage +See the examples for basic usage options -Generated documentation can be found [here](https://knx.readthedocs.io/en/latest/). -## Stack configuration possibilities +## Changelog -Specify prog button GPIO other then `GPIO0`: -```C++ -knx.buttonPin(3); // Use GPIO3 Pin -``` +### V2.1.0 - 2024-07-03 +- complete rework of the TPUart DataLinkLayer with support interrupt-based handling and optimized queue handling +- added DMA support for RP2040 platform +- fix some issues with continous integration causing github actions to fail +- added rp2040 plattform to knx-demo example +- added bool GroupObject::valueCompare method for only sending the value when it has changed -Specify a LED GPIO for programming mode other then the `LED_BUILTIN`: -```C++ -knx.ledPin(5); -``` - -Use a custom function instead of a LED connected to GPIO to indicate the programming mode: -```C++ -#include -#include -#include -// create a pixel strand with 1 pixel on PIN_NEOPIXEL -Adafruit_NeoPixel pixels(1, PIN_NEOPIXEL); - -void progLedOff() -{ - pixels.clear(); - pixels.show(); -} - -void progLedOn() -{ - pixels.setPixelColor(0, pixels.Color(20, 0, 0)); - pixels.show(); -} - -void main () -{ - knx.setProgLedOffCallback(progLedOff); - knx.setProgLedOnCallback(progLedOn); - [...] -} -``` - -More configuration options can be found in the examples. +### V2.0.0 - 2024-02-13 +- first OpenKNX version \ No newline at end of file diff --git a/library.json b/library.json new file mode 100644 index 0000000..a8ff4f3 --- /dev/null +++ b/library.json @@ -0,0 +1,23 @@ +{ + "name": "knx", + "version": "2.1.0", + "dependencies": { + }, + "description": "knx stack", + "homepage": "https://openknx.de", + "authors": [ + { + "name": "Thomas Kunze" + }, + { + "name": "OpenKNX", + "email": "info@openknx.de", + "url": "https://openknx.de", + "maintainer": true + } + ], + "repository": { + "type": "git", + "url": "https://github.com/OpenKNX/knx" + } +} \ No newline at end of file diff --git a/library.properties b/library.properties index 980e8ea..8a6dcf8 100644 --- a/library.properties +++ b/library.properties @@ -1,6 +1,6 @@ name=knx -version=2.0.0 -author=Thomas Kunze et al. +version=2.1.0 +author=Thomas Kunze, the OpenKNX Team, et. al. maintainer=OpenKNX Team sentence=knx stack paragraph= From 12fb67cc63df03ddafedfd7759eaf0e51ac92abd Mon Sep 17 00:00:00 2001 From: Ing-Dom Date: Wed, 17 Jul 2024 22:22:58 +0200 Subject: [PATCH 29/44] fix: only set pinMode of Prog button pin when pin is >= 0 and isr function is defined otherwise it is useless anyway and causes warnings. --- src/knx_facade.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/knx_facade.h b/src/knx_facade.h index 75d8fdb..3f1c60b 100644 --- a/src/knx_facade.h +++ b/src/knx_facade.h @@ -286,10 +286,10 @@ template class KnxFacade : private SaveRestore pinMode(ledPin(), OUTPUT); progLedOff(); - pinMode(buttonPin(), INPUT_PULLUP); - + if (_progButtonISRFuncPtr && _buttonPin >= 0) { + pinMode(buttonPin(), INPUT_PULLUP); // Workaround for https://github.com/arduino/ArduinoCore-samd/issues/587 #if (ARDUINO_API_VERSION >= 10200) attachInterrupt(_buttonPin, _progButtonISRFuncPtr, (PinStatus)CHANGE); From 093ae425b0040796e742ad8f74b8155b988fe4bd Mon Sep 17 00:00:00 2001 From: Michael Geramb Date: Sat, 20 Jul 2024 16:27:47 +0200 Subject: [PATCH 30/44] String \0 terminated in group objects (#25) * String \0 terminated in group objects * Remove copy constructor, fix bugs in setting buffer to 0 * Remove copy constructor in GroupObject --- src/knx/dptconvert.cpp | 8 +++----- src/knx/group_object.cpp | 27 +++++++++++++-------------- src/knx/group_object.h | 11 ++++++----- src/knx/group_object_table_object.cpp | 7 ++++--- 4 files changed, 26 insertions(+), 27 deletions(-) diff --git a/src/knx/dptconvert.cpp b/src/knx/dptconvert.cpp index 1954d2d..9f05fb2 100644 --- a/src/knx/dptconvert.cpp +++ b/src/knx/dptconvert.cpp @@ -517,15 +517,13 @@ int busValueToAccess(const uint8_t* payload, size_t payload_length, const Dpt& d int busValueToString(const uint8_t* payload, size_t payload_length, const Dpt& datatype, KNXValue& value) { ASSERT_PAYLOAD(14); - char strValue[15]; - strValue[14] = '\0'; for (int n = 0; n < 14; ++n) { - strValue[n] = signed8FromPayload(payload, n); - if (!datatype.subGroup && (strValue[n] & 0x80)) + auto value = signed8FromPayload(payload, n); + if (!datatype.subGroup && (value & 0x80)) return false; } - value = strValue; + value = (const char*) payload; return true; } diff --git a/src/knx/group_object.cpp b/src/knx/group_object.cpp index 5a9ad28..7b172d5 100644 --- a/src/knx/group_object.cpp +++ b/src/knx/group_object.cpp @@ -20,18 +20,6 @@ GroupObject::GroupObject() #endif } -GroupObject::GroupObject(const GroupObject& other) -{ - _data = new uint8_t[other._dataLength]; - _commFlagEx = other._commFlagEx; - _dataLength = other._dataLength; - _asap = other._asap; -#ifndef SMALL_GROUPOBJECT - _updateHandler = other._updateHandler; -#endif - memcpy(_data, other._data, _dataLength); -} - GroupObject::~GroupObject() { if (_data) @@ -114,12 +102,12 @@ size_t GroupObject::goSize() size_t size = sizeInTelegram(); if (size == 0) return 1; - + return size; } // see knxspec 3.5.1 p. 178 -size_t GroupObject::asapValueSize(uint8_t code) +size_t GroupObject::asapValueSize(uint8_t code) const { if (code < 7) return 0; @@ -194,6 +182,17 @@ size_t GroupObject::sizeInTelegram() return asapValueSize(code); } +size_t GroupObject::sizeInMemory() const +{ + uint8_t code = lowByte(ntohs(_table->_tableData[_asap])); + size_t result = asapValueSize(code); + if (code == 0) + return 1; + if (code == 14) + return 14 + 1; + return result; +} + #ifdef SMALL_GROUPOBJECT GroupObjectUpdatedHandler GroupObject::classCallback() { diff --git a/src/knx/group_object.h b/src/knx/group_object.h index 02bd0c3..244fab6 100644 --- a/src/knx/group_object.h +++ b/src/knx/group_object.h @@ -57,10 +57,6 @@ class GroupObject * The constructor. */ GroupObject(); - /** - * The copy constructor. - */ - GroupObject(const GroupObject& other); /** * The destructor. */ @@ -139,6 +135,11 @@ class GroupObject * will return 0. */ size_t sizeInTelegram(); + /** + * returns the size of the group object in the heap memory of the group object. The function returns the same value as goSize(), + * exept fot the 14 byte string type to reserve one byte of a \0 terminator character. + */ + size_t sizeInMemory() const; /** * returns the pointer to the value of the group object. This can be used if a datapoint type is not supported or if you want do * your own conversion. @@ -274,7 +275,7 @@ class GroupObject static GroupObjectUpdatedHandler _updateHandlerStatic; #endif - size_t asapValueSize(uint8_t code); + size_t asapValueSize(uint8_t code) const; size_t goSize(); uint16_t _asap = 0; ComFlagEx _commFlagEx; diff --git a/src/knx/group_object_table_object.cpp b/src/knx/group_object_table_object.cpp index bdcf8dd..8d931ce 100644 --- a/src/knx/group_object_table_object.cpp +++ b/src/knx/group_object_table_object.cpp @@ -107,10 +107,11 @@ bool GroupObjectTableObject::initGroupObjects() GroupObject& go = _groupObjects[asap - 1]; go._asap = asap; go._table = this; - + go._dataLength = go.goSize(); - go._data = new uint8_t[go._dataLength]; - memset(go._data, 0, go._dataLength); + size_t sizeInMemory = go.sizeInMemory(); + go._data = new uint8_t[sizeInMemory]; + memset(go._data, 0, sizeInMemory); if (go.valueReadOnInit()) go.requestObjectRead(); From d0b5e84526cf8a895f37884259de61f63aa29c32 Mon Sep 17 00:00:00 2001 From: Marco Scholl Date: Sat, 20 Jul 2024 23:47:58 +0200 Subject: [PATCH 31/44] remove generic ethernet support on rp2040. uses now KNX_IP_LAN or KNX_IP_WIFI --- src/rp2040_arduino_platform.cpp | 11 ++++------ src/rp2040_arduino_platform.h | 39 +++++---------------------------- 2 files changed, 9 insertions(+), 41 deletions(-) diff --git a/src/rp2040_arduino_platform.cpp b/src/rp2040_arduino_platform.cpp index 96c13c9..cbc745e 100644 --- a/src/rp2040_arduino_platform.cpp +++ b/src/rp2040_arduino_platform.cpp @@ -18,9 +18,8 @@ EEPROM Emulation from arduino-pico core (max 4k) can be use by defining USE_RP20 A RAM-buffered Flash can be use by defining USE_RP2040_LARGE_EEPROM_EMULATION For usage of KNX-IP you have to define either -- KNX_IP_W5500 (use the arduino-pico core's w5500 lwip stack) +- KNX_IP_LAN (use the arduino-pico core's w5500 lwip stack) - KNX_IP_WIFI (use the arduino-pico core's PiPicoW lwip stack) -- KNX_IP_GENERIC (use the Ethernet_Generic stack) ----------------------------------------------------*/ @@ -106,7 +105,7 @@ void __time_critical_func(uartDmaRestart)() #endif #endif -#ifdef KNX_IP_W5500 +#ifdef KNX_IP_LAN extern Wiznet5500lwIP KNX_NETIF; #elif defined(KNX_IP_WIFI) #elif defined(KNX_IP_GENERIC) @@ -462,13 +461,11 @@ uint32_t RP2040ArduinoPlatform::currentDefaultGateway() } void RP2040ArduinoPlatform::macAddress(uint8_t* addr) { -#if defined(KNX_IP_W5500) +#if defined(KNX_IP_LAN) addr = KNX_NETIF.getNetIf()->hwaddr; -#elif defined(KNX_IP_WIFI) +#else uint8_t macaddr[6] = {0, 0, 0, 0, 0, 0}; addr = KNX_NETIF.macAddress(macaddr); -#elif defined(KNX_IP_GENERIC) - KNX_NETIF.MACAddress(addr); #endif } diff --git a/src/rp2040_arduino_platform.h b/src/rp2040_arduino_platform.h index c02c2a7..bb756d0 100644 --- a/src/rp2040_arduino_platform.h +++ b/src/rp2040_arduino_platform.h @@ -22,40 +22,18 @@ #define KNX_SERIAL Serial1 #endif -#ifdef KNX_IP_W5500 -#if ARDUINO_PICO_MAJOR * 10000 + ARDUINO_PICO_MINOR * 100 + ARDUINO_PICO_REVISION < 30600 -#pragma error "arduino-pico >= 3.6.0 needed" +#ifdef KNX_IP_LAN +#if ARDUINO_PICO_MAJOR * 10000 + ARDUINO_PICO_MINOR * 100 + ARDUINO_PICO_REVISION < 30700 +#pragma error "arduino-pico >= 3.7.0 needed" #endif #define KNX_NETIF Eth #include "SPI.h" #include -#elif defined(KNX_IP_WIFI) - -#define KNX_NETIF WiFi +#else #include - -#elif defined(KNX_IP_GENERIC) - - -#include - -#ifndef DEBUG_ETHERNET_GENERIC_PORT -#define DEBUG_ETHERNET_GENERIC_PORT Serial -#endif - -#ifndef _ETG_LOGLEVEL_ -#define _ETG_LOGLEVEL_ 1 -#endif - - -#define ETHERNET_USE_RPIPICO true -#include // https://github.com/khoih-prog/Ethernet_Generic - - -#define KNX_NETIF Ethernet - +#define KNX_NETIF WiFi #endif #if USE_KNX_DMA_UART == 1 @@ -146,15 +124,8 @@ public: // unicast bool sendBytesUniCast(uint32_t addr, uint16_t port, uint8_t* buffer, uint16_t len) override; - #if defined(KNX_IP_W5500) || defined(KNX_IP_WIFI) #define UDP_UNICAST _udp protected: WiFiUDP _udp; - #elif defined(KNX_IP_GENERIC) - #define UDP_UNICAST _udp_uni - protected: bool _unicast_socket_setup = false; - protected: EthernetUDP _udp; - protected: EthernetUDP UDP_UNICAST; - #endif protected: IPAddress mcastaddr; protected: uint16_t _port; #endif From 194de33ee2e5e5be3aaf6f68dc6f7cea806c8807 Mon Sep 17 00:00:00 2001 From: Ing-Dom Date: Mon, 22 Jul 2024 23:27:07 +0200 Subject: [PATCH 32/44] update rp2040 plattform version in examples and ci --- examples/knx-demo/platformio-ci.ini | 4 ++-- examples/knx-demo/platformio.ini | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/knx-demo/platformio-ci.ini b/examples/knx-demo/platformio-ci.ini index 0ea2778..b426eb0 100644 --- a/examples/knx-demo/platformio-ci.ini +++ b/examples/knx-demo/platformio-ci.ini @@ -83,8 +83,8 @@ build_flags = ;--- RP2040 ----------------------------------------------- [env:rp2040] framework = arduino -platform = https://github.com/maxgerhardt/platform-raspberrypi.git#182d833 -platform_packages = framework-arduinopico @ https://github.com/earlephilhower/arduino-pico/releases/download/3.6.2/rp2040-3.6.2.zip +platform = https://github.com/maxgerhardt/platform-raspberrypi.git#60d6ae8 +platform_packages = framework-arduinopico @ https://github.com/earlephilhower/arduino-pico/releases/download/3.9.3/rp2040-3.9.3.zip board = rpipico board_build.core = earlephilhower diff --git a/examples/knx-demo/platformio.ini b/examples/knx-demo/platformio.ini index 0db86f5..0de299b 100644 --- a/examples/knx-demo/platformio.ini +++ b/examples/knx-demo/platformio.ini @@ -151,8 +151,8 @@ extra_scripts = ../scripts/stm32rdu.py ;--- RP2040 ----------------------------------------------- [env:rp2040] framework = arduino -platform = https://github.com/maxgerhardt/platform-raspberrypi.git#182d833 -platform_packages = framework-arduinopico @ https://github.com/earlephilhower/arduino-pico/releases/download/3.6.2/rp2040-3.6.2.zip +platform = https://github.com/maxgerhardt/platform-raspberrypi.git#60d6ae8 +platform_packages = framework-arduinopico @ https://github.com/earlephilhower/arduino-pico/releases/download/3.9.3/rp2040-3.9.3.zip board = rpipico board_build.core = earlephilhower ; We consider that the this projects is opened within its project directory From 7f119275eed8d3f1a6a0feedd9d22f4976c50e7a Mon Sep 17 00:00:00 2001 From: Dom Date: Mon, 29 Jul 2024 10:47:41 +0200 Subject: [PATCH 33/44] Better Routing and new Tunneling Support (#26) Support 0x091A IP-Router better, including KNX IP Tunneling and a lot of smaller fixes --- src/esp32_platform.cpp | 34 +- src/esp32_platform.h | 6 +- src/knx/application_layer.cpp | 114 ++- src/knx/application_layer.h | 13 + src/knx/application_program_object.cpp | 4 + src/knx/bau.cpp | 25 + src/knx/bau.h | 11 + src/knx/bau07B0.cpp | 17 +- src/knx/bau07B0.h | 4 +- src/knx/bau091A.cpp | 81 +- src/knx/bau091A.h | 5 +- src/knx/bau27B0.cpp | 4 +- src/knx/bau27B0.h | 2 +- src/knx/bau2920.cpp | 6 +- src/knx/bau2920.h | 2 +- src/knx/bau57B0.cpp | 4 +- src/knx/bau57B0.h | 2 +- src/knx/bau_systemB.cpp | 82 ++ src/knx/bau_systemB.h | 13 +- src/knx/bau_systemB_coupler.cpp | 1 - src/knx/cemi_frame.cpp | 38 +- src/knx/cemi_server.cpp | 502 ++++++---- src/knx/cemi_server.h | 13 + src/knx/config.h | 2 +- src/knx/data_link_layer.cpp | 139 ++- src/knx/data_link_layer.h | 16 +- src/knx/device_object.h | 6 +- src/knx/interface_object.cpp | 14 + src/knx/interface_object.h | 14 +- src/knx/ip_data_link_layer.cpp | 938 +++++++++++++++++- src/knx/ip_data_link_layer.h | 29 +- .../ip_host_protocol_address_information.h | 1 + src/knx/ip_parameter_object.cpp | 5 + src/knx/knx_ip_ch.cpp | 48 + src/knx/knx_ip_ch.h | 28 + src/knx/knx_ip_config_dib.cpp | 91 ++ src/knx/knx_ip_config_dib.h | 28 + src/knx/knx_ip_config_request.cpp | 17 + src/knx/knx_ip_config_request.h | 17 + src/knx/knx_ip_connect_request.cpp | 21 + src/knx/knx_ip_connect_request.h | 19 + src/knx/knx_ip_connect_response.cpp | 45 + src/knx/knx_ip_connect_response.h | 45 + src/knx/knx_ip_crd.cpp | 41 + src/knx/knx_ip_crd.h | 23 + src/knx/knx_ip_cri.cpp | 38 + src/knx/knx_ip_cri.h | 36 + src/knx/knx_ip_description_request.cpp | 13 + src/knx/knx_ip_description_request.h | 15 + src/knx/knx_ip_description_response.cpp | 72 ++ src/knx/knx_ip_description_response.h | 21 + src/knx/knx_ip_dib.h | 3 + src/knx/knx_ip_disconnect_request.cpp | 26 + src/knx/knx_ip_disconnect_request.h | 17 + src/knx/knx_ip_disconnect_response.cpp | 12 + src/knx/knx_ip_disconnect_response.h | 13 + ...knx_ip_extended_device_information_dib.cpp | 42 + .../knx_ip_extended_device_information_dib.h | 19 + src/knx/knx_ip_knx_addresses_dib.cpp | 27 + src/knx/knx_ip_knx_addresses_dib.h | 17 + src/knx/knx_ip_search_request_extended.cpp | 59 ++ src/knx/knx_ip_search_request_extended.h | 26 + src/knx/knx_ip_search_response.cpp | 36 +- src/knx/knx_ip_search_response.h | 1 + src/knx/knx_ip_search_response_extended.cpp | 221 +++++ src/knx/knx_ip_search_response_extended.h | 38 + src/knx/knx_ip_state_request.cpp | 16 + src/knx/knx_ip_state_request.h | 17 + src/knx/knx_ip_state_response.cpp | 26 + src/knx/knx_ip_state_response.h | 13 + src/knx/knx_ip_tunnel_connection.cpp | 19 + src/knx/knx_ip_tunnel_connection.h | 24 + src/knx/knx_ip_tunneling_ack.cpp | 20 + src/knx/knx_ip_tunneling_ack.h | 17 + src/knx/knx_ip_tunneling_info_dib.cpp | 31 + src/knx/knx_ip_tunneling_info_dib.h | 20 + src/knx/knx_ip_tunneling_request.cpp | 26 + src/knx/knx_ip_tunneling_request.h | 19 + src/knx/knx_types.h | 44 + src/knx/memory.cpp | 7 +- src/knx/memory.h | 5 +- src/knx/network_layer_coupler.cpp | 469 ++++++--- src/knx/network_layer_coupler.h | 7 +- src/knx/network_layer_entity.cpp | 7 +- src/knx/network_layer_entity.h | 3 +- src/knx/platform.cpp | 85 ++ src/knx/platform.h | 2 + src/knx/property.h | 2 + src/knx/rf_data_link_layer.cpp | 4 +- src/knx/rf_data_link_layer.h | 2 +- src/knx/router_object.cpp | 155 ++- src/knx/router_object.h | 4 +- src/knx/service_families.h | 15 + src/knx/table_object.cpp | 67 +- src/knx/table_object.h | 12 +- src/knx/tp_frame.h | 6 +- src/knx/tpuart_data_link_layer.cpp | 8 +- src/knx/tpuart_data_link_layer.h | 4 +- src/knx_facade.h | 16 - src/rp2040_arduino_platform.cpp | 11 +- src/rp2040_arduino_platform.h | 5 +- 101 files changed, 3988 insertions(+), 522 deletions(-) create mode 100644 src/knx/knx_ip_ch.cpp create mode 100644 src/knx/knx_ip_ch.h create mode 100644 src/knx/knx_ip_config_dib.cpp create mode 100644 src/knx/knx_ip_config_dib.h create mode 100644 src/knx/knx_ip_config_request.cpp create mode 100644 src/knx/knx_ip_config_request.h create mode 100644 src/knx/knx_ip_connect_request.cpp create mode 100644 src/knx/knx_ip_connect_request.h create mode 100644 src/knx/knx_ip_connect_response.cpp create mode 100644 src/knx/knx_ip_connect_response.h create mode 100644 src/knx/knx_ip_crd.cpp create mode 100644 src/knx/knx_ip_crd.h create mode 100644 src/knx/knx_ip_cri.cpp create mode 100644 src/knx/knx_ip_cri.h create mode 100644 src/knx/knx_ip_description_request.cpp create mode 100644 src/knx/knx_ip_description_request.h create mode 100644 src/knx/knx_ip_description_response.cpp create mode 100644 src/knx/knx_ip_description_response.h create mode 100644 src/knx/knx_ip_disconnect_request.cpp create mode 100644 src/knx/knx_ip_disconnect_request.h create mode 100644 src/knx/knx_ip_disconnect_response.cpp create mode 100644 src/knx/knx_ip_disconnect_response.h create mode 100644 src/knx/knx_ip_extended_device_information_dib.cpp create mode 100644 src/knx/knx_ip_extended_device_information_dib.h create mode 100644 src/knx/knx_ip_knx_addresses_dib.cpp create mode 100644 src/knx/knx_ip_knx_addresses_dib.h create mode 100644 src/knx/knx_ip_search_request_extended.cpp create mode 100644 src/knx/knx_ip_search_request_extended.h create mode 100644 src/knx/knx_ip_search_response_extended.cpp create mode 100644 src/knx/knx_ip_search_response_extended.h create mode 100644 src/knx/knx_ip_state_request.cpp create mode 100644 src/knx/knx_ip_state_request.h create mode 100644 src/knx/knx_ip_state_response.cpp create mode 100644 src/knx/knx_ip_state_response.h create mode 100644 src/knx/knx_ip_tunnel_connection.cpp create mode 100644 src/knx/knx_ip_tunnel_connection.h create mode 100644 src/knx/knx_ip_tunneling_ack.cpp create mode 100644 src/knx/knx_ip_tunneling_ack.h create mode 100644 src/knx/knx_ip_tunneling_info_dib.cpp create mode 100644 src/knx/knx_ip_tunneling_info_dib.h create mode 100644 src/knx/knx_ip_tunneling_request.cpp create mode 100644 src/knx/knx_ip_tunneling_request.h create mode 100644 src/knx/service_families.h diff --git a/src/esp32_platform.cpp b/src/esp32_platform.cpp index 9367f4e..b0d5b03 100644 --- a/src/esp32_platform.cpp +++ b/src/esp32_platform.cpp @@ -99,32 +99,50 @@ bool Esp32Platform::sendBytesMultiCast(uint8_t * buffer, uint16_t len) return true; } -int Esp32Platform::readBytesMultiCast(uint8_t * buffer, uint16_t maxLen) +int Esp32Platform::readBytesMultiCast(uint8_t * buffer, uint16_t maxLen, uint32_t& src_addr, uint16_t& src_port) { int len = _udp.parsePacket(); if (len == 0) return 0; - + if (len > maxLen) { - KNX_DEBUG_SERIAL.printf("udp buffer to small. was %d, needed %d\n", maxLen, len); - fatalError(); + println("Unexpected UDP data packet length - drop packet"); + for (size_t i = 0; i < len; i++) + _udp.read(); + return 0; } _udp.read(buffer, len); - //printHex("-> ", buffer, len); + _remoteIP = _udp.remoteIP(); + _remotePort = _udp.remotePort(); + src_addr = htonl(_remoteIP); + src_port = _remotePort; + + // print("Remote IP: "); + // print(_udp.remoteIP().toString().c_str()); + // printHex("-> ", buffer, len); + return len; } bool Esp32Platform::sendBytesUniCast(uint32_t addr, uint16_t port, uint8_t* buffer, uint16_t len) { IPAddress ucastaddr(htonl(addr)); - println("sendBytesUniCast endPacket fail"); - if(_udp.beginPacket(ucastaddr, port) == 1) { + + if(!addr) + ucastaddr = _remoteIP; + + if(!port) + port = _remotePort; + + if(_udp.beginPacket(ucastaddr, port) == 1) + { _udp.write(buffer, len); if(_udp.endPacket() == 0) println("sendBytesUniCast endPacket fail"); } - else println("sendBytesUniCast beginPacket fail"); + else + println("sendBytesUniCast beginPacket fail"); return true; } diff --git a/src/esp32_platform.h b/src/esp32_platform.h index 318291c..4318fa4 100644 --- a/src/esp32_platform.h +++ b/src/esp32_platform.h @@ -30,7 +30,7 @@ public: void setupMultiCast(uint32_t addr, uint16_t port) override; void closeMultiCast() override; bool sendBytesMultiCast(uint8_t* buffer, uint16_t len) override; - int readBytesMultiCast(uint8_t* buffer, uint16_t maxLen) override; + int readBytesMultiCast(uint8_t* buffer, uint16_t maxLen, uint32_t& src_addr, uint16_t& src_port) override; //unicast bool sendBytesUniCast(uint32_t addr, uint16_t port, uint8_t* buffer, uint16_t len) override; @@ -38,6 +38,10 @@ public: //memory uint8_t* getEepromBuffer(uint32_t size); void commitToEeprom(); + + protected: IPAddress _remoteIP; + protected: uint16_t _remotePort; + private: WiFiUDP _udp; int8_t _rxPin = -1; diff --git a/src/knx/application_layer.cpp b/src/knx/application_layer.cpp index 1e8ccec..21346a6 100644 --- a/src/knx/application_layer.cpp +++ b/src/knx/application_layer.cpp @@ -722,6 +722,38 @@ void ApplicationLayer::propertyDescriptionReadResponse(AckType ack, Priority pri individualSend(ack, hopType, priority, asap, apdu, secCtrl); } +void ApplicationLayer::propertyExtDescriptionReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint16_t objectType, uint16_t objectInstance, uint16_t propertyId, uint16_t propertyIndex, uint8_t descriptionType, bool writeEnable, uint8_t type, + uint16_t maxNumberOfElements, uint8_t access) +{ + CemiFrame frame(16); + APDU& apdu = frame.apdu(); + apdu.type(PropertyExtDescriptionResponse); + uint8_t* data = apdu.data(); + + data[1] = (objectType & 0xff00) >> 8; + data[2] = (objectType & 0x00ff); + + data[3] = (objectInstance & 0x0ff0) >> 4; + data[4] = (objectInstance & 0x000f) << 4 | (propertyId & 0x0f00) >> 8; + data[5] = (propertyId & 0x00ff); + + data[6] = (descriptionType & 0x000f) << 4 | (propertyIndex & 0x0f00) >> 8; + data[7] = (propertyIndex & 0x00ff); + data[8] = 0; // DataPointType ?? + data[9] = 0; // DataPointType ?? + data[10] = 0; // DataPointType ?? + data[11] = 0; // DataPointType ?? + + if (writeEnable) + data[12] |= 0x80; + data[12] |= (type & 0x3f); + + pushWord(maxNumberOfElements & 0xfff, data + 13); + data[15] = access; + individualSend(ack, hopType, priority, asap, apdu, secCtrl); +} + void ApplicationLayer::memoryReadRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint16_t memoryAddress) { @@ -740,6 +772,18 @@ void ApplicationLayer::memoryReadResponse(AckType ack, Priority priority, HopCou memorySend(MemoryResponse, ack, priority, hopType, asap, secCtrl, number, memoryAddress, memoryData); } +void ApplicationLayer::memoryRouterReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t * memoryData) +{ + memoryRouterSend(MemoryRouterReadResponse, ack, priority, hopType, asap, secCtrl, number, memoryAddress, memoryData); +} + +void ApplicationLayer::memoryRoutingTableReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t * memoryData) +{ + memoryRoutingTableSend(RoutingTableReadResponse, ack, priority, hopType, asap, secCtrl, number, memoryAddress, memoryData); +} + void ApplicationLayer::memoryExtReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, ReturnCodes code, uint8_t number, uint32_t memoryAddress, uint8_t * memoryData) { @@ -962,6 +1006,34 @@ void ApplicationLayer::memorySend(ApduType type, AckType ack, Priority priority, individualSend(ack, hopType, priority, asap, apdu, secCtrl); } +void ApplicationLayer::memoryRouterSend(ApduType type, AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t * memoryData) +{ + CemiFrame frame(4 + number); + APDU& apdu = frame.apdu(); + apdu.type(type); + uint8_t* data = apdu.data(); + data[1] |= (number & 0xf); + pushWord(memoryAddress & 0xffff, data + 2); + if (number > 0) + memcpy(data + 4, memoryData, number); + individualSend(ack, hopType, priority, asap, apdu, secCtrl); +} + +void ApplicationLayer::memoryRoutingTableSend(ApduType type, AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t * memoryData) +{ + CemiFrame frame(4 + number); + APDU& apdu = frame.apdu(); + apdu.type(type); + uint8_t* data = apdu.data(); + data[1] |= (number & 0xf); + pushWord(memoryAddress & 0xffff, data + 2); + if (number > 0) + memcpy(data + 4, memoryData, number); + individualSend(ack, hopType, priority, asap, apdu, secCtrl); +} + void ApplicationLayer::userMemorySend(ApduType type, AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint32_t memoryAddress, uint8_t * memoryData) { @@ -1084,6 +1156,17 @@ void ApplicationLayer::individualIndication(HopCountType hopType, Priority prior case PropertyDescriptionRead: _bau.propertyDescriptionReadIndication(priority, hopType, tsap, secCtrl, data[1], data[2], data[3]); break; + case PropertyExtDescriptionRead: + { + ObjectType objectType = (ObjectType)(((data[1] & 0xff) << 8) | (data[2] & 0xff)); + uint16_t objectInstance = ((data[3] & 0xff) << 4) | ((data[4] & 0xf0) >> 4); + uint16_t propertyId = ((data[4] & 0x0f) << 8) | (data[5] & 0xff); + uint8_t descriptionType = (data[6] & 0xf0) >> 4; + uint16_t propertyIndex = ((data[7] & 0x0f) << 8) | (data[8] & 0xff); + + _bau.propertyExtDescriptionReadIndication(priority, hopType, tsap, secCtrl, objectType, objectInstance, propertyId, descriptionType, propertyIndex); + break; + } case PropertyDescriptionResponse: _bau.propertyDescriptionReadAppLayerConfirm(priority, hopType, tsap, secCtrl, data[1], data[2], data[3], (data[4] & 0x80) > 0, data[4] & 0x3f, getWord(data + 5) & 0xfff, data[7]); @@ -1097,8 +1180,30 @@ void ApplicationLayer::individualIndication(HopCountType hopType, Priority prior case MemoryWrite: _bau.memoryWriteIndication(priority, hopType, tsap, secCtrl, data[0] & 0x3f, getWord(data + 1), data + 3); break; - case MemoryExtRead: - { + + // EC + case MemoryRouterWrite: + print("MemoryRouterWrite: "); + _bau.memoryRouterWriteIndication(priority, hopType, tsap, secCtrl, data[1], getWord(data + 2), data + 4); + break; + case MemoryRouterReadResponse: + _bau.memoryRouterReadAppLayerConfirm(priority, hopType, tsap, secCtrl, data[1], getWord(data + 2), data + 4); + break; + case RoutingTableOpen: + println("Received OpenRoutingTable APDU, doing nothing"); + break; + case RoutingTableRead: + _bau.memoryRoutingTableReadIndication(priority, hopType, tsap, secCtrl, data[1], getWord(data + 2)); + break; + case RoutingTableReadResponse: + _bau.memoryRoutingTableReadAppLayerConfirm(priority, hopType, tsap, secCtrl, data[1], getWord(data + 2), data + 4); + break; + case RoutingTableWrite: + _bau.memoryRoutingTableWriteIndication(priority, hopType, tsap, secCtrl, data[1], getWord(data + 2), data + 4); + break; + // end EC + + case MemoryExtRead: { uint8_t number = data[1]; uint32_t memoryAddress = ((data[2] & 0xff) << 16) | ((data[3] & 0xff) << 8) | (data[4] & 0xff); _bau.memoryExtReadIndication(priority, hopType, tsap, secCtrl, number, memoryAddress); @@ -1162,7 +1267,7 @@ void ApplicationLayer::individualIndication(HopCountType hopType, Priority prior } default: print("Individual-indication: unhandled APDU-Type: "); - println(apdu.type()); + apdu.printPDU(); } } @@ -1210,6 +1315,9 @@ void ApplicationLayer::individualConfirm(AckType ack, HopCountType hopType, Prio case PropertyDescriptionRead: _bau.propertyDescriptionReadLocalConfirm(ack, priority, hopType, tsap, secCtrl, data[1], data[2], data[3], status); break; + case PropertyExtDescriptionRead: + _bau.propertyExtDescriptionReadLocalConfirm(ack, priority, hopType, tsap, secCtrl, data[1], data[2], data[3], status); + break; case PropertyDescriptionResponse: _bau.propertyDescriptionReadResponseConfirm(ack, priority, hopType, tsap, secCtrl, data[1], data[2], data[3], (data[4] & 0x80) > 0, data[4] & 0x3f, getWord(data + 5) & 0xfff, data[7], status); diff --git a/src/knx/application_layer.h b/src/knx/application_layer.h index c7593c8..e7f80d1 100644 --- a/src/knx/application_layer.h +++ b/src/knx/application_layer.h @@ -125,10 +125,17 @@ class ApplicationLayer void propertyDescriptionReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t objectIndex, uint8_t propertyId, uint8_t propertyIndex, bool writeEnable, uint8_t type, uint16_t maxNumberOfElements, uint8_t access); + void propertyExtDescriptionReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, + uint16_t objectType, uint16_t objectInstance, uint16_t propertyId, uint16_t propertyIndex, uint8_t descriptionType, bool writeEnable, uint8_t type, + uint16_t maxNumberOfElements, uint8_t access); void memoryReadRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint16_t memoryAddress); void memoryReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint16_t memoryAddress, uint8_t* data); + void memoryRouterReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t* data); + void memoryRoutingTableReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t* data); void memoryExtReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, ReturnCodes code, uint8_t number, uint32_t memoryAddress, uint8_t* data); void memoryExtWriteResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, ReturnCodes code, uint8_t number, @@ -195,6 +202,12 @@ class ApplicationLayer uint16_t objectType, uint8_t objectInstance, uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t* data, uint8_t length); void memorySend(ApduType type, AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint16_t memoryAddress, uint8_t* memoryData); + // Added EC + void memoryRouterSend(ApduType type, AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t* memoryData); + void memoryRoutingTableSend(ApduType type, AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t* memoryData); + // void userMemorySend(ApduType type, AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl& secCtrl, uint8_t number, uint32_t memoryAddress, uint8_t* memoryData); void groupValueSend(ApduType type, AckType ack, uint16_t asap, Priority priority, HopCountType hopType, const SecurityControl& secCtrl, uint8_t* data, uint8_t& dataLength); diff --git a/src/knx/application_program_object.cpp b/src/knx/application_program_object.cpp index 598d0fe..d58061f 100644 --- a/src/knx/application_program_object.cpp +++ b/src/knx/application_program_object.cpp @@ -6,7 +6,11 @@ #include ApplicationProgramObject::ApplicationProgramObject(Memory& memory) +#if MASK_VERSION == 0x091A + : TableObject(memory, 0x0100, 0x0100) +#else : TableObject(memory) +#endif { Property* properties[] = { diff --git a/src/knx/bau.cpp b/src/knx/bau.cpp index d54602f..a4958ff 100644 --- a/src/knx/bau.cpp +++ b/src/knx/bau.cpp @@ -151,10 +151,19 @@ void BusAccessUnit::propertyDescriptionReadLocalConfirm(AckType ack, Priority pr { } +void BusAccessUnit::propertyExtDescriptionReadLocalConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, uint16_t objectIndex, uint8_t propertyId, uint16_t propertyIndex, bool status) +{ +} + void BusAccessUnit::propertyDescriptionReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, uint8_t objectIndex, uint8_t propertyId, uint8_t propertyIndex) { } +void BusAccessUnit::propertyExtDescriptionReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, +uint16_t objectType, uint16_t objectInstance, uint16_t propertyId, uint8_t descriptionType, uint16_t propertyIndex) +{ +} + void BusAccessUnit::propertyDescriptionReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, uint8_t objectIndex, uint8_t propertyId, uint8_t propertyIndex, bool writeEnable, uint8_t type, uint16_t maxNumberOfElements, uint8_t access) { } @@ -191,6 +200,22 @@ void BusAccessUnit::memoryWriteIndication(Priority priority, HopCountType hopTyp { } +void BusAccessUnit::memoryRouterWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, uint8_t number, uint16_t memoryAddress, uint8_t* data) +{ +} +void BusAccessUnit::memoryRouterReadAppLayerConfirm(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, uint8_t number, uint16_t memoryAddress, uint8_t* data) +{ +} +void BusAccessUnit::memoryRoutingTableReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, uint8_t number, uint16_t memoryAddress) +{ +} +void BusAccessUnit::memoryRoutingTableReadAppLayerConfirm(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, uint8_t number, uint16_t memoryAddress, uint8_t* data) +{ +} +void BusAccessUnit::memoryRoutingTableWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, uint8_t number, uint16_t memoryAddress, uint8_t *data) +{ +} + void BusAccessUnit::memoryExtReadLocalConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, uint8_t number, uint32_t memoryAddress, bool status) { } diff --git a/src/knx/bau.h b/src/knx/bau.h index ad26183..44a0833 100644 --- a/src/knx/bau.h +++ b/src/knx/bau.h @@ -73,8 +73,12 @@ class BusAccessUnit uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t* data, uint8_t length, bool confirmed); virtual void propertyDescriptionReadLocalConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, uint8_t objectIndex, uint8_t propertyId, uint8_t propertyIndex, bool status); + virtual void propertyExtDescriptionReadLocalConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, + uint16_t objectIndex, uint8_t propertyId, uint16_t propertyIndex, bool status); virtual void propertyDescriptionReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, uint8_t objectIndex, uint8_t propertyId, uint8_t propertyIndex); + virtual void propertyExtDescriptionReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, + uint16_t objectType, uint16_t objectInstance, uint16_t propertyId, uint8_t descriptionType, uint16_t propertyIndex); virtual void propertyDescriptionReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, uint8_t objectIndex, uint8_t propertyId, uint8_t propertyIndex, bool writeEnable, uint8_t type, uint16_t maxNumberOfElements, uint8_t access); @@ -95,6 +99,13 @@ class BusAccessUnit uint16_t memoryAddress, uint8_t* data, bool status); virtual void memoryWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, uint8_t number, uint16_t memoryAddress, uint8_t* data); + virtual void memoryRouterWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t* data); + virtual void memoryRouterReadAppLayerConfirm(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t* data); + virtual void memoryRoutingTableReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, uint8_t number, uint16_t memoryAddress); + virtual void memoryRoutingTableReadAppLayerConfirm(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, uint8_t number, uint16_t memoryAddress, uint8_t *data); + virtual void memoryRoutingTableWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, uint8_t number, uint16_t memoryAddress, uint8_t *data); virtual void memoryExtReadLocalConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, uint8_t number, uint32_t memoryAddress, bool status); virtual void memoryExtReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, uint8_t number, uint32_t memoryAddress); diff --git a/src/knx/bau07B0.cpp b/src/knx/bau07B0.cpp index c30906b..cfbc1bb 100644 --- a/src/knx/bau07B0.cpp +++ b/src/knx/bau07B0.cpp @@ -10,7 +10,7 @@ using namespace std; Bau07B0::Bau07B0(Platform& platform) : BauSystemBDevice(platform), - _dlLayer(_deviceObj, _netLayer.getInterface(), _platform, (ITpUartCallBacks&) *this, (DataLinkLayerCallbacks*) this), + _dlLayer(_deviceObj, _netLayer.getInterface(), _platform, *this, (ITpUartCallBacks&) *this, (DataLinkLayerCallbacks*) this), DataLinkLayerCallbacks() #ifdef USE_CEMI_SERVER , _cemiServer(*this) @@ -78,7 +78,7 @@ InterfaceObject* Bau07B0::getInterfaceObject(uint8_t idx) } } -InterfaceObject* Bau07B0::getInterfaceObject(ObjectType objectType, uint8_t objectInstance) +InterfaceObject* Bau07B0::getInterfaceObject(ObjectType objectType, uint16_t objectInstance) { // We do not use it right now. // Required for coupler mode as there are multiple router objects for example @@ -128,27 +128,30 @@ void Bau07B0::loop() #endif } -bool Bau07B0::isAckRequired(uint16_t address, bool isGrpAddr) +TPAckType Bau07B0::isAckRequired(uint16_t address, bool isGrpAddr) { if (isGrpAddr) { // ACK for broadcasts if (address == 0) - return true; + return TPAckType::AckReqAck; // is group address in group address table? ACK if yes. - return _addrTable.contains(address); + if(_addrTable.contains(address)) + return TPAckType::AckReqAck; + else + return TPAckType::AckReqNone; } // Also ACK for our own individual address if (address == _deviceObj.individualAddress()) - return true; + return TPAckType::AckReqAck; if (address == 0) { println("Invalid broadcast detected: destination address is 0, but address type is \"individual\""); } - return false; + return TPAckType::AckReqNone; } TpUartDataLinkLayer* Bau07B0::getDataLinkLayer() { diff --git a/src/knx/bau07B0.h b/src/knx/bau07B0.h index b87ca6d..44c51ca 100644 --- a/src/knx/bau07B0.h +++ b/src/knx/bau07B0.h @@ -19,10 +19,10 @@ class Bau07B0 : public BauSystemBDevice, public ITpUartCallBacks, public DataLin TpUartDataLinkLayer* getDataLinkLayer(); protected: InterfaceObject* getInterfaceObject(uint8_t idx); - InterfaceObject* getInterfaceObject(ObjectType objectType, uint8_t objectInstance); + InterfaceObject* getInterfaceObject(ObjectType objectType, uint16_t objectInstance); // For TP1 only - bool isAckRequired(uint16_t address, bool isGrpAddr) override; + TPAckType isAckRequired(uint16_t address, bool isGrpAddr) override; private: TpUartDataLinkLayer _dlLayer; diff --git a/src/knx/bau091A.cpp b/src/knx/bau091A.cpp index 202315f..430f2e6 100644 --- a/src/knx/bau091A.cpp +++ b/src/knx/bau091A.cpp @@ -8,12 +8,17 @@ using namespace std; +/* ToDos +Announce the line status of sec side 03_05_01 4.4.3 +implement PID_COUPLER_SERVICES_CONTROL 03_05_01 4.4.7 +*/ + Bau091A::Bau091A(Platform& platform) : BauSystemBCoupler(platform), - _routerObj(memory()), + _routerObj(memory(), 0x200, 0x2000), // the Filtertable of 0x091A IP Routers is fixed at 0x200 and 0x2000 long _ipParameters(_deviceObj, platform), - _dlLayerPrimary(_deviceObj, _ipParameters, _netLayer.getPrimaryInterface(), _platform, (DataLinkLayerCallbacks*) this), - _dlLayerSecondary(_deviceObj, _netLayer.getSecondaryInterface(), platform, (ITpUartCallBacks&) *this, (DataLinkLayerCallbacks*) this), + _dlLayerPrimary(_deviceObj, _ipParameters, _netLayer.getPrimaryInterface(), _platform, *this, (DataLinkLayerCallbacks*) this), + _dlLayerSecondary(_deviceObj, _netLayer.getSecondaryInterface(), platform, *this, (ITpUartCallBacks&) *this, (DataLinkLayerCallbacks*) this), DataLinkLayerCallbacks() #ifdef USE_CEMI_SERVER , @@ -33,9 +38,14 @@ Bau091A::Bau091A(Platform& platform) #ifdef USE_CEMI_SERVER _cemiServerObject.setMediumTypeAsSupported(DptMedium::KNX_IP); _cemiServerObject.setMediumTypeAsSupported(DptMedium::KNX_TP1); + _cemiServer.dataLinkLayerPrimary(_dlLayerPrimary); _cemiServer.dataLinkLayer(_dlLayerSecondary); // Secondary I/F is the important one! + _dlLayerPrimary.cemiServer(_cemiServer); _dlLayerSecondary.cemiServer(_cemiServer); _memory.addSaveRestore(&_cemiServerObject); + uint8_t count = 1; + uint16_t suppCommModes = 0x0100; + _cemiServerObject.writeProperty(PID_COMM_MODES_SUPPORTED, 1, (uint8_t*)&suppCommModes, count); // set the properties Bit 0 to 1 meaning "LinkLayer supported" #endif _memory.addSaveRestore(&_routerObj); @@ -92,7 +102,7 @@ InterfaceObject* Bau091A::getInterfaceObject(uint8_t idx) } } -InterfaceObject* Bau091A::getInterfaceObject(ObjectType objectType, uint8_t objectInstance) +InterfaceObject* Bau091A::getInterfaceObject(ObjectType objectType, uint16_t objectInstance) { // We do not use it right now. // Required for coupler mode as there are multiple router objects for example @@ -139,6 +149,9 @@ void Bau091A::enabled(bool value) { _dlLayerPrimary.enabled(value); _dlLayerSecondary.enabled(value); + + // ToDo change frame repitition in the TP layer - but default is ok. + //_dlLayerSecondary.setFrameRepetition(3,3); } void Bau091A::loop() @@ -148,23 +161,71 @@ void Bau091A::loop() BauSystemBCoupler::loop(); } -bool Bau091A::isAckRequired(uint16_t address, bool isGrpAddr) +TPAckType Bau091A::isAckRequired(uint16_t address, bool isGrpAddr) { + //only called from TpUartDataLinkLayer + TPAckType ack = TPAckType::AckReqNone; + + uint8_t lcconfig = LCCONFIG::PHYS_FRAME_ROUT | LCCONFIG::PHYS_REPEAT | LCCONFIG::BROADCAST_REPEAT | LCCONFIG::GROUP_IACK_ROUT | LCCONFIG::PHYS_IACK_NORMAL; // default value from spec. in case prop is not availible. + Property* prop_lcconfig = _routerObj.property(PID_SUB_LCCONFIG); + if(lcconfig) + prop_lcconfig->read(lcconfig); + if (isGrpAddr) { // ACK for broadcasts if (address == 0) - return true; + ack = TPAckType::AckReqAck; - // is group address in filter table? ACK if yes. - return _routerObj.isGroupAddressInFilterTable(address); + if(lcconfig & LCCONFIG::GROUP_IACK_ROUT) + // is group address in filter table? ACK if yes, No if not + if(_netLayer.isRoutedGroupAddress(address, 1)) + ack = TPAckType::AckReqAck; + else + ack = TPAckType::AckReqNone; + else + // all are ACKED + ack = TPAckType::AckReqAck; +#ifdef KNX_TUNNELING + if(_dlLayerPrimary.isSentToTunnel(address, isGrpAddr)) + ack = TPAckType::AckReqAck; +#endif } else { - return _netLayer.isRoutedIndividualAddress(address); + if((lcconfig & LCCONFIG::PHYS_IACK) == LCCONFIG::PHYS_IACK_ALL) + ack = TPAckType::AckReqAck; + else if((lcconfig & LCCONFIG::PHYS_IACK) == LCCONFIG::PHYS_IACK_NACK) + ack = TPAckType::AckReqNack; + else + if(_netLayer.isRoutedIndividualAddress(address, 1) || address == _deviceObj.individualAddress()) // Also ACK for our own individual address + ack = TPAckType::AckReqAck; + else + ack = TPAckType::AckReqNone; + +#ifdef KNX_TUNNELING + if(_dlLayerPrimary.isSentToTunnel(address, isGrpAddr)) + ack = TPAckType::AckReqAck; +#endif + } - return false; + return ack; +} + +bool Bau091A::configured() +{ + // _configured is set to true initially, if the device was configured with ETS it will be set to true after restart + + if (!_configured) + return false; + + _configured = _routerObj.loadState() == LS_LOADED; +#ifdef USE_DATASECURE + _configured &= _secIfObj.loadState() == LS_LOADED; +#endif + + return _configured; } IpDataLinkLayer* Bau091A::getPrimaryDataLinkLayer() { diff --git a/src/knx/bau091A.h b/src/knx/bau091A.h index 59b8151..494d2da 100644 --- a/src/knx/bau091A.h +++ b/src/knx/bau091A.h @@ -17,15 +17,16 @@ class Bau091A : public BauSystemBCoupler, public ITpUartCallBacks, public DataLi void loop() override; bool enabled() override; void enabled(bool value) override; + bool configured() override; IpDataLinkLayer* getPrimaryDataLinkLayer(); TpUartDataLinkLayer* getSecondaryDataLinkLayer(); protected: InterfaceObject* getInterfaceObject(uint8_t idx); - InterfaceObject* getInterfaceObject(ObjectType objectType, uint8_t objectInstance); + InterfaceObject* getInterfaceObject(ObjectType objectType, uint16_t objectInstance); // For TP1 only - bool isAckRequired(uint16_t address, bool isGrpAddr) override; + TPAckType isAckRequired(uint16_t address, bool isGrpAddr) override; void doMasterReset(EraseCode eraseCode, uint8_t channel) override; private: diff --git a/src/knx/bau27B0.cpp b/src/knx/bau27B0.cpp index e11cde7..3573202 100644 --- a/src/knx/bau27B0.cpp +++ b/src/knx/bau27B0.cpp @@ -10,7 +10,7 @@ using namespace std; Bau27B0::Bau27B0(Platform& platform) : BauSystemBDevice(platform), - _dlLayer(_deviceObj, _rfMediumObj, _netLayer.getInterface(), _platform) + _dlLayer(_deviceObj, _rfMediumObj, _netLayer.getInterface(), _platform, *this) #ifdef USE_CEMI_SERVER , _cemiServer(*this) #endif @@ -90,7 +90,7 @@ InterfaceObject* Bau27B0::getInterfaceObject(uint8_t idx) } } -InterfaceObject* Bau27B0::getInterfaceObject(ObjectType objectType, uint8_t objectInstance) +InterfaceObject* Bau27B0::getInterfaceObject(ObjectType objectType, uint16_t objectInstance) { // We do not use it right now. // Required for coupler mode as there are multiple router objects for example diff --git a/src/knx/bau27B0.h b/src/knx/bau27B0.h index b5d5e81..d002040 100644 --- a/src/knx/bau27B0.h +++ b/src/knx/bau27B0.h @@ -25,7 +25,7 @@ class Bau27B0 : public BauSystemBDevice RfDataLinkLayer* getDataLinkLayer(); protected: InterfaceObject* getInterfaceObject(uint8_t idx); - InterfaceObject* getInterfaceObject(ObjectType objectType, uint8_t objectInstance); + InterfaceObject* getInterfaceObject(ObjectType objectType, uint16_t objectInstance); void doMasterReset(EraseCode eraseCode, uint8_t channel) override; private: diff --git a/src/knx/bau2920.cpp b/src/knx/bau2920.cpp index 3ed7827..b0ed9b4 100644 --- a/src/knx/bau2920.cpp +++ b/src/knx/bau2920.cpp @@ -14,8 +14,8 @@ Bau2920::Bau2920(Platform& platform) _rtObjPrimary(memory()), _rtObjSecondary(memory()), _rfMediumObject(), - _dlLayerPrimary(_deviceObj, _netLayer.getPrimaryInterface(), _platform, (ITpUartCallBacks&) *this), - _dlLayerSecondary(_deviceObj, _rfMediumObject, _netLayer.getSecondaryInterface(), platform) + _dlLayerPrimary(_deviceObj, _netLayer.getPrimaryInterface(), _platform, *this, (ITpUartCallBacks&) *this), + _dlLayerSecondary(_deviceObj, _rfMediumObject, _netLayer.getSecondaryInterface(), platform, *this) #ifdef USE_CEMI_SERVER , _cemiServer(*this) @@ -97,7 +97,7 @@ InterfaceObject* Bau2920::getInterfaceObject(uint8_t idx) } } -InterfaceObject* Bau2920::getInterfaceObject(ObjectType objectType, uint8_t objectInstance) +InterfaceObject* Bau2920::getInterfaceObject(ObjectType objectType, uint16_t objectInstance) { // We do not use it right now. // Required for coupler mode as there are multiple router objects for example diff --git a/src/knx/bau2920.h b/src/knx/bau2920.h index 6c0bbcb..98552fc 100644 --- a/src/knx/bau2920.h +++ b/src/knx/bau2920.h @@ -26,7 +26,7 @@ class Bau2920 : public BauSystemBCoupler RfDataLinkLayer* getSecondaryDataLinkLayer(); protected: InterfaceObject* getInterfaceObject(uint8_t idx); - InterfaceObject* getInterfaceObject(ObjectType objectType, uint8_t objectInstance); + InterfaceObject* getInterfaceObject(ObjectType objectType, uint16_t objectInstance); void doMasterReset(EraseCode eraseCode, uint8_t channel) override; private: diff --git a/src/knx/bau57B0.cpp b/src/knx/bau57B0.cpp index 2e3b7d2..170bf7e 100644 --- a/src/knx/bau57B0.cpp +++ b/src/knx/bau57B0.cpp @@ -11,7 +11,7 @@ using namespace std; Bau57B0::Bau57B0(Platform& platform) : BauSystemBDevice(platform), _ipParameters(_deviceObj, platform), - _dlLayer(_deviceObj, _ipParameters, _netLayer.getInterface(), _platform, (DataLinkLayerCallbacks*) this), + _dlLayer(_deviceObj, _ipParameters, _netLayer.getInterface(), _platform, *this, (DataLinkLayerCallbacks*) this), DataLinkLayerCallbacks() #ifdef USE_CEMI_SERVER , @@ -85,7 +85,7 @@ InterfaceObject* Bau57B0::getInterfaceObject(uint8_t idx) } } -InterfaceObject* Bau57B0::getInterfaceObject(ObjectType objectType, uint8_t objectInstance) +InterfaceObject* Bau57B0::getInterfaceObject(ObjectType objectType, uint16_t objectInstance) { // We do not use it right now. // Required for coupler mode as there are multiple router objects for example diff --git a/src/knx/bau57B0.h b/src/knx/bau57B0.h index fb437a1..e8e6835 100644 --- a/src/knx/bau57B0.h +++ b/src/knx/bau57B0.h @@ -19,7 +19,7 @@ class Bau57B0 : public BauSystemBDevice, public DataLinkLayerCallbacks IpDataLinkLayer* getDataLinkLayer(); protected: InterfaceObject* getInterfaceObject(uint8_t idx); - InterfaceObject* getInterfaceObject(ObjectType objectType, uint8_t objectInstance); + InterfaceObject* getInterfaceObject(ObjectType objectType, uint16_t objectInstance); void doMasterReset(EraseCode eraseCode, uint8_t channel) override; private: diff --git a/src/knx/bau_systemB.cpp b/src/knx/bau_systemB.cpp index fc1fdf8..61e8b82 100644 --- a/src/knx/bau_systemB.cpp +++ b/src/knx/bau_systemB.cpp @@ -112,6 +112,50 @@ void BauSystemB::deviceDescriptorReadIndication(Priority priority, HopCountType pushWord(_deviceObj.maskVersion(), data); applicationLayer().deviceDescriptorReadResponse(AckRequested, priority, hopType, asap, secCtrl, descriptorType, data); } +void BauSystemB::memoryRouterWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t *data) +{ + print("Writing memory at: "); + print(memoryAddress, HEX); + print(" length: "); + print(number); + print(" data: "); + printHex("=>", data, number); + _memory.writeMemory(memoryAddress, number, data); + if (_deviceObj.verifyMode()) + { + print("Sending Read indication"); + memoryRouterReadIndication(priority, hopType, asap, secCtrl, number, memoryAddress, data); + } +} + +void BauSystemB::memoryRouterReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t *data) +{ + applicationLayer().memoryRouterReadResponse(AckRequested, priority, hopType, asap, secCtrl, number, memoryAddress, data); +} + +void BauSystemB::memoryRoutingTableReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, uint8_t number, uint16_t memoryAddress, uint8_t *data) +{ + applicationLayer().memoryRoutingTableReadResponse(AckRequested, priority, hopType, asap, secCtrl, number, memoryAddress, data); +} +void BauSystemB::memoryRoutingTableReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, uint8_t number, uint16_t memoryAddress) +{ + memoryRoutingTableReadIndication(priority, hopType, asap, secCtrl, number, memoryAddress, _memory.toAbsolute(memoryAddress)); +} + +void BauSystemB::memoryRoutingTableWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, uint8_t number, uint16_t memoryAddress, uint8_t *data) +{ + print("Writing memory at: "); + print(memoryAddress, HEX); + print(" length: "); + print(number); + print(" data: "); + printHex("=>", data, number); + _memory.writeMemory(memoryAddress, number, data); + if (_deviceObj.verifyMode()) + memoryRoutingTableReadIndication(priority, hopType, asap, secCtrl, number, memoryAddress, data); +} void BauSystemB::memoryWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, uint8_t number, uint16_t memoryAddress, uint8_t * data) @@ -215,6 +259,33 @@ void BauSystemB::propertyDescriptionReadIndication(Priority priority, HopCountTy writeEnable, type, numberOfElements, access); } +void BauSystemB::propertyExtDescriptionReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, + uint16_t objectType, uint16_t objectInstance, uint16_t propertyId, uint8_t descriptionType, uint16_t propertyIndex) +{ + uint8_t pid = propertyId; + uint8_t pidx = propertyIndex; + if(propertyId > 0xFF || propertyIndex > 0xFF) + { + println("BauSystemB::propertyExtDescriptionReadIndication: propertyId or Idx > 256 are not supported"); + return; + } + if(descriptionType != 0) + { + println("BauSystemB::propertyExtDescriptionReadIndication: only descriptionType 0 supported"); + return; + } + bool writeEnable = false; + uint8_t type = 0; + uint16_t numberOfElements = 0; + uint8_t access = 0; + InterfaceObject* obj = getInterfaceObject((ObjectType)objectType, objectInstance); + if (obj) + obj->readPropertyDescription(pid, pidx, writeEnable, type, numberOfElements, access); + + applicationLayer().propertyExtDescriptionReadResponse(AckRequested, priority, hopType, asap, secCtrl, objectType, objectInstance, propertyId, propertyIndex, + descriptionType, writeEnable, type, numberOfElements, access); +} + void BauSystemB::propertyValueWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, uint8_t objectIndex, uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t* data, uint8_t length) { @@ -246,6 +317,17 @@ void BauSystemB::propertyValueReadIndication(Priority priority, HopCountType hop { uint8_t size = 0; uint8_t elementCount = numberOfElements; +#ifdef LOG_KNX_PROP + print("propertyValueReadIndication: ObjIdx "); + print(objectIndex); + print(" propId "); + print(propertyId); + print(" num "); + print(numberOfElements); + print(" start "); + print(startIndex); +#endif + InterfaceObject* obj = getInterfaceObject(objectIndex); if (obj) { diff --git a/src/knx/bau_systemB.h b/src/knx/bau_systemB.h index ad57e66..c652c94 100644 --- a/src/knx/bau_systemB.h +++ b/src/knx/bau_systemB.h @@ -51,7 +51,7 @@ class BauSystemB : protected BusAccessUnit protected: virtual ApplicationLayer& applicationLayer() = 0; virtual InterfaceObject* getInterfaceObject(uint8_t idx) = 0; - virtual InterfaceObject* getInterfaceObject(ObjectType objectType, uint8_t objectInstance) = 0; + virtual InterfaceObject* getInterfaceObject(ObjectType objectType, uint16_t objectInstance) = 0; void memoryWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, uint8_t number, uint16_t memoryAddress, uint8_t* data) override; @@ -59,6 +59,15 @@ class BauSystemB : protected BusAccessUnit uint16_t memoryAddress) override; void memoryReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, uint8_t number, uint16_t memoryAddress, uint8_t * data); + void memoryRouterWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t *data); + void memoryRouterReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t *data); + void memoryRoutingTableWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, uint8_t number, + uint16_t memoryAddress, uint8_t *data); + void memoryRoutingTableReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, uint8_t number, uint16_t memoryAddress, uint8_t *data); + void memoryRoutingTableReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, uint8_t number, uint16_t memoryAddress); + // void memoryExtWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, uint8_t number, uint32_t memoryAddress, uint8_t* data) override; void memoryExtReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, uint8_t number, @@ -71,6 +80,8 @@ class BauSystemB : protected BusAccessUnit uint32_t memoryAddress, uint8_t* memoryData) override; void propertyDescriptionReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, uint8_t objectIndex, uint8_t propertyId, uint8_t propertyIndex) override; + void propertyExtDescriptionReadIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, + uint16_t objectType, uint16_t objectInstance, uint16_t propertyId, uint8_t descriptionType, uint16_t propertyIndex) override; void propertyValueWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, uint8_t objectIndex, uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t* data, uint8_t length) override; void propertyValueExtWriteIndication(Priority priority, HopCountType hopType, uint16_t asap, const SecurityControl &secCtrl, ObjectType objectType, uint8_t objectInstance, diff --git a/src/knx/bau_systemB_coupler.cpp b/src/knx/bau_systemB_coupler.cpp index d9e92ce..5a7f12b 100644 --- a/src/knx/bau_systemB_coupler.cpp +++ b/src/knx/bau_systemB_coupler.cpp @@ -17,7 +17,6 @@ BauSystemBCoupler::BauSystemBCoupler(Platform& platform) : _appLayer.transportLayer(_transLayer); _transLayer.networkLayer(_netLayer); _memory.addSaveRestore(&_deviceObj); - _memory.addSaveRestore(&_appProgram); #ifdef USE_DATASECURE _memory.addSaveRestore(&_secIfObj); #endif diff --git a/src/knx/cemi_frame.cpp b/src/knx/cemi_frame.cpp index b58b1f2..50354b8 100644 --- a/src/knx/cemi_frame.cpp +++ b/src/knx/cemi_frame.cpp @@ -6,11 +6,17 @@ /* cEMI Frame Format -+---------+--------+--------+--------+--------+---------+---------+--------+---------+ - | Header | Msg |Add.Info| Ctrl 1 | Ctrl 2 | Source | Dest. | Data | APDU | - | | Code | Length | | | Address | Address | Length | | + +--------+--------+--------+--------+---------+---------+--------+---------+ + | _data | + +--------+--------+--------+--------+---------+---------+--------+---------+ + | LPDU | + +--------+--------+--------+--------+---------+---------+--------+---------+ + | NPDU | +---------+--------+--------+--------+--------+---------+---------+--------+---------+ - 6 bytes 1 byte 1 byte 1 byte 1 byte 2 bytes 2 bytes 1 byte 2 bytes + | Header | Msg |Add.Info| Ctrl 1 | Ctrl 2 | Source | Dest. | Data | TPDU | + | | Code | Length | | | Address | Address | Length | APDU | + +---------+--------+--------+--------+--------+---------+---------+--------+---------+ + 6 bytes 1 byte 1 byte 1 byte 1 byte 2 bytes 2 bytes 1 byte n bytes Header = See below the structure of a cEMI header Message Code = See below. On Appendix A is the list of all existing EMI and cEMI codes @@ -85,11 +91,11 @@ CemiFrame::CemiFrame(uint8_t apduLength) _apdu(_data + APDU_LPDU_DIFF, *this) { _ctrl1 = _data + CEMI_HEADER_SIZE; - _length = 0; memset(_data, 0, apduLength + APDU_LPDU_DIFF); _ctrl1[0] |= Broadcast; _npdu.octetCount(apduLength); + _length = _npdu.length() + NPDU_LPDU_DIFF; } CemiFrame::CemiFrame(const CemiFrame & other) @@ -116,6 +122,7 @@ CemiFrame& CemiFrame::operator=(CemiFrame other) return *this; } + MessageCode CemiFrame::messageCode() const { return (MessageCode)_data[0]; @@ -128,9 +135,7 @@ void CemiFrame::messageCode(MessageCode msgCode) uint16_t CemiFrame::totalLenght() const { - uint16_t tmp = - _npdu.length() + NPDU_LPDU_DIFF; - return tmp; + return _length; } uint16_t CemiFrame::telegramLengthtTP() const @@ -369,14 +374,27 @@ bool CemiFrame::valid() const uint8_t apduLen = _data[_data[1] + NPDU_LPDU_DIFF]; if (_length != 0 && _length != (addInfoLen + apduLen + NPDU_LPDU_DIFF + 2)) + { + print("length issue, length: "); + print(_length); + print(" addInfoLen: "); + print(addInfoLen); + print(" apduLen: "); + print(apduLen); + print(" expected length: "); + println(addInfoLen + apduLen + NPDU_LPDU_DIFF + 2); + printHex("Frame: ", _data, _length, true); + return false; - + } if ((_ctrl1[0] & 0x40) > 0 // Bit 6 has do be 0 || (_ctrl1[1] & 0xF) > 0 // only standard or extended frames || _npdu.octetCount() == 0xFF // not allowed || (_npdu.octetCount() > 15 && frameType() == StandardFrame) - ) + ){ + print("Other issue"); return false; + } return true; } diff --git a/src/knx/cemi_server.cpp b/src/knx/cemi_server.cpp index 983d6dc..3961c61 100644 --- a/src/knx/cemi_server.cpp +++ b/src/knx/cemi_server.cpp @@ -11,10 +11,13 @@ #include CemiServer::CemiServer(BauSystemB& bau) - : _bau(bau), - _usbTunnelInterface(*this, + : _bau(bau) +#ifdef USE_USB + , + _usbTunnelInterface(*this, _bau.deviceObject().maskVersion(), _bau.deviceObject().manufacturerId()) +#endif { // The cEMI server will hand out the device address + 1 to the cEMI client (e.g. ETS), // so that the device and the cEMI client/server connection(tunnel) can operate simultaneously. @@ -26,6 +29,13 @@ void CemiServer::dataLinkLayer(DataLinkLayer& layer) _dataLinkLayer = &layer; } +#ifdef KNX_TUNNELING +void CemiServer::dataLinkLayerPrimary(DataLinkLayer& layer) +{ + _dataLinkLayerPrimary = &layer; +} + +#endif uint16_t CemiServer::clientAddress() const { return _clientAddress; @@ -42,23 +52,34 @@ void CemiServer::dataConfirmationToTunnel(CemiFrame& frame) frame.messageCode(L_data_con); +#ifdef KNX_LOG_TUNNELING print("L_data_con: src: "); print(frame.sourceAddress(), HEX); print(" dst: "); print(frame.destinationAddress(), HEX); printHex(" frame: ", frame.data(), frame.dataLength()); +#endif +#ifdef USE_USB _usbTunnelInterface.sendCemiFrame(frame); +#elif defined(KNX_TUNNELING) + _dataLinkLayerPrimary->dataConfirmationToTunnel(frame); +#endif frame.messageCode(backupMsgCode); } void CemiServer::dataIndicationToTunnel(CemiFrame& frame) { +#ifdef USE_RF bool isRf = _dataLinkLayer->mediumType() == DptMedium::KNX_RF; uint8_t data[frame.dataLength() + (isRf ? 10 : 0)]; +#else + uint8_t data[frame.dataLength()]; +#endif +#ifdef USE_RF if (isRf) { data[0] = L_data_ind; // Message Code @@ -72,237 +93,51 @@ void CemiServer::dataIndicationToTunnel(CemiFrame& frame) } else { +#endif memcpy(&data[0], frame.data(), frame.dataLength()); +#ifdef USE_RF } +#endif CemiFrame tmpFrame(data, sizeof(data)); +#ifdef KNX_LOG_TUNNELING + print("ToTunnel "); print("L_data_ind: src: "); print(tmpFrame.sourceAddress(), HEX); print(" dst: "); print(tmpFrame.destinationAddress(), HEX); printHex(" frame: ", tmpFrame.data(), tmpFrame.dataLength()); +#endif tmpFrame.apdu().type(); +#ifdef USE_USB _usbTunnelInterface.sendCemiFrame(tmpFrame); +#elif defined(KNX_TUNNELING) + _dataLinkLayerPrimary->dataIndicationToTunnel(frame); +#endif } void CemiServer::frameReceived(CemiFrame& frame) { - bool isRf = _dataLinkLayer->mediumType() == DptMedium::KNX_RF; - switch(frame.messageCode()) { case L_data_req: { - // Fill in the cEMI client address if the client sets - // source address to 0. - if(frame.sourceAddress() == 0x0000) - { - frame.sourceAddress(_clientAddress); - } - - if (isRf) - { - // Check if we have additional info for RF - if (((frame.data())[1] == 0x0A) && // Additional info total length: we only handle one additional info of type RF - ((frame.data())[2] == 0x02) && // Additional info type: RF - ((frame.data())[3] == 0x08) ) // Additional info length of type RF: 8 bytes (fixed) - { - frame.rfInfo((frame.data())[4]); - // Use the values provided in the RF additonal info - if ( ((frame.data())[5] != 0x00) || ((frame.data())[6] != 0x00) || ((frame.data())[7] != 0x00) || - ((frame.data())[8] != 0x00) || ((frame.data())[9] != 0x00) || ((frame.data())[10] != 0x00) ) - { - frame.rfSerialOrDoA(&((frame.data())[5])); - } // else leave the nullptr as it is - frame.rfLfn((frame.data())[11]); - } - - // If the cEMI client does not provide a link layer frame number (LFN), - // we use our own counter. - // Note: There is another link layer frame number counter inside the RF data link layer class! - // That counter is solely for the local application! - // If we set a LFN here, the data link layer counter is NOT used! - if (frame.rfLfn() == 0xFF) - { - // Set Data Link Layer Frame Number - frame.rfLfn(_frameNumber); - // Link Layer frame number counts 0..7 - _frameNumber = (_frameNumber + 1) & 0x7; - } - } - - print("L_data_req: src: "); - print(frame.sourceAddress(), HEX); - print(" dst: "); - print(frame.destinationAddress(), HEX); - - printHex(" frame: ", frame.data(), frame.dataLength()); - - _dataLinkLayer->dataRequestFromTunnel(frame); + handleLData(frame); break; } case M_PropRead_req: { - print("M_PropRead_req: "); - - uint16_t objectType; - popWord(objectType, &frame.data()[1]); - uint8_t objectInstance = frame.data()[3]; - uint8_t propertyId = frame.data()[4]; - uint8_t numberOfElements = frame.data()[5] >> 4; - uint16_t startIndex = frame.data()[6] | ((frame.data()[5]&0x0F)<<8); - uint8_t* data = nullptr; - uint32_t dataSize = 0; - - print("ObjType: "); - print(objectType, DEC); - print(" ObjInst: "); - print(objectInstance, DEC); - print(" PropId: "); - print(propertyId, DEC); - print(" NoE: "); - print(numberOfElements, DEC); - print(" startIdx: "); - print(startIndex, DEC); - - // propertyValueRead() allocates memory for the data! Needs to be deleted again! - _bau.propertyValueRead((ObjectType)objectType, objectInstance, propertyId, numberOfElements, startIndex, &data, dataSize); - - // Patch result for device address in device object - // The cEMI server will hand out the device address + 1 to the cEMI client (e.g. ETS), - // so that the device and the cEMI client/server connection(tunnel) can operate simultaneously. - // KNX IP Interfaces which offer multiple simultaneous tunnel connections seem to operate the same way. - // Each tunnel has its own cEMI client address which is based on the main device address. - if (((ObjectType) objectType == OT_DEVICE) && - (propertyId == PID_DEVICE_ADDR) && - (numberOfElements == 1)) - { - data[0] = (uint8_t) (_clientAddress & 0xFF); - } - else if (((ObjectType) objectType == OT_DEVICE) && - (propertyId == PID_SUBNET_ADDR) && - (numberOfElements == 1)) - { - data[0] = (uint8_t) ((_clientAddress >> 8) & 0xFF); - } - - if (data && dataSize && numberOfElements) - { - printHex(" <- data: ", data, dataSize); - println(""); - - // Prepare positive response - uint8_t responseData[7 + dataSize]; - memcpy(responseData, frame.data(), 7); - memcpy(&responseData[7], data, dataSize); - - CemiFrame responseFrame(responseData, sizeof(responseData)); - responseFrame.messageCode(M_PropRead_con); - _usbTunnelInterface.sendCemiFrame(responseFrame); - - delete[] data; - } - else - { - // Prepare negative response - uint8_t responseData[7 + 1]; - memcpy(responseData, frame.data(), sizeof(responseData)); - responseData[7] = Void_DP; // Set cEMI error code - responseData[5] = 0; // Set Number of elements to zero - - printHex(" <- error: ", &responseData[7], 1); - println(""); - - CemiFrame responseFrame(responseData, sizeof(responseData)); - responseFrame.messageCode(M_PropRead_con); - _usbTunnelInterface.sendCemiFrame(responseFrame); - } + handleMPropRead(frame); break; } case M_PropWrite_req: { - print("M_PropWrite_req: "); - - uint16_t objectType; - popWord(objectType, &frame.data()[1]); - uint8_t objectInstance = frame.data()[3]; - uint8_t propertyId = frame.data()[4]; - uint8_t numberOfElements = frame.data()[5] >> 4; - uint16_t startIndex = frame.data()[6] | ((frame.data()[5]&0x0F)<<8); - uint8_t* requestData = &frame.data()[7]; - uint32_t requestDataSize = frame.dataLength() - 7; - - print("ObjType: "); - print(objectType, DEC); - print(" ObjInst: "); - print(objectInstance, DEC); - print(" PropId: "); - print(propertyId, DEC); - print(" NoE: "); - print(numberOfElements, DEC); - print(" startIdx: "); - print(startIndex, DEC); - - printHex(" -> data: ", requestData, requestDataSize); - - // Patch request for device address in device object - if (((ObjectType) objectType == OT_DEVICE) && - (propertyId == PID_DEVICE_ADDR) && - (numberOfElements == 1)) - { - // Temporarily store new cEMI client address in memory - // We also be sent back if the client requests it again - _clientAddress = (_clientAddress & 0xFF00) | requestData[0]; - print("cEMI client address: "); - println(_clientAddress, HEX); - } - else if (((ObjectType) objectType == OT_DEVICE) && - (propertyId == PID_SUBNET_ADDR) && - (numberOfElements == 1)) - { - // Temporarily store new cEMI client address in memory - // We also be sent back if the client requests it again - _clientAddress = (_clientAddress & 0x00FF) | (requestData[0] << 8); - print("cEMI client address: "); - println(_clientAddress, HEX); - } - else - { - _bau.propertyValueWrite((ObjectType)objectType, objectInstance, propertyId, numberOfElements, startIndex, requestData, requestDataSize); - } - - if (numberOfElements) - { - // Prepare positive response - uint8_t responseData[7]; - memcpy(responseData, frame.data(), sizeof(responseData)); - - println(" <- no error"); - - CemiFrame responseFrame(responseData, sizeof(responseData)); - responseFrame.messageCode(M_PropWrite_con); - _usbTunnelInterface.sendCemiFrame(responseFrame); - } - else - { - // Prepare negative response - uint8_t responseData[7 + 1]; - memcpy(responseData, frame.data(), sizeof(responseData)); - responseData[7] = Illegal_Command; // Set cEMI error code - responseData[5] = 0; // Set Number of elements to zero - - printHex(" <- error: ", &responseData[7], 1); - println(""); - - CemiFrame responseFrame(responseData, sizeof(responseData)); - responseFrame.messageCode(M_PropWrite_con); - _usbTunnelInterface.sendCemiFrame(responseFrame); - } + handleMPropWrite(frame); break; } @@ -320,16 +155,7 @@ void CemiServer::frameReceived(CemiFrame& frame) case M_Reset_req: { - println("M_Reset_req: sending M_Reset_ind"); - // A real device reset does not work for USB or KNXNET/IP. - // Thus, M_Reset_ind is NOT mandatory for USB and KNXNET/IP. - // We just save all data to the EEPROM - _bau.writeMemory(); - // Prepare response - uint8_t responseData[1]; - CemiFrame responseFrame(responseData, sizeof(responseData)); - responseFrame.messageCode(M_Reset_ind); - _usbTunnelInterface.sendCemiFrame(responseFrame); + handleMReset(frame); break; } @@ -347,9 +173,263 @@ void CemiServer::frameReceived(CemiFrame& frame) } } +void CemiServer::handleLData(CemiFrame& frame) +{ + // Fill in the cEMI client address if the client sets + // source address to 0. +#ifndef KNX_TUNNELING + //We already set the correct IA + if(frame.sourceAddress() == 0x0000) + { + frame.sourceAddress(_clientAddress); + } +#endif + +#ifdef USE_RF + if (_dataLinkLayer->mediumType() == DptMedium::KNX_RF) + { + // Check if we have additional info for RF + if (((frame.data())[1] == 0x0A) && // Additional info total length: we only handle one additional info of type RF + ((frame.data())[2] == 0x02) && // Additional info type: RF + ((frame.data())[3] == 0x08) ) // Additional info length of type RF: 8 bytes (fixed) + { + frame.rfInfo((frame.data())[4]); + // Use the values provided in the RF additonal info + if ( ((frame.data())[5] != 0x00) || ((frame.data())[6] != 0x00) || ((frame.data())[7] != 0x00) || + ((frame.data())[8] != 0x00) || ((frame.data())[9] != 0x00) || ((frame.data())[10] != 0x00) ) + { + frame.rfSerialOrDoA(&((frame.data())[5])); + } // else leave the nullptr as it is + frame.rfLfn((frame.data())[11]); + } + + // If the cEMI client does not provide a link layer frame number (LFN), + // we use our own counter. + // Note: There is another link layer frame number counter inside the RF data link layer class! + // That counter is solely for the local application! + // If we set a LFN here, the data link layer counter is NOT used! + if (frame.rfLfn() == 0xFF) + { + // Set Data Link Layer Frame Number + frame.rfLfn(_frameNumber); + // Link Layer frame number counts 0..7 + _frameNumber = (_frameNumber + 1) & 0x7; + } + } +#endif + +#ifdef KNX_LOG_TUNNELING + print("L_data_req: src: "); + print(frame.sourceAddress(), HEX); + print(" dst: "); + print(frame.destinationAddress(), HEX); + printHex(" frame: ", frame.data(), frame.dataLength()); +#endif + _dataLinkLayer->dataRequestFromTunnel(frame); +} + +void CemiServer::handleMPropRead(CemiFrame& frame) +{ +#ifdef KNX_LOG_TUNNELING + print("M_PropRead_req: "); +#endif + + uint16_t objectType; + popWord(objectType, &frame.data()[1]); + uint8_t objectInstance = frame.data()[3]; + uint8_t propertyId = frame.data()[4]; + uint8_t numberOfElements = frame.data()[5] >> 4; + uint16_t startIndex = frame.data()[6] | ((frame.data()[5]&0x0F)<<8); + uint8_t* data = nullptr; + uint32_t dataSize = 0; + +#ifdef KNX_LOG_TUNNELING + print("ObjType: "); + print(objectType, DEC); + print(" ObjInst: "); + print(objectInstance, DEC); + print(" PropId: "); + print(propertyId, DEC); + print(" NoE: "); + print(numberOfElements, DEC); + print(" startIdx: "); + print(startIndex, DEC); +#endif + + // propertyValueRead() allocates memory for the data! Needs to be deleted again! + _bau.propertyValueRead((ObjectType)objectType, objectInstance, propertyId, numberOfElements, startIndex, &data, dataSize); + + // Patch result for device address in device object + // The cEMI server will hand out the device address + 1 to the cEMI client (e.g. ETS), + // so that the device and the cEMI client/server connection(tunnel) can operate simultaneously. + // KNX IP Interfaces which offer multiple simultaneous tunnel connections seem to operate the same way. + // Each tunnel has its own cEMI client address which is based on the main device address. + if (((ObjectType) objectType == OT_DEVICE) && + (propertyId == PID_DEVICE_ADDR) && + (numberOfElements == 1)) + { + data[0] = (uint8_t) (_clientAddress & 0xFF); + } + else if (((ObjectType) objectType == OT_DEVICE) && + (propertyId == PID_SUBNET_ADDR) && + (numberOfElements == 1)) + { + data[0] = (uint8_t) ((_clientAddress >> 8) & 0xFF); + } + + if (data && dataSize && numberOfElements) + { +#ifdef KNX_LOG_TUNNELING + printHex(" <- data: ", data, dataSize); +#endif + + // Prepare positive response + uint8_t responseData[7 + dataSize]; + memcpy(responseData, frame.data(), 7); + memcpy(&responseData[7], data, dataSize); + + CemiFrame responseFrame(responseData, sizeof(responseData)); + responseFrame.messageCode(M_PropRead_con); +#ifdef USE_USB + _usbTunnelInterface.sendCemiFrame(responseFrame); +#elif defined(KNX_TUNNELING) + _dataLinkLayerPrimary->dataRequestToTunnel(responseFrame); +#endif + delete[] data; + } + else + { + // Prepare negative response + uint8_t responseData[7 + 1]; + memcpy(responseData, frame.data(), sizeof(responseData)); + responseData[7] = Void_DP; // Set cEMI error code + responseData[5] = 0; // Set Number of elements to zero + + printHex(" <- error: ", &responseData[7], 1); + println(""); + + CemiFrame responseFrame(responseData, sizeof(responseData)); + responseFrame.messageCode(M_PropRead_con); +#ifdef USE_USB + _usbTunnelInterface.sendCemiFrame(responseFrame); +#elif defined(KNX_TUNNELING) + _dataLinkLayerPrimary->dataRequestToTunnel(responseFrame); +#endif + } +} + +void CemiServer::handleMPropWrite(CemiFrame& frame) +{ + print("M_PropWrite_req: "); + + uint16_t objectType; + popWord(objectType, &frame.data()[1]); + uint8_t objectInstance = frame.data()[3]; + uint8_t propertyId = frame.data()[4]; + uint8_t numberOfElements = frame.data()[5] >> 4; + uint16_t startIndex = frame.data()[6] | ((frame.data()[5]&0x0F)<<8); + uint8_t* requestData = &frame.data()[7]; + uint32_t requestDataSize = frame.dataLength() - 7; + + print("ObjType: "); + print(objectType, DEC); + print(" ObjInst: "); + print(objectInstance, DEC); + print(" PropId: "); + print(propertyId, DEC); + print(" NoE: "); + print(numberOfElements, DEC); + print(" startIdx: "); + print(startIndex, DEC); + + printHex(" -> data: ", requestData, requestDataSize); + + // Patch request for device address in device object + if (((ObjectType) objectType == OT_DEVICE) && + (propertyId == PID_DEVICE_ADDR) && + (numberOfElements == 1)) + { + // Temporarily store new cEMI client address in memory + // We also be sent back if the client requests it again + _clientAddress = (_clientAddress & 0xFF00) | requestData[0]; + print("cEMI client address: "); + println(_clientAddress, HEX); + } + else if (((ObjectType) objectType == OT_DEVICE) && + (propertyId == PID_SUBNET_ADDR) && + (numberOfElements == 1)) + { + // Temporarily store new cEMI client address in memory + // We also be sent back if the client requests it again + _clientAddress = (_clientAddress & 0x00FF) | (requestData[0] << 8); + print("cEMI client address: "); + println(_clientAddress, HEX); + } + else + { + _bau.propertyValueWrite((ObjectType)objectType, objectInstance, propertyId, numberOfElements, startIndex, requestData, requestDataSize); + } + + if (numberOfElements) + { + // Prepare positive response + uint8_t responseData[7]; + memcpy(responseData, frame.data(), sizeof(responseData)); + + println(" <- no error"); + + CemiFrame responseFrame(responseData, sizeof(responseData)); + responseFrame.messageCode(M_PropWrite_con); +#ifdef USE_USB + _usbTunnelInterface.sendCemiFrame(responseFrame); +#elif defined(KNX_TUNNELING) + _dataLinkLayerPrimary->dataRequestToTunnel(responseFrame); +#endif + } + else + { + // Prepare negative response + uint8_t responseData[7 + 1]; + memcpy(responseData, frame.data(), sizeof(responseData)); + responseData[7] = Illegal_Command; // Set cEMI error code + responseData[5] = 0; // Set Number of elements to zero + + printHex(" <- error: ", &responseData[7], 1); + println(""); + + CemiFrame responseFrame(responseData, sizeof(responseData)); + responseFrame.messageCode(M_PropWrite_con); +#ifdef USE_USB + _usbTunnelInterface.sendCemiFrame(responseFrame); +#elif defined(KNX_TUNNELING) + _dataLinkLayerPrimary->dataRequestToTunnel(responseFrame); +#endif + } +} + +void CemiServer::handleMReset(CemiFrame& frame) +{ + println("M_Reset_req: sending M_Reset_ind"); + // A real device reset does not work for USB or KNXNET/IP. + // Thus, M_Reset_ind is NOT mandatory for USB and KNXNET/IP. + // We just save all data to the EEPROM + _bau.writeMemory(); + // Prepare response + uint8_t responseData[1]; + CemiFrame responseFrame(responseData, sizeof(responseData)); + responseFrame.messageCode(M_Reset_ind); +#ifdef USE_USB + _usbTunnelInterface.sendCemiFrame(responseFrame); +#elif defined(KNX_TUNNELING) + _dataLinkLayerPrimary->dataRequestToTunnel(responseFrame); +#endif +} + void CemiServer::loop() { +#ifdef USE_USB _usbTunnelInterface.loop(); +#endif } #endif diff --git a/src/knx/cemi_server.h b/src/knx/cemi_server.h index b44e1cf..f97ead8 100644 --- a/src/knx/cemi_server.h +++ b/src/knx/cemi_server.h @@ -29,6 +29,9 @@ class CemiServer CemiServer(BauSystemB& bau); void dataLinkLayer(DataLinkLayer& layer); +#ifdef KNX_TUNNELING + void dataLinkLayerPrimary(DataLinkLayer& layer); +#endif // from data link layer // Only L_Data service @@ -47,9 +50,19 @@ class CemiServer uint16_t _clientAddress = 0; uint8_t _frameNumber = 0; + void handleLData(CemiFrame& frame); + void handleMPropRead(CemiFrame& frame); + void handleMPropWrite(CemiFrame& frame); + void handleMReset(CemiFrame& frame); + DataLinkLayer* _dataLinkLayer = nullptr; +#ifdef KNX_TUNNELING + DataLinkLayer* _dataLinkLayerPrimary = nullptr; +#endif BauSystemB& _bau; +#ifdef USE_USB UsbTunnelInterface _usbTunnelInterface; +#endif }; #endif \ No newline at end of file diff --git a/src/knx/config.h b/src/knx/config.h index f937510..a7a0ada 100644 --- a/src/knx/config.h +++ b/src/knx/config.h @@ -52,7 +52,7 @@ // cEMI options //#define USE_USB //#define USE_CEMI_SERVER -#ifdef USE_USB +#if defined(USE_USB) || defined(KNX_TUNNELING) #define USE_CEMI_SERVER #endif diff --git a/src/knx/data_link_layer.cpp b/src/knx/data_link_layer.cpp index be9d6fa..e111566 100644 --- a/src/knx/data_link_layer.cpp +++ b/src/knx/data_link_layer.cpp @@ -18,8 +18,8 @@ void DataLinkLayerCallbacks::setActivityCallback(ActivityCallback activityCallba _activityCallback = activityCallback; } -DataLinkLayer::DataLinkLayer(DeviceObject& devObj, NetworkLayerEntity& netLayerEntity, Platform& platform) : - _deviceObject(devObj), _networkLayerEntity(netLayerEntity), _platform(platform) +DataLinkLayer::DataLinkLayer(DeviceObject& devObj, NetworkLayerEntity& netLayerEntity, Platform& platform, BusAccessUnit& busAccessUnit) : + _deviceObject(devObj), _networkLayerEntity(netLayerEntity), _platform(platform), _bau(busAccessUnit) { #ifdef KNX_ACTIVITYCALLBACK _netIndex = netLayerEntity.getEntityIndex(); @@ -33,15 +33,59 @@ void DataLinkLayer::cemiServer(CemiServer& cemiServer) _cemiServer = &cemiServer; } +#ifdef KNX_TUNNELING +void DataLinkLayer::dataRequestToTunnel(CemiFrame& frame) +{ + println("default dataRequestToTunnel"); +} + +void DataLinkLayer::dataConfirmationToTunnel(CemiFrame& frame) +{ + println("default dataConfirmationToTunnel"); +} + +void DataLinkLayer::dataIndicationToTunnel(CemiFrame& frame) +{ + println("default dataIndicationToTunnel"); +} + +bool DataLinkLayer::isTunnelAddress(uint16_t addr) +{ + println("default IsTunnelAddress"); + return false; +} +#endif + void DataLinkLayer::dataRequestFromTunnel(CemiFrame& frame) { _cemiServer->dataConfirmationToTunnel(frame); frame.messageCode(L_data_ind); - // Send to local stack + // Send to local stack ( => cemiServer for potential other tunnel and network layer for routing) frameReceived(frame); +#ifdef KNX_TUNNELING + // TunnelOpti + // Optimize performance when receiving unicast data over tunnel wich is not meant to be used on the physical TP line + // dont send to knx when + // frame is individual adressed AND + // destionation == PA of Tunnel-Server OR + // destination == PA of a Tunnel OR (TODO) + // destination is not the TP/secondary line/segment but IP/primary (TODO) + + if(frame.addressType() == AddressType::IndividualAddress) + { + if(frame.destinationAddress() == _deviceObject.individualAddress()) + return; + if(isRoutedPA(frame.destinationAddress())) + return; + if(isTunnelingPA(frame.destinationAddress())) + return; + } + +#endif + // Send to KNX medium sendFrame(frame); } @@ -111,12 +155,24 @@ void DataLinkLayer::frameReceived(CemiFrame& frame) #ifdef USE_CEMI_SERVER // Do not send our own message back to the tunnel +#ifdef KNX_TUNNELING + //we dont need to check it here + // send inbound frames to the tunnel if we are the secondary (TP) interface + if( _networkLayerEntity.getEntityIndex() == 1) + _cemiServer->dataIndicationToTunnel(frame); +#else if (frame.sourceAddress() != _cemiServer->clientAddress()) { _cemiServer->dataIndicationToTunnel(frame); } +#endif #endif + // print("Frame received destination: "); + // print(destination, 16); + // println(); + // print("frameReceived: frame valid? :"); + // println(npdu.frame().valid() ? "true" : "false"); if (source == ownAddr) _deviceObject.individualAddressDuplication(true); @@ -133,15 +189,17 @@ void DataLinkLayer::frameReceived(CemiFrame& frame) } } -bool DataLinkLayer::sendTelegram(NPDU & npdu, AckType ack, uint16_t destinationAddr, AddressType addrType, uint16_t sourceAddr, FrameFormat format, Priority priority, SystemBroadcast systemBroadcast) +bool DataLinkLayer::sendTelegram(NPDU & npdu, AckType ack, uint16_t destinationAddr, AddressType addrType, uint16_t sourceAddr, FrameFormat format, Priority priority, SystemBroadcast systemBroadcast, bool doNotRepeat) { CemiFrame& frame = npdu.frame(); + // print("Send telegram frame valid ?: "); + // println(frame.valid()?"true":"false"); frame.messageCode(L_data_ind); frame.destinationAddress(destinationAddr); frame.sourceAddress(sourceAddr); frame.addressType(addrType); frame.priority(priority); - frame.repetition(RepetitionAllowed); + frame.repetition(doNotRepeat?NoRepitiion:RepetitionAllowed); frame.systemBroadcast(systemBroadcast); if (npdu.octetCount() <= 15) @@ -162,22 +220,45 @@ bool DataLinkLayer::sendTelegram(NPDU & npdu, AckType ack, uint16_t destinationA // frame.apdu().printPDU(); // } + bool sendTheFrame = true; + bool success = true; + +#ifdef KNX_TUNNELING + // TunnelOpti + // Optimize performance when sending unicast data over tunnel wich is not meant to be used on the physical TP line + // dont send to knx when + // a) we are the secondary interface (e.g. TP) AND + // b) destination == PA of a Tunnel (TODO) + + if(_networkLayerEntity.getEntityIndex() == 1 && addrType == AddressType::IndividualAddress) // don't send to tp if we are the secondary (TP) interface AND the destination is a tunnel-PA + { + if(isTunnelingPA(destinationAddr)) + sendTheFrame = false; + } +#endif + // The data link layer might be an open media link layer // and will setup rfSerialOrDoA, rfInfo and rfLfn that we also // have to send through the cEMI server tunnel // Thus, reuse the modified cEMI frame as "frame" is only passed by reference here! - bool success = sendFrame(frame); + if(sendTheFrame) + success = sendFrame(frame); #ifdef USE_CEMI_SERVER CemiFrame tmpFrame(frame.data(), frame.totalLenght()); // We can just copy the pointer for rfSerialOrDoA as sendFrame() sets // a pointer to const uint8_t data in either device object (serial) or // RF medium object (domain address) + +#ifdef USE_RF tmpFrame.rfSerialOrDoA(frame.rfSerialOrDoA()); tmpFrame.rfInfo(frame.rfInfo()); tmpFrame.rfLfn(frame.rfLfn()); +#endif tmpFrame.confirm(ConfirmNoError); - _cemiServer->dataIndicationToTunnel(tmpFrame); + + if(_networkLayerEntity.getEntityIndex() == 1) // only send to tunnel if we are the secondary (TP) interface + _cemiServer->dataIndicationToTunnel(tmpFrame); #endif return success; @@ -188,4 +269,48 @@ uint8_t* DataLinkLayer::frameData(CemiFrame& frame) return frame._data; } +#ifdef KNX_TUNNELING +bool DataLinkLayer::isTunnelingPA(uint16_t pa) +{ + uint8_t num = KNX_TUNNELING; + uint32_t len = 0; + uint8_t* data = nullptr; + _bau.propertyValueRead(OT_IP_PARAMETER, 0, PID_ADDITIONAL_INDIVIDUAL_ADDRESSES, num, 1, &data, len); + //printHex("isTunnelingPA, PID_ADDITIONAL_INDIVIDUAL_ADDRESSES: ", *data, len); + if(len != KNX_TUNNELING * 2) + { + println("Tunnel PAs unkwnown"); + if(data != nullptr) + delete[] data; + return false; + } + for(uint8_t i = 0; i < KNX_TUNNELING; i++) + { + uint16_t tunnelpa; + popWord(tunnelpa, (data)+i*2); + if(pa == tunnelpa) + { + if(data != nullptr) + delete[] data; + return true; + } + } + if(data != nullptr) + delete[] data; + return false; +} + +bool DataLinkLayer::isRoutedPA(uint16_t pa) +{ + uint16_t ownpa = _deviceObject.individualAddress(); + uint16_t own_sm; + + if ((ownpa & 0x0F00) == 0x0) + own_sm = 0xF000; + else + own_sm = 0xFF00; + + return (pa & own_sm) != ownpa; +} +#endif diff --git a/src/knx/data_link_layer.h b/src/knx/data_link_layer.h index 817078e..b544fb0 100644 --- a/src/knx/data_link_layer.h +++ b/src/knx/data_link_layer.h @@ -7,6 +7,7 @@ #include "knx_types.h" #include "network_layer_entity.h" #include "cemi_server.h" +#include "bau.h" class Platform; @@ -26,12 +27,18 @@ class DataLinkLayer { public: DataLinkLayer(DeviceObject& devObj, NetworkLayerEntity& netLayerEntity, - Platform& platform); + Platform& platform, BusAccessUnit& busAccessUnit); #ifdef USE_CEMI_SERVER // from tunnel void cemiServer(CemiServer& cemiServer); void dataRequestFromTunnel(CemiFrame& frame); +#ifdef KNX_TUNNELING + virtual void dataRequestToTunnel(CemiFrame& frame); + virtual void dataConfirmationToTunnel(CemiFrame& frame); + virtual void dataIndicationToTunnel(CemiFrame& frame); + virtual bool isTunnelAddress(uint16_t addr); +#endif #endif // from network layer @@ -46,16 +53,21 @@ class DataLinkLayer protected: void frameReceived(CemiFrame& frame); void dataConReceived(CemiFrame& frame, bool success); - bool sendTelegram(NPDU& npdu, AckType ack, uint16_t destinationAddr, AddressType addrType, uint16_t sourceAddr, FrameFormat format, Priority priority, SystemBroadcast systemBroadcast); + bool sendTelegram(NPDU& npdu, AckType ack, uint16_t destinationAddr, AddressType addrType, uint16_t sourceAddr, FrameFormat format, Priority priority, SystemBroadcast systemBroadcast, bool doNotRepeat = false); virtual bool sendFrame(CemiFrame& frame) = 0; uint8_t* frameData(CemiFrame& frame); DeviceObject& _deviceObject; NetworkLayerEntity& _networkLayerEntity; Platform& _platform; + BusAccessUnit& _bau; #ifdef USE_CEMI_SERVER CemiServer* _cemiServer; #endif #ifdef KNX_ACTIVITYCALLBACK uint8_t _netIndex = 0; #endif +#ifdef KNX_TUNNELING + bool isTunnelingPA(uint16_t pa); + bool isRoutedPA(uint16_t pa); +#endif }; diff --git a/src/knx/device_object.h b/src/knx/device_object.h index 10f171d..744965a 100644 --- a/src/knx/device_object.h +++ b/src/knx/device_object.h @@ -43,5 +43,9 @@ public: uint8_t defaultHopCount(); private: uint8_t _prgMode = 0; - uint16_t _ownAddress = 65535; // 15.15.255; +#if MASK_VERSION == 0x091A || MASK_VERSION == 0x2920 + uint16_t _ownAddress = 0xFF00; // 15.15.0; couplers have 15.15.0 as default PA +#else + uint16_t _ownAddress = 0xFFFF; // 15.15.255; +#endif }; diff --git a/src/knx/interface_object.cpp b/src/knx/interface_object.cpp index 0789992..19c58b6 100644 --- a/src/knx/interface_object.cpp +++ b/src/knx/interface_object.cpp @@ -60,6 +60,20 @@ void InterfaceObject::masterReset(EraseCode eraseCode, uint8_t channel) // However, for the time being we provide an empty default implementation } +void InterfaceObject::readPropertyLength(PropertyID id, uint16_t &length) +{ + uint8_t count = 1; + uint16_t propval = 0; + readProperty(id, 0, count, (uint8_t*)&propval); + + if(count == 0) + { + length = 0; + return; + } + length = ntohs(propval); +} + void InterfaceObject::readProperty(PropertyID id, uint16_t start, uint8_t& count, uint8_t* data) { Property* prop = property(id); diff --git a/src/knx/interface_object.h b/src/knx/interface_object.h index 47b1124..0cfa5b1 100644 --- a/src/knx/interface_object.h +++ b/src/knx/interface_object.h @@ -4,6 +4,7 @@ #include "property.h" #include "save_restore.h" #include "knx_types.h" +#include "bits.h" /** Enum for the type of an interface object. See Section 2.2 of knx:3/7/3 */ enum ObjectType @@ -54,7 +55,10 @@ enum ObjectType OT_SECURITY = 17, /** RF Medium Object */ - OT_RF_MEDIUM = 19 + OT_RF_MEDIUM = 19, + + /** Dummy so this enum is 16bit */ + OT_DUMMY = 0xFFFF }; /** @@ -67,6 +71,14 @@ class InterfaceObject : public SaveRestore * Destructor */ virtual ~InterfaceObject(); + /** + * Read length of a property of the interface object. See section 4.8.4.2 of @cite knx:3/4/1. + * + * @param id id of the property to read + * + * @param[out] length length of the requested property + */ + virtual void readPropertyLength(PropertyID id, uint16_t &length); /** * Read a property of the interface object. See section 4.8.4.2 of @cite knx:3/4/1. * diff --git a/src/knx/ip_data_link_layer.cpp b/src/knx/ip_data_link_layer.cpp index 6219b09..3fe45b9 100644 --- a/src/knx/ip_data_link_layer.cpp +++ b/src/knx/ip_data_link_layer.cpp @@ -9,6 +9,22 @@ #include "knx_ip_routing_indication.h" #include "knx_ip_search_request.h" #include "knx_ip_search_response.h" +#include "knx_ip_search_request_extended.h" +#include "knx_ip_search_response_extended.h" +#include "knx_facade.h" +#ifdef KNX_TUNNELING +#include "knx_ip_connect_request.h" +#include "knx_ip_connect_response.h" +#include "knx_ip_state_request.h" +#include "knx_ip_state_response.h" +#include "knx_ip_disconnect_request.h" +#include "knx_ip_disconnect_response.h" +#include "knx_ip_tunneling_request.h" +#include "knx_ip_tunneling_ack.h" +#include "knx_ip_description_request.h" +#include "knx_ip_description_response.h" +#include "knx_ip_config_request.h" +#endif #include #include @@ -19,7 +35,7 @@ #define MIN_LEN_CEMI 10 IpDataLinkLayer::IpDataLinkLayer(DeviceObject& devObj, IpParameterObject& ipParam, - NetworkLayerEntity &netLayerEntity, Platform& platform, DataLinkLayerCallbacks* dllcb) : DataLinkLayer(devObj, netLayerEntity, platform), _ipParameters(ipParam), _dllcb(dllcb) + NetworkLayerEntity &netLayerEntity, Platform& platform, BusAccessUnit& busAccessUnit, DataLinkLayerCallbacks* dllcb) : DataLinkLayer(devObj, netLayerEntity, platform, busAccessUnit), _ipParameters(ipParam), _dllcb(dllcb) { } @@ -38,13 +54,241 @@ bool IpDataLinkLayer::sendFrame(CemiFrame& frame) return success; } +#ifdef KNX_TUNNELING +void IpDataLinkLayer::dataRequestToTunnel(CemiFrame& frame) +{ + if(frame.addressType() == AddressType::GroupAddress) + { + for(int i = 0; i < KNX_TUNNELING; i++) + if(tunnels[i].ChannelId != 0 && tunnels[i].IndividualAddress == frame.sourceAddress()) + sendFrameToTunnel(&tunnels[i], frame); + //TODO check if source is from tunnel + return; + } + + KnxIpTunnelConnection *tun = nullptr; + for(int i = 0; i < KNX_TUNNELING; i++) + { + if(tunnels[i].IndividualAddress == frame.sourceAddress()) + continue; + + if(tunnels[i].IndividualAddress == frame.destinationAddress()) + { + tun = &tunnels[i]; + break; + } + } + + if(tun == nullptr) + { + for(int i = 0; i < KNX_TUNNELING; i++) + { + if(tunnels[i].IsConfig) + { +#ifdef KNX_LOG_TUNNELING + println("Found config Channel"); +#endif + tun = &tunnels[i]; + break; + } + } + } + + if(tun == nullptr) + { +#ifdef KNX_LOG_TUNNELING + print("Found no Tunnel for IA: "); + println(frame.destinationAddress(), 16); +#endif + return; + } + + sendFrameToTunnel(tun, frame); +} + +void IpDataLinkLayer::dataConfirmationToTunnel(CemiFrame& frame) +{ + if(frame.addressType() == AddressType::GroupAddress) + { + for(int i = 0; i < KNX_TUNNELING; i++) + if(tunnels[i].ChannelId != 0 && tunnels[i].IndividualAddress == frame.sourceAddress()) + sendFrameToTunnel(&tunnels[i], frame); + //TODO check if source is from tunnel + return; + } + + KnxIpTunnelConnection *tun = nullptr; + for(int i = 0; i < KNX_TUNNELING; i++) + { + if(tunnels[i].IndividualAddress == frame.destinationAddress()) + continue; + + if(tunnels[i].IndividualAddress == frame.sourceAddress()) + { + tun = &tunnels[i]; + break; + } + } + + if(tun == nullptr) + { + for(int i = 0; i < KNX_TUNNELING; i++) + { + if(tunnels[i].IsConfig) + { +#ifdef KNX_LOG_TUNNELING + println("Found config Channel"); +#endif + tun = &tunnels[i]; + break; + } + } + } + + if(tun == nullptr) + { +#ifdef KNX_LOG_TUNNELING + print("Found no Tunnel for IA: "); + println(frame.destinationAddress(), 16); +#endif + return; + } + + sendFrameToTunnel(tun, frame); +} + +void IpDataLinkLayer::dataIndicationToTunnel(CemiFrame& frame) +{ + if(frame.addressType() == AddressType::GroupAddress) + { + for(int i = 0; i < KNX_TUNNELING; i++) + if(tunnels[i].ChannelId != 0 && tunnels[i].IndividualAddress != frame.sourceAddress()) + sendFrameToTunnel(&tunnels[i], frame); + return; + } + + KnxIpTunnelConnection *tun = nullptr; + for(int i = 0; i < KNX_TUNNELING; i++) + { + if(tunnels[i].ChannelId == 0 || tunnels[i].IndividualAddress == frame.sourceAddress()) + continue; + + if(tunnels[i].IndividualAddress == frame.destinationAddress()) + { + tun = &tunnels[i]; + break; + } + } + + if(tun == nullptr) + { + for(int i = 0; i < KNX_TUNNELING; i++) + { + if(tunnels[i].IsConfig) + { +#ifdef KNX_LOG_TUNNELING + println("Found config Channel"); +#endif + tun = &tunnels[i]; + break; + } + } + } + + if(tun == nullptr) + { +#ifdef KNX_LOG_TUNNELING + print("Found no Tunnel for IA: "); + println(frame.destinationAddress(), 16); +#endif + return; + } + + sendFrameToTunnel(tun, frame); +} + +void IpDataLinkLayer::sendFrameToTunnel(KnxIpTunnelConnection *tunnel, CemiFrame& frame) +{ +#ifdef KNX_LOG_TUNNELING + print("Send to Channel: "); + println(tunnel->ChannelId, 16); +#endif + KnxIpTunnelingRequest req(frame); + req.connectionHeader().sequenceCounter(tunnel->SequenceCounter_S++); + req.connectionHeader().length(LEN_CH); + req.connectionHeader().channelId(tunnel->ChannelId); + + if(frame.messageCode() != L_data_req && frame.messageCode() != L_data_con && frame.messageCode() != L_data_ind) + req.serviceTypeIdentifier(DeviceConfigurationRequest); + + _platform.sendBytesUniCast(tunnel->IpAddress, tunnel->PortData, req.data(), req.totalLength()); +} + +bool IpDataLinkLayer::isTunnelAddress(uint16_t addr) +{ + if(addr == 0) + return false; // 0.0.0 is not a valid tunnel address and is used as default value + + for(int i = 0; i < KNX_TUNNELING; i++) + if(tunnels[i].IndividualAddress == addr) + return true; + + return false; +} + +bool IpDataLinkLayer::isSentToTunnel(uint16_t address, bool isGrpAddr) +{ + if(isGrpAddr) + { + for(int i = 0; i < KNX_TUNNELING; i++) + if(tunnels[i].ChannelId != 0) + return true; + return false; + } else { + for(int i = 0; i < KNX_TUNNELING; i++) + if(tunnels[i].ChannelId != 0 && tunnels[i].IndividualAddress == address) + return true; + return false; + } +} +#endif + void IpDataLinkLayer::loop() { if (!_enabled) return; +#ifdef KNX_TUNNELING + for(int i = 0; i < KNX_TUNNELING; i++) + { + if(tunnels[i].ChannelId != 0) + { + if(millis() - tunnels[i].lastHeartbeat > 120000) + { + #ifdef KNX_LOG_TUNNELING + print("Closed Tunnel 0x"); + print(tunnels[i].ChannelId, 16); + println(" due to no heartbeat in 2 minutes"); + #endif + KnxIpDisconnectRequest discReq; + discReq.channelId(tunnels[i].ChannelId); + discReq.hpaiCtrl().length(LEN_IPHPAI); + discReq.hpaiCtrl().code(IPV4_UDP); + discReq.hpaiCtrl().ipAddress(tunnels[i].IpAddress); + discReq.hpaiCtrl().ipPortNumber(tunnels[i].PortCtrl); + _platform.sendBytesUniCast(tunnels[i].IpAddress, tunnels[i].PortCtrl, discReq.data(), discReq.totalLength()); + tunnels[i].Reset(); + } + break; + } + } +#endif + + uint8_t buffer[512]; - int len = _platform.readBytesMultiCast(buffer, 512); + uint16_t remotePort = 0; + uint32_t remoteAddr = 0; + int len = _platform.readBytesMultiCast(buffer, 512, remoteAddr, remotePort); if (len <= 0) return; @@ -70,6 +314,7 @@ void IpDataLinkLayer::loop() frameReceived(routingIndication.frame()); break; } + case SearchRequest: { KnxIpSearchRequest searchRequest(buffer, len); @@ -85,17 +330,698 @@ void IpDataLinkLayer::loop() } case SearchRequestExt: { - // FIXME, implement (not needed atm) + #if KNX_SERVICE_FAMILY_CORE >= 2 + loopHandleSearchRequestExtended(buffer, len); + #endif break; } - default: +#ifdef KNX_TUNNELING + case ConnectRequest: { - // print("Unhandled service identifier: "); - // println(code, HEX); + loopHandleConnectRequest(buffer, len, remoteAddr, remotePort); + break; } + + case ConnectionStateRequest: + { + loopHandleConnectionStateRequest(buffer, len); + break; + } + + case DisconnectRequest: + { + loopHandleDisconnectRequest(buffer, len); + break; + } + + case DescriptionRequest: + { + loopHandleDescriptionRequest(buffer, len); + break; + } + + case DeviceConfigurationRequest: + { + loopHandleDeviceConfigurationRequest(buffer, len); + break; + } + + case TunnelingRequest: + { + loopHandleTunnelingRequest(buffer, len); + return; + } + + case DeviceConfigurationAck: + { + //TOOD nothing to do now + //println("got Ack"); + break; + } + + case TunnelingAck: + { + //TOOD nothing to do now + //println("got Ack"); + break; + } +#endif + default: + print("Unhandled service identifier: "); + println(code, HEX); + break; } } +#if KNX_SERVICE_FAMILY_CORE >= 2 +void IpDataLinkLayer::loopHandleSearchRequestExtended(uint8_t* buffer, uint16_t length) +{ + KnxIpSearchRequestExtended searchRequest(buffer, length); + + if(searchRequest.srpByProgMode) + { + println("srpByProgMode"); + if(!knx.progMode()) return; + } + + if(searchRequest.srpByMacAddr) + { + println("srpByMacAddr"); + const uint8_t *x = _ipParameters.propertyData(PID_MAC_ADDRESS); + for(int i = 0; i<6;i++) + if(searchRequest.srpMacAddr[i] != x[i]) + return; + } + + #define LEN_SERVICE_FAMILIES 2 + #if MASK_VERSION == 0x091A + #ifdef KNX_TUNNELING + #define LEN_SERVICE_DIB (2 + 4 * LEN_SERVICE_FAMILIES) + #else + #define LEN_SERVICE_DIB (2 + 3 * LEN_SERVICE_FAMILIES) + #endif + #else + #ifdef KNX_TUNNELING + #define LEN_SERVICE_DIB (2 + 3 * LEN_SERVICE_FAMILIES) + #else + #define LEN_SERVICE_DIB (2 + 2 * LEN_SERVICE_FAMILIES) + #endif + #endif + + //defaults: "Device Information DIB", "Extended Device Information DIB" and "Supported Services DIB". + int dibLength = LEN_DEVICE_INFORMATION_DIB + LEN_SERVICE_DIB + LEN_EXTENDED_DEVICE_INFORMATION_DIB; + + if(searchRequest.srpByService) + { + println("srpByService"); + uint8_t length = searchRequest.srpServiceFamilies[0]; + uint8_t *currentPos = searchRequest.srpServiceFamilies + 2; + for(int i = 0; i < (length-2)/2; i++) + { + uint8_t serviceFamily = (currentPos + i*2)[0]; + uint8_t version = (currentPos + i*2)[1]; + switch(serviceFamily) + { + case Core: + if(version > KNX_SERVICE_FAMILY_CORE) return; + break; + case DeviceManagement: + if(version > KNX_SERVICE_FAMILY_DEVICE_MANAGEMENT) return; + break; + case Tunnelling: + if(version > KNX_SERVICE_FAMILY_TUNNELING) return; + break; + case Routing: + if(version > KNX_SERVICE_FAMILY_ROUTING) return; + break; + } + } + } + + if(searchRequest.srpRequestDIBs) + { + println("srpRequestDIBs"); + if(searchRequest.requestedDIB(IP_CONFIG)) + dibLength += LEN_IP_CONFIG_DIB; //16 + + if(searchRequest.requestedDIB(IP_CUR_CONFIG)) + dibLength += LEN_IP_CURRENT_CONFIG_DIB; //20 + + if(searchRequest.requestedDIB(KNX_ADDRESSES)) + {uint16_t length = 0; + _ipParameters.readPropertyLength(PID_ADDITIONAL_INDIVIDUAL_ADDRESSES, length); + dibLength += 4 + length*2; + } + + if(searchRequest.requestedDIB(MANUFACTURER_DATA)) + dibLength += 0; //4 + n + + if(searchRequest.requestedDIB(TUNNELING_INFO)) + { + uint16_t length = 0; + _ipParameters.readPropertyLength(PID_ADDITIONAL_INDIVIDUAL_ADDRESSES, length); + dibLength += 4 + length*4; + } + } + + KnxIpSearchResponseExtended searchResponse(_ipParameters, _deviceObject, dibLength); + + searchResponse.setDeviceInfo(_ipParameters, _deviceObject); //DescriptionTypeCode::DeviceInfo 1 + searchResponse.setSupportedServices(); //DescriptionTypeCode::SUPP_SVC_FAMILIES 2 + searchResponse.setExtendedDeviceInfo(); //DescriptionTypeCode::EXTENDED_DEVICE_INFO 8 + + if(searchRequest.srpRequestDIBs) + { + if(searchRequest.requestedDIB(IP_CONFIG)) + searchResponse.setIpConfig(_ipParameters); + + if(searchRequest.requestedDIB(IP_CUR_CONFIG)) + searchResponse.setIpCurrentConfig(_ipParameters); + + if(searchRequest.requestedDIB(KNX_ADDRESSES)) + searchResponse.setKnxAddresses(_ipParameters, _deviceObject); + + if(searchRequest.requestedDIB(MANUFACTURER_DATA)) + { + //println("requested MANUFACTURER_DATA but not implemented"); + } + + if(searchRequest.requestedDIB(TUNNELING_INFO)) + searchResponse.setTunnelingInfo(_ipParameters, _deviceObject, tunnels); + } + + if(searchResponse.totalLength() > 150) + { + println("skipped response cause length is not plausible"); + return; + } + + _platform.sendBytesUniCast(searchRequest.hpai().ipAddress(), searchRequest.hpai().ipPortNumber(), searchResponse.data(), searchResponse.totalLength()); +} +#endif + +#ifdef KNX_TUNNELING +void IpDataLinkLayer::loopHandleConnectRequest(uint8_t* buffer, uint16_t length, uint32_t& src_addr, uint16_t& src_port) +{ + KnxIpConnectRequest connRequest(buffer, length); +#ifdef KNX_LOG_TUNNELING + println("Got Connect Request!"); + switch(connRequest.cri().type()) + { + case DEVICE_MGMT_CONNECTION: + println("Device Management Connection"); + break; + case TUNNEL_CONNECTION: + println("Tunnel Connection"); + break; + case REMLOG_CONNECTION: + println("RemLog Connection"); + break; + case REMCONF_CONNECTION: + println("RemConf Connection"); + break; + case OBJSVR_CONNECTION: + println("ObjectServer Connection"); + break; + } + + print("Data Endpoint: "); + uint32_t ip = connRequest.hpaiData().ipAddress(); + print(ip >> 24); + print("."); + print((ip >> 16) & 0xFF); + print("."); + print((ip >> 8) & 0xFF); + print("."); + print(ip & 0xFF); + print(":"); + println(connRequest.hpaiData().ipPortNumber()); + print("Ctrl Endpoint: "); + ip = connRequest.hpaiCtrl().ipAddress(); + print(ip >> 24); + print("."); + print((ip >> 16) & 0xFF); + print("."); + print((ip >> 8) & 0xFF); + print("."); + print(ip & 0xFF); + print(":"); + println(connRequest.hpaiCtrl().ipPortNumber()); +#endif + + //We only support 0x03 and 0x04! + if(connRequest.cri().type() != TUNNEL_CONNECTION && connRequest.cri().type() != DEVICE_MGMT_CONNECTION) + { +#ifdef KNX_LOG_TUNNELING + println("Only Tunnel/DeviceMgmt Connection ist supported!"); +#endif + KnxIpConnectResponse connRes(0x00, E_CONNECTION_TYPE); + _platform.sendBytesUniCast(connRequest.hpaiCtrl().ipAddress(), connRequest.hpaiCtrl().ipPortNumber(), connRes.data(), connRes.totalLength()); + return; + } + + if(connRequest.cri().type() == TUNNEL_CONNECTION && connRequest.cri().layer() != 0x02) //LinkLayer + { + //We only support 0x02! +#ifdef KNX_LOG_TUNNELING + println("Only LinkLayer ist supported!"); +#endif + KnxIpConnectResponse connRes(0x00, E_TUNNELING_LAYER); + _platform.sendBytesUniCast(connRequest.hpaiCtrl().ipAddress(), connRequest.hpaiCtrl().ipPortNumber(), connRes.data(), connRes.totalLength()); + return; + } + + // data preparation + + uint32_t srcIP = connRequest.hpaiCtrl().ipAddress()? connRequest.hpaiCtrl().ipAddress() : src_addr; + uint16_t srcPort = connRequest.hpaiCtrl().ipPortNumber()? connRequest.hpaiCtrl().ipPortNumber() : src_port; + + // read current elements in PID_ADDITIONAL_INDIVIDUAL_ADDRESSES + uint16_t propCount = 0; + _ipParameters.readPropertyLength(PID_ADDITIONAL_INDIVIDUAL_ADDRESSES, propCount); + const uint8_t *addresses; + if(propCount == KNX_TUNNELING) + { + addresses = _ipParameters.propertyData(PID_ADDITIONAL_INDIVIDUAL_ADDRESSES); + } + else // no tunnel PA configured, that means device is unconfigured and has 15.15.0 + { + uint8_t addrbuffer[KNX_TUNNELING*2]; + addresses = (uint8_t*)addrbuffer; + for(int i = 0; i < KNX_TUNNELING; i++) + { + addrbuffer[i*2+1] = i+1; + addrbuffer[i*2] = _deviceObject.individualAddress() / 0x0100; + } + uint8_t count = KNX_TUNNELING; + _ipParameters.writeProperty(PID_ADDITIONAL_INDIVIDUAL_ADDRESSES, 1, addrbuffer, count); +#ifdef KNX_LOG_TUNNELING + println("no Tunnel-PAs configured, using own subnet"); +#endif + } + + _ipParameters.readPropertyLength(PID_CUSTOM_RESERVED_TUNNELS_CTRL, propCount); + const uint8_t *tunCtrlBytes = nullptr; + if(propCount == KNX_TUNNELING) + tunCtrlBytes = _ipParameters.propertyData(PID_CUSTOM_RESERVED_TUNNELS_CTRL); + + _ipParameters.readPropertyLength(PID_CUSTOM_RESERVED_TUNNELS_IP, propCount); + const uint8_t *tunCtrlIp = nullptr; + if(propCount == KNX_TUNNELING) + tunCtrlIp = _ipParameters.propertyData(PID_CUSTOM_RESERVED_TUNNELS_IP); + + bool resTunActive = (tunCtrlBytes && tunCtrlIp); +#ifdef KNX_LOG_TUNNELING + if(resTunActive) println("Reserved Tunnel Feature active"); + + if(tunCtrlBytes) + printHex("tunCtrlBytes", tunCtrlBytes, KNX_TUNNELING); + if(tunCtrlIp) + printHex("tunCtrlIp", tunCtrlIp, KNX_TUNNELING*4); +#endif + + // check if there is a reserved tunnel for the source + int firstFreeTunnel = -1; + int firstResAndFreeTunnel = -1; + int firstResAndOccTunnel = -1; + bool tunnelResActive[KNX_TUNNELING]; + uint8_t tunnelResOptions[KNX_TUNNELING]; + for(int i = 0; i < KNX_TUNNELING; i++) + { + if(resTunActive) + { + tunnelResActive[i] = *(tunCtrlBytes+i) & 0x80; + tunnelResOptions[i] = (*(tunCtrlBytes+i) & 0x60) >> 5; + } + + + if(tunnelResActive[i]) // tunnel reserve feature active for this tunnel + { + #ifdef KNX_LOG_TUNNELING + print("tunnel reserve feature active for this tunnel: "); + print(tunnelResActive[i]); + print(" options: "); + println(tunnelResOptions[i]); + #endif + + uint32_t rIP = 0; + popInt(rIP, tunCtrlIp+4*i); + if(srcIP == rIP && connRequest.cri().type() == TUNNEL_CONNECTION) + { + // reserved tunnel for this ip found + if(tunnels[i].ChannelId == 0) // check if it is free + { + if(firstResAndFreeTunnel < 0) + firstResAndFreeTunnel = i; + } + else + { + if(firstResAndOccTunnel < 0) + firstResAndOccTunnel = i; + } + } + } + else + { + if(tunnels[i].ChannelId == 0 && firstFreeTunnel < 0) + firstFreeTunnel = i; + } + } +#ifdef KNX_LOG_TUNNELING + print("firstFreeTunnel: "); + print(firstFreeTunnel); + print(" firstResAndFreeTunnel: "); + print(firstResAndFreeTunnel); + print(" firstResAndOccTunnel: "); + println(firstResAndOccTunnel); +#endif + + + uint8_t tunIdx = 0xff; + if(resTunActive & (firstResAndFreeTunnel >= 0 || firstResAndOccTunnel >= 0)) // tunnel reserve feature active (for this src) + { + if(firstResAndFreeTunnel >= 0) + { + tunIdx = firstResAndFreeTunnel; + } + else if(firstResAndOccTunnel >= 0) + { + if(tunnelResOptions[firstResAndOccTunnel] == 1) // decline req + { + ; // do nothing => decline + } + else if(tunnelResOptions[firstResAndOccTunnel] == 2) // close current tunnel connection on this tunnel and assign to this request + { + KnxIpDisconnectRequest discReq; + discReq.channelId(tunnels[firstResAndOccTunnel].ChannelId); + discReq.hpaiCtrl().length(LEN_IPHPAI); + discReq.hpaiCtrl().code(IPV4_UDP); + discReq.hpaiCtrl().ipAddress(tunnels[firstResAndOccTunnel].IpAddress); + discReq.hpaiCtrl().ipPortNumber(tunnels[firstResAndOccTunnel].PortCtrl); + _platform.sendBytesUniCast(tunnels[firstResAndOccTunnel].IpAddress, tunnels[firstResAndOccTunnel].PortCtrl, discReq.data(), discReq.totalLength()); + tunnels[firstResAndOccTunnel].Reset(); + + + tunIdx = firstResAndOccTunnel; + } + else if(tunnelResOptions[firstResAndOccTunnel] == 3) // use the first unreserved tunnel (if one) + { + if(firstFreeTunnel >= 0) + tunIdx = firstFreeTunnel; + else + ; // do nothing => decline + } + //else + // should not happen + // do nothing => decline + } + //else + // should not happen + // do nothing => decline + } + else + { + if(firstFreeTunnel >= 0) + tunIdx = firstFreeTunnel; + //else + // do nothing => decline + } + + KnxIpTunnelConnection *tun = nullptr; + if(tunIdx != 0xFF) + { + tun = &tunnels[tunIdx]; + + uint16_t tunPa = 0; + popWord(tunPa, addresses + (tunIdx*2)); + + //check if this PA is in use (should not happen, only when there is one pa wrongly assigned to more then one tunnel) + for(int x = 0; x < KNX_TUNNELING; x++) + if(tunnels[x].IndividualAddress == tunPa) + { +#ifdef KNX_LOG_TUNNELING + println("cannot use tunnel because PA is already in use"); +#endif + tunIdx = 0xFF; + tun = nullptr; + break; + } + + tun->IndividualAddress = tunPa; + + } + + if(tun == nullptr) + { + println("no free tunnel availible"); + KnxIpConnectResponse connRes(0x00, E_NO_MORE_CONNECTIONS); + _platform.sendBytesUniCast(connRequest.hpaiCtrl().ipAddress(), connRequest.hpaiCtrl().ipPortNumber(), connRes.data(), connRes.totalLength()); + return; + } + + if(connRequest.cri().type() == DEVICE_MGMT_CONNECTION) + tun->IsConfig = true; + + // the channel ID shall be unique on this tunnel server. catch the rare case of a double channel ID + bool channelIdInUse; + do + { + _lastChannelId++; + channelIdInUse = false; + for(int x = 0; x < KNX_TUNNELING; x++) + if(tunnels[x].ChannelId == _lastChannelId) + channelIdInUse = true; + } + while(channelIdInUse); + + tun->ChannelId = _lastChannelId; + tun->lastHeartbeat = millis(); + if(_lastChannelId == 255) + _lastChannelId = 0; + + tun->IpAddress = srcIP; + tun->PortData = srcPort; + tun->PortCtrl = connRequest.hpaiCtrl().ipPortNumber()?connRequest.hpaiCtrl().ipPortNumber():srcPort; + + print("New Tunnel-Connection["); + print(tunIdx); + print("], Channel: 0x"); + print(tun->ChannelId, 16); + print(" PA: "); + print(tun->IndividualAddress >> 12); + print("."); + print((tun->IndividualAddress >> 8) & 0xF); + print("."); + print(tun->IndividualAddress & 0xFF); + + print(" with "); + print(tun->IpAddress >> 24); + print("."); + print((tun->IpAddress >> 16) & 0xFF); + print("."); + print((tun->IpAddress >> 8) & 0xFF); + print("."); + print(tun->IpAddress & 0xFF); + print(":"); + print(tun->PortData); + if(tun->PortData != tun->PortCtrl) + { + print(" (Ctrlport: "); + print(tun->PortCtrl); + print(")"); + } + if(tun->IsConfig) + { + print(" (Config-Channel)"); + } + println(); + + + KnxIpConnectResponse connRes(_ipParameters, tun->IndividualAddress, 3671, tun->ChannelId, connRequest.cri().type()); + _platform.sendBytesUniCast(tun->IpAddress, tun->PortCtrl, connRes.data(), connRes.totalLength()); +} + +void IpDataLinkLayer::loopHandleConnectionStateRequest(uint8_t* buffer, uint16_t length) +{ + KnxIpStateRequest stateRequest(buffer, length); + + KnxIpTunnelConnection *tun = nullptr; + for(int i = 0; i < KNX_TUNNELING; i++) + { + if(tunnels[i].ChannelId == stateRequest.channelId()) + { + tun = &tunnels[i]; + break; + } + } + + if(tun == nullptr) + { +#ifdef KNX_LOG_TUNNELING + print("Channel ID nicht gefunden: "); + println(stateRequest.channelId()); +#endif + KnxIpStateResponse stateRes(0x00, E_CONNECTION_ID); + _platform.sendBytesUniCast(stateRequest.hpaiCtrl().ipAddress(), stateRequest.hpaiCtrl().ipPortNumber(), stateRes.data(), stateRes.totalLength()); + return; + } + + //TODO check knx connection! + //if no connection return E_KNX_CONNECTION + + //TODO check when to send E_DATA_CONNECTION + + tun->lastHeartbeat = millis(); + KnxIpStateResponse stateRes(tun->ChannelId, E_NO_ERROR); + _platform.sendBytesUniCast(stateRequest.hpaiCtrl().ipAddress(), stateRequest.hpaiCtrl().ipPortNumber(), stateRes.data(), stateRes.totalLength()); +} + +void IpDataLinkLayer::loopHandleDisconnectRequest(uint8_t* buffer, uint16_t length) +{ + KnxIpDisconnectRequest discReq(buffer, length); + +#ifdef KNX_LOG_TUNNELING + print(">>> Disconnect Channel ID: "); + println(discReq.channelId()); +#endif + + KnxIpTunnelConnection *tun = nullptr; + for(int i = 0; i < KNX_TUNNELING; i++) + { + if(tunnels[i].ChannelId == discReq.channelId()) + { + tun = &tunnels[i]; + break; + } + } + + if(tun == nullptr) + { +#ifdef KNX_LOG_TUNNELING + print("Channel ID nicht gefunden: "); + println(discReq.channelId()); +#endif + KnxIpDisconnectResponse discRes(0x00, E_CONNECTION_ID); + _platform.sendBytesUniCast(discReq.hpaiCtrl().ipAddress(), discReq.hpaiCtrl().ipPortNumber(), discRes.data(), discRes.totalLength()); + return; + } + + + KnxIpDisconnectResponse discRes(tun->ChannelId, E_NO_ERROR); + _platform.sendBytesUniCast(discReq.hpaiCtrl().ipAddress(), discReq.hpaiCtrl().ipPortNumber(), discRes.data(), discRes.totalLength()); + tun->Reset(); +} + +void IpDataLinkLayer::loopHandleDescriptionRequest(uint8_t* buffer, uint16_t length) +{ + KnxIpDescriptionRequest descReq(buffer, length); + KnxIpDescriptionResponse descRes(_ipParameters, _deviceObject); + _platform.sendBytesUniCast(descReq.hpaiCtrl().ipAddress(), descReq.hpaiCtrl().ipPortNumber(), descRes.data(), descRes.totalLength()); +} + +void IpDataLinkLayer::loopHandleDeviceConfigurationRequest(uint8_t* buffer, uint16_t length) +{ + KnxIpConfigRequest confReq(buffer, length); + + KnxIpTunnelConnection *tun = nullptr; + for(int i = 0; i < KNX_TUNNELING; i++) + { + if(tunnels[i].ChannelId == confReq.connectionHeader().channelId()) + { + tun = &tunnels[i]; + break; + } + } + + if(tun == nullptr) + { + print("Channel ID nicht gefunden: "); + println(confReq.connectionHeader().channelId()); + KnxIpStateResponse stateRes(0x00, E_CONNECTION_ID); + _platform.sendBytesUniCast(0, 0, stateRes.data(), stateRes.totalLength()); + return; + } + + KnxIpTunnelingAck tunnAck; + tunnAck.serviceTypeIdentifier(DeviceConfigurationAck); + tunnAck.connectionHeader().length(4); + tunnAck.connectionHeader().channelId(tun->ChannelId); + tunnAck.connectionHeader().sequenceCounter(confReq.connectionHeader().sequenceCounter()); + tunnAck.connectionHeader().status(E_NO_ERROR); + _platform.sendBytesUniCast(tun->IpAddress, tun->PortData, tunnAck.data(), tunnAck.totalLength()); + + tun->lastHeartbeat = millis(); + _cemiServer->frameReceived(confReq.frame()); +} + +void IpDataLinkLayer::loopHandleTunnelingRequest(uint8_t* buffer, uint16_t length) +{ + KnxIpTunnelingRequest tunnReq(buffer, length); + + KnxIpTunnelConnection *tun = nullptr; + for(int i = 0; i < KNX_TUNNELING; i++) + { + if(tunnels[i].ChannelId == tunnReq.connectionHeader().channelId()) + { + tun = &tunnels[i]; + break; + } + } + + if(tun == nullptr) + { +#ifdef KNX_LOG_TUNNELING + print("Channel ID nicht gefunden: "); + println(tunnReq.connectionHeader().channelId()); +#endif + KnxIpStateResponse stateRes(0x00, E_CONNECTION_ID); + _platform.sendBytesUniCast(0, 0, stateRes.data(), stateRes.totalLength()); + return; + } + + uint8_t sequence = tunnReq.connectionHeader().sequenceCounter(); + if(sequence == tun->SequenceCounter_R) + { +#ifdef KNX_LOG_TUNNELING + print("Received SequenceCounter again: "); + println(tunnReq.connectionHeader().sequenceCounter()); +#endif + //we already got this one + //so just ack it + KnxIpTunnelingAck tunnAck; + tunnAck.connectionHeader().length(4); + tunnAck.connectionHeader().channelId(tun->ChannelId); + tunnAck.connectionHeader().sequenceCounter(tunnReq.connectionHeader().sequenceCounter()); + tunnAck.connectionHeader().status(E_NO_ERROR); + _platform.sendBytesUniCast(tun->IpAddress, tun->PortData, tunnAck.data(), tunnAck.totalLength()); + return; + } else if((uint8_t)(sequence - 1) != tun->SequenceCounter_R) { +#ifdef KNX_LOG_TUNNELING + print("Wrong SequenceCounter: got "); + print(tunnReq.connectionHeader().sequenceCounter()); + print(" expected "); + println((uint8_t)(tun->SequenceCounter_R + 1)); +#endif + //Dont handle it + return; + } + + KnxIpTunnelingAck tunnAck; + tunnAck.connectionHeader().length(4); + tunnAck.connectionHeader().channelId(tun->ChannelId); + tunnAck.connectionHeader().sequenceCounter(tunnReq.connectionHeader().sequenceCounter()); + tunnAck.connectionHeader().status(E_NO_ERROR); + _platform.sendBytesUniCast(tun->IpAddress, tun->PortData, tunnAck.data(), tunnAck.totalLength()); + + tun->SequenceCounter_R = tunnReq.connectionHeader().sequenceCounter(); + + if(tunnReq.frame().sourceAddress() == 0) + tunnReq.frame().sourceAddress(tun->IndividualAddress); + + _cemiServer->frameReceived(tunnReq.frame()); +} +#endif + void IpDataLinkLayer::enabled(bool value) { // _print("own address: "); diff --git a/src/knx/ip_data_link_layer.h b/src/knx/ip_data_link_layer.h index 5402a93..128f59f 100644 --- a/src/knx/ip_data_link_layer.h +++ b/src/knx/ip_data_link_layer.h @@ -6,6 +6,8 @@ #include #include "data_link_layer.h" #include "ip_parameter_object.h" +#include "knx_ip_tunnel_connection.h" +#include "service_families.h" class IpDataLinkLayer : public DataLinkLayer { @@ -13,12 +15,19 @@ class IpDataLinkLayer : public DataLinkLayer public: IpDataLinkLayer(DeviceObject& devObj, IpParameterObject& ipParam, NetworkLayerEntity& netLayerEntity, - Platform& platform, DataLinkLayerCallbacks* dllcb = nullptr); + Platform& platform, BusAccessUnit& busAccessUnit, DataLinkLayerCallbacks* dllcb = nullptr); void loop(); void enabled(bool value); bool enabled() const; DptMedium mediumType() const override; +#ifdef KNX_TUNNELING + void dataRequestToTunnel(CemiFrame& frame) override; + void dataConfirmationToTunnel(CemiFrame& frame) override; + void dataIndicationToTunnel(CemiFrame& frame) override; + bool isTunnelAddress(uint16_t addr) override; + bool isSentToTunnel(uint16_t address, bool isGrpAddr); +#endif private: bool _enabled = false; @@ -26,10 +35,26 @@ class IpDataLinkLayer : public DataLinkLayer uint8_t _frameCountBase = 0; uint32_t _frameCountTimeBase = 0; bool sendFrame(CemiFrame& frame); +#ifdef KNX_TUNNELING + void sendFrameToTunnel(KnxIpTunnelConnection *tunnel, CemiFrame& frame); + void loopHandleConnectRequest(uint8_t* buffer, uint16_t length, uint32_t& src_addr, uint16_t& src_port); + void loopHandleConnectionStateRequest(uint8_t* buffer, uint16_t length); + void loopHandleDisconnectRequest(uint8_t* buffer, uint16_t length); + void loopHandleDescriptionRequest(uint8_t* buffer, uint16_t length); + void loopHandleDeviceConfigurationRequest(uint8_t* buffer, uint16_t length); + void loopHandleTunnelingRequest(uint8_t* buffer, uint16_t length); +#endif +#if KNX_SERVICE_FAMILY_CORE >= 2 + void loopHandleSearchRequestExtended(uint8_t* buffer, uint16_t length); +#endif bool sendBytes(uint8_t* buffer, uint16_t length); bool isSendLimitReached(); IpParameterObject& _ipParameters; DataLinkLayerCallbacks* _dllcb; -}; +#ifdef KNX_TUNNELING + KnxIpTunnelConnection tunnels[KNX_TUNNELING]; + uint8_t _lastChannelId = 1; #endif +}; +#endif \ No newline at end of file diff --git a/src/knx/ip_host_protocol_address_information.h b/src/knx/ip_host_protocol_address_information.h index f2c9ae7..bcf0494 100644 --- a/src/knx/ip_host_protocol_address_information.h +++ b/src/knx/ip_host_protocol_address_information.h @@ -12,6 +12,7 @@ enum HostProtocolCode : uint8_t #ifdef USE_IP #define LEN_IPHPAI 8 +#define LEN_CRD 4 class IpHostProtocolAddressInformation { diff --git a/src/knx/ip_parameter_object.cpp b/src/knx/ip_parameter_object.cpp index 3de44d3..5980783 100644 --- a/src/knx/ip_parameter_object.cpp +++ b/src/knx/ip_parameter_object.cpp @@ -34,6 +34,11 @@ IpParameterObject::IpParameterObject(DeviceObject& deviceObject, Platform& platf io->_deviceObject.individualAddress(getWord(data)); return 1; }), +#ifdef KNX_TUNNELING + new DataProperty(PID_ADDITIONAL_INDIVIDUAL_ADDRESSES, true, PDT_UNSIGNED_INT, KNX_TUNNELING, ReadLv3 | WriteLv3), + new DataProperty(PID_CUSTOM_RESERVED_TUNNELS_CTRL, true, PDT_UNSIGNED_CHAR, KNX_TUNNELING, ReadLv3 | WriteLv3), // custom propertiy to control the stacks behaviour for reserverd tunnels, not in Spec (PID >= 200) + new DataProperty(PID_CUSTOM_RESERVED_TUNNELS_IP, true, PDT_UNSIGNED_LONG, KNX_TUNNELING, ReadLv3 | WriteLv3), // custom propertiy to control the stacks behaviour for reserverd tunnels, not in Spec (PID >= 200) +#endif new DataProperty(PID_CURRENT_IP_ASSIGNMENT_METHOD, false, PDT_UNSIGNED_CHAR, 0, ReadLv3 | WriteLv3), new DataProperty(PID_IP_ASSIGNMENT_METHOD, true, PDT_UNSIGNED_CHAR, 1, ReadLv3 | WriteLv3), new DataProperty(PID_IP_CAPABILITIES, true, PDT_BITSET8, 0, ReadLv3 | WriteLv1), // must be set by application due to capabilities of the used ip stack diff --git a/src/knx/knx_ip_ch.cpp b/src/knx/knx_ip_ch.cpp new file mode 100644 index 0000000..aa473fb --- /dev/null +++ b/src/knx/knx_ip_ch.cpp @@ -0,0 +1,48 @@ +#include "knx_ip_ch.h" +#ifdef USE_IP +KnxIpCH::KnxIpCH(uint8_t* data) : _data(data) +{} + +KnxIpCH::~KnxIpCH() +{} + +uint8_t KnxIpCH::length() const +{ + return *_data; +} + +void KnxIpCH::length(uint8_t value) +{ + *_data = value; +} + +void KnxIpCH::channelId(uint8_t value) +{ + _data[1] = value; +} + +uint8_t KnxIpCH::channelId() const +{ + return _data[1]; +} + +void KnxIpCH::sequenceCounter(uint8_t value) +{ + _data[2] = value; +} + +uint8_t KnxIpCH::sequenceCounter() const +{ + return _data[2]; +} + +void KnxIpCH::status(uint8_t value) +{ + _data[3] = value; +} + +uint8_t KnxIpCH::status() const +{ + return _data[3]; +} +#endif \ No newline at end of file diff --git a/src/knx/knx_ip_ch.h b/src/knx/knx_ip_ch.h new file mode 100644 index 0000000..5277928 --- /dev/null +++ b/src/knx/knx_ip_ch.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include "config.h" + +#ifdef USE_IP + +#define LEN_CH 4 + +// Connection Header +class KnxIpCH +{ + public: + KnxIpCH(uint8_t* data); + virtual ~KnxIpCH(); + void channelId(uint8_t channelId); + uint8_t channelId() const; + void sequenceCounter(uint8_t sequenceCounter); + uint8_t sequenceCounter() const; + void status(uint8_t status); + uint8_t status() const; + void length(uint8_t value); + uint8_t length() const; + + protected: + uint8_t* _data = 0; +}; +#endif diff --git a/src/knx/knx_ip_config_dib.cpp b/src/knx/knx_ip_config_dib.cpp new file mode 100644 index 0000000..e603de0 --- /dev/null +++ b/src/knx/knx_ip_config_dib.cpp @@ -0,0 +1,91 @@ +#include "knx_ip_config_dib.h" + +#ifdef USE_IP +KnxIpConfigDIB::KnxIpConfigDIB(uint8_t* data, bool isCurrent) : KnxIpDIB(data) +{ + _isCurrent = isCurrent; +} + +uint32_t KnxIpConfigDIB::address() +{ + uint32_t addr = 0; + popInt(addr, _data + 2); + return addr; +} + +void KnxIpConfigDIB::address(uint32_t addr) +{ + pushInt(addr, _data + 2); +} + +uint32_t KnxIpConfigDIB::subnet() +{ + uint32_t addr = 0; + popInt(addr, _data + 6); + return addr; +} + +void KnxIpConfigDIB::subnet(uint32_t addr) +{ + pushInt(addr, _data + 6); +} + +uint32_t KnxIpConfigDIB::gateway() +{ + uint32_t addr = 0; + popInt(addr, _data + 10); + return addr; +} + +void KnxIpConfigDIB::gateway(uint32_t addr) +{ + pushInt(addr, _data + 10); +} + +uint32_t KnxIpConfigDIB::dhcp() +{ + if(!_isCurrent) return 0; + uint32_t addr = 0; + popInt(addr, _data + 14); + return addr; +} + +void KnxIpConfigDIB::dhcp(uint32_t addr) +{ + if(!_isCurrent) return; + pushInt(addr, _data + 14); +} + +uint8_t KnxIpConfigDIB::info1() +{ + if(_isCurrent) + return _data[14]; + else + return _data[18]; +} + +void KnxIpConfigDIB::info1(uint8_t addr) +{ + if(_isCurrent) + _data[14] = addr; + else + _data[18] = addr; +} + +uint8_t KnxIpConfigDIB::info2() +{ + if(_isCurrent) + return _data[15]; + else + return _data[19]; +} + +void KnxIpConfigDIB::info2(uint8_t addr) +{ + if(_isCurrent) + _data[15] = addr; + else + _data[19] = addr; +} + +#endif \ No newline at end of file diff --git a/src/knx/knx_ip_config_dib.h b/src/knx/knx_ip_config_dib.h new file mode 100644 index 0000000..717bab5 --- /dev/null +++ b/src/knx/knx_ip_config_dib.h @@ -0,0 +1,28 @@ +#pragma once +#include "knx_ip_dib.h" +#include "bits.h" + +#ifdef USE_IP +#define LEN_IP_CONFIG_DIB 16 +#define LEN_IP_CURRENT_CONFIG_DIB 20 + +class KnxIpConfigDIB : public KnxIpDIB +{ + public: + KnxIpConfigDIB(uint8_t* data, bool isCurrent = false); + uint32_t address(); + void address(uint32_t addr); + uint32_t subnet(); + void subnet(uint32_t addr); + uint32_t gateway(); + void gateway(uint32_t addr); + uint32_t dhcp(); + void dhcp(uint32_t addr); + uint8_t info1(); + void info1(uint8_t addr); + uint8_t info2(); + void info2(uint8_t addr); + private: + bool _isCurrent = false; +}; +#endif \ No newline at end of file diff --git a/src/knx/knx_ip_config_request.cpp b/src/knx/knx_ip_config_request.cpp new file mode 100644 index 0000000..962d1ea --- /dev/null +++ b/src/knx/knx_ip_config_request.cpp @@ -0,0 +1,17 @@ +#include "knx_ip_config_request.h" +#ifdef USE_IP +KnxIpConfigRequest::KnxIpConfigRequest(uint8_t* data, uint16_t length) + : KnxIpFrame(data, length), _ch(data + LEN_KNXIP_HEADER), _frame(data + LEN_KNXIP_HEADER + LEN_CH, length - LEN_KNXIP_HEADER - LEN_CH) +{ +} + + +CemiFrame& KnxIpConfigRequest::frame() +{ + return _frame; +} +KnxIpCH& KnxIpConfigRequest::connectionHeader() +{ + return _ch; +} +#endif \ No newline at end of file diff --git a/src/knx/knx_ip_config_request.h b/src/knx/knx_ip_config_request.h new file mode 100644 index 0000000..d7d6d1c --- /dev/null +++ b/src/knx/knx_ip_config_request.h @@ -0,0 +1,17 @@ +#pragma once + +#include "knx_ip_frame.h" +#include "knx_ip_ch.h" +#include "ip_host_protocol_address_information.h" +#ifdef USE_IP +class KnxIpConfigRequest : public KnxIpFrame +{ + public: + KnxIpConfigRequest(uint8_t* data, uint16_t length); + CemiFrame& frame(); + KnxIpCH& connectionHeader(); + private: + CemiFrame _frame; + KnxIpCH _ch; +}; +#endif \ No newline at end of file diff --git a/src/knx/knx_ip_connect_request.cpp b/src/knx/knx_ip_connect_request.cpp new file mode 100644 index 0000000..a1f64c0 --- /dev/null +++ b/src/knx/knx_ip_connect_request.cpp @@ -0,0 +1,21 @@ +#include "knx_ip_connect_request.h" +#ifdef USE_IP +KnxIpConnectRequest::KnxIpConnectRequest(uint8_t* data, uint16_t length) + : KnxIpFrame(data, length), _hpaiCtrl(data + LEN_KNXIP_HEADER), _hpaiData(data + LEN_KNXIP_HEADER + LEN_IPHPAI), _cri(data + LEN_KNXIP_HEADER + 2*LEN_IPHPAI) +{ +} + + +IpHostProtocolAddressInformation& KnxIpConnectRequest::hpaiCtrl() +{ + return _hpaiCtrl; +} +IpHostProtocolAddressInformation& KnxIpConnectRequest::hpaiData() +{ + return _hpaiData; +} +KnxIpCRI& KnxIpConnectRequest::cri() +{ + return _cri; +} +#endif \ No newline at end of file diff --git a/src/knx/knx_ip_connect_request.h b/src/knx/knx_ip_connect_request.h new file mode 100644 index 0000000..23e398f --- /dev/null +++ b/src/knx/knx_ip_connect_request.h @@ -0,0 +1,19 @@ +#pragma once + +#include "knx_ip_frame.h" +#include "knx_ip_cri.h" +#include "ip_host_protocol_address_information.h" +#ifdef USE_IP +class KnxIpConnectRequest : public KnxIpFrame +{ + public: + KnxIpConnectRequest(uint8_t* data, uint16_t length); + IpHostProtocolAddressInformation& hpaiCtrl(); + IpHostProtocolAddressInformation& hpaiData(); + KnxIpCRI& cri(); + private: + IpHostProtocolAddressInformation _hpaiCtrl; + IpHostProtocolAddressInformation _hpaiData; + KnxIpCRI _cri; +}; +#endif \ No newline at end of file diff --git a/src/knx/knx_ip_connect_response.cpp b/src/knx/knx_ip_connect_response.cpp new file mode 100644 index 0000000..11eb705 --- /dev/null +++ b/src/knx/knx_ip_connect_response.cpp @@ -0,0 +1,45 @@ +#include "knx_ip_connect_response.h" +#ifdef USE_IP + +KnxIpConnectResponse::KnxIpConnectResponse(IpParameterObject& parameters, uint16_t address, uint16_t port, uint8_t channel, uint8_t type) + : KnxIpFrame(LEN_KNXIP_HEADER + 1 /*Channel*/ + 1 /*Status*/ + LEN_IPHPAI + ((type == 4) ? 4 : 2)), + _controlEndpoint(_data + LEN_KNXIP_HEADER + 1 /*Channel*/ + 1 /*Status*/), + _crd(_data + LEN_KNXIP_HEADER + 1 /*Channel*/ + 1 /*Status*/ + LEN_IPHPAI) +{ + serviceTypeIdentifier(ConnectResponse); + + _data[LEN_KNXIP_HEADER] = channel; + + _controlEndpoint.length(LEN_IPHPAI); + _controlEndpoint.code(IPV4_UDP); + _controlEndpoint.ipAddress(parameters.propertyValue(PID_CURRENT_IP_ADDRESS)); + _controlEndpoint.ipPortNumber(KNXIP_MULTICAST_PORT); + + _crd.length((type == 4) ? 4 : 2); //TunnelConnectionResponse length = 4; ConfigConnectionResponse length = 2; + _crd.type(type); + _crd.address(address); +} + +KnxIpConnectResponse::KnxIpConnectResponse(uint8_t channel, uint8_t errorCode) + : KnxIpFrame(LEN_KNXIP_HEADER + 1 /*Channel*/ + 1 /*Status*/), + _controlEndpoint(nullptr), + _crd(nullptr) +{ + serviceTypeIdentifier(ConnectResponse); + + _data[LEN_KNXIP_HEADER] = channel; + _data[LEN_KNXIP_HEADER + 1] = errorCode; +} + + +IpHostProtocolAddressInformation& KnxIpConnectResponse::controlEndpoint() +{ + return _controlEndpoint; +} + +KnxIpCRD& KnxIpConnectResponse::crd() +{ + return _crd; +} + +#endif diff --git a/src/knx/knx_ip_connect_response.h b/src/knx/knx_ip_connect_response.h new file mode 100644 index 0000000..ecf1d8f --- /dev/null +++ b/src/knx/knx_ip_connect_response.h @@ -0,0 +1,45 @@ +#pragma once + +#include "knx_ip_frame.h" +#include "knx_ip_crd.h" +#include "ip_host_protocol_address_information.h" +#include "knx_ip_device_information_dib.h" +#include "knx_ip_supported_service_dib.h" +#include "ip_parameter_object.h" +#ifdef USE_IP + +enum KnxIpConnectionRequestErrorCodes +{ + E_NO_ERROR = 0, + + E_HOST_PROTOCOL_TYPE = 0x01, + E_VERSION_NOT_SUPPORTED = 0x02, + E_SEQUENCE_NUMBER = 0x04, + + E_ERROR = 0x0F, + + E_CONNECTION_ID = 0x21, + E_CONNECTION_TYPE = 0x22, + E_CONNECTION_OPTION = 0x23, + E_NO_MORE_CONNECTIONS = 0x24, + E_DATA_CONNECTION = 0x26, + E_KNX_CONNECTION = 0x27, + E_AUTHORISATION_ERROR = 0x28, + E_TUNNELING_LAYER = 0x29, + E_NO_TUNNELLING_ADDRESS = 0x2D, + E_CONNECTION_IN_USE = 0x2E +}; + +class KnxIpConnectResponse : public KnxIpFrame +{ + public: + KnxIpConnectResponse(IpParameterObject& parameters, uint16_t address, uint16_t port, uint8_t channel, uint8_t type); + KnxIpConnectResponse(uint8_t channel, uint8_t errorCode); + IpHostProtocolAddressInformation& controlEndpoint(); + KnxIpCRD& crd(); + private: + IpHostProtocolAddressInformation _controlEndpoint; + KnxIpCRD _crd; +}; + +#endif \ No newline at end of file diff --git a/src/knx/knx_ip_crd.cpp b/src/knx/knx_ip_crd.cpp new file mode 100644 index 0000000..b9f96b0 --- /dev/null +++ b/src/knx/knx_ip_crd.cpp @@ -0,0 +1,41 @@ +#include "knx_ip_crd.h" +#ifdef USE_IP +KnxIpCRD::KnxIpCRD(uint8_t* data) : _data(data) +{} + +KnxIpCRD::~KnxIpCRD() +{} + +uint8_t KnxIpCRD::length() const +{ + return *_data; +} + +void KnxIpCRD::length(uint8_t value) +{ + *_data = value; +} + +uint8_t KnxIpCRD::type() const +{ + return _data[1]; +} + +void KnxIpCRD::type(uint8_t value) +{ + _data[1] = value; +} + +uint16_t KnxIpCRD::address() const +{ + uint16_t addr = _data[3]; + addr |= _data[2] << 8; + return addr; +} + +void KnxIpCRD::address(uint16_t value) +{ + _data[2] = value >> 8; + _data[3] = value & 0xFF; +} +#endif \ No newline at end of file diff --git a/src/knx/knx_ip_crd.h b/src/knx/knx_ip_crd.h new file mode 100644 index 0000000..c7e613b --- /dev/null +++ b/src/knx/knx_ip_crd.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include "config.h" + +#ifdef USE_IP + +class KnxIpCRD +{ + public: + KnxIpCRD(uint8_t* data); + virtual ~KnxIpCRD(); + void address(uint16_t addr); + uint16_t address() const; + void type(uint8_t addr); + uint8_t type() const; + uint8_t length() const; + void length(uint8_t value); + + protected: + uint8_t* _data = 0; +}; +#endif diff --git a/src/knx/knx_ip_cri.cpp b/src/knx/knx_ip_cri.cpp new file mode 100644 index 0000000..8bb2107 --- /dev/null +++ b/src/knx/knx_ip_cri.cpp @@ -0,0 +1,38 @@ +#include "knx_ip_cri.h" +#ifdef USE_IP +KnxIpCRI::KnxIpCRI(uint8_t* data) : _data(data) +{} + +KnxIpCRI::~KnxIpCRI() +{} + +uint8_t KnxIpCRI::length() const +{ + return *_data; +} + +void KnxIpCRI::length(uint8_t value) +{ + *_data = value; +} + +ConnectionType KnxIpCRI::type() const +{ + return (ConnectionType)_data[1]; +} + +void KnxIpCRI::type(ConnectionType value) +{ + _data[1] = value; +} + +uint8_t KnxIpCRI::layer() const +{ + return _data[2]; +} + +void KnxIpCRI::layer(uint8_t value) +{ + _data[2] = value; +} +#endif \ No newline at end of file diff --git a/src/knx/knx_ip_cri.h b/src/knx/knx_ip_cri.h new file mode 100644 index 0000000..6c1377a --- /dev/null +++ b/src/knx/knx_ip_cri.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include "config.h" + +#ifdef USE_IP + +#define LEN_CRI 4 + +//TODO vervollständigen +enum ConnectionType : uint8_t +{ + DEVICE_MGMT_CONNECTION = 3, + TUNNEL_CONNECTION = 4, + REMLOG_CONNECTION = 6, + REMCONF_CONNECTION = 7, + OBJSVR_CONNECTION = 8 +}; + +// Connection Request Information +class KnxIpCRI +{ + public: + KnxIpCRI(uint8_t* data); + virtual ~KnxIpCRI(); + ConnectionType type() const; + void type(ConnectionType value); + void layer(uint8_t layer); + uint8_t layer() const; + uint8_t length() const; + void length(uint8_t value); + + protected: + uint8_t* _data = 0; +}; +#endif diff --git a/src/knx/knx_ip_description_request.cpp b/src/knx/knx_ip_description_request.cpp new file mode 100644 index 0000000..2e463f8 --- /dev/null +++ b/src/knx/knx_ip_description_request.cpp @@ -0,0 +1,13 @@ +#include "knx_ip_description_request.h" +#ifdef USE_IP +KnxIpDescriptionRequest::KnxIpDescriptionRequest(uint8_t* data, uint16_t length) + : KnxIpFrame(data, length), _hpaiCtrl(data + LEN_KNXIP_HEADER) +{ +} + + +IpHostProtocolAddressInformation& KnxIpDescriptionRequest::hpaiCtrl() +{ + return _hpaiCtrl; +} +#endif \ No newline at end of file diff --git a/src/knx/knx_ip_description_request.h b/src/knx/knx_ip_description_request.h new file mode 100644 index 0000000..39327e2 --- /dev/null +++ b/src/knx/knx_ip_description_request.h @@ -0,0 +1,15 @@ +#pragma once + +#include "knx_ip_frame.h" +#include "knx_ip_cri.h" +#include "ip_host_protocol_address_information.h" +#ifdef USE_IP +class KnxIpDescriptionRequest : public KnxIpFrame +{ + public: + KnxIpDescriptionRequest(uint8_t* data, uint16_t length); + IpHostProtocolAddressInformation& hpaiCtrl(); + private: + IpHostProtocolAddressInformation _hpaiCtrl; +}; +#endif \ No newline at end of file diff --git a/src/knx/knx_ip_description_response.cpp b/src/knx/knx_ip_description_response.cpp new file mode 100644 index 0000000..6df1c13 --- /dev/null +++ b/src/knx/knx_ip_description_response.cpp @@ -0,0 +1,72 @@ +#include "knx_ip_description_response.h" +#ifdef USE_IP + +#define LEN_SERVICE_FAMILIES 2 +#if MASK_VERSION == 0x091A +#ifdef KNX_TUNNELING +#define LEN_SERVICE_DIB (2 + 4 * LEN_SERVICE_FAMILIES) +#else +#define LEN_SERVICE_DIB (2 + 3 * LEN_SERVICE_FAMILIES) +#endif +#else +#ifdef KNX_TUNNELING +#define LEN_SERVICE_DIB (2 + 3 * LEN_SERVICE_FAMILIES) +#else +#define LEN_SERVICE_DIB (2 + 2 * LEN_SERVICE_FAMILIES) +#endif +#endif + +KnxIpDescriptionResponse::KnxIpDescriptionResponse(IpParameterObject& parameters, DeviceObject& deviceObject) + : KnxIpFrame(LEN_KNXIP_HEADER + LEN_DEVICE_INFORMATION_DIB + LEN_SERVICE_DIB), + _deviceInfo(_data + LEN_KNXIP_HEADER), + _supportedServices(_data + LEN_KNXIP_HEADER + LEN_DEVICE_INFORMATION_DIB) +{ + serviceTypeIdentifier(DescriptionResponse); + + _deviceInfo.length(LEN_DEVICE_INFORMATION_DIB); + _deviceInfo.code(DEVICE_INFO); +#if MASK_VERSION == 0x57B0 + _deviceInfo.medium(0x20); //MediumType is IP (for IP-Only Devices) +#else + _deviceInfo.medium(0x02); //MediumType is TP +#endif + _deviceInfo.status(deviceObject.progMode()); + _deviceInfo.individualAddress(parameters.propertyValue(PID_KNX_INDIVIDUAL_ADDRESS)); + _deviceInfo.projectInstallationIdentifier(parameters.propertyValue(PID_PROJECT_INSTALLATION_ID)); + _deviceInfo.serialNumber(deviceObject.propertyData(PID_SERIAL_NUMBER)); + _deviceInfo.routingMulticastAddress(parameters.propertyValue(PID_ROUTING_MULTICAST_ADDRESS)); + //_deviceInfo.routingMulticastAddress(0); + + uint8_t mac_address[LEN_MAC_ADDRESS] = {0}; + Property* prop = parameters.property(PID_MAC_ADDRESS); + prop->read(mac_address); + _deviceInfo.macAddress(mac_address); + + uint8_t friendlyName[LEN_FRIENDLY_NAME] = {0}; + prop = parameters.property(PID_FRIENDLY_NAME); + prop->read(1, LEN_FRIENDLY_NAME, friendlyName); + _deviceInfo.friendlyName(friendlyName); + + _supportedServices.length(LEN_SERVICE_DIB); + _supportedServices.code(SUPP_SVC_FAMILIES); + _supportedServices.serviceVersion(Core, 1); + _supportedServices.serviceVersion(DeviceManagement, 1); +#ifdef KNX_TUNNELING + _supportedServices.serviceVersion(Tunnelling, 1); +#endif +#if MASK_VERSION == 0x091A + _supportedServices.serviceVersion(Routing, 1); +#endif +} + +KnxIpDeviceInformationDIB& KnxIpDescriptionResponse::deviceInfo() +{ + return _deviceInfo; +} + + +KnxIpSupportedServiceDIB& KnxIpDescriptionResponse::supportedServices() +{ + return _supportedServices; +} +#endif diff --git a/src/knx/knx_ip_description_response.h b/src/knx/knx_ip_description_response.h new file mode 100644 index 0000000..f9d1055 --- /dev/null +++ b/src/knx/knx_ip_description_response.h @@ -0,0 +1,21 @@ +#pragma once + +#include "knx_ip_frame.h" +#include "ip_host_protocol_address_information.h" +#include "knx_ip_device_information_dib.h" +#include "knx_ip_supported_service_dib.h" +#include "ip_parameter_object.h" +#ifdef USE_IP + +class KnxIpDescriptionResponse : public KnxIpFrame +{ + public: + KnxIpDescriptionResponse(IpParameterObject& parameters, DeviceObject& deviceObj); + KnxIpDeviceInformationDIB& deviceInfo(); + KnxIpSupportedServiceDIB& supportedServices(); + private: + KnxIpDeviceInformationDIB _deviceInfo; + KnxIpSupportedServiceDIB _supportedServices; +}; + +#endif \ No newline at end of file diff --git a/src/knx/knx_ip_dib.h b/src/knx/knx_ip_dib.h index 236d25c..cb848cf 100644 --- a/src/knx/knx_ip_dib.h +++ b/src/knx/knx_ip_dib.h @@ -12,6 +12,9 @@ enum DescriptionTypeCode : uint8_t IP_CONFIG = 0x03, IP_CUR_CONFIG = 0x04, KNX_ADDRESSES = 0x05, + MANUFACTURER_DATA = 0x06, + TUNNELING_INFO = 0x07, + EXTENDED_DEVICE_INFO = 0x08, MFR_DATA = 0xFE }; diff --git a/src/knx/knx_ip_disconnect_request.cpp b/src/knx/knx_ip_disconnect_request.cpp new file mode 100644 index 0000000..f5f041f --- /dev/null +++ b/src/knx/knx_ip_disconnect_request.cpp @@ -0,0 +1,26 @@ +#include "knx_ip_disconnect_request.h" +#ifdef USE_IP +KnxIpDisconnectRequest::KnxIpDisconnectRequest(uint8_t* data, uint16_t length) + : KnxIpFrame(data, length), _hpaiCtrl(data + LEN_KNXIP_HEADER + 1 /*ChannelId*/ + 1 /*Reserved*/) +{ +} + +KnxIpDisconnectRequest::KnxIpDisconnectRequest() + : KnxIpFrame(1 /*ChannelId*/ + 1 /*Reserved*/ + LEN_KNXIP_HEADER + LEN_IPHPAI), _hpaiCtrl(_data + 1 /*ChannelId*/ + 1 /*Reserved*/ + LEN_KNXIP_HEADER) +{ + serviceTypeIdentifier(DisconnectRequest); +} + +IpHostProtocolAddressInformation& KnxIpDisconnectRequest::hpaiCtrl() +{ + return _hpaiCtrl; +} +uint8_t KnxIpDisconnectRequest::channelId() +{ + return _data[LEN_KNXIP_HEADER]; +} +void KnxIpDisconnectRequest::channelId(uint8_t channelId) +{ + _data[LEN_KNXIP_HEADER] = channelId; +} +#endif \ No newline at end of file diff --git a/src/knx/knx_ip_disconnect_request.h b/src/knx/knx_ip_disconnect_request.h new file mode 100644 index 0000000..560441d --- /dev/null +++ b/src/knx/knx_ip_disconnect_request.h @@ -0,0 +1,17 @@ +#pragma once + +#include "knx_ip_frame.h" +#include "ip_host_protocol_address_information.h" +#ifdef USE_IP +class KnxIpDisconnectRequest : public KnxIpFrame +{ + public: + KnxIpDisconnectRequest(uint8_t* data, uint16_t length); + KnxIpDisconnectRequest(); + IpHostProtocolAddressInformation& hpaiCtrl(); + uint8_t channelId(); + void channelId(uint8_t channelId); + private: + IpHostProtocolAddressInformation _hpaiCtrl; +}; +#endif \ No newline at end of file diff --git a/src/knx/knx_ip_disconnect_response.cpp b/src/knx/knx_ip_disconnect_response.cpp new file mode 100644 index 0000000..d232539 --- /dev/null +++ b/src/knx/knx_ip_disconnect_response.cpp @@ -0,0 +1,12 @@ +#include "knx_ip_disconnect_response.h" +#ifdef USE_IP + +KnxIpDisconnectResponse::KnxIpDisconnectResponse(uint8_t channel, uint8_t status) + : KnxIpFrame(LEN_KNXIP_HEADER + 1 /*Channel*/ + 1 /*Status*/) +{ + serviceTypeIdentifier(DisconnectResponse); + + _data[LEN_KNXIP_HEADER] = channel; + _data[LEN_KNXIP_HEADER+1] = status; +} +#endif diff --git a/src/knx/knx_ip_disconnect_response.h b/src/knx/knx_ip_disconnect_response.h new file mode 100644 index 0000000..26ef875 --- /dev/null +++ b/src/knx/knx_ip_disconnect_response.h @@ -0,0 +1,13 @@ +#pragma once + +#include "knx_ip_frame.h" +#ifdef USE_IP + +class KnxIpDisconnectResponse : public KnxIpFrame +{ + public: + KnxIpDisconnectResponse(uint8_t channel, uint8_t status); + private: +}; + +#endif \ No newline at end of file diff --git a/src/knx/knx_ip_extended_device_information_dib.cpp b/src/knx/knx_ip_extended_device_information_dib.cpp new file mode 100644 index 0000000..574f9a2 --- /dev/null +++ b/src/knx/knx_ip_extended_device_information_dib.cpp @@ -0,0 +1,42 @@ +#include "knx_ip_extended_device_information_dib.h" +#include "bits.h" + +#ifdef USE_IP +KnxIpExtendedDeviceInformationDIB::KnxIpExtendedDeviceInformationDIB(uint8_t* data) : KnxIpDIB(data) +{} + +uint8_t KnxIpExtendedDeviceInformationDIB::status() const +{ + return _data[2]; +} + + +void KnxIpExtendedDeviceInformationDIB::status(uint8_t value) +{ + _data[2] = value; +} + + +uint16_t KnxIpExtendedDeviceInformationDIB::localMaxApdu() const +{ + return getWord(_data + 4); +} + + +void KnxIpExtendedDeviceInformationDIB::localMaxApdu(uint16_t value) +{ + pushWord(value, _data + 4); +} + + +uint16_t KnxIpExtendedDeviceInformationDIB::deviceDescriptor() const +{ + return getWord(_data + 6); +} + + +void KnxIpExtendedDeviceInformationDIB::deviceDescriptor(uint16_t value) +{ + pushWord(value, _data + 6); +} +#endif \ No newline at end of file diff --git a/src/knx/knx_ip_extended_device_information_dib.h b/src/knx/knx_ip_extended_device_information_dib.h new file mode 100644 index 0000000..df46f5e --- /dev/null +++ b/src/knx/knx_ip_extended_device_information_dib.h @@ -0,0 +1,19 @@ +#pragma once +#include "knx_ip_dib.h" + +#ifdef USE_IP +#define LEN_EXTENDED_DEVICE_INFORMATION_DIB 8 + +class KnxIpExtendedDeviceInformationDIB : public KnxIpDIB +{ + public: + KnxIpExtendedDeviceInformationDIB(uint8_t* data); + uint8_t status() const; + void status(uint8_t value); + uint16_t localMaxApdu() const; + void localMaxApdu(uint16_t value); + uint16_t deviceDescriptor() const; + void deviceDescriptor(uint16_t value); +}; + +#endif \ No newline at end of file diff --git a/src/knx/knx_ip_knx_addresses_dib.cpp b/src/knx/knx_ip_knx_addresses_dib.cpp new file mode 100644 index 0000000..63b5f7b --- /dev/null +++ b/src/knx/knx_ip_knx_addresses_dib.cpp @@ -0,0 +1,27 @@ +#include "knx_ip_knx_addresses_dib.h" + +#ifdef USE_IP +KnxIpKnxAddressesDIB::KnxIpKnxAddressesDIB(uint8_t* data) : KnxIpDIB(data) +{ + currentPos = data + 4; +} + +uint16_t KnxIpKnxAddressesDIB::individualAddress() +{ + uint16_t addr = 0; + popWord(addr, _data + 2); + return addr; +} + +void KnxIpKnxAddressesDIB::individualAddress(uint16_t addr) +{ + pushInt(addr, _data + 2); +} + +void KnxIpKnxAddressesDIB::additional(uint16_t addr) +{ + pushWord(addr, currentPos); + currentPos += 2; + length(currentPos - _data); +} +#endif \ No newline at end of file diff --git a/src/knx/knx_ip_knx_addresses_dib.h b/src/knx/knx_ip_knx_addresses_dib.h new file mode 100644 index 0000000..c090552 --- /dev/null +++ b/src/knx/knx_ip_knx_addresses_dib.h @@ -0,0 +1,17 @@ +#pragma once +#include "knx_ip_dib.h" +#include "bits.h" + +#ifdef USE_IP + +class KnxIpKnxAddressesDIB : public KnxIpDIB +{ + public: + KnxIpKnxAddressesDIB(uint8_t* data); + uint16_t individualAddress(); + void individualAddress(uint16_t addr); + void additional(uint16_t addr); + private: + uint8_t *currentPos = 0; +}; +#endif \ No newline at end of file diff --git a/src/knx/knx_ip_search_request_extended.cpp b/src/knx/knx_ip_search_request_extended.cpp new file mode 100644 index 0000000..c838716 --- /dev/null +++ b/src/knx/knx_ip_search_request_extended.cpp @@ -0,0 +1,59 @@ +#include "knx_ip_search_request_extended.h" +#include "bits.h" +#include "service_families.h" +#if KNX_SERVICE_FAMILY_CORE >= 2 +#ifdef USE_IP +KnxIpSearchRequestExtended::KnxIpSearchRequestExtended(uint8_t* data, uint16_t length) + : KnxIpFrame(data, length), _hpai(data + LEN_KNXIP_HEADER) +{ + if(length == LEN_KNXIP_HEADER + LEN_IPHPAI) return; //we dont have SRPs + + int currentPos = LEN_KNXIP_HEADER + LEN_IPHPAI; + while(currentPos < length) + { + switch(data[currentPos+1]) + { + case 0x01: + srpByProgMode = true; + break; + + case 0x02: + srpByMacAddr = true; + srpMacAddr = data + currentPos + 2; + break; + + case 0x03: + srpByService = true; + srpServiceFamilies = data + currentPos; + break; + + case 0x04: + srpRequestDIBs = true; + for(int i = 0; i < data[currentPos]-2; i++) + { + if(data[currentPos+i+2] == 0) continue; + if(data[currentPos+i+2] > REQUESTED_DIBS_MAX) + { + print("Requested DIBs too high "); + continue; + } + requestedDIBs[data[currentPos+i+2]] = true; + } + break; + } + currentPos += data[currentPos]; + }; +} + +IpHostProtocolAddressInformation& KnxIpSearchRequestExtended::hpai() +{ + return _hpai; +} + +bool KnxIpSearchRequestExtended::requestedDIB(uint8_t code) +{ + if(code > REQUESTED_DIBS_MAX) return false; + return requestedDIBs[code]; +} +#endif +#endif \ No newline at end of file diff --git a/src/knx/knx_ip_search_request_extended.h b/src/knx/knx_ip_search_request_extended.h new file mode 100644 index 0000000..6d019d4 --- /dev/null +++ b/src/knx/knx_ip_search_request_extended.h @@ -0,0 +1,26 @@ +#pragma once + +#include "service_families.h" +#if KNX_SERVICE_FAMILY_CORE >= 2 +#include "knx_ip_frame.h" +#include "ip_host_protocol_address_information.h" +#ifdef USE_IP +#define REQUESTED_DIBS_MAX 9 +class KnxIpSearchRequestExtended : public KnxIpFrame +{ + public: + KnxIpSearchRequestExtended(uint8_t* data, uint16_t length); + IpHostProtocolAddressInformation& hpai(); + bool requestedDIB(uint8_t code); + bool srpByProgMode = false; + bool srpByMacAddr = false; + bool srpByService = false; + bool srpRequestDIBs = false; + uint8_t *srpMacAddr = nullptr; + uint8_t *srpServiceFamilies = nullptr; + private: + IpHostProtocolAddressInformation _hpai; + bool requestedDIBs[REQUESTED_DIBS_MAX]; //for now only 1 to 8 +}; +#endif +#endif \ No newline at end of file diff --git a/src/knx/knx_ip_search_response.cpp b/src/knx/knx_ip_search_response.cpp index c4d8830..508617d 100644 --- a/src/knx/knx_ip_search_response.cpp +++ b/src/knx/knx_ip_search_response.cpp @@ -1,10 +1,23 @@ #include "knx_ip_search_response.h" #ifdef USE_IP -#define SERVICE_FAMILIES 2 +#define LEN_SERVICE_FAMILIES 2 +#if MASK_VERSION == 0x091A +#ifdef KNX_TUNNELING +#define LEN_SERVICE_DIB (2 + 4 * LEN_SERVICE_FAMILIES) +#else +#define LEN_SERVICE_DIB (2 + 3 * LEN_SERVICE_FAMILIES) +#endif +#else +#ifdef KNX_TUNNELING +#define LEN_SERVICE_DIB (2 + 3 * LEN_SERVICE_FAMILIES) +#else +#define LEN_SERVICE_DIB (2 + 2 * LEN_SERVICE_FAMILIES) +#endif +#endif KnxIpSearchResponse::KnxIpSearchResponse(IpParameterObject& parameters, DeviceObject& deviceObject) - : KnxIpFrame(LEN_KNXIP_HEADER + LEN_IPHPAI + LEN_DEVICE_INFORMATION_DIB + 2 + 2 * SERVICE_FAMILIES), + : KnxIpFrame(LEN_KNXIP_HEADER + LEN_IPHPAI + LEN_DEVICE_INFORMATION_DIB + LEN_SERVICE_DIB), _controlEndpoint(_data + LEN_KNXIP_HEADER), _deviceInfo(_data + LEN_KNXIP_HEADER + LEN_IPHPAI), _supportedServices(_data + LEN_KNXIP_HEADER + LEN_IPHPAI + LEN_DEVICE_INFORMATION_DIB) { @@ -17,7 +30,11 @@ KnxIpSearchResponse::KnxIpSearchResponse(IpParameterObject& parameters, DeviceOb _deviceInfo.length(LEN_DEVICE_INFORMATION_DIB); _deviceInfo.code(DEVICE_INFO); - _deviceInfo.medium(0x20); //KNX-IP FIXME get this value from somewhere else +#if MASK_VERSION == 0x57B0 + _deviceInfo.medium(0x20); //MediumType is IP (for IP-Only Devices) +#else + _deviceInfo.medium(0x02); //MediumType is TP +#endif _deviceInfo.status(deviceObject.progMode()); _deviceInfo.individualAddress(parameters.propertyValue(PID_KNX_INDIVIDUAL_ADDRESS)); _deviceInfo.projectInstallationIdentifier(parameters.propertyValue(PID_PROJECT_INSTALLATION_ID)); @@ -35,11 +52,16 @@ KnxIpSearchResponse::KnxIpSearchResponse(IpParameterObject& parameters, DeviceOb prop->read(1, LEN_FRIENDLY_NAME, friendlyName); _deviceInfo.friendlyName(friendlyName); - _supportedServices.length(2 + 2 * SERVICE_FAMILIES); + _supportedServices.length(LEN_SERVICE_DIB); _supportedServices.code(SUPP_SVC_FAMILIES); - _supportedServices.serviceVersion(Core, 1); - _supportedServices.serviceVersion(DeviceManagement, 1); -// _supportedServices.serviceVersion(Routing, 1); + _supportedServices.serviceVersion(Core, KNX_SERVICE_FAMILY_CORE); + _supportedServices.serviceVersion(DeviceManagement, KNX_SERVICE_FAMILY_DEVICE_MANAGEMENT); +#ifdef KNX_TUNNELING + _supportedServices.serviceVersion(Tunnelling, KNX_SERVICE_FAMILY_TUNNELING); +#endif +#if MASK_VERSION == 0x091A + _supportedServices.serviceVersion(Routing, KNX_SERVICE_FAMILY_ROUTING); +#endif } diff --git a/src/knx/knx_ip_search_response.h b/src/knx/knx_ip_search_response.h index 5cc84cd..056f975 100644 --- a/src/knx/knx_ip_search_response.h +++ b/src/knx/knx_ip_search_response.h @@ -5,6 +5,7 @@ #include "knx_ip_device_information_dib.h" #include "knx_ip_supported_service_dib.h" #include "ip_parameter_object.h" +#include "service_families.h" #ifdef USE_IP class KnxIpSearchResponse : public KnxIpFrame diff --git a/src/knx/knx_ip_search_response_extended.cpp b/src/knx/knx_ip_search_response_extended.cpp new file mode 100644 index 0000000..eae2182 --- /dev/null +++ b/src/knx/knx_ip_search_response_extended.cpp @@ -0,0 +1,221 @@ +#include "knx_ip_search_response_extended.h" +#include "service_families.h" +#if KNX_SERVICE_FAMILY_CORE >= 2 +#ifdef USE_IP + +#define LEN_SERVICE_FAMILIES 2 +#if MASK_VERSION == 0x091A +#ifdef KNX_TUNNELING +#define LEN_SERVICE_DIB (2 + 4 * LEN_SERVICE_FAMILIES) +#else +#define LEN_SERVICE_DIB (2 + 3 * LEN_SERVICE_FAMILIES) +#endif +#else +#ifdef KNX_TUNNELING +#define LEN_SERVICE_DIB (2 + 3 * LEN_SERVICE_FAMILIES) +#else +#define LEN_SERVICE_DIB (2 + 2 * LEN_SERVICE_FAMILIES) +#endif +#endif + +KnxIpSearchResponseExtended::KnxIpSearchResponseExtended(IpParameterObject& parameters, DeviceObject& deviceObject, int dibLength) + : KnxIpFrame(LEN_KNXIP_HEADER + LEN_IPHPAI + dibLength), + _controlEndpoint(_data + LEN_KNXIP_HEADER) +{ + serviceTypeIdentifier(SearchResponseExt); + + _controlEndpoint.length(LEN_IPHPAI); + _controlEndpoint.code(IPV4_UDP); + _controlEndpoint.ipAddress(parameters.propertyValue(PID_CURRENT_IP_ADDRESS)); + _controlEndpoint.ipPortNumber(KNXIP_MULTICAST_PORT); + + currentPos = LEN_KNXIP_HEADER + LEN_IPHPAI; +} + +void KnxIpSearchResponseExtended::setDeviceInfo(IpParameterObject& parameters, DeviceObject& deviceObject) +{ + println("setDeviceInfo"); + KnxIpDeviceInformationDIB _deviceInfo(_data + currentPos); + _deviceInfo.length(LEN_DEVICE_INFORMATION_DIB); + _deviceInfo.code(DEVICE_INFO); +#if MASK_VERSION == 0x57B0 + _deviceInfo.medium(0x20); //MediumType is IP (for IP-Only Devices) +#else + _deviceInfo.medium(0x02); //MediumType is TP +#endif + _deviceInfo.status(deviceObject.progMode()); + _deviceInfo.individualAddress(parameters.propertyValue(PID_KNX_INDIVIDUAL_ADDRESS)); + _deviceInfo.projectInstallationIdentifier(parameters.propertyValue(PID_PROJECT_INSTALLATION_ID)); + _deviceInfo.serialNumber(deviceObject.propertyData(PID_SERIAL_NUMBER)); + _deviceInfo.routingMulticastAddress(parameters.propertyValue(PID_ROUTING_MULTICAST_ADDRESS)); + //_deviceInfo.routingMulticastAddress(0); + + uint8_t mac_address[LEN_MAC_ADDRESS] = {0}; + Property* prop = parameters.property(PID_MAC_ADDRESS); + prop->read(mac_address); + _deviceInfo.macAddress(mac_address); + + uint8_t friendlyName[LEN_FRIENDLY_NAME] = {0}; + prop = parameters.property(PID_FRIENDLY_NAME); + prop->read(1, LEN_FRIENDLY_NAME, friendlyName); + _deviceInfo.friendlyName(friendlyName); + + currentPos += LEN_DEVICE_INFORMATION_DIB; +} + +void KnxIpSearchResponseExtended::setSupportedServices() +{ + println("setSupportedServices"); + KnxIpSupportedServiceDIB _supportedServices(_data + currentPos); + _supportedServices.length(LEN_SERVICE_DIB); + _supportedServices.code(SUPP_SVC_FAMILIES); + _supportedServices.serviceVersion(Core, KNX_SERVICE_FAMILY_CORE); + _supportedServices.serviceVersion(DeviceManagement, KNX_SERVICE_FAMILY_DEVICE_MANAGEMENT); +#ifdef KNX_TUNNELING + _supportedServices.serviceVersion(Tunnelling, KNX_SERVICE_FAMILY_TUNNELING); +#endif +#if MASK_VERSION == 0x091A + _supportedServices.serviceVersion(Routing, KNX_SERVICE_FAMILY_ROUTING); +#endif + currentPos += LEN_SERVICE_DIB; +} + +void KnxIpSearchResponseExtended::setIpConfig(IpParameterObject& parameters) +{ + println("setIpConfig"); + KnxIpConfigDIB _ipConfig(_data + currentPos); + _ipConfig.length(LEN_IP_CONFIG_DIB); + _ipConfig.code(IP_CONFIG); + _ipConfig.address(parameters.propertyValue(PID_IP_ADDRESS)); + _ipConfig.subnet(parameters.propertyValue(PID_SUBNET_MASK)); + _ipConfig.gateway(parameters.propertyValue(PID_DEFAULT_GATEWAY)); + _ipConfig.info1(parameters.propertyValue(PID_IP_CAPABILITIES)); + _ipConfig.info2(parameters.propertyValue(PID_IP_ASSIGNMENT_METHOD)); + + currentPos += LEN_IP_CONFIG_DIB; +} + +void KnxIpSearchResponseExtended::setIpCurrentConfig(IpParameterObject& parameters) +{ + println("setIpCurrentConfig"); + KnxIpConfigDIB _ipCurConfig(_data + currentPos, true); + _ipCurConfig.length(LEN_IP_CURRENT_CONFIG_DIB); + _ipCurConfig.code(IP_CUR_CONFIG); + _ipCurConfig.address(parameters.propertyValue(PID_CURRENT_IP_ADDRESS)); + _ipCurConfig.subnet(parameters.propertyValue(PID_CURRENT_SUBNET_MASK)); + _ipCurConfig.gateway(parameters.propertyValue(PID_CURRENT_DEFAULT_GATEWAY)); + _ipCurConfig.dhcp(parameters.propertyValue(PID_DHCP_BOOTP_SERVER)); + _ipCurConfig.info1(parameters.propertyValue(PID_CURRENT_IP_ASSIGNMENT_METHOD)); + _ipCurConfig.info2(0x00); //Reserved + + currentPos += LEN_IP_CURRENT_CONFIG_DIB; +} + +void KnxIpSearchResponseExtended::setKnxAddresses(IpParameterObject& parameters, DeviceObject& deviceObject) +{ + println("setKnxAddresses"); + KnxIpKnxAddressesDIB _knxAddresses(_data + currentPos); + _knxAddresses.length(4); //minimum + _knxAddresses.code(KNX_ADDRESSES); + _knxAddresses.individualAddress(deviceObject.individualAddress()); + + uint16_t length = 0; + parameters.readPropertyLength(PID_ADDITIONAL_INDIVIDUAL_ADDRESSES, length); + + const uint8_t *addresses = parameters.propertyData(PID_ADDITIONAL_INDIVIDUAL_ADDRESSES); + + for(int i = 0; i < length; i++) + { + uint16_t additional = 0; + popWord(additional, addresses + i*2); + _knxAddresses.additional(additional); + } + + currentPos += _knxAddresses.length(); +} + +void KnxIpSearchResponseExtended::setTunnelingInfo(IpParameterObject& parameters, DeviceObject& deviceObject, KnxIpTunnelConnection tunnels[]) +{ + println("setTunnelingInfo"); + KnxIpTunnelingInfoDIB _tunnelInfo(_data + currentPos); + _tunnelInfo.length(4); //minlength + _tunnelInfo.code(TUNNELING_INFO); + _tunnelInfo.apduLength(254); //FIXME where to get from + + uint16_t length = 0; + parameters.readPropertyLength(PID_ADDITIONAL_INDIVIDUAL_ADDRESSES, length); + + const uint8_t *addresses; + if(length == KNX_TUNNELING) + { + addresses = parameters.propertyData(PID_ADDITIONAL_INDIVIDUAL_ADDRESSES); + } else { + uint8_t addrbuffer[KNX_TUNNELING*2]; + addresses = (uint8_t*)addrbuffer; + for(int i = 0; i < KNX_TUNNELING; i++) + { + addrbuffer[i*2+1] = i+1; + addrbuffer[i*2] = deviceObject.individualAddress() / 0x0100; + } + } + + for(int i = 0; i < length; i++) + { + uint16_t additional = 0; + popWord(additional, addresses + i*2); + uint16_t flags = 0; + + uint8_t doubleCounter = 0; + bool used = false; + for(int i = 0; i < KNX_TUNNELING; i++) + { + if(tunnels[i].IndividualAddress == additional) + { + doubleCounter += 1; + if(tunnels[i].ChannelId != 0) + used = true; + } + } + + if(doubleCounter > 1 && used) + flags |= 1 << 2; //Slot is not usable; double PA is already used + + if(used) + { + flags |= 1 << 2; //Slot is not usable; PA is already used + flags |= 1; //Slot is not free + } + + flags = ~flags; + + _tunnelInfo.tunnelingSlot(additional, flags); + } + + currentPos += _tunnelInfo.length(); +} + +void KnxIpSearchResponseExtended::setExtendedDeviceInfo() +{ + println("setExtendedDeviceInfo"); + KnxIpExtendedDeviceInformationDIB _extended(_data + currentPos); + _extended.length(LEN_EXTENDED_DEVICE_INFORMATION_DIB); + _extended.code(EXTENDED_DEVICE_INFO); + _extended.status(0x01); //FIXME dont know encoding PID_MEDIUM_STATUS=51 RouterObject + _extended.localMaxApdu(254); //FIXME is this correct? + _extended.deviceDescriptor(MASK_VERSION); + + currentPos += LEN_EXTENDED_DEVICE_INFORMATION_DIB; +} + +IpHostProtocolAddressInformation& KnxIpSearchResponseExtended::controlEndpoint() +{ + return _controlEndpoint; +} + + +uint8_t *KnxIpSearchResponseExtended::DIBs() +{ + return _data + LEN_KNXIP_HEADER + LEN_IPHPAI; +} +#endif +#endif \ No newline at end of file diff --git a/src/knx/knx_ip_search_response_extended.h b/src/knx/knx_ip_search_response_extended.h new file mode 100644 index 0000000..c7d3793 --- /dev/null +++ b/src/knx/knx_ip_search_response_extended.h @@ -0,0 +1,38 @@ +#pragma once + +#include "service_families.h" +#if KNX_SERVICE_FAMILY_CORE >= 2 + +#include "knx_ip_frame.h" +#include "ip_host_protocol_address_information.h" +#include "knx_ip_device_information_dib.h" +#include "knx_ip_extended_device_information_dib.h" +#include "knx_ip_supported_service_dib.h" +#include "knx_ip_config_dib.h" +#include "knx_ip_knx_addresses_dib.h" +#include "knx_ip_tunneling_info_dib.h" +#include "ip_parameter_object.h" +#include "knx_ip_tunnel_connection.h" +#ifdef USE_IP + +class KnxIpSearchResponseExtended : public KnxIpFrame +{ + public: + KnxIpSearchResponseExtended(IpParameterObject& parameters, DeviceObject& deviceObj, int dibLength); + IpHostProtocolAddressInformation& controlEndpoint(); + void setDeviceInfo(IpParameterObject& parameters, DeviceObject& deviceObject); + void setSupportedServices(); + void setIpConfig(IpParameterObject& parameters); + void setIpCurrentConfig(IpParameterObject& parameters); + void setKnxAddresses(IpParameterObject& parameters, DeviceObject& deviceObject); + //setManuData + void setTunnelingInfo(IpParameterObject& parameters, DeviceObject& deviceObject, KnxIpTunnelConnection tunnels[]); + void setExtendedDeviceInfo(); + uint8_t *DIBs(); + private: + IpHostProtocolAddressInformation _controlEndpoint; + int currentPos = 0; +}; + +#endif +#endif \ No newline at end of file diff --git a/src/knx/knx_ip_state_request.cpp b/src/knx/knx_ip_state_request.cpp new file mode 100644 index 0000000..a1828d1 --- /dev/null +++ b/src/knx/knx_ip_state_request.cpp @@ -0,0 +1,16 @@ +#include "knx_ip_state_request.h" +#ifdef USE_IP +KnxIpStateRequest::KnxIpStateRequest(uint8_t* data, uint16_t length) + : KnxIpFrame(data, length), _hpaiCtrl(data + LEN_KNXIP_HEADER + 1 /*ChannelId*/ + 1 /*Reserved*/) +{ +} + +IpHostProtocolAddressInformation& KnxIpStateRequest::hpaiCtrl() +{ + return _hpaiCtrl; +} +uint8_t KnxIpStateRequest::channelId() +{ + return _data[LEN_KNXIP_HEADER]; +} +#endif \ No newline at end of file diff --git a/src/knx/knx_ip_state_request.h b/src/knx/knx_ip_state_request.h new file mode 100644 index 0000000..26cab9b --- /dev/null +++ b/src/knx/knx_ip_state_request.h @@ -0,0 +1,17 @@ +#pragma once + +#include "knx_ip_frame.h" +#include "knx_ip_cri.h" +#include "ip_host_protocol_address_information.h" +#ifdef USE_IP +class KnxIpStateRequest : public KnxIpFrame +{ + public: + KnxIpStateRequest(uint8_t* data, uint16_t length); + IpHostProtocolAddressInformation& hpaiCtrl(); + uint8_t channelId(); + private: + IpHostProtocolAddressInformation _hpaiCtrl; + +}; +#endif \ No newline at end of file diff --git a/src/knx/knx_ip_state_response.cpp b/src/knx/knx_ip_state_response.cpp new file mode 100644 index 0000000..bfb8871 --- /dev/null +++ b/src/knx/knx_ip_state_response.cpp @@ -0,0 +1,26 @@ +#include "knx_ip_state_response.h" +#ifdef USE_IP + +#define LEN_SERVICE_FAMILIES 2 +#if MASK_VERSION == 0x091A +#ifdef KNX_TUNNELING +#define LEN_SERVICE_DIB (2 + 4 * LEN_SERVICE_FAMILIES) +#else +#define LEN_SERVICE_DIB (2 + 3 * LEN_SERVICE_FAMILIES) +#endif +#else +#ifdef KNX_TUNNELING +#define LEN_SERVICE_DIB (2 + 3 * LEN_SERVICE_FAMILIES) +#else +#define LEN_SERVICE_DIB (2 + 2 * LEN_SERVICE_FAMILIES) +#endif +#endif + +KnxIpStateResponse::KnxIpStateResponse(uint8_t channelId, uint8_t errorCode) + : KnxIpFrame(LEN_KNXIP_HEADER + 2) +{ + serviceTypeIdentifier(ConnectionStateResponse); + _data[LEN_KNXIP_HEADER] = channelId; + _data[LEN_KNXIP_HEADER + 1] = errorCode; +} +#endif diff --git a/src/knx/knx_ip_state_response.h b/src/knx/knx_ip_state_response.h new file mode 100644 index 0000000..253de4d --- /dev/null +++ b/src/knx/knx_ip_state_response.h @@ -0,0 +1,13 @@ +#pragma once + +#include "knx_ip_frame.h" +#ifdef USE_IP + +class KnxIpStateResponse : public KnxIpFrame +{ + public: + KnxIpStateResponse(uint8_t channelId, uint8_t errorCode); + private: +}; + +#endif \ No newline at end of file diff --git a/src/knx/knx_ip_tunnel_connection.cpp b/src/knx/knx_ip_tunnel_connection.cpp new file mode 100644 index 0000000..9d4fe2a --- /dev/null +++ b/src/knx/knx_ip_tunnel_connection.cpp @@ -0,0 +1,19 @@ +#include "knx_ip_tunnel_connection.h" + +KnxIpTunnelConnection::KnxIpTunnelConnection() +{ + +} + +void KnxIpTunnelConnection::Reset() +{ + ChannelId = 0; + IpAddress = 0; + PortData = 0; + PortCtrl = 0; + lastHeartbeat = 0; + SequenceCounter_S = 0; + SequenceCounter_R = 255; + IndividualAddress = 0; + IsConfig = false; +} diff --git a/src/knx/knx_ip_tunnel_connection.h b/src/knx/knx_ip_tunnel_connection.h new file mode 100644 index 0000000..e1323ad --- /dev/null +++ b/src/knx/knx_ip_tunnel_connection.h @@ -0,0 +1,24 @@ +#pragma once +#include "config.h" +#include "platform.h" +#include "bits.h" + +class KnxIpTunnelConnection +{ + public: + KnxIpTunnelConnection(); + uint8_t ChannelId = 0; + uint16_t IndividualAddress = 0; + uint32_t IpAddress = 0; + uint16_t PortData = 0; + uint16_t PortCtrl = 0; + uint8_t SequenceCounter_S = 0; + uint8_t SequenceCounter_R = 255; + unsigned long lastHeartbeat = 0; + bool IsConfig = false; + + void Reset(); + + private: + +}; \ No newline at end of file diff --git a/src/knx/knx_ip_tunneling_ack.cpp b/src/knx/knx_ip_tunneling_ack.cpp new file mode 100644 index 0000000..b17ae35 --- /dev/null +++ b/src/knx/knx_ip_tunneling_ack.cpp @@ -0,0 +1,20 @@ +#include "knx_ip_tunneling_ack.h" +#include + +#ifdef USE_IP +KnxIpTunnelingAck::KnxIpTunnelingAck(uint8_t* data, + uint16_t length) : KnxIpFrame(data, length), _ch(_data + LEN_KNXIP_HEADER) +{ +} + +KnxIpTunnelingAck::KnxIpTunnelingAck() + : KnxIpFrame(LEN_KNXIP_HEADER + LEN_CH), _ch(_data + LEN_KNXIP_HEADER) +{ + serviceTypeIdentifier(TunnelingAck); +} + +KnxIpCH& KnxIpTunnelingAck::connectionHeader() +{ + return _ch; +} +#endif \ No newline at end of file diff --git a/src/knx/knx_ip_tunneling_ack.h b/src/knx/knx_ip_tunneling_ack.h new file mode 100644 index 0000000..8ef0983 --- /dev/null +++ b/src/knx/knx_ip_tunneling_ack.h @@ -0,0 +1,17 @@ +#pragma once + +#include "knx_ip_frame.h" +#include "cemi_frame.h" +#include "knx_ip_ch.h" +#ifdef USE_IP + +class KnxIpTunnelingAck : public KnxIpFrame +{ + public: + KnxIpTunnelingAck(uint8_t* data, uint16_t length); + KnxIpTunnelingAck(); + KnxIpCH& connectionHeader(); + private: + KnxIpCH _ch; +}; +#endif \ No newline at end of file diff --git a/src/knx/knx_ip_tunneling_info_dib.cpp b/src/knx/knx_ip_tunneling_info_dib.cpp new file mode 100644 index 0000000..ef5b4ef --- /dev/null +++ b/src/knx/knx_ip_tunneling_info_dib.cpp @@ -0,0 +1,31 @@ +#include "knx_ip_tunneling_info_dib.h" +#include "service_families.h" +#if KNX_SERVICE_FAMILY_CORE >= 2 + +#ifdef USE_IP +KnxIpTunnelingInfoDIB::KnxIpTunnelingInfoDIB(uint8_t* data) : KnxIpDIB(data) +{ + currentPos = data + 4; +} + +uint16_t KnxIpTunnelingInfoDIB::apduLength() +{ + uint16_t addr = 0; + popWord(addr, _data+2); + return addr; +} + +void KnxIpTunnelingInfoDIB::apduLength(uint16_t addr) +{ + pushWord(addr, _data+2); +} + +void KnxIpTunnelingInfoDIB::tunnelingSlot(uint16_t addr, uint16_t state) +{ + pushWord(addr, currentPos); + pushWord(state, currentPos + 2); + currentPos += 4; + length(currentPos - _data); +} +#endif +#endif \ No newline at end of file diff --git a/src/knx/knx_ip_tunneling_info_dib.h b/src/knx/knx_ip_tunneling_info_dib.h new file mode 100644 index 0000000..77d63fd --- /dev/null +++ b/src/knx/knx_ip_tunneling_info_dib.h @@ -0,0 +1,20 @@ +#pragma once +#include "knx_ip_dib.h" +#include "bits.h" +#include "service_families.h" +#if KNX_SERVICE_FAMILY_CORE >= 2 + +#ifdef USE_IP + +class KnxIpTunnelingInfoDIB : public KnxIpDIB +{ + public: + KnxIpTunnelingInfoDIB(uint8_t* data); + uint16_t apduLength(); + void apduLength(uint16_t addr); + void tunnelingSlot(uint16_t addr, uint16_t state); + private: + uint8_t *currentPos = 0; +}; +#endif +#endif \ No newline at end of file diff --git a/src/knx/knx_ip_tunneling_request.cpp b/src/knx/knx_ip_tunneling_request.cpp new file mode 100644 index 0000000..452fbfc --- /dev/null +++ b/src/knx/knx_ip_tunneling_request.cpp @@ -0,0 +1,26 @@ +#include "knx_ip_tunneling_request.h" +#include + +#ifdef USE_IP +KnxIpTunnelingRequest::KnxIpTunnelingRequest(uint8_t* data, + uint16_t length) : KnxIpFrame(data, length), _ch(_data + headerLength()), _frame(data + LEN_CH + headerLength(), length - LEN_CH - headerLength()) +{ +} + +KnxIpTunnelingRequest::KnxIpTunnelingRequest(CemiFrame frame) + : KnxIpFrame(frame.totalLenght() + LEN_CH + LEN_KNXIP_HEADER), _ch(_data + LEN_KNXIP_HEADER), _frame(_data + LEN_CH + LEN_KNXIP_HEADER, frame.totalLenght()) +{ + serviceTypeIdentifier(TunnelingRequest); + memcpy(_data + LEN_KNXIP_HEADER + LEN_CH, frame.data(), frame.totalLenght()); +} + +CemiFrame& KnxIpTunnelingRequest::frame() +{ + return _frame; +} + +KnxIpCH& KnxIpTunnelingRequest::connectionHeader() +{ + return _ch; +} +#endif \ No newline at end of file diff --git a/src/knx/knx_ip_tunneling_request.h b/src/knx/knx_ip_tunneling_request.h new file mode 100644 index 0000000..1373eb7 --- /dev/null +++ b/src/knx/knx_ip_tunneling_request.h @@ -0,0 +1,19 @@ +#pragma once + +#include "knx_ip_frame.h" +#include "cemi_frame.h" +#include "knx_ip_ch.h" +#ifdef USE_IP + +class KnxIpTunnelingRequest : public KnxIpFrame +{ + public: + KnxIpTunnelingRequest(uint8_t* data, uint16_t length); + KnxIpTunnelingRequest(CemiFrame frame); + CemiFrame& frame(); + KnxIpCH& connectionHeader(); + private: + CemiFrame _frame; + KnxIpCH _ch; +}; +#endif \ No newline at end of file diff --git a/src/knx/knx_types.h b/src/knx/knx_types.h index b0a2a95..ecbbd97 100644 --- a/src/knx/knx_types.h +++ b/src/knx/knx_types.h @@ -20,6 +20,15 @@ enum AckType AckRequested = 0x2, //!< We want a DataLinkLayer acknowledgement. }; +enum TPAckType +{ + // see U_ACK_REQ defines in tpuart_data_link_layer.cpp + AckReqNack = 0x04, + AckReqBusy = 0x02, + AckReqAck = 0x01, + AckReqNone = 0x0, +}; + enum AddressType { IndividualAddress = 0, @@ -190,6 +199,12 @@ enum ApduType DeviceDescriptorResponse = 0x340, Restart = 0x380, RestartMasterReset = 0x381, + RoutingTableOpen = 0x3C0, + RoutingTableRead = 0x3C1, + RoutingTableReadResponse = 0x3C2, + RoutingTableWrite = 0x3C3, + MemoryRouterWrite = 0x3CA, + MemoryRouterReadResponse = 0x3C9, AuthorizeRequest = 0x3d1, AuthorizeResponse = 0x3d2, KeyWrite = 0x3d3, @@ -244,3 +259,32 @@ enum DptMedium KNX_RF = 0x02, KNX_IP = 0x05 }; + +enum LCGRPCONFIG +{ + GROUP_6FFF = 0b00000011, + GROUP_7000 = 0b00001100, + GROUP_REPEAT = 0b00010000, + GROUP_6FFFUNLOCK = 0b00000001, + GROUP_6FFFLOCK = 0b00000010, + GROUP_6FFFROUTE = 0b00000011, + GROUP_7000UNLOCK = 0b00000100, + GROUP_7000LOCK = 0b00001000, + GROUP_7000ROUTE = 0b00001100 +}; + +enum LCCONFIG +{ + PHYS_FRAME = 0b00000011, + PHYS_FRAME_UNLOCK = 0b00000001, + PHYS_FRAME_LOCK = 0b00000010, + PHYS_FRAME_ROUT = 0b00000011, + PHYS_REPEAT = 0b00000100, + BROADCAST_LOCK = 0b00001000, + BROADCAST_REPEAT = 0b00010000, + GROUP_IACK_ROUT = 0b00100000, + PHYS_IACK = 0b11000000, + PHYS_IACK_NORMAL = 0b01000000, + PHYS_IACK_ALL = 0b10000000, + PHYS_IACK_NACK = 0b11000000 +}; \ No newline at end of file diff --git a/src/knx/memory.cpp b/src/knx/memory.cpp index c7bdd00..2cedf47 100644 --- a/src/knx/memory.cpp +++ b/src/knx/memory.cpp @@ -114,7 +114,7 @@ void Memory::readMemory() buffer = _tableObjects[i]->restore(buffer); uint16_t memorySize = 0; buffer = popWord(memorySize, buffer); - + println(memorySize); if (memorySize == 0) continue; @@ -279,6 +279,11 @@ void Memory::writeMemory(uint32_t relativeAddress, size_t size, uint8_t* data) _platform.writeNonVolatileMemory(relativeAddress, data, size); } +void Memory::readMemory(uint32_t relativeAddress, size_t size, uint8_t* data) +{ + _platform.readNonVolatileMemory(relativeAddress, data, size); +} + uint8_t* Memory::toAbsolute(uint32_t relativeAddress) { diff --git a/src/knx/memory.h b/src/knx/memory.h index 58a10ef..f0c2298 100644 --- a/src/knx/memory.h +++ b/src/knx/memory.h @@ -35,7 +35,9 @@ typedef VersionCheckResult (*VersionCheckCallback)(uint16_t manufacturerId, uint class Memory { -public: + friend class TableObject; + + public: Memory(Platform& platform, DeviceObject& deviceObject); virtual ~Memory(); void readMemory(); @@ -47,6 +49,7 @@ public: uint8_t* allocMemory(size_t size); void freeMemory(uint8_t* ptr); void writeMemory(uint32_t relativeAddress, size_t size, uint8_t* data); + void readMemory(uint32_t relativeAddress, size_t size, uint8_t* data); uint8_t* toAbsolute(uint32_t relativeAddress); uint32_t toRelative(uint8_t* absoluteAddress); diff --git a/src/knx/network_layer_coupler.cpp b/src/knx/network_layer_coupler.cpp index 038d455..bcfe68d 100644 --- a/src/knx/network_layer_coupler.cpp +++ b/src/knx/network_layer_coupler.cpp @@ -1,4 +1,5 @@ #include "network_layer_coupler.h" +#include "data_link_layer.h" #include "device_object.h" #include "router_object.h" #include "tpdu.h" @@ -86,29 +87,202 @@ bool NetworkLayerCoupler::isGroupAddressInFilterTable(uint16_t groupAddress) } } -bool NetworkLayerCoupler::isRoutedIndividualAddress(uint16_t individualAddress) +bool NetworkLayerCoupler::isRoutedGroupAddress(uint16_t groupAddress, uint8_t sourceInterfaceIndex) { - // TODO: ACKs for frames with individual addresses of the sub line (secondary I/F) - // Check spec. about his - // See PID_MAIN_LCCONFIG/PID_SUB_LCCONFIG: PHYS_IACK - // 0 = not used - // 1 = normal mode (all frames that will be routed or that are addressed to the Coupler itself will be acknowledged) - // 2 = all frames will be acknowledged (useful only to avoid the repetitions of misrouted frames) - // 3 = all frames on point-to-point connectionless – or connection-oriented communication mode shall be negatively acknowledge (NACK). - // This shall serve for protection purposes. (It is useful to prevent all parameterisation in one Subnetwork; the Coupler shall be protected - // too. A typical use case is the protection of a Subnetwork that is located outside a building) + uint8_t lcconfig = LCCONFIG::PHYS_FRAME_ROUT | LCCONFIG::PHYS_REPEAT | LCCONFIG::BROADCAST_REPEAT | LCCONFIG::GROUP_IACK_ROUT | LCCONFIG::PHYS_IACK_NORMAL; // default value from spec. in case prop is not availible. + uint8_t lcgrpconfig = LCGRPCONFIG::GROUP_6FFFROUTE | LCGRPCONFIG::GROUP_7000UNLOCK | LCGRPCONFIG::GROUP_REPEAT; // default value from spec. in case prop is not availible. + Property* prop_lcgrpconfig; + Property* prop_lcconfig; - // Also ACK for our own individual address - if (individualAddress == _deviceObj.individualAddress()) - return true; + if(sourceInterfaceIndex == kPrimaryIfIndex) // direction Prim -> Sec ( e.g. IP -> TP) + { + prop_lcgrpconfig = _rtObjPrimary->property(PID_MAIN_LCGRPCONFIG); + prop_lcconfig = _rtObjPrimary->property(PID_MAIN_LCCONFIG); + } + else // direction Sec -> Prim ( e.g. TP -> IP) + { + prop_lcgrpconfig = _rtObjPrimary->property(PID_SUB_LCGRPCONFIG); + prop_lcconfig = _rtObjPrimary->property(PID_SUB_LCCONFIG); + } + if(prop_lcgrpconfig) + prop_lcgrpconfig->read(lcgrpconfig); + + if(prop_lcconfig) + prop_lcconfig->read(lcconfig); + + + if(groupAddress < 0x7000) // Main group 0-13 + { + // PID_SUB_LCGRPCONFIG Bit 0-1 + switch(lcgrpconfig & LCGRPCONFIG::GROUP_6FFF) + { + case LCGRPCONFIG::GROUP_6FFFLOCK: + //printHex("1drop frame to 0x", (uint8_t*)destination, 2); + return false;//drop + break; + case LCGRPCONFIG::GROUP_6FFFROUTE: + if(isGroupAddressInFilterTable(groupAddress)) + ;//send + else + { + //printHex("2drop frame to 0x", (uint8_t*)destination, 2); + return false;//drop + } + break; + default: // LCGRPCONFIG::GROUP_6FFFUNLOCK + ;//send + } + } + else // Main group 14-31 + { + // PID_SUB_LCGRPCONFIG Bit 2-3 LCGRPCONFIG::GROUP_7000 + switch(lcgrpconfig & LCGRPCONFIG::GROUP_7000) + { + case LCGRPCONFIG::GROUP_7000LOCK: + //printHex("3drop frame to 0x", (uint8_t*)destination, 2); + return false;//drop + break; + case LCGRPCONFIG::GROUP_7000ROUTE: + if(isGroupAddressInFilterTable(groupAddress)) + ;//send + else + { + //printHex("4drop frame to 0x", (uint8_t*)destination, 2); + return false;//drop + } + break; + default: // LCGRPCONFIG::GROUP_7000UNLOCK + ;//send + } + } - // use 2 for now return true; } +bool NetworkLayerCoupler::isRoutedIndividualAddress(uint16_t individualAddress, uint8_t srcIfIndex) +{ + // TODO: improve: we have to be notified about anything that might affect routing decision + // Ugly: we could ALWAYS evaluate coupler type for every received frame + if (_currentAddress != _deviceObj.individualAddress()) + { + evaluateCouplerType(); + } + + // See KNX spec.: Network Layer (03/03/03) and AN161 (Coupler model 2.0) + /* + * C hop count value contained in the N-protocol header + * D low order octet of the Destination Address, i.e. Device Address part + * G Group Address + * SD low nibble of high order octet plus low order octet, i.e. Line Address + Device Address + * Z high nibble of high order octet of the Destination Address, i.e. Area Address + * ZS high order octet of the Destination Address, i.e. hierarchy information part: Area Address + Line Address + */ + uint16_t ownSNA = _deviceObj.individualAddress() & 0xFF00; // Own subnetwork address (area + line) + uint16_t ownAA = _deviceObj.individualAddress() & 0xF000; // Own area address + uint16_t ZS = individualAddress & 0xFF00; // destination subnetwork address (area + line) + uint16_t Z = individualAddress & 0xF000; // destination area address + + + if (_couplerType == LineCoupler) + { + // Main line to sub line routing + if (srcIfIndex == kPrimaryIfIndex) + { + if (ZS != ownSNA) + { + // IGNORE_TOTALLY + return false; + } + return true; + } + else if (srcIfIndex == kSecondaryIfIndex) // Sub line to main line routing + { + if (ZS != ownSNA) + { + // ROUTE_XXX + return true; + } + else + { + return false; + } + } + else + { + //not from primiary not from sec if, should not happen + return false; + } + } + else if (_couplerType == BackboneCoupler) + { + // Backbone line to main line routing + if (srcIfIndex == kPrimaryIfIndex) + { + if (Z != ownAA) + { + return false; + } + + return true; + } + else if (srcIfIndex == kSecondaryIfIndex) // Main line to backbone line routing + { + if (Z != ownAA) + { + return true; + } + else + { + return false; + } + } + else + { + //not from primiary not from sec if, should not happen + return false; + } + } + else + { + //unknown coupler type, should not happen + return false; + } +} + void NetworkLayerCoupler::sendMsgHopCount(AckType ack, AddressType addrType, uint16_t destination, NPDU& npdu, Priority priority, SystemBroadcast broadcastType, uint8_t sourceInterfaceIndex, uint16_t source) { + uint8_t interfaceIndex = (sourceInterfaceIndex == kSecondaryIfIndex) ? kPrimaryIfIndex : kSecondaryIfIndex; + + uint8_t lcconfig = LCCONFIG::PHYS_FRAME_ROUT | LCCONFIG::PHYS_REPEAT | LCCONFIG::BROADCAST_REPEAT | LCCONFIG::GROUP_IACK_ROUT | LCCONFIG::PHYS_IACK_NORMAL; // default value from spec. in case prop is not availible. + uint8_t lcgrpconfig = LCGRPCONFIG::GROUP_6FFFROUTE | LCGRPCONFIG::GROUP_7000UNLOCK | LCGRPCONFIG::GROUP_REPEAT; // default value from spec. in case prop is not availible. + Property* prop_lcgrpconfig; + Property* prop_lcconfig; + + if(sourceInterfaceIndex == kPrimaryIfIndex) // direction Prim -> Sec ( e.g. IP -> TP) + { + prop_lcgrpconfig = _rtObjPrimary->property(PID_MAIN_LCGRPCONFIG); + prop_lcconfig = _rtObjPrimary->property(PID_MAIN_LCCONFIG); + } + else // direction Sec -> Prim ( e.g. TP -> IP) + { + prop_lcgrpconfig = _rtObjPrimary->property(PID_SUB_LCGRPCONFIG); + prop_lcconfig = _rtObjPrimary->property(PID_SUB_LCCONFIG); + } + if(prop_lcgrpconfig) + prop_lcgrpconfig->read(lcgrpconfig); + + if(prop_lcconfig) + prop_lcconfig->read(lcconfig); + + + if(addrType == AddressType::GroupAddress && destination != 0) // destination == 0 means broadcast and must not be filtered with the GroupAddresses + { + if(!isRoutedGroupAddress(destination, sourceInterfaceIndex)) + return; // drop; + } + + // If we have a frame from open medium on secondary side (e.g. RF) to primary side, then shall use the hop count of the primary router object if ((_rtObjPrimary != nullptr) && (_rtObjSecondary != nullptr) && (sourceInterfaceIndex == kSecondaryIfIndex)) { @@ -138,10 +312,10 @@ void NetworkLayerCoupler::sendMsgHopCount(AckType ack, AddressType addrType, uin { // ROUTE_UNMODIFIED } -} + } // Use other interface - uint8_t interfaceIndex = (sourceInterfaceIndex == kSecondaryIfIndex) ? kPrimaryIfIndex : kSecondaryIfIndex; +#ifdef KNX_LOG_COUPLER if (sourceInterfaceIndex == 0) print("Routing from P->S: "); else @@ -149,144 +323,104 @@ void NetworkLayerCoupler::sendMsgHopCount(AckType ack, AddressType addrType, uin print(source, HEX); print(" -> "); print(destination, HEX); print(" - "); npdu.frame().apdu().printPDU(); - _netLayerEntities[interfaceIndex].sendDataRequest(npdu, ack, destination, source, priority, addrType, broadcastType); +#endif + + //evaluiate PHYS_REPEAT, BROADCAST_REPEAT and GROUP_REPEAT + bool doNotRepeat = false; + if((addrType == AddressType::GroupAddress && !(lcgrpconfig & LCGRPCONFIG::GROUP_REPEAT)) || + (addrType == AddressType::IndividualAddress && !(lcconfig & LCCONFIG::PHYS_REPEAT)) || + (addrType == AddressType::GroupAddress && destination == 0 && !(lcconfig & LCCONFIG::BROADCAST_REPEAT))) + doNotRepeat = true; + + _netLayerEntities[interfaceIndex].sendDataRequest(npdu, ack, destination, source, priority, addrType, broadcastType, doNotRepeat); } // TODO: for later: improve by putting routing algorithms in its own class/functions and only instantiate required algorithm (line vs. coupler) // TODO: we could also do the sanity checks here, i.e. check if sourceAddress is really coming in from correct srcIfIdx, etc. (see PID_COUPL_SERV_CONTROL: EN_SNA_INCONSISTENCY_CHECK) void NetworkLayerCoupler::routeDataIndividual(AckType ack, uint16_t destination, NPDU& npdu, Priority priority, uint16_t source, uint8_t srcIfIndex) { - // TODO: improve: we have to be notified about anything that might affect routing decision - // Ugly: we could ALWAYS evaluate coupler type for every received frame - if (_currentAddress != _deviceObj.individualAddress()) + //print("NetworkLayerCoupler::routeDataIndividual dest 0x"); + //print(destination, HEX); + //print(" own addr 0x"); + //println(_deviceObj.individualAddress(), HEX); + + if(destination == _deviceObj.individualAddress()) { - evaluateCouplerType(); + // FORWARD_LOCALLY + //println("NetworkLayerCoupler::routeDataIndividual locally"); + HopCountType hopType = npdu.hopCount() == 7 ? UnlimitedRouting : NetworkLayerParameter; + _transportLayer.dataIndividualIndication(destination, hopType, priority, source, npdu.tpdu()); + return; } - // See KNX spec.: Network Layer (03/03/03) and AN161 (Coupler model 2.0) - /* - * C hop count value contained in the N-protocol header - * D low order octet of the Destination Address, i.e. Device Address part - * G Group Address - * SD low nibble of high order octet plus low order octet, i.e. Line Address + Device Address - * Z high nibble of high order octet of the Destination Address, i.e. Area Address - * ZS high order octet of the Destination Address, i.e. hierarchy information part: Area Address + Line Address - */ - uint16_t ownSNA = _deviceObj.individualAddress() & 0xFF00; // Own subnetwork address (area + line) - uint16_t ownAA = _deviceObj.individualAddress() & 0xF000; // Own area address - uint16_t ZS = destination & 0xFF00; // destination subnetwork address (area + line) - uint16_t Z = destination & 0xF000; // destination area address - uint16_t D = destination & 0x00FF; // destination device address (without subnetwork part) - uint16_t SD = destination & 0x0FFF; // destination device address (with line part, but without area part) - - if (_couplerType == LineCoupler) + // Local to main or sub line + if (srcIfIndex == kLocalIfIndex) { - // Main line to sub line routing - if (srcIfIndex == kPrimaryIfIndex) + uint16_t netaddr; + uint16_t Z; + if(_couplerType == CouplerType::BackboneCoupler) { - if (ZS != ownSNA) - { - // IGNORE_TOTALLY - return; - } - - if (D == 0) - { - // FORWARD_LOCALLY - HopCountType hopType = npdu.hopCount() == 7 ? UnlimitedRouting : NetworkLayerParameter; - _transportLayer.dataIndividualIndication(destination, hopType, priority, source, npdu.tpdu()); - } - else - { // ROUTE_XXX - sendMsgHopCount(ack, AddressType::IndividualAddress, destination, npdu, priority, Broadcast, srcIfIndex, source); - } - return; + netaddr = _deviceObj.individualAddress() & 0xF000; + Z = destination & 0xF000; } - - // Sub line to main line routing - if (srcIfIndex == kSecondaryIfIndex) + else if(_couplerType == CouplerType::LineCoupler) { - if (ZS != ownSNA) - { - // ROUTE_XXX - sendMsgHopCount(ack, AddressType::IndividualAddress, destination, npdu, priority, Broadcast, srcIfIndex, source); - } - else if (D == 0) - { - // FORWARD_LOCALLY - HopCountType hopType = npdu.hopCount() == 7 ? UnlimitedRouting : NetworkLayerParameter; - _transportLayer.dataIndividualIndication(destination, hopType, priority, source, npdu.tpdu()); - } - else - { - // IGNORE_TOTALLY - } - return; + netaddr = _deviceObj.individualAddress() & 0xFF00; + Z = destination & 0xFF00; } - - // Local to main or sub line - if (srcIfIndex == kLocalIfIndex) + else { - // if destination is not within our subnet then send via primary interface, else via secondary interface - uint8_t destIfidx = (ZS != ownSNA) ? kPrimaryIfIndex : kSecondaryIfIndex; - _netLayerEntities[destIfidx].sendDataRequest(npdu, ack, destination, source, priority, AddressType::IndividualAddress, Broadcast); - return; + //unknown coupler type, should not happen + return ; } + + + // if destination is not within our scope then send via primary interface, else via secondary interface + uint8_t destIfidx = (Z != netaddr) ? kPrimaryIfIndex : kSecondaryIfIndex; +#ifdef KNX_TUNNELING + if(destIfidx == kPrimaryIfIndex) + if(isTunnelAddress(destination)) + destIfidx = kSecondaryIfIndex; +#endif + //print("NetworkLayerCoupler::routeDataIndividual local to s or p: "); + //println(destIfidx); + _netLayerEntities[destIfidx].sendDataRequest(npdu, ack, destination, source, priority, AddressType::IndividualAddress, Broadcast); + return; } - if (_couplerType == BackboneCoupler) + uint8_t lcconfig = LCCONFIG::PHYS_FRAME_ROUT | LCCONFIG::PHYS_REPEAT | LCCONFIG::BROADCAST_REPEAT | LCCONFIG::GROUP_IACK_ROUT | LCCONFIG::PHYS_IACK_NORMAL; // default value from spec. in case prop is not availible. + Property* prop_lcconfig; + if(srcIfIndex == kPrimaryIfIndex) // direction Prim -> Sec ( e.g. IP -> TP) + prop_lcconfig = _rtObjPrimary->property(PID_MAIN_LCCONFIG); + else // direction Sec -> Prim ( e.g. TP -> IP) + prop_lcconfig = _rtObjPrimary->property(PID_SUB_LCCONFIG); + if(prop_lcconfig) + prop_lcconfig->read(lcconfig); + + if((lcconfig & LCCONFIG::PHYS_FRAME) == LCCONFIG::PHYS_FRAME_LOCK) { - // Backbone line to main line routing - if (srcIfIndex == kPrimaryIfIndex) + // IGNORE_TOTALLY + //println("NetworkLayerCoupler::routeDataIndividual locked"); + return; + } + else if((lcconfig & LCCONFIG::PHYS_FRAME) == LCCONFIG::PHYS_FRAME_UNLOCK) + { + // ROUTE_XXX + //println("NetworkLayerCoupler::routeDataIndividual unlocked"); + sendMsgHopCount(ack, AddressType::IndividualAddress, destination, npdu, priority, Broadcast, srcIfIndex, source); + return; + } + else // LCCONFIG::PHYS_FRAME_ROUTE or 0 + { + if(isRoutedIndividualAddress(destination, srcIfIndex)) { - if (Z != ownAA) - { - // IGNORE_TOTALLY - return; - } - - if (SD == 0) - { - // FORWARD_LOCALLY - HopCountType hopType = npdu.hopCount() == 7 ? UnlimitedRouting : NetworkLayerParameter; - _transportLayer.dataIndividualIndication(destination, hopType, priority, source, npdu.tpdu()); - } - else - { - // ROUTE_XXX - sendMsgHopCount(ack, AddressType::IndividualAddress, destination, npdu, priority, Broadcast, srcIfIndex, source); - } - return; + //println("NetworkLayerCoupler::routeDataIndividual routed"); + sendMsgHopCount(ack, AddressType::IndividualAddress, destination, npdu, priority, Broadcast, srcIfIndex, source); // ROUTE_XXX } - - // Main line to backbone line routing - if (srcIfIndex == kSecondaryIfIndex) + else { - if (Z != ownAA) - { - // ROUTE_XXX - sendMsgHopCount(ack, AddressType::IndividualAddress, destination, npdu, priority, Broadcast, srcIfIndex, source); - } - else if(SD == 0) - { - // FORWARD_LOCALLY - HopCountType hopType = npdu.hopCount() == 7 ? UnlimitedRouting : NetworkLayerParameter; - _transportLayer.dataIndividualIndication(destination, hopType, priority, source, npdu.tpdu()); - } - else - { - // IGNORE_TOTALLY - } - return; - } - - // Local to main or sub line - if (srcIfIndex == kLocalIfIndex) - { - // if destination is not within our area then send via primary interface, else via secondary interface - uint8_t destIfidx = (Z != ownAA) ? kPrimaryIfIndex : kSecondaryIfIndex; - _netLayerEntities[destIfidx].sendDataRequest(npdu, ack, destination, source, priority, AddressType::IndividualAddress, Broadcast); - return; + //println("NetworkLayerCoupler::routeDataIndividual not routed"); + ; // IGNORE_TOTALLY } } } @@ -296,31 +430,25 @@ void NetworkLayerCoupler::dataIndication(AckType ack, AddressType addrType, uint // routing for individual addresses if (addrType == IndividualAddress) { + //printHex("NetworkLayerCoupler::dataIndication to IA ", (uint8_t*)&destination, 2); + //npdu.frame().valid(); routeDataIndividual(ack, destination, npdu, priority, source, srcIfIdx); return; } - + //printHex("NetworkLayerCoupler::dataIndication to GA ", (uint8_t*)&destination, 2); // routing for group addresses // TODO: check new AN189 // "AN189 only makes that group messages with hop count 7 cannot bypass the Filter Table unfiltered, // what made the Security Proxy(AN192) useless; now, hc 7 Telegrams are filtered as any other and the value is decremented. - if (isGroupAddressInFilterTable(destination)) - { - // ROUTE_XXX - sendMsgHopCount(ack, addrType, destination, npdu, priority, Broadcast, srcIfIdx, source); - return; - } - else - { - // IGNORE_TOTALLY - return; - } - println("Unhandled routing case! Should not happen!"); + // ROUTE_XXX + sendMsgHopCount(ack, addrType, destination, npdu, priority, Broadcast, srcIfIdx, source); + return; } void NetworkLayerCoupler::dataConfirm(AckType ack, AddressType addrType, uint16_t destination, FrameFormat format, Priority priority, uint16_t source, NPDU& npdu, bool status, uint8_t srcIfIdx) { + //println("NetworkLayerCoupler::dataConfirm"); HopCountType hopType = npdu.hopCount() == 7 ? UnlimitedRouting : NetworkLayerParameter; // Check if received frame is an echo from our sent frame, we are a normal device in this case @@ -356,8 +484,18 @@ void NetworkLayerCoupler::broadcastIndication(AckType ack, FrameFormat format, N _transportLayer.dataBroadcastIndication(hopType, priority, source, npdu.tpdu()); } + uint8_t lcconfig = LCCONFIG::PHYS_FRAME_ROUT | LCCONFIG::PHYS_REPEAT | LCCONFIG::BROADCAST_REPEAT | LCCONFIG::GROUP_IACK_ROUT | LCCONFIG::PHYS_IACK_NORMAL; // default value from spec. in case prop is not availible. + Property* prop_lcconfig; + if(srcIfIdx == kPrimaryIfIndex) // direction Prim -> Sec ( e.g. IP -> TP) + prop_lcconfig = _rtObjPrimary->property(PID_MAIN_LCCONFIG); + else // direction Sec -> Prim ( e.g. TP -> IP) + prop_lcconfig = _rtObjPrimary->property(PID_SUB_LCCONFIG); + if(prop_lcconfig) + prop_lcconfig->read(lcconfig); + // Route to other interface - sendMsgHopCount(ack, GroupAddress, 0, npdu, priority, Broadcast, srcIfIdx, source); + if(!(lcconfig & LCCONFIG::BROADCAST_LOCK)) + sendMsgHopCount(ack, GroupAddress, 0, npdu, priority, Broadcast, srcIfIdx, source); } void NetworkLayerCoupler::broadcastConfirm(AckType ack, FrameFormat format, Priority priority, uint16_t source, NPDU& npdu, bool status, uint8_t srcIfIdx) @@ -379,8 +517,19 @@ void NetworkLayerCoupler::systemBroadcastIndication(AckType ack, FrameFormat for HopCountType hopType = npdu.hopCount() == 7 ? UnlimitedRouting : NetworkLayerParameter; _transportLayer.dataSystemBroadcastIndication(hopType, priority, source, npdu.tpdu()); } + + uint8_t lcconfig = LCCONFIG::PHYS_FRAME_ROUT | LCCONFIG::PHYS_REPEAT | LCCONFIG::BROADCAST_REPEAT | LCCONFIG::GROUP_IACK_ROUT | LCCONFIG::PHYS_IACK_NORMAL; // default value from spec. in case prop is not availible. + Property* prop_lcconfig; + if(srcIfIdx == kPrimaryIfIndex) // direction Prim -> Sec ( e.g. IP -> TP) + prop_lcconfig = _rtObjPrimary->property(PID_MAIN_LCCONFIG); + else // direction Sec -> Prim ( e.g. TP -> IP) + prop_lcconfig = _rtObjPrimary->property(PID_SUB_LCCONFIG); + if(prop_lcconfig) + prop_lcconfig->read(lcconfig); + // Route to other interface - sendMsgHopCount(ack, GroupAddress, 0, npdu, priority, SysBroadcast, srcIfIdx, source); + if(!(lcconfig & LCCONFIG::BROADCAST_LOCK)) + sendMsgHopCount(ack, GroupAddress, 0, npdu, priority, SysBroadcast, srcIfIdx, source); } void NetworkLayerCoupler::systemBroadcastConfirm(AckType ack, FrameFormat format, Priority priority, uint16_t source, NPDU& npdu, bool status, uint8_t srcIfIdx) @@ -438,8 +587,10 @@ void NetworkLayerCoupler::dataBroadcastRequest(AckType ack, HopCountType hopType else npdu.hopCount(hopCount()); + CemiFrame tmpFrame(tpdu.frame()); + _netLayerEntities[kPrimaryIfIndex].sendDataRequest(npdu, ack, 0, _deviceObj.individualAddress(), priority, GroupAddress, Broadcast); - _netLayerEntities[kSecondaryIfIndex].sendDataRequest(npdu, ack, 0, _deviceObj.individualAddress(), priority, GroupAddress, Broadcast); + _netLayerEntities[kSecondaryIfIndex].sendDataRequest(tmpFrame.npdu(), ack, 0, _deviceObj.individualAddress(), priority, GroupAddress, Broadcast); } void NetworkLayerCoupler::dataSystemBroadcastRequest(AckType ack, HopCountType hopType, Priority priority, TPDU& tpdu) @@ -451,6 +602,24 @@ void NetworkLayerCoupler::dataSystemBroadcastRequest(AckType ack, HopCountType h else npdu.hopCount(hopCount()); - _netLayerEntities[kPrimaryIfIndex].sendDataRequest(npdu, ack, 0, _deviceObj.individualAddress(), priority, GroupAddress, SysBroadcast); - _netLayerEntities[kSecondaryIfIndex].sendDataRequest(npdu, ack, 0, _deviceObj.individualAddress(), priority, GroupAddress, SysBroadcast); + + CemiFrame tmpFrame(tpdu.frame()); + + // for closed media like TP1 and IP + bool isClosedMedium = (_netLayerEntities[kPrimaryIfIndex].mediumType() == DptMedium::KNX_TP1) || (_netLayerEntities[kPrimaryIfIndex].mediumType() == DptMedium::KNX_IP); + SystemBroadcast broadcastType = (isClosedMedium && isApciSystemBroadcast(tpdu.apdu()) ? Broadcast : SysBroadcast); + _netLayerEntities[kPrimaryIfIndex].sendDataRequest(npdu, ack, 0, _deviceObj.individualAddress(), priority, GroupAddress, broadcastType); + + isClosedMedium = (_netLayerEntities[kSecondaryIfIndex].mediumType() == DptMedium::KNX_TP1) || (_netLayerEntities[kSecondaryIfIndex].mediumType() == DptMedium::KNX_IP); + broadcastType = (isClosedMedium && isApciSystemBroadcast(tmpFrame.apdu()) ? Broadcast : SysBroadcast); + println(broadcastType); + _netLayerEntities[kSecondaryIfIndex].sendDataRequest(tmpFrame.npdu(), ack, 0, _deviceObj.individualAddress(), priority, GroupAddress, broadcastType); } + +#ifdef KNX_TUNNELING +bool NetworkLayerCoupler::isTunnelAddress(uint16_t destination) +{ + // tunnels are managed within the IpDataLinkLayer - kPrimaryIfIndex + return _netLayerEntities[kPrimaryIfIndex].dataLinkLayer().isTunnelAddress(destination); +} +#endif \ No newline at end of file diff --git a/src/knx/network_layer_coupler.h b/src/knx/network_layer_coupler.h index b57f30a..23f714b 100644 --- a/src/knx/network_layer_coupler.h +++ b/src/knx/network_layer_coupler.h @@ -20,7 +20,9 @@ class NetworkLayerCoupler : public NetworkLayer NetworkLayerEntity& getPrimaryInterface(); NetworkLayerEntity& getSecondaryInterface(); - bool isRoutedIndividualAddress(uint16_t individualAddress); + bool isRoutedIndividualAddress(uint16_t individualAddress, uint8_t srcIfIndex); + + bool isRoutedGroupAddress(uint16_t groupAddress, uint8_t sourceInterfaceIndex); void rtObjPrimary(RouterObject& rtObjPrimary); // Coupler model 2.0 void rtObjSecondary(RouterObject& rtObjSecondary); // Coupler model 2.0 @@ -63,6 +65,9 @@ class NetworkLayerCoupler : public NetworkLayer void evaluateCouplerType(); bool isGroupAddressInFilterTable(uint16_t groupAddress); +#ifdef KNX_TUNNELING + bool isTunnelAddress(uint16_t destination); +#endif // Support a maximum of two physical interfaces for couplers NetworkLayerEntity _netLayerEntities[2]; diff --git a/src/knx/network_layer_entity.cpp b/src/knx/network_layer_entity.cpp index 6463d83..389f765 100644 --- a/src/knx/network_layer_entity.cpp +++ b/src/knx/network_layer_entity.cpp @@ -18,6 +18,11 @@ DataLinkLayer& NetworkLayerEntity::dataLinkLayer() return *_dataLinkLayer; } +NetworkLayer& NetworkLayerEntity::networkLayer() +{ + return _netLayer; +} + DptMedium NetworkLayerEntity::mediumType() const { return _dataLinkLayer->mediumType(); @@ -58,7 +63,7 @@ void NetworkLayerEntity::systemBroadcastConfirm(AckType ack, FrameFormat format, _netLayer.systemBroadcastConfirm(ack, format, priority, source, npdu, status, _entityIndex); } -void NetworkLayerEntity::sendDataRequest(NPDU &npdu, AckType ack, uint16_t destination, uint16_t source, Priority priority, AddressType addrType, SystemBroadcast systemBroadcast) +void NetworkLayerEntity::sendDataRequest(NPDU &npdu, AckType ack, uint16_t destination, uint16_t source, Priority priority, AddressType addrType, SystemBroadcast systemBroadcast, bool doNotRepeat) { FrameFormat frameFormat = npdu.octetCount() > 15 ? ExtendedFrame : StandardFrame; diff --git a/src/knx/network_layer_entity.h b/src/knx/network_layer_entity.h index 82db48c..22599e4 100644 --- a/src/knx/network_layer_entity.h +++ b/src/knx/network_layer_entity.h @@ -17,6 +17,7 @@ class NetworkLayerEntity void dataLinkLayer(DataLinkLayer& layer); DataLinkLayer& dataLinkLayer(); + NetworkLayer& networkLayer(); DptMedium mediumType() const; uint8_t getEntityIndex(); @@ -35,7 +36,7 @@ class NetworkLayerEntity private: // From network layer - void sendDataRequest(NPDU& npdu, AckType ack, uint16_t destination, uint16_t source, Priority priority, AddressType addrType, SystemBroadcast systemBroadcast); + void sendDataRequest(NPDU& npdu, AckType ack, uint16_t destination, uint16_t source, Priority priority, AddressType addrType, SystemBroadcast systemBroadcast, bool doNotRepeat = false); DataLinkLayer* _dataLinkLayer = 0; NetworkLayer& _netLayer; diff --git a/src/knx/platform.cpp b/src/knx/platform.cpp index 2b398ae..51b2d03 100644 --- a/src/knx/platform.cpp +++ b/src/knx/platform.cpp @@ -109,6 +109,11 @@ int Platform::readBytesMultiCast(uint8_t *buffer, uint16_t maxLen) return 0; } +int Platform::readBytesMultiCast(uint8_t* buffer, uint16_t maxLen, uint32_t& src_addr, uint16_t& src_port) +{ + return readBytesMultiCast(buffer, maxLen); +} + size_t Platform::flashEraseBlockSize() { return 0; @@ -193,6 +198,13 @@ void Platform::commitNonVolatileMemory() uint32_t Platform::writeNonVolatileMemory(uint32_t relativeAddress, uint8_t* buffer, size_t size) { +#ifdef KNX_LOG_MEM + print("Platform::writeNonVolatileMemory relativeAddress "); + print(relativeAddress); + print(" size "); + println(size); +#endif + if(_memoryType == Flash) { while (size > 0) @@ -225,6 +237,79 @@ uint32_t Platform::writeNonVolatileMemory(uint32_t relativeAddress, uint8_t* buf } } +uint32_t Platform::readNonVolatileMemory(uint32_t relativeAddress, uint8_t* buffer, size_t size) +{ +#ifdef KNX_LOG_MEM + print("Platform::readNonVolatileMemory relativeAddress "); + print(relativeAddress); + print(" size "); + println(size); +#endif + + if(_memoryType == Flash) + { + uint32_t offset = 0; + while (size > 0) + { + // bufferd block is "left" of requested memory, read until the end and return + if(_bufferedEraseblockNumber < getEraseBlockNumberOf(relativeAddress)) + { + memcpy(buffer+offset, userFlashStart()+relativeAddress, size); + return relativeAddress + size; + } + // bufferd block is "right" of requested memory, and may interfere + else if(_bufferedEraseblockNumber > getEraseBlockNumberOf(relativeAddress)) + { + // if the end of the requested memory is before the buffered block, read until the end and return + int32_t eraseblockNumberEnd = getEraseBlockNumberOf(relativeAddress+size-1); + if(_bufferedEraseblockNumber > eraseblockNumberEnd) + { + memcpy(buffer+offset, userFlashStart()+relativeAddress, size); + return relativeAddress + size; + } + // if not, read until the buffered block starts and loop through while again + else + { + uint32_t sizeToRead = (eraseblockNumberEnd * flashEraseBlockSize()) - relativeAddress; + memcpy(buffer+offset, userFlashStart()+relativeAddress, sizeToRead); + relativeAddress += sizeToRead; + size -= sizeToRead; + offset += sizeToRead; + } + } + // start of requested memory is within the buffered erase block + else + { + // if the end of the requested memory is also in the buffered block, read until the end and return + int32_t eraseblockNumberEnd = getEraseBlockNumberOf(relativeAddress+size-1); + if(_bufferedEraseblockNumber == eraseblockNumberEnd) + { + uint8_t* start = _eraseblockBuffer + (relativeAddress - _bufferedEraseblockNumber * flashEraseBlockSize()); + memcpy(buffer+offset, start, size); + return relativeAddress + size; + } + // if not, read until the end of the buffered block and loop through while again + else + { + uint32_t offsetInBufferedBlock = relativeAddress - _bufferedEraseblockNumber * flashEraseBlockSize(); + uint8_t* start = _eraseblockBuffer + offsetInBufferedBlock; + uint32_t sizeToRead = flashEraseBlockSize() - offsetInBufferedBlock; + memcpy(buffer+offset, start, sizeToRead); + relativeAddress += sizeToRead; + size -= sizeToRead; + offset += sizeToRead; + } + } + } + return relativeAddress; + } + else + { + memcpy(buffer, getEepromBuffer(KNX_FLASH_SIZE)+relativeAddress, size); + return relativeAddress+size; + } +} + // writes value repeat times into flash starting at relativeAddress // returns next free relativeAddress uint32_t Platform::writeNonVolatileMemory(uint32_t relativeAddress, uint8_t value, size_t repeat) diff --git a/src/knx/platform.h b/src/knx/platform.h index ddb1a67..29c46f1 100644 --- a/src/knx/platform.h +++ b/src/knx/platform.h @@ -50,6 +50,7 @@ class Platform virtual void closeMultiCast(); virtual bool sendBytesMultiCast(uint8_t* buffer, uint16_t len); virtual int readBytesMultiCast(uint8_t* buffer, uint16_t maxLen); + virtual int readBytesMultiCast(uint8_t* buffer, uint16_t maxLen, uint32_t& src_addr, uint16_t& src_port); //unicast socket virtual bool sendBytesUniCast(uint32_t addr, uint16_t port, uint8_t* buffer, uint16_t len); @@ -85,6 +86,7 @@ class Platform virtual void commitNonVolatileMemory(); // address is relative to start of nonvolatile memory virtual uint32_t writeNonVolatileMemory(uint32_t relativeAddress, uint8_t* buffer, size_t size); + virtual uint32_t readNonVolatileMemory(uint32_t relativeAddress, uint8_t* buffer, size_t size); virtual uint32_t writeNonVolatileMemory(uint32_t relativeAddress, uint8_t value, size_t repeat); NvMemoryType NonVolatileMemoryType(); diff --git a/src/knx/property.h b/src/knx/property.h index 56c6b24..09a964c 100644 --- a/src/knx/property.h +++ b/src/knx/property.h @@ -139,6 +139,8 @@ enum PropertyID PID_MSG_TRANSMIT_TO_KNX = 75, PID_FRIENDLY_NAME = 76, PID_ROUTING_BUSY_WAIT_TIME = 78, + PID_CUSTOM_RESERVED_TUNNELS_CTRL = 201, // custom propertiy to control the stacks behaviour for reserverd tunnels, not in Spec (PID >= 200) + PID_CUSTOM_RESERVED_TUNNELS_IP = 202, // custom propertiy to control the stacks behaviour for reserverd tunnels, not in Spec (PID >= 200) /** cEMI Server Object */ PID_MEDIUM_TYPE = 51, diff --git a/src/knx/rf_data_link_layer.cpp b/src/knx/rf_data_link_layer.cpp index 7765736..3e0baab 100644 --- a/src/knx/rf_data_link_layer.cpp +++ b/src/knx/rf_data_link_layer.cpp @@ -78,8 +78,8 @@ bool RfDataLinkLayer::sendFrame(CemiFrame& frame) } RfDataLinkLayer::RfDataLinkLayer(DeviceObject& devObj, RfMediumObject& rfMediumObj, - NetworkLayerEntity &netLayerEntity, Platform& platform) - : DataLinkLayer(devObj, netLayerEntity, platform), + NetworkLayerEntity &netLayerEntity, Platform& platform, BusAccessUnit& busAccessUnit) + : DataLinkLayer(devObj, netLayerEntity, platform, busAccessUnit), _rfMediumObj(rfMediumObj), _rfPhy(*this, platform) { diff --git a/src/knx/rf_data_link_layer.h b/src/knx/rf_data_link_layer.h index 79dd28b..93ed1e5 100644 --- a/src/knx/rf_data_link_layer.h +++ b/src/knx/rf_data_link_layer.h @@ -22,7 +22,7 @@ class RfDataLinkLayer : public DataLinkLayer public: RfDataLinkLayer(DeviceObject& devObj, RfMediumObject& rfMediumObj, NetworkLayerEntity& netLayerEntity, - Platform& platform); + Platform& platform, BusAccessUnit& busAccessUnit); void loop(); void enabled(bool value); diff --git a/src/knx/router_object.cpp b/src/knx/router_object.cpp index b7c7b62..70fab47 100644 --- a/src/knx/router_object.cpp +++ b/src/knx/router_object.cpp @@ -8,6 +8,7 @@ #include "callback_property.h" #include "function_property.h" + // Filter Table Realization Type 3 // The Filter Table Realisation Type 3 shall be organised as a memory mapped bit-field of // 65536 bits and thus 8 192 octets. Each bit shall uniquely correspond to one Group Address. @@ -25,8 +26,8 @@ enum RouteTableServices SetGroupAddress = 0x04, // 4 bytes: start address and end address }; -RouterObject::RouterObject(Memory& memory) - : TableObject(memory) +RouterObject::RouterObject(Memory& memory, uint32_t staticTableAdr, uint32_t staticTableSize) + : TableObject(memory, staticTableAdr, staticTableSize) { } @@ -45,6 +46,7 @@ void RouterObject::initialize(CouplerModel model, uint8_t objIndex, DptMedium me { bool useHopCount = false; bool useTable = true; + _model = model; if (model == CouplerModel::Model_20) { @@ -64,11 +66,11 @@ void RouterObject::initialize(CouplerModel model, uint8_t objIndex, DptMedium me // Only present if coupler model is 1.x Property* model1xProperties[] = { - // TODO: implement filtering based on this config here - new DataProperty( PID_MAIN_LCCONFIG, true, PDT_BITSET8, 1, ReadLv3 | WriteLv0, (uint8_t) 0 ), // Primary: data individual (connless and connorient) + broadcast - new DataProperty( PID_SUB_LCCONFIG, true, PDT_BITSET8, 1, ReadLv3 | WriteLv0, (uint8_t) 0 ), // Secondary: data individual (connless and connorient) + broadcast - new DataProperty( PID_MAIN_LCGRPCONFIG, true, PDT_BITSET8, 1, ReadLv3 | WriteLv0, (uint8_t) 0 ), // Primary: data group - new DataProperty( PID_SUB_LCGRPCONFIG, true, PDT_BITSET8, 1, ReadLv3 | WriteLv0, (uint8_t) 0 ), // Secondary: data group + // default values from Spec, see 03_05_01 4.4.4 and 4.4.5 + new DataProperty( PID_MAIN_LCCONFIG, true, PDT_BITSET8, 1, ReadLv3 | WriteLv0, (uint8_t) (LCCONFIG::PHYS_FRAME_ROUT | LCCONFIG::PHYS_REPEAT | LCCONFIG::BROADCAST_REPEAT | LCCONFIG::GROUP_IACK_ROUT | LCCONFIG::PHYS_IACK_NORMAL) ), // Primary: data individual (connless and connorient) + broadcast + new DataProperty( PID_SUB_LCCONFIG, true, PDT_BITSET8, 1, ReadLv3 | WriteLv0, (uint8_t) (LCCONFIG::PHYS_FRAME_ROUT | LCCONFIG::PHYS_REPEAT | LCCONFIG::BROADCAST_REPEAT | LCCONFIG::GROUP_IACK_ROUT | LCCONFIG::PHYS_IACK_NORMAL) ), // Secondary: data individual (connless and connorient) + broadcast + new DataProperty( PID_MAIN_LCGRPCONFIG, true, PDT_BITSET8, 1, ReadLv3 | WriteLv0, (uint8_t) (LCGRPCONFIG::GROUP_6FFFROUTE | LCGRPCONFIG::GROUP_7000UNLOCK | LCGRPCONFIG::GROUP_REPEAT)) , // Primary: data group + new DataProperty( PID_SUB_LCGRPCONFIG, true, PDT_BITSET8, 1, ReadLv3 | WriteLv0, (uint8_t) (LCGRPCONFIG::GROUP_6FFFROUTE | LCGRPCONFIG::GROUP_7000UNLOCK | LCGRPCONFIG::GROUP_REPEAT)), // Secondary: data group }; uint8_t model1xPropertiesCount = sizeof(model1xProperties) / sizeof(Property*); @@ -83,8 +85,6 @@ void RouterObject::initialize(CouplerModel model, uint8_t objIndex, DptMedium me Property* tableProperties[] = { - new DataProperty( PID_COUPLER_SERVICES_CONTROL, true, PDT_GENERIC_01, 1, ReadLv3 | WriteLv0, (uint8_t) 0), // written by ETS TODO: implement - new DataProperty( PID_FILTER_TABLE_USE, true, PDT_BINARY_INFORMATION, 1, ReadLv3 | WriteLv0, (uint16_t) 0 ), // default: invalid filter table, do not use, written by ETS new FunctionProperty(this, PID_ROUTETABLE_CONTROL, // Command Callback of PID_ROUTETABLE_CONTROL [](RouterObject* obj, uint8_t* data, uint8_t length, uint8_t* resultData, uint8_t& resultLength) -> void { @@ -95,12 +95,21 @@ void RouterObject::initialize(CouplerModel model, uint8_t objIndex, DptMedium me obj->functionRouteTableControl(false, data, length, resultData, resultLength); }) }; + + Property* tableProperties20[] = + { + new DataProperty( PID_COUPLER_SERVICES_CONTROL, true, PDT_GENERIC_01, 1, ReadLv3 | WriteLv0, (uint8_t) 0), // written by ETS TODO: implement + new DataProperty( PID_FILTER_TABLE_USE, true, PDT_BINARY_INFORMATION, 1, ReadLv3 | WriteLv0, (uint16_t) 0 ) // default: invalid filter table, do not use, written by ETS + }; + uint8_t tablePropertiesCount = sizeof(tableProperties) / sizeof(Property*); + uint8_t tableProperties20Count = sizeof(tableProperties20) / sizeof(Property*); size_t allPropertiesCount = fixedPropertiesCount; allPropertiesCount += (model == CouplerModel::Model_1x) ? model1xPropertiesCount : model20PropertiesCount; allPropertiesCount += useHopCount ? 1 : 0; allPropertiesCount += useTable ? tablePropertiesCount : 0; + allPropertiesCount += useTable && (model == CouplerModel::Model_20) ? tableProperties20Count : 0; allPropertiesCount += ((mediumType == DptMedium::KNX_RF) || (mediumType == DptMedium::KNX_IP)) ? 1 : 0; // PID_RF_ENABLE_SBC and PID_IP_ENABLE_SBC Property* allProperties[allPropertiesCount]; @@ -131,6 +140,11 @@ void RouterObject::initialize(CouplerModel model, uint8_t objIndex, DptMedium me { memcpy(&allProperties[i], tableProperties, sizeof(tableProperties)); i += tablePropertiesCount; + if((model == CouplerModel::Model_20)) + { + memcpy(&allProperties[i], tableProperties20, sizeof(tableProperties20)); + i += tableProperties20Count; + } } if (mediumType == DptMedium::KNX_RF) @@ -166,23 +180,32 @@ void RouterObject::initialize(CouplerModel model, uint8_t objIndex, DptMedium me const uint8_t* RouterObject::restore(const uint8_t* buffer) { - buffer = TableObject::restore(buffer); - - _filterTableGroupAddresses = (uint16_t*)data(); - - return buffer; + return TableObject::restore(buffer); } void RouterObject::commandClearSetRoutingTable(bool bitIsSet) { + uint8_t fillbyte = bitIsSet ? 0xFF : 0x00; + uint32_t relptr = _memory.toRelative(data()); +#ifdef KNX_LOG_COUPLER + print("RouterObject::commandClearSetRoutingTable "); + println(bitIsSet); + println(relptr); + println((uint32_t)data()); +#endif + for (uint16_t i = 0; i < kFilterTableSize; i++) { - data()[i] = bitIsSet ? 0xFF : 0x00; + _memory.writeMemory(relptr+i, 1, &fillbyte); } } bool RouterObject::statusClearSetRoutingTable(bool bitIsSet) { +#ifdef KNX_LOG_COUPLER + print("RouterObject::statusClearSetRoutingTable "); + println(bitIsSet); +#endif for (uint16_t i = 0; i < kFilterTableSize; i++) { if (data()[i] != (bitIsSet ? 0xFF : 0x00)) @@ -193,6 +216,15 @@ bool RouterObject::statusClearSetRoutingTable(bool bitIsSet) void RouterObject::commandClearSetGroupAddress(uint16_t startAddress, uint16_t endAddress, bool bitIsSet) { +#ifdef KNX_LOG_COUPLER + print("RouterObject::commandClearSetGroupAddress "); + print(startAddress); + print(" "); + print(endAddress); + print(" "); + println(bitIsSet); +#endif + uint16_t startOctet = startAddress / 8; uint8_t startBitPosition = startAddress % 8; uint16_t endOctet = endAddress / 8; @@ -200,26 +232,34 @@ void RouterObject::commandClearSetGroupAddress(uint16_t startAddress, uint16_t e if (startOctet == endOctet) { + uint32_t relptr = _memory.toRelative(data()) + startOctet; + uint8_t octetData = 0; // = data()[startOctet]; + _memory.readMemory(relptr, 1, &octetData); + for (uint8_t bitPos = startBitPosition; bitPos <= endBitPosition; bitPos++) { if (bitIsSet) - data()[startOctet] |= 1 << bitPos; + octetData |= 1 << bitPos; else - data()[startOctet] &= ~(1 << bitPos); + octetData &= ~(1 << bitPos); } + _memory.writeMemory(relptr, 1, &octetData); return; } for (uint16_t i = startOctet; i <= endOctet; i++) { + uint32_t relptr = _memory.toRelative(data()) + i; + uint8_t octetData = 0; + _memory.readMemory(relptr, 1, &octetData); if (i == startOctet) { for (uint8_t bitPos = startBitPosition; bitPos <= 7; bitPos++) { if (bitIsSet) - data()[i] |= 1 << bitPos; + octetData |= 1 << bitPos; else - data()[i] &= ~(1 << bitPos); + octetData &= ~(1 << bitPos); } } else if (i == endOctet) @@ -227,23 +267,33 @@ void RouterObject::commandClearSetGroupAddress(uint16_t startAddress, uint16_t e for (uint8_t bitPos = 0; bitPos <= endBitPosition; bitPos++) { if (bitIsSet) - data()[i] |= 1 << bitPos; + octetData |= 1 << bitPos; else - data()[i] &= ~(1 << bitPos); + octetData &= ~(1 << bitPos); } } else { if (bitIsSet) - data()[i] = 0xFF; + octetData = 0xFF; else - data()[i] = 0x00; + octetData = 0x00; } + _memory.writeMemory(relptr, 1, &octetData); } } bool RouterObject::statusClearSetGroupAddress(uint16_t startAddress, uint16_t endAddress, bool bitIsSet) { +#ifdef KNX_LOG_COUPLER + print("RouterObject::statusClearSetGroupAddress "); + print(startAddress); + print(" "); + print(endAddress); + print(" "); + println(bitIsSet); +#endif + uint16_t startOctet = startAddress / 8; uint8_t startBitPosition = startAddress % 8; uint16_t endOctet = endAddress / 8; @@ -313,10 +363,25 @@ bool RouterObject::statusClearSetGroupAddress(uint16_t startAddress, uint16_t en void RouterObject::functionRouteTableControl(bool isCommand, uint8_t* data, uint8_t length, uint8_t* resultData, uint8_t& resultLength) { +#ifdef KNX_LOG_COUPLER + print("RouterObject::functionRouteTableControl "); + print(isCommand); + print(" "); + printHex("", data, length); +#endif + RouteTableServices srvId = (RouteTableServices) data[1]; if (isCommand) { + if (loadState() != LS_LOADING) + { + println("access violation. filter table can only be modified in LS_LOADING"); + resultData[0] = ReturnCodes::AccessReadOnly; + resultData[1] = srvId; + resultLength = 2; + return; + } switch(srvId) { case ClearRoutingTable: @@ -424,12 +489,22 @@ void RouterObject::functionRfEnableSbc(bool isCommand, uint8_t* data, uint8_t le bool RouterObject::isRfSbcRoutingEnabled() { +#ifdef KNX_LOG_COUPLER + print("RouterObject::isRfSbcRoutingEnabled "); + println(_rfSbcRoutingEnabled); +#endif return _rfSbcRoutingEnabled; } // TODO: check if IP SBC works the same way, just copied from RF void RouterObject::functionIpEnableSbc(bool isCommand, uint8_t* data, uint8_t length, uint8_t* resultData, uint8_t& resultLength) { +#ifdef KNX_LOG_COUPLER + print("RouterObject::functionIpEnableSbc "); + print(isCommand); + printHex(" ", data, length); +#endif + if (isCommand) { _ipSbcRoutingEnabled = (data[0] == 1) ? true : false; @@ -443,19 +518,31 @@ void RouterObject::functionIpEnableSbc(bool isCommand, uint8_t* data, uint8_t le // TODO: check if IP SBC works the same way, just copied from RF bool RouterObject::isIpSbcRoutingEnabled() { +#ifdef KNX_LOG_COUPLER + print("RouterObject::isIpSbcRoutingEnabled "); + println(_ipSbcRoutingEnabled); +#endif return _ipSbcRoutingEnabled; } void RouterObject::beforeStateChange(LoadState& newState) { +#ifdef KNX_LOG_COUPLER + println("RouterObject::beforeStateChange"); +#endif if (newState != LS_LOADED) return; - - _filterTableGroupAddresses = (uint16_t*)data(); } void RouterObject::masterReset(EraseCode eraseCode, uint8_t channel) { +#ifdef KNX_LOG_COUPLER + print("RouterObject::masterReset "); + print(eraseCode); + print(" "); + println(channel); +#endif + if (eraseCode == FactoryReset) { // TODO: handle different erase codes @@ -468,18 +555,28 @@ bool RouterObject::isGroupAddressInFilterTable(uint16_t groupAddress) if (loadState() != LS_LOADED) return false; - uint8_t filterTableUse = 0x00; - if (property(PID_FILTER_TABLE_USE)->read(filterTableUse) == 0) - return false; + uint8_t filterTableUse = 0x01; + Property* propFilterTableUse = property(PID_FILTER_TABLE_USE); + if(propFilterTableUse) // check if property PID_FILTER_TABLE_USE exists (only coupler 20), if not, ignore this + if (propFilterTableUse->read(filterTableUse) == 0) // check if property PID_FILTER_TABLE_USE is empty, if so, return false + return false; if ((filterTableUse&0x01) == 1) { + uint8_t* filterTable = data(); // octet_address = GA_value div 8 // bit_position = GA_value mod 8 uint16_t octetAddress = groupAddress / 8; uint8_t bitPosition = groupAddress % 8; + - return (data()[octetAddress] & (1 << bitPosition)) == (1 << bitPosition); + if(filterTable) + return (filterTable[octetAddress] & (1 << bitPosition)) == (1 << bitPosition); + else + { + println("RouterObject::isGroupAddressInFilterTable filterTable is NULL"); + return false; + } } return false; diff --git a/src/knx/router_object.h b/src/knx/router_object.h index 0064a06..467835b 100644 --- a/src/knx/router_object.h +++ b/src/knx/router_object.h @@ -23,7 +23,7 @@ enum RouterObjectType class RouterObject : public TableObject { public: - RouterObject(Memory& memory); + RouterObject(Memory& memory, uint32_t staticTableAdr = 0, uint32_t staticTableSize = 0); void initialize1x(DptMedium mediumType, uint16_t maxApduSize); void initialize20(uint8_t objIndex, DptMedium mediumType, RouterObjectType rtType, uint16_t maxApduSize); @@ -54,5 +54,5 @@ private: bool _rfSbcRoutingEnabled = false; bool _ipSbcRoutingEnabled = false; - uint16_t* _filterTableGroupAddresses = 0; + CouplerModel _model = CouplerModel::Model_20; }; diff --git a/src/knx/service_families.h b/src/knx/service_families.h new file mode 100644 index 0000000..5120e4f --- /dev/null +++ b/src/knx/service_families.h @@ -0,0 +1,15 @@ +#ifndef KNX_SERVICE_FAMILY_CORE +#define KNX_SERVICE_FAMILY_CORE 1 +#endif + +#ifndef KNX_SERVICE_FAMILY_DEVICE_MANAGEMENT +#define KNX_SERVICE_FAMILY_DEVICE_MANAGEMENT 1 +#endif + +#ifndef KNX_SERVICE_FAMILY_TUNNELING +#define KNX_SERVICE_FAMILY_TUNNELING 1 +#endif + +#ifndef KNX_SERVICE_FAMILY_ROUTING +#define KNX_SERVICE_FAMILY_ROUTING 1 +#endif \ No newline at end of file diff --git a/src/knx/table_object.cpp b/src/knx/table_object.cpp index 90e6624..993277c 100644 --- a/src/knx/table_object.cpp +++ b/src/knx/table_object.cpp @@ -19,9 +19,12 @@ BeforeTablesUnloadCallback TableObject::beforeTablesUnloadCallback() return _beforeTablesUnload; } -TableObject::TableObject(Memory& memory) +TableObject::TableObject(Memory& memory, uint32_t staticTableAdr , uint32_t staticTableSize) : _memory(memory) -{} +{ + _staticTableAdr = staticTableAdr; + _staticTableSize = staticTableSize; +} TableObject::~TableObject() {} @@ -55,6 +58,9 @@ void TableObject::loadState(LoadState newState) uint8_t* TableObject::save(uint8_t* buffer) { + //println("TableObject::save"); + allocTableStatic(); + buffer = pushByte(_state, buffer); buffer = pushInt(_size, buffer); @@ -64,12 +70,14 @@ uint8_t* TableObject::save(uint8_t* buffer) else buffer = pushInt(0, buffer); - return buffer; + return InterfaceObject::save(buffer); } const uint8_t* TableObject::restore(const uint8_t* buffer) { + //println("TableObject::restore"); + uint8_t state = 0; buffer = popByte(state, buffer); _state = (LoadState)state; @@ -78,13 +86,14 @@ const uint8_t* TableObject::restore(const uint8_t* buffer) uint32_t relativeAddress = 0; buffer = popInt(relativeAddress, buffer); + //println(relativeAddress); if (relativeAddress != 0) _data = _memory.toAbsolute(relativeAddress); else _data = 0; - - return buffer; + //println((uint32_t)_data); + return InterfaceObject::restore(buffer); } uint32_t TableObject::tableReference() @@ -94,6 +103,9 @@ uint32_t TableObject::tableReference() bool TableObject::allocTable(uint32_t size, bool doFill, uint8_t fillByte) { + if(_staticTableAdr) + return false; + if (_data) { _memory.freeMemory(_data); @@ -119,8 +131,20 @@ bool TableObject::allocTable(uint32_t size, bool doFill, uint8_t fillByte) return true; } + +void TableObject::allocTableStatic() +{ + if(_staticTableAdr && !_data) + { + _data = _memory.toAbsolute(_staticTableAdr); + _size = _staticTableSize; + _memory.addNewUsedBlock(_data, _size); + } +} + void TableObject::loadEvent(const uint8_t* data) { + //printHex("TableObject::loadEvent 0x", data, 10); switch (_state) { case LS_UNLOADED: @@ -200,8 +224,11 @@ void TableObject::loadEventLoaded(const uint8_t* data) //free nv memory if (_data) { - _memory.freeMemory(_data); - _data = 0; + if(!_staticTableAdr) + { + _memory.freeMemory(_data); + _data = 0; + } } break; case LE_ADDITIONAL_LOAD_CONTROLS: @@ -288,7 +315,28 @@ void TableObject::initializeProperties(size_t propertiesSize, Property** propert [](TableObject* obj, uint16_t start, uint8_t count, const uint8_t* data) -> uint8_t { obj->loadEvent(data); return 1; - }), + }) + }; + + uint8_t ownPropertiesCount = sizeof(ownProperties) / sizeof(Property*); + + uint8_t propertyCount = propertiesSize / sizeof(Property*); + uint8_t allPropertiesCount = propertyCount + ownPropertiesCount; + + Property* allProperties[allPropertiesCount]; + memcpy(allProperties, properties, propertiesSize); + memcpy(allProperties + propertyCount, ownProperties, sizeof(ownProperties)); + + if(_staticTableAdr) + InterfaceObject::initializeProperties(sizeof(allProperties), allProperties); + else + initializeDynTableProperties(sizeof(allProperties), allProperties); +} + +void TableObject::initializeDynTableProperties(size_t propertiesSize, Property** properties) +{ + Property* ownProperties[] = + { new CallbackProperty(this, PID_TABLE_REFERENCE, false, PDT_UNSIGNED_LONG, 1, ReadLv3 | WriteLv0, [](TableObject* obj, uint16_t start, uint8_t count, uint8_t* data) -> uint8_t { if(start == 0) @@ -321,9 +369,6 @@ void TableObject::initializeProperties(size_t propertiesSize, Property** propert }), new DataProperty(PID_ERROR_CODE, false, PDT_ENUM8, 1, ReadLv3 | WriteLv0, (uint8_t)E_NO_FAULT) }; - //TODO: missing - - // 23 PID_TABLE 3 / (3) uint8_t ownPropertiesCount = sizeof(ownProperties) / sizeof(Property*); diff --git a/src/knx/table_object.h b/src/knx/table_object.h index 66768cd..61520c6 100644 --- a/src/knx/table_object.h +++ b/src/knx/table_object.h @@ -18,7 +18,7 @@ class TableObject: public InterfaceObject * The constuctor. * @param memory The instance of the memory management class to use. */ - TableObject(Memory& memory); + TableObject(Memory& memory, uint32_t staticTableAdr = 0, uint32_t staticTableSize = 0); /** * The destructor. @@ -45,7 +45,7 @@ class TableObject: public InterfaceObject /** * returns the internal data of the interface object. This pointer belongs to the TableObject class and - * must not be freed. + * must not be written at nor freed. */ uint8_t* data(); /** @@ -57,15 +57,20 @@ class TableObject: public InterfaceObject static BeforeTablesUnloadCallback _beforeTablesUnload; + Memory& _memory; + private: uint32_t tableReference(); bool allocTable(uint32_t size, bool doFill, uint8_t fillByte); + void allocTableStatic(); + void initializeDynTableProperties(size_t propertiesSize, Property** properties); void loadEvent(const uint8_t* data); void loadEventUnloaded(const uint8_t* data); void loadEventLoading(const uint8_t* data); void loadEventLoaded(const uint8_t* data); void loadEventError(const uint8_t* data); void additionalLoadControls(const uint8_t* data); + /** * set the ::LoadState of the interface object. * @@ -75,9 +80,10 @@ class TableObject: public InterfaceObject */ void loadState(LoadState newState); LoadState _state = LS_UNLOADED; - Memory& _memory; uint8_t *_data = 0; static uint8_t _tableUnloadCount; + uint32_t _staticTableAdr; + uint32_t _staticTableSize; /** * used to store size of data() in allocTable(), needed for calculation of crc in PID_MCB_TABLE. diff --git a/src/knx/tp_frame.h b/src/knx/tp_frame.h index cd900a6..f1bbf76 100644 --- a/src/knx/tp_frame.h +++ b/src/knx/tp_frame.h @@ -245,7 +245,7 @@ class TpFrame */ uint16_t cemiSize() { - return fullSize() + (isExtended() ? 2 : 3); + return fullSize() + (isExtended() ? 2 : 3) - 1; // -1 without CRC } /** @@ -262,14 +262,14 @@ class TpFrame cemiBuffer[2] = _data[0]; if (isExtended()) { - memcpy(cemiBuffer + 2, _data, fullSize()); + memcpy(cemiBuffer + 2, _data, fullSize() - 1); // -1 without CRC } else { cemiBuffer[3] = _data[5] & 0xF0; memcpy(cemiBuffer + 4, _data + 1, 4); cemiBuffer[8] = _data[5] & 0x0F; - memcpy(cemiBuffer + 9, _data + 6, cemiBuffer[8] + 2); + memcpy(cemiBuffer + 9, _data + 6, cemiBuffer[8] + 2 - 1); // -1 without CRC } return cemiBuffer; diff --git a/src/knx/tpuart_data_link_layer.cpp b/src/knx/tpuart_data_link_layer.cpp index 627daf0..4649b68 100644 --- a/src/knx/tpuart_data_link_layer.cpp +++ b/src/knx/tpuart_data_link_layer.cpp @@ -408,7 +408,8 @@ void TpUartDataLinkLayer::processRxFrameByte(uint8_t byte) if (_rxFrame->size() == 7) { // Prüfe ob ich für das Frame zuständig bin - if (_forceAck || _cb.isAckRequired(_rxFrame->destination(), _rxFrame->isGroupAddress())) + TPAckType ack = _cb.isAckRequired(_rxFrame->destination(), _rxFrame->isGroupAddress()); + if (_forceAck || ack) { /* * Speichere die Zuständigkeit dass dieses Frame weiterverarbeitet werden soll. @@ -425,7 +426,7 @@ void TpUartDataLinkLayer::processRxFrameByte(uint8_t byte) _rxFrame->addFlags(TP_FRAME_FLAG_ACK); // und im TPUart damit dieser das ACK schicken kann - _platform.writeUart(U_ACK_REQ | U_ACK_REQ_ADRESSED); + _platform.writeUart(U_ACK_REQ | ack); } } } @@ -1022,9 +1023,10 @@ bool TpUartDataLinkLayer::processTxFrameBytes() TpUartDataLinkLayer::TpUartDataLinkLayer(DeviceObject &devObj, NetworkLayerEntity &netLayerEntity, Platform &platform, + BusAccessUnit& busAccessUnit, ITpUartCallBacks &cb, DataLinkLayerCallbacks *dllcb) - : DataLinkLayer(devObj, netLayerEntity, platform), + : DataLinkLayer(devObj, netLayerEntity, platform, busAccessUnit), _cb(cb), _dllcb(dllcb) { diff --git a/src/knx/tpuart_data_link_layer.h b/src/knx/tpuart_data_link_layer.h index 7dd0d55..4fe8fe8 100644 --- a/src/knx/tpuart_data_link_layer.h +++ b/src/knx/tpuart_data_link_layer.h @@ -29,7 +29,7 @@ class ITpUartCallBacks { public: virtual ~ITpUartCallBacks() = default; - virtual bool isAckRequired(uint16_t address, bool isGrpAddr) = 0; + virtual TPAckType isAckRequired(uint16_t address, bool isGrpAddr) = 0; }; class TpUartDataLinkLayer : public DataLinkLayer @@ -39,7 +39,7 @@ class TpUartDataLinkLayer : public DataLinkLayer public: TpUartDataLinkLayer(DeviceObject& devObj, NetworkLayerEntity& netLayerEntity, - Platform& platform, ITpUartCallBacks& cb, DataLinkLayerCallbacks* dllcb = nullptr); + Platform& platform, BusAccessUnit& busAccessUnit, ITpUartCallBacks& cb, DataLinkLayerCallbacks* dllcb = nullptr); void loop(); void enabled(bool value); diff --git a/src/knx_facade.h b/src/knx_facade.h index 3f1c60b..0e55667 100644 --- a/src/knx_facade.h +++ b/src/knx_facade.h @@ -189,22 +189,6 @@ template class KnxFacade : private SaveRestore { _progLedOnCallback = progLedOnCallback; } - -#ifdef KNX_ACTIVITYCALLBACK - /// @brief sets the Callback Function indicating sent or received telegrams - /// @param activityCallback - /// @details the info parameter - void setActivityCallback(ActivityCallback activityCallback) - { - _activityCallback = activityCallback; - } - - void Activity(uint8_t info) - { - if(_activityCallback) - _activityCallback(info); - } -#endif int32_t buttonPin() { diff --git a/src/rp2040_arduino_platform.cpp b/src/rp2040_arduino_platform.cpp index cbc745e..6ac1ea4 100644 --- a/src/rp2040_arduino_platform.cpp +++ b/src/rp2040_arduino_platform.cpp @@ -506,7 +506,7 @@ bool RP2040ArduinoPlatform::sendBytesMultiCast(uint8_t* buffer, uint16_t len) return true; } -int RP2040ArduinoPlatform::readBytesMultiCast(uint8_t* buffer, uint16_t maxLen) +int RP2040ArduinoPlatform::readBytesMultiCast(uint8_t* buffer, uint16_t maxLen, uint32_t& src_addr, uint16_t& src_port) { int len = _udp.parsePacket(); if (len == 0) @@ -521,6 +521,10 @@ int RP2040ArduinoPlatform::readBytesMultiCast(uint8_t* buffer, uint16_t maxLen) } _udp.read(buffer, len); + _remoteIP = _udp.remoteIP(); + _remotePort = _udp.remotePort(); + src_addr = htonl(_remoteIP); + src_port = _remotePort; // print("Remote IP: "); // print(_udp.remoteIP().toString().c_str()); @@ -534,6 +538,11 @@ bool RP2040ArduinoPlatform::sendBytesUniCast(uint32_t addr, uint16_t port, uint8 { IPAddress ucastaddr(htonl(addr)); + if(!addr) + ucastaddr = _remoteIP; + + if(!port) + port = _remotePort; // print("sendBytesUniCast to:"); // println(ucastaddr.toString().c_str()); diff --git a/src/rp2040_arduino_platform.h b/src/rp2040_arduino_platform.h index bb756d0..aa6e746 100644 --- a/src/rp2040_arduino_platform.h +++ b/src/rp2040_arduino_platform.h @@ -119,7 +119,7 @@ public: void setupMultiCast(uint32_t addr, uint16_t port) override; void closeMultiCast() override; bool sendBytesMultiCast(uint8_t* buffer, uint16_t len) override; - int readBytesMultiCast(uint8_t* buffer, uint16_t maxLen) override; + int readBytesMultiCast(uint8_t* buffer, uint16_t maxLen, uint32_t& src_addr, uint16_t& src_port) override; // unicast bool sendBytesUniCast(uint32_t addr, uint16_t port, uint8_t* buffer, uint16_t len) override; @@ -131,6 +131,9 @@ public: #endif protected: pin_size_t _rxPin = UART_PIN_NOT_DEFINED; protected: pin_size_t _txPin = UART_PIN_NOT_DEFINED; + + protected: IPAddress _remoteIP = 0; + protected: uint16_t _remotePort = 0; }; #endif From 84a0ce3c228341b418931dd44e45565c2533ffd5 Mon Sep 17 00:00:00 2001 From: Marco Scholl Date: Sat, 3 Aug 2024 21:27:00 +0200 Subject: [PATCH 34/44] fixes modulo in rx queue --- src/knx/tpuart_data_link_layer.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/knx/tpuart_data_link_layer.cpp b/src/knx/tpuart_data_link_layer.cpp index 627daf0..ac14581 100644 --- a/src/knx/tpuart_data_link_layer.cpp +++ b/src/knx/tpuart_data_link_layer.cpp @@ -1204,19 +1204,19 @@ void TpUartDataLinkLayer::processRxQueue() void TpUartDataLinkLayer::pushByteToRxQueue(uint8_t byte) { _rxBuffer[_rxBufferFront] = byte; - _rxBufferFront = (_rxBufferFront + 1) % MAX_RX_QUEUE_BYTES; + _rxBufferFront = (_rxBufferFront + 1) % (MAX_RX_QUEUE_BYTES); } uint8_t TpUartDataLinkLayer::pullByteFromRxQueue() { uint8_t byte = _rxBuffer[_rxBufferRear]; - _rxBufferRear = (_rxBufferRear + 1) % MAX_RX_QUEUE_BYTES; + _rxBufferRear = (_rxBufferRear + 1) % (MAX_RX_QUEUE_BYTES); return byte; } uint16_t TpUartDataLinkLayer::availableInRxQueue() { - return ((_rxBufferFront == _rxBufferRear) ? MAX_RX_QUEUE_BYTES : (((MAX_RX_QUEUE_BYTES - _rxBufferFront) + _rxBufferRear) % MAX_RX_QUEUE_BYTES)) - 1; + return ((_rxBufferFront == _rxBufferRear) ? (MAX_RX_QUEUE_BYTES) : ((((MAX_RX_QUEUE_BYTES) - _rxBufferFront) + _rxBufferRear) % (MAX_RX_QUEUE_BYTES))) - 1; } #endif From f0cd44cefa8c25922a5863dc5fa540acf510eeb5 Mon Sep 17 00:00:00 2001 From: Michael Geramb Date: Wed, 7 Aug 2024 21:09:05 +0200 Subject: [PATCH 35/44] add DPT 27.001 --- src/knx/dptconvert.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/knx/dptconvert.cpp b/src/knx/dptconvert.cpp index 1954d2d..079f4a9 100644 --- a/src/knx/dptconvert.cpp +++ b/src/knx/dptconvert.cpp @@ -87,6 +87,9 @@ int KNX_Decode_Value(uint8_t* payload, size_t payload_length, const Dpt& datatyp // DPT 26.* - Scene Info if (datatype.mainGroup == 26 && datatype.subGroup == 1 && datatype.index <= 1) return busValueToSceneInfo(payload, payload_length, datatype, value); + // DPT 27.001 - 32 Bit field + if (datatype.mainGroup == 27 && datatype.subGroup == 1 && !datatype.index) + return busValueToSigned32(payload, payload_length, datatype, value); // DPT 28.* - Unicode String if (datatype.mainGroup == 28 && datatype.subGroup == 1 && !datatype.index) return busValueToUnicode(payload, payload_length, datatype, value); @@ -205,7 +208,10 @@ int KNX_Encode_Value(const KNXValue& value, uint8_t* payload, size_t payload_len return valueToBusValueDateTime(value, payload, payload_length, datatype); // DPT 26.* - Scene Info if (datatype.mainGroup == 26 && datatype.subGroup == 1 && datatype.index <= 1) - return valueToBusValueSceneInfo(value, payload, payload_length, datatype); + return valueToBusValueSceneInfo(value, payload, payload_length, datatype); + // DPT 27.001 - 32 Bit Field + if (datatype.mainGroup == 27 && datatype.subGroup == 1 && !datatype.index) + return valueToBusValueUnsigned32(value, payload, payload_length, datatype); // DPT 28.* - Unicode String if (datatype.mainGroup == 28 && datatype.subGroup == 1 && !datatype.index) return valueToBusValueUnicode(value, payload, payload_length, datatype); From 27ef9ee1f7afc826a4f6c65a0a30dc0dacdd4a09 Mon Sep 17 00:00:00 2001 From: Thomas Kunze Date: Fri, 9 Aug 2024 19:08:03 +0200 Subject: [PATCH 36/44] cmake changes --- CMakeLists.txt | 6 +++++- examples/knx-linux-coupler/CMakeLists.txt | 4 ++-- examples/knx-linux/CMakeLists.txt | 4 ++-- examples/knxPython/CMakeLists.txt | 6 +++--- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 52bb6ff..841e743 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,3 +1,7 @@ +cmake_policy(SET CMP0048 NEW) +project(knx VERSION 1.4) +cmake_minimum_required(VERSION 3.16) + add_subdirectory(examples/knx-linux) add_subdirectory(examples/knx-linux-coupler) -add_subdirectory(examples/knxPython) \ No newline at end of file +add_subdirectory(examples/knxPython) diff --git a/examples/knx-linux-coupler/CMakeLists.txt b/examples/knx-linux-coupler/CMakeLists.txt index 7e36926..1809326 100644 --- a/examples/knx-linux-coupler/CMakeLists.txt +++ b/examples/knx-linux-coupler/CMakeLists.txt @@ -1,5 +1,5 @@ -cmake_minimum_required(VERSION 2.7) -project(knx-linux-coupler) +cmake_minimum_required(VERSION 3.16) +project(knx-linux-coupler VERSION 1.4) set(LIBRARIES_FROM_REFERENCES "") set(SOURCES diff --git a/examples/knx-linux/CMakeLists.txt b/examples/knx-linux/CMakeLists.txt index c0ef749..ba740df 100644 --- a/examples/knx-linux/CMakeLists.txt +++ b/examples/knx-linux/CMakeLists.txt @@ -1,5 +1,5 @@ -cmake_minimum_required(VERSION 2.7) -project(knx-linux) +cmake_minimum_required(VERSION 3.16) +project(knx-linux VERSION 1.4) set(LIBRARIES_FROM_REFERENCES "") set(SOURCES diff --git a/examples/knxPython/CMakeLists.txt b/examples/knxPython/CMakeLists.txt index 8b67bbe..7f795e0 100644 --- a/examples/knxPython/CMakeLists.txt +++ b/examples/knxPython/CMakeLists.txt @@ -1,5 +1,5 @@ -cmake_minimum_required(VERSION 2.7) -project(knx) +cmake_minimum_required(VERSION 3.16) +project(knx VERSION 1.4) add_subdirectory(pybind11) @@ -139,4 +139,4 @@ include_directories(../../src) #set_target_properties(knx PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${outdir}) set_target_properties(knx PROPERTIES OUTPUT_NAME knx) set_property(TARGET knx PROPERTY CXX_STANDARD 11) -target_compile_definitions(knx PUBLIC -DMASK_VERSION=0x57B0) \ No newline at end of file +target_compile_definitions(knx PUBLIC -DMASK_VERSION=0x57B0) From 85ac27a6ec435a292fb606bbb764893c95ef03b4 Mon Sep 17 00:00:00 2001 From: Ing-Dom Date: Mon, 26 Feb 2024 12:40:29 +0100 Subject: [PATCH 37/44] add rp2040 build environment to knx-demo example for both example project and ci --- examples/knx-demo/platformio-ci.ini | 17 +++++++++++++++++ examples/knx-demo/platformio.ini | 23 +++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/examples/knx-demo/platformio-ci.ini b/examples/knx-demo/platformio-ci.ini index 2f0b04c..0ea2778 100644 --- a/examples/knx-demo/platformio-ci.ini +++ b/examples/knx-demo/platformio-ci.ini @@ -79,3 +79,20 @@ build_flags = -DMASK_VERSION=0x07B0 -Wno-unknown-pragmas -DUSE_DATASECURE + +;--- RP2040 ----------------------------------------------- +[env:rp2040] +framework = arduino +platform = https://github.com/maxgerhardt/platform-raspberrypi.git#182d833 +platform_packages = framework-arduinopico @ https://github.com/earlephilhower/arduino-pico/releases/download/3.6.2/rp2040-3.6.2.zip +board = rpipico +board_build.core = earlephilhower + +lib_deps = + knx + +build_flags = + -DMASK_VERSION=0x07B0 + -DKNX_FLASH_SIZE=4096 + -D PIO_FRAMEWORK_ARDUINO_ENABLE_RTTI + -Wno-unknown-pragmas diff --git a/examples/knx-demo/platformio.ini b/examples/knx-demo/platformio.ini index f47cade..0db86f5 100644 --- a/examples/knx-demo/platformio.ini +++ b/examples/knx-demo/platformio.ini @@ -146,3 +146,26 @@ build_flags = -Wno-unknown-pragmas extra_scripts = ../scripts/stm32rdu.py + + +;--- RP2040 ----------------------------------------------- +[env:rp2040] +framework = arduino +platform = https://github.com/maxgerhardt/platform-raspberrypi.git#182d833 +platform_packages = framework-arduinopico @ https://github.com/earlephilhower/arduino-pico/releases/download/3.6.2/rp2040-3.6.2.zip +board = rpipico +board_build.core = earlephilhower +; We consider that the this projects is opened within its project directory +; while working with VS Code. + + +lib_deps = + knx=file://../../../knx + +lib_ldf_mode = deep+ + +build_flags = + -DMASK_VERSION=0x07B0 + -DKNX_FLASH_SIZE=4096 + -D PIO_FRAMEWORK_ARDUINO_ENABLE_RTTI + -Wno-unknown-pragmas \ No newline at end of file From 9a88b484678d324afc48fbb07bf8039dc42c3a29 Mon Sep 17 00:00:00 2001 From: Thomas Kunze Date: Fri, 9 Aug 2024 19:32:41 +0200 Subject: [PATCH 38/44] merge --- .../knx-usb/platformio-ci_BACKUP_12507.ini | 34 +++++++++++++++++++ examples/knx-usb/platformio-ci_BACKUP_584.ini | 34 +++++++++++++++++++ examples/knx-usb/platformio-ci_BACKUP_641.ini | 34 +++++++++++++++++++ examples/knx-usb/platformio-ci_BASE_12507.ini | 30 ++++++++++++++++ examples/knx-usb/platformio-ci_BASE_584.ini | 30 ++++++++++++++++ examples/knx-usb/platformio-ci_BASE_641.ini | 30 ++++++++++++++++ .../knx-usb/platformio-ci_LOCAL_12507.ini | 30 ++++++++++++++++ examples/knx-usb/platformio-ci_LOCAL_584.ini | 30 ++++++++++++++++ examples/knx-usb/platformio-ci_LOCAL_641.ini | 30 ++++++++++++++++ .../knx-usb/platformio-ci_REMOTE_12507.ini | 31 +++++++++++++++++ examples/knx-usb/platformio-ci_REMOTE_584.ini | 31 +++++++++++++++++ examples/knx-usb/platformio-ci_REMOTE_641.ini | 31 +++++++++++++++++ 12 files changed, 375 insertions(+) create mode 100644 examples/knx-usb/platformio-ci_BACKUP_12507.ini create mode 100644 examples/knx-usb/platformio-ci_BACKUP_584.ini create mode 100644 examples/knx-usb/platformio-ci_BACKUP_641.ini create mode 100644 examples/knx-usb/platformio-ci_BASE_12507.ini create mode 100644 examples/knx-usb/platformio-ci_BASE_584.ini create mode 100644 examples/knx-usb/platformio-ci_BASE_641.ini create mode 100644 examples/knx-usb/platformio-ci_LOCAL_12507.ini create mode 100644 examples/knx-usb/platformio-ci_LOCAL_584.ini create mode 100644 examples/knx-usb/platformio-ci_LOCAL_641.ini create mode 100644 examples/knx-usb/platformio-ci_REMOTE_12507.ini create mode 100644 examples/knx-usb/platformio-ci_REMOTE_584.ini create mode 100644 examples/knx-usb/platformio-ci_REMOTE_641.ini diff --git a/examples/knx-usb/platformio-ci_BACKUP_12507.ini b/examples/knx-usb/platformio-ci_BACKUP_12507.ini new file mode 100644 index 0000000..dbe704d --- /dev/null +++ b/examples/knx-usb/platformio-ci_BACKUP_12507.ini @@ -0,0 +1,34 @@ +;PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html +[env:adafruit_feather_m0] +platform = atmelsam +board = adafruit_feather_m0 +framework = arduino + +; VID must be changed to some known KNX Manufacturer +; so that the KNX USB interface gets recognized by ETS +<<<<<<< HEAD +======= +; not possible within ci +>>>>>>> 06e0365 +;extra_scripts = pre:custom_hwids.py +;board_build.usb_product="KNX RF - USB Interface" + +lib_deps = + SPI + Adafruit TinyUSB Library@0.7.1 + knx + +build_flags = + -DMASK_VERSION=0x27B0 + -DUSE_USB + -DUSE_TINYUSB + -Wno-unknown-pragmas + -DUSE_DATASECURE diff --git a/examples/knx-usb/platformio-ci_BACKUP_584.ini b/examples/knx-usb/platformio-ci_BACKUP_584.ini new file mode 100644 index 0000000..dbe704d --- /dev/null +++ b/examples/knx-usb/platformio-ci_BACKUP_584.ini @@ -0,0 +1,34 @@ +;PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html +[env:adafruit_feather_m0] +platform = atmelsam +board = adafruit_feather_m0 +framework = arduino + +; VID must be changed to some known KNX Manufacturer +; so that the KNX USB interface gets recognized by ETS +<<<<<<< HEAD +======= +; not possible within ci +>>>>>>> 06e0365 +;extra_scripts = pre:custom_hwids.py +;board_build.usb_product="KNX RF - USB Interface" + +lib_deps = + SPI + Adafruit TinyUSB Library@0.7.1 + knx + +build_flags = + -DMASK_VERSION=0x27B0 + -DUSE_USB + -DUSE_TINYUSB + -Wno-unknown-pragmas + -DUSE_DATASECURE diff --git a/examples/knx-usb/platformio-ci_BACKUP_641.ini b/examples/knx-usb/platformio-ci_BACKUP_641.ini new file mode 100644 index 0000000..dbe704d --- /dev/null +++ b/examples/knx-usb/platformio-ci_BACKUP_641.ini @@ -0,0 +1,34 @@ +;PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html +[env:adafruit_feather_m0] +platform = atmelsam +board = adafruit_feather_m0 +framework = arduino + +; VID must be changed to some known KNX Manufacturer +; so that the KNX USB interface gets recognized by ETS +<<<<<<< HEAD +======= +; not possible within ci +>>>>>>> 06e0365 +;extra_scripts = pre:custom_hwids.py +;board_build.usb_product="KNX RF - USB Interface" + +lib_deps = + SPI + Adafruit TinyUSB Library@0.7.1 + knx + +build_flags = + -DMASK_VERSION=0x27B0 + -DUSE_USB + -DUSE_TINYUSB + -Wno-unknown-pragmas + -DUSE_DATASECURE diff --git a/examples/knx-usb/platformio-ci_BASE_12507.ini b/examples/knx-usb/platformio-ci_BASE_12507.ini new file mode 100644 index 0000000..60cc010 --- /dev/null +++ b/examples/knx-usb/platformio-ci_BASE_12507.ini @@ -0,0 +1,30 @@ +;PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html +[env:adafruit_feather_m0] +platform = atmelsam@6.0.1 +board = adafruit_feather_m0 +framework = arduino + +; VID must be changed to some known KNX Manufacturer +; so that the KNX USB interface gets recognized by ETS +extra_scripts = pre:custom_hwids.py +board_build.usb_product="KNX RF - USB Interface" + +lib_deps = + SPI + Adafruit TinyUSB Library@0.7.1 + knx + +build_flags = + -DMASK_VERSION=0x27B0 + -DUSE_USB + -DUSE_TINYUSB + -Wno-unknown-pragmas + -DUSE_DATASECURE diff --git a/examples/knx-usb/platformio-ci_BASE_584.ini b/examples/knx-usb/platformio-ci_BASE_584.ini new file mode 100644 index 0000000..6cb0046 --- /dev/null +++ b/examples/knx-usb/platformio-ci_BASE_584.ini @@ -0,0 +1,30 @@ +;PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html +[env:adafruit_feather_m0] +platform = atmelsam@6.0.1 +board = adafruit_feather_m0 +framework = arduino + +; VID must be changed to some known KNX Manufacturer +; so that the KNX USB interface gets recognized by ETS +extra_scripts = pre:custom_hwids.py +board_build.usb_product="KNX RF - USB Interface" + +lib_deps = + SPI + Adafruit TinyUSB Library@0.7.1 + knx + +build_flags = + -DMASK_VERSION=0x27B0 + -DUSE_USB + -DUSE_TINYUSB + -Wno-unknown-pragmas + -DUSE_DATASECURE diff --git a/examples/knx-usb/platformio-ci_BASE_641.ini b/examples/knx-usb/platformio-ci_BASE_641.ini new file mode 100644 index 0000000..60cc010 --- /dev/null +++ b/examples/knx-usb/platformio-ci_BASE_641.ini @@ -0,0 +1,30 @@ +;PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html +[env:adafruit_feather_m0] +platform = atmelsam@6.0.1 +board = adafruit_feather_m0 +framework = arduino + +; VID must be changed to some known KNX Manufacturer +; so that the KNX USB interface gets recognized by ETS +extra_scripts = pre:custom_hwids.py +board_build.usb_product="KNX RF - USB Interface" + +lib_deps = + SPI + Adafruit TinyUSB Library@0.7.1 + knx + +build_flags = + -DMASK_VERSION=0x27B0 + -DUSE_USB + -DUSE_TINYUSB + -Wno-unknown-pragmas + -DUSE_DATASECURE diff --git a/examples/knx-usb/platformio-ci_LOCAL_12507.ini b/examples/knx-usb/platformio-ci_LOCAL_12507.ini new file mode 100644 index 0000000..90d791c --- /dev/null +++ b/examples/knx-usb/platformio-ci_LOCAL_12507.ini @@ -0,0 +1,30 @@ +;PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html +[env:adafruit_feather_m0] +platform = atmelsam +board = adafruit_feather_m0 +framework = arduino + +; VID must be changed to some known KNX Manufacturer +; so that the KNX USB interface gets recognized by ETS +;extra_scripts = pre:custom_hwids.py +;board_build.usb_product="KNX RF - USB Interface" + +lib_deps = + SPI + Adafruit TinyUSB Library@0.7.1 + knx + +build_flags = + -DMASK_VERSION=0x27B0 + -DUSE_USB + -DUSE_TINYUSB + -Wno-unknown-pragmas + -DUSE_DATASECURE diff --git a/examples/knx-usb/platformio-ci_LOCAL_584.ini b/examples/knx-usb/platformio-ci_LOCAL_584.ini new file mode 100644 index 0000000..b3dc599 --- /dev/null +++ b/examples/knx-usb/platformio-ci_LOCAL_584.ini @@ -0,0 +1,30 @@ +;PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html +[env:adafruit_feather_m0] +platform = atmelsam +board = adafruit_feather_m0 +framework = arduino + +; VID must be changed to some known KNX Manufacturer +; so that the KNX USB interface gets recognized by ETS +;extra_scripts = pre:custom_hwids.py +;board_build.usb_product="KNX RF - USB Interface" + +lib_deps = + SPI + Adafruit TinyUSB Library@0.7.1 + knx + +build_flags = + -DMASK_VERSION=0x27B0 + -DUSE_USB + -DUSE_TINYUSB + -Wno-unknown-pragmas + -DUSE_DATASECURE diff --git a/examples/knx-usb/platformio-ci_LOCAL_641.ini b/examples/knx-usb/platformio-ci_LOCAL_641.ini new file mode 100644 index 0000000..90d791c --- /dev/null +++ b/examples/knx-usb/platformio-ci_LOCAL_641.ini @@ -0,0 +1,30 @@ +;PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html +[env:adafruit_feather_m0] +platform = atmelsam +board = adafruit_feather_m0 +framework = arduino + +; VID must be changed to some known KNX Manufacturer +; so that the KNX USB interface gets recognized by ETS +;extra_scripts = pre:custom_hwids.py +;board_build.usb_product="KNX RF - USB Interface" + +lib_deps = + SPI + Adafruit TinyUSB Library@0.7.1 + knx + +build_flags = + -DMASK_VERSION=0x27B0 + -DUSE_USB + -DUSE_TINYUSB + -Wno-unknown-pragmas + -DUSE_DATASECURE diff --git a/examples/knx-usb/platformio-ci_REMOTE_12507.ini b/examples/knx-usb/platformio-ci_REMOTE_12507.ini new file mode 100644 index 0000000..6688b72 --- /dev/null +++ b/examples/knx-usb/platformio-ci_REMOTE_12507.ini @@ -0,0 +1,31 @@ +;PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html +[env:adafruit_feather_m0] +platform = atmelsam +board = adafruit_feather_m0 +framework = arduino + +; VID must be changed to some known KNX Manufacturer +; so that the KNX USB interface gets recognized by ETS +; not possible within ci +;extra_scripts = pre:custom_hwids.py +;board_build.usb_product="KNX RF - USB Interface" + +lib_deps = + SPI + Adafruit TinyUSB Library@0.7.1 + knx + +build_flags = + -DMASK_VERSION=0x27B0 + -DUSE_USB + -DUSE_TINYUSB + -Wno-unknown-pragmas + -DUSE_DATASECURE diff --git a/examples/knx-usb/platformio-ci_REMOTE_584.ini b/examples/knx-usb/platformio-ci_REMOTE_584.ini new file mode 100644 index 0000000..056b639 --- /dev/null +++ b/examples/knx-usb/platformio-ci_REMOTE_584.ini @@ -0,0 +1,31 @@ +;PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html +[env:adafruit_feather_m0] +platform = atmelsam +board = adafruit_feather_m0 +framework = arduino + +; VID must be changed to some known KNX Manufacturer +; so that the KNX USB interface gets recognized by ETS +; not possible within ci +;extra_scripts = pre:custom_hwids.py +;board_build.usb_product="KNX RF - USB Interface" + +lib_deps = + SPI + Adafruit TinyUSB Library@0.7.1 + knx + +build_flags = + -DMASK_VERSION=0x27B0 + -DUSE_USB + -DUSE_TINYUSB + -Wno-unknown-pragmas + -DUSE_DATASECURE diff --git a/examples/knx-usb/platformio-ci_REMOTE_641.ini b/examples/knx-usb/platformio-ci_REMOTE_641.ini new file mode 100644 index 0000000..6688b72 --- /dev/null +++ b/examples/knx-usb/platformio-ci_REMOTE_641.ini @@ -0,0 +1,31 @@ +;PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html +[env:adafruit_feather_m0] +platform = atmelsam +board = adafruit_feather_m0 +framework = arduino + +; VID must be changed to some known KNX Manufacturer +; so that the KNX USB interface gets recognized by ETS +; not possible within ci +;extra_scripts = pre:custom_hwids.py +;board_build.usb_product="KNX RF - USB Interface" + +lib_deps = + SPI + Adafruit TinyUSB Library@0.7.1 + knx + +build_flags = + -DMASK_VERSION=0x27B0 + -DUSE_USB + -DUSE_TINYUSB + -Wno-unknown-pragmas + -DUSE_DATASECURE From 934d9ca5ba6a7b737a9453fec62102452567d95a Mon Sep 17 00:00:00 2001 From: Thomas Kunze Date: Fri, 9 Aug 2024 19:53:00 +0200 Subject: [PATCH 39/44] translate german comments with deepL --- src/knx/tpuart_data_link_layer.cpp | 257 ++++++++++++++--------------- src/rp2040_arduino_platform.cpp | 12 +- 2 files changed, 134 insertions(+), 135 deletions(-) diff --git a/src/knx/tpuart_data_link_layer.cpp b/src/knx/tpuart_data_link_layer.cpp index e74df73..07ab432 100644 --- a/src/knx/tpuart_data_link_layer.cpp +++ b/src/knx/tpuart_data_link_layer.cpp @@ -13,7 +13,6 @@ * A new implementation of the tpuart connection. * Author Marco Scholl * - * To avoid misunderstandings (also for myself), this is in German, at least for the time being. */ // services Host -> Controller : @@ -117,16 +116,16 @@ enum enum { - // In diesem Zustand wird auf neue Steuerbefehle gewartet. + // In this state, the system waits for new control commands. RX_IDLE, - // In diesem Zustand werden alle Bytes als Bytes für ein Frame betrachtet. + // In this state, all bytes are regarded as bytes for a frame. RX_FRAME, - // In diesem Zustand werdem alle Bytes verworfen + // In this state, all bytes are discarded RX_INVALID, - // Im Monitoring wird noch auf ein ACk gewartet + // Monitoring is still waiting for an ACk RX_AWAITING_ACK }; @@ -150,7 +149,7 @@ void printFrame(TpFrame *tpframe) } /* - * Verarbeitet alle Bytes. + * Processes all bytes. */ void __isr __time_critical_func(TpUartDataLinkLayer::processRx)(bool isr) { @@ -158,14 +157,14 @@ void __isr __time_critical_func(TpUartDataLinkLayer::processRx)(bool isr) return; /* - * Manche Platformen untersützen die Erkennung, ob der Hardwarebuffer übergelaufen ist. - * Theoretisch könnte man nun den Buffer verwerfen, aber dann geht ggf. noch ein gültiges Frame verloren. - * Daher wird später im loop nur eine Info ausgegben und im Byteprocessing wird "versucht" noch darauf einzugehen. + * Some platforms support the detection of whether the hardware buffer has overflowed. + * Theoretically, you could now discard the buffer, but then a valid frame may be lost. + * Therefore, only one piece of information is output later in the loop and byte processing "tries" to respond to it. */ if (_platform.overflowUart()) _rxOverflow = true; - // verarbeiten daten + // process data while (_platform.uartAvailable()) { processRxByte(); @@ -175,7 +174,7 @@ void __isr __time_critical_func(TpUartDataLinkLayer::processRx)(bool isr) } /* - * Verarbeitet 1 eigehendes Byte (wenn vorhanden) + * Processes 1 incoming byte (if available) */ void TpUartDataLinkLayer::processRxByte() { @@ -186,10 +185,10 @@ void TpUartDataLinkLayer::processRxByte() return; /* - * Wenn ich im RX_INVALID Modus bin - * und das letzte Byte vor mehr als 2ms verarbeitet wurde (also pause >2ms) - * und keine weiteren Bytes in der Buffer vorliegen, - * dann kann ich den INVALID State verwerfen. + * If I am in RX_INVALID mode + * and the last byte was processed more than 2ms ago (i.e. pause >2ms) + * and there are no more bytes in the buffer, + * then I can discard the INVALID state. */ if (_rxState == RX_INVALID && (millis() - _rxLastTime) > 2 && !_platform.uartAvailable()) { @@ -200,14 +199,14 @@ void TpUartDataLinkLayer::processRxByte() if (_rxState == RX_INVALID) { /* - * Sobald ein Frame ungültig verarbeitet wurde oder ein unbekanntes Kommando eintrifft, wechselt der Zustand in RX_INVALID. - * Ab jetzt muss ich davon ausgehen, dass einen Übertragungsfehler gegeben hat und die aktuellen Bytes ungültig sind. - * Gleiches gilt auch wenn ein HW Overflow erkannt wurde. + * As soon as a frame has been processed invalidly or an unknown command arrives, the status changes to RX_INVALID. + * From now on I must assume that there has been a transmission error and the current bytes are invalid. + * The same applies if a HW overflow is detected. * - * - Die Zeit des letzten Frames 3ms vorbei ist und keine Daten mehr im Buffer sind. (Wird übermir geprüft) - * - Wenn der Markermodus aktiv ist und ein U_FRAME_END_IND sauber erkannt wurde. (Wird hier geprüft) + * The time of the last frame is 3ms past and there is no more data in the buffer. (Is checked by me) + * - If the marker mode is active and a U_FRAME_END_IND has been detected correctly. (Checked here) * - * Ansonsten macht dieser Abschnitt nichts und verwirrft damit die ungültigen Bytes + * Otherwise this section does nothing and thus discards the invalid bytes */ if (markerMode()) { @@ -217,12 +216,12 @@ void TpUartDataLinkLayer::processRxByte() } else if (_rxMarker && byte == U_FRAME_END_IND) { - // doppeltes byte gefunden also marker zurück setzten - kein Frameende + // double byte found so reset marker - no frame end _rxMarker = false; } else if (_rxMarker) { - // frame ende gefunden. -> RX_IDLE + // frame end found. -> RX_IDLE _rxMarker = false; _rxState = RX_IDLE; } @@ -235,12 +234,12 @@ void TpUartDataLinkLayer::processRxByte() else if ((byte & L_DATA_MASK) == L_DATA_STANDARD_IND || (byte & L_DATA_MASK) == L_DATA_EXTENDED_IND) { /* - * Verarbeite ein vorheriges Frame falls noch vorhanden. Das dürfte normal nur im Busmonitor auftreten, weil hier noch auch ein ACK gewartet wird + * Process a previous frame if still available. This should normally only occur in the bus monitor because an ACK is also being waited for here */ processRxFrameComplete(); _rxFrame->addByte(byte); - // Provoziere ungültige Frames für Tests + // Provoke invalid frames for tests // if (millis() % 20 == 0) // _rxFrame->addByte(0x1); @@ -248,12 +247,12 @@ void TpUartDataLinkLayer::processRxByte() _rxState = RX_FRAME; /* - * Hier wird inital ein Ack ohne Addressed gesetzt. Das dient dazu, falls noch ein Ack vom vorherigen Frame gesetzt ist, - * zurück gesetzt wird. Das passiert wenn die Verarbeitung zu stark verzögert ist (z.B. weil kein DMA/IRQ genutzt wird). - * Das ACK kann beliebig oft gesendet werden, weil es in der BCU nur gespeichert wird und erst bei bedarf genutzt / gesendet wird. + * Here an ack is set inital without Addressed. This is used if an Ack is still set from the previous frame, + * is set back. This happens if processing is delayed too much (e.g. because no DMA/IRQ is used). + * The ACK can be sent as often as required because it is only stored in the BCU and is only used / sent when required. * - * Das darf man natürlich nur wenn ich nicht gerade selber sende, da man eigene Frames nicht ACKt. Ggf ignoriert die BCU dies, - * aber ich wollte hier auf sicher gehen. + * Of course, you can only do this if you are not sending yourself, as you do not ACK your own frames. The BCU may ignore this, + * but I wanted to be on the safe side here. */ if (_txState == TX_IDLE) { @@ -262,7 +261,7 @@ void TpUartDataLinkLayer::processRxByte() } else { - // Hier werden die Commands ausgewertet, falls das noch schon passiert ist. + // The commands are evaluated here, if this has already happened. if (byte == U_RESET_IND) { @@ -273,8 +272,8 @@ void TpUartDataLinkLayer::processRxByte() _tpState |= (byte ^ U_STATE_MASK); #ifndef NCN5120 /* - * Filtere "Protocol errors" weil auf anderen BCU wie der Siements dies gesetzt, when das timing nicht stimmt. - * Leider ist kein perfektes Timing möglich, so dass dieser Fehler ignoriert werden muss. Hat auch keine bekannte Auswirkungen. + * Filter "Protocol errors" because this is set on other BCUs such as the Siements when the timing is not correct. + * Unfortunately, perfect timing is not possible, so this error must be ignored. Also has no known effects. */ _tpState &= 0b11101000; #endif @@ -290,8 +289,8 @@ void TpUartDataLinkLayer::processRxByte() else if ((byte & L_ACKN_MASK) == L_ACKN_IND) { /* - * Wenn ein Frame noch nicht geschlossen wurde und ein Ack rein kommt. - * dann setze noch das ACK. + * If a frame has not yet been closed and an Ack comes in. + * then set the ACK. */ if (_rxFrame->size() > 0) { @@ -309,7 +308,7 @@ void TpUartDataLinkLayer::processRxByte() } else { - // Dieses Byte wurde nicht erwartet, da garnichts gesendet wurde. + // This byte was not expected because nothing was sent. _rxUnkownControlCounter++; _rxState = RX_INVALID; // println("L_DATA_CON"); @@ -336,13 +335,13 @@ void TpUartDataLinkLayer::processRxByte() } /* - * Verarbeite eigehendes Byte eines Frames + * Process incoming byte of a frame */ void TpUartDataLinkLayer::processRxFrameByte(uint8_t byte) { /* - * Bei aktivem Maker muss das erste U_FRAME_END_IND ignoriert und auf ein Folgebyte gewartet werden. - * Das Folgebyte ist also ausschlaggebend wie dieses Byte zu bewerten ist. + * If the maker is active, the first U_FRAME_END_IND must be ignored and a subsequent byte must be waited for. + * The subsequent byte is therefore decisive for how this byte is to be evaluated. */ if (markerMode() && (byte == U_FRAME_END_IND && !_rxMarker)) { @@ -350,8 +349,8 @@ void TpUartDataLinkLayer::processRxFrameByte(uint8_t byte) } /* - * Wenn das vorherige Byte ein U_FRAME_END_IND war, und das neue Byte ein U_FRAME_STATE_IND ist, - * dann ist der Empfang sauber abgeschlossen und das Frame kann verarbeitet werden. + * If the previous byte was a U_FRAME_END_IND and the new byte is a U_FRAME_STATE_IND, + * then the reception is cleanly completed and the frame can be processed. */ else if (_rxMarker && (byte & U_FRAME_STATE_MASK) == U_FRAME_STATE_IND) { @@ -359,76 +358,76 @@ void TpUartDataLinkLayer::processRxFrameByte(uint8_t byte) processRxFrameComplete(); /* - * Setze den Zustand auf RX_IDLE, da durch den Marker sichergestellt ist, - * dass das Frame erfolgreich verarbeitet wurde. Nachfolgende Bytes sind also wieder sauber Steuerbefehle, - * selbst wenn das Frame aufgrund einer ungültigen Checksumme verworfen wurde (was RX_INVAID) bedeuten würde + * Set the status to RX_IDLE, as the marker ensures, + * that the frame has been processed successfully. Subsequent bytes are therefore clean again Control commands, + * even if the frame was discarded due to an invalid checksum (which would mean RX_INVAID) */ _rxState = RX_IDLE; } /* - * Dies ist ein hypotetischer Fall, dass die Frames ohne Marker kommen, obwohl der MarkerModus aktiv ist. - * Hier wird der aktuelle Frame abgearbeitet und RX_INVALID gesetzt, da das aktuelle Byte hierbei nicht bearbeitet wird. - * Dieser Fall kann eintreffen wenn der Marker Modus von der TPUart nicht unterstützt wird (NCN51xx Feature) aber aktiviert wurde. + * This is a hypothetical case in which the frames are sent without markers even though marker mode is active. + * Here the current frame is processed and RX_INVALID is set, as the current byte is not processed. + * This case can occur if the marker mode is not supported by the TPUart (NCN51xx feature) but has been activated. */ else if (markerMode() && _rxFrame->isFull()) { processRxFrameComplete(); /* - * RX_INVALID weil theoretisch könnte das Frame als gültig verarbeitet worden sein. - * Da aber das aktuelle Byte schon "angefangen" wurde zu verarbeiten, fehlt das in der Verarbeitungskette - * und somit sind die nachfolgenden Bytes nicht verwertbar. + * RX_INVALID because theoretically the frame could have been processed as valid. + * However, since the current byte has already been "started" to be processed, it is missing in the processing chain + * and therefore the subsequent bytes cannot be used. */ _rxState = RX_INVALID; } /* - * Wenn der Markermodus in aktiv ist, soll das Byte normal verarbeitet werden. - * Wenn der Markermodus aktiv ist, dann darf ein U_FRAME_END_IND Byte nur verarbeitet werden, wenn des vorherige Byte auch ein U_FRAME_END_IND war. + * If marker mode is active, the byte should be processed normally. + * If marker mode is active, a U_FRAME_END_IND byte may only be processed if the previous byte was also a U_FRAME_END_IND. */ else if (!markerMode() || byte != U_FRAME_END_IND || (byte == U_FRAME_END_IND && _rxMarker)) { - // Setze den Marker wieder zurück falls aktiv + // Reset the marker if active _rxMarker = false; - // Übernehme das Byte + // Accept the byte _rxFrame->addByte(byte); - // Wenn der Busmonitor gestartet wurde, findet keine Verarbeitung - also auch kein ACKing + // If the bus monitor has been started, no processing takes place - i.e. no ACKing if (!_monitoring) { - // Wenn mehr als 7 bytes vorhanden kann geschaut werden ob das Frame für "mich" bestimmt ist + // If more than 7 bytes are available, you can check whether the frame is intended for "me". if (_rxFrame->size() == 7) { - // Prüfe ob ich für das Frame zuständig bin + // Check whether I am responsible for the frame if (_forceAck || _cb.isAckRequired(_rxFrame->destination(), _rxFrame->isGroupAddress())) { /* - * Speichere die Zuständigkeit dass dieses Frame weiterverarbeitet werden soll. - * Da es keine extra Funktion außer dem isAckRequired gibt, wird das erstmal gleich behandelt. - * Eine spätere Unterscheidung (ggf für Routermodus) muss man dann schauen. + * Save the responsibility that this frame is to be processed further. + * Since there is no extra function apart from the isAckRequired, this is initially treated the same. + * A later differentiation (possibly for router mode) must then be looked at. */ _rxFrame->addFlags(TP_FRAME_FLAG_ADDRESSED); - // Das darf man natürlich nur wenn ich nicht gerade selber sende, da man eigene Frames nicht ACKt + // Of course, this is only allowed if I am not sending myself, as you cannot ACK your own frames if (_txState == TX_IDLE) { - // Speichere das ein Acking erfolgen soll + // Save that tracking should take place _rxFrame->addFlags(TP_FRAME_FLAG_ACKING); - // und im TPUart damit dieser das ACK schicken kann + // and in the TPUart so that it can send the ACK _platform.writeUart(U_ACK_REQ | U_ACK_REQ_ADRESSED); } } } #ifdef USE_TP_RX_QUEUE - // Prüfe nun ob die RxQueue noch Platz hat für Frame + Size (2) + Flags(1) + // Now check whether the RxQueue still has space for Frame + Size (2) + Flags(1) if (_rxFrame->size() == 8 && (_rxFrame->flags() & TP_FRAME_FLAG_ADDRESSED)) { if (availableInRxQueue() < (_rxFrame->size() + 3)) { - // Nur wenn ich nicht selber sende + // Only if I am not sending myself if (_txState == TX_IDLE) { _platform.writeUart(U_ACK_REQ | U_ACK_REQ_ADRESSED | U_ACK_REQ_BUSY); @@ -440,9 +439,9 @@ void TpUartDataLinkLayer::processRxFrameByte(uint8_t byte) } /* - * Wenn kein Markermodus aktiv ist, dann muss anhand des Frame geschaut werden, ob es Vollständig ist. - * isFull prüft hier ob die maxSize oder die Längenangabe des Frames überschritten wurde! - * In beiden Fällen muss das Frame verarbeitet werden. + * If no marker mode is active, the frame must be checked to see if it is complete. + * isFull checks here whether the maxSize or the length specification of the frame has been exceeded! + * In both cases, the frame must be processed. */ if (!markerMode() && (_rxFrame->isFull())) { @@ -451,40 +450,40 @@ void TpUartDataLinkLayer::processRxFrameByte(uint8_t byte) } /* - * Verarbeitet das aktuelle Frame und prüft ob dieses vollständig und gültig (checksum) ist. - * Sollte ein Frame vollständig und gültig sein, so wird deses falls für "mich" bestimmt in die Queue gelegt und der Modus ist wieder RX_IDLE. - * Ansonsten wird das Frame als ungültig verworfen und der Zustand ist RX_INVALID, da nicht sichergestellt ist das Folgebytes wieder Steuercodes sind. - * Ausnahme im Markermodus, hier wird der Status RX_INVALID an einer anderen Stelle direkt wieder auf RX_IDLE geändert, weil - * dann ist sicher gestellt, dass das das Frame auf TP Ebene kaputt gegangen ist. + * Processes the current frame and checks whether it is complete and valid (checksum). + * If a frame is complete and valid, it is placed in the queue if it is intended for "me" and the mode is RX_IDLE again. + * Otherwise the frame is discarded as invalid and the status is RX_INVALID, as it is not guaranteed that subsequent bytes are control codes again. + * Exception in marker mode, here the status RX_INVALID is changed directly back to RX_IDLE at another point because + * it is then ensured that the frame has been broken at TP level. */ void TpUartDataLinkLayer::processRxFrameComplete() { - // Sollte aktuell kein Frame in der Bearbeitung sein, dann breche ab + // If no frame is currently being edited, then cancel if (!_rxFrame->size()) return; - // Ist das Frame vollständig und gültig + // Is the frame complete and valid if (_rxFrame->isValid()) { - // Wenn ein Frame gesendet wurde + // When a frame has been sent if (_txState == TX_FRAME) { - // prüfe ob das Empfangen diesem entspricht + // check whether the reception corresponds to this if (!memcmp(_rxFrame->data(), _txFrame->data(), _txFrame->size())) { - // und markiere das entsprechend + // and mark this accordingly // println("MATCH"); _rxFrame->addFlags(TP_FRAME_FLAG_ECHO); } - // Jetzt warte noch auf das L_DATA_CON + // Now wait for the L_DATA_CON } - // wenn das frame für mich ist oder ich im busmonitor modus bin dann möchte ich es weiter verarbeiten + // if the frame is for me or i am in busmonitor mode then i want to process it further if (_rxFrame->flags() & TP_FRAME_FLAG_ADDRESSED || _monitoring) { /* - * Im Busmonitormodus muss noch auf ein Ack gewartet werden. - * Daher wird hier der Status geändert und vor dem echten abschließen zurück gesprungen. - * Sobald ein weiteres mal aufgerufen wird, (egal ob geackt oder nicht) wird das Frame geschlossen. + * In bus monitor mode, you still have to wait for an Ack. + * Therefore, the status is changed here and jumps back before the real completion. + * As soon as another call is made (regardless of whether or not the frame has been acked), the frame is closed. */ if (_monitoring && _rxState != RX_AWAITING_ACK) { @@ -495,17 +494,17 @@ void TpUartDataLinkLayer::processRxFrameComplete() } else { - // Sonst verwerfe das Paket und gebe den Speicher frei -> da nicht in die Queue gepackt wird + // Otherwise, discard the package and release the memory -> as it is not packed into the queue _rxIgnoredFrameCounter++; } - // Und wieder bereit für Steuercodes + // And ready for control codes again _rxState = RX_IDLE; } else { /* - * Ist das Frame unvollständig oder ungültig dann wechsle in RX_INVALID Modus da ich nicht unterscheiden kann, - * ob es sich um einen TPBus Error oder ein UART Error oder ein Timming Error handelt. + * If the frame is incomplete or invalid then switch to RX_INVALID mode as I cannot distinguish, + * whether it is a TPBus error or a UART error or a Timming error. */ _rxInvalidFrameCounter++; _rxFrame->addFlags(TP_FRAME_FLAG_INVALID); @@ -518,7 +517,7 @@ void TpUartDataLinkLayer::processRxFrameComplete() processRxFrame(_rxFrame); #endif - // resete den aktuellen Framepointer + // resets the current frame pointer _rxFrame->reset(); } @@ -547,7 +546,7 @@ void TpUartDataLinkLayer::processTxFrameComplete(bool success) } /* - * Steckt das zu sendende Frame in eine Queue, da der TpUart vielleicht gerade noch nicht sende bereit ist. + * Puts the frame to be sent into a queue, as the TpUart may not yet be ready to send. */ void TpUartDataLinkLayer::pushTxFrameQueue(TpFrame *tpFrame) { @@ -597,9 +596,9 @@ bool TpUartDataLinkLayer::sendFrame(CemiFrame &cemiFrame) } /* - * Es soll regelmäßig der Status abgefragt werden um ein Disconnect des TPUart und dessen Status zu erkennen. - * Außerdem soll regelmäßig die aktuelle Config bzw der Modus übermittelt werden, so dass nach einem Disconnect, - * der TPUart im richtigen Zustand ist. + * The status should be queried regularly to detect a disconnect of the TPUart and its status. + * In addition, the current config or mode should be transmitted regularly so that after a disconnect, + * the TPUart is in the correct state. */ void TpUartDataLinkLayer::requestState(bool force /* = false */) { @@ -608,20 +607,20 @@ void TpUartDataLinkLayer::requestState(bool force /* = false */) if (!(_rxState == RX_IDLE || _rxState == RX_INVALID)) return; - // Nur 1x pro Sekunde + // Only 1x per second if ((millis() - _lastStateRequest) < 1000) return; } // println("requestState"); - // Sende Konfiguration bzw. Modus + // Send configuration or mode if (_monitoring) _platform.writeUart(U_BUSMON_REQ); else requestConfig(); - // Frage status an - wenn monitoring inaktiv + // Question status on - if monitoring inactive if (!_monitoring) _platform.writeUart(U_STATE_REQ); @@ -629,7 +628,7 @@ void TpUartDataLinkLayer::requestState(bool force /* = false */) } /* - * Sendet die aktuelle Config an den Chip + * Sends the current config to the chip */ void TpUartDataLinkLayer::requestConfig() { @@ -639,7 +638,7 @@ void TpUartDataLinkLayer::requestConfig() _platform.writeUart(U_CONFIGURE_REQ | U_CONFIGURE_MARKER_REQ); #endif - // Abweichende Config + // Deviating Config if (_repetitions != 0b00110011) { #ifdef NCN5120 @@ -655,8 +654,8 @@ void TpUartDataLinkLayer::requestConfig() } /* - * Ein vereinfachter Lockmechanismus der nur auf dem gleichen Core funktionert. - * Also perfekt für ISR + * A simplified lock mechanism that only works on the same core. + * Perfect for ISR */ bool TpUartDataLinkLayer::isrLock(bool blocking /* = false */) { @@ -809,9 +808,9 @@ bool TpUartDataLinkLayer::enabled() const } /* - * Wenn ein TxFrame gesendet wurde, wird eine Bestätigung für den Versand erwartet. - * Kam es aber zu einem ungültigen Frame oder Busdisconnect, bleibt die Bestätigung aus und der STack hängt im TX_FRAME fest. - * Daher muss nach einer kurzen Wartezeit das Warten beendet werden. + * If a TxFrame has been sent, a confirmation for the transmission is expected. + * However, if there was an invalid frame or bus disconnect, the confirmation is not received and the STack is stuck in the TX_FRAME. + * The wait must therefore be ended after a short waiting time. */ void TpUartDataLinkLayer::clearOutdatedTxFrame() { @@ -820,8 +819,8 @@ void TpUartDataLinkLayer::clearOutdatedTxFrame() } /* - * Hier werden die ausgehenden Frames aus der Warteschlange genomnmen und versendet. - * Das passiert immer nur einzelnd, da nach jedem Frame, gewartet werden muss bis das Frame wieder reingekommen ist und das L_DATA_CON rein kommt. + * Here the outgoing frames are taken from the queue and sent. + * This only happens one at a time, as after each frame it is necessary to wait until the frame has come in again and the L_DATA_CON comes in. * */ void TpUartDataLinkLayer::processTxQueue() @@ -861,10 +860,10 @@ void TpUartDataLinkLayer::processTxQueue() } /* - * Prüfe ob ich zu lange keine Daten mehr erhalten habe und setzte den Status auf nicht verbunden. - * Im normalen Modus wird jede Sekunde der Status angefragt. Daher kann hier eine kurze Zeit gewählt werden. - * Im Monitoringmodus gibt es eigentlichen Frames, daher ist hier eine längere Zeit verwendet. - * Dennoch kommt es bei größeren Datenmengen zu vermuteten Disconnect, daher wird auch noch die RxQueue beachtet. + * Check whether I have not received any data for too long and set the status to not connected. + * In normal mode, the status is requested every second. A short time can therefore be selected here. + * In monitoring mode there are actual frames, so a longer time is used here. + * Nevertheless, there are suspected disconnects with larger data volumes, so the RxQueue is also taken into account. */ void TpUartDataLinkLayer::checkConnected() { @@ -898,8 +897,8 @@ void TpUartDataLinkLayer::loop() return; /* - * Sollte ein overflow erkannt worden sein, so wechsle in RX_INVALID. - * Das greift aber nur im loop und nicht im ISR. Aber bei Nutzung von ISR und DMA sollte dieser Fall nie passieren. + * If an overflow has been detected, change to RX_INVALID. + * However, this only applies in the loop and not in ISR. But when using ISR and DMA, this should never happen. */ if (_rxOverflow) { @@ -948,7 +947,7 @@ DptMedium TpUartDataLinkLayer::mediumType() const } /* - * Hiermit kann die Stromversorgung des V20V (VCC2) + * This can be used to switch the power supply to the V20V (VCC2) */ #ifdef NCN5120 void TpUartDataLinkLayer::powerControl(bool state) @@ -966,13 +965,13 @@ bool TpUartDataLinkLayer::processTxFrameBytes() // println("processTxFrameBytes"); /* - * Jedes Frame muss mit einem U_L_DATA_START_REQ eingeleitet werden und jedes Folgebyte mit einem weiteren Positionsbyte (6bit). - * Da das Positionsbyte aus dem U_L_DATA_START_REQ + Position besteht und wir sowieso mit 0 starten, ist eine weitere - * Unterscheidung nicht nötig. + * Each frame must be introduced with a U_L_DATA_START_REQ and each subsequent byte with a further position byte (6bit). + * Since the position byte consists of the U_L_DATA_START_REQ + position and we start with 0 anyway, a further distinction is not necessary. + * distinction is not necessary. * - * Das letzte Byte (Checksumme) verwendet allerdings das U_L_DATA_END_REQ + Postion! - * Außerdem gibt es eine weitere Besondertheit bei Extendedframes bis 263 Bytes lang sein können reichen die 6bit nicht mehr. - * Hier muss ein U_L_DATA_OFFSET_REQ + Position (3bit) vorangestellt werden. Somit stehten 9bits für die Postion bereit. + * However, the last byte (checksum) uses the U_L_DATA_END_REQ + position! + * In addition, there is another special feature for extended frames up to 263 bytes long, the 6 bits are no longer sufficient. + * Here a U_L_DATA_OFFSET_REQ + Position (3bit) must be prefixed. This means that 9 bits are available for the position. */ for (uint16_t i = 0; i < _txFrame->size(); i++) { @@ -985,7 +984,7 @@ bool TpUartDataLinkLayer::processTxFrameBytes() _platform.writeUart(U_L_DATA_OFFSET_REQ | offset); } - if (i == (_txFrame->size() - 1)) // Letztes Bytes (Checksumme) + if (i == (_txFrame->size() - 1)) // Last bytes (checksum) _platform.writeUart(U_L_DATA_END_REQ | position); else _platform.writeUart(U_L_DATA_START_REQ | position); @@ -1014,7 +1013,7 @@ TpUartDataLinkLayer::TpUartDataLinkLayer(DeviceObject &devObj, } /* - * Liefert die Anzahl der Frames, die nicht verarbeitet werden konnte. + * Returns the number of frames that could not be processed. */ uint32_t TpUartDataLinkLayer::getRxInvalidFrameCounter() { @@ -1022,7 +1021,7 @@ uint32_t TpUartDataLinkLayer::getRxInvalidFrameCounter() } /* - * Liefert die Anzahl der Frames, welche gültig und für das Geräte bestimmt sind + * Returns the number of frames that are valid and intended for the device */ uint32_t TpUartDataLinkLayer::getRxProcessdFrameCounter() { @@ -1030,7 +1029,7 @@ uint32_t TpUartDataLinkLayer::getRxProcessdFrameCounter() } /* - * Liefert die Anzahl der Frames, welche gültig aber nicht f+r das Gerät bestimmt sind + * Returns the number of frames that are valid but not intended for the device */ uint32_t TpUartDataLinkLayer::getRxIgnoredFrameCounter() { @@ -1038,7 +1037,7 @@ uint32_t TpUartDataLinkLayer::getRxIgnoredFrameCounter() } /* - * Liefert die Anzahl der gezählten Steuerbytes, welche nicht erkannt wurden + * Returns the number of control bytes counted that were not recognized */ uint32_t TpUartDataLinkLayer::getRxUnknownControlCounter() { @@ -1046,14 +1045,14 @@ uint32_t TpUartDataLinkLayer::getRxUnknownControlCounter() } /* - * Liefert die Anzahl der zusendenden Frames + * Returns the number of frames sent */ uint32_t TpUartDataLinkLayer::getTxFrameCounter() { return _txFrameCounter; } /* - * Liefert die Anzahl der versendeten Frames + * Returns the number of frames sent */ uint32_t TpUartDataLinkLayer::getTxProcessedFrameCounter() { @@ -1128,8 +1127,8 @@ void TpUartDataLinkLayer::processRxFrame(TpFrame *tpFrame) * This method allows the processing of the incoming bytes to be handled additionally via an interrupt (ISR). * The prerequisite is that the interrupt runs on the same core as the knx.loop! * - * Bei einem RP2040 wo beim "erase" eines Blocks auch der ISR gesperrt sind, - * kann zwischern den Erases die Verarbeitung nachgeholt werden. So wird das Risiko von Frameverlusten deutlich minimiert. + * With an RP2040 where the ISR is also locked when a block is erased, + * processing can be caught up between the erases. This significantly minimizes the risk of frame losses. */ void __isr __time_critical_func(TpUartDataLinkLayer::processRxISR)() { @@ -1137,9 +1136,9 @@ void __isr __time_critical_func(TpUartDataLinkLayer::processRxISR)() } /* - * Steckt das empfangene Frame in eine Queue. Diese Queue ist notwendig, - * weil ein Frame optional über ein ISR empfangen werden kann und die verarbeitung noch normal im knx.loop statt finden muss. - * Außerdem ist diese Queue statisch vorallokierte, da in einem ISR keine malloc u.ä. gemacht werden kann. + * Puts the received frame into a queue. This queue is necessary, + * because a frame can optionally be received via an ISR and processing must still take place normally in the knx.loop. + * In addition, this queue is statically preallocated, as no malloc etc. can be made in an ISR. */ void TpUartDataLinkLayer::pushRxFrameQueue() { diff --git a/src/rp2040_arduino_platform.cpp b/src/rp2040_arduino_platform.cpp index 691d768..ffd2cb0 100644 --- a/src/rp2040_arduino_platform.cpp +++ b/src/rp2040_arduino_platform.cpp @@ -50,38 +50,38 @@ volatile uint16_t uartDmaRestartCount = 0; volatile uint32_t uartDmaWriteCount2 = 0; volatile uint32_t uartDmaAvail = 0; -// Liefert die Zahl der gelesenen Bytes seit dem DMA Transferstart +// Returns the number of bytes read since the DMA transfer start inline uint32_t uartDmaWriteCount() { uartDmaWriteCount2 = uartDmaTransferCount - dma_channel_hw_addr(uartDmaChannel)->transfer_count; return uartDmaWriteCount2; } -// Liefert die aktuelle Schreibposition im DMA Buffer +// Returns the current write position in the DMA buffer inline uint16_t uartDmaWriteBufferPosition() { return uartDmaWriteCount() % uartDmaBufferSize; } -// Liefert die aktuelle Leseposition im DMA Buffer +// Returns the current read position in the DMA buffer inline uint16_t uartDmaReadBufferPosition() { return uartDmaReadCount % uartDmaBufferSize; } -// Liefert die aktuelle Leseposition als Pointer +// Returns the current reading position as a pointer inline uint8_t* uartDmaReadAddr() { return ((uint8_t*)uartDmaBuffer + uartDmaReadBufferPosition()); } -// Startet den Transfer nach Abschluss neu. +// Restarts the transfer after completion. void __time_critical_func(uartDmaRestart)() { // println("Restart"); uartDmaRestartCount = uartDmaWriteBufferPosition() - uartDmaReadBufferPosition(); - // wenn uartDmaRestartCount == 0 ist, wurde alles verarbeitet und der read count kann mit dem neustart wieder auf 0 gesetzt werden. + // if uartDmaRestartCount == 0, everything has been processed and the read count can be set to 0 again with the restart. if (uartDmaRestartCount == 0) { uartDmaReadCount = 0; From dd5367eaac109febf01bc0b18bba93990232ed23 Mon Sep 17 00:00:00 2001 From: Thomas Kunze Date: Fri, 9 Aug 2024 20:18:10 +0200 Subject: [PATCH 40/44] lib props --- README.md | 47 ++-------------------------------------------- library.json | 16 ++++++++++++++++ library.properties | 8 ++++---- 3 files changed, 22 insertions(+), 49 deletions(-) create mode 100644 library.json diff --git a/README.md b/README.md index 953cfa8..3d6b0f5 100644 --- a/README.md +++ b/README.md @@ -4,50 +4,7 @@ This projects provides a knx-device stack for arduino (ESP8266, ESP32, SAMD21, R It implements most of System-B specification and can be configured with ETS. The necessary knxprod-files can be generated with the [Kaenx-Creator](https://github.com/OpenKNX/Kaenx-Creator) tool. -For ESP8266 and ESP32 [WifiManager](https://github.com/tzapu/WiFiManager) is used to configure wifi. - -Don't forget to reset ESP8266 manually (disconnect power) after flashing. The reboot doen't work during configuration with ETS otherwise. - Generated documentation can be found [here](https://knx.readthedocs.io/en/latest/). -## Stack configuration possibilities - -Specify prog button GPIO other then `GPIO0`: -```C++ -knx.buttonPin(3); // Use GPIO3 Pin -``` - -Specify a LED GPIO for programming mode other then the `LED_BUILTIN`: -```C++ -knx.ledPin(5); -``` - -Use a custom function instead of a LED connected to GPIO to indicate the programming mode: -```C++ -#include -#include -#include -// create a pixel strand with 1 pixel on PIN_NEOPIXEL -Adafruit_NeoPixel pixels(1, PIN_NEOPIXEL); - -void progLedOff() -{ - pixels.clear(); - pixels.show(); -} - -void progLedOn() -{ - pixels.setPixelColor(0, pixels.Color(20, 0, 0)); - pixels.show(); -} - -void main () -{ - knx.setProgLedOffCallback(progLedOff); - knx.setProgLedOnCallback(progLedOn); - [...] -} -``` - -More configuration options can be found in the examples. +## Usage +See the examples for basic usage options diff --git a/library.json b/library.json new file mode 100644 index 0000000..8f76877 --- /dev/null +++ b/library.json @@ -0,0 +1,16 @@ +{ + "name": "knx", + "version": "1.2.0", + "dependencies": { + }, + "description": "knx stack", + "authors": [ + { + "name": "Thomas Kunze" + } + ], + "repository": { + "type": "git", + "url": "https://github.com/thelsing/knx" + } +} \ No newline at end of file diff --git a/library.properties b/library.properties index 980e8ea..3e79e42 100644 --- a/library.properties +++ b/library.properties @@ -1,10 +1,10 @@ name=knx -version=2.0.0 -author=Thomas Kunze et al. -maintainer=OpenKNX Team +version=1.2.0 +author=Thomas Kunze, et. al. +maintainer=Thomas Kunze sentence=knx stack paragraph= category=Communication -url=https://github.com/OpenKNX/knx +url=https://github.com/thelsing/knx architectures=* includes=knx.h \ No newline at end of file From 84808912dc9b89672c442c35f0bb52905ee0ae13 Mon Sep 17 00:00:00 2001 From: Thomas Kunze Date: Fri, 9 Aug 2024 20:42:28 +0200 Subject: [PATCH 41/44] del files --- .../knx-usb/platformio-ci_BACKUP_12507.ini | 34 ------------------- examples/knx-usb/platformio-ci_BACKUP_584.ini | 34 ------------------- examples/knx-usb/platformio-ci_BACKUP_641.ini | 34 ------------------- examples/knx-usb/platformio-ci_BASE_12507.ini | 30 ---------------- examples/knx-usb/platformio-ci_BASE_584.ini | 30 ---------------- examples/knx-usb/platformio-ci_BASE_641.ini | 30 ---------------- .../knx-usb/platformio-ci_LOCAL_12507.ini | 30 ---------------- examples/knx-usb/platformio-ci_LOCAL_584.ini | 30 ---------------- examples/knx-usb/platformio-ci_LOCAL_641.ini | 30 ---------------- .../knx-usb/platformio-ci_REMOTE_12507.ini | 31 ----------------- examples/knx-usb/platformio-ci_REMOTE_584.ini | 31 ----------------- examples/knx-usb/platformio-ci_REMOTE_641.ini | 31 ----------------- 12 files changed, 375 deletions(-) delete mode 100644 examples/knx-usb/platformio-ci_BACKUP_12507.ini delete mode 100644 examples/knx-usb/platformio-ci_BACKUP_584.ini delete mode 100644 examples/knx-usb/platformio-ci_BACKUP_641.ini delete mode 100644 examples/knx-usb/platformio-ci_BASE_12507.ini delete mode 100644 examples/knx-usb/platformio-ci_BASE_584.ini delete mode 100644 examples/knx-usb/platformio-ci_BASE_641.ini delete mode 100644 examples/knx-usb/platformio-ci_LOCAL_12507.ini delete mode 100644 examples/knx-usb/platformio-ci_LOCAL_584.ini delete mode 100644 examples/knx-usb/platformio-ci_LOCAL_641.ini delete mode 100644 examples/knx-usb/platformio-ci_REMOTE_12507.ini delete mode 100644 examples/knx-usb/platformio-ci_REMOTE_584.ini delete mode 100644 examples/knx-usb/platformio-ci_REMOTE_641.ini diff --git a/examples/knx-usb/platformio-ci_BACKUP_12507.ini b/examples/knx-usb/platformio-ci_BACKUP_12507.ini deleted file mode 100644 index dbe704d..0000000 --- a/examples/knx-usb/platformio-ci_BACKUP_12507.ini +++ /dev/null @@ -1,34 +0,0 @@ -;PlatformIO Project Configuration File -; -; Build options: build flags, source filter -; Upload options: custom upload port, speed and extra flags -; Library options: dependencies, extra library storages -; Advanced options: extra scripting -; -; Please visit documentation for the other options and examples -; https://docs.platformio.org/page/projectconf.html -[env:adafruit_feather_m0] -platform = atmelsam -board = adafruit_feather_m0 -framework = arduino - -; VID must be changed to some known KNX Manufacturer -; so that the KNX USB interface gets recognized by ETS -<<<<<<< HEAD -======= -; not possible within ci ->>>>>>> 06e0365 -;extra_scripts = pre:custom_hwids.py -;board_build.usb_product="KNX RF - USB Interface" - -lib_deps = - SPI - Adafruit TinyUSB Library@0.7.1 - knx - -build_flags = - -DMASK_VERSION=0x27B0 - -DUSE_USB - -DUSE_TINYUSB - -Wno-unknown-pragmas - -DUSE_DATASECURE diff --git a/examples/knx-usb/platformio-ci_BACKUP_584.ini b/examples/knx-usb/platformio-ci_BACKUP_584.ini deleted file mode 100644 index dbe704d..0000000 --- a/examples/knx-usb/platformio-ci_BACKUP_584.ini +++ /dev/null @@ -1,34 +0,0 @@ -;PlatformIO Project Configuration File -; -; Build options: build flags, source filter -; Upload options: custom upload port, speed and extra flags -; Library options: dependencies, extra library storages -; Advanced options: extra scripting -; -; Please visit documentation for the other options and examples -; https://docs.platformio.org/page/projectconf.html -[env:adafruit_feather_m0] -platform = atmelsam -board = adafruit_feather_m0 -framework = arduino - -; VID must be changed to some known KNX Manufacturer -; so that the KNX USB interface gets recognized by ETS -<<<<<<< HEAD -======= -; not possible within ci ->>>>>>> 06e0365 -;extra_scripts = pre:custom_hwids.py -;board_build.usb_product="KNX RF - USB Interface" - -lib_deps = - SPI - Adafruit TinyUSB Library@0.7.1 - knx - -build_flags = - -DMASK_VERSION=0x27B0 - -DUSE_USB - -DUSE_TINYUSB - -Wno-unknown-pragmas - -DUSE_DATASECURE diff --git a/examples/knx-usb/platformio-ci_BACKUP_641.ini b/examples/knx-usb/platformio-ci_BACKUP_641.ini deleted file mode 100644 index dbe704d..0000000 --- a/examples/knx-usb/platformio-ci_BACKUP_641.ini +++ /dev/null @@ -1,34 +0,0 @@ -;PlatformIO Project Configuration File -; -; Build options: build flags, source filter -; Upload options: custom upload port, speed and extra flags -; Library options: dependencies, extra library storages -; Advanced options: extra scripting -; -; Please visit documentation for the other options and examples -; https://docs.platformio.org/page/projectconf.html -[env:adafruit_feather_m0] -platform = atmelsam -board = adafruit_feather_m0 -framework = arduino - -; VID must be changed to some known KNX Manufacturer -; so that the KNX USB interface gets recognized by ETS -<<<<<<< HEAD -======= -; not possible within ci ->>>>>>> 06e0365 -;extra_scripts = pre:custom_hwids.py -;board_build.usb_product="KNX RF - USB Interface" - -lib_deps = - SPI - Adafruit TinyUSB Library@0.7.1 - knx - -build_flags = - -DMASK_VERSION=0x27B0 - -DUSE_USB - -DUSE_TINYUSB - -Wno-unknown-pragmas - -DUSE_DATASECURE diff --git a/examples/knx-usb/platformio-ci_BASE_12507.ini b/examples/knx-usb/platformio-ci_BASE_12507.ini deleted file mode 100644 index 60cc010..0000000 --- a/examples/knx-usb/platformio-ci_BASE_12507.ini +++ /dev/null @@ -1,30 +0,0 @@ -;PlatformIO Project Configuration File -; -; Build options: build flags, source filter -; Upload options: custom upload port, speed and extra flags -; Library options: dependencies, extra library storages -; Advanced options: extra scripting -; -; Please visit documentation for the other options and examples -; https://docs.platformio.org/page/projectconf.html -[env:adafruit_feather_m0] -platform = atmelsam@6.0.1 -board = adafruit_feather_m0 -framework = arduino - -; VID must be changed to some known KNX Manufacturer -; so that the KNX USB interface gets recognized by ETS -extra_scripts = pre:custom_hwids.py -board_build.usb_product="KNX RF - USB Interface" - -lib_deps = - SPI - Adafruit TinyUSB Library@0.7.1 - knx - -build_flags = - -DMASK_VERSION=0x27B0 - -DUSE_USB - -DUSE_TINYUSB - -Wno-unknown-pragmas - -DUSE_DATASECURE diff --git a/examples/knx-usb/platformio-ci_BASE_584.ini b/examples/knx-usb/platformio-ci_BASE_584.ini deleted file mode 100644 index 6cb0046..0000000 --- a/examples/knx-usb/platformio-ci_BASE_584.ini +++ /dev/null @@ -1,30 +0,0 @@ -;PlatformIO Project Configuration File -; -; Build options: build flags, source filter -; Upload options: custom upload port, speed and extra flags -; Library options: dependencies, extra library storages -; Advanced options: extra scripting -; -; Please visit documentation for the other options and examples -; https://docs.platformio.org/page/projectconf.html -[env:adafruit_feather_m0] -platform = atmelsam@6.0.1 -board = adafruit_feather_m0 -framework = arduino - -; VID must be changed to some known KNX Manufacturer -; so that the KNX USB interface gets recognized by ETS -extra_scripts = pre:custom_hwids.py -board_build.usb_product="KNX RF - USB Interface" - -lib_deps = - SPI - Adafruit TinyUSB Library@0.7.1 - knx - -build_flags = - -DMASK_VERSION=0x27B0 - -DUSE_USB - -DUSE_TINYUSB - -Wno-unknown-pragmas - -DUSE_DATASECURE diff --git a/examples/knx-usb/platformio-ci_BASE_641.ini b/examples/knx-usb/platformio-ci_BASE_641.ini deleted file mode 100644 index 60cc010..0000000 --- a/examples/knx-usb/platformio-ci_BASE_641.ini +++ /dev/null @@ -1,30 +0,0 @@ -;PlatformIO Project Configuration File -; -; Build options: build flags, source filter -; Upload options: custom upload port, speed and extra flags -; Library options: dependencies, extra library storages -; Advanced options: extra scripting -; -; Please visit documentation for the other options and examples -; https://docs.platformio.org/page/projectconf.html -[env:adafruit_feather_m0] -platform = atmelsam@6.0.1 -board = adafruit_feather_m0 -framework = arduino - -; VID must be changed to some known KNX Manufacturer -; so that the KNX USB interface gets recognized by ETS -extra_scripts = pre:custom_hwids.py -board_build.usb_product="KNX RF - USB Interface" - -lib_deps = - SPI - Adafruit TinyUSB Library@0.7.1 - knx - -build_flags = - -DMASK_VERSION=0x27B0 - -DUSE_USB - -DUSE_TINYUSB - -Wno-unknown-pragmas - -DUSE_DATASECURE diff --git a/examples/knx-usb/platformio-ci_LOCAL_12507.ini b/examples/knx-usb/platformio-ci_LOCAL_12507.ini deleted file mode 100644 index 90d791c..0000000 --- a/examples/knx-usb/platformio-ci_LOCAL_12507.ini +++ /dev/null @@ -1,30 +0,0 @@ -;PlatformIO Project Configuration File -; -; Build options: build flags, source filter -; Upload options: custom upload port, speed and extra flags -; Library options: dependencies, extra library storages -; Advanced options: extra scripting -; -; Please visit documentation for the other options and examples -; https://docs.platformio.org/page/projectconf.html -[env:adafruit_feather_m0] -platform = atmelsam -board = adafruit_feather_m0 -framework = arduino - -; VID must be changed to some known KNX Manufacturer -; so that the KNX USB interface gets recognized by ETS -;extra_scripts = pre:custom_hwids.py -;board_build.usb_product="KNX RF - USB Interface" - -lib_deps = - SPI - Adafruit TinyUSB Library@0.7.1 - knx - -build_flags = - -DMASK_VERSION=0x27B0 - -DUSE_USB - -DUSE_TINYUSB - -Wno-unknown-pragmas - -DUSE_DATASECURE diff --git a/examples/knx-usb/platformio-ci_LOCAL_584.ini b/examples/knx-usb/platformio-ci_LOCAL_584.ini deleted file mode 100644 index b3dc599..0000000 --- a/examples/knx-usb/platformio-ci_LOCAL_584.ini +++ /dev/null @@ -1,30 +0,0 @@ -;PlatformIO Project Configuration File -; -; Build options: build flags, source filter -; Upload options: custom upload port, speed and extra flags -; Library options: dependencies, extra library storages -; Advanced options: extra scripting -; -; Please visit documentation for the other options and examples -; https://docs.platformio.org/page/projectconf.html -[env:adafruit_feather_m0] -platform = atmelsam -board = adafruit_feather_m0 -framework = arduino - -; VID must be changed to some known KNX Manufacturer -; so that the KNX USB interface gets recognized by ETS -;extra_scripts = pre:custom_hwids.py -;board_build.usb_product="KNX RF - USB Interface" - -lib_deps = - SPI - Adafruit TinyUSB Library@0.7.1 - knx - -build_flags = - -DMASK_VERSION=0x27B0 - -DUSE_USB - -DUSE_TINYUSB - -Wno-unknown-pragmas - -DUSE_DATASECURE diff --git a/examples/knx-usb/platformio-ci_LOCAL_641.ini b/examples/knx-usb/platformio-ci_LOCAL_641.ini deleted file mode 100644 index 90d791c..0000000 --- a/examples/knx-usb/platformio-ci_LOCAL_641.ini +++ /dev/null @@ -1,30 +0,0 @@ -;PlatformIO Project Configuration File -; -; Build options: build flags, source filter -; Upload options: custom upload port, speed and extra flags -; Library options: dependencies, extra library storages -; Advanced options: extra scripting -; -; Please visit documentation for the other options and examples -; https://docs.platformio.org/page/projectconf.html -[env:adafruit_feather_m0] -platform = atmelsam -board = adafruit_feather_m0 -framework = arduino - -; VID must be changed to some known KNX Manufacturer -; so that the KNX USB interface gets recognized by ETS -;extra_scripts = pre:custom_hwids.py -;board_build.usb_product="KNX RF - USB Interface" - -lib_deps = - SPI - Adafruit TinyUSB Library@0.7.1 - knx - -build_flags = - -DMASK_VERSION=0x27B0 - -DUSE_USB - -DUSE_TINYUSB - -Wno-unknown-pragmas - -DUSE_DATASECURE diff --git a/examples/knx-usb/platformio-ci_REMOTE_12507.ini b/examples/knx-usb/platformio-ci_REMOTE_12507.ini deleted file mode 100644 index 6688b72..0000000 --- a/examples/knx-usb/platformio-ci_REMOTE_12507.ini +++ /dev/null @@ -1,31 +0,0 @@ -;PlatformIO Project Configuration File -; -; Build options: build flags, source filter -; Upload options: custom upload port, speed and extra flags -; Library options: dependencies, extra library storages -; Advanced options: extra scripting -; -; Please visit documentation for the other options and examples -; https://docs.platformio.org/page/projectconf.html -[env:adafruit_feather_m0] -platform = atmelsam -board = adafruit_feather_m0 -framework = arduino - -; VID must be changed to some known KNX Manufacturer -; so that the KNX USB interface gets recognized by ETS -; not possible within ci -;extra_scripts = pre:custom_hwids.py -;board_build.usb_product="KNX RF - USB Interface" - -lib_deps = - SPI - Adafruit TinyUSB Library@0.7.1 - knx - -build_flags = - -DMASK_VERSION=0x27B0 - -DUSE_USB - -DUSE_TINYUSB - -Wno-unknown-pragmas - -DUSE_DATASECURE diff --git a/examples/knx-usb/platformio-ci_REMOTE_584.ini b/examples/knx-usb/platformio-ci_REMOTE_584.ini deleted file mode 100644 index 056b639..0000000 --- a/examples/knx-usb/platformio-ci_REMOTE_584.ini +++ /dev/null @@ -1,31 +0,0 @@ -;PlatformIO Project Configuration File -; -; Build options: build flags, source filter -; Upload options: custom upload port, speed and extra flags -; Library options: dependencies, extra library storages -; Advanced options: extra scripting -; -; Please visit documentation for the other options and examples -; https://docs.platformio.org/page/projectconf.html -[env:adafruit_feather_m0] -platform = atmelsam -board = adafruit_feather_m0 -framework = arduino - -; VID must be changed to some known KNX Manufacturer -; so that the KNX USB interface gets recognized by ETS -; not possible within ci -;extra_scripts = pre:custom_hwids.py -;board_build.usb_product="KNX RF - USB Interface" - -lib_deps = - SPI - Adafruit TinyUSB Library@0.7.1 - knx - -build_flags = - -DMASK_VERSION=0x27B0 - -DUSE_USB - -DUSE_TINYUSB - -Wno-unknown-pragmas - -DUSE_DATASECURE diff --git a/examples/knx-usb/platformio-ci_REMOTE_641.ini b/examples/knx-usb/platformio-ci_REMOTE_641.ini deleted file mode 100644 index 6688b72..0000000 --- a/examples/knx-usb/platformio-ci_REMOTE_641.ini +++ /dev/null @@ -1,31 +0,0 @@ -;PlatformIO Project Configuration File -; -; Build options: build flags, source filter -; Upload options: custom upload port, speed and extra flags -; Library options: dependencies, extra library storages -; Advanced options: extra scripting -; -; Please visit documentation for the other options and examples -; https://docs.platformio.org/page/projectconf.html -[env:adafruit_feather_m0] -platform = atmelsam -board = adafruit_feather_m0 -framework = arduino - -; VID must be changed to some known KNX Manufacturer -; so that the KNX USB interface gets recognized by ETS -; not possible within ci -;extra_scripts = pre:custom_hwids.py -;board_build.usb_product="KNX RF - USB Interface" - -lib_deps = - SPI - Adafruit TinyUSB Library@0.7.1 - knx - -build_flags = - -DMASK_VERSION=0x27B0 - -DUSE_USB - -DUSE_TINYUSB - -Wno-unknown-pragmas - -DUSE_DATASECURE From 7c856351831a0732d1b525c4b5fe06eb23a2cc9d Mon Sep 17 00:00:00 2001 From: Thomas Kunze Date: Fri, 9 Aug 2024 21:02:51 +0200 Subject: [PATCH 42/44] fix build --- src/knx/bau57B0.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/knx/bau57B0.cpp b/src/knx/bau57B0.cpp index 68cb919..84da0a5 100644 --- a/src/knx/bau57B0.cpp +++ b/src/knx/bau57B0.cpp @@ -11,7 +11,7 @@ using namespace std; Bau57B0::Bau57B0(Platform& platform) : BauSystemBDevice(platform), DataLinkLayerCallbacks(), _ipParameters(_deviceObj, platform), - _dlLayer(_deviceObj, _ipParameters, _netLayer.getInterface(), _platform, (DataLinkLayerCallbacks*) this) + _dlLayer(_deviceObj, _ipParameters, _netLayer.getInterface(), _platform, *this, (DataLinkLayerCallbacks*) this) #ifdef USE_CEMI_SERVER , _cemiServer(*this) #endif From 5293dd88943161148a1e48703f6203f6c16878e0 Mon Sep 17 00:00:00 2001 From: Thomas Kunze Date: Fri, 9 Aug 2024 21:30:35 +0200 Subject: [PATCH 43/44] no more warnings --- CMakeLists.txt | 3 ++- examples/knx-linux-coupler/CMakeLists.txt | 2 +- examples/knx-linux-coupler/fdsk.cpp | 2 -- examples/knx-linux/CMakeLists.txt | 2 +- examples/knx-linux/fdsk.cpp | 2 -- examples/knxPython/CMakeLists.txt | 2 +- library.json | 2 +- library.properties | 2 +- src/knx/bau07B0.cpp | 5 ++--- src/knx/bau091A.cpp | 8 +++----- src/knx/ip_data_link_layer.cpp | 2 +- src/knx/tpuart_data_link_layer.cpp | 2 +- 12 files changed, 14 insertions(+), 20 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 841e743..af5b0ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,7 @@ cmake_policy(SET CMP0048 NEW) -project(knx VERSION 1.4) cmake_minimum_required(VERSION 3.16) +project(knx VERSION 1.5) + add_subdirectory(examples/knx-linux) add_subdirectory(examples/knx-linux-coupler) diff --git a/examples/knx-linux-coupler/CMakeLists.txt b/examples/knx-linux-coupler/CMakeLists.txt index 1809326..6b839c6 100644 --- a/examples/knx-linux-coupler/CMakeLists.txt +++ b/examples/knx-linux-coupler/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.16) -project(knx-linux-coupler VERSION 1.4) +project(knx-linux-coupler VERSION 1.5) set(LIBRARIES_FROM_REFERENCES "") set(SOURCES diff --git a/examples/knx-linux-coupler/fdsk.cpp b/examples/knx-linux-coupler/fdsk.cpp index bb62d57..c8ca285 100644 --- a/examples/knx-linux-coupler/fdsk.cpp +++ b/examples/knx-linux-coupler/fdsk.cpp @@ -62,8 +62,6 @@ int FdskCalculator::toBase32(uint8_t* in, long length, uint8_t*& out, bool usePa char standardPaddingChar = '='; int result = 0; - int count = 0; - int bufSize = 8; int index = 0; int size = 0; // size of temporary array uint8_t* temp = nullptr; diff --git a/examples/knx-linux/CMakeLists.txt b/examples/knx-linux/CMakeLists.txt index ba740df..837b230 100644 --- a/examples/knx-linux/CMakeLists.txt +++ b/examples/knx-linux/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.16) -project(knx-linux VERSION 1.4) +project(knx-linux VERSION 1.5) set(LIBRARIES_FROM_REFERENCES "") set(SOURCES diff --git a/examples/knx-linux/fdsk.cpp b/examples/knx-linux/fdsk.cpp index bb62d57..c8ca285 100644 --- a/examples/knx-linux/fdsk.cpp +++ b/examples/knx-linux/fdsk.cpp @@ -62,8 +62,6 @@ int FdskCalculator::toBase32(uint8_t* in, long length, uint8_t*& out, bool usePa char standardPaddingChar = '='; int result = 0; - int count = 0; - int bufSize = 8; int index = 0; int size = 0; // size of temporary array uint8_t* temp = nullptr; diff --git a/examples/knxPython/CMakeLists.txt b/examples/knxPython/CMakeLists.txt index 7f795e0..9b3d1e5 100644 --- a/examples/knxPython/CMakeLists.txt +++ b/examples/knxPython/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.16) -project(knx VERSION 1.4) +project(knx VERSION 1.5) add_subdirectory(pybind11) diff --git a/library.json b/library.json index 8f76877..29ff45d 100644 --- a/library.json +++ b/library.json @@ -1,6 +1,6 @@ { "name": "knx", - "version": "1.2.0", + "version": "1.5.0", "dependencies": { }, "description": "knx stack", diff --git a/library.properties b/library.properties index 3e79e42..62a4296 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=knx -version=1.2.0 +version=1.5.0 author=Thomas Kunze, et. al. maintainer=Thomas Kunze sentence=knx stack diff --git a/src/knx/bau07B0.cpp b/src/knx/bau07B0.cpp index cfbc1bb..099987a 100644 --- a/src/knx/bau07B0.cpp +++ b/src/knx/bau07B0.cpp @@ -9,9 +9,8 @@ using namespace std; Bau07B0::Bau07B0(Platform& platform) - : BauSystemBDevice(platform), - _dlLayer(_deviceObj, _netLayer.getInterface(), _platform, *this, (ITpUartCallBacks&) *this, (DataLinkLayerCallbacks*) this), - DataLinkLayerCallbacks() + : BauSystemBDevice(platform), DataLinkLayerCallbacks(), + _dlLayer(_deviceObj, _netLayer.getInterface(), _platform, *this, (ITpUartCallBacks&) *this, (DataLinkLayerCallbacks*) this) #ifdef USE_CEMI_SERVER , _cemiServer(*this) #endif diff --git a/src/knx/bau091A.cpp b/src/knx/bau091A.cpp index 430f2e6..0fa0e7d 100644 --- a/src/knx/bau091A.cpp +++ b/src/knx/bau091A.cpp @@ -14,15 +14,13 @@ implement PID_COUPLER_SERVICES_CONTROL 03_05_01 4.4.7 */ Bau091A::Bau091A(Platform& platform) - : BauSystemBCoupler(platform), + : BauSystemBCoupler(platform), DataLinkLayerCallbacks(), _routerObj(memory(), 0x200, 0x2000), // the Filtertable of 0x091A IP Routers is fixed at 0x200 and 0x2000 long _ipParameters(_deviceObj, platform), _dlLayerPrimary(_deviceObj, _ipParameters, _netLayer.getPrimaryInterface(), _platform, *this, (DataLinkLayerCallbacks*) this), - _dlLayerSecondary(_deviceObj, _netLayer.getSecondaryInterface(), platform, *this, (ITpUartCallBacks&) *this, (DataLinkLayerCallbacks*) this), - DataLinkLayerCallbacks() + _dlLayerSecondary(_deviceObj, _netLayer.getSecondaryInterface(), platform, *this, (ITpUartCallBacks&) *this, (DataLinkLayerCallbacks*) this) #ifdef USE_CEMI_SERVER - , - _cemiServer(*this) + , _cemiServer(*this) #endif { // Before accessing anything of the router object they have to be initialized according to the used medium diff --git a/src/knx/ip_data_link_layer.cpp b/src/knx/ip_data_link_layer.cpp index 3fe45b9..ec19e8d 100644 --- a/src/knx/ip_data_link_layer.cpp +++ b/src/knx/ip_data_link_layer.cpp @@ -1069,7 +1069,7 @@ bool IpDataLinkLayer::isSendLimitReached() uint32_t timeBaseDiff = _frameCountTimeBase - curTime; if(timeBaseDiff > 10) timeBaseDiff = 10; - for(int i = 0; i < timeBaseDiff ; i++) + for(uint32_t i = 0; i < timeBaseDiff ; i++) { _frameCountBase++; _frameCountBase = _frameCountBase % 10; diff --git a/src/knx/tpuart_data_link_layer.cpp b/src/knx/tpuart_data_link_layer.cpp index e70b58c..57f03ef 100644 --- a/src/knx/tpuart_data_link_layer.cpp +++ b/src/knx/tpuart_data_link_layer.cpp @@ -659,7 +659,7 @@ void TpUartDataLinkLayer::requestConfig() _platform.writeUart(0x0); // dummy, see NCN5120 datasheet #else _platform.writeUart(U_MXRSTCNT); - _platform.writeUart(((_repetitions & 0xF0) << 1) || (_repetitions & 0x0F)); + _platform.writeUart(((_repetitions & 0xF0) << 1) | (_repetitions & 0x0F)); #endif } } From af96a7673f3842f7a75500da8bbf452e76b8efab Mon Sep 17 00:00:00 2001 From: Thomas Kunze Date: Fri, 9 Aug 2024 21:47:22 +0200 Subject: [PATCH 44/44] empty lines --- src/knx/bau091A.cpp | 1 - src/knx/ip_data_link_layer.cpp | 1 - src/knx/tpuart_data_link_layer.cpp | 1 - 3 files changed, 3 deletions(-) diff --git a/src/knx/bau091A.cpp b/src/knx/bau091A.cpp index 0fa0e7d..20319d1 100644 --- a/src/knx/bau091A.cpp +++ b/src/knx/bau091A.cpp @@ -205,7 +205,6 @@ TPAckType Bau091A::isAckRequired(uint16_t address, bool isGrpAddr) if(_dlLayerPrimary.isSentToTunnel(address, isGrpAddr)) ack = TPAckType::AckReqAck; #endif - } return ack; diff --git a/src/knx/ip_data_link_layer.cpp b/src/knx/ip_data_link_layer.cpp index ec19e8d..fd07359 100644 --- a/src/knx/ip_data_link_layer.cpp +++ b/src/knx/ip_data_link_layer.cpp @@ -768,7 +768,6 @@ void IpDataLinkLayer::loopHandleConnectRequest(uint8_t* buffer, uint16_t length, } tun->IndividualAddress = tunPa; - } if(tun == nullptr) diff --git a/src/knx/tpuart_data_link_layer.cpp b/src/knx/tpuart_data_link_layer.cpp index 57f03ef..daab217 100644 --- a/src/knx/tpuart_data_link_layer.cpp +++ b/src/knx/tpuart_data_link_layer.cpp @@ -758,7 +758,6 @@ bool TpUartDataLinkLayer::reset() success = true; break; // next run for U_CONFIGURE_IND } - } while (!((millis() - start) >= 10)); connected(success);