diff --git a/.travis.yml b/.travis.yml
index 0b8bcb6..852763e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -34,8 +34,8 @@ before_install:
- if [ "$MODE" = "ARDUINO" ]; then sed -i 's#compiler.warning_level=all#compiler.warning_level=default#' ~/.arduino15/preferences.txt; fi
# changes for bsec lib
# samd
- - if [ "$MODE" = "ARDUINO" ]; then sed -ri 's#(recipe.c.combine.pattern=[^$]*\{archive_file\}")( -Wl,--end-group)#\1 {compiler.libraries.ldflags}\2#' ~/.arduino15/packages/arduino/hardware/samd/1.8.3/platform.txt; fi
- - if [ "$MODE" = "ARDUINO" ]; then sed -i 's#compiler.elf2hex.extra_flags=#compiler.elf2hex.extra_flags=\ncompiler.libraries.ldflags=#' ~/.arduino15/packages/arduino/hardware/samd/1.8.3/platform.txt; fi
+ - if [ "$MODE" = "ARDUINO" ]; then sed -ri 's#(recipe.c.combine.pattern=[^$]*\{archive_file\}")( -Wl,--end-group)#\1 {compiler.libraries.ldflags}\2#' ~/.arduino15/packages/arduino/hardware/samd/1.8.4/platform.txt; fi
+ - if [ "$MODE" = "ARDUINO" ]; then sed -i 's#compiler.elf2hex.extra_flags=#compiler.elf2hex.extra_flags=\ncompiler.libraries.ldflags=#' ~/.arduino15/packages/arduino/hardware/samd/1.8.4/platform.txt; fi
# esp8266
- if [ "$MODE" = "ARDUINO" ]; then sed -ri 's#(recipe.c.combine.pattern=[^$]*\{compiler.c.elf.libs\})( -Wl,--end-group "-L\{build.path\}")#\1 {compiler.libraries.ldflags}\2#' ~/.arduino15/packages/esp8266/hardware/esp8266/2.5.2/platform.txt; fi
- if [ "$MODE" = "ARDUINO" ]; then sed -i 's#compiler.elf2hex.extra_flags=#compiler.elf2hex.extra_flags=\ncompiler.libraries.ldflags=#' ~/.arduino15/packages/esp8266/hardware/esp8266/2.5.2/platform.txt; fi
diff --git a/doc/CC1101-868mhz-radio-module-pinout.jpg b/doc/CC1101-868mhz-radio-module-pinout.jpg
new file mode 100644
index 0000000..1071fc9
Binary files /dev/null and b/doc/CC1101-868mhz-radio-module-pinout.jpg differ
diff --git a/doc/knx_rf_notes.md b/doc/knx_rf_notes.md
new file mode 100644
index 0000000..f20cbd0
--- /dev/null
+++ b/doc/knx_rf_notes.md
@@ -0,0 +1,79 @@
+KNX-RF S-Mode
+=============
+
+Implementation Notes
+--------------------
+* KNX-RF E-Mode (pushbutton method) is NOT supported!
+* KNX-RF S-Mode is implemented as KNX-RF READY (KNX-RF 1.R) which means only one single channel and no fast-ack is used.
+ -> KNX RF Multi (KNX-RF 1.M) would be required for this. However, implementation is way more complex as frequency hopping (fast and slow channels) is used and fast-ack
+ is based on a TDMA-like access scheme which has very strict timing requirements for the time slots.
+ -> summary: KNX-RF 1.R does NOT acknowledge packets on the air (data link layer). So standard GROUP_VALUE_WRITE messages in multicast mode could get lost!
+ Connection-oriented communication for device management is handled by the transport layer and uses T_ACK and T_NACK.
+* the driver (rf_physical_layer.cpp) uses BOTH signals (GDO0, GDO2) of the RF transceiver C1101
+ -> GDO0 is asserted on RX/TX packet start and de-asserted on RX/TX packet end or RX/TX FIFO overflow/underflow
+ -> GDO2 is asserted is the FIFO needs to read out (RX) or refilled (TX)
+* the driver (rf_physical_layer.cpp) uses both packet length modes of the CC1101: infinite length and fixed length are switched WHILE receiving or transmitting a packet.
+* the edges of the signals GDO0 and GDO2 are detected by polling in the main loop and NOT by using interrupts
+ -> as a consequence the main loop must not be delayed too much, the transceiver receives/transmitts the data bytes itself into/from the FIFOs though (max. FIFO size 64 bytes each (TX/RX)!).
+ -> KNX-RF bitrate is 16384 bits/sec. -> so 40 bytes (Preamble, Syncword, Postamble) around 20ms for reception/transmission of a complete packet (to be verified!)
+ -> another implementation using interrupts could also be realized
+* the driver does not use the wake-on-radio of the CC1101. The bidirectional battery powered devices are not useful at the moment.
+ -> wake-on-radio would also require the MCU to sleep and be woken up through interrupts.
+ -> BUT: UNIdirectional (battery powered) device could be realized: one the device has been configured in bidirectional mode.
+ The device (MCU) could sleep and only wake up if something needs to be done. Only useful for transmitters (e.g. sensors like push button, temperature sensor, etc.).
+
+ToDo
+----
+* Packet duplication prevention based on the data link layer frame counter if KNX-RF retransmitters (range extension) are active (should be easy to add)
+* KNX-RF 1.M (complex, may need an additional MCU, not planned for now)
+ -> maybe with a more capable transceiver: http://www.lapis-semi.com/en/semicon/telecom/telecom-product.php?PartNo=ML7345
+ it could handle manchester code, CRC-16 and the whole Wireless MBUS frame structure in hardware. Also used by Tapko for KAIstack.
+* KNX Data Secure with security proxy profile in line coupler (e.g. TP<>RF). See KNX AN158 (KNX Data Secure draft spec.) p.9
+ -> KNX-RF very much benefits from having authenticated, encrypted data exchange.
+ -> Security Proxy terminates the secure applicationj layer (S-AL) in the line coupler. So the existing plain TP installation without data secure feature
+ can be kept as is.
+
+Development Setup
+-----------------
+Development is done on a cheap Wemos SAMD21 M0-Mini board with a standard CC1101 module (868MHz) from Ebay. Beware of defective and bad quality modules.
+The SAMD21 MCU is connected via SWD (Segger J-Link) to PlatformIO (Visual Studio Code). Additionally the standard UART (Arduino) is used for serial debug messages.
+The USB port of the SAMD21 is not used at all.
+
+Connection wiring:
+------------------
+
+Signal SAMD21 CC1101
+----------+-------------+---------------
+SPI_nCS | D10(PA18) | CSN
+SPI_MOSI | D11(PA16) | SI
+SPI_MISO | D12(PA19) | SO
+SPI_SCK | D13(PA17) | SCLK
+GDO0 | D7(PA21) | GDO0
+GDO2 | D9(PA07) | GDO2
+
+Arduino MZEROUSB variant needs patching to enable SPI on SERCOM1 on D10-D13.
+
+variant.h
+---------
+/*
+ * SPI Interfaces
+ */
+#define SPI_INTERFACES_COUNT 1
+
+#define PIN_SPI_MISO (18u)
+#define PIN_SPI_MOSI (21u)
+#define PIN_SPI_SCK (20u)
+#define PERIPH_SPI sercom1
+#define PAD_SPI_TX SPI_PAD_0_SCK_1
+#define PAD_SPI_RX SERCOM_RX_PAD_3
+
+variant.cpp
+-----------
+ // 18..23 - SPI pins (ICSP:MISO,SCK,MOSI)
+ // ----------------------
+ { PORTA, 19, PIO_SERCOM, PIN_ATTR_DIGITAL, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_12 }, // MISO: SERCOM1/PAD[3]
+ { NOT_A_PORT, 0, PIO_NOT_A_PIN, PIN_ATTR_NONE, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_NONE }, // 5V0
+ { PORTA, 17, PIO_SERCOM, PIN_ATTR_DIGITAL, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_11 }, // SCK: SERCOM1/PAD[1]
+ { PORTA, 16, PIO_SERCOM, PIN_ATTR_DIGITAL, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_10 }, // MOSI: SERCOM1/PAD[0]
+ { NOT_A_PORT, 0, PIO_NOT_A_PIN, PIN_ATTR_NONE, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_NONE }, // RESET
+ { NOT_A_PORT, 0, PIO_NOT_A_PIN, PIN_ATTR_NONE, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_NONE }, // GND
diff --git a/examples/knx-demo/knx-demo-rf.knxprod b/examples/knx-demo/knx-demo-rf.knxprod
new file mode 100644
index 0000000..ece15a9
Binary files /dev/null and b/examples/knx-demo/knx-demo-rf.knxprod differ
diff --git a/examples/knx-demo/knx-demo-rf.xml b/examples/knx-demo/knx-demo-rf.xml
new file mode 100644
index 0000000..38993f6
--- /dev/null
+++ b/examples/knx-demo/knx-demo-rf.xml
@@ -0,0 +1,136 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/knx-linux/CMakeLists.txt b/knx-linux/CMakeLists.txt
index 5a9ba65..6b2f1ad 100644
--- a/knx-linux/CMakeLists.txt
+++ b/knx-linux/CMakeLists.txt
@@ -9,7 +9,8 @@ add_executable(knx-linux
../src/knx/association_table_object.cpp
../src/knx/bau.cpp
../src/knx/bau07B0.cpp
- ../src/knx/bau57B0.cpp
+ ../src/knx/bau27B0.cpp
+ ../src/knx/bau57B0.cpp
../src/knx/bau_systemB.cpp
../src/knx/bits.cpp
../src/knx/cemi_frame.cpp
@@ -23,12 +24,50 @@ add_executable(knx-linux
../src/knx/memory.cpp
../src/knx/network_layer.cpp
../src/knx/npdu.cpp
- ../src/knx/table_object.cpp
+ ../src/knx/rf_physical_layer.cpp
+ ../src/knx/rf_data_link_layer.cpp
+ ../src/knx/rf_medium_object.cpp
+ ../src/knx/table_object.cpp
../src/knx/tpdu.cpp
../src/knx/tpuart_data_link_layer.cpp
../src/knx/transport_layer.cpp
../src/knx/platform.cpp
+ ../src/knx/address_table_object.h
+ ../src/knx/apdu.h
+ ../src/knx/application_layer.h
+ ../src/knx/application_program_object.h
+ ../src/knx/association_table_object.h
+ ../src/knx/bau.h
+ ../src/knx/bau07B0.h
+ ../src/knx/bau27B0.h
+ ../src/knx/bau57B0.h
+ ../src/knx/bau_systemB.h
+ ../src/knx/bits.h
+ ../src/knx/cemi_frame.h
+ ../src/knx/data_link_layer.h
+ ../src/knx/device_object.h
+ ../src/knx/group_object.h
+ ../src/knx/group_object_table_object.h
+ ../src/knx/interface_object.h
+ ../src/knx/ip_data_link_layer.h
+ ../src/knx/ip_parameter_object.h
+ ../src/knx/memory.h
+ ../src/knx/network_layer.h
+ ../src/knx/npdu.h
+ ../src/knx/rf_physical_layer.h
+ ../src/knx/rf_data_link_layer.h
+ ../src/knx/rf_medium_object.h
+ ../src/knx/table_object.h
+ ../src/knx/tpdu.h
+ ../src/knx/tpuart_data_link_layer.h
+ ../src/knx/transport_layer.h
+ ../src/knx/platform.h
main.cpp
+ ../src/linux_platform.h
+ ../src/knx_facade.h
+ ../src/knx/dptconvert.h
+ ../src/knx/knx_value.h
+ ../src/knx/dpt.h
../src/linux_platform.cpp
../src/knx_facade.cpp
../src/knx/dptconvert.cpp
@@ -38,3 +77,5 @@ target_link_libraries(knx-linux "${LIBRARIES_FROM_REFERENCES}")
include_directories(../src)
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Wall -Wno-unknown-pragmas -Wno-switch -g -O0")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wall -Wno-unknown-pragmas -Wno-switch -g -O0")
+set_property(TARGET knx-linux PROPERTY CXX_STANDARD 11)
+add_definitions(-DMEDIUM_TYPE=5)
diff --git a/knx-linux/main.cpp b/knx-linux/main.cpp
index 3a38d4a..4b64f64 100644
--- a/knx-linux/main.cpp
+++ b/knx-linux/main.cpp
@@ -1,12 +1,37 @@
#include "knx_facade.h"
+#if MEDIUM_TYPE == 5
#include "knx/bau57B0.h"
+#elif MEDIUM_TYPE == 2
+#include "knx/bau27B0.h"
+#else
+#error Only MEDIUM_TYPE IP and RF supported
+#endif
#include "knx/group_object_table_object.h"
#include "knx/bits.h"
#include
#include
#include
+#include
+#include
+#include
+#include
+volatile sig_atomic_t loopActive = 1;
+void signalHandler(int sig)
+{
+ (void)sig;
+
+ // can be called asynchronously
+ loopActive = 0;
+}
+
+#if MEDIUM_TYPE == 5
KnxFacade knx;
+#elif MEDIUM_TYPE == 2
+KnxFacade knx;
+#else
+#error Only MEDIUM_TYPE IP and RF supported
+#endif
long lastsend = 0;
@@ -85,15 +110,39 @@ void setup()
int main(int argc, char **argv)
{
+ printf("main() start.\n");
+
+ // Prevent swapping of this process
+ struct sched_param sp;
+ memset(&sp, 0, sizeof(sp));
+ sp.sched_priority = sched_get_priority_max(SCHED_FIFO);
+ sched_setscheduler(0, SCHED_FIFO, &sp);
+ mlockall(MCL_CURRENT | MCL_FUTURE);
+
+ // Register signals
+ signal(SIGINT, signalHandler);
+ signal(SIGTERM, signalHandler);
+
knx.platform().cmdLineArgs(argc, argv);
setup();
- while (1)
+ while (loopActive)
{
knx.loop();
if(knx.configured())
appLoop();
- delay(100);
+ delayMicroseconds(100);
}
-}
\ No newline at end of file
+
+ // pinMode() will automatically export GPIO pin in sysfs
+ // Read or writing the GPIO pin for the first time automatically
+ // opens the "value" sysfs file to read or write the GPIO pin value.
+ // The following calls will close the "value" sysfs fiel for the pin
+ // and unexport the GPIO pin.
+ gpio_unexport(SPI_SS_PIN);
+ gpio_unexport(GPIO_GDO2_PIN);
+ gpio_unexport(GPIO_GDO0_PIN);
+
+ printf("main() exit.\n");
+}
diff --git a/src/arduino_platform.cpp b/src/arduino_platform.cpp
index 979745d..58d9986 100644
--- a/src/arduino_platform.cpp
+++ b/src/arduino_platform.cpp
@@ -2,6 +2,7 @@
#include
#include
+#include
Stream* ArduinoPlatform::SerialDebug = &Serial;
@@ -60,6 +61,7 @@ void ArduinoPlatform::closeMultiCast()
bool ArduinoPlatform::sendBytes(uint8_t * buffer, uint16_t len)
{
//not needed
+ return false;
}
int ArduinoPlatform::readBytes(uint8_t * buffer, uint16_t maxLen)
@@ -137,6 +139,24 @@ size_t ArduinoPlatform::readBytesUart(uint8_t *buffer, size_t length)
return length;
}
+void ArduinoPlatform::setupSpi()
+{
+ SPI.begin();
+ SPI.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0));
+}
+
+void ArduinoPlatform::closeSpi()
+{
+ SPI.endTransaction();
+ SPI.end();
+}
+
+int ArduinoPlatform::readWriteSpi(uint8_t *data, size_t len)
+{
+ SPI.transfer(data, len);
+ return 0;
+}
+
void print(const char* s)
{
ArduinoPlatform::SerialDebug->print(s);
diff --git a/src/arduino_platform.h b/src/arduino_platform.h
index 60acba8..d7b6f69 100644
--- a/src/arduino_platform.h
+++ b/src/arduino_platform.h
@@ -35,6 +35,11 @@ class ArduinoPlatform : public Platform
virtual int readUart();
virtual size_t readBytesUart(uint8_t* buffer, size_t length);
+ //spi
+ void setupSpi() override;
+ void closeSpi() override;
+ int readWriteSpi (uint8_t *data, size_t len) override;
+
static Stream* SerialDebug;
protected:
diff --git a/src/knx/apdu.cpp b/src/knx/apdu.cpp
index 8809d10..9867215 100644
--- a/src/knx/apdu.cpp
+++ b/src/knx/apdu.cpp
@@ -12,7 +12,7 @@ ApduType APDU::type()
apci = getWord(_data);
popWord(apci, _data);
apci &= 0x3ff;
- if ((apci >> 6) < 11) //short apci
+ if ((apci >> 6) < 11 && (apci >> 6) != 7) //short apci
apci &= 0x3c0;
return (ApduType)apci;
}
diff --git a/src/knx/application_layer.cpp b/src/knx/application_layer.cpp
index 321942a..669bb44 100644
--- a/src/knx/application_layer.cpp
+++ b/src/knx/application_layer.cpp
@@ -90,8 +90,11 @@ void ApplicationLayer::dataBroadcastIndication(HopCountType hopType, Priority pr
_bau.individualAddressReadAppLayerConfirm(hopType, apdu.frame().sourceAddress());
break;
case IndividualAddressSerialNumberRead:
- _bau.individualAddressSerialNumberReadIndication(hopType, data + 1);
+ {
+ uint8_t* knxSerialNumber = &data[1];
+ _bau.individualAddressSerialNumberReadIndication(priority, hopType, knxSerialNumber);
break;
+ }
case IndividualAddressSerialNumberResponse:
{
uint16_t domainAddress;
@@ -102,9 +105,10 @@ void ApplicationLayer::dataBroadcastIndication(HopCountType hopType, Priority pr
}
case IndividualAddressSerialNumberWrite:
{
- uint16_t newAddress;
- popWord(newAddress, data + 7);
- _bau.individualAddressSerialNumberWriteIndication(hopType, data + 1, newAddress);
+ uint8_t* knxSerialNumber = &data[1];
+ uint16_t newIndividualAddress;
+ popWord(newIndividualAddress, &data[7]);
+ _bau.individualAddressSerialNumberWriteIndication(priority, hopType, newIndividualAddress, knxSerialNumber);
break;
}
}
@@ -150,7 +154,40 @@ void ApplicationLayer::dataBroadcastConfirm(AckType ack, HopCountType hopType, P
void ApplicationLayer::dataSystemBroadcastIndication(HopCountType hopType, Priority priority, uint16_t source, APDU& apdu)
{
-
+ uint8_t* data = apdu.data();
+ switch (apdu.type())
+ {
+ // TODO: testInfo could be of any length
+ case SystemNetworkParameterRead:
+ {
+ uint16_t objectType;
+ uint16_t propertyId;
+ uint8_t testInfo[2];
+ popWord(objectType, data + 1);
+ popWord(propertyId, data + 3);
+ popByte(testInfo[0], data + 4);
+ popByte(testInfo[1], data + 5);
+ propertyId = (propertyId >> 4) & 0x0FFF;;
+ testInfo[0] &= 0x0F;
+ _bau.systemNetworkParameterReadIndication(priority, hopType, objectType, propertyId, testInfo, sizeof(testInfo));
+ break;
+ }
+ case DomainAddressSerialNumberWrite:
+ {
+ uint8_t* knxSerialNumber = &data[1];
+ uint8_t* domainAddress = &data[7];
+ _bau.domainAddressSerialNumberWriteIndication(priority, hopType, domainAddress, knxSerialNumber);
+ break;
+ }
+ case DomainAddressSerialNumberRead:
+ {
+ uint8_t* knxSerialNumber = &data[1];
+ _bau.domainAddressSerialNumberReadIndication(priority, hopType, knxSerialNumber);
+ break;
+ }
+ default:
+ break;
+ }
}
void ApplicationLayer::dataSystemBroadcastConfirm(HopCountType hopType, Priority priority, APDU& apdu, bool status)
@@ -176,7 +213,10 @@ void ApplicationLayer::connectIndication(uint16_t tsap)
void ApplicationLayer::connectConfirm(uint16_t destination, uint16_t tsap, bool status)
{
if (status)
+ {
_connectedTsap = tsap;
+ _bau.connectConfirm(tsap);
+ }
else
_connectedTsap = -1;
}
@@ -188,7 +228,7 @@ void ApplicationLayer::disconnectIndication(uint16_t tsap)
void ApplicationLayer::disconnectConfirm(Priority priority, uint16_t tsap, bool status)
{
-
+ _connectedTsap = -1;
}
void ApplicationLayer::dataConnectedIndication(Priority priority, uint16_t tsap, APDU& apdu)
@@ -334,13 +374,82 @@ void ApplicationLayer::deviceDescriptorReadResponse(AckType ack, Priority priori
individualSend(ack, hopType, priority, asap, apdu);
}
-void ApplicationLayer::restartRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap)
+void ApplicationLayer::connectRequest(uint16_t destination, Priority priority)
+{
+ _transportLayer->connectRequest(destination, priority);
+}
+
+void ApplicationLayer::disconnectRequest(Priority priority)
+{
+ _transportLayer->disconnectRequest(_connectedTsap, priority);
+}
+
+void ApplicationLayer::restartRequest(AckType ack, Priority priority, HopCountType hopType)
{
CemiFrame frame(1);
APDU& apdu = frame.apdu();
apdu.type(Restart);
- individualSend(ack, hopType, priority, asap, apdu);
+ individualSend(ack, hopType, priority, _connectedTsap, apdu);
+}
+
+//TODO: ApplicationLayer::systemNetworkParameterReadRequest()
+void ApplicationLayer::systemNetworkParameterReadResponse(Priority priority, HopCountType hopType,
+ uint16_t objectType, uint16_t propertyId,
+ uint8_t* testInfo, uint16_t testInfoLength,
+ uint8_t* testResult, uint16_t testResultLength)
+{
+ CemiFrame frame(testInfoLength + testResultLength + 3 + 1); // PID and testInfo share an octet (+3) and +1 for APCI byte(?)
+ APDU& apdu = frame.apdu();
+ apdu.type(SystemNetworkParameterResponse);
+ uint8_t* data = apdu.data() + 1;
+
+ pushWord(objectType, data);
+ pushWord((propertyId << 4) & 0xFFF0, data + 2); // Reserved bits for test_info are always 0
+ uint8_t* pData = pushByteArray(&testInfo[1], testInfoLength - 1, data + 4); // TODO: upper reserved bits (testInfo + 0) have to put into the lower bits of data + 3
+ memcpy(pData, testResult, testResultLength);
+
+ //apdu.printPDU();
+
+ _transportLayer->dataSystemBroadcastRequest(AckDontCare, hopType, SystemPriority, apdu);
+}
+
+//TODO: ApplicationLayer::domainAddressSerialNumberWriteRequest()
+//TODO: ApplicationLayer::domainAddressSerialNumberReadRequest()
+void ApplicationLayer::domainAddressSerialNumberReadResponse(Priority priority, HopCountType hopType, uint8_t* rfDoA,
+ uint8_t* knxSerialNumber)
+{
+ CemiFrame frame(13);
+ APDU& apdu = frame.apdu();
+ apdu.type(DomainAddressSerialNumberResponse);
+
+ uint8_t* data = apdu.data() + 1;
+
+ memcpy(data, knxSerialNumber, 6);
+ memcpy(data + 6, rfDoA, 6);
+
+ //apdu.printPDU();
+
+ _transportLayer->dataSystemBroadcastRequest(AckDontCare, hopType, SystemPriority, apdu);
+}
+
+//TODO: ApplicationLayer::IndividualAddressSerialNumberWriteRequest()
+//TODO: ApplicationLayer::IndividualAddressSerialNumberReadRequest()
+void ApplicationLayer::IndividualAddressSerialNumberReadResponse(Priority priority, HopCountType hopType, uint8_t* rfDoA,
+ uint8_t* knxSerialNumber)
+{
+ CemiFrame frame(13);
+ APDU& apdu = frame.apdu();
+ apdu.type(IndividualAddressSerialNumberResponse);
+
+ uint8_t* data = apdu.data() + 1;
+
+ memcpy(data, knxSerialNumber, 6);
+ memcpy(data + 6, rfDoA, 6);
+
+ //apdu.printPDU();
+
+ _transportLayer->dataBroadcastRequest(AckDontCare, hopType, SystemPriority, apdu);
}
void ApplicationLayer::propertyValueReadRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap,
@@ -788,3 +897,8 @@ void ApplicationLayer::individualSend(AckType ack, HopCountType hopType, Priorit
else
_transportLayer->dataIndividualRequest(ack, hopType, priority, asap, apdu);
}
+
+bool ApplicationLayer::isConnected()
+{
+ return (_connectedTsap >= 0);
+}
\ No newline at end of file
diff --git a/src/knx/application_layer.h b/src/knx/application_layer.h
index 7af7262..9d7223f 100644
--- a/src/knx/application_layer.h
+++ b/src/knx/application_layer.h
@@ -95,7 +95,10 @@ class ApplicationLayer
uint8_t descriptorType);
void deviceDescriptorReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap,
uint8_t descriptorType, uint8_t* deviceDescriptor);
- void restartRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap);
+ void connectRequest(uint16_t destination, Priority priority);
+ void disconnectRequest(Priority priority);
+ bool isConnected();
+ void restartRequest(AckType ack, Priority priority, HopCountType hopType);
void propertyValueReadRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap,
uint8_t objectIndex, uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex);
void propertyValueReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, uint8_t objectIndex,
@@ -126,6 +129,14 @@ class ApplicationLayer
void authorizeResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, uint8_t level);
void keyWriteRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, uint8_t level, uint32_t key);
void keyWriteResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, uint8_t level);
+
+ void systemNetworkParameterReadResponse(Priority priority, HopCountType hopType, uint16_t objectType,
+ uint16_t propertyId, uint8_t* testInfo, uint16_t testInfoLength,
+ uint8_t* testResult, uint16_t testResultLength);
+ void domainAddressSerialNumberReadResponse(Priority priority, HopCountType hopType, uint8_t* rfDoA,
+ uint8_t* knxSerialNumber);
+ void IndividualAddressSerialNumberReadResponse(Priority priority, HopCountType hopType, uint8_t* rfDoA,
+ uint8_t* knxSerialNumber);
#pragma endregion
private:
@@ -147,5 +158,5 @@ class ApplicationLayer
AssociationTableObject& _assocTable;
BusAccessUnit& _bau;
TransportLayer* _transportLayer = 0;
- int32_t _connectedTsap;
+ int32_t _connectedTsap = -1;
};
diff --git a/src/knx/bau.cpp b/src/knx/bau.cpp
index 105b380..b60d60c 100644
--- a/src/knx/bau.cpp
+++ b/src/knx/bau.cpp
@@ -52,7 +52,7 @@ void BusAccessUnit::individualAddressSerialNumberReadLocalConfirm(AckType ack, H
{
}
-void BusAccessUnit::individualAddressSerialNumberReadIndication(HopCountType hopType, uint8_t * serialNumber)
+void BusAccessUnit::individualAddressSerialNumberReadIndication(Priority priority, HopCountType hopType, uint8_t* knxSerialNumber)
{
}
@@ -68,7 +68,8 @@ void BusAccessUnit::individualAddressSerialNumberWriteLocalConfirm(AckType ack,
{
}
-void BusAccessUnit::individualAddressSerialNumberWriteIndication(HopCountType hopType, uint8_t * serialNumber, uint16_t newaddress)
+void BusAccessUnit::individualAddressSerialNumberWriteIndication(Priority priority, HopCountType hopType, uint16_t newIndividualAddress,
+ uint8_t* knxSerialNumber)
{
}
@@ -236,3 +237,25 @@ void BusAccessUnit::keyWriteResponseConfirm(AckType ack, Priority priority, HopC
void BusAccessUnit::keyWriteAppLayerConfirm(Priority priority, HopCountType hopType, uint16_t asap, uint8_t level)
{
}
+
+void BusAccessUnit::connectConfirm(uint16_t destination)
+{
+}
+
+void BusAccessUnit::systemNetworkParameterReadIndication(Priority priority, HopCountType hopType, uint16_t objectType,
+ uint16_t propertyId, uint8_t* testInfo, uint16_t testInfoLength)
+{
+}
+
+void BusAccessUnit::domainAddressSerialNumberWriteIndication(Priority priority, HopCountType hopType, uint8_t* rfDoA,
+ uint8_t* knxSerialNumber)
+{
+}
+
+void BusAccessUnit::domainAddressSerialNumberReadIndication(Priority priority, HopCountType hopType, uint8_t* knxSerialNumber)
+{
+}
+
+
+
+
diff --git a/src/knx/bau.h b/src/knx/bau.h
index 14ca5b5..d2d6ada 100644
--- a/src/knx/bau.h
+++ b/src/knx/bau.h
@@ -25,14 +25,15 @@ class BusAccessUnit
virtual void individualAddressReadAppLayerConfirm(HopCountType hopType, uint16_t individualAddress);
virtual void individualAddressSerialNumberReadLocalConfirm(AckType ack, HopCountType hopType,
uint8_t* serialNumber, bool status);
- virtual void individualAddressSerialNumberReadIndication(HopCountType hopType, uint8_t* serialNumber);
+ virtual void individualAddressSerialNumberReadIndication(Priority priority, HopCountType hopType, uint8_t* knxSerialNumber);
virtual void individualAddressSerialNumberReadResponseConfirm(AckType ack, HopCountType hopType,
uint8_t* serialNumber, uint16_t domainAddress, bool status);
virtual void individualAddressSerialNumberReadAppLayerConfirm(HopCountType hopType, uint8_t* serialNumber,
uint16_t individualAddress, uint16_t domainAddress);
virtual void individualAddressSerialNumberWriteLocalConfirm(AckType ack, HopCountType hopType, uint8_t* serialNumber,
uint16_t newaddress, bool status);
- virtual void individualAddressSerialNumberWriteIndication(HopCountType hopType, uint8_t* serialNumber, uint16_t newaddress);
+ virtual void individualAddressSerialNumberWriteIndication(Priority priority, HopCountType hopType, uint16_t newIndividualAddress,
+ uint8_t* knxSerialNumber);
virtual void deviceDescriptorReadLocalConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap,
uint8_t descriptorType, bool status);
virtual void deviceDescriptorReadIndication(Priority priority, HopCountType hopType, uint16_t asap, uint8_t descriptorType);
@@ -108,4 +109,12 @@ class BusAccessUnit
virtual void keyWriteResponseConfirm(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, uint8_t level,
bool status);
virtual void keyWriteAppLayerConfirm(Priority priority, HopCountType hopType, uint16_t asap, uint8_t level);
+ virtual void connectConfirm(uint16_t destination);
+ virtual void systemNetworkParameterReadIndication(Priority priority, HopCountType hopType, uint16_t objectType,
+ uint16_t propertyId, uint8_t* testInfo, uint16_t testInfoLength);
+
+ virtual void domainAddressSerialNumberWriteIndication(Priority priority, HopCountType hopType, uint8_t* rfDoA,
+ uint8_t* knxSerialNumber);
+
+ virtual void domainAddressSerialNumberReadIndication(Priority priority, HopCountType hopType, uint8_t* knxSerialNumber);
};
diff --git a/src/knx/bau07B0.cpp b/src/knx/bau07B0.cpp
index 435ba5e..6d5b464 100644
--- a/src/knx/bau07B0.cpp
+++ b/src/knx/bau07B0.cpp
@@ -1,4 +1,5 @@
#include "bau07B0.h"
+#include "bits.h"
#include
#include
@@ -9,6 +10,16 @@ Bau07B0::Bau07B0(Platform& platform)
_dlLayer(_deviceObj, _addrTable, _netLayer, _platform)
{
_netLayer.dataLinkLayer(_dlLayer);
+
+ // Set Mask Version in Device Object depending on the BAU
+ uint16_t maskVersion;
+ popWord(maskVersion, _descriptor);
+ _deviceObj.maskVersion(maskVersion);
+
+ // Set which interface objects are available in the device object
+ // This differs from BAU to BAU with different medium types.
+ // See PID_IO_LIST
+ _deviceObj.ifObj(_ifObjs);
}
InterfaceObject* Bau07B0::getInterfaceObject(uint8_t idx)
diff --git a/src/knx/bau07B0.h b/src/knx/bau07B0.h
index 84d8aba..5fe3f67 100644
--- a/src/knx/bau07B0.h
+++ b/src/knx/bau07B0.h
@@ -16,4 +16,6 @@ class Bau07B0 : public BauSystemB
private:
TpUartDataLinkLayer _dlLayer;
uint8_t _descriptor[2] = {0x07, 0xb0};
+ const uint32_t _ifObjs[6] = { 5, // length
+ OT_DEVICE, OT_ADDR_TABLE, OT_ASSOC_TABLE, OT_GRP_OBJ_TABLE, OT_APPLICATION_PROG};
};
\ No newline at end of file
diff --git a/src/knx/bau27B0.cpp b/src/knx/bau27B0.cpp
new file mode 100644
index 0000000..3e1b548
--- /dev/null
+++ b/src/knx/bau27B0.cpp
@@ -0,0 +1,119 @@
+#if MEDIUM_TYPE == 2
+
+#include "bau27B0.h"
+#include "bits.h"
+#include
+#include
+
+using namespace std;
+
+Bau27B0::Bau27B0(Platform& platform)
+ : BauSystemB(platform),
+ _dlLayer(_deviceObj, _rfMediumObj, _addrTable, _netLayer, _platform)
+{
+ _netLayer.dataLinkLayer(_dlLayer);
+ _memory.addSaveRestore(&_rfMediumObj);
+
+ // Set Mask Version in Device Object depending on the BAU
+ uint16_t maskVersion;
+ popWord(maskVersion, _descriptor);
+ _deviceObj.maskVersion(maskVersion);
+
+ // Set the maximum APDU length
+ // ETS will consider this value while programming the device
+ // For KNX-RF we use a smallest allowed value for now,
+ // although long frame are also supported by the implementation.
+ // Needs some experimentation.
+ _deviceObj.maxApduLength(15);
+
+ // Set which interface objects are available in the device object
+ // This differs from BAU to BAU with different medium types.
+ // See PID_IO_LIST
+ _deviceObj.ifObj(_ifObjs);
+}
+
+// see KNX AN160 p.74 for mask 27B0
+InterfaceObject* Bau27B0::getInterfaceObject(uint8_t idx)
+{
+ switch (idx)
+ {
+ case 0:
+ return &_deviceObj;
+ case 1:
+ return &_addrTable;
+ case 2:
+ return &_assocTable;
+ case 3:
+ return &_groupObjTable;
+ case 4:
+ return &_appProgram;
+ case 5: // would be app_program 2
+ return nullptr;
+ case 6:
+ return &_rfMediumObj;
+ default:
+ return nullptr;
+ }
+}
+
+uint8_t* Bau27B0::descriptor()
+{
+ return _descriptor;
+}
+
+DataLinkLayer& Bau27B0::dataLinkLayer()
+{
+ return _dlLayer;
+}
+
+void Bau27B0::domainAddressSerialNumberWriteIndication(Priority priority, HopCountType hopType, uint8_t* rfDoA,
+ uint8_t* knxSerialNumber)
+{
+ uint8_t curSerialNumber[6];
+ pushWord(_deviceObj.manufacturerId(), &curSerialNumber[0]);
+ pushInt(_deviceObj.bauNumber(), &curSerialNumber[2]);
+
+ // If the received serial number matches our serial number
+ // then store the received RF domain address in the RF medium object
+ if (!memcmp(knxSerialNumber, curSerialNumber, 6))
+ _rfMediumObj.rfDomainAddress(rfDoA);
+}
+
+void Bau27B0::domainAddressSerialNumberReadIndication(Priority priority, HopCountType hopType, uint8_t* knxSerialNumber)
+{
+ uint8_t curSerialNumber[6];
+ pushWord(_deviceObj.manufacturerId(), &curSerialNumber[0]);
+ pushInt(_deviceObj.bauNumber(), &curSerialNumber[2]);
+
+ // If the received serial number matches our serial number
+ // then send a response with the current RF domain address stored in the RF medium object
+ if (!memcmp(knxSerialNumber, curSerialNumber, 6))
+ _appLayer.domainAddressSerialNumberReadResponse(priority, hopType, _rfMediumObj.rfDomainAddress(), knxSerialNumber);
+}
+
+void Bau27B0::individualAddressSerialNumberWriteIndication(Priority priority, HopCountType hopType, uint16_t newIndividualAddress,
+ uint8_t* knxSerialNumber)
+{
+ uint8_t curSerialNumber[6];
+ pushWord(_deviceObj.manufacturerId(), &curSerialNumber[0]);
+ pushInt(_deviceObj.bauNumber(), &curSerialNumber[2]);
+
+ // If the received serial number matches our serial number
+ // then store the received new individual address in the device object
+ if (!memcmp(knxSerialNumber, curSerialNumber, 6))
+ _deviceObj.induvidualAddress(newIndividualAddress);
+}
+
+void Bau27B0::individualAddressSerialNumberReadIndication(Priority priority, HopCountType hopType, uint8_t* knxSerialNumber)
+{
+ uint8_t curSerialNumber[6];
+ pushWord(_deviceObj.manufacturerId(), &curSerialNumber[0]);
+ pushInt(_deviceObj.bauNumber(), &curSerialNumber[2]);
+
+ // If the received serial number matches our serial number
+ // then send a response with the current RF domain address stored in the RF medium object and the serial number
+ if (!memcmp(knxSerialNumber, curSerialNumber, 6))
+ _appLayer.IndividualAddressSerialNumberReadResponse(priority, hopType, _rfMediumObj.rfDomainAddress(), knxSerialNumber);
+}
+
+#endif // #if MEDIUM_TYPE == 2
diff --git a/src/knx/bau27B0.h b/src/knx/bau27B0.h
new file mode 100644
index 0000000..b2c129e
--- /dev/null
+++ b/src/knx/bau27B0.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include "bau_systemB.h"
+#include "rf_medium_object.h"
+#include "rf_physical_layer.h"
+#include "rf_data_link_layer.h"
+
+class Bau27B0 : public BauSystemB
+{
+ public:
+ Bau27B0(Platform& platform);
+
+ protected:
+ InterfaceObject* getInterfaceObject(uint8_t idx);
+ uint8_t* descriptor();
+ DataLinkLayer& dataLinkLayer();
+
+ private:
+ RfDataLinkLayer _dlLayer;
+ RfMediumObject _rfMediumObj;
+
+ uint8_t _descriptor[2] = {0x27, 0xB0};
+ const uint32_t _ifObjs[7] = { 6, // length
+ OT_DEVICE, OT_ADDR_TABLE, OT_ASSOC_TABLE, OT_GRP_OBJ_TABLE, OT_APPLICATION_PROG, OT_RF_MEDIUM};
+
+ void domainAddressSerialNumberWriteIndication(Priority priority, HopCountType hopType, uint8_t* rfDoA,
+ uint8_t* knxSerialNumber);
+ void domainAddressSerialNumberReadIndication(Priority priority, HopCountType hopType, uint8_t* knxSerialNumber);
+ void individualAddressSerialNumberWriteIndication(Priority priority, HopCountType hopType, uint16_t newIndividualAddress,
+ uint8_t* knxSerialNumber);
+ void individualAddressSerialNumberReadIndication(Priority priority, HopCountType hopType, uint8_t* knxSerialNumber);
+};
\ No newline at end of file
diff --git a/src/knx/bau57B0.cpp b/src/knx/bau57B0.cpp
index c255934..648d777 100644
--- a/src/knx/bau57B0.cpp
+++ b/src/knx/bau57B0.cpp
@@ -1,4 +1,5 @@
#include "bau57B0.h"
+#include "bits.h"
#include
#include
@@ -11,6 +12,16 @@ Bau57B0::Bau57B0(Platform& platform)
{
_netLayer.dataLinkLayer(_dlLayer);
_memory.addSaveRestore(&_ipParameters);
+
+ // Set Mask Version in Device Object depending on the BAU
+ uint16_t maskVersion;
+ popWord(maskVersion, _descriptor);
+ _deviceObj.maskVersion(maskVersion);
+
+ // Set which interface objects are available in the device object
+ // This differs from BAU to BAU with different medium types.
+ // See PID_IO_LIST
+ _deviceObj.ifObj(_ifObjs);
}
InterfaceObject* Bau57B0::getInterfaceObject(uint8_t idx)
diff --git a/src/knx/bau57B0.h b/src/knx/bau57B0.h
index 297f4aa..8835150 100644
--- a/src/knx/bau57B0.h
+++ b/src/knx/bau57B0.h
@@ -18,4 +18,6 @@ class Bau57B0 : public BauSystemB
IpParameterObject _ipParameters;
IpDataLinkLayer _dlLayer;
uint8_t _descriptor[2] = {0x57, 0xb0};
+ const uint32_t _ifObjs[7] = { 6, // length
+ OT_DEVICE, OT_ADDR_TABLE, OT_ASSOC_TABLE, OT_GRP_OBJ_TABLE, OT_APPLICATION_PROG, OT_IP_PARAMETER};
};
\ No newline at end of file
diff --git a/src/knx/bau_systemB.cpp b/src/knx/bau_systemB.cpp
index 4e66daa..9840c0d 100644
--- a/src/knx/bau_systemB.cpp
+++ b/src/knx/bau_systemB.cpp
@@ -1,7 +1,16 @@
#include "bau_systemB.h"
+#include "bits.h"
#include
#include
+enum NmReadSerialNumberType
+{
+ NM_Read_SerialNumber_By_ProgrammingMode = 0x01,
+ NM_Read_SerialNumber_By_ExFactoryState = 0x02,
+ NM_Read_SerialNumber_By_PowerReset = 0x03,
+ NM_Read_SerialNumber_By_ManufacturerSpecific = 0xFE,
+};
+
BauSystemB::BauSystemB(Platform& platform): _memory(platform), _addrTable(platform),
_assocTable(platform), _groupObjTable(platform), _appProgram(platform),
_platform(platform), _appLayer(_assocTable, *this),
@@ -21,6 +30,7 @@ void BauSystemB::loop()
dataLinkLayer().loop();
_transLayer.loop();
sendNextGroupTelegram();
+ nextRestartState();
}
bool BauSystemB::enabled()
@@ -293,8 +303,91 @@ void BauSystemB::addSaveRestore(SaveRestore* obj)
_memory.addSaveRestore(obj);
}
-
-void BauSystemB::restartRequest(uint16_t asap)
+bool BauSystemB::restartRequest(uint16_t asap)
{
- _appLayer.restartRequest(AckRequested, LowPriority, NetworkLayerParameter, asap);
+ if (_appLayer.isConnected())
+ return false;
+ _restartState = Connecting; // order important, has to be set BEFORE connectRequest
+ _appLayer.connectRequest(asap, SystemPriority);
+ _appLayer.deviceDescriptorReadRequest(AckRequested, SystemPriority, NetworkLayerParameter, asap, 0);
+ return true;
+}
+
+void BauSystemB::connectConfirm(uint16_t tsap)
+{
+ if (_restartState == Connecting && tsap >= 0)
+ {
+ /* restart connection is confirmed, go to the next state */
+ _restartState = Connected;
+ _restartDelay = millis();
+ }
+ else
+ {
+ _restartState = Idle;
+ }
+}
+
+void BauSystemB::nextRestartState()
+{
+ switch (_restartState)
+ {
+ case Idle:
+ /* inactive state, do nothing */
+ break;
+ case Connecting:
+ /* wait for connection, we do nothing here */
+ break;
+ case Connected:
+ /* connection confirmed, we send restartRequest, but we wait a moment (sending ACK etc)... */
+ if (millis() - _restartDelay > 30)
+ {
+ _appLayer.restartRequest(AckRequested, SystemPriority, NetworkLayerParameter);
+ _restartState = Restarted;
+ _restartDelay = millis();
+ }
+ break;
+ case Restarted:
+ /* restart is finished, we send a discommect */
+ if (millis() - _restartDelay > 30)
+ {
+ _appLayer.disconnectRequest(SystemPriority);
+ _restartState = Idle;
+ }
+ default:
+ break;
+ }
+}
+
+void BauSystemB::systemNetworkParameterReadIndication(Priority priority, HopCountType hopType, uint16_t objectType,
+ uint16_t propertyId, uint8_t* testInfo, uint16_t testInfoLength)
+{
+ uint8_t knxSerialNumber[6];
+ uint8_t operand;
+
+ popByte(operand, testInfo + 1); // First byte (+ 0) contains only 4 reserved bits (0)
+
+ // See KNX spec. 3.5.2 p.33 (Management Procedures: Procedures with A_SystemNetworkParameter_Read)
+ switch((NmReadSerialNumberType)operand)
+ {
+ case NM_Read_SerialNumber_By_ProgrammingMode: // NM_Read_SerialNumber_By_ProgrammingMode
+ // Only send a reply if programming mode is on
+ if (_deviceObj.progMode() && (objectType == OT_DEVICE) && (propertyId == PID_SERIAL_NUMBER))
+ {
+ // Send reply. testResult data is KNX serial number
+ pushWord(_deviceObj.manufacturerId(), &knxSerialNumber[0]);
+ pushInt(_deviceObj.bauNumber(), &knxSerialNumber[2]);
+ _appLayer.systemNetworkParameterReadResponse(priority, hopType, objectType, propertyId,
+ testInfo, testInfoLength, knxSerialNumber, sizeof(knxSerialNumber));
+ }
+ break;
+
+ case NM_Read_SerialNumber_By_ExFactoryState: // NM_Read_SerialNumber_By_ExFactoryState
+ break;
+
+ case NM_Read_SerialNumber_By_PowerReset: // NM_Read_SerialNumber_By_PowerReset
+ break;
+
+ case NM_Read_SerialNumber_By_ManufacturerSpecific: // Manufacturer specific use of A_SystemNetworkParameter_Read
+ break;
+ }
}
diff --git a/src/knx/bau_systemB.h b/src/knx/bau_systemB.h
index 91a0741..5ca0ff5 100644
--- a/src/knx/bau_systemB.h
+++ b/src/knx/bau_systemB.h
@@ -27,7 +27,7 @@ class BauSystemB : protected BusAccessUnit
void readMemory();
void writeMemory();
void addSaveRestore(SaveRestore* obj);
- void restartRequest(uint16_t asap);
+ bool restartRequest(uint16_t asap);
protected:
virtual DataLinkLayer& dataLinkLayer() = 0;
@@ -58,10 +58,22 @@ class BauSystemB : protected BusAccessUnit
uint8_t* data, uint8_t dataLength) override;
void groupValueWriteIndication(uint16_t asap, Priority priority, HopCountType hopType,
uint8_t* data, uint8_t dataLength) override;
+ void systemNetworkParameterReadIndication(Priority priority, HopCountType hopType, uint16_t objectType,
+ uint16_t propertyId, uint8_t* testInfo, uint16_t testinfoLength);
+ void connectConfirm(uint16_t tsap) override;
virtual InterfaceObject* getInterfaceObject(uint8_t idx) = 0;
void sendNextGroupTelegram();
void updateGroupObject(GroupObject& go, uint8_t* data, uint8_t length);
+ void nextRestartState();
+
+ enum RestartState
+ {
+ Idle,
+ Connecting,
+ Connected,
+ Restarted
+ };
DeviceObject _deviceObj;
Memory _memory;
@@ -74,4 +86,6 @@ class BauSystemB : protected BusAccessUnit
TransportLayer _transLayer;
NetworkLayer _netLayer;
bool _configured = true;
+ RestartState _restartState = Idle;
+ uint32_t _restartDelay = 0;
};
\ No newline at end of file
diff --git a/src/knx/bits.h b/src/knx/bits.h
index de25682..3e6a95e 100644
--- a/src/knx/bits.h
+++ b/src/knx/bits.h
@@ -26,9 +26,11 @@
#define RISING 4
void delay(uint32_t millis);
+void delayMicroseconds (unsigned int howLong);
uint32_t millis();
void pinMode(uint32_t dwPin, uint32_t dwMode);
void digitalWrite(uint32_t dwPin, uint32_t dwVal);
+uint32_t digitalRead(uint32_t dwPin);
typedef void (*voidFuncPtr)(void);
void attachInterrupt(uint32_t pin, voidFuncPtr callback, uint32_t mode);
diff --git a/src/knx/cemi_frame.cpp b/src/knx/cemi_frame.cpp
index 005cd2e..d7be5d4 100644
--- a/src/knx/cemi_frame.cpp
+++ b/src/knx/cemi_frame.cpp
@@ -3,18 +3,88 @@
#include "string.h"
#include
-CemiFrame::CemiFrame(uint8_t* data, uint16_t length): _npdu(data + NPDU_LPDU_DIFF, *this),
- _tpdu(data + TPDU_LPDU_DIFF, *this), _apdu(data + APDU_LPDU_DIFF, *this)
+/*
+cEMI Frame Format
+
++---------+--------+--------+--------+--------+---------+---------+--------+---------+
+ | Header | Msg |Add.Info| Ctrl 1 | Ctrl 2 | Source | Dest. | Data | APDU |
+ | | Code | Length | | | Address | Address | Length | |
+ +---------+--------+--------+--------+--------+---------+---------+--------+---------+
+ 6 bytes 1 byte 1 byte 1 byte 1 byte 2 bytes 2 bytes 1 byte 2 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
+ Add.Info Length = 0x00 - no additional info
+ Control Field 1 =
+ Control Field 2 =
+ Source Address = 0x0000 - filled in by router/gateway with its source address which is
+ part of the KNX subnet
+ Dest. Address = KNX group or individual address (2 byte)
+ Data Length = Number of bytes of data in the APDU excluding the TPCI/APCI bits
+ APDU = Application Protocol Data Unit - the actual payload including transport
+ protocol control information (TPCI), application protocol control
+ information (APCI) and data passed as an argument from higher layers of
+ the KNX communication stack
+
+Control Field 1
+
+ Bit |
+ ------+---------------------------------------------------------------
+ 7 | Frame Type - 0x0 for extended frame
+ | 0x1 for standard frame
+ ------+---------------------------------------------------------------
+ 6 | Reserved
+ |
+ ------+---------------------------------------------------------------
+ 5 | Repeat Flag - 0x0 repeat frame on medium in case of an error
+ | 0x1 do not repeat
+ ------+---------------------------------------------------------------
+ 4 | System Broadcast - 0x0 system broadcast
+ | 0x1 broadcast
+ ------+---------------------------------------------------------------
+ 3 | Priority - 0x0 system
+ | 0x1 normal
+ ------+ 0x2 urgent
+ 2 | 0x3 low
+ |
+ ------+---------------------------------------------------------------
+ 1 | Acknowledge Request - 0x0 no ACK requested
+ | (L_Data.req) 0x1 ACK requested
+ ------+---------------------------------------------------------------
+ 0 | Confirm - 0x0 no error
+ | (L_Data.con) - 0x1 error
+ ------+---------------------------------------------------------------
+
+ Control Field 2
+
+ Bit |
+ ------+---------------------------------------------------------------
+ 7 | Destination Address Type - 0x0 individual address
+ | - 0x1 group address
+ ------+---------------------------------------------------------------
+ 6-4 | Hop Count (0-7)
+ ------+---------------------------------------------------------------
+ 3-0 | Extended Frame Format - 0x0 standard frame
+ ------+---------------------------------------------------------------
+*/
+
+CemiFrame::CemiFrame(uint8_t* data, uint16_t length)
+ : _npdu(data + NPDU_LPDU_DIFF, *this),
+ _tpdu(data + TPDU_LPDU_DIFF, *this),
+ _apdu(data + APDU_LPDU_DIFF, *this)
{
_data = data;
- _ctrl1 = data + data[1] + 2;
+ _ctrl1 = data + data[1] + CEMI_HEADER_SIZE;
_length = length;
}
-CemiFrame::CemiFrame(uint8_t apduLength): _data(buffer),
- _npdu(_data + NPDU_LPDU_DIFF, *this), _tpdu(_data + TPDU_LPDU_DIFF, *this), _apdu(_data + APDU_LPDU_DIFF, *this)
+CemiFrame::CemiFrame(uint8_t apduLength)
+ : _data(buffer),
+ _npdu(_data + NPDU_LPDU_DIFF, *this),
+ _tpdu(_data + TPDU_LPDU_DIFF, *this),
+ _apdu(_data + APDU_LPDU_DIFF, *this)
{
- _ctrl1 = _data + 2;
+ _ctrl1 = _data + CEMI_HEADER_SIZE;
_length = 0;
memset(_data, 0, apduLength + APDU_LPDU_DIFF);
@@ -22,10 +92,13 @@ CemiFrame::CemiFrame(uint8_t apduLength): _data(buffer),
_npdu.octetCount(apduLength);
}
-CemiFrame::CemiFrame(const CemiFrame & other): _data(buffer),
- _npdu(_data + NPDU_LPDU_DIFF, *this), _tpdu(_data + TPDU_LPDU_DIFF, *this), _apdu(_data + APDU_LPDU_DIFF, *this)
+CemiFrame::CemiFrame(const CemiFrame & other)
+ : _data(buffer),
+ _npdu(_data + NPDU_LPDU_DIFF, *this),
+ _tpdu(_data + TPDU_LPDU_DIFF, *this),
+ _apdu(_data + APDU_LPDU_DIFF, *this)
{
- _ctrl1 = _data + 2;
+ _ctrl1 = _data + CEMI_HEADER_SIZE;
_length = other._length;
memcpy(_data, other._data, other.totalLenght());
@@ -35,7 +108,7 @@ CemiFrame& CemiFrame::operator=(CemiFrame other)
{
_length = other._length;
_data = buffer;
- _ctrl1 = _data + 2;
+ _ctrl1 = _data + CEMI_HEADER_SIZE;
memcpy(_data, other._data, other.totalLenght());
_npdu._data = _data + NPDU_LPDU_DIFF;
_tpdu._data = _data + TPDU_LPDU_DIFF;
@@ -84,10 +157,34 @@ void CemiFrame::fillTelegramTP(uint8_t* data)
{
memcpy(data, _ctrl1, len - 1);
}
- data[len - 1] = calcCRC(data, len - 1);
+ data[len - 1] = calcCrcTP(data, len - 1);
}
-uint8_t CemiFrame::calcCRC(uint8_t * buffer, uint16_t len)
+uint16_t CemiFrame::telegramLengthtRF() const
+{
+ return totalLenght() - 3;
+}
+
+void CemiFrame::fillTelegramRF(uint8_t* data)
+{
+ uint16_t len = telegramLengthtRF();
+
+ // We prepare the actual KNX telegram for RF here only.
+ // The packaging into blocks with CRC16 (Format based on FT3 Data Link Layer (IEC 870-5))
+ // is done in the RF Data Link Layer code.
+ // RF always uses the Extended Frame Format. However, the length field is missing (right before the APDU)
+ // as there is already a length field at the beginning of the raw RF frame which is also used by the
+ // physical layer to control the HW packet engine of the transceiver.
+
+ data[0] = _ctrl1[1] & 0x0F; // KNX CTRL field for RF (bits 3..0 EFF only), bits 7..4 are set to 0 for asynchronous RF frames
+ memcpy(data + 1, _ctrl1 + 2, 4); // SA, DA
+ data[5] = (_ctrl1[1] & 0xF0) | ((_rfLfn & 0x7) << 1) | ((_ctrl1[0] & 0x10) >> 4); // L/NPCI field: AT, Hopcount, LFN, AET
+ memcpy(data + 6, _ctrl1 + 7, len - 6); // APDU
+
+ //printHex("cEMI_fill: ", &data[0], len);
+}
+
+uint8_t CemiFrame::calcCrcTP(uint8_t * buffer, uint16_t len)
{
uint8_t crc = 0xFF;
@@ -198,6 +295,36 @@ void CemiFrame::destinationAddress(uint16_t value)
pushWord(value, _ctrl1 + 4);
}
+uint8_t* CemiFrame::rfSerialOrDoA() const
+{
+ return _rfSerialOrDoA;
+}
+
+void CemiFrame::rfSerialOrDoA(uint8_t* rfSerialOrDoA)
+{
+ _rfSerialOrDoA = rfSerialOrDoA;
+}
+
+uint8_t CemiFrame::rfInfo() const
+{
+ return _rfInfo;
+}
+
+void CemiFrame::rfInfo(uint8_t rfInfo)
+{
+ _rfInfo = rfInfo;
+}
+
+uint8_t CemiFrame::rfLfn() const
+{
+ return _rfLfn;
+}
+
+void CemiFrame::rfLfn(uint8_t rfLfn)
+{
+ _rfLfn = rfLfn;
+}
+
NPDU& CemiFrame::npdu()
{
return _npdu;
diff --git a/src/knx/cemi_frame.h b/src/knx/cemi_frame.h
index f8f69e6..d363bb3 100644
--- a/src/knx/cemi_frame.h
+++ b/src/knx/cemi_frame.h
@@ -12,6 +12,9 @@
#define TPDU_LPDU_DIFF (TPDU_NPDU_DIFF + NPDU_LPDU_DIFF)
#define APDU_LPDU_DIFF (APDU_TPDU_DIFF + TPDU_NPDU_DIFF + NPDU_LPDU_DIFF)
+// Mesg Code and additional info length
+#define CEMI_HEADER_SIZE 2
+
class CemiFrame
{
friend class DataLinkLayer;
@@ -27,6 +30,8 @@ class CemiFrame
uint16_t totalLenght() const;
uint16_t telegramLengthtTP() const;
void fillTelegramTP(uint8_t* data);
+ uint16_t telegramLengthtRF() const;
+ void fillTelegramRF(uint8_t* data);
FrameFormat frameType() const;
void frameType(FrameFormat value);
@@ -47,11 +52,19 @@ class CemiFrame
uint16_t destinationAddress() const;
void destinationAddress(uint16_t value);
+ // only for RF medium
+ uint8_t* rfSerialOrDoA() const;
+ void rfSerialOrDoA(uint8_t* rfSerialOrDoA);
+ uint8_t rfInfo() const;
+ void rfInfo(uint8_t rfInfo);
+ uint8_t rfLfn() const;
+ void rfLfn(uint8_t rfInfo);
+
NPDU& npdu();
TPDU& tpdu();
APDU& apdu();
- uint8_t calcCRC(uint8_t* buffer, uint16_t len);
+ uint8_t calcCrcTP(uint8_t* buffer, uint16_t len);
bool valid() const;
private:
@@ -62,4 +75,9 @@ class CemiFrame
TPDU _tpdu;
APDU _apdu;
uint16_t _length = 0; // only set if created from byte array
+
+ // nly for RF medium
+ uint8_t* _rfSerialOrDoA = 0;
+ uint8_t _rfInfo = 0;
+ uint8_t _rfLfn = 0; // RF Data Link layer frame number
};
\ No newline at end of file
diff --git a/src/knx/data_link_layer.cpp b/src/knx/data_link_layer.cpp
index 1690eb4..92d34a6 100644
--- a/src/knx/data_link_layer.cpp
+++ b/src/knx/data_link_layer.cpp
@@ -14,15 +14,20 @@ DataLinkLayer::DataLinkLayer(DeviceObject& devObj, AddressTableObject& addrTab,
void DataLinkLayer::dataRequest(AckType ack, AddressType addrType, uint16_t destinationAddr, FrameFormat format, Priority priority, NPDU& npdu)
{
- sendTelegram(npdu, ack, destinationAddr, addrType, format, priority);
+ // Normal data requests and broadcasts will always be transmitted as (domain) broadcast with domain address for open media (e.g. RF medium)
+ // The domain address "simulates" a closed medium (such as TP) on an open medium (such as RF or PL)
+ // See 3.2.5 p.22
+ sendTelegram(npdu, ack, destinationAddr, addrType, format, priority, Broadcast);
}
void DataLinkLayer::systemBroadcastRequest(AckType ack, FrameFormat format, Priority priority, NPDU& npdu)
{
- sendTelegram(npdu, ack, 0, GroupAddress, format, priority);
+ // System Broadcast requests will always be transmitted as broadcast with KNX serial number for open media (e.g. RF medium)
+ // See 3.2.5 p.22
+ sendTelegram(npdu, ack, 0, GroupAddress, format, priority, SysBroadcast);
}
-void DataLinkLayer::dataConReceived(CemiFrame& frame,bool success)
+void DataLinkLayer::dataConReceived(CemiFrame& frame, bool success)
{
AckType ack = frame.ack();
AddressType addrType = frame.addressType();
@@ -31,9 +36,13 @@ void DataLinkLayer::dataConReceived(CemiFrame& frame,bool success)
FrameFormat type = frame.frameType();
Priority priority = frame.priority();
NPDU& npdu = frame.npdu();
+ SystemBroadcast systemBroadcast = frame.systemBroadcast();
if (addrType == GroupAddress && destination == 0)
- _networkLayer.systemBroadcastConfirm(ack, type, priority, source, npdu, success);
+ if (systemBroadcast == SysBroadcast)
+ _networkLayer.systemBroadcastConfirm(ack, type, priority, source, npdu, success);
+ else
+ _networkLayer.broadcastConfirm(ack, type, priority, source, npdu, success);
else
_networkLayer.dataConfirm(ack, addrType, destination, type, priority, source, npdu, success);
@@ -49,12 +58,18 @@ void DataLinkLayer::frameRecieved(CemiFrame& frame)
Priority priority = frame.priority();
NPDU& npdu = frame.npdu();
uint16_t ownAddr = _deviceObject.induvidualAddress();
+ SystemBroadcast systemBroadcast = frame.systemBroadcast();
if (source == ownAddr)
_deviceObject.induvidualAddressDuplication(true);
if (addrType == GroupAddress && destination == 0)
- _networkLayer.systemBroadcastIndication(ack, type, npdu, priority, source);
+ {
+ if (systemBroadcast == SysBroadcast)
+ _networkLayer.systemBroadcastIndication(ack, type, npdu, priority, source);
+ else
+ _networkLayer.broadcastIndication(ack, type, npdu, priority, source);
+ }
else
{
if (addrType == InduvidualAddress && destination != _deviceObject.induvidualAddress())
@@ -73,7 +88,7 @@ void DataLinkLayer::frameRecieved(CemiFrame& frame)
}
}
-bool DataLinkLayer::sendTelegram(NPDU & npdu, AckType ack, uint16_t destinationAddr, AddressType addrType, FrameFormat format, Priority priority)
+bool DataLinkLayer::sendTelegram(NPDU & npdu, AckType ack, uint16_t destinationAddr, AddressType addrType, FrameFormat format, Priority priority, SystemBroadcast systemBroadcast)
{
CemiFrame& frame = npdu.frame();
frame.messageCode(L_data_ind);
@@ -82,6 +97,7 @@ bool DataLinkLayer::sendTelegram(NPDU & npdu, AckType ack, uint16_t destinationA
frame.addressType(addrType);
frame.priority(priority);
frame.repetition(RepititionAllowed);
+ frame.systemBroadcast(systemBroadcast);
if (npdu.octetCount() <= 15)
frame.frameType(StandardFrame);
diff --git a/src/knx/data_link_layer.h b/src/knx/data_link_layer.h
index c1bd908..a08e524 100644
--- a/src/knx/data_link_layer.h
+++ b/src/knx/data_link_layer.h
@@ -23,7 +23,7 @@ class DataLinkLayer
protected:
void frameRecieved(CemiFrame& frame);
void dataConReceived(CemiFrame& frame, bool success);
- bool sendTelegram(NPDU& npdu, AckType ack, uint16_t destinationAddr, AddressType addrType, FrameFormat format, Priority priority);
+ bool sendTelegram(NPDU& npdu, AckType ack, uint16_t destinationAddr, AddressType addrType, FrameFormat format, Priority priority, SystemBroadcast systemBroadcast);
virtual bool sendFrame(CemiFrame& frame) = 0;
uint8_t* frameData(CemiFrame& frame);
DeviceObject& _deviceObject;
diff --git a/src/knx/device_object.cpp b/src/knx/device_object.cpp
index 1784f62..e9d0905 100644
--- a/src/knx/device_object.cpp
+++ b/src/knx/device_object.cpp
@@ -11,7 +11,7 @@ void DeviceObject::readProperty(PropertyID propertyId, uint32_t start, uint32_t&
break;
case PID_SERIAL_NUMBER:
pushWord(_manufacturerId, data);
- pushInt(_bauNumber, data);
+ pushInt(_bauNumber, data + 2);
break;
case PID_MANUFACTURER_ID:
pushWord(_manufacturerId, data);
@@ -35,7 +35,7 @@ void DeviceObject::readProperty(PropertyID propertyId, uint32_t start, uint32_t&
*data = _prgMode;
break;
case PID_MAX_APDU_LENGTH:
- pushWord(254, data);
+ pushWord(_maxApduLength, data);
break;
case PID_SUBNET_ADDR:
*data = ((_ownAddress >> 8) & 0xff);
@@ -45,18 +45,12 @@ void DeviceObject::readProperty(PropertyID propertyId, uint32_t start, uint32_t&
break;
case PID_IO_LIST:
{
- uint32_t ifObjs[] = {
- 6, // length
- OT_DEVICE, OT_ADDR_TABLE, OT_ASSOC_TABLE, OT_GRP_OBJ_TABLE, OT_APPLICATION_PROG, OT_IP_PARAMETER};
-
- for (uint32_t i = start; i < (ifObjs[0] + 1) && i < count; i++)
- pushInt(ifObjs[i], data);
-
+ for (uint32_t i = start; i < (_ifObjs[0] + 1) && i < count; i++)
+ pushInt(_ifObjs[i], data);
break;
}
case PID_DEVICE_DESCRIPTOR:
- data[0] = 0x57;
- data[1] = 0xB0;
+ pushWord(_maskVersion, data);
break;
default:
count = 0;
@@ -254,6 +248,36 @@ void DeviceObject::version(uint16_t value)
_version = value;
}
+uint16_t DeviceObject::maskVersion()
+{
+ return _maskVersion;
+}
+
+void DeviceObject::maskVersion(uint16_t value)
+{
+ _maskVersion = value;
+}
+
+void DeviceObject::maxApduLength(uint16_t value)
+{
+ _maxApduLength = value;
+}
+
+uint16_t DeviceObject::maxApduLength()
+{
+ return _maxApduLength;
+}
+
+const uint32_t* DeviceObject::ifObj()
+{
+ return _ifObjs;
+}
+
+void DeviceObject::ifObj(const uint32_t* value)
+{
+ _ifObjs = value;
+}
+
static PropertyDescription _propertyDescriptions[] =
{
{ PID_OBJECT_TYPE, false, PDT_UNSIGNED_INT, 1, ReadLv3 | WriteLv0 },
diff --git a/src/knx/device_object.h b/src/knx/device_object.h
index 6d66133..35704f1 100644
--- a/src/knx/device_object.h
+++ b/src/knx/device_object.h
@@ -35,6 +35,12 @@ public:
void hardwareType(const uint8_t* value);
uint16_t version();
void version(uint16_t value);
+ uint16_t maskVersion();
+ void maskVersion(uint16_t value);
+ uint16_t maxApduLength();
+ void maxApduLength(uint16_t value);
+ const uint32_t* ifObj();
+ void ifObj(const uint32_t* value);
protected:
uint8_t propertyCount();
PropertyDescription* propertyDescriptions();
@@ -48,4 +54,7 @@ private:
char _orderNumber[10] = "";
uint8_t _hardwareType[6] = { 0, 0, 0, 0, 0, 0};
uint16_t _version = 0;
+ uint16_t _maskVersion = 0x0000;
+ uint16_t _maxApduLength = 254;
+ const uint32_t* _ifObjs;
};
\ No newline at end of file
diff --git a/src/knx/interface_object.h b/src/knx/interface_object.h
index 1452eca..2d2cfe8 100644
--- a/src/knx/interface_object.h
+++ b/src/knx/interface_object.h
@@ -47,7 +47,10 @@ enum ObjectType
OT_RESERVED = 12,
/** File Server Object */
- OT_FILE_SERVER = 13
+ OT_FILE_SERVER = 13,
+
+ /** RF Medium Object */
+ OT_RF_MEDIUM = 19
};
/**
diff --git a/src/knx/knx_types.h b/src/knx/knx_types.h
index e4cd2c5..0421526 100644
--- a/src/knx/knx_types.h
+++ b/src/knx/knx_types.h
@@ -71,12 +71,30 @@ enum TpduType
enum ApduType
{
+ // Application Layer services on Multicast Communication Mode
GroupValueRead = 0x000,
GroupValueResponse = 0x040,
GroupValueWrite = 0x080,
+
+ // Application Layer services on Broadcast Communication Mode
IndividualAddressWrite = 0x0c0,
IndividualAddressRead = 0x100,
IndividualAddressResponse = 0x140,
+ IndividualAddressSerialNumberRead = 0x3dc,
+ IndividualAddressSerialNumberResponse = 0x3dd,
+ IndividualAddressSerialNumberWrite = 0x3de,
+
+ // Application Layer Services on System Broadcast communication mode
+ SystemNetworkParameterRead = 0x1c8,
+ SystemNetworkParameterResponse = 0x1c9,
+ SystemNetworkParameterWrite = 0x1ca,
+ // Open media specific Application Layer Services on System Broadcast communication mode
+ DomainAddressSerialNumberRead = 0x3ec,
+ DomainAddressSerialNumberResponse = 0x3ed,
+ DomainAddressSerialNumberWrite = 0x3ee,
+
+ // Application Layer Services on Point-to-point Connection-Oriented Communication Mode (mandatory)
+ // Application Layer Services on Point-to-point Connectionless Communication Mode (either optional or mandatory)
MemoryRead = 0x200,
MemoryResponse = 0x240,
MemoryWrite = 0x280,
@@ -97,7 +115,4 @@ enum ApduType
PropertyValueWrite = 0x3d7,
PropertyDescriptionRead = 0x3d8,
PropertyDescriptionResponse = 0x3d9,
- IndividualAddressSerialNumberRead = 0x3dc,
- IndividualAddressSerialNumberResponse = 0x3dd,
- IndividualAddressSerialNumberWrite = 0x3de,
-};
\ No newline at end of file
+};
diff --git a/src/knx/network_layer.cpp b/src/knx/network_layer.cpp
index ceab6b6..ffea9d3 100644
--- a/src/knx/network_layer.cpp
+++ b/src/knx/network_layer.cpp
@@ -66,18 +66,30 @@ void NetworkLayer::dataConfirm(AckType ack, AddressType addressType, uint16_t de
_transportLayer.dataBroadcastConfirm(ack, hopType, priority, npdu.tpdu(), status);
}
-void NetworkLayer::systemBroadcastIndication(AckType ack, FrameFormat format, NPDU& npdu, Priority priority, uint16_t source)
+void NetworkLayer::broadcastIndication(AckType ack, FrameFormat format, NPDU& npdu, Priority priority, uint16_t source)
{
HopCountType hopType = npdu.hopCount() == 7 ? UnlimitedRouting : NetworkLayerParameter;
_transportLayer.dataBroadcastIndication(hopType, priority, source, npdu.tpdu());
}
-void NetworkLayer::systemBroadcastConfirm(AckType ack, FrameFormat format, Priority priority, uint16_t source, NPDU& npdu, bool status)
+void NetworkLayer::broadcastConfirm(AckType ack, FrameFormat format, Priority priority, uint16_t source, NPDU& npdu, bool status)
{
HopCountType hopType = npdu.hopCount() == 7 ? UnlimitedRouting : NetworkLayerParameter;
_transportLayer.dataBroadcastConfirm(ack, hopType, priority, npdu.tpdu(), status);
}
+void NetworkLayer::systemBroadcastIndication(AckType ack, FrameFormat format, NPDU& npdu, Priority priority, uint16_t source)
+{
+ HopCountType hopType = npdu.hopCount() == 7 ? UnlimitedRouting : NetworkLayerParameter;
+ _transportLayer.dataSystemBroadcastIndication(hopType, priority, source, npdu.tpdu());
+}
+
+void NetworkLayer::systemBroadcastConfirm(AckType ack, FrameFormat format, Priority priority, uint16_t source, NPDU& npdu, bool status)
+{
+ HopCountType hopType = npdu.hopCount() == 7 ? UnlimitedRouting : NetworkLayerParameter;
+ _transportLayer.dataSystemBroadcastConfirm(ack, hopType, npdu.tpdu(), priority, status);
+}
+
void NetworkLayer::dataIndividualRequest(AckType ack, uint16_t destination, HopCountType hopType, Priority priority, TPDU& tpdu)
{
//if (tpdu.apdu().length() > 0)
diff --git a/src/knx/network_layer.h b/src/knx/network_layer.h
index 2386469..0b1f88e 100644
--- a/src/knx/network_layer.h
+++ b/src/knx/network_layer.h
@@ -20,6 +20,9 @@ class NetworkLayer
Priority priority, uint16_t source);
void dataConfirm(AckType ack, AddressType addressType, uint16_t destination, FrameFormat format, Priority priority,
uint16_t source, NPDU& npdu, bool status);
+ void broadcastIndication(AckType ack, FrameFormat format, NPDU& npdu,
+ Priority priority, uint16_t source);
+ void broadcastConfirm(AckType ack, FrameFormat format, Priority priority, uint16_t source, NPDU& npdu, bool status);
void systemBroadcastIndication(AckType ack, FrameFormat format, NPDU& npdu,
Priority priority, uint16_t source);
void systemBroadcastConfirm(AckType ack, FrameFormat format, Priority priority, uint16_t source, NPDU& npdu, bool status);
diff --git a/src/knx/platform.h b/src/knx/platform.h
index 497d98e..5a082aa 100644
--- a/src/knx/platform.h
+++ b/src/knx/platform.h
@@ -29,6 +29,10 @@ class Platform
virtual int readUart() = 0;
virtual size_t readBytesUart(uint8_t* buffer, size_t length) = 0;
+ virtual void setupSpi() = 0;
+ virtual void closeSpi() = 0;
+ virtual int readWriteSpi (uint8_t *data, size_t len) = 0;
+
virtual uint8_t* getEepromBuffer(uint16_t size) = 0;
virtual void commitToEeprom() = 0;
diff --git a/src/knx/property_types.h b/src/knx/property_types.h
index 5f392e8..088c741 100644
--- a/src/knx/property_types.h
+++ b/src/knx/property_types.h
@@ -96,6 +96,17 @@ enum PropertyID
PID_HARDWARE_TYPE = 78,
PID_DEVICE_DESCRIPTOR = 83,
+ /** Properties in the RF Medium Object */
+ PID_RF_MULTI_TYPE = 51,
+ PID_RF_DOMAIN_ADDRESS = 56,
+ PID_RF_RETRANSMITTER = 57,
+ PID_RF_FILTERING_MODE_SUPPORT = 58,
+ PID_RF_FILTERING_MODE_SELECT = 59,
+ PID_RF_BIDIR_TIMEOUT = 60,
+ PID_RF_DIAG_SA_FILTER_TABLE = 61,
+ PID_RF_DIAG_BUDGET_TABLE = 62,
+ PID_RF_DIAG_PROBE = 63,
+
/** KNXnet/IP Parameter Object */
PID_PROJECT_INSTALLATION_ID = 51,
PID_KNX_INDIVIDUAL_ADDRESS = 52,
diff --git a/src/knx/rf_data_link_layer.cpp b/src/knx/rf_data_link_layer.cpp
new file mode 100644
index 0000000..f4b6ff6
--- /dev/null
+++ b/src/knx/rf_data_link_layer.cpp
@@ -0,0 +1,369 @@
+#if MEDIUM_TYPE == 2
+
+#include "rf_physical_layer.h"
+#include "rf_data_link_layer.h"
+
+#include "bits.h"
+#include "platform.h"
+#include "device_object.h"
+#include "address_table_object.h"
+#include "rf_medium_object.h"
+#include "cemi_frame.h"
+
+#include
+#include
+
+void RfDataLinkLayer::loop()
+{
+ if (!_enabled)
+ return;
+
+ _rfPhy.loop();
+}
+
+bool RfDataLinkLayer::sendFrame(CemiFrame& frame)
+{
+ if (!_enabled)
+ return false;
+
+ // Depending on this flag, use either KNX Serial Number
+ // or the RF domain address that was programmed by ETS
+ if (frame.systemBroadcast() == SysBroadcast)
+ {
+ uint8_t knxSerialNumber[6];
+ pushWord(_deviceObject.manufacturerId(), &knxSerialNumber[0]);
+ pushInt(_deviceObject.bauNumber(), &knxSerialNumber[2]);
+ frame.rfSerialOrDoA(&knxSerialNumber[0]);
+ }
+ else
+ {
+ frame.rfSerialOrDoA(_rfMediumObj.rfDomainAddress());
+ }
+
+ // Set Data Link Layer Frame Number
+ frame.rfLfn(_frameNumber);
+ // Link Layer frame number counts 0..7
+ _frameNumber = (_frameNumber + 1) & 0x7;
+
+ // bidirectional device, battery is ok, signal strength indication is void (no measurement)
+ frame.rfInfo(0x02);
+
+ // TODO: Is queueing really required?
+ // According to the spec. the upper layer may only send a new L_Data.req if it received
+ // the L_Data.con for the previous L_Data.req.
+ addFrameTxQueue(frame);
+
+ // TODO: For now L_data.req is confirmed immediately (L_Data.con)
+ // see 3.6.3 p.80: L_Data.con shall be generated AFTER transmission of the corresponsing frame
+ // RF sender will never generate L_Data.con with C=1 (Error), but only if the TX buffer overflows
+ // The RF sender cannot detect if the RF frame was transmitted successfully or not according to the spec.
+ dataConReceived(frame, true);
+
+ return true;
+}
+
+RfDataLinkLayer::RfDataLinkLayer(DeviceObject& devObj, RfMediumObject& rfMediumObj, AddressTableObject& addrTab,
+ NetworkLayer& layer, Platform& platform)
+ : DataLinkLayer(devObj, addrTab, layer, platform),
+ _rfMediumObj(rfMediumObj),
+ _rfPhy(*this, platform)
+{
+}
+
+uint16_t RfDataLinkLayer::calcCrcRF(uint8_t* buffer, uint32_t offset, uint32_t len)
+{
+ // CRC-16-DNP
+ // generator polynomial = 2^16 + 2^13 + 2^12 + 2^11 + 2^10 + 2^8 + 2^6 + 2^5 + 2^2 + 2^0
+ uint32_t pn = 0x13d65; // 1 0011 1101 0110 0101
+
+ // for much data, using a lookup table would be a way faster CRC calculation
+ uint32_t crc = 0;
+ for (uint32_t i = offset; i < offset + len; i++) {
+ uint8_t bite = buffer[i] & 0xff;
+ for (uint8_t b = 8; b --> 0;) {
+ bool bit = ((bite >> b) & 1) == 1;
+ bool one = (crc >> 15 & 1) == 1;
+ crc <<= 1;
+ if (one ^ bit)
+ crc ^= pn;
+ }
+ }
+ return (~crc) & 0xffff;
+}
+
+void RfDataLinkLayer::frameBytesReceived(uint8_t* rfPacketBuf, uint16_t length)
+{
+ // RF data link layer frame format
+ // See 3.2.5 p.22
+
+ // First block + smallest KNX telegram will give a minimum size of 22 bytes with checksum bytes
+ if (length < 21)
+ {
+ print("Received packet is too small. length: ");
+ println(length);
+ return;
+ }
+
+ // CRC16-DNP of first block is always located here
+ uint16_t block1Crc = rfPacketBuf[10] << 8 | rfPacketBuf[11];
+
+ // If the checksum was ok and the other
+ // two constant header bytes match the KNX-RF spec. (C-field: 0x44 and ESC-field: 0xFF)...
+ // then we seem to have a valid first block of an KNX RF frame.
+ // The first block basically contains the RF-info field and the KNX SN/Domain address.
+ if ((rfPacketBuf[1] == 0x44) &&
+ (rfPacketBuf[2] == 0xFF) &&
+ (calcCrcRF(rfPacketBuf, 0, 10) == block1Crc))
+ {
+ // bytes left from the remaining block(s)
+ uint16_t bytesLeft = length - 12;
+ // we use two pointers to move over the two buffers
+ uint8_t* pRfPacketBuf = &rfPacketBuf[12]; // pointer to start of RF frame block 2 (with CTRL field)
+ // Reserve 1 byte (+1) for the second ctrl field
+ // cEMI frame has two CTRL fields, but RF frame has only one, but uses ALWAYS extended frames
+ // Information for BOTH cEMI CTRL fields is distributed in a RF frame (RF CTRL field and RF L/NPCI field)
+ // So we cannot just copy an RF frame with CTRL fields as is
+ // KNX RF frame will be placed starting at cEMI CTRL2 field (so RF CTRL field is CTRL2 field cEMI)
+ uint8_t* pBuffer = &_buffer[CEMI_HEADER_SIZE + 1];
+ // New length of the packet with CRC bytes removed, add space for CEMI header and the second CTRL field
+ uint16_t newLength = CEMI_HEADER_SIZE + 1;
+
+ // Now check each block checksum and copy the payload of the block
+ // into a new buffer without checksum
+ uint16_t blockCrc;
+ bool crcOk = true;
+ while (bytesLeft > 18)
+ {
+ // Get CRC16 from end of the block
+ blockCrc = pRfPacketBuf[16] << 8 | pRfPacketBuf[17];
+ if (calcCrcRF(pRfPacketBuf, 0, 16) == blockCrc)
+ {
+ // Copy only the payload without the checksums
+ memcpy(pBuffer, pRfPacketBuf, 16);
+ }
+ else
+ {
+ crcOk = false;
+ break;
+ }
+ pBuffer += 16;
+ pRfPacketBuf += 18;
+ newLength += 16;
+ bytesLeft -= 18;
+ }
+
+ // Now process the last block
+ blockCrc = pRfPacketBuf[bytesLeft - 2] << 8 | pRfPacketBuf[bytesLeft - 1];
+ crcOk = crcOk && (calcCrcRF(&pRfPacketBuf[0], 0, bytesLeft -2) == blockCrc);
+
+ // If all checksums were ok, then...
+ if (crcOk)
+ {
+ // Copy rest of the received packet without checksum
+ memcpy(pBuffer, pRfPacketBuf, bytesLeft -2);
+ newLength += bytesLeft -2;
+
+ // Prepare CEMI by writing/overwriting certain fields in the buffer (contiguous frame without CRC checksums)
+ // See 3.6.3 p.79: L_Data services for KNX RF asynchronous frames
+ // For now we do not use additional info, but use normal method arguments for CEMI
+ _buffer[0] = 0x29; // L_data.ind
+ _buffer[1] = 0; // Additional info length (spec. says that local dev management is not required to use AddInfo internally)
+ _buffer[2] = 0; // CTRL1 field (will be set later, this is the field we reserved space for)
+ _buffer[3] &= 0x0F; // CTRL2 field (take only RFCtrl.b3..0, b7..4 shall always be 0 for asynchronous KNX RF)
+
+ // Now get all control bits from the L/NPCI field of the RF frame
+ // so that we can overwrite it afterwards with the correct NPDU length
+ // Get data link layer frame number (LFN field) from L/NPCI.LFN (bit 3..1)
+ uint8_t lfn = (_buffer[8] & 0x0E) >> 1;
+ // Get address type from L/NPCI.LFN (bit 7)
+ AddressType addressType = (_buffer[8] & 0x80) ? GroupAddress:InduvidualAddress;
+ // Get routing counter from L/NPCI.LFN (bit 6..4) and map to hop count in Ctrl2.b6-4
+ uint8_t hopCount = (_buffer[8] & 0x70) >> 4;
+ // Get AddrExtensionType from L/NPCI.LFN (bit 7) and map to system broadcast flag in Ctrl1.b4
+ SystemBroadcast systemBroadcast = (_buffer[8] & 0x01) ? Broadcast:SysBroadcast;
+
+ // Setup L field of the cEMI frame with the NPDU length
+ // newLength -8 bytes (NPDU_LPDU_DIFF, no AddInfo) -1 byte length field -1 byte TPCI/APCI bits
+ _buffer[8] = newLength - NPDU_LPDU_DIFF - 1 - 1;
+
+ // If we have a broadcast message (within the domain),
+ // then we received the domain address and not the KNX serial number
+ if (systemBroadcast == Broadcast)
+ {
+ // Check if the received RF domain address matches the one stored in the RF medium object
+ // If it does not match then skip the remaining processing
+ if (memcmp(_rfMediumObj.rfDomainAddress(), &rfPacketBuf[4], 6))
+ {
+ println("RX domain address does not match. Skipping...");
+ return;
+ }
+ }
+
+ // TODO
+ // Frame duplication prevention based on LFN (see KKNX RF spec. 3.2.5 p.28)
+
+ // Prepare the cEMI frame
+ CemiFrame frame(_buffer, newLength);
+ frame.frameType(ExtendedFrame); // KNX RF uses only extended frame format
+ frame.priority(SystemPriority); // Not used in KNX RF
+ frame.ack(AckDontCare); // Not used in KNX RF
+ frame.systemBroadcast(systemBroadcast); // Mapped from flag AddrExtensionType (KNX serial(0) or Domain Address(1))
+ frame.hopCount(hopCount); // Hop count from routing counter
+ frame.addressType(addressType); // Group address or individual address
+ frame.rfSerialOrDoA(&rfPacketBuf[4]); // Copy pointer to field Serial or Domain Address (check broadcast flag what it is exactly)
+ frame.rfInfo(rfPacketBuf[3]); // RF-info field (1 byte)
+ frame.rfLfn(lfn); // Data link layer frame number (LFN field)
+/*
+ print("RX LFN: ");
+ print(lfn);
+ print(" len: ");
+ print(newLength);
+
+ print(" data: ");
+ printHex(" data: ", _buffer, newLength);
+*/
+ frameRecieved(frame);
+ }
+ }
+}
+
+void RfDataLinkLayer::enabled(bool value)
+{
+ if (value && !_enabled)
+ {
+ if (_rfPhy.InitChip())
+ {
+ _enabled = true;
+ print("ownaddr ");
+ println(_deviceObject.induvidualAddress(), HEX);
+ }
+ else
+ {
+ _enabled = false;
+ println("ERROR, RF transceiver not responding");
+ }
+ return;
+ }
+
+ if (!value && _enabled)
+ {
+ _rfPhy.stopChip();
+ _enabled = false;
+ return;
+ }
+}
+
+bool RfDataLinkLayer::enabled() const
+{
+ return _enabled;
+}
+
+void RfDataLinkLayer::fillRfFrame(CemiFrame& frame, uint8_t* data)
+{
+ uint16_t crc;
+ uint16_t length = frame.telegramLengthtRF();
+
+ data[0] = 9 + length; // Length block1 (always 9 bytes, without length itself) + Length of KNX telegram without CRCs
+ data[1] = 0x44; // C field: According to IEC870-5. KNX only uses SEND/NO REPLY (C = 44h)
+ data[2] = 0xFF; // ESC field: This field shall have the fixed value FFh.
+ data[3] = frame.rfInfo(); // RF-info field
+
+ // Generate CRC16-DNP over the first block of data
+ pushByteArray(frame.rfSerialOrDoA(), 6, &data[4]);
+ crc = calcCrcRF(&data[0], 0, 10);
+ pushWord(crc, &data[10]);
+
+ // Put the complete KNX telegram into a temporary buffer
+ // as we have to add CRC16 checksums after each block of 16 bytes
+ frame.fillTelegramRF(_buffer);
+
+ // Create a checksum for each block of full 16 bytes
+ uint16_t bytesLeft = length;
+ uint8_t *pBuffer = &_buffer[0];
+ uint8_t *pData = &data[12];
+ while (bytesLeft > 16)
+ {
+ memcpy(pData, pBuffer, 16);
+ crc = calcCrcRF(pData, 0, 16);
+ pushWord(crc, &pData[16]);
+
+ pBuffer += 16;
+ pData += 18;
+ bytesLeft -= 16;
+ }
+
+ // Copy remaining bytes of last block. Could be less than 16 bytes
+ memcpy(pData, pBuffer, bytesLeft);
+ // And add last CRC
+ crc = calcCrcRF(pData, 0, bytesLeft);
+ pushWord(crc, &pData[bytesLeft]);
+}
+
+void RfDataLinkLayer::addFrameTxQueue(CemiFrame& frame)
+{
+ _tx_queue_frame_t* tx_frame = new _tx_queue_frame_t;
+
+ uint16_t length = frame.telegramLengthtRF(); // Just the pure KNX telegram from CTRL field until end of APDU
+ uint8_t nrFullBlocks = length / 16; // Number of full (16 bytes) RF blocks required
+ uint8_t bytesLeft = length % 16; // Remaining bytes of the last packet
+
+ // Calculate total number of bytes required to store the complete raw RF frame
+ // Block1 always requires 12 bytes including Length and CRC
+ // Each full block has 16 bytes payload plus 2 bytes CRC
+ // Add remaining bytes of the last block and add 2 bytes for CRC
+ uint16_t totalLength = 12 + (nrFullBlocks * 18) + bytesLeft + 2;
+
+ tx_frame->length = totalLength;
+ tx_frame->data = new uint8_t[tx_frame->length];
+ tx_frame->next = NULL;
+
+ // Prepare the raw RF frame
+ fillRfFrame(frame, tx_frame->data);
+/*
+ print("TX LFN: ");
+ print(frame.rfLfn());
+
+ print(" len: ");
+ print(totalLength);
+
+ printHex(" data:", tx_frame->data, totalLength);
+*/
+ 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;
+ }
+}
+
+bool RfDataLinkLayer::isTxQueueEmpty()
+{
+ if (_tx_queue.front == NULL)
+ {
+ return true;
+ }
+ return false;
+}
+
+void RfDataLinkLayer::loadNextTxFrame(uint8_t** sendBuffer, uint16_t* sendBufferLength)
+{
+ if (_tx_queue.front == NULL)
+ {
+ return;
+ }
+ _tx_queue_frame_t* tx_frame = _tx_queue.front;
+ *sendBuffer = tx_frame->data;
+ *sendBufferLength = tx_frame->length;
+ _tx_queue.front = tx_frame->next;
+
+ if (_tx_queue.front == NULL)
+ {
+ _tx_queue.back = NULL;
+ }
+ delete tx_frame;
+}
+
+#endif // #if MEDIUM_TYPE == 2
diff --git a/src/knx/rf_data_link_layer.h b/src/knx/rf_data_link_layer.h
new file mode 100644
index 0000000..10092ca
--- /dev/null
+++ b/src/knx/rf_data_link_layer.h
@@ -0,0 +1,58 @@
+#pragma once
+
+#include
+#include "data_link_layer.h"
+
+#define MAX_KNX_TELEGRAM_SIZE 263
+
+class RfPhysicalLayer;
+class RfMediumObject;
+
+class RfDataLinkLayer : public DataLinkLayer
+{
+ friend class RfPhysicalLayer;
+
+ using DataLinkLayer::_deviceObject;
+ using DataLinkLayer::_groupAddressTable;
+ using DataLinkLayer::_platform;
+
+ public:
+ RfDataLinkLayer(DeviceObject& devObj, RfMediumObject& rfMediumObj, AddressTableObject& addrTab, NetworkLayer& layer,
+ Platform& platform);
+
+ void loop();
+ void enabled(bool value);
+ bool enabled() const;
+
+ private:
+ bool _enabled = false;
+ uint8_t _loopState = 0;
+
+ uint8_t _buffer[512];
+
+ uint8_t _frameNumber = 0;
+
+ struct _tx_queue_frame_t
+ {
+ uint8_t* data;
+ uint16_t length;
+ _tx_queue_frame_t* next;
+ };
+
+ struct _tx_queue_t
+ {
+ _tx_queue_frame_t* front = NULL;
+ _tx_queue_frame_t* back = NULL;
+ } _tx_queue;
+
+ RfMediumObject& _rfMediumObj;
+ RfPhysicalLayer _rfPhy;
+
+ void fillRfFrame(CemiFrame& frame, uint8_t* data);
+ void addFrameTxQueue(CemiFrame& frame);
+ bool isTxQueueEmpty();
+ void loadNextTxFrame(uint8_t** sendBuffer, uint16_t* sendBufferLength);
+ bool sendFrame(CemiFrame& frame);
+ void frameBytesReceived(uint8_t* buffer, uint16_t length);
+ uint16_t calcCrcRF(uint8_t* buffer, uint32_t offset, uint32_t len);
+};
diff --git a/src/knx/rf_medium_object.cpp b/src/knx/rf_medium_object.cpp
new file mode 100644
index 0000000..2e96d9d
--- /dev/null
+++ b/src/knx/rf_medium_object.cpp
@@ -0,0 +1,128 @@
+#include
+#include "rf_medium_object.h"
+#include "bits.h"
+
+void RfMediumObject::readProperty(PropertyID propertyId, uint32_t start, uint32_t& count, uint8_t* data)
+{
+ switch (propertyId)
+ {
+ case PID_OBJECT_TYPE:
+ pushWord(OT_RF_MEDIUM, data);
+ break;
+ case PID_RF_MULTI_TYPE:
+ data[0] = 0x00; // KNX RF ready only
+ break;
+ case PID_RF_DOMAIN_ADDRESS:
+ pushByteArray((uint8_t*)_rfDomainAddress, 6, data);
+ break;
+ case PID_RF_RETRANSMITTER:
+ data[0] = 0x00; // No KNX RF retransmitter
+ break;
+ case PID_RF_BIDIR_TIMEOUT: // PDT_FUNCTION
+ data[0] = 0x00; // success
+ data[1] = 0xFF; // permanent bidirectional device
+ data[2] = 0xFF; // permanent bidirectional device
+ break;
+ case PID_RF_DIAG_SA_FILTER_TABLE: // PDT_GENERIC_03[]
+ pushByteArray((uint8_t*)_rfDiagSourceAddressFilterTable, 24, data);
+ break;
+ case PID_RF_DIAG_BUDGET_TABLE:
+ pushByteArray((uint8_t*)_rfDiagLinkBudgetTable, 24, data);
+ break;
+ case PID_RF_DIAG_PROBE: // PDT_FUNCTION
+ // Not supported yet
+ break;
+ default:
+ count = 0;
+ }
+}
+
+void RfMediumObject::writeProperty(PropertyID id, uint8_t start, uint8_t* data, uint8_t count)
+{
+ switch (id)
+ {
+ case PID_RF_DOMAIN_ADDRESS:
+ for (uint8_t i = start; i < start + count; i++)
+ _rfDomainAddress[i-1] = data[i - start];
+ break;
+ case PID_RF_BIDIR_TIMEOUT: // PDT_FUNCTION
+ // Not supported yet (permanent bidir device)
+ break;
+ case PID_RF_DIAG_SA_FILTER_TABLE:
+ for (uint8_t i = start; i < start + count; i++)
+ _rfDiagSourceAddressFilterTable[i-1] = data[i - start];
+ break;
+ case PID_RF_DIAG_BUDGET_TABLE:
+ for (uint8_t i = start; i < start + count; i++)
+ _rfDiagLinkBudgetTable[i-1] = data[i - start];
+ break;
+ case PID_RF_DIAG_PROBE:
+ // Not supported yet
+ break;
+ default:
+ break;
+ }
+}
+
+uint8_t RfMediumObject::propertySize(PropertyID id)
+{
+ switch (id)
+ {
+ case PID_RF_MULTI_TYPE:
+ case PID_RF_RETRANSMITTER:
+ return 1;
+ case PID_OBJECT_TYPE:
+ return 2;
+ case PID_RF_DOMAIN_ADDRESS:
+ return 6;
+ case PID_RF_DIAG_SA_FILTER_TABLE:
+ case PID_RF_DIAG_BUDGET_TABLE:
+ return 24;
+ // case PID_RF_BIDIR_TIMEOUT: ?
+ // case PID_RF_DIAG_PROBE: ?
+ default:
+ break;
+ }
+ return 0;
+}
+
+uint8_t* RfMediumObject::save(uint8_t* buffer)
+{
+ buffer = pushByteArray((uint8_t*)_rfDomainAddress, 6, buffer);
+ return buffer;
+}
+
+uint8_t* RfMediumObject::restore(uint8_t* buffer)
+{
+ buffer = popByteArray((uint8_t*)_rfDomainAddress, 6, buffer);
+ return buffer;
+}
+
+uint8_t* RfMediumObject::rfDomainAddress()
+{
+ return _rfDomainAddress;
+}
+
+void RfMediumObject::rfDomainAddress(uint8_t* value)
+{
+ pushByteArray(value, 6, _rfDomainAddress);
+}
+
+static PropertyDescription _propertyDescriptions[] =
+{
+ { PID_OBJECT_TYPE, false, PDT_UNSIGNED_INT, 1, ReadLv3 | WriteLv0 },
+ { PID_RF_MULTI_TYPE, false, PDT_GENERIC_01, 1, ReadLv3 | WriteLv0 },
+ { PID_RF_RETRANSMITTER, false, PDT_GENERIC_01, 1, ReadLv3 | WriteLv0 },
+ { PID_RF_DOMAIN_ADDRESS, true, PDT_GENERIC_06, 1, ReadLv3 | WriteLv0 }
+};
+static uint8_t _propertyCount = sizeof(_propertyDescriptions) / sizeof(PropertyDescription);
+
+uint8_t RfMediumObject::propertyCount()
+{
+ return _propertyCount;
+}
+
+PropertyDescription* RfMediumObject::propertyDescriptions()
+{
+ return _propertyDescriptions;
+}
diff --git a/src/knx/rf_medium_object.h b/src/knx/rf_medium_object.h
new file mode 100644
index 0000000..57c0a2e
--- /dev/null
+++ b/src/knx/rf_medium_object.h
@@ -0,0 +1,27 @@
+#pragma once
+
+#include "interface_object.h"
+
+class RfMediumObject: public InterfaceObject
+{
+public:
+ void readProperty(PropertyID id, uint32_t start, uint32_t& count, uint8_t* data);
+ void writeProperty(PropertyID id, uint8_t start, uint8_t* data, uint8_t count);
+ uint8_t propertySize(PropertyID id);
+ uint8_t* save(uint8_t* buffer);
+ uint8_t* restore(uint8_t* buffer);
+ void readPropertyDescription(uint8_t propertyId, uint8_t& propertyIndex, bool& writeEnable, uint8_t& type, uint16_t& numberOfElements, uint8_t& access);
+
+ uint8_t* rfDomainAddress();
+ void rfDomainAddress(uint8_t* value);
+
+protected:
+ uint8_t propertyCount();
+ PropertyDescription* propertyDescriptions();
+private:
+ uint8_t _rfDomainAddress[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; // see KNX RF S-Mode AN160 p.11
+ uint8_t _rfDiagSourceAddressFilterTable[24] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,};
+ uint8_t _rfDiagLinkBudgetTable[24] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,};
+
+
+};
\ No newline at end of file
diff --git a/src/knx/rf_physical_layer.cpp b/src/knx/rf_physical_layer.cpp
new file mode 100644
index 0000000..590667d
--- /dev/null
+++ b/src/knx/rf_physical_layer.cpp
@@ -0,0 +1,799 @@
+#if MEDIUM_TYPE == 2
+
+#include "rf_physical_layer.h"
+#include "rf_data_link_layer.h"
+
+#include "bits.h"
+#include "platform.h"
+
+#include
+#include
+
+#define MIN(a, b) ((a < b) ? (a) : (b))
+#define MAX(a, b) ((a > b) ? (a) : (b))
+#define ABS(x) ((x > 0) ? (x) : (-x))
+
+// Table for encoding 4-bit data into a 8-bit Manchester encoding.
+const uint8_t RfPhysicalLayer::manchEncodeTab[16] = {0xAA, // 0x0 Manchester encoded
+ 0xA9, // 0x1 Manchester encoded
+ 0xA6, // 0x2 Manchester encoded
+ 0xA5, // 0x3 Manchester encoded
+ 0x9A, // 0x4 Manchester encoded
+ 0x99, // 0x5 Manchester encoded
+ 0x96, // 0x6 Manchester encoded
+ 0x95, // 0x7 Manchester encoded
+ 0x6A, // 0x8 Manchester encoded
+ 0x69, // 0x9 Manchester encoded
+ 0x66, // 0xA Manchester encoded
+ 0x65, // 0xB Manchester encoded
+ 0x5A, // 0xC Manchester encoded
+ 0x59, // 0xD Manchester encoded
+ 0x56, // 0xE Manchester encoded
+ 0x55}; // 0xF Manchester encoded
+
+// Table for decoding 4-bit Manchester encoded data into 2-bit
+// data. 0xFF indicates invalid Manchester encoding
+const uint8_t RfPhysicalLayer::manchDecodeTab[16] = {0xFF, // Manchester encoded 0x0 decoded
+ 0xFF, // Manchester encoded 0x1 decoded
+ 0xFF, // Manchester encoded 0x2 decoded
+ 0xFF, // Manchester encoded 0x3 decoded
+ 0xFF, // Manchester encoded 0x4 decoded
+ 0x03, // Manchester encoded 0x5 decoded
+ 0x02, // Manchester encoded 0x6 decoded
+ 0xFF, // Manchester encoded 0x7 decoded
+ 0xFF, // Manchester encoded 0x8 decoded
+ 0x01, // Manchester encoded 0x9 decoded
+ 0x00, // Manchester encoded 0xA decoded
+ 0xFF, // Manchester encoded 0xB decoded
+ 0xFF, // Manchester encoded 0xC decoded
+ 0xFF, // Manchester encoded 0xD decoded
+ 0xFF, // Manchester encoded 0xE decoded
+ 0xFF};// Manchester encoded 0xF decoded
+
+// Product = CC1101
+// Chip version = A (VERSION = 0x04)
+// Crystal accuracy = 10 ppm
+// X-tal frequency = 26 MHz
+// RF output power = + 10 dBm
+// RX filterbandwidth = 270 kHz
+// Deviation = 47 kHz
+// Datarate = 32.73 kBaud
+// Modulation = (0) 2-FSK
+// Manchester enable = (0) Manchester disabled
+// RF Frequency = 868.299866 MHz
+// Channel spacing = 199.951172 kHz
+// Channel number = 0
+// Optimization = -
+// Sync mode = (5) 15/16 + carrier-sense above threshold
+// Format of RX/TX data = (0) Normal mode, use FIFOs for RX and TX
+// CRC operation = (0) CRC disabled for TX and RX
+// Forward Error Correction = (0) FEC disabled
+// Length configuration = (0) Fixed length packets, length configured in PKTLEN register.
+// Packetlength = 255
+// Preamble count = (2) 4 bytes
+// Append status = 0
+// Address check = (0) No address check
+// FIFO autoflush = 0
+// Device address = 0
+// GDO0 signal selection = ( 6) Asserts when sync word has been sent / received, and de-asserts at the end of the packet
+// GDO2 signal selection = ( 0) Asserts when RX FiFO threshold
+const uint8_t RfPhysicalLayer::cc1101_2FSK_32_7_kb[CFG_REGISTER] = {
+ 0x00, // IOCFG2 GDO2 Output Pin Configuration
+ 0x2E, // IOCFG1 GDO1 Output Pin Configuration
+ 0x06, // IOCFG0 GDO0 Output Pin Configuration
+ 0x40, // FIFOTHR RX FIFO and TX FIFO Thresholds // 4 bytes in RX FIFO (2 bytes manchester encoded)
+ 0x76, // SYNC1 Sync Word
+ 0x96, // SYNC0 Sync Word
+ 0xFF, // PKTLEN Packet Length
+ 0x00, // PKTCTRL1 Packet Automation Control
+ 0x00, // PKTCTRL0 Packet Automation Control
+ 0x00, // ADDR Device Address
+ 0x00, // CHANNR Channel Number
+ 0x08, // FSCTRL1 Frequency Synthesizer Control
+ 0x00, // FSCTRL0 Frequency Synthesizer Control
+ 0x21, // FREQ2 Frequency Control Word
+ 0x65, // FREQ1 Frequency Control Word
+ 0x6A, // FREQ0 Frequency Control Word
+ 0x6A, // MDMCFG4 Modem Configuration
+ 0x4A, // MDMCFG3 Modem Configuration
+ 0x05, // MDMCFG2 Modem Configuration
+ 0x22, // MDMCFG1 Modem Configuration
+ 0xF8, // MDMCFG0 Modem Configuration
+ 0x47, // DEVIATN Modem Deviation Setting
+ 0x07, // MCSM2 Main Radio Control State Machine Configuration
+ 0x30, // MCSM1 Main Radio Control State Machine Configuration (IDLE after TX and RX)
+ 0x18, // MCSM0 Main Radio Control State Machine Configuration
+ 0x2E, // FOCCFG Frequency Offset Compensation Configuration
+ 0x6D, // BSCFG Bit Synchronization Configuration
+ 0x43, // AGCCTRL2 AGC Control 0x04, // AGCCTRL2 magn target 33dB vs 36dB (max LNA+LNA2 gain vs. ) (highest gain cannot be used vs. all gain settings)
+ 0x40, // AGCCTRL1 AGC Control 0x09, // AGCCTRL1 carrier sense threshold disabled vs. 7dB below magn target (LNA prio strat. 1 vs 0)
+ 0x91, // AGCCTRL0 AGC Control 0xB2, // AGCCTRL0 channel filter samples 16 vs.32
+ 0x87, // WOREVT1 High Byte Event0 Timeout
+ 0x6B, // WOREVT0 Low Byte Event0 Timeout
+ 0xFB, // WORCTRL Wake On Radio Control
+ 0xB6, // FREND1 Front End RX Configuration
+ 0x10, // FREND0 Front End TX Configuration
+ 0xE9, // FSCAL3 Frequency Synthesizer Calibration 0xEA, // FSCAL3
+ 0x2A, // FSCAL2 Frequency Synthesizer Calibration
+ 0x00, // FSCAL1 Frequency Synthesizer Calibration
+ 0x1F, // FSCAL0 Frequency Synthesizer Calibration
+ 0x41, // RCCTRL1 RC Oscillator Configuration
+ 0x00, // RCCTRL0 RC Oscillator Configuration
+ 0x59, // FSTEST Frequency Synthesizer Calibration Control
+ 0x7F, // PTEST Production Test
+ 0x3F, // AGCTEST AGC Test
+ 0x81, // TEST2 Various Test Settings
+ 0x35, // TEST1 Various Test Settings
+ 0x09 // TEST0 Various Test Settings
+};
+
+ //Patable index: -30 -20- -15 -10 0 5 7 10 dBm
+const uint8_t RfPhysicalLayer::paTablePower868[8] = {0x03,0x17,0x1D,0x26,0x50,0x86,0xCD,0xC0};
+
+RfPhysicalLayer::RfPhysicalLayer(RfDataLinkLayer& rfDataLinkLayer, Platform& platform)
+ : _rfDataLinkLayer(rfDataLinkLayer),
+ _platform(platform)
+{
+}
+
+void RfPhysicalLayer::manchEncode(uint8_t *uncodedData, uint8_t *encodedData)
+{
+ uint8_t data0, data1;
+
+ // - Shift to get 4-bit data values
+ data1 = (((*uncodedData) >> 4) & 0x0F);
+ data0 = ((*uncodedData) & 0x0F);
+
+ // - Perform Manchester encoding -
+ *encodedData = (manchEncodeTab[data1]);
+ *(encodedData + 1) = manchEncodeTab[data0];
+}
+
+bool RfPhysicalLayer::manchDecode(uint8_t *encodedData, uint8_t *decodedData)
+{
+ uint8_t data0, data1, data2, data3;
+
+ // - Shift to get 4 bit data and decode
+ data3 = ((*encodedData >> 4) & 0x0F);
+ data2 = ( *encodedData & 0x0F);
+ data1 = ((*(encodedData + 1) >> 4) & 0x0F);
+ data0 = ((*(encodedData + 1)) & 0x0F);
+
+ // Check for invalid Manchester encoding
+ if ( (manchDecodeTab[data3] == 0xFF ) | (manchDecodeTab[data2] == 0xFF ) |
+ (manchDecodeTab[data1] == 0xFF ) | (manchDecodeTab[data0] == 0xFF ) )
+ {
+ return false;
+ }
+
+ // Shift result into a byte
+ *decodedData = (manchDecodeTab[data3] << 6) | (manchDecodeTab[data2] << 4) |
+ (manchDecodeTab[data1] << 2) | manchDecodeTab[data0];
+
+ return true;
+}
+
+int RfPhysicalLayer::crc16(uint8_t* buffer, int offset, int length)
+{
+ // CRC-16-DNP
+ // generator polynomial = 2^16 + 2^13 + 2^12 + 2^11 + 2^10 + 2^8 + 2^6 + 2^5 + 2^2 + 2^0
+ int pn = 0x13d65; // 1 0011 1101 0110 0101
+
+ // for much data, using a lookup table would be a way faster CRC calculation
+ int crc = 0;
+ for (int i = offset; i < offset + length; i++) {
+ int bite = buffer[i] & 0xff;
+ for (int b = 8; b --> 0;) {
+ bool bit = ((bite >> b) & 1) == 1;
+ bool one = (crc >> 15 & 1) == 1;
+ crc <<= 1;
+ if (one ^ bit)
+ crc ^= pn;
+ }
+ }
+ return (~crc) & 0xffff;
+}
+
+uint8_t RfPhysicalLayer::sIdle()
+{
+ uint8_t marcState;
+ uint32_t timeStart;
+
+ spiWriteStrobe(SIDLE); //sets to idle first. must be in
+
+ marcState = 0xFF; //set unknown/dummy state value
+ timeStart = millis();
+ while((marcState != MARCSTATE_IDLE) && ((millis() - timeStart) < CC1101_TIMEOUT)) //0x01 = sidle
+ {
+ marcState = (spiReadRegister(MARCSTATE) & MARCSTATE_BITMASK); //read out state of cc1101 to be sure in RX
+ }
+
+ //print("marcstate: 0x");
+ //println(marcState, HEX);
+
+ if(marcState != MARCSTATE_IDLE)
+ {
+ println("Timeout when trying to set idle state.");
+ return false;
+ }
+
+ return true;
+}
+
+uint8_t RfPhysicalLayer::sReceive()
+{
+ uint8_t marcState;
+ uint32_t timeStart;
+
+ spiWriteStrobe(SRX); //writes receive strobe (receive mode)
+
+ marcState = 0xFF; //set unknown/dummy state value
+ timeStart = millis();
+ while((marcState != MARCSTATE_RX) && ((millis() - timeStart) < CC1101_TIMEOUT)) //0x0D = RX
+ {
+ marcState = (spiReadRegister(MARCSTATE) & MARCSTATE_BITMASK); //read out state of cc1101 to be sure in RX
+ }
+
+ //print("marcstate: 0x");
+ //println(marcState, HEX);
+
+ if(marcState != MARCSTATE_RX)
+ {
+ println("Timeout when trying to set receive state.");
+ return false;
+ }
+
+ return true;
+}
+
+void RfPhysicalLayer::spiWriteRegister(uint8_t spi_instr, uint8_t value)
+{
+ uint8_t tbuf[2] = {0};
+ tbuf[0] = spi_instr | WRITE_SINGLE_BYTE;
+ tbuf[1] = value;
+ uint8_t len = 2;
+ digitalWrite(SPI_SS_PIN, LOW);
+ _platform.readWriteSpi(tbuf, len);
+ digitalWrite(SPI_SS_PIN, HIGH);
+}
+
+uint8_t RfPhysicalLayer::spiReadRegister(uint8_t spi_instr)
+{
+ uint8_t value;
+ uint8_t rbuf[2] = {0};
+ rbuf[0] = spi_instr | READ_SINGLE_BYTE;
+ uint8_t len = 2;
+ digitalWrite(SPI_SS_PIN, LOW);
+ _platform.readWriteSpi(rbuf, len);
+ digitalWrite(SPI_SS_PIN, HIGH);
+ value = rbuf[1];
+ //printf("SPI_arr_0: 0x%02X\n", rbuf[0]);
+ //printf("SPI_arr_1: 0x%02X\n", rbuf[1]);
+ return value;
+}
+
+uint8_t RfPhysicalLayer::spiWriteStrobe(uint8_t spi_instr)
+{
+ uint8_t tbuf[1] = {0};
+ tbuf[0] = spi_instr;
+ //printf("SPI_data: 0x%02X\n", tbuf[0]);
+ digitalWrite(SPI_SS_PIN, LOW);
+ _platform.readWriteSpi(tbuf, 1);
+ digitalWrite(SPI_SS_PIN, HIGH);
+ return tbuf[0];
+}
+
+void RfPhysicalLayer::spiReadBurst(uint8_t spi_instr, uint8_t *pArr, uint8_t len)
+{
+ uint8_t rbuf[len + 1];
+ rbuf[0] = spi_instr | READ_BURST;
+ digitalWrite(SPI_SS_PIN, LOW);
+ _platform.readWriteSpi(rbuf, len + 1);
+ digitalWrite(SPI_SS_PIN, HIGH);
+ for (uint8_t i=0; i abort
+ if(version == 0x00 || version == 0xFF)
+ {
+ println("No CC11xx found!");
+ stopChip();
+ return false;
+ }
+
+ print("Partnumber: 0x");
+ println(partnum, HEX);
+ print("Version : 0x");
+ println(version, HEX);
+
+ // Set modulation mode 2FSK, 32768kbit/s
+ spiWriteBurst(WRITE_BURST,cc1101_2FSK_32_7_kb,CFG_REGISTER);
+
+ // Set PA table
+ spiWriteBurst(PATABLE_BURST, paTablePower868, 8);
+
+ // Set ISM band to 868.3MHz
+ spiWriteRegister(FREQ2,0x21);
+ spiWriteRegister(FREQ1,0x65);
+ spiWriteRegister(FREQ0,0x6A);
+
+ // Set channel 0 in ISM band
+ spiWriteRegister(CHANNR, 0);
+
+ // Set PA to 0dBm as default
+ setOutputPowerLevel(0);
+
+ return true;
+}
+
+void RfPhysicalLayer::stopChip()
+{
+ powerDownCC1101();
+
+ _platform.closeSpi();
+}
+
+void RfPhysicalLayer::showRegisterSettings()
+{
+ uint8_t config_reg_verify[CFG_REGISTER];
+ uint8_t Patable_verify[CFG_REGISTER];
+
+ spiReadBurst(READ_BURST,config_reg_verify,CFG_REGISTER); //reads all 47 config register from cc1101
+ spiReadBurst(PATABLE_BURST,Patable_verify,8); //reads output power settings from cc1101
+
+ println("Config Register:");
+ printHex("", config_reg_verify, CFG_REGISTER);
+
+ println("PaTable:");
+ printHex("", Patable_verify, 8);
+}
+
+uint16_t RfPhysicalLayer::packetSize (uint8_t lField)
+{
+ uint16_t nrBytes;
+ uint8_t nrBlocks;
+
+ // The 2 first blocks contains 25 bytes when excluding CRC and the L-field
+ // The other blocks contains 16 bytes when excluding the CRC-fields
+ // Less than 26 (15 + 10)
+ if ( lField < 26 )
+ nrBlocks = 2;
+ else
+ nrBlocks = (((lField - 26) / 16) + 3);
+
+ // Add all extra fields, excluding the CRC fields
+ nrBytes = lField + 1;
+
+ // Add the CRC fields, each block has 2 CRC bytes
+ nrBytes += (2 * nrBlocks);
+
+ return nrBytes;
+}
+
+void RfPhysicalLayer::loop()
+{
+ switch (_loopState)
+ {
+ case TX_START:
+ {
+ prevStatusGDO0 = 0;
+ prevStatusGDO2 = 0;
+ // Set sync word in TX mode
+ // The same sync word is used in RX mode, but we use it in different way here:
+ // Important: the TX FIFO must provide the last byte of the
+ // sync word
+ spiWriteRegister(SYNC1, 0x54);
+ spiWriteRegister(SYNC0, 0x76);
+ // Set TX FIFO threshold to 33 bytes
+ spiWriteRegister(FIFOTHR, 0x47);
+ // Set GDO2 to be TX FIFO threshold signal
+ spiWriteRegister(IOCFG2, 0x02);
+ // Set GDO0 to be packet transmitted signal
+ spiWriteRegister(IOCFG0, 0x06);
+ // Flush TX FIFO
+ spiWriteStrobe(SFTX);
+
+ _rfDataLinkLayer.loadNextTxFrame(&sendBuffer, &sendBufferLength);
+
+ // Calculate total number of bytes in the KNX RF packet from L-field
+ pktLen = packetSize(sendBuffer[0]);
+ // Check for valid length
+ if ((pktLen == 0) || (pktLen > 290))
+ {
+ println("TX packet length error!");
+ break;
+ }
+
+ // Manchester encoded data takes twice the space plus
+ // 1 byte for postamble and 1 byte (LSB) of the synchronization word
+ bytesLeft = (2 * pktLen) + 2;
+ // Last byte of synchronization word
+ buffer[0] = 0x96;
+ // Manchester encode packet
+ for (int i = 0; i < pktLen; i++)
+ {
+ manchEncode(&sendBuffer[i], &buffer[1 + i*2]);
+ }
+ // Append the postamble sequence
+ buffer[1 + bytesLeft - 1] = 0x55;
+
+ // Fill TX FIFO
+ pByteIndex = &buffer[0];
+ // Set fixed packet length mode if less than 256 bytes to transmit
+ if (bytesLeft < 256)
+ {
+ spiWriteRegister(PKTLEN, bytesLeft);
+ spiWriteRegister(PKTCTRL0, 0x00); // Set fixed pktlen mode
+ fixedLengthMode = true;
+ }
+ else // Else set infinite length mode
+ {
+ uint8_t fixedLength = bytesLeft % 256;
+ spiWriteRegister(PKTLEN, fixedLength);
+ spiWriteRegister(PKTCTRL0, 0x02);
+ fixedLengthMode = false;
+ }
+
+ uint8_t bytesToWrite = MIN(64, bytesLeft);
+ spiWriteBurst(TXFIFO_BURST, pByteIndex, bytesToWrite);
+ pByteIndex += bytesToWrite;
+ bytesLeft -= bytesToWrite;
+
+ // Enable transmission of packet
+ spiWriteStrobe(STX);
+
+ _loopState = TX_ACTIVE;
+ }
+ // Fall through
+
+ case TX_ACTIVE:
+ {
+ // Check if we have an incomplete packet transmission
+ if (syncStart && (millis() - packetStartTime > TX_PACKET_TIMEOUT))
+ {
+ println("TX packet timeout!");
+ // Set transceiver to IDLE (no RX or TX)
+ sIdle();
+ _loopState = TX_END;
+ break;
+ }
+
+ // Detect falling edge 1->0 on GDO2
+ statusGDO2 = digitalRead(GPIO_GDO2_PIN);
+ if(prevStatusGDO2 != statusGDO2)
+ {
+ prevStatusGDO2 = statusGDO2;
+
+ // Check if signal GDO2 is de-asserted (TX FIFO is below threshold of 33 bytes, i.e. TX FIFO is half full)
+ if(statusGDO2 == 0)
+ {
+ // - TX FIFO half full detected (< 33 bytes)
+ // Write data fragment to TX FIFO
+ uint8_t bytesToWrite = MIN(64, bytesLeft);
+ spiWriteBurst(TXFIFO_BURST, pByteIndex, bytesToWrite);
+ pByteIndex += bytesToWrite;
+ bytesLeft -= bytesToWrite;
+
+ // Set fixed length mode if less than 256 left to transmit
+ if ( (bytesLeft < (256 - 64)) && !fixedLengthMode )
+ {
+ spiWriteRegister(PKTCTRL0, 0x00); // Set fixed pktlen mode
+ fixedLengthMode = true;
+ }
+ }
+ }
+
+ // Detect falling edge 1->0 on GDO0
+ statusGDO0 = digitalRead(GPIO_GDO0_PIN);
+ if(prevStatusGDO0 != statusGDO0)
+ {
+ prevStatusGDO0 = statusGDO0;
+
+ // If GDO0 is de-asserted: TX packet complete or TX FIFO underflow
+ if (statusGDO0 == 0x00)
+ {
+ // There might be an TX FIFO underflow
+ uint8_t chipStatusBytes = spiWriteStrobe(SNOP);
+ if ((chipStatusBytes & CHIPSTATUS_STATE_BITMASK) == CHIPSTATUS_STATE_TX_UNDERFLOW)
+ {
+ println("TX FIFO underflow!");
+ // Set transceiver to IDLE (no RX or TX)
+ sIdle();
+ }
+ _loopState = TX_END;
+ }
+ else
+ {
+ // GDO0 asserted because sync word was transmitted
+ //println("TX Syncword!");
+ // wait for TX_PACKET_TIMEOUT milliseconds
+ // Complete packet must have been transmitted within this time
+ packetStartTime = millis();
+ syncStart = true;
+ }
+ }
+ }
+ break;
+
+ case TX_END:
+ {
+ // free buffer
+ delete sendBuffer;
+ // Go back to RX after TX
+ _loopState = RX_START;
+ }
+ break;
+
+ case RX_START:
+ {
+ prevStatusGDO2 = 0;
+ prevStatusGDO0 = 0;
+ syncStart = false;
+ packetStart = true;
+ fixedLengthMode = false;
+ pByteIndex = buffer;
+ bytesLeft = 0;
+ pktLen = 0;
+ // Set sync word in RX mode
+ // The same sync word is used in TX mode, but we use it in different way
+ spiWriteRegister(SYNC1, 0x76);
+ spiWriteRegister(SYNC0, 0x96);
+ // Set GDO2 to be RX FIFO threshold signal
+ spiWriteRegister(IOCFG2, 0x00);
+ // Set GDO0 to be packet received signal
+ spiWriteRegister(IOCFG0, 0x06);
+ // Set RX FIFO threshold to 4 bytes
+ spiWriteRegister(FIFOTHR, 0x40);
+ // Set infinite pktlen mode
+ spiWriteRegister(PKTCTRL0, 0x02);
+ // Flush RX FIFO
+ spiWriteStrobe(SFRX);
+ // Start RX
+ sReceive();
+ _loopState = RX_ACTIVE;
+ }
+ break;
+
+ case RX_ACTIVE:
+ {
+ if (!_rfDataLinkLayer.isTxQueueEmpty() && !syncStart)
+ {
+ sIdle();
+ _loopState = TX_START;
+ break;
+ }
+
+ // Check if we have an incomplete packet reception
+ // This is related to CC1101 errata "Radio stays in RX state instead of entering RXFIFO_OVERFLOW state"
+ if (syncStart && (millis() - packetStartTime > RX_PACKET_TIMEOUT))
+ {
+ println("RX packet timeout!");
+ //uint8_t marcState = (spiReadRegister(MARCSTATE) & MARCSTATE_BITMASK); //read out state of cc1101 to be sure in RX
+ //print("marcstate: 0x");
+ //println(marcState, HEX);
+ sIdle();
+ _loopState = RX_START;
+ break;
+ }
+
+ // Detect rising edge 0->1 on GDO2
+ statusGDO2 = digitalRead(GPIO_GDO2_PIN);
+ if(prevStatusGDO2 != statusGDO2)
+ {
+ prevStatusGDO2 = statusGDO2;
+
+ // Check if signal GDO2 is asserted (RX FIFO is equal to or above threshold of 4 bytes)
+ if(statusGDO2 == 1)
+ {
+ if (packetStart)
+ {
+ // - RX FIFO 4 bytes detected -
+ // Calculate the total length of the packet, and set fixed mode if less
+ // than 255 bytes to receive
+
+ // Read the 2 first bytes
+ spiReadBurst(RXFIFO_BURST, pByteIndex, 2);
+
+ // Decode the L-field
+ if (!manchDecode(&buffer[0], &packet[0]))
+ {
+ //println("Could not decode L-field: manchester code violation");
+ _loopState = RX_START;
+ break;
+ }
+ // Get bytes to receive from L-field, multiply by 2 because of manchester code
+ pktLen = 2 * packetSize(packet[0]);
+
+ // - Length mode -
+ if (pktLen < 256)
+ {
+ // Set fixed packet length mode is less than 256 bytes
+ spiWriteRegister(PKTLEN, pktLen);
+ spiWriteRegister(PKTCTRL0, 0x00); // Set fixed pktlen mode
+ fixedLengthMode = true;
+ }
+ else
+ {
+ // Infinite packet length mode is more than 255 bytes
+ // Calculate the PKTLEN value
+ uint8_t fixedLength = pktLen % 256;
+ spiWriteRegister(PKTLEN, fixedLength);
+ }
+
+ pByteIndex += 2;
+ bytesLeft = pktLen - 2;
+
+ // Set RX FIFO threshold to 32 bytes
+ packetStart = false;
+ spiWriteRegister(FIFOTHR, 0x47);
+ }
+ else
+ {
+ // - RX FIFO Half Full detected -
+ // Read out the RX FIFO and set fixed mode if less
+ // than 255 bytes to receive
+
+ // - Length mode -
+ // Set fixed packet length mode if less than 256 bytes
+ if ((bytesLeft < 256 ) && !fixedLengthMode)
+ {
+ spiWriteRegister(PKTCTRL0, 0x00); // Set fixed pktlen mode
+ fixedLengthMode = true;
+ }
+
+ // Read out the RX FIFO
+ // Do not empty the FIFO (See the CC110x or 2500 Errata Note)
+ spiReadBurst(RXFIFO_BURST, pByteIndex, 32 - 1);
+
+ bytesLeft -= (32 - 1);
+ pByteIndex += (32 - 1);
+ }
+
+ }
+ }
+
+ // Detect falling edge 1->0 on GDO0
+ statusGDO0 = digitalRead(GPIO_GDO0_PIN);
+ if(prevStatusGDO0 != statusGDO0)
+ {
+ prevStatusGDO0 = statusGDO0;
+
+ // If GDO0 is de-asserted: RX packet complete or RX FIFO overflow
+ if (statusGDO0 == 0x00)
+ {
+ // There might be an RX FIFO overflow
+ uint8_t chipStatusBytes = spiWriteStrobe(SNOP);
+ if ((chipStatusBytes & CHIPSTATUS_STATE_BITMASK) == CHIPSTATUS_STATE_RX_OVERFLOW)
+ {
+ println("RX FIFO overflow!");
+ _loopState = RX_START;
+ break;
+ }
+
+ // Check if we are in the middle of the packet reception
+ if (!packetStart)
+ {
+ // Complete packet received
+ // Read out remaining bytes in the RX FIFO
+ spiReadBurst(RXFIFO_BURST, pByteIndex, bytesLeft);
+ _loopState = RX_END;
+ }
+ }
+ else
+ {
+ // GDO0 asserted because sync word was received and recognized
+ //println("RX Syncword!");
+ // wait for RX_PACKET_TIMEOUT milliseconds
+ // Complete packet must have been received within this time
+ packetStartTime = millis();
+ syncStart = true;
+ }
+
+ }
+ }
+ break;
+
+ case RX_END:
+ {
+ uint16_t pLen = packetSize(packet[0]);
+ // Decode the first block (always 10 bytes + 2 bytes CRC)
+ bool decodeOk = true;
+ for (uint16_t i = 1; i < pLen; i++)
+ {
+ // Check for manchester violation, abort if there is one
+ if(!manchDecode(&buffer[i*2], &packet[i]))
+ {
+ println("Could not decode packet: manchester code violation");
+ decodeOk = false;
+ break;
+ }
+ }
+
+ if (decodeOk)
+ {
+ _rfDataLinkLayer.frameBytesReceived(&packet[0], pLen);
+ }
+
+ _loopState = RX_START;
+ }
+ break;
+ }
+}
+
+#endif // #if MEDIUM_TYPE == 2
diff --git a/src/knx/rf_physical_layer.h b/src/knx/rf_physical_layer.h
new file mode 100644
index 0000000..931985e
--- /dev/null
+++ b/src/knx/rf_physical_layer.h
@@ -0,0 +1,257 @@
+
+#ifndef RF_PHYSICAL_LAYER_H
+#define RF_PHYSICAL_LAYER_H
+
+#include
+
+#include "platform.h"
+
+/*----------------------------------[standard]--------------------------------*/
+#define CC1101_TIMEOUT 2000 // Time to wait for a response from CC1101
+
+#define RX_PACKET_TIMEOUT 20 // Wait 20ms for packet reception to complete
+#define TX_PACKET_TIMEOUT 20 // Wait 20ms for packet reception to complete
+
+//**************************** pins ******************************************//
+#ifdef ARDUINO_ARCH_SAMD
+#define SPI_SS_PIN 10
+#define GPIO_GDO2_PIN 9
+#define GPIO_GDO0_PIN 7
+#elif ARDUINO_ARCH_ESP8266
+#error KNX-RF not yet supported on ESP8266
+#elif ARDUINO_ARCH_ESP32
+#error KNX-RF not yet supported on ESP32
+#else // Linux Platform
+extern void delayMicroseconds (unsigned int howLong);
+#define SPI_SS_PIN 8 // GPIO 8 (SPI_CE0_N) -> WiringPi: 10 -> Pin number on header: 24
+#define GPIO_GDO2_PIN 25 // GPIO 25 (GPIO_GEN6) -> WiringPi: 6 -> Pin number on header: 22
+#define GPIO_GDO0_PIN 24 // GPIO 24 (GPIO_GEN5) -> WiringPi: 5 -> Pin number on header: 18
+#endif
+
+/*----------------------[CC1101 - misc]---------------------------------------*/
+#define CRYSTAL_FREQUENCY 26000000
+#define CFG_REGISTER 0x2F //47 registers
+#define FIFOBUFFER 0x42 //size of Fifo Buffer +2 for rssi and lqi
+#define RSSI_OFFSET_868MHZ 0x4E //dec = 74
+#define TX_RETRIES_MAX 0x05 //tx_retries_max
+#define ACK_TIMEOUT 250 //ACK timeout in ms
+#define CC1101_COMPARE_REGISTER 0x00 //register compare 0=no compare 1=compare
+#define BROADCAST_ADDRESS 0x00 //broadcast address
+#define CC1101_FREQ_315MHZ 0x01
+#define CC1101_FREQ_434MHZ 0x02
+#define CC1101_FREQ_868MHZ 0x03
+#define CC1101_FREQ_915MHZ 0x04
+#define CC1101_TEMP_ADC_MV 3.225 //3.3V/1023 . mV pro digit
+#define CC1101_TEMP_CELS_CO 2.47 //Temperature coefficient 2.47mV per Grad Celsius
+
+/*---------------------------[CC1101 - R/W offsets]---------------------------*/
+#define WRITE_SINGLE_BYTE 0x00
+#define WRITE_BURST 0x40
+#define READ_SINGLE_BYTE 0x80
+#define READ_BURST 0xC0
+/*---------------------------[END R/W offsets]--------------------------------*/
+
+/*------------------------[CC1101 - FIFO commands]----------------------------*/
+#define TXFIFO_BURST 0x7F //write burst only
+#define TXFIFO_SINGLE_BYTE 0x3F //write single only
+#define RXFIFO_BURST 0xFF //read burst only
+#define RXFIFO_SINGLE_BYTE 0xBF //read single only
+#define PATABLE_BURST 0x7E //power control read/write
+#define PATABLE_SINGLE_BYTE 0xFE //power control read/write
+/*---------------------------[END FIFO commands]------------------------------*/
+
+/*----------------------[CC1101 - config register]----------------------------*/
+#define IOCFG2 0x00 // GDO2 output pin configuration
+#define IOCFG1 0x01 // GDO1 output pin configuration
+#define IOCFG0 0x02 // GDO0 output pin configuration
+#define FIFOTHR 0x03 // RX FIFO and TX FIFO thresholds
+#define SYNC1 0x04 // Sync word, high byte
+#define SYNC0 0x05 // Sync word, low byte
+#define PKTLEN 0x06 // Packet length
+#define PKTCTRL1 0x07 // Packet automation control
+#define PKTCTRL0 0x08 // Packet automation control
+#define DADDR 0x09 // Device address
+#define CHANNR 0x0A // Channel number
+#define FSCTRL1 0x0B // Frequency synthesizer control
+#define FSCTRL0 0x0C // Frequency synthesizer control
+#define FREQ2 0x0D // Frequency control word, high byte
+#define FREQ1 0x0E // Frequency control word, middle byte
+#define FREQ0 0x0F // Frequency control word, low byte
+#define MDMCFG4 0x10 // Modem configuration
+#define MDMCFG3 0x11 // Modem configuration
+#define MDMCFG2 0x12 // Modem configuration
+#define MDMCFG1 0x13 // Modem configuration
+#define MDMCFG0 0x14 // Modem configuration
+#define DEVIATN 0x15 // Modem deviation setting
+#define MCSM2 0x16 // Main Radio Cntrl State Machine config
+#define MCSM1 0x17 // Main Radio Cntrl State Machine config
+#define MCSM0 0x18 // Main Radio Cntrl State Machine config
+#define FOCCFG 0x19 // Frequency Offset Compensation config
+#define BSCFG 0x1A // Bit Synchronization configuration
+#define AGCCTRL2 0x1B // AGC control
+#define AGCCTRL1 0x1C // AGC control
+#define AGCCTRL0 0x1D // AGC control
+#define WOREVT1 0x1E // High byte Event 0 timeout
+#define WOREVT0 0x1F // Low byte Event 0 timeout
+#define WORCTRL 0x20 // Wake On Radio control
+#define FREND1 0x21 // Front end RX configuration
+#define FREND0 0x22 // Front end TX configuration
+#define FSCAL3 0x23 // Frequency synthesizer calibration
+#define FSCAL2 0x24 // Frequency synthesizer calibration
+#define FSCAL1 0x25 // Frequency synthesizer calibration
+#define FSCAL0 0x26 // Frequency synthesizer calibration
+#define RCCTRL1 0x27 // RC oscillator configuration
+#define RCCTRL0 0x28 // RC oscillator configuration
+#define FSTEST 0x29 // Frequency synthesizer cal control
+#define PTEST 0x2A // Production test
+#define AGCTEST 0x2B // AGC test
+#define TEST2 0x2C // Various test settings
+#define TEST1 0x2D // Various test settings
+#define TEST0 0x2E // Various test settings
+/*-------------------------[END config register]------------------------------*/
+
+/*------------------------[CC1101-command strobes]----------------------------*/
+#define SRES 0x30 // Reset chip
+#define SFSTXON 0x31 // Enable/calibrate freq synthesizer
+#define SXOFF 0x32 // Turn off crystal oscillator.
+#define SCAL 0x33 // Calibrate freq synthesizer & disable
+#define SRX 0x34 // Enable RX.
+#define STX 0x35 // Enable TX.
+#define SIDLE 0x36 // Exit RX / TX
+#define SAFC 0x37 // AFC adjustment of freq synthesizer
+#define SWOR 0x38 // Start automatic RX polling sequence
+#define SPWD 0x39 // Enter pwr down mode when CSn goes hi
+#define SFRX 0x3A // Flush the RX FIFO buffer.
+#define SFTX 0x3B // Flush the TX FIFO buffer.
+#define SWORRST 0x3C // Reset real time clock.
+#define SNOP 0x3D // No operation.
+/*-------------------------[END command strobes]------------------------------*/
+
+/*----------------------[CC1101 - status register]----------------------------*/
+#define PARTNUM 0xF0 // Part number
+#define VERSION 0xF1 // Current version number
+#define FREQEST 0xF2 // Frequency offset estimate
+#define LQI 0xF3 // Demodulator estimate for link quality
+#define RSSI 0xF4 // Received signal strength indication
+#define MARCSTATE 0xF5 // Control state machine state
+#define WORTIME1 0xF6 // High byte of WOR timer
+#define WORTIME0 0xF7 // Low byte of WOR timer
+#define PKTSTATUS 0xF8 // Current GDOx status and packet status
+#define VCO_VC_DAC 0xF9 // Current setting from PLL cal module
+#define TXBYTES 0xFA // Underflow and # of bytes in TXFIFO
+#define RXBYTES 0xFB // Overflow and # of bytes in RXFIFO
+#define RCCTRL1_STATUS 0xFC //Last RC Oscillator Calibration Result
+#define RCCTRL0_STATUS 0xFD //Last RC Oscillator Calibration Result
+//--------------------------[END status register]-------------------------------
+
+/*----------------------[CC1101 - Main Radio Control State Machine states]-----*/
+#define MARCSTATE_BITMASK 0x1F
+#define MARCSTATE_SLEEP 0x00
+#define MARCSTATE_IDLE 0x01
+#define MARCSTATE_XOFF 0x02
+#define MARCSTATE_VCOON_MC 0x03
+#define MARCSTATE_REGON_MC 0x04
+#define MARCSTATE_MANCAL 0x05
+#define MARCSTATE_VCOON 0x06
+#define MARCSTATE_REGON 0x07
+#define MARCSTATE_STARTCAL 0x08
+#define MARCSTATE_BWBOOST 0x09
+#define MARCSTATE_FS_LOCK 0x0A
+#define MARCSTATE_IFADCON 0x0B
+#define MARCSTATE_ENDCAL 0x0C
+#define MARCSTATE_RX 0x0D
+#define MARCSTATE_RX_END 0x0E
+#define MARCSTATE_RX_RST 0x0F
+#define MARCSTATE_TXRX_SWITCH 0x10
+#define MARCSTATE_RXFIFO_OVERFLOW 0x11
+#define MARCSTATE_FSTXON 0x12
+#define MARCSTATE_TX 0x13
+#define MARCSTATE_TX_END 0x14
+#define MARCSTATE_RXTX_SWITCH 0x15
+#define MARCSTATE_TXFIFO_UNDERFLOW 0x16
+
+// Chip Status Byte
+// Bit fields in the chip status byte
+#define CHIPSTATUS_CHIP_RDYn_BITMASK 0x80
+#define CHIPSTATUS_STATE_BITMASK 0x70
+#define CHIPSTATUS_FIFO_BYTES_AVAILABLE_BITMASK 0x0F
+// Chip states
+ #define CHIPSTATUS_STATE_IDLE 0x00
+ #define CHIPSTATUS_STATE_RX 0x10
+ #define CHIPSTATUS_STATE_TX 0x20
+ #define CHIPSTATUS_STATE_FSTXON 0x30
+ #define CHIPSTATUS_STATE_CALIBRATE 0x40
+ #define CHIPSTATUS_STATE_SETTLING 0x50
+ #define CHIPSTATUS_STATE_RX_OVERFLOW 0x60
+ #define CHIPSTATUS_STATE_TX_UNDERFLOW 0x70
+
+// loop states
+#define RX_START 0
+#define RX_ACTIVE 1
+#define RX_END 2
+#define TX_START 3
+#define TX_ACTIVE 4
+#define TX_END 5
+
+class RfDataLinkLayer;
+
+class RfPhysicalLayer
+{
+ public:
+ RfPhysicalLayer(RfDataLinkLayer& rfDataLinkLayer, Platform& platform);
+
+ bool InitChip();
+ void showRegisterSettings();
+ void stopChip();
+ void loop();
+
+ private:
+ // Table for encoding 4-bit data into a 8-bit Manchester encoding.
+ static const uint8_t manchEncodeTab[16];
+ // Table for decoding 4-bit Manchester encoded data into 2-bit
+ static const uint8_t manchDecodeTab[16];
+
+ static const uint8_t cc1101_2FSK_32_7_kb[CFG_REGISTER];
+ static const uint8_t paTablePower868[8];
+
+ void manchEncode(uint8_t *uncodedData, uint8_t *encodedData);
+ bool manchDecode(uint8_t *encodedData, uint8_t *decodedData);
+
+ int crc16(uint8_t* buffer, int offset, int length);
+ void powerDownCC1101();
+ void setOutputPowerLevel(int8_t dBm);
+
+ uint16_t packetSize (uint8_t lField);
+
+ uint8_t sIdle();
+ uint8_t sReceive();
+
+ void spiWriteRegister(uint8_t spi_instr, uint8_t value);
+ uint8_t spiReadRegister(uint8_t spi_instr);
+ uint8_t spiWriteStrobe(uint8_t spi_instr);
+ void spiReadBurst(uint8_t spi_instr, uint8_t *pArr, uint8_t len);
+ void spiWriteBurst(uint8_t spi_instr, const uint8_t *pArr, uint8_t len);
+
+ uint8_t _loopState = RX_START;
+
+ bool syncStart = false;
+ bool packetStart = true;
+ bool fixedLengthMode = false;
+ uint8_t *sendBuffer {0};
+ uint16_t sendBufferLength {0};
+ uint8_t packet[512];
+ uint8_t buffer[sizeof(packet)*2]; // We need twice the space due to manchester encoding
+ uint8_t* pByteIndex = &buffer[0];
+ uint16_t pktLen {0};
+ uint16_t bytesLeft = {0};
+ uint8_t statusGDO0 {0};
+ uint8_t statusGDO2 {0};
+ uint8_t prevStatusGDO0 {0}; // for edge detection during polling
+ uint8_t prevStatusGDO2 {0}; // for edge detection during polling
+ uint32_t packetStartTime {0};
+
+ RfDataLinkLayer& _rfDataLinkLayer;
+ Platform& _platform;
+};
+
+#endif
diff --git a/src/knx_facade.cpp b/src/knx_facade.cpp
index 2b6d1cb..45cdbcc 100644
--- a/src/knx_facade.cpp
+++ b/src/knx_facade.cpp
@@ -3,15 +3,28 @@
#include "knx/bits.h"
#ifdef ARDUINO_ARCH_SAMD
-KnxFacade knx;
-#define ICACHE_RAM_ATTR
+ // predefined global instance for TP or RF
+ #ifdef MEDIUM_TYPE
+ #if MEDIUM_TYPE == 0
+ KnxFacade knx;
+ #elif MEDIUM_TYPE == 2
+ KnxFacade knx;
+ #else
+ #error "Only TP and RF supported for Arduino SAMD platform!"
+ #endif
+ #else
+ #error "No medium type specified for platform Arduino_SAMD! Please set MEDIUM_TYPE! (TP:0, RF:2, IP:5)"
+ #endif
+ #define ICACHE_RAM_ATTR
#elif ARDUINO_ARCH_ESP8266
-KnxFacade knx;
+ // predefined global instance for IP only
+ KnxFacade knx;
#elif ARDUINO_ARCH_ESP32
-//KnxFacade knx;
-KnxFacade knx;
+ // predefined global instance for IP only
+ KnxFacade knx;
#elif __linux__
-#define ICACHE_RAM_ATTR
+ // no predefined global instance
+ #define ICACHE_RAM_ATTR
#endif
#ifndef __linux__
diff --git a/src/knx_facade.h b/src/knx_facade.h
index 5328ac9..ab4c0a4 100644
--- a/src/knx_facade.h
+++ b/src/knx_facade.h
@@ -2,20 +2,27 @@
#include "knx/bits.h"
+// Set default medium type to TP if no external definitions was given
+#ifndef MEDIUM_TYPE
+#define MEDIUM_TYPE 0
+#endif
+
#ifdef ARDUINO_ARCH_SAMD
-#include "samd_platform.h"
-#include "knx/bau07B0.h"
+ #include "samd_platform.h"
+ #include "knx/bau07B0.h"
+ #include "knx/bau27B0.h"
#elif ARDUINO_ARCH_ESP8266
-#include "esp_platform.h"
-#include "knx/bau57B0.h"
+ #include "esp_platform.h"
+ #include "knx/bau57B0.h"
#elif ARDUINO_ARCH_ESP32
-#define LED_BUILTIN 13
-#include "esp32_platform.h"
-#include "knx/bau57B0.h"
+ #define LED_BUILTIN 13
+ #include "esp32_platform.h"
+ #include "knx/bau57B0.h"
#else
-#include "linux_platform.h"
-#include "knx/bau57B0.h"
-#define LED_BUILTIN 0
+ #define LED_BUILTIN 0
+ #include "linux_platform.h"
+ #include "knx/bau57B0.h"
+ #include "knx/bau27B0.h"
#endif
void buttonUp();
@@ -297,11 +304,24 @@ template class KnxFacade : private SaveRestore
};
#ifdef ARDUINO_ARCH_SAMD
-extern KnxFacade knx;
+ // predefined global instance for TP or RF
+ #ifdef MEDIUM_TYPE
+ #if MEDIUM_TYPE == 0
+ extern KnxFacade knx;
+ #elif MEDIUM_TYPE == 2
+ extern KnxFacade knx;
+ #else
+ #error "Only TP and RF supported for Arduino SAMD platform!"
+ #endif
+ #else
+ #error "No medium type specified for Arduino_SAMD platform! Please set MEDIUM_TYPE! (TP:0, RF:2, IP:5)"
+ #endif
#elif ARDUINO_ARCH_ESP8266
-extern KnxFacade knx;
+ // predefined global instance for IP only
+ extern KnxFacade knx;
#elif ARDUINO_ARCH_ESP32
-extern KnxFacade knx;
+ // predefined global instance for IP only
+ extern KnxFacade knx;
#elif __linux__
-// no predefined global instance
-#endif
\ No newline at end of file
+ // no predefined global instance
+#endif
diff --git a/src/linux_platform.cpp b/src/linux_platform.cpp
index 8445381..e7f8b4a 100644
--- a/src/linux_platform.cpp
+++ b/src/linux_platform.cpp
@@ -19,6 +19,11 @@
#include
#include
+#include // Needed for SPI port
+#include // Needed for SPI port
+#include // Needed for GPIO edge detection
+#include // Needed for delayMicroseconds()
+
#include "knx/device_object.h"
#include "knx/address_table_object.h"
#include "knx/association_table_object.h"
@@ -299,6 +304,74 @@ void LinuxPlatform::setupUart()
{
}
+void LinuxPlatform::closeSpi()
+{
+ close(_spiFd);
+ printf ("SPI device closed.\r\n");
+}
+
+int LinuxPlatform::readWriteSpi (uint8_t *data, size_t len)
+{
+ uint16_t spiDelay = 0 ;
+ uint32_t spiSpeed = 8000000; // 4 MHz SPI speed
+ uint8_t spiBPW = 8; // Bits per word
+
+ struct spi_ioc_transfer spi ;
+
+ // Mentioned in spidev.h but not used in the original kernel documentation
+ // test program )-:
+
+ memset (&spi, 0, sizeof (spi)) ;
+
+ spi.tx_buf = (uint64_t)data;
+ spi.rx_buf = (uint64_t)data;
+ spi.len = len;
+ spi.delay_usecs = spiDelay;
+ spi.speed_hz = spiSpeed;
+ spi.bits_per_word = spiBPW;
+
+ return ioctl (_spiFd, SPI_IOC_MESSAGE(1), &spi) ;
+}
+
+void LinuxPlatform::setupSpi()
+{
+ if ((_spiFd = open ("/dev/spidev0.0", O_RDWR)) < 0)
+ {
+ printf ("ERROR: SPI setup failed! Could not open SPI device!\r\n");
+ return;
+ }
+
+ // Set SPI parameters.
+ int mode = 0; // Mode 0
+ uint8_t spiBPW = 8; // Bits per word
+ int speed = 8000000; // 4 MHz SPI speed
+
+ if (ioctl (_spiFd, SPI_IOC_WR_MODE, &mode) < 0)
+ {
+ printf ("ERROR: SPI Mode Change failure: %s\n", strerror (errno)) ;
+ close(_spiFd);
+ return;
+ }
+
+ if (ioctl (_spiFd, SPI_IOC_WR_BITS_PER_WORD, &spiBPW) < 0)
+ {
+ printf ("ERROR: SPI BPW Change failure: %s\n", strerror (errno)) ;
+ close(_spiFd);
+ return;
+ }
+
+ if (ioctl (_spiFd, SPI_IOC_WR_MAX_SPEED_HZ, &speed) < 0)
+ {
+ printf ("ERROR: SPI Speed Change failure: %s\n", strerror (errno)) ;
+ close(_spiFd);
+ return;
+ }
+
+ printf ("SPI device setup ok.\r\n");
+
+
+}
+
/*
* On linux the memory addresses from malloc may be to big for usermermory_write.
* So we allocate some memory at the beginning and use it for address table, group object table etc.
@@ -499,10 +572,18 @@ void println(void)
void pinMode(uint32_t dwPin, uint32_t dwMode)
{
+ gpio_export(dwPin);
+ gpio_direction(dwPin, dwMode);
}
void digitalWrite(uint32_t dwPin, uint32_t dwVal)
{
+ gpio_write(dwPin, dwVal);
+}
+
+uint32_t digitalRead(uint32_t dwPin)
+{
+ return gpio_read(dwPin);
}
typedef void (*voidFuncPtr)(void);
@@ -519,4 +600,315 @@ void LinuxPlatform::cmdLineArgs(int argc, char** argv)
memcpy(_args, argv, argc * sizeof(char*));
_args[argc] = 0;
}
-#endif
\ No newline at end of file
+
+/* Buffer size for string operations (e.g. snprintf())*/
+#define MAX_STRBUF_SIZE 100
+#define MAX_NUM_GPIO 64
+
+static int gpioFds [MAX_NUM_GPIO] =
+{
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+} ;
+
+/* Activate GPIO-Pin
+ * Write GPIO pin number to /sys/class/gpio/export
+ * Result: 0 = success, -1 = error
+ */
+int gpio_export(int pin)
+{
+ char buffer[MAX_STRBUF_SIZE]; /* Output Buffer */
+ ssize_t bytes; /* Used Buffer length */
+ int fd; /* Filedescriptor */
+ int res; /* Result from write() */
+
+ fprintf(stderr, "Export GPIO pin %d\n", pin);
+
+ fd = open("/sys/class/gpio/export", O_WRONLY);
+ if (fd < 0)
+ {
+ perror("Could not export GPIO pin(open)!\n");
+ return(-1);
+ }
+
+ bytes = snprintf(buffer, MAX_STRBUF_SIZE, "%d", pin);
+ res = write(fd, buffer, bytes);
+
+ if (res < 0)
+ {
+ perror("Could not export GPIO pin(write)!\n");
+ return(-1);
+ }
+
+ close(fd);
+ delay(100);
+
+ return(0);
+}
+
+/* Deactivate GPIO pin
+ * Write GPIO pin number to /sys/class/gpio/unexport
+ * Result: 0 = success, -1 = error
+ */
+int gpio_unexport(int pin)
+{
+ char buffer[MAX_STRBUF_SIZE]; /* Output Buffer */
+ ssize_t bytes; /* Used Buffer length */
+ int fd; /* Filedescriptor */
+ int res; /* Result from write() */
+
+ fprintf(stderr, "Unexport GPIO pin %d\n", pin);
+
+ close(gpioFds[pin]);
+
+ fd = open("/sys/class/gpio/unexport", O_WRONLY);
+ if (fd < 0)
+ {
+ perror("Could not unexport GPIO pin(open)!\n");
+ return(-1);
+ }
+
+ bytes = snprintf(buffer, MAX_STRBUF_SIZE, "%d", pin);
+ res = write(fd, buffer, bytes);
+
+ if (res < 0)
+ {
+ perror("Could not unexport GPIO pin(write)!\n");
+ return(-1);
+ }
+
+ close(fd);
+ return(0);
+}
+
+/* Set GPIO pin mode (input/output)
+ * Write GPIO pin number to /sys/class/gpioXX/direction
+ * Direction: 0 = input, 1 = output
+ * Result: 0 = success, -1 = error
+ */
+int gpio_direction(int pin, int dir)
+{
+ char path[MAX_STRBUF_SIZE]; /* Buffer for path */
+ int fd; /* Filedescriptor */
+ int res; /* Result from write() */
+
+ fprintf(stderr, "Set GPIO direction for pin %d to %s\n", pin, (dir==INPUT) ? "INPUT":"OUTPUT");
+
+ snprintf(path, MAX_STRBUF_SIZE, "/sys/class/gpio/gpio%d/direction", pin);
+ fd = open(path, O_WRONLY);
+ if (fd < 0)
+ {
+ perror("Could not set mode for GPIO pin(open)!\n");
+ return(-1);
+ }
+
+ switch (dir)
+ {
+ case INPUT : res = write(fd,"in",2); break;
+ case OUTPUT: res = write(fd,"out",3); break;
+ default: res = -1; break;
+ }
+
+ if (res < 0)
+ {
+ perror("Could not set mode for GPIO pin(write)!\n");
+ return(-1);
+ }
+
+ close(fd);
+ return(0);
+}
+
+/* Read from GPIO pin
+ * Result: -1 = error, 0/1 = GPIO pin state
+ */
+int gpio_read(int pin)
+{
+ char path[MAX_STRBUF_SIZE]; /* Buffer for path */
+ char c;
+
+ snprintf(path, MAX_STRBUF_SIZE, "/sys/class/gpio/gpio%d/value", pin);
+ if (gpioFds[pin] < 0)
+ gpioFds[pin] = open(path, O_RDWR);
+ if (gpioFds[pin] < 0)
+ {
+ perror("Could not read from GPIO(open)!\n");
+ return(-1);
+ }
+
+ lseek(gpioFds [pin], 0L, SEEK_SET) ;
+ if (read(gpioFds[pin], &c, 1) < 0)
+ {
+ perror("Could not read from GPIO(read)!\n");
+ return(-1);
+ }
+
+ return (c == '0') ? LOW : HIGH;
+}
+
+/* Write to GPIO pin
+ * Result: -1 = error, 0 = success
+ */
+int gpio_write(int pin, int value)
+{
+ char path[MAX_STRBUF_SIZE]; /* Buffer for path */
+ int res; /* Result from write()*/
+
+ snprintf(path, MAX_STRBUF_SIZE, "/sys/class/gpio/gpio%d/value", pin);
+ if (gpioFds[pin] < 0)
+ gpioFds[pin] = open(path, O_RDWR);
+
+ if (gpioFds[pin] < 0)
+ {
+ perror("Could not write to GPIO(open)!\n");
+ return(-1);
+ }
+
+ switch (value)
+ {
+ case LOW : res = write(gpioFds[pin], "0\n", 2); break;
+ case HIGH: res = write(gpioFds[pin], "1\n", 2); break;
+ default: res = -1; break;
+ }
+
+ if (res < 0)
+ {
+ perror("Could not write to GPIO(write)!\n");
+ return(-1);
+ }
+
+ return(0);
+}
+
+/* Set GPIO pin edge detection
+ * 'r' (rising)
+ * 'f' (falling)
+ * 'b' (both)
+ */
+int gpio_edge(unsigned int pin, char edge)
+{
+ char path[MAX_STRBUF_SIZE]; /* Buffer for path */
+ int fd; /* Filedescriptor */
+
+ snprintf(path, MAX_STRBUF_SIZE, "/sys/class/gpio/gpio%d/edge", pin);
+
+ fd = open(path, O_WRONLY | O_NONBLOCK );
+ if (fd < 0)
+ {
+ perror("Could not set GPIO edge detection(open)!\n");
+ return(-1);
+ }
+
+ switch (edge)
+ {
+ case 'r': strncpy(path,"rising",8); break;
+ case 'f': strncpy(path,"falling",8); break;
+ case 'b': strncpy(path,"both",8); break;
+ case 'n': strncpy(path,"none",8); break;
+ default: close(fd);return(-2);
+ }
+
+ write(fd, path, strlen(path) + 1);
+
+ close(fd);
+ return 0;
+}
+
+/* Wait for edge on GPIO pin
+ * timeout in milliseconds
+ * Result: <0: error, 0: poll() Timeout,
+ * 1: edge detected, GPIO pin reads "0"
+ * 2: edge detected, GPIO pin reads "1"
+ */
+int gpio_wait(unsigned int pin, int timeout)
+{
+ char path[MAX_STRBUF_SIZE]; /* Buffer for path */
+ int fd; /* Filedescriptor */
+ struct pollfd polldat[1]; /* Variable for poll() */
+ char buf[MAX_STRBUF_SIZE]; /* Read buffer */
+ int rc; /* Result */
+
+ /* Open GPIO pin */
+ snprintf(path, MAX_STRBUF_SIZE, "/sys/class/gpio/gpio%d/value", pin);
+ fd = open(path, O_RDONLY | O_NONBLOCK );
+ if (fd < 0)
+ {
+ perror("Could not wait for GPIO edge(open)!\n");
+ return(-1);
+ }
+
+ /* prepare poll() */
+ memset((void*)buf, 0, sizeof(buf));
+ memset((void*)polldat, 0, sizeof(polldat));
+ polldat[0].fd = fd;
+ polldat[0].events = POLLPRI;
+
+ /* clear any existing detected edges before */
+ lseek(fd, 0, SEEK_SET);
+ rc = read(fd, buf, MAX_STRBUF_SIZE - 1);
+
+ rc = poll(polldat, 1, timeout);
+ if (rc < 0)
+ { /* poll() failed! */
+ perror("Could not wait for GPIO edge(poll)!\n");
+ close(fd);
+ return(-1);
+ }
+
+ if (rc == 0)
+ { /* poll() timeout! */
+ close(fd);
+ return(0);
+ }
+
+ if (polldat[0].revents & POLLPRI)
+ {
+ if (rc < 0)
+ { /* read() failed! */
+ perror("Could not wait for GPIO edge(read)!\n");
+ close(fd);
+ return(-2);
+ }
+ /* printf("poll() GPIO %d interrupt occurred: %s\n", pin, buf); */
+ close(fd);
+ return(1 + atoi(buf));
+ }
+
+ close(fd);
+ return(-1);
+}
+
+void delayMicrosecondsHard (unsigned int howLong)
+{
+ struct timeval tNow, tLong, tEnd ;
+
+ gettimeofday (&tNow, NULL) ;
+ tLong.tv_sec = howLong / 1000000 ;
+ tLong.tv_usec = howLong % 1000000 ;
+ timeradd (&tNow, &tLong, &tEnd) ;
+
+ while (timercmp (&tNow, &tEnd, <))
+ gettimeofday (&tNow, NULL) ;
+}
+
+void delayMicroseconds (unsigned int howLong)
+{
+ struct timespec sleeper ;
+ unsigned int uSecs = howLong % 1000000 ;
+ unsigned int wSecs = howLong / 1000000 ;
+
+ /**/ if (howLong == 0)
+ return ;
+ else if (howLong < 100)
+ delayMicrosecondsHard (howLong) ;
+ else
+ {
+ sleeper.tv_sec = wSecs ;
+ sleeper.tv_nsec = (long)(uSecs * 1000L) ;
+ nanosleep (&sleeper, NULL) ;
+ }
+}
+
+#endif
diff --git a/src/linux_platform.h b/src/linux_platform.h
index 722c537..a5605cd 100644
--- a/src/linux_platform.h
+++ b/src/linux_platform.h
@@ -5,6 +5,12 @@
#include
#include "knx/platform.h"
+extern int gpio_direction(int pin, int dir);
+extern int gpio_read(int pin);
+extern int gpio_write(int pin, int value);
+extern int gpio_export(int pin);
+extern int gpio_unexport(int pin);
+
class LinuxPlatform: public Platform
{
using Platform::_memoryReference;
@@ -43,6 +49,11 @@ public:
int readUart() override;
size_t readBytesUart(uint8_t *buffer, size_t length) override;
+ //spi
+ void setupSpi() override;
+ void closeSpi() override;
+ int readWriteSpi (uint8_t *data, size_t len) override;
+
//memory
uint8_t* getEepromBuffer(uint16_t size) override;
void commitToEeprom() override;
@@ -57,6 +68,7 @@ public:
void doMemoryMapping();
uint8_t* _mappedFile = 0;
int _fd = -1;
+ int _spiFd = -1;
uint8_t* _currentMaxMem = 0;
std::string _flashFilePath = "flash.bin";
char** _args = 0;