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" - ] -} diff --git a/CMakeLists.txt b/CMakeLists.txt index 52bb6ff..af5b0ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,3 +1,8 @@ +cmake_policy(SET CMP0048 NEW) +cmake_minimum_required(VERSION 3.16) +project(knx VERSION 1.5) + + 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/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/examples/knx-demo/platformio-ci.ini b/examples/knx-demo/platformio-ci.ini index 2f0b04c..b426eb0 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#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 + +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..0de299b 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#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 +; 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 diff --git a/examples/knx-linux-coupler/CMakeLists.txt b/examples/knx-linux-coupler/CMakeLists.txt index 7e36926..6b839c6 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.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 c0ef749..837b230 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.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/knx-usb/platformio-ci.ini b/examples/knx-usb/platformio-ci.ini index 90d791c..d362556 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/knxPython/CMakeLists.txt b/examples/knxPython/CMakeLists.txt index 8b67bbe..9b3d1e5 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.5) 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) diff --git a/library.json b/library.json new file mode 100644 index 0000000..29ff45d --- /dev/null +++ b/library.json @@ -0,0 +1,16 @@ +{ + "name": "knx", + "version": "1.5.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 441ebef..62a4296 100644 --- a/library.properties +++ b/library.properties @@ -1,10 +1,10 @@ name=knx -version=1.2.0 -author=Thomas Kunze +version=1.5.0 +author=Thomas Kunze, et. al. maintainer=Thomas Kunze sentence=knx stack paragraph= category=Communication url=https://github.com/thelsing/knx architectures=* -includes=knx.h +includes=knx.h \ No newline at end of file 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/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 c68ecb2..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, (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 @@ -78,7 +77,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 +127,33 @@ 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() { + return (TpUartDataLinkLayer*)&_dlLayer; +} #endif diff --git a/src/knx/bau07B0.h b/src/knx/bau07B0.h index b9c1e6c..44c51ca 100644 --- a/src/knx/bau07B0.h +++ b/src/knx/bau07B0.h @@ -15,13 +15,14 @@ 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); + 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 2465bc4..20319d1 100644 --- a/src/knx/bau091A.cpp +++ b/src/knx/bau091A.cpp @@ -8,16 +8,19 @@ 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()), + : 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, (DataLinkLayerCallbacks*) this), - _dlLayerSecondary(_deviceObj, _netLayer.getSecondaryInterface(), platform, (ITpUartCallBacks&) *this, (DataLinkLayerCallbacks*) this), - DataLinkLayerCallbacks() + _dlLayerPrimary(_deviceObj, _ipParameters, _netLayer.getPrimaryInterface(), _platform, *this, (DataLinkLayerCallbacks*) this), + _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 @@ -33,9 +36,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 +100,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 +147,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 +159,77 @@ 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() { + return (IpDataLinkLayer*)&_dlLayerPrimary; +} + +TpUartDataLinkLayer* Bau091A::getSecondaryDataLinkLayer() { + return (TpUartDataLinkLayer*)&_dlLayerSecondary; +} #endif diff --git a/src/knx/bau091A.h b/src/knx/bau091A.h index 606fbda..494d2da 100644 --- a/src/knx/bau091A.h +++ b/src/knx/bau091A.h @@ -17,13 +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 6180539..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 @@ -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..d002040 100644 --- a/src/knx/bau27B0.h +++ b/src/knx/bau27B0.h @@ -22,9 +22,10 @@ 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); + 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 f5e5367..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 @@ -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..98552fc 100644 --- a/src/knx/bau2920.h +++ b/src/knx/bau2920.h @@ -22,9 +22,11 @@ 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); + 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 3786c2a..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 @@ -83,7 +83,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 @@ -143,4 +143,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..e8e6835 100644 --- a/src/knx/bau57B0.h +++ b/src/knx/bau57B0.h @@ -15,10 +15,11 @@ 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); + 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 1debaf3..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,14 +52,20 @@ 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); } @@ -59,7 +75,11 @@ 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 @@ -73,241 +93,51 @@ void CemiServer::dataIndicationToTunnel(CemiFrame& frame) } else { +#endif memcpy(&data[0], frame.data(), frame.dataLength()); +#ifdef USE_RF } -#else - uint8_t data[frame.dataLength()]; - memcpy(&data[0], frame.data(), frame.dataLength()); #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); - } -#ifdef USE_RF - 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; - } - } -#endif - 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; } @@ -325,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; } @@ -352,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 73c88d2..5782fad 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,11 +220,29 @@ 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()); @@ -179,7 +255,9 @@ bool DataLinkLayer::sendTelegram(NPDU & npdu, AckType ack, uint16_t destinationA 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; @@ -190,4 +268,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/dptconvert.cpp b/src/knx/dptconvert.cpp index 1954d2d..9b3ff29 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); @@ -517,15 +523,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 0abbfc0..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() { @@ -310,4 +309,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 85975be..244fab6 100644 --- a/src/knx/group_object.h +++ b/src/knx/group_object.h @@ -7,12 +7,6 @@ class GroupObjectTableObject; -/** - * LIMITATION: The differentiation between uninitialized and initialized state can NOT be represented correctly in ComFlag alone: - * It might be in state Transmitting during a ReadRequest on startup while value is still not valid. - * - * See ComFlagEx for a clear uninitialized handling. - */ enum ComFlag : uint8_t { Updated = 0, //!< Group object was updated @@ -21,17 +15,13 @@ enum ComFlag : uint8_t Transmitting = 3, //!< Group Object is processed a the moment (read or write) Ok = 4, //!< read or write request were send successfully Error = 5, //!< there was an error on processing a request - Uninitialized = 6 //!< uninitialized Group Object, its value is not valid; WARNING: Other Values do NOT guarantee an actual valid value! + Uninitialized = 6 //!< uninitialized Group Object, its value is not valid }; -/** - * Extended ComFlag - * Add a separate uninitialized flag to overcome the limitations of ComFlag. - * - * Implementation Note: - * We use MSB to store uninitialized state and keep the size of GroupObject the same saving memory resources. - * The old uninitialized handling is not changed for compatibility reasons. - */ +// 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; @@ -67,10 +57,6 @@ class GroupObject * The constructor. */ GroupObject(); - /** - * The copy constructor. - */ - GroupObject(const GroupObject& other); /** * The destructor. */ @@ -149,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. @@ -183,6 +174,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 @@ -271,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(); 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..fd07359 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,697 @@ 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: "); @@ -143,7 +1068,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/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 545e3ad..51b2d03 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; @@ -101,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; @@ -185,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) @@ -217,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 b9e4c5c..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); @@ -62,6 +63,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(); @@ -83,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 new file mode 100644 index 0000000..f1bbf76 --- /dev/null +++ b/src/knx/tp_frame.h @@ -0,0 +1,305 @@ +#pragma once +#pragma GCC optimize("O3") + +#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 0b00001000 + +// Means that the frame has been acked with BUSY +#define TP_FRAME_FLAG_ACK_BUSY 0b00000100 + +// 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 +{ + 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 = (uint8_t *)malloc(_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) - 1; // -1 without CRC + } + + /** + * 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() - 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 - 1); // -1 without CRC + } + + 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..daab217 100644 --- a/src/knx/tpuart_data_link_layer.cpp +++ b/src/knx/tpuart_data_link_layer.cpp @@ -1,620 +1,962 @@ #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 + * + */ // 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_ACKN_BUSY_MASK 0x0C +#define L_ACKN_NACK_MASK 0xC0 +#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 this state, the system waits for new control commands. + RX_IDLE, + + // In this state, all bytes are regarded as bytes for a frame. + RX_FRAME, + + // In this state, all bytes are discarded + RX_INVALID, + + // Monitoring is still waiting for an ACk + 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) ? '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(")"); } -void TpUartDataLinkLayer::loop() +/* + * Processes all 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 + /* + * 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; -#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 + // process data + while (_platform.uartAvailable()) + { + processRxByte(); + } - // 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()); + isrUnlock(); } -bool TpUartDataLinkLayer::sendFrame(CemiFrame& frame) +/* + * Processes 1 incoming byte (if available) + */ +void TpUartDataLinkLayer::processRxByte() { - if (!_enabled) + int byte = _platform.readUart(); + + // RxBuffer empty + if (byte < 0) + return; + + /* + * 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()) { - dataConReceived(frame, false); + processRxFrameComplete(); + _rxState = RX_IDLE; + } + + if (_rxState == RX_INVALID) + { + /* + * 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. + * + * 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) + * + * Otherwise this section does nothing and thus discards the invalid bytes + */ + if (markerMode()) + { + if (!_rxMarker && byte == U_FRAME_END_IND) + { + _rxMarker = true; + } + else if (_rxMarker && byte == U_FRAME_END_IND) + { + // double byte found so reset marker - no frame end + _rxMarker = false; + } + else if (_rxMarker) + { + // frame end found. -> RX_IDLE + _rxMarker = false; + _rxState = RX_IDLE; + } + } + } + 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) + { + /* + * 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); + + // Provoke invalid frames for tests + // if (millis() % 20 == 0) + // _rxFrame->addByte(0x1); + + _rxMarker = false; + _rxState = RX_FRAME; + + /* + * 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. + * + * 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) + { + _platform.writeUart(U_ACK_REQ); + } + } + else + { + // The commands are evaluated here, if this has already happened. + + 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 + /* + * 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 + } + 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) + { + /* + * If a frame has not yet been closed and an Ack comes in. + * then set the ACK. + */ + if (_rxFrame->size() > 0) + { + 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"); + } + else if ((byte & L_DATA_CON_MASK) == L_DATA_CON) + { + if (_txState == TX_FRAME) + { + const bool success = ((byte ^ L_DATA_CON_MASK) >> 7); + processTxFrameComplete(success); + } + else + { + // This byte was not expected because nothing was sent. + _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(); +} + +/* + * Process incoming byte of a frame + */ +void TpUartDataLinkLayer::processRxFrameByte(uint8_t byte) +{ + /* + * 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)) + { + _rxMarker = true; + } + + /* + * 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) + { + _rxMarker = false; + processRxFrameComplete(); + + /* + * 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; + } + + /* + * 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 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; + } + + /* + * 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)) + { + // Reset the marker if active + _rxMarker = false; + // Accept the byte + _rxFrame->addByte(byte); + + // If the bus monitor has been started, no processing takes place - i.e. no ACKing + if (!_monitoring) + { + // If more than 7 bytes are available, you can check whether the frame is intended for "me". + if (_rxFrame->size() == 7) + { + // Check whether I am responsible for the frame + TPAckType ack = _cb.isAckRequired(_rxFrame->destination(), _rxFrame->isGroupAddress()); + if (_forceAck || ack) + { + /* + * 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); + + // Of course, this is only allowed if I am not sending myself, as you cannot ACK your own frames + if (_txState == TX_IDLE) + { + // Save that Acking should take place + _rxFrame->addFlags(TP_FRAME_FLAG_ACK); + + // and in the TPUart so that it can send the ACK + _platform.writeUart(U_ACK_REQ | ack); + } + } + } + +#ifdef USE_TP_RX_QUEUE + // 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)) + { + // Only if I am not sending myself + if (_txState == TX_IDLE) + { + _platform.writeUart(U_ACK_REQ | U_ACK_REQ_ADRESSED | U_ACK_REQ_BUSY); + } + } + } +#endif + } + } + + /* + * 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())) + { + processRxFrameComplete(); + } +} + +/* + * 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() +{ + // If no frame is currently being edited, then cancel + if (!_rxFrame->size()) + return; + + // Is the frame complete and valid + if (_rxFrame->isValid()) + { + // When a frame has been sent + if (_txState == TX_FRAME) + { + // check whether the receive corresponds to this: comparison of the source address and destination address and start byte without taking the retry bit into account + if(!((_rxFrame->data(0) ^ _txFrame->data(0)) & ~0x20) && _rxFrame->destination() == _txFrame->destination() && _rxFrame->source() == _txFrame->source()) + { + // and mark this accordingly + // println("MATCH"); + _rxFrame->addFlags(TP_FRAME_FLAG_ECHO); + } + // Now wait for the L_DATA_CON + } + // 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) + { + /* + * 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) + { + _rxState = RX_AWAITING_ACK; + return; + } + _rxProcessdFrameCounter++; + } + else + { + // Otherwise, discard the package and release the memory -> as it is not packed into the queue + _rxIgnoredFrameCounter++; + } + // And ready for control codes again + _rxState = RX_IDLE; + } + else + { + /* + * 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); + _rxState = RX_INVALID; // ignore bytes + } + +#ifdef USE_TP_RX_QUEUE + pushRxFrameQueue(); +#else + processRxFrame(_rxFrame); +#endif + + // resets the current frame pointer + _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; +} + +/* + * Puts the frame to be sent into a queue, as the TpUart may not yet be ready to send. + */ +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; + } + + _txQueueCount++; +} + +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) +{ + _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; } - addFrameTxQueue(frame); + TpFrame *tpFrame = new TpFrame(cemiFrame); + // printHex(" TP>: ", tpFrame->data(), tpFrame->size()); + pushTxFrameQueue(tpFrame); return true; } -bool TpUartDataLinkLayer::resetChip() +/* + * 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 */) { - 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; - - _waitConfirmStartTime = millis(); - return false; -} - -bool TpUartDataLinkLayer::resetChipTick() -{ - int resp = _platform.readUart(); - if (resp == U_RESET_IND) + if (!force) { - _waitConfirmStartTime = 0; - return true; + if (!(_rxState == RX_IDLE || _rxState == RX_INVALID)) + return; + + // Only 1x per second + if ((millis() - _lastStateRequest) < 1000) + return; } - else if (millis() - _waitConfirmStartTime > RESET_TIMEOUT) - _waitConfirmStartTime = 0; - - return false; + + // println("requestState"); + + // Send configuration or mode + if (_monitoring) + _platform.writeUart(U_BUSMON_REQ); + else + requestConfig(); + + // Question status on - if monitoring inactive + if (!_monitoring) + _platform.writeUart(U_STATE_REQ); + + _lastStateRequest = millis(); } -void TpUartDataLinkLayer::stopChip() +/* + * Sends the current config to the 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 + + // Deviating 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) +/* + * A simplified lock mechanism that only works on the same core. + * Perfect for 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; +} + +void TpUartDataLinkLayer::resetStats() +{ + _rxProcessdFrameCounter = 0; + _rxIgnoredFrameCounter = 0; + _rxInvalidFrameCounter = 0; + _rxInvalidFrameCounter = 0; + _rxUnkownControlCounter = 0; + _txFrameCounter = 0; + _txProcessdFrameCounter = 0; +} + +bool TpUartDataLinkLayer::reset() +{ + // println("Reset TP"); + if (!_initialized) + { + _platform.setupUart(); + _initialized = true; + } + + // Wait for isr & block isr + isrLock(true); + + // Reset + resetStats(); + clearTxFrame(); + clearTxFrameQueue(); + + if (_rxFrame != nullptr) + { + _rxFrame->reset(); + } + _rxState = RX_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(true); + _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); + resetStats(); } 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; +} + +/* + * 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() +{ + if (_txState == TX_FRAME && (millis() - _txLastTime) > 1000) + processTxFrameComplete(false); +} + +/* + * 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() +{ + 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; + } + + _txQueueCount--; + + clearTxFrame(); + + // 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 + + processTxFrameBytes(); + } +} + +/* + * 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() +{ + 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 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) + { + println("TPUart overflow detected!"); + _rxOverflow = false; + _rxState = RX_INVALID; + } + + if (_tpState) + { + print("TPUart state error: "); + println(_tpState, 2); + _tpState = 0; + } + + processRx(); +#ifdef USE_TP_RX_QUEUE + processRxQueue(); +#endif + + requestState(); + clearOutdatedTxFrame(); + processTxQueue(); + checkConnected(); +} + +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 +964,260 @@ DptMedium TpUartDataLinkLayer::mediumType() const return DptMedium::KNX_TP1; } -bool TpUartDataLinkLayer::sendSingleFrameByte() +/* + * This can be used to switch the power supply to the 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) + /* + * 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. + * + * 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++) { - 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)) // Last bytes (checksum) + _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, + BusAccessUnit& busAccessUnit, + ITpUartCallBacks &cb, + DataLinkLayerCallbacks *dllcb) + : DataLinkLayer(devObj, netLayerEntity, platform, busAccessUnit), + _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() +/* + * Returns the number of frames that could not be processed. + */ +uint32_t TpUartDataLinkLayer::getRxInvalidFrameCounter() { - if (_tx_queue.front == NULL) - { - return true; - } + return _rxInvalidFrameCounter; +} + +/* + * Returns the number of frames that are valid and intended for the device + */ +uint32_t TpUartDataLinkLayer::getRxProcessdFrameCounter() +{ + return _rxProcessdFrameCounter; +} + +/* + * Returns the number of frames that are valid but not intended for the device + */ +uint32_t TpUartDataLinkLayer::getRxIgnoredFrameCounter() +{ + return _rxIgnoredFrameCounter; +} + +/* + * Returns the number of control bytes counted that were not recognized + */ +uint32_t TpUartDataLinkLayer::getRxUnknownControlCounter() +{ + return _rxUnkownControlCounter; +} + +/* + * Returns the number of frames sent + */ +uint32_t TpUartDataLinkLayer::getTxFrameCounter() +{ + return _txFrameCounter; +} +/* + * Returns the number of frames sent + */ +uint32_t TpUartDataLinkLayer::getTxProcessedFrameCounter() +{ + return _txProcessdFrameCounter; +} + +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 + if (!(tpFrame->flags() & TP_FRAME_FLAG_ECHO)) + 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! + * + * 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)() +{ + processRx(true); +} + +/* + * 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() +{ + 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 = TpFrame(size); + tpFrame.addFlags(pullByteFromRxQueue()); + + for (uint16_t i = 0; i < size; i++) + tpFrame.addByte(pullByteFromRxQueue()); + + processRxFrame(&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..4fe8fe8 100644 --- a/src/knx/tpuart_data_link_layer.h +++ b/src/knx/tpuart_data_link_layer.h @@ -3,16 +3,33 @@ #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 + +#ifndef MAX_TX_QUEUE +#define MAX_TX_QUEUE 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; + virtual TPAckType isAckRequired(uint16_t address, bool isGrpAddr) = 0; }; class TpUartDataLinkLayer : public DataLinkLayer @@ -22,58 +39,140 @@ 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); 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(); + void resetStats(); + +#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(); + uint32_t getTxFrameCounter(); + uint32_t getTxProcessedFrameCounter(); + 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 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(); + + /* + * 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 clearTxFrameQueue(); + void processRxFrameComplete(); + inline void processRxFrame(TpFrame* tpFrame); + void pushTxFrameQueue(TpFrame* tpFrame); + void requestState(bool force = false); + 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); + void clearTxFrame(); + void clearOutdatedTxFrame(); + void processTxFrameComplete(bool success); ITpUartCallBacks& _cb; DataLinkLayerCallbacks* _dllcb; diff --git a/src/knx_facade.h b/src/knx_facade.h index 75d8fdb..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() { @@ -286,10 +270,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); diff --git a/src/rp2040_arduino_platform.cpp b/src/rp2040_arduino_platform.cpp index ddc89cb..472d86b 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 @@ -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) ----------------------------------------------------*/ @@ -31,25 +30,82 @@ 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; + +// 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; +} + +// Returns the current write position in the DMA buffer +inline uint16_t uartDmaWriteBufferPosition() +{ + return uartDmaWriteCount() % uartDmaBufferSize; +} + +// Returns the current read position in the DMA buffer +inline uint16_t uartDmaReadBufferPosition() +{ + return uartDmaReadCount % uartDmaBufferSize; +} + +// Returns the current reading position as a pointer +inline uint8_t* uartDmaReadAddr() +{ + return ((uint8_t*)uartDmaBuffer + uartDmaReadBufferPosition()); +} + +// Restarts the transfer after completion. +void __time_critical_func(uartDmaRestart)() +{ + // println("Restart"); + uartDmaRestartCount = uartDmaWriteBufferPosition() - uartDmaReadBufferPosition(); + + // 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; + } + + 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 -#ifdef KNX_IP_W5500 +#ifdef KNX_IP_LAN extern Wiznet5500lwIP KNX_NETIF; #elif defined(KNX_IP_WIFI) #elif defined(KNX_IP_GENERIC) @@ -57,26 +113,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,31 +142,162 @@ void RP2040ArduinoPlatform::knxUartPins(pin_size_t rxPin, pin_size_t txPin) _txPin = txPin; } +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(); @@ -123,7 +311,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 @@ -132,20 +320,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; } @@ -154,10 +342,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); } @@ -167,22 +355,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; } @@ -212,10 +400,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) @@ -223,7 +411,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(); @@ -242,12 +430,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(); @@ -273,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) - uint8_t macaddr[6] = {0,0,0,0,0,0}; +#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 } @@ -289,12 +475,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()); @@ -313,14 +499,14 @@ 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(); 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) @@ -328,14 +514,17 @@ 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); + _remoteIP = _udp.remoteIP(); + _remotePort = _udp.remotePort(); + src_addr = htonl(_remoteIP); + src_port = _remotePort; // print("Remote IP: "); // print(_udp.remoteIP().toString().c_str()); @@ -348,12 +537,17 @@ 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)); + + if(!addr) + ucastaddr = _remoteIP; + if(!port) + port = _remotePort; // 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 @@ -371,5 +565,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 b4e7f8d..aa6e746 100644 --- a/src/rp2040_arduino_platform.h +++ b/src/rp2040_arduino_platform.h @@ -22,40 +22,34 @@ #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 +#define KNX_NETIF WiFi #endif -#ifndef _ETG_LOGLEVEL_ -#define _ETG_LOGLEVEL_ 1 +#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 - -#define ETHERNET_USE_RPIPICO true -#include // https://github.com/khoih-prog/Ethernet_Generic - - -#define KNX_NETIF Ethernet - +#if USE_KNX_DMA_IRQ == 1 +#define KNX_DMA_IRQ DMA_IRQ_1 +#else +#define KNX_DMA_IRQ DMA_IRQ_0 #endif @@ -67,7 +61,20 @@ public: // uart void knxUartPins(pin_size_t rxPin, pin_size_t txPin); - void setupUart(); + 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; @@ -112,25 +119,21 @@ 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; - #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 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