diff --git a/src/knx/usb_tunnel_interface.cpp b/src/knx/usb_tunnel_interface.cpp index 85dc761..2997b1b 100644 --- a/src/knx/usb_tunnel_interface.cpp +++ b/src/knx/usb_tunnel_interface.cpp @@ -15,93 +15,189 @@ extern bool sendKnxHidReport(uint8_t* data, uint16_t length); extern bool isKnxHidSendReportPossible(); -static const uint8_t descHidReport[] = -{ - //TUD_HID_REPORT_DESC_KNXHID_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 -}; +// class UsbTunnelInterface -static uint16_t manufacturerId; -static uint16_t maskVersion; - -struct _rx_queue_frame_t +UsbTunnelInterface::UsbTunnelInterface(CemiServer& cemiServer, + uint16_t mId, + uint16_t mV) + : _cemiServer(cemiServer), + _manufacturerId(mId), + _maskVersion(mV) { - uint8_t* data; - uint16_t length; - _rx_queue_frame_t* next; -}; - -static struct _rx_queue_t -{ - _rx_queue_frame_t* front = nullptr; - _rx_queue_frame_t* back = nullptr; -} _rx_queue; - -const uint8_t* getKnxHidReportDescriptor() -{ - return &descHidReport[0]; } -uint16_t getHidReportDescriptorLength() +void UsbTunnelInterface::loop() { - return sizeof(descHidReport); + // Make sure that the USB HW is also ready to send another report + if (!isTxQueueEmpty() && isKnxHidSendReportPossible()) + { + uint8_t* buffer; + uint16_t length; + loadNextTxFrame(&buffer, &length); +/* + print("cEMI USB TX len: "); + print(length); + + print(" data: "); + printHex(" data: ", buffer, length); +*/ + sendKnxTunnelHidReport(buffer, length); + } + + if (!isRxQueueEmpty()) + { + uint8_t* buffer; + uint16_t length; + loadNextRxBuffer(&buffer, &length); + handleKnxHidReport(buffer, length); + delete buffer; + } } -static void addFrameRxQueue(CemiFrame& frame) +/* USB TX */ + +bool UsbTunnelInterface::sendCemiFrame(CemiFrame& frame) { - _rx_queue_frame_t* rx_frame = new _rx_queue_frame_t; + addFrameTxQueue(frame); - rx_frame->length = frame.dataLength(); - rx_frame->data = new uint8_t[rx_frame->length]; - rx_frame->next = nullptr; + return true; +} - memcpy(rx_frame->data, frame.data(), rx_frame->length); +void UsbTunnelInterface::addFrameTxQueue(CemiFrame& frame) +{ + _queue_buffer_t* tx_frame = new _queue_buffer_t; + + tx_frame->length = frame.dataLength(); + tx_frame->data = new uint8_t[tx_frame->length]; + tx_frame->next = nullptr; + + memcpy(tx_frame->data, frame.data(), tx_frame->length); /* - print("cEMI USB RX len: "); - print(rx_frame->length); + print("cEMI USB TX len: "); + print(tx_frame->length); - printHex(" data:", rx_frame->data, rx_frame->length); + printHex(" data:", tx_frame->data, tx_frame->length); */ - if (_rx_queue.back == nullptr) + if (_tx_queue.back == nullptr) { - _rx_queue.front = _rx_queue.back = rx_frame; + _tx_queue.front = _tx_queue.back = tx_frame; } else { - _rx_queue.back->next = rx_frame; - _rx_queue.back = rx_frame; + _tx_queue.back->next = tx_frame; + _tx_queue.back = tx_frame; } } -static bool isRxQueueEmpty() +bool UsbTunnelInterface::isTxQueueEmpty() +{ + if (_tx_queue.front == nullptr) + { + return true; + } + return false; +} + +void UsbTunnelInterface::loadNextTxFrame(uint8_t** sendBuffer, uint16_t* sendBufferLength) +{ + if (_tx_queue.front == nullptr) + { + return; + } + _queue_buffer_t* tx_frame = _tx_queue.front; + *sendBuffer = tx_frame->data; + *sendBufferLength = tx_frame->length; + _tx_queue.front = tx_frame->next; + + if (_tx_queue.front == nullptr) + { + _tx_queue.back = nullptr; + } + delete tx_frame; +} + +void UsbTunnelInterface::sendKnxTunnelHidReport(uint8_t* data, uint16_t length) +{ + uint8_t buffer[length + 11]; + + buffer[0] = 0x01; // ReportID (fixed 0x01) + buffer[1] = 0x13; // PacketInfo must be 0x13 (SeqNo: 1, Type: 3) + buffer[3] = 0x00; // Protocol version (fixed 0x00) + buffer[4] = 0x08; // USB KNX Transfer Protocol Header Length (fixed 0x08) + buffer[7] = 0x01; // KNX Tunneling (0x01) + buffer[8] = 0x03; // EMI ID: 3 -> cEMI + buffer[9] = 0x00; // Manufacturer + buffer[10] = 0x00; // Manufacturer + + // Copy cEMI frame to buffer + memcpy(&buffer[11], data, length + HID_HEADER_SIZE + 8); + + buffer[2] = 8 + length; // KNX USB Transfer Protocol Header length (8, only first packet!) + cEMI length + pushWord(length, &buffer[5]); // KNX USB Transfer Protocol Body length (cEMI length) + +#ifdef DEBUG_TX_HID_REPORT + Serial1.print("TX HID report: len: "); + Serial1.println(buffer[2] + HID_HEADER_SIZE, DEC); + + for (int i = 0; i < (buffer[2] + HID_HEADER_SIZE); i++) + { + if (buffer[i] < 16) + Serial1.print("0"); + Serial1.print(buffer[i], HEX); + Serial1.print(" "); + } + Serial1.println(""); +#endif + + sendKnxHidReport(buffer, MAX_EP_SIZE); +} + +/* USB RX */ + +// Invoked when received SET_REPORT control request or via interrupt out pipe +void UsbTunnelInterface::receiveKnxHidReport(uint8_t const* data, uint16_t bufSize) +{ + // Check KNX ReportID (fixed 0x01) + if (data[0] == 0x01) + { + // We just store only the used space of the HID report buffer + // which is normally padded with 0 to fill the complete USB EP size (e.g. 64 bytes) + uint8_t packetLength = data[2] + HID_HEADER_SIZE; + UsbTunnelInterface::addBufferRxQueue(data, packetLength); + } +} + +UsbTunnelInterface::_queue_t UsbTunnelInterface::_rx_queue; + +void UsbTunnelInterface::addBufferRxQueue(const uint8_t* data, uint16_t length) +{ + _queue_buffer_t* rx_buffer = new _queue_buffer_t; + + rx_buffer->length = length; + rx_buffer->data = new uint8_t[rx_buffer->length]; + rx_buffer->next = nullptr; + + memcpy(rx_buffer->data, data, rx_buffer->length); + +/* + print("cEMI USB RX len: "); + print(rx_buffer->length); + + printHex(" data:", rx_buffer->data, rx_buffer->length); +*/ + if (_rx_queue.back == nullptr) + { + _rx_queue.front =_rx_queue.back = rx_buffer; + } + else + { + _rx_queue.back->next = rx_buffer; + _rx_queue.back = rx_buffer; + } +} + +bool UsbTunnelInterface::isRxQueueEmpty() { if (_rx_queue.front == nullptr) { @@ -110,29 +206,79 @@ static bool isRxQueueEmpty() return false; } -static void loadNextRxFrame(uint8_t** receiveBuffer, uint16_t* receiveBufferLength) +void UsbTunnelInterface::loadNextRxBuffer(uint8_t** receiveBuffer, uint16_t* receiveBufferLength) { if (_rx_queue.front == nullptr) { return; } - _rx_queue_frame_t* rx_frame = _rx_queue.front; - *receiveBuffer = rx_frame->data; - *receiveBufferLength = rx_frame->length; - _rx_queue.front = rx_frame->next; + _queue_buffer_t* rx_buffer = _rx_queue.front; + *receiveBuffer = rx_buffer->data; + *receiveBufferLength = rx_buffer->length; + _rx_queue.front = rx_buffer->next; if (_rx_queue.front == nullptr) { _rx_queue.back = nullptr; } - delete rx_frame; + delete rx_buffer; } -static void handleBusAccessServerProtocol(const uint8_t* requestData, uint16_t packetLength) +void UsbTunnelInterface::handleKnxHidReport(uint8_t const* data, uint16_t bufSize) +{ + if (data[1] == 0x13) // PacketInfo must be 0x13 (SeqNo: 1, Type: 3) + { + uint8_t packetLength = data[2]; + +#ifdef DEBUG_RX_HID_REPORT + Serial1.print("RX HID report: len: "); + Serial1.println(packetLength, DEC); + + for (int i = 0; i < (packetLength + HID_HEADER_SIZE); i++) + { + if (data[i] < 16) + Serial1.print("0"); + Serial1.print(data[i], HEX); + Serial1.print(" "); + } + Serial1.println(""); +#endif + + if (data[3] == 0x00 && // Protocol version (fixed 0x00) + data[4] == 0x08) // USB KNX Transfer Protocol Header Length (fixed 0x08) + { + if (data[7] == 0x0F) // Bus Access Server Feature (0x0F) + { + handleBusAccessServerProtocol(&data[0], packetLength + HID_HEADER_SIZE); + } + else if (data[7] == 0x01 && // KNX Tunneling (0x01) + data[8] == 0x03) // EMI type: only cEMI supported + { + uint16_t bodyLength; + popWord(bodyLength, (uint8_t*)&data[5]); // KNX USB Transfer Protocol Body length + + // Prepare the cEMI frame + CemiFrame frame((uint8_t*)&data[11], bodyLength); + /* + print("cEMI USB RX len: "); + print(length); + + print(" data: "); + printHex(" data: ", buffer, length); + */ + _cemiServer.frameReceived(frame); + } + } + } +} + +void UsbTunnelInterface::handleBusAccessServerProtocol(const uint8_t* requestData, uint16_t packetLength) { if (packetLength > MAX_EP_SIZE) return; + // Create a copy of the request data so that we can + // simply append the response data later uint8_t data[MAX_EP_SIZE]; memset(data, 0, sizeof(data)); memcpy(data, requestData, packetLength); @@ -158,7 +304,7 @@ static void handleBusAccessServerProtocol(const uint8_t* requestData, uint16_t p case 0x02: // Host Device Descriptor Type 0 Serial1.println("Device Feature Get: Host Device Descriptor Type 0"); respDataSize = 2; - pushWord(maskVersion, &data[12]); // USB KNX Transfer Protocol Body: Feature Data -> Mask version + pushWord(_maskVersion, &data[12]); // USB KNX Transfer Protocol Body: Feature Data -> Mask version break; case 0x03: // Bus connection status Serial1.println("Device Feature Get: Bus connection status"); @@ -168,7 +314,7 @@ static void handleBusAccessServerProtocol(const uint8_t* requestData, uint16_t p case 0x04: // KNX manufacturer code Serial1.println("Device Feature Get: KNX manufacturer code"); respDataSize = 2; - pushWord(manufacturerId, &data[12]); // USB KNX Transfer Protocol Body: Feature Data -> Manufacturer Code + pushWord(_manufacturerId, &data[12]); // USB KNX Transfer Protocol Body: Feature Data -> Manufacturer Code break; case 0x05: // Active EMI type Serial1.println("Device Feature Get: Active EMI type"); @@ -235,191 +381,47 @@ static void handleBusAccessServerProtocol(const uint8_t* requestData, uint16_t p } } -static void sendKnxTunnelHidReport(uint8_t* data, uint16_t length) +/* USB HID report descriptor for KNX HID */ + +const uint8_t UsbTunnelInterface::descHidReport[] = { - uint8_t buffer[length + 11]; + //TUD_HID_REPORT_DESC_KNXHID_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 +}; - buffer[0] = 0x01; // ReportID (fixed 0x01) - buffer[1] = 0x13; // PacketInfo must be 0x13 (SeqNo: 1, Type: 3) - buffer[3] = 0x00; // Protocol version (fixed 0x00) - buffer[4] = 0x08; // USB KNX Transfer Protocol Header Length (fixed 0x08) - buffer[7] = 0x01; // KNX Tunneling (0x01) - buffer[8] = 0x03; // EMI ID: 3 -> cEMI - buffer[9] = 0x00; // Manufacturer - buffer[10] = 0x00; // Manufacturer - - // Copy cEMI frame to buffer - memcpy(&buffer[11], data, length + HID_HEADER_SIZE + 8); - - buffer[2] = 8 + length; // KNX USB Transfer Protocol Header length (8, only first packet!) + cEMI length - pushWord(length, &buffer[5]); // KNX USB Transfer Protocol Body length (cEMI length) - -#ifdef DEBUG_TX_HID_REPORT - Serial1.print("TX HID report: len: "); - Serial1.println(buffer[2] + HID_HEADER_SIZE, DEC); - - for (int i = 0; i < (buffer[2] + HID_HEADER_SIZE); i++) - { - if (buffer[i] < 16) - Serial1.print("0"); - Serial1.print(buffer[i], HEX); - Serial1.print(" "); - } - Serial1.println(""); -#endif - - sendKnxHidReport(buffer, MAX_EP_SIZE); +const uint8_t* UsbTunnelInterface::getKnxHidReportDescriptor() +{ + return &descHidReport[0]; } -// Invoked when received SET_REPORT control request or via interrupt out pipe -void handleKnxHidReport(uint8_t const* data, uint16_t bufSize) +uint16_t UsbTunnelInterface::getHidReportDescriptorLength() { - if (bufSize!=MAX_EP_SIZE) - return; - - if (data[0] == 0x01 && // ReportID (fixed 0x01) - data[1] == 0x13) // PacketInfo must be 0x13 (SeqNo: 1, Type: 3) - { - uint8_t packetLength = data[2]; - -#ifdef DEBUG_RX_HID_REPORT - Serial1.print("RX HID report: len: "); - Serial1.println(packetLength, DEC); - - for (int i = 0; i < (packetLength + HID_HEADER_SIZE); i++) - { - if (data[i] < 16) - Serial1.print("0"); - Serial1.print(data[i], HEX); - Serial1.print(" "); - } - Serial1.println(""); -#endif - - if (data[3] == 0x00 && // Protocol version (fixed 0x00) - data[4] == 0x08) // USB KNX Transfer Protocol Header Length (fixed 0x08) - { - if (data[7] == 0x0F) // Bus Access Server Feature (0x0F) - { - handleBusAccessServerProtocol(&data[0], packetLength + HID_HEADER_SIZE); - } - else if (data[7] == 0x01 && // KNX Tunneling (0x01) - data[8] == 0x03) // EMI type: only cEMI supported - { - uint16_t bodyLength; - popWord(bodyLength, (uint8_t*)&data[5]); // KNX USB Transfer Protocol Body length - CemiFrame frame((uint8_t*)&data[11], bodyLength); - addFrameRxQueue(frame); - } - } - } -} - -// class UsbTunnelInterface - -UsbTunnelInterface::UsbTunnelInterface(CemiServer& cemiServer, - uint16_t mId, - uint16_t mV) - : _cemiServer(cemiServer) -{ - manufacturerId = mId; - maskVersion = mV; -} - -void UsbTunnelInterface::loop() -{ - // Make sure that the USB HW is also ready to send another report - if (!isTxQueueEmpty() && isKnxHidSendReportPossible()) - { - uint8_t* buffer; - uint16_t length; - loadNextTxFrame(&buffer, &length); -/* - print("cEMI USB TX len: "); - print(length); - - print(" data: "); - printHex(" data: ", buffer, length); -*/ - sendKnxTunnelHidReport(buffer, length); - } - - if (!isRxQueueEmpty()) - { - uint8_t* buffer; - uint16_t length; - loadNextRxFrame(&buffer, &length); - - // Prepare the cEMI frame - CemiFrame frame(buffer, length); -/* - print("cEMI USB RX len: "); - print(length); - - print(" data: "); - printHex(" data: ", buffer, length); -*/ - _cemiServer.frameReceived(frame); - } -} - -bool UsbTunnelInterface::sendCemiFrame(CemiFrame& frame) -{ - addFrameTxQueue(frame); - - return true; -} - -void UsbTunnelInterface::addFrameTxQueue(CemiFrame& frame) -{ - _tx_queue_frame_t* tx_frame = new _tx_queue_frame_t; - - tx_frame->length = frame.dataLength(); - tx_frame->data = new uint8_t[tx_frame->length]; - tx_frame->next = nullptr; - - memcpy(tx_frame->data, frame.data(), tx_frame->length); - -/* - print("cEMI USB TX len: "); - print(tx_frame->length); - - printHex(" data:", tx_frame->data, tx_frame->length); -*/ - if (_tx_queue.back == nullptr) - { - _tx_queue.front = _tx_queue.back = tx_frame; - } - else - { - _tx_queue.back->next = tx_frame; - _tx_queue.back = tx_frame; - } -} - -bool UsbTunnelInterface::isTxQueueEmpty() -{ - if (_tx_queue.front == nullptr) - { - return true; - } - return false; -} - -void UsbTunnelInterface::loadNextTxFrame(uint8_t** sendBuffer, uint16_t* sendBufferLength) -{ - if (_tx_queue.front == nullptr) - { - 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 == nullptr) - { - _tx_queue.back = nullptr; - } - delete tx_frame; + return sizeof(descHidReport); }