knx
ETS configurable knx-stack
cemi_server.cpp
Go to the documentation of this file.
1 #include "config.h"
2 #ifdef USE_CEMI_SERVER
3 
4 #include "cemi_server.h"
5 #include "cemi_frame.h"
6 #include "bau_systemB.h"
7 #include "usb_tunnel_interface.h"
8 #include "data_link_layer.h"
9 #include "string.h"
10 #include "bits.h"
11 #include <stdio.h>
12 
14  : _bau(bau)
15 #ifdef USE_USB
16  ,
17  _usbTunnelInterface(*this,
18  _bau.deviceObject().maskVersion(),
19  _bau.deviceObject().manufacturerId())
20 #endif
21 {
22  // The cEMI server will hand out the device address + 1 to the cEMI client (e.g. ETS),
23  // so that the device and the cEMI client/server connection(tunnel) can operate simultaneously.
24  _clientAddress = _bau.deviceObject().individualAddress() + 1;
25 }
26 
28 {
29  _dataLinkLayer = &layer;
30 }
31 
32 #ifdef KNX_TUNNELING
34 {
35  _dataLinkLayerPrimary = &layer;
36 }
37 
38 #endif
39 uint16_t CemiServer::clientAddress() const
40 {
41  return _clientAddress;
42 }
43 
44 void CemiServer::clientAddress(uint16_t value)
45 {
46  _clientAddress = value;
47 }
48 
50 {
51  MessageCode backupMsgCode = frame.messageCode();
52 
53  frame.messageCode(L_data_con);
54 
55 #ifdef KNX_LOG_TUNNELING
56  print("L_data_con: src: ");
57  print(frame.sourceAddress(), HEX);
58  print(" dst: ");
59  print(frame.destinationAddress(), HEX);
60 
61  printHex(" frame: ", frame.data(), frame.dataLength());
62 #endif
63 
64 #ifdef USE_USB
65  _usbTunnelInterface.sendCemiFrame(frame);
66 #elif defined(KNX_TUNNELING)
67  _dataLinkLayerPrimary->dataConfirmationToTunnel(frame);
68 #endif
69 
70  frame.messageCode(backupMsgCode);
71 }
72 
74 {
75 #ifdef USE_RF
76  bool isRf = _dataLinkLayer->mediumType() == DptMedium::KNX_RF;
77  uint8_t data[frame.dataLength() + (isRf ? 10 : 0)];
78 #else
79  uint8_t data[frame.dataLength()];
80 #endif
81 
82 #ifdef USE_RF
83 
84  if (isRf)
85  {
86  data[0] = L_data_ind; // Message Code
87  data[1] = 0x0A; // Total additional info length
88  data[2] = 0x02; // RF add. info: type
89  data[3] = 0x08; // RF add. info: length
90  data[4] = frame.rfInfo(); // RF add. info: info field (batt ok, bidir)
91  pushByteArray(frame.rfSerialOrDoA(), 6, &data[5]); // RF add. info:Serial or Domain Address
92  data[11] = frame.rfLfn(); // RF add. info: link layer frame number
93  memcpy(&data[12], &((frame.data())[2]), frame.dataLength() - 2);
94  }
95  else
96  {
97 #endif
98  memcpy(&data[0], frame.data(), frame.dataLength());
99 #ifdef USE_RF
100  }
101 
102 #endif
103 
104  CemiFrame tmpFrame(data, sizeof(data));
105 
106 #ifdef KNX_LOG_TUNNELING
107  print("ToTunnel ");
108  print("L_data_ind: src: ");
109  print(tmpFrame.sourceAddress(), HEX);
110  print(" dst: ");
111  print(tmpFrame.destinationAddress(), HEX);
112 
113  printHex(" frame: ", tmpFrame.data(), tmpFrame.dataLength());
114 #endif
115  tmpFrame.apdu().type();
116 
117 #ifdef USE_USB
118  _usbTunnelInterface.sendCemiFrame(tmpFrame);
119 #elif defined(KNX_TUNNELING)
120  _dataLinkLayerPrimary->dataIndicationToTunnel(frame);
121 #endif
122 }
123 
125 {
126  switch (frame.messageCode())
127  {
128  case L_data_req:
129  {
130  handleLData(frame);
131  break;
132  }
133 
134  case M_PropRead_req:
135  {
136  handleMPropRead(frame);
137  break;
138  }
139 
140  case M_PropWrite_req:
141  {
142  handleMPropWrite(frame);
143  break;
144  }
145 
147  {
148  println("M_FuncPropCommand_req not implemented");
149  break;
150  }
151 
153  {
154  println("M_FuncPropStateRead_req not implemented");
155  break;
156  }
157 
158  case M_Reset_req:
159  {
160  handleMReset(frame);
161  break;
162  }
163 
164  // we should never receive these: server -> client
165  case L_data_con:
166  case L_data_ind:
167  case M_PropInfo_ind:
168  case M_PropRead_con:
169  case M_PropWrite_con:
171 
172  //case M_FuncPropStateRead_con: // same value as M_FuncPropCommand_con
173  case M_Reset_ind:
174  default:
175  break;
176  }
177 }
178 
179 void CemiServer::handleLData(CemiFrame& frame)
180 {
181  // Fill in the cEMI client address if the client sets
182  // source address to 0.
183 #ifndef KNX_TUNNELING
184  //We already set the correct IA
185  if (frame.sourceAddress() == 0x0000)
186  {
187  frame.sourceAddress(_clientAddress);
188  }
189 
190 #endif
191 
192 #ifdef USE_RF
193 
194  if (_dataLinkLayer->mediumType() == DptMedium::KNX_RF)
195  {
196  // Check if we have additional info for RF
197  if (((frame.data())[1] == 0x0A) && // Additional info total length: we only handle one additional info of type RF
198  ((frame.data())[2] == 0x02) && // Additional info type: RF
199  ((frame.data())[3] == 0x08) ) // Additional info length of type RF: 8 bytes (fixed)
200  {
201  frame.rfInfo((frame.data())[4]);
202 
203  // Use the values provided in the RF additonal info
204  if ( ((frame.data())[5] != 0x00) || ((frame.data())[6] != 0x00) || ((frame.data())[7] != 0x00) ||
205  ((frame.data())[8] != 0x00) || ((frame.data())[9] != 0x00) || ((frame.data())[10] != 0x00) )
206  {
207  frame.rfSerialOrDoA(&((frame.data())[5]));
208  } // else leave the nullptr as it is
209 
210  frame.rfLfn((frame.data())[11]);
211  }
212 
213  // If the cEMI client does not provide a link layer frame number (LFN),
214  // we use our own counter.
215  // Note: There is another link layer frame number counter inside the RF data link layer class!
216  // That counter is solely for the local application!
217  // If we set a LFN here, the data link layer counter is NOT used!
218  if (frame.rfLfn() == 0xFF)
219  {
220  // Set Data Link Layer Frame Number
221  frame.rfLfn(_frameNumber);
222  // Link Layer frame number counts 0..7
223  _frameNumber = (_frameNumber + 1) & 0x7;
224  }
225  }
226 
227 #endif
228 
229 #ifdef KNX_LOG_TUNNELING
230  print("L_data_req: src: ");
231  print(frame.sourceAddress(), HEX);
232  print(" dst: ");
233  print(frame.destinationAddress(), HEX);
234  printHex(" frame: ", frame.data(), frame.dataLength());
235 #endif
236  _dataLinkLayer->dataRequestFromTunnel(frame);
237 }
238 
239 void CemiServer::handleMPropRead(CemiFrame& frame)
240 {
241 #ifdef KNX_LOG_TUNNELING
242  print("M_PropRead_req: ");
243 #endif
244 
245  uint16_t objectType;
246  popWord(objectType, &frame.data()[1]);
247  uint8_t objectInstance = frame.data()[3];
248  uint8_t propertyId = frame.data()[4];
249  uint8_t numberOfElements = frame.data()[5] >> 4;
250  uint16_t startIndex = frame.data()[6] | ((frame.data()[5] & 0x0F) << 8);
251  uint8_t* data = nullptr;
252  uint32_t dataSize = 0;
253 
254 #ifdef KNX_LOG_TUNNELING
255  print("ObjType: ");
256  print(objectType, DEC);
257  print(" ObjInst: ");
258  print(objectInstance, DEC);
259  print(" PropId: ");
260  print(propertyId, DEC);
261  print(" NoE: ");
262  print(numberOfElements, DEC);
263  print(" startIdx: ");
264  print(startIndex, DEC);
265 #endif
266 
267  // propertyValueRead() allocates memory for the data! Needs to be deleted again!
268  _bau.propertyValueRead((ObjectType)objectType, objectInstance, propertyId, numberOfElements, startIndex, &data, dataSize);
269 
270  // Patch result for device address in device object
271  // The cEMI server will hand out the device address + 1 to the cEMI client (e.g. ETS),
272  // so that the device and the cEMI client/server connection(tunnel) can operate simultaneously.
273  // KNX IP Interfaces which offer multiple simultaneous tunnel connections seem to operate the same way.
274  // Each tunnel has its own cEMI client address which is based on the main device address.
275  if (((ObjectType) objectType == OT_DEVICE) &&
276  (propertyId == PID_DEVICE_ADDR) &&
277  (numberOfElements == 1))
278  {
279  data[0] = (uint8_t) (_clientAddress & 0xFF);
280  }
281  else if (((ObjectType) objectType == OT_DEVICE) &&
282  (propertyId == PID_SUBNET_ADDR) &&
283  (numberOfElements == 1))
284  {
285  data[0] = (uint8_t) ((_clientAddress >> 8) & 0xFF);
286  }
287 
288  if (data && dataSize && numberOfElements)
289  {
290 #ifdef KNX_LOG_TUNNELING
291  printHex(" <- data: ", data, dataSize);
292 #endif
293 
294  // Prepare positive response
295  uint8_t responseData[7 + dataSize];
296  memcpy(responseData, frame.data(), 7);
297  memcpy(&responseData[7], data, dataSize);
298 
299  CemiFrame responseFrame(responseData, sizeof(responseData));
300  responseFrame.messageCode(M_PropRead_con);
301 #ifdef USE_USB
302  _usbTunnelInterface.sendCemiFrame(responseFrame);
303 #elif defined(KNX_TUNNELING)
304  _dataLinkLayerPrimary->dataRequestToTunnel(responseFrame);
305 #endif
306  delete[] data;
307  }
308  else
309  {
310  // Prepare negative response
311  uint8_t responseData[7 + 1];
312  memcpy(responseData, frame.data(), sizeof(responseData));
313  responseData[7] = Void_DP; // Set cEMI error code
314  responseData[5] = 0; // Set Number of elements to zero
315 
316  printHex(" <- error: ", &responseData[7], 1);
317  println("");
318 
319  CemiFrame responseFrame(responseData, sizeof(responseData));
320  responseFrame.messageCode(M_PropRead_con);
321 #ifdef USE_USB
322  _usbTunnelInterface.sendCemiFrame(responseFrame);
323 #elif defined(KNX_TUNNELING)
324  _dataLinkLayerPrimary->dataRequestToTunnel(responseFrame);
325 #endif
326  }
327 }
328 
329 void CemiServer::handleMPropWrite(CemiFrame& frame)
330 {
331  print("M_PropWrite_req: ");
332 
333  uint16_t objectType;
334  popWord(objectType, &frame.data()[1]);
335  uint8_t objectInstance = frame.data()[3];
336  uint8_t propertyId = frame.data()[4];
337  uint8_t numberOfElements = frame.data()[5] >> 4;
338  uint16_t startIndex = frame.data()[6] | ((frame.data()[5] & 0x0F) << 8);
339  uint8_t* requestData = &frame.data()[7];
340  uint32_t requestDataSize = frame.dataLength() - 7;
341 
342  print("ObjType: ");
343  print(objectType, DEC);
344  print(" ObjInst: ");
345  print(objectInstance, DEC);
346  print(" PropId: ");
347  print(propertyId, DEC);
348  print(" NoE: ");
349  print(numberOfElements, DEC);
350  print(" startIdx: ");
351  print(startIndex, DEC);
352 
353  printHex(" -> data: ", requestData, requestDataSize);
354 
355  // Patch request for device address in device object
356  if (((ObjectType) objectType == OT_DEVICE) &&
357  (propertyId == PID_DEVICE_ADDR) &&
358  (numberOfElements == 1))
359  {
360  // Temporarily store new cEMI client address in memory
361  // We also be sent back if the client requests it again
362  _clientAddress = (_clientAddress & 0xFF00) | requestData[0];
363  print("cEMI client address: ");
364  println(_clientAddress, HEX);
365  }
366  else if (((ObjectType) objectType == OT_DEVICE) &&
367  (propertyId == PID_SUBNET_ADDR) &&
368  (numberOfElements == 1))
369  {
370  // Temporarily store new cEMI client address in memory
371  // We also be sent back if the client requests it again
372  _clientAddress = (_clientAddress & 0x00FF) | (requestData[0] << 8);
373  print("cEMI client address: ");
374  println(_clientAddress, HEX);
375  }
376  else
377  {
378  _bau.propertyValueWrite((ObjectType)objectType, objectInstance, propertyId, numberOfElements, startIndex, requestData, requestDataSize);
379  }
380 
381  if (numberOfElements)
382  {
383  // Prepare positive response
384  uint8_t responseData[7];
385  memcpy(responseData, frame.data(), sizeof(responseData));
386 
387  println(" <- no error");
388 
389  CemiFrame responseFrame(responseData, sizeof(responseData));
390  responseFrame.messageCode(M_PropWrite_con);
391 #ifdef USE_USB
392  _usbTunnelInterface.sendCemiFrame(responseFrame);
393 #elif defined(KNX_TUNNELING)
394  _dataLinkLayerPrimary->dataRequestToTunnel(responseFrame);
395 #endif
396  }
397  else
398  {
399  // Prepare negative response
400  uint8_t responseData[7 + 1];
401  memcpy(responseData, frame.data(), sizeof(responseData));
402  responseData[7] = Illegal_Command; // Set cEMI error code
403  responseData[5] = 0; // Set Number of elements to zero
404 
405  printHex(" <- error: ", &responseData[7], 1);
406  println("");
407 
408  CemiFrame responseFrame(responseData, sizeof(responseData));
409  responseFrame.messageCode(M_PropWrite_con);
410 #ifdef USE_USB
411  _usbTunnelInterface.sendCemiFrame(responseFrame);
412 #elif defined(KNX_TUNNELING)
413  _dataLinkLayerPrimary->dataRequestToTunnel(responseFrame);
414 #endif
415  }
416 }
417 
418 void CemiServer::handleMReset(CemiFrame& frame)
419 {
420  println("M_Reset_req: sending M_Reset_ind");
421  // A real device reset does not work for USB or KNXNET/IP.
422  // Thus, M_Reset_ind is NOT mandatory for USB and KNXNET/IP.
423  // We just save all data to the EEPROM
424  _bau.writeMemory();
425  // Prepare response
426  uint8_t responseData[1];
427  CemiFrame responseFrame(responseData, sizeof(responseData));
428  responseFrame.messageCode(M_Reset_ind);
429 #ifdef USE_USB
430  _usbTunnelInterface.sendCemiFrame(responseFrame);
431 #elif defined(KNX_TUNNELING)
432  _dataLinkLayerPrimary->dataRequestToTunnel(responseFrame);
433 #endif
434 }
435 
437 {
438 #ifdef USE_USB
439  _usbTunnelInterface.loop();
440 #endif
441 }
442 
443 #endif
void print(const char *s)
void println(const char *s)
void printHex(const char *suffix, const uint8_t *data, size_t length, bool newline)
Definition: bits.cpp:12
uint8_t * pushByteArray(const uint8_t *src, uint32_t size, uint8_t *data)
Definition: bits.cpp:82
const uint8_t * popWord(uint16_t &w, const uint8_t *data)
Definition: bits.cpp:34
ApduType type()
Get the type of the APDU.
Definition: apdu.cpp:9
void propertyValueRead(ObjectType objectType, uint8_t objectInstance, uint8_t propertyId, uint8_t &numberOfElements, uint16_t startIndex, uint8_t **data, uint32_t &length) override
void writeMemory()
Definition: bau_systemB.cpp:29
void propertyValueWrite(ObjectType objectType, uint8_t objectInstance, uint8_t propertyId, uint8_t &numberOfElements, uint16_t startIndex, uint8_t *data, uint32_t length) override
DeviceObject & deviceObject()
Definition: bau_systemB.cpp:44
uint16_t sourceAddress() const
Definition: cemi_frame.cpp:303
uint8_t * rfSerialOrDoA() const
Definition: cemi_frame.cpp:327
uint8_t * data()
Definition: cemi_frame.cpp:195
uint8_t rfLfn() const
Definition: cemi_frame.cpp:347
APDU & apdu()
Definition: cemi_frame.cpp:367
uint8_t rfInfo() const
Definition: cemi_frame.cpp:337
uint16_t destinationAddress() const
Definition: cemi_frame.cpp:315
MessageCode messageCode() const
Definition: cemi_frame.cpp:126
uint16_t dataLength()
Definition: cemi_frame.cpp:200
void dataConfirmationToTunnel(CemiFrame &frame)
Definition: cemi_server.cpp:49
CemiServer(BauSystemB &bau)
The constructor.
Definition: cemi_server.cpp:13
void dataIndicationToTunnel(CemiFrame &frame)
Definition: cemi_server.cpp:73
void dataLinkLayerPrimary(DataLinkLayer &layer)
Definition: cemi_server.cpp:33
void frameReceived(CemiFrame &frame)
uint16_t clientAddress() const
Definition: cemi_server.cpp:39
void dataLinkLayer(DataLinkLayer &layer)
uint16_t individualAddress()
void sendCemiFrame(CemiFrame &frame)
ObjectType
Enum for the type of an interface object.
@ OT_DEVICE
Device object.
@ Void_DP
Definition: knx_types.h:72
@ Illegal_Command
Definition: knx_types.h:71
MessageCode
Definition: knx_types.h:39
@ M_PropWrite_req
Definition: knx_types.h:48
@ M_Reset_ind
Definition: knx_types.h:60
@ M_PropWrite_con
Definition: knx_types.h:49
@ M_PropInfo_ind
Definition: knx_types.h:50
@ M_Reset_req
Definition: knx_types.h:59
@ L_data_con
Definition: knx_types.h:42
@ L_data_req
Definition: knx_types.h:41
@ M_FuncPropCommand_con
Definition: knx_types.h:54
@ L_data_ind
Definition: knx_types.h:43
@ M_FuncPropCommand_req
Definition: knx_types.h:53
@ M_PropRead_con
Definition: knx_types.h:47
@ M_FuncPropStateRead_req
Definition: knx_types.h:55
@ M_PropRead_req
Definition: knx_types.h:46
@ KNX_RF
Definition: knx_types.h:259
@ PID_DEVICE_ADDR
Definition: property.h:97
@ PID_SUBNET_ADDR
Definition: property.h:96