knx
ETS configurable knx-stack
usb_tunnel_interface.cpp
Go to the documentation of this file.
1 #include "config.h"
2 #ifdef USE_USB
3 
4 #include "bits.h"
5 #include "usb_tunnel_interface.h"
6 #include "cemi_server.h"
7 #include "cemi_frame.h"
8 
9 #include <stdio.h>
10 #include <string.h>
11 
12 #define MIN(a, b) ((a < b) ? (a) : (b))
13 
14 #define MAX_EP_SIZE 64
15 #define HID_HEADER_SIZE 3
16 #define MAX_KNX_TELEGRAM_SIZE 263
17 #define KNX_HID_REPORT_ID 0x01
18 #define PROTOCOL_VERSION 0x00
19 #define PROTOCOL_HEADER_LENGTH 0x08
20 
21 // Maximum possible payload data bytes in a transfer protocol body
22 #define MAX_DATASIZE_START_PACKET 52
23 #define MAX_DATASIZE_PARTIAL_PACKET 61
24 
25 #define PACKET_TYPE_START 1
26 #define PACKET_TYPE_END 2
27 #define PACKET_TYPE_PARTIAL 4
28 
29 //#define DEBUG_TX_HID_REPORT
30 //#define DEBUG_RX_HID_REPORT
31 
32 extern bool sendHidReport(uint8_t* data, uint16_t length);
34 
35 // class UsbTunnelInterface
36 
38  uint16_t mId,
39  uint16_t mV)
40  : _cemiServer(cemiServer),
41  _manufacturerId(mId),
42  _maskVersion(mV)
43 {
44 }
45 
47 {
48  // Make sure that the USB HW is also ready to send another report
49  if (!isTxQueueEmpty() && isSendHidReportPossible())
50  {
51  uint8_t* buffer;
52  uint16_t length;
53  loadNextTxFrame(&buffer, &length);
54  sendHidReport(buffer, length);
55  delete buffer;
56  }
57 
58  // Check if we already a COMPLETE transport protocol packet
59  // A transport protocol packet might be split into multiple HID reports and
60  // need to be assembled again
61  if (rxHaveCompletePacket)
62  {
63  handleHidReportRxQueue();
64  rxHaveCompletePacket = false;
65  }
66 }
67 
68 /* USB TX */
69 
71 {
72  sendKnxHidReport(KnxTunneling, ServiceIdNotUsed, frame.data(), frame.dataLength());
73 }
74 
75 void UsbTunnelInterface::addBufferTxQueue(uint8_t* data, uint16_t length)
76 {
77  _queue_buffer_t* tx_buffer = new _queue_buffer_t;
78 
79  tx_buffer->length = MAX_EP_SIZE;
80  tx_buffer->data = new uint8_t[MAX_EP_SIZE]; // We always have to send full max. USB endpoint size of 64 bytes
81  tx_buffer->next = nullptr;
82 
83  memcpy(tx_buffer->data, data, tx_buffer->length);
84  memset(&tx_buffer->data[length], 0x00, MAX_EP_SIZE - length); // Set unused bytes to zero
85 
86  if (_tx_queue.back == nullptr)
87  {
88  _tx_queue.front = _tx_queue.back = tx_buffer;
89  }
90  else
91  {
92  _tx_queue.back->next = tx_buffer;
93  _tx_queue.back = tx_buffer;
94  }
95 }
96 
97 bool UsbTunnelInterface::isTxQueueEmpty()
98 {
99  if (_tx_queue.front == nullptr)
100  {
101  return true;
102  }
103 
104  return false;
105 }
106 
107 void UsbTunnelInterface::loadNextTxFrame(uint8_t** sendBuffer, uint16_t* sendBufferLength)
108 {
109  if (_tx_queue.front == nullptr)
110  {
111  return;
112  }
113 
114  _queue_buffer_t* tx_buffer = _tx_queue.front;
115  *sendBuffer = tx_buffer->data;
116  *sendBufferLength = tx_buffer->length;
117  _tx_queue.front = tx_buffer->next;
118 
119  if (_tx_queue.front == nullptr)
120  {
121  _tx_queue.back = nullptr;
122  }
123 
124  delete tx_buffer;
125 
126 #ifdef DEBUG_TX_HID_REPORT
127  print("TX HID report: len: ");
128  // We do not print the padded zeros
129  uint8_t len = (*sendBuffer)[2];
130  println(len, DEC);
131 
132  for (int i = 0; i < len; i++)
133  {
134  if ((*sendBuffer)[i] < 16)
135  print("0");
136 
137  print((*sendBuffer)[i], HEX);
138  print(" ");
139  }
140 
141  println("");
142 #endif
143 }
144 
145 void UsbTunnelInterface::sendKnxHidReport(ProtocolIdType protId, ServiceIdType servId, uint8_t* data, uint16_t length)
146 {
147  uint16_t maxData = MAX_DATASIZE_START_PACKET;
148  uint8_t packetType = PACKET_TYPE_START;
149 
150  if (length > maxData)
151  {
152  packetType |= PACKET_TYPE_PARTIAL;
153  }
154 
155  uint16_t offset = 0;
156  uint8_t* buffer = nullptr;
157 
158  // In theory we can only have sequence numbers from 1..5
159  // First packet: 51 bytes max
160  // Other packets: 62 bytes max.
161  // -> 51 + 4*62 = 296 bytes -> enough for a KNX cEMI extended frame APDU + Transport Protocol Header length
162  for (uint8_t seqNum = 1; seqNum < 6; seqNum++)
163  {
164  uint16_t copyLen = MIN(length, maxData);
165 
166  // If this is the first packet we include the transport protocol header
167  if (packetType & PACKET_TYPE_START)
168  {
169  buffer = new uint8_t[copyLen + 8 + HID_HEADER_SIZE]; // length of transport protocol header: 11 bytes
170  buffer[2] = 8 + copyLen; // KNX USB Transfer Protocol Body length
171  buffer[3] = PROTOCOL_VERSION; // Protocol version (fixed 0x00)
172  buffer[4] = PROTOCOL_HEADER_LENGTH; // USB KNX Transfer Protocol Header Length (fixed 0x08)
173  pushWord(copyLen, &buffer[5]); // KNX USB Transfer Protocol Body length (e.g. cEMI length)
174  buffer[7] = (uint8_t) protId; // KNX Tunneling (0x01) or KNX Bus Access Server (0x0f)
175  buffer[8] = (protId == KnxTunneling) ? (uint8_t)CEMI : (uint8_t)servId; // either EMI ID or Service Id
176  buffer[9] = 0x00; // Manufacturer (fixed 0x00) see KNX Spec 9/3 p.23 3.4.1.3.5
177  buffer[10] = 0x00; // Manufacturer (fixed 0x00) see KNX Spec 9/3 p.23 3.4.1.3.5
178  memcpy(&buffer[11], &data[offset], copyLen); // Copy payload for KNX USB Transfer Protocol Body
179  }
180  else
181  {
182  buffer = new uint8_t[copyLen]; // no transport protocol header in partial packets
183  buffer[2] = copyLen; // KNX USB Transfer Protocol Body length
184  memcpy(&buffer[0], &data[offset], copyLen); // Copy payload for KNX USB Transfer Protocol Body
185  }
186 
187  offset += copyLen;
188 
189  if (offset >= length)
190  {
191  packetType |= PACKET_TYPE_END;
192  }
193 
194  buffer[0] = KNX_HID_REPORT_ID; // ReportID (fixed 0x01)
195  buffer[1] = ((seqNum << 4) & 0xF0) | (packetType & 0x07); // PacketInfo (SeqNo and Type)
196 
197  addBufferTxQueue(buffer, (buffer[2] + HID_HEADER_SIZE));
198 
199  delete[] buffer;
200 
201  if (offset >= length)
202  {
203  break;
204  }
205 
206  packetType &= ~PACKET_TYPE_START;
207  maxData = MAX_DATASIZE_PARTIAL_PACKET;
208  }
209 }
210 
211 /* USB RX */
212 
213 // Invoked when received SET_REPORT control request or via interrupt out pipe
214 void UsbTunnelInterface::receiveHidReport(uint8_t const* data, uint16_t bufSize)
215 {
216  // Check KNX ReportID (fixed 0x01)
217  if (data[0] == KNX_HID_REPORT_ID)
218  {
219  // We just store only the used space of the HID report buffer
220  // which is normally padded with 0 to fill the complete USB EP size (e.g. 64 bytes)
221  uint8_t packetLength = data[2] + HID_HEADER_SIZE;
222  UsbTunnelInterface::addBufferRxQueue(data, packetLength);
223 
224  // Check if packet type indicates last packet
225  if ((data[1] & PACKET_TYPE_END) == PACKET_TYPE_END)
226  {
227  // Signal main loop that we have a complete KNX USB packet
228  rxHaveCompletePacket = true;
229  }
230  }
231 }
232 
233 UsbTunnelInterface::_queue_t UsbTunnelInterface::_rx_queue;
234 bool UsbTunnelInterface::rxHaveCompletePacket = false;
235 
236 void UsbTunnelInterface::addBufferRxQueue(const uint8_t* data, uint16_t length)
237 {
238  _queue_buffer_t* rx_buffer = new _queue_buffer_t;
239 
240  rx_buffer->length = length;
241  rx_buffer->data = new uint8_t[rx_buffer->length];
242  rx_buffer->next = nullptr;
243 
244  memcpy(rx_buffer->data, data, rx_buffer->length);
245 
246  if (_rx_queue.back == nullptr)
247  {
248  _rx_queue.front = _rx_queue.back = rx_buffer;
249  }
250  else
251  {
252  _rx_queue.back->next = rx_buffer;
253  _rx_queue.back = rx_buffer;
254  }
255 }
256 
257 bool UsbTunnelInterface::isRxQueueEmpty()
258 {
259  if (_rx_queue.front == nullptr)
260  {
261  return true;
262  }
263 
264  return false;
265 }
266 
267 void UsbTunnelInterface::loadNextRxBuffer(uint8_t** receiveBuffer, uint16_t* receiveBufferLength)
268 {
269  if (_rx_queue.front == nullptr)
270  {
271  return;
272  }
273 
274  _queue_buffer_t* rx_buffer = _rx_queue.front;
275  *receiveBuffer = rx_buffer->data;
276  *receiveBufferLength = rx_buffer->length;
277  _rx_queue.front = rx_buffer->next;
278 
279  if (_rx_queue.front == nullptr)
280  {
281  _rx_queue.back = nullptr;
282  }
283 
284  delete rx_buffer;
285 
286 #ifdef DEBUG_RX_HID_REPORT
287  print("RX HID report: len: ");
288  println(*receiveBufferLength, DEC);
289 
290  for (int i = 0; i < (*receiveBufferLength); i++)
291  {
292  if ((*receiveBuffer)[i] < 16)
293  print("0");
294 
295  print((*receiveBuffer)[i], HEX);
296  print(" ");
297  }
298 
299  println("");
300 #endif
301 }
302 
303 void UsbTunnelInterface::handleTransferProtocolPacket(uint8_t* data, uint16_t length)
304 {
305  if (data[0] == PROTOCOL_VERSION && // Protocol version (fixed 0x00)
306  data[1] == PROTOCOL_HEADER_LENGTH) // USB KNX Transfer Protocol Header Length (fixed 0x08)
307  {
308  uint16_t bodyLength;
309  popWord(bodyLength, (uint8_t*)&data[2]); // KNX USB Transfer Protocol Body length
310 
311  if (data[4] == (uint8_t) BusAccessServer) // Bus Access Server Feature (0x0F)
312  {
313  handleBusAccessServerProtocol((ServiceIdType)data[5], &data[8], bodyLength);
314  }
315  else if (data[4] == (uint8_t) KnxTunneling) // KNX Tunneling (0x01)
316  {
317  if (data[5] == (uint8_t) CEMI) // EMI type: only cEMI supported (0x03))
318  {
319  // Prepare the cEMI frame
320  CemiFrame frame((uint8_t*)&data[8], bodyLength);
321  /*
322  print("cEMI USB RX len: ");
323  print(length);
324 
325  print(" data: ");
326  printHex(" data: ", buffer, length);
327  */
328  _cemiServer.frameReceived(frame);
329  }
330  else
331  {
332  println("Error: Only cEMI is supported!");
333  }
334  }
335  }
336 }
337 
338 void UsbTunnelInterface::handleHidReportRxQueue()
339 {
340  if (isRxQueueEmpty())
341  {
342  println("Error: RX HID report queue was empty!");
343  return;
344  }
345 
346  uint8_t tpPacket[MAX_KNX_TELEGRAM_SIZE + PROTOCOL_HEADER_LENGTH]; // Transport Protocol Header + Body
347  uint16_t offset = 0;
348  bool success = false;
349 
350  // Now we have to reassemble the whole transport protocol packet which might be distributed over multiple HID reports
351 
352  // In theory we can only have sequence numbers from 1..5
353  // First packet: 51 bytes max
354  // Other packets: 62 bytes max.
355  // -> 51 + 4*62 = 296 bytes -> enough for a KNX cEMI extended frame APDU + Transport Protocol Header length
356  for (int expSeqNum = 1; expSeqNum < 6; expSeqNum++)
357  {
358  // We should have at least one packet: either single packet (START and END set) or
359  // start packet (START and PARTIAL set) -> thus load first part
360  uint8_t* data;
361  uint16_t bufSize;
362  loadNextRxBuffer(&data, &bufSize); // bufSize contains the complete HID report length incl. HID header
363 
364  // Get KNX HID report header details
365  uint8_t seqNum = data[1] >> 4;
366  uint8_t packetType = data[1] & 0x07;
367  uint8_t packetLength = MIN(data[2], bufSize - HID_HEADER_SIZE); // Do not try to read more than we actually have!
368 
369  // Does the received sequence number match the expected one?
370  if (expSeqNum != seqNum)
371  {
372  println("Error: Wrong sequence number!");
373  delete data;
374  continue;
375  }
376 
377  // first RX buffer from queue should contain the first part of the transfer protocol packet
378  if ((expSeqNum == 1) && ((packetType & PACKET_TYPE_START) != PACKET_TYPE_START))
379  {
380  println("Error: Sequence number 1 does not contain a START packet!");
381  delete data;
382  continue;
383  }
384 
385  // Make sure we only have one START packet
386  if ((expSeqNum != 1) && ((packetType & PACKET_TYPE_START) == PACKET_TYPE_START))
387  {
388  println("Error: Sequence number (!=1) contains a START packet!");
389  delete data;
390  continue;
391  }
392 
393  // Make sure other packets are marked correctly as PARTIAL packet
394  if ((expSeqNum != 1) && ((packetType & PACKET_TYPE_PARTIAL) != PACKET_TYPE_PARTIAL))
395  {
396  println("Error: Sequence number (!=1) must be a PARTIAL packet!");
397  delete data;
398  continue;
399  }
400 
401  // Not really necessary, but we reset the offset here to zero
402  if ((packetType & PACKET_TYPE_START) == PACKET_TYPE_START)
403  {
404  offset = 0;
405  }
406 
407  // Copy KNX HID Report Body to final buffer for concatenating
408  memcpy(&tpPacket[offset], &data[3], packetLength);
409  // Remove the source HID report buffer
410  delete data;
411  // Move offset
412  offset += packetLength;
413 
414  // If we reached the end of the transport protocol packet, leave the loop
415  if ((packetType & PACKET_TYPE_END) == PACKET_TYPE_END)
416  {
417  success = true;
418  break;
419  }
420  }
421 
422  // Make sure that we really saw the end of the transport protocol packet
423  if (success)
424  {
425  handleTransferProtocolPacket(tpPacket, offset);
426  }
427  else
428  {
429  println("Error: Did not find END packet!");
430  }
431 }
432 
433 void UsbTunnelInterface::handleBusAccessServerProtocol(ServiceIdType servId, const uint8_t* requestData, uint16_t packetLength)
434 {
435  uint8_t respData[3]; // max. 3 bytes are required for a response
436 
437  switch (servId)
438  {
439  case DeviceFeatureGet: // Device Feature Get
440  {
441  FeatureIdType featureId = (FeatureIdType)requestData[0];
442  respData[0] = (uint8_t) featureId; // first byte in repsonse is the featureId itself again
443 
444  switch (featureId)
445  {
446  case SupportedEmiType: // Supported EMI types
447  println("Device Feature Get: Supported EMI types");
448  respData[1] = 0x00; // USB KNX Transfer Protocol Body: Feature Data
449  respData[2] = 0x04; // USB KNX Transfer Protocol Body: Feature Data -> only cEMI supported
450  sendKnxHidReport(BusAccessServer, DeviceFeatureResponse, respData, 3);
451  break;
452 
453  case HostDeviceDescriptorType0: // Host Device Descriptor Type 0
454  println("Device Feature Get: Host Device Descriptor Type 0");
455  pushWord(_maskVersion, &respData[1]); // USB KNX Transfer Protocol Body: Feature Data -> Mask version
456  sendKnxHidReport(BusAccessServer, DeviceFeatureResponse, respData, 3);
457  break;
458 
459  case BusConnectionStatus: // Bus connection status
460  println("Device Feature Get: Bus connection status");
461  respData[1] = 1; // USB KNX Transfer Protocol Body: Feature Data -> bus connection status
462  sendKnxHidReport(BusAccessServer, DeviceFeatureResponse, respData, 2);
463  break;
464 
465  case KnxManufacturerCode: // KNX manufacturer code
466  println("Device Feature Get: KNX manufacturer code");
467  pushWord(_manufacturerId, &respData[1]); // USB KNX Transfer Protocol Body: Feature Data -> Manufacturer Code
468  sendKnxHidReport(BusAccessServer, DeviceFeatureResponse, respData, 3);
469  break;
470 
471  case ActiveEmiType: // Active EMI type
472  println("Device Feature Get: Active EMI type");
473  respData[1] = (uint8_t) CEMI; // USB KNX Transfer Protocol Body: Feature Data -> cEMI type ID
474  sendKnxHidReport(BusAccessServer, DeviceFeatureResponse, respData, 2);
475  break;
476 
477  default:
478  break;
479  }
480 
481  break;
482  }
483 
484  case DeviceFeatureSet: // Device Feature Set
485  {
486  FeatureIdType featureId = (FeatureIdType)requestData[0];
487 
488  switch (featureId)
489  {
490  case ActiveEmiType: // Active EMI type
491  print("Device Feature Set: Active EMI type: ");
492 
493  if (requestData[1] < 16)
494  print("0");
495 
496  println(requestData[1], HEX); // USB KNX Transfer Protocol Body: Feature Data -> EMI TYPE ID
497  break;
498 
499  // All other featureIds must not be set
500  case SupportedEmiType: // Supported EMI types
501  case HostDeviceDescriptorType0: // Host Device Descriptor Type 0
502  case BusConnectionStatus: // Bus connection status
503  case KnxManufacturerCode: // KNX manufacturer code
504  default:
505  break;
506  }
507 
508  break;
509  }
510 
511  // These are only sent from the device to the host
512  case DeviceFeatureResponse: // Device Feature Response
513  case DeviceFeatureInfo: // Device Feature Info
514  case DeviceFeatureEscape: // reserved (ESCAPE for future extensions)
515  default:
516  break;
517  }
518 }
519 
520 /* USB HID report descriptor for KNX HID */
521 
522 const uint8_t UsbTunnelInterface::descHidReport[] =
523 {
524  //TUD_HID_REPORT_DESC_KNXHID_INOUT(64)
525  0x06, 0xA0, 0xFF, // Usage Page (Vendor Defined 0xFFA0)
526  0x09, 0x01, // Usage (0x01)
527  0xA1, 0x01, // Collection (Application)
528  0x09, 0x01, // Usage (0x01)
529  0xA1, 0x00, // Collection (Physical)
530  0x06, 0xA1, 0xFF, // Usage Page (Vendor Defined 0xFFA1)
531  0x09, 0x03, // Usage (0x03)
532  0x09, 0x04, // Usage (0x04)
533  0x15, 0x80, // Logical Minimum (-128)
534  0x25, 0x7F, // Logical Maximum (127)
535  0x35, 0x00, // Physical Minimum (0)
536  0x45, 0xFF, // Physical Maximum (-1)
537  0x75, 0x08, // Report Size (8)
538  0x85, 0x01, // Report ID (1)
539  0x95, 0x3F, // Report Count (63)
540  0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
541  0x09, 0x05, // Usage (0x05)
542  0x09, 0x06, // Usage (0x06)
543  0x15, 0x80, // Logical Minimum (-128)
544  0x25, 0x7F, // Logical Maximum (127)
545  0x35, 0x00, // Physical Minimum (0)
546  0x45, 0xFF, // Physical Maximum (-1)
547  0x75, 0x08, // Report Size (8)
548  0x85, 0x01, // Report ID (1)
549  0x95, 0x3F, // Report Count (63)
550  0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
551  0xC0, // End Collection
552  0xC0 // End Collection
553 };
554 
556 {
557  return &descHidReport[0];
558 }
559 
561 {
562  return sizeof(descHidReport);
563 }
564 
565 #endif
void print(const char *s)
void println(const char *s)
uint8_t * pushWord(uint16_t w, uint8_t *data)
Definition: bits.cpp:64
const uint8_t * popWord(uint16_t &w, const uint8_t *data)
Definition: bits.cpp:34
uint8_t * data()
Definition: cemi_frame.cpp:195
uint16_t dataLength()
Definition: cemi_frame.cpp:200
This is an implementation of the cEMI server as specified in .
Definition: cemi_server.h:23
void frameReceived(CemiFrame &frame)
static void receiveHidReport(uint8_t const *data, uint16_t bufSize)
void sendCemiFrame(CemiFrame &frame)
UsbTunnelInterface(CemiServer &cemiServer, uint16_t manufacturerId, uint16_t maskVersion)
static const uint8_t * getKnxHidReportDescriptor()
static uint16_t getHidReportDescriptorLength()
bool sendHidReport(uint8_t *data, uint16_t length)
bool isSendHidReportPossible()
@ DeviceFeatureResponse
@ DeviceFeatureInfo
@ DeviceFeatureSet
@ DeviceFeatureGet
@ ServiceIdNotUsed
@ DeviceFeatureEscape
ProtocolIdType
@ BusAccessServer
@ KnxTunneling
@ SupportedEmiType
@ HostDeviceDescriptorType0
@ BusConnectionStatus
@ ActiveEmiType
@ KnxManufacturerCode