diff --git a/src/knx/bau27B0.cpp b/src/knx/bau27B0.cpp index 3e1b548..3e66890 100644 --- a/src/knx/bau27B0.cpp +++ b/src/knx/bau27B0.cpp @@ -51,6 +51,10 @@ InterfaceObject* Bau27B0::getInterfaceObject(uint8_t idx) return nullptr; case 6: return &_rfMediumObj; +#ifdef USE_USBIF + case 7: + return &_cemiServerObject; +#endif default: return nullptr; } diff --git a/src/knx/bau27B0.h b/src/knx/bau27B0.h index b2c129e..544409d 100644 --- a/src/knx/bau27B0.h +++ b/src/knx/bau27B0.h @@ -20,8 +20,13 @@ class Bau27B0 : public BauSystemB RfMediumObject _rfMediumObj; uint8_t _descriptor[2] = {0x27, 0xB0}; +#ifdef USE_USBIF + const uint32_t _ifObjs[8] = { 7, // length + OT_DEVICE, OT_ADDR_TABLE, OT_ASSOC_TABLE, OT_GRP_OBJ_TABLE, OT_APPLICATION_PROG, OT_RF_MEDIUM, OT_CEMI_SERVER}; +#else 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}; +#endif void domainAddressSerialNumberWriteIndication(Priority priority, HopCountType hopType, uint8_t* rfDoA, uint8_t* knxSerialNumber); diff --git a/src/knx/bau_systemB.cpp b/src/knx/bau_systemB.cpp index 9840c0d..b8dd785 100644 --- a/src/knx/bau_systemB.cpp +++ b/src/knx/bau_systemB.cpp @@ -15,6 +15,10 @@ BauSystemB::BauSystemB(Platform& platform): _memory(platform), _addrTable(platfo _assocTable(platform), _groupObjTable(platform), _appProgram(platform), _platform(platform), _appLayer(_assocTable, *this), _transLayer(_appLayer, _addrTable), _netLayer(_transLayer) +#ifdef USE_USBIF + , _dlLayerUsb(_deviceObj, _addrTable, _netLayer, _platform), + _cemiServer(_dlLayerUsb, *this) +#endif { _appLayer.transportLayer(_transLayer); _transLayer.networkLayer(_netLayer); @@ -23,11 +27,17 @@ BauSystemB::BauSystemB(Platform& platform): _memory(platform), _addrTable(platfo _memory.addSaveRestore(&_addrTable); _memory.addSaveRestore(&_assocTable); _memory.addSaveRestore(&_groupObjTable); +#ifdef USE_USBIF + _memory.addSaveRestore(&_cemiServerObject); +#endif } void BauSystemB::loop() { dataLinkLayer().loop(); +#ifdef USE_USBIF + _dlLayerUsb.loop(); +#endif _transLayer.loop(); sendNextGroupTelegram(); nextRestartState(); @@ -41,6 +51,9 @@ bool BauSystemB::enabled() void BauSystemB::enabled(bool value) { dataLinkLayer().enabled(value); +#ifdef USE_USBIF + _dlLayerUsb.enabled(value); +#endif } void BauSystemB::sendNextGroupTelegram() diff --git a/src/knx/bau_systemB.h b/src/knx/bau_systemB.h index 5ca0ff5..6b65899 100644 --- a/src/knx/bau_systemB.h +++ b/src/knx/bau_systemB.h @@ -12,6 +12,11 @@ #include "tpuart_data_link_layer.h" #include "platform.h" #include "memory.h" +#ifdef USE_USBIF +#include "usb_data_link_layer.h" +#include "cemi_server.h" +#include "cemi_server_object.h" +#endif class BauSystemB : protected BusAccessUnit { @@ -88,4 +93,9 @@ class BauSystemB : protected BusAccessUnit bool _configured = true; RestartState _restartState = Idle; uint32_t _restartDelay = 0; +#ifdef USE_USBIF + UsbDataLinkLayer _dlLayerUsb; + CemiServer _cemiServer; + CemiServerObject _cemiServerObject; +#endif }; \ No newline at end of file diff --git a/src/knx/cemi_server.cpp b/src/knx/cemi_server.cpp new file mode 100644 index 0000000..2333525 --- /dev/null +++ b/src/knx/cemi_server.cpp @@ -0,0 +1,178 @@ +#include "cemi_server.h" +#include "cemi_frame.h" +#include "bau.h" +#include "usb_data_link_layer.h" +#include "string.h" +#include "bits.h" +#include + +CemiServer::CemiServer(UsbDataLinkLayer& dlLayer, BusAccessUnit& bau): + _dlLayer(dlLayer), + _bau(bau) +{ +} + +#pragma region DLL Callbacks + +void CemiServer::propertyValueReadRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, + uint8_t objectIndex, uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex) +{ + CemiFrame frame(5); + APDU& apdu = frame.apdu(); + apdu.type(PropertyValueRead); + uint8_t* data = apdu.data(); + data += 1; + data = pushByte(objectIndex, data); + data = pushByte(propertyId, data); + pushWord(startIndex & 0xfff, data); + *data &= ((numberOfElements & 0xf) << 4); + + individualSend(ack, hopType, priority, asap, apdu); +} + +void CemiServer::propertyValueReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, + uint8_t objectIndex, uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t* data, uint8_t length) +{ + propertyDataSend(PropertyValueResponse, ack, priority, hopType, asap, objectIndex, propertyId, numberOfElements, + startIndex, data, length); +} + +void CemiServer::propertyValueWriteRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, + uint8_t objectIndex, uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t * data, uint8_t length) +{ + propertyDataSend(PropertyValueWrite, ack, priority, hopType, asap, objectIndex, propertyId, numberOfElements, + startIndex, data, length); +} + +void CemiServer::propertyDescriptionReadRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, + uint8_t objectIndex, uint8_t propertyId, uint8_t propertyIndex) +{ + CemiFrame frame(4); + APDU& apdu = frame.apdu(); + apdu.type(PropertyDescriptionRead); + uint8_t* data = apdu.data(); + data[1] = objectIndex; + data[2] = propertyId; + data[3] = propertyIndex; + individualSend(ack, hopType, priority, asap, apdu); +} + +void CemiServer::propertyDescriptionReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, + uint8_t objectIndex, uint8_t propertyId, uint8_t propertyIndex, bool writeEnable, uint8_t type, + uint16_t maxNumberOfElements, uint8_t access) +{ + CemiFrame frame(8); + APDU& apdu = frame.apdu(); + apdu.type(PropertyDescriptionResponse); + uint8_t* data = apdu.data(); + data[1] = objectIndex; + data[2] = propertyId; + data[3] = propertyIndex; + if (writeEnable) + data[4] |= 0x80; + data[4] |= (type & 0x3f); + pushWord(maxNumberOfElements & 0xfff, data + 5); + data[7] = access; + individualSend(ack, hopType, priority, asap, apdu); +} + + +void CemiServer::propertyDataSend(ApduType type, AckType ack, Priority priority, HopCountType hopType, uint16_t asap, + uint8_t objectIndex, uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t* data, uint8_t length) +{ + CemiFrame frame(5 + length); + APDU& apdu = frame.apdu(); + apdu.type(type); + uint8_t* apduData = apdu.data(); + apduData += 1; + apduData = pushByte(objectIndex, apduData); + apduData = pushByte(propertyId, apduData); + pushWord(startIndex & 0xfff, apduData); + *apduData |= ((numberOfElements & 0xf) << 4); + apduData += 2; + if (length > 0) + memcpy(apduData, data, length); +} + +void CemiServer::individualIndication(HopCountType hopType, Priority priority, uint16_t tsap, APDU & apdu) +{ + uint8_t* data = apdu.data(); + switch (apdu.type()) + { + case PropertyValueRead: + { + uint16_t startIndex; + popWord(startIndex, data + 3); + startIndex &= 0xfff; + _bau.propertyValueReadIndication(priority, hopType, tsap, data[1], data[2], data[3] >> 4, startIndex); + break; + } + case PropertyValueResponse: + { + uint16_t startIndex; + popWord(startIndex, data + 3); + startIndex &= 0xfff; + _bau.propertyValueReadAppLayerConfirm(priority, hopType, tsap, data[1], data[2], data[3] >> 4, + startIndex, data + 5, apdu.length() - 5); + break; + } + case PropertyValueWrite: + { + uint16_t startIndex; + popWord(startIndex, data + 3); + startIndex &= 0xfff; + _bau.propertyValueWriteIndication(priority, hopType, tsap, data[1], data[2], data[3] >> 4, + startIndex, data + 5, apdu.length() - 5); + break; + } + case PropertyDescriptionRead: + _bau.propertyDescriptionReadIndication(priority, hopType, tsap, data[1], data[2], data[3]); + break; + case PropertyDescriptionResponse: + _bau.propertyDescriptionReadAppLayerConfirm(priority, hopType, tsap, data[1], data[2], data[3], + (data[4] & 0x80) > 0, data[4] & 0x3f, getWord(data + 5) & 0xfff, data[7]); + break; + } +} + +void CemiServer::individualConfirm(AckType ack, HopCountType hopType, Priority priority, uint16_t tsap, APDU & apdu, bool status) +{ + uint8_t* data = apdu.data(); + switch (apdu.type()) + { + case PropertyValueRead: + { + uint16_t startIndex; + popWord(startIndex, data + 3); + startIndex &= 0xfff; + _bau.propertyValueReadLocalConfirm(ack, priority, hopType, tsap, data[1], data[2], data[3] >> 4, + startIndex, status); + break; + } + case PropertyValueResponse: + { + uint16_t startIndex; + popWord(startIndex, data + 3); + startIndex &= 0xfff; + _bau.propertyValueReadResponseConfirm(ack, priority, hopType, tsap, data[1], data[2], data[3] >> 4, + startIndex, data + 5, apdu.length() - 5, status); + break; + } + case PropertyValueWrite: + { + uint16_t startIndex; + popWord(startIndex, data + 3); + startIndex &= 0xfff; + _bau.propertyValueWriteLocalConfirm(ack, priority, hopType, tsap, data[1], data[2], data[3] >> 4, + startIndex, data + 5, apdu.length() - 5, status); + break; + } + case PropertyDescriptionRead: + _bau.propertyDescriptionReadLocalConfirm(ack, priority, hopType, tsap, data[1], data[2], data[3], status); + break; + case PropertyDescriptionResponse: + _bau.propertyDescriptionReadResponseConfirm(ack, priority, hopType, tsap, data[1], data[2], data[3], + (data[4] & 0x80) > 0, data[4] & 0x3f, getWord(data + 5) & 0xfff, data[7], status); + break; + } +} diff --git a/src/knx/cemi_server.h b/src/knx/cemi_server.h new file mode 100644 index 0000000..697046f --- /dev/null +++ b/src/knx/cemi_server.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include "knx_types.h" + +class UsbDataLinkLayer; +class BusAccessUnit; +/** + * This is an implementation of the cEMI server as specified in @cite knx:3/6/3. + * It provides methods for the BusAccessUnit to do different things and translates this + * call to an cEMI frame and calls the correct method of the data link layer. + * It also takes calls from data link layer, decodes the submitted cEMI frames and calls the corresponding + * methods of the BusAccessUnit class. + */ +class CemiServer +{ + public: + /** + * The constructor. + * @param assocTable The AssociationTable is used to translate between asap (i.e. group objects) and group addresses. + * @param bau methods are called here depending of the content of the APDU + */ + CemiServer(UsbDataLinkLayer& dlLayer, BusAccessUnit& bau); + + // from data link layer +#pragma region Data - Link - Layer - Callbacks + void dataIndividualIndication(HopCountType hopType, Priority priority, uint16_t source, APDU& apdu); + void dataIndividualConfirm(AckType ack, HopCountType hopType, Priority priority, uint16_t tsap, APDU& apdu, bool status); +#pragma endregion + +#pragma region from bau + 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, + uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t* data, uint8_t length); + void propertyValueWriteRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, uint8_t objectIndex, + uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t* data, uint8_t length); + void propertyDescriptionReadRequest(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, + uint8_t objectIndex, uint8_t propertyId, uint8_t propertyIndex); + void propertyDescriptionReadResponse(AckType ack, Priority priority, HopCountType hopType, uint16_t asap, + uint8_t objectIndex, uint8_t propertyId, uint8_t propertyIndex, bool writeEnable, uint8_t type, + uint16_t maxNumberOfElements, uint8_t access); +#pragma endregion + + private: + void propertyDataSend(ApduType type, AckType ack, Priority priority, HopCountType hopType, uint16_t asap, + uint8_t objectIndex, uint8_t propertyId, uint8_t numberOfElements, uint16_t startIndex, uint8_t* data, + uint8_t length); + void individualIndication(HopCountType hopType, Priority priority, uint16_t tsap, APDU& apdu); + void individualConfirm(AckType ack, HopCountType hopType, Priority priority, uint16_t tsap, APDU& apdu, bool status); + + UsbDataLinkLayer& _dlLayer; + BusAccessUnit& _bau; +}; diff --git a/src/knx/cemi_server_object.cpp b/src/knx/cemi_server_object.cpp new file mode 100644 index 0000000..433172e --- /dev/null +++ b/src/knx/cemi_server_object.cpp @@ -0,0 +1,104 @@ +#include +#include "cemi_server_object.h" +#include "bits.h" + +void CemiServerObject::readProperty(PropertyID propertyId, uint32_t start, uint32_t& count, uint8_t* data) +{ + switch (propertyId) + { + case PID_OBJECT_TYPE: + pushWord(OT_CEMI_SERVER, data); + break; + case PID_MEDIUM_TYPE: // PDT_BITSET16 +#if MEDIUM_TYPE==0 + data[0] = 2; // TP1 supported +#elif MEDIUM_TYPE==2 + data[0] = 16; // RF supported +#elif MEDIUM_TYPE==5 + data[0] = 32; // IP supported +#endif + break; + case PID_COMM_MODE: // PDT_ENUM8 + // See KNX spec. cEMI 3/6/3 p.110 + data[0] = 0x00; // Only Data Link Layer mode supported and we do not allow switching (read-only) + break; + case PID_MEDIUM_AVAILABILITY: // PDT_BITSET16 +#if MEDIUM_TYPE==0 + data[0] = 2; // TP1 active +#elif MEDIUM_TYPE==2 + data[0] = 16; // RF active +#elif MEDIUM_TYPE==5 + data[0] = 32; // IP active +#endif + break; + case PID_ADD_INFO_TYPES: // PDT_ENUM8[] + pushByteArray((uint8_t*)_addInfoTypesTable, sizeof(_addInfoTypesTable), data); + break; + // Not supported yet + break; + default: + count = 0; + } +} + +void CemiServerObject::writeProperty(PropertyID id, uint8_t start, uint8_t* data, uint8_t count) +{ + switch (id) + { + case PID_COMM_MODE: + //_commMode = data[0]; // TODO: only Data Link Layer supported for now + // Property is also marked as read-only, normally it is read/write. + break; + + default: + break; + } +} + +uint8_t CemiServerObject::propertySize(PropertyID id) +{ + switch (id) + { + case PID_COMM_MODE: + return 1; + case PID_OBJECT_TYPE: + case PID_MEDIUM_TYPE: + case PID_MEDIUM_AVAILABILITY: + return 2; + case PID_ADD_INFO_TYPES: + return sizeof(_addInfoTypesTable); + default: + break; + } + return 0; +} + +uint8_t* CemiServerObject::save(uint8_t* buffer) +{ + return buffer; +} + +uint8_t* CemiServerObject::restore(uint8_t* buffer) +{ + return buffer; +} + +static PropertyDescription _propertyDescriptions[] = +{ + { PID_OBJECT_TYPE, false, PDT_UNSIGNED_INT, 1, ReadLv3 | WriteLv0 }, + { PID_MEDIUM_TYPE, false, PDT_BITSET16, 1, ReadLv3 | WriteLv0 }, + { PID_COMM_MODE, false, PDT_ENUM8, 1, ReadLv3 | WriteLv0 }, + { PID_MEDIUM_AVAILABILITY, false, PDT_BITSET16, 1, ReadLv3 | WriteLv0 }, + { PID_ADD_INFO_TYPES, false, PDT_ENUM8, 1, ReadLv3 | WriteLv0 } +}; +static uint8_t _propertyCount = sizeof(_propertyDescriptions) / sizeof(PropertyDescription); + +uint8_t CemiServerObject::propertyCount() +{ + return _propertyCount; +} + +PropertyDescription* CemiServerObject::propertyDescriptions() +{ + return _propertyDescriptions; +} diff --git a/src/knx/cemi_server_object.h b/src/knx/cemi_server_object.h new file mode 100644 index 0000000..b917183 --- /dev/null +++ b/src/knx/cemi_server_object.h @@ -0,0 +1,23 @@ +#pragma once + +#include "interface_object.h" + +class CemiServerObject: 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); + +protected: + uint8_t propertyCount(); + PropertyDescription* propertyDescriptions(); +private: + // cEMI additional info types supported by this cEMI server: only 0x02 (RF Control Octet and Serial Number or DoA) + uint8_t _addInfoTypesTable[1] = { 0x02 }; + uint8_t _commMode = 0x00; + +}; \ No newline at end of file diff --git a/src/knx/property_types.h b/src/knx/property_types.h index 088c741..4ed89aa 100644 --- a/src/knx/property_types.h +++ b/src/knx/property_types.h @@ -135,6 +135,16 @@ enum PropertyID PID_MSG_TRANSMIT_TO_KNX = 75, PID_FRIENDLY_NAME = 76, PID_ROUTING_BUSY_WAIT_TIME = 78, + + /** cEMI Server Object */ + PID_MEDIUM_TYPE = 51, + PID_COMM_MODE = 52, + PID_MEDIUM_AVAILABILITY = 53, + PID_ADD_INFO_TYPES = 54, + PID_TIME_BASE = 55, + PID_TRANSP_ENABLE = 56, + PID_CLIENT_SNA = 57, + PID_CLIENT_DEVICE_ADDRESS = 58 }; enum LoadState diff --git a/src/knx/usb_data_link_layer.cpp b/src/knx/usb_data_link_layer.cpp new file mode 100644 index 0000000..5592b2b --- /dev/null +++ b/src/knx/usb_data_link_layer.cpp @@ -0,0 +1,374 @@ +#include "usb_data_link_layer.h" + +#include "bits.h" +#include "platform.h" +#include "device_object.h" +#include "address_table_object.h" +#include "cemi_frame.h" + +#include +#include + +#define MIN(a, b) ((a < b) ? (a) : (b)) + +Adafruit_USBD_HID usb_hid; + +// HID report descriptor using TinyUSB's template +// Generic In Out with 64 bytes report (max) +uint8_t const desc_hid_report[] = +{ + //TUD_HID_REPORT_DESC_GENERIC_INOUT(64) + 0x06, 0xA0, 0xFF, // Usage Page (Vendor Defined 0xFFA0) +0x09, 0x01, // Usage (0x01) +0xA1, 0x01, // Collection (Application) +0x09, 0x01, // Usage (0x01) +0xA1, 0x00, // Collection (Physical) +0x06, 0xA1, 0xFF, // Usage Page (Vendor Defined 0xFFA1) +0x09, 0x03, // Usage (0x03) +0x09, 0x04, // Usage (0x04) +0x15, 0x80, // Logical Minimum (-128) +0x25, 0x7F, // Logical Maximum (127) +0x35, 0x00, // Physical Minimum (0) +0x45, 0xFF, // Physical Maximum (-1) +0x75, 0x08, // Report Size (8) +0x85, 0x01, // Report ID (1) +0x95, 0x3F, // Report Count (63) +0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) +0x09, 0x05, // Usage (0x05) +0x09, 0x06, // Usage (0x06) +0x15, 0x80, // Logical Minimum (-128) +0x25, 0x7F, // Logical Maximum (127) +0x35, 0x00, // Physical Minimum (0) +0x45, 0xFF, // Physical Maximum (-1) +0x75, 0x08, // Report Size (8) +0x85, 0x01, // Report ID (1) +0x95, 0x3F, // Report Count (63) +0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) +0xC0, // End Collection +0xC0 // End Collection +}; + +uint16_t parseReport(uint8_t* data, uint16_t dataLength) +{ + int8_t respDataSize = 0; + bool forceSend = false; + uint16_t length = 0; + + popWord(length, &data[5]); + length = MIN(dataLength, length); + + Serial1.print("RX HID report: len: "); + Serial1.println(length, DEC); + + for (int i = 0; i < (length + data[4] + 3); i++) + { + if (data[i] < 16) + Serial1.print("0"); + Serial1.print(data[i], HEX); + Serial1.print(" "); + } + Serial1.println(""); + + if ( data[0] == 0x01 && // ReportID (fixed 0x01) + data[1] == 0x13 && // PacketInfo must be 0x13 (SeqNo: 1, Type: 3) + data[3] == 0x00 && // Protocol version (fixed 0x00) + data[4] == 0x08 && // USB KNX Transfer Protocol Header Length (fixed 0x08) + data[7] == 0x0F ) // Bus Access Server Feature (0x0F) + { + uint8_t serviceId = data[8]; + switch (serviceId) + { + case 0x01: // Device Feature Get + { + data[8] = 0x02; // Device Feature Response + + uint8_t featureId = data[11]; + switch (featureId) + { + case 0x01: // Supported EMI types + Serial1.println("Device Feature Get: Supported EMI types"); + respDataSize = 2; + data[12] = 0x00; // USB KNX Transfer Protocol Body: Feature Data + data[13] = 0x04; // USB KNX Transfer Protocol Body: Feature Data -> only cEMI supported + break; + case 0x02: // Host Device Descriptor Type 0 + Serial1.println("Device Feature Get: Host Device Descriptor Type 0"); + respDataSize = 2; + data[12] = 0x00; // USB KNX Transfer Protocol Body: Feature Data + data[13] = 0x00; // USB KNX Transfer Protocol Body: Feature Data + break; + case 0x03: // Bus connection status + Serial1.println("Device Feature Get: Bus connection status"); + respDataSize = 1; + data[12] = 1; // USB KNX Transfer Protocol Body: Feature Data + break; + case 0x04: // KNX manufacturer code + Serial1.println("Device Feature Get: KNX manufacturer code"); + respDataSize = 2; + data[12] = 0x00; // USB KNX Transfer Protocol Body: Feature Data + data[13] = 0x00; // USB KNX Transfer Protocol Body: Feature Data -> Manufacturer Code + break; + case 0x05: // Active EMI type + Serial1.println("Device Feature Get: Active EMI type"); + respDataSize = 1; + data[12] = 0x03; // USB KNX Transfer Protocol Body: Feature Data -> cEMI ID + break; + default: + respDataSize = 0; + break; + } + break; + } + case 0x03: // Device Feature Set + { + uint8_t featureId = data[11]; + switch (featureId) + { + case 0x05: // Active EMI type + Serial1.print("Device Feature Set: Active EMI type: "); + if (data[12] < 16) + Serial1.print("0"); + Serial1.println(data[12], HEX); // USB KNX Transfer Protocol Body: Feature Data -> EMI TYPE ID + break; + // All other featureIds must not be set + case 0x01: // Supported EMI types + case 0x02: // Host Device Descriptor Type 0 + case 0x03: // Bus connection status + case 0x04: // KNX manufacturer code + default: + break; + } + break; + } + + // These are only sent from the device to the host + case 0x02: // Device Feature Response + case 0x04: // Device Feature Info + case 0xEF: // reserved, not used + case 0xFF: // reserved (ESCAPE for future extensions) + default: + break; + } + } + else if ( data[0] == 0x01 && // ReportID (fixed 0x01) + data[1] == 0x13 && // PacketInfo must be 0x13 (SeqNo: 1, Type: 3) + data[3] == 0x00 && // Protocol version (fixed 0x00) + data[4] == 0x08 && // USB KNX Transfer Protocol Header Length (fixed 0x08) + data[7] == 0x01 && // KNX Tunneling (0x01) + data[8] == 0x03 ) // EMI ID: 3 -> cEMI + { + uint8_t messageCode = data[11]; + switch(messageCode) + { + case 0xFC: // M_PropRead.req + { + data[11] = 0xFB; // M_PropRead.con + if (data[15] == 0x34) + { + data[18] = 00; // PID_COMM_MODE: 0: Data Link Layer + } + else + { + data[16] = 0; // Number of elements must be 0 if negative response + data[18] = 7; // Error code 7 (Void DP) + } + respDataSize = 1; + break; + } + case 0xF6: // M_PropWrite.req + { + data[11] = 0xF5; // M_PropWrite.con + if ((data[15] == 0x34) && (data[18] == 0x00)) + { + respDataSize = -1; + } + else + { + data[16] = 0; // Number of elements must be 0 if negative response + data[18] = 6; // Error code 6 (illegal command) + respDataSize = 0; + forceSend = true; + } + break; + } + } + } + + if ((respDataSize != 0) || forceSend) + { + data[2] += respDataSize; // HID Report Header: Packet Length + data[6] += respDataSize; // USB KNX Transfer Protocol Header: Body Length + + Serial1.print("TX HID report: len: "); + Serial1.println((length + data[4] + 3) + respDataSize, DEC); + + for (int i = 0; i < ((length + data[4] + 3) + respDataSize); i++) + { + if (data[i] < 16) + Serial1.print("0"); + Serial1.print(data[i], HEX); + Serial1.print(" "); + } + Serial1.println(""); + } + + return respDataSize; +} + +// Invoked when received SET_REPORT control request or +// received data on OUT endpoint ( Report ID = 0, Type = 0 ) +void set_report_callback(uint8_t report_id, hid_report_type_t report_type, uint8_t const* buffer, uint16_t bufsize) +{ + // we don't use multiple report and report ID + (void) report_id; + (void) report_type; + + uint8_t tmpbuf[bufsize]; + memcpy(tmpbuf, buffer, bufsize); + + if (parseReport(tmpbuf, bufsize) > 0) + { + usb_hid.sendReport(tmpbuf[0], &tmpbuf[1], bufsize); + } +} + +void UsbDataLinkLayer::loop() +{ + if (!_enabled) + return; +} + +bool UsbDataLinkLayer::sendFrame(CemiFrame& frame) +{ + if (!_enabled) + return false; + + // 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; +} + +UsbDataLinkLayer::UsbDataLinkLayer(DeviceObject& devObj, AddressTableObject& addrTab, + NetworkLayer& layer, Platform& platform) + : DataLinkLayer(devObj, addrTab, layer, platform) +{ +} + +void UsbDataLinkLayer::frameBytesReceived(uint8_t* _buffer, uint16_t length) +{ + // Prepare the cEMI frame + CemiFrame frame(_buffer, length); +/* + 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(" len: "); + print(newLength); + + print(" data: "); + printHex(" data: ", _buffer, newLength); +*/ + frameRecieved(frame); +} + +void UsbDataLinkLayer::enabled(bool value) +{ + if (value && !_enabled) + { + usb_hid.enableOutEndpoint(true); + usb_hid.setPollInterval(2); + usb_hid.setReportDescriptor(desc_hid_report, sizeof(desc_hid_report)); + usb_hid.setReportCallback(NULL, set_report_callback); + + usb_hid.begin(); + + // wait until device mounted + while( !USBDevice.mounted() ) delay(1); + + _enabled = true; + print("ownaddr "); + println(_deviceObject.induvidualAddress(), HEX); + return; + } + + if (!value && _enabled) + { + println("USB data link layer cannot be disabled once enabled!"); + return; + } +} + +bool UsbDataLinkLayer::enabled() const +{ + return _enabled; +} + +void UsbDataLinkLayer::addFrameTxQueue(CemiFrame& frame) +{ + _tx_queue_frame_t* tx_frame = new _tx_queue_frame_t; + + tx_frame->length = frame.totalLenght(); + tx_frame->data = new uint8_t[tx_frame->length]; + tx_frame->next = NULL; + +/* + 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 UsbDataLinkLayer::isTxQueueEmpty() +{ + if (_tx_queue.front == NULL) + { + return true; + } + return false; +} + +void UsbDataLinkLayer::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; +} diff --git a/src/knx/usb_data_link_layer.h b/src/knx/usb_data_link_layer.h new file mode 100644 index 0000000..eb1f1fb --- /dev/null +++ b/src/knx/usb_data_link_layer.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include "data_link_layer.h" + +#define MAX_KNX_TELEGRAM_SIZE 263 + +extern Adafruit_USBD_HID usb_hid; + +class UsbDataLinkLayer : public DataLinkLayer +{ + using DataLinkLayer::_deviceObject; + using DataLinkLayer::_groupAddressTable; + using DataLinkLayer::_platform; + + public: + UsbDataLinkLayer(DeviceObject& devObj, 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; + + + 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); +};