2 This file is part of CanFestival, a library implementing CanOpen Stack.
4 Copyright (C): Edouard TISSERANT and Francis DUPIN
6 See COPYING file for copyrights details.
8 This library is free software; you can redistribute it and/or
9 modify it under the terms of the GNU Lesser General Public
10 License as published by the Free Software Foundation; either
11 version 2.1 of the License, or (at your option) any later version.
13 This library is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public
19 License along with this library; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 //#define DEBUG_WAR_CONSOLE_ON
24 //#define DEBUG_ERR_CONSOLE_ON
29 /*Internals prototypes*/
31 /** Called by writeNetworkDict */
32 inline UNS8 _writeNetworkDict (CO_Data* d, UNS8 nodeId, UNS16 index,
33 UNS8 subIndex, UNS8 count, UNS8 dataType, void *data, SDOCallback_t Callback);
35 /** Called by readNetworkDict */
36 inline UNS8 _readNetworkDict (CO_Data* d, UNS8 nodeId, UNS16 index, UNS8 subIndex,
37 UNS8 dataType, SDOCallback_t Callback);
40 /***************************************************************************/
41 // SDO (un)packing macros
43 /** Returns the command specifier (cs, ccs, scs) from the first byte of the SDO
45 #define getSDOcs(byte) (byte >> 5)
47 /** Returns the number of bytes without data from the first byte of the SDO. Coded in 2 bits
49 #define getSDOn2(byte) ((byte >> 2) & 3)
51 /** Returns the number of bytes without data from the first byte of the SDO. Coded in 3 bits
53 #define getSDOn3(byte) ((byte >> 1) & 7)
55 /** Returns the transfer type from the first byte of the SDO
57 #define getSDOe(byte) ((byte >> 1) & 1)
59 /** Returns the size indicator from the first byte of the SDO
61 #define getSDOs(byte) (byte & 1)
63 /** Returns the indicator of end transmission from the first byte of the SDO
65 #define getSDOc(byte) (byte & 1)
67 /** Returns the toggle from the first byte of the SDO
69 #define getSDOt(byte) ((byte >> 4) & 1)
71 /** Returns the index from the bytes 1 and 2 of the SDO
73 #define getSDOindex(byte1, byte2) ((byte2 << 8) | (byte1))
75 /** Returns the subIndex from the byte 3 of the SDO
77 #define getSDOsubIndex(byte3) (byte3)
79 /***************************************************************************
82 void SDOTimeoutAlarm(CO_Data* d, UNS32 id)
84 MSG_ERR(0x1A01, "SDO timeout. SDO response not received.", 0);
85 MSG_WAR(0x2A02, "server node : ", d->transfers[id].nodeId);
86 MSG_WAR(0x2A02, " index : ", d->transfers[id].index);
87 MSG_WAR(0x2A02, " subIndex : ", d->transfers[id].subIndex);
88 // Reset timer handler
89 d->transfers[id].timer = TIMER_NONE;
90 // Call the user function to inform of the problem.
91 (*d->SDOtimeoutError)(id);
92 // Sending a SDO abort
93 sendSDOabort(d, d->transfers[id].whoami,
94 d->transfers[id].index, d->transfers[id].subIndex, SDOABT_TIMED_OUT);
99 #define StopSDO_TIMER(id) \
100 MSG_WAR(0x3A05, "StopSDO_TIMER for line : ", line);\
101 d->transfers[id].timer = DelAlarm(d->transfers[id].timer);
103 #define StartSDO_TIMER(id) \
104 MSG_WAR(0x3A06, "StartSDO_TIMER for line : ", line);\
105 d->transfers[id].timer = SetAlarm(d,id,&SDOTimeoutAlarm,MS_TO_TIMEVAL(SDO_TIMEOUT_MS),0);
107 #define RestartSDO_TIMER(id) \
108 MSG_WAR(0x3A07, "restartSDO_TIMER for line : ", line);\
109 if(d->transfers[id].timer != TIMER_NONE) { StopSDO_TIMER(id) StartSDO_TIMER(id) }
111 /***************************************************************************/
112 /** Reset all sdo buffers
114 void resetSDO (CO_Data* d)
118 /* transfer structure initialization */
119 for (j = 0 ; j < SDO_MAX_SIMULTANEOUS_TRANSFERTS ; j++)
123 /***************************************************************************/
124 UNS32 SDOlineToObjdict (CO_Data* d, UNS8 line)
128 MSG_WAR(0x3A08, "Enter in SDOlineToObjdict ", line);
129 size = d->transfers[line].count;
130 errorCode = setODentry(d, d->transfers[line].index, d->transfers[line].subIndex,
131 (void *) d->transfers[line].data, &size, 1);
132 if (errorCode != OD_SUCCESSFUL)
134 MSG_WAR(0x3A08, "exit of SDOlineToObjdict ", line);
139 /***************************************************************************/
140 UNS32 objdictToSDOline (CO_Data* d, UNS8 line)
146 MSG_WAR(0x3A05, "objdict->line index : ", d->transfers[line].index);
147 MSG_WAR(0x3A06, " subIndex : ", d->transfers[line].subIndex);
149 errorCode = getODentry(d, d->transfers[line].index,
150 d->transfers[line].subIndex,
151 (void *)d->transfers[line].data,
152 &size, &dataType, 0);
154 if (errorCode != OD_SUCCESSFUL)
157 d->transfers[line].count = size;
158 d->transfers[line].offset = 0;
160 // Me laisser ça, please ! (FD)
163 for (i = 0 ; i < 10 ; i++) {
164 MSG_WAR(i, "data= ", d->transfers[line].data[i]);
171 /***************************************************************************/
172 UNS8 lineToSDO (CO_Data* d, UNS8 line, UNS8 nbBytes, UNS8* data) {
176 if ((d->transfers[line].offset + nbBytes) > SDO_MAX_LENGTH_TRANSFERT) {
177 MSG_ERR(0x1A10,"SDO Size of data too large. Exceed SDO_MAX_LENGTH_TRANSFERT", nbBytes);
180 if ((d->transfers[line].offset + nbBytes) > d->transfers[line].count) {
181 MSG_ERR(0x1A11,"SDO Size of data too large. Exceed count", nbBytes);
184 offset = d->transfers[line].offset;
185 for (i = 0 ; i < nbBytes ; i++)
186 * (data + i) = d->transfers[line].data[offset + i];
187 d->transfers[line].offset = d->transfers[line].offset + nbBytes;
192 /***************************************************************************/
193 UNS8 SDOtoLine (CO_Data* d, UNS8 line, UNS8 nbBytes, UNS8* data)
198 if ((d->transfers[line].offset + nbBytes) > SDO_MAX_LENGTH_TRANSFERT) {
199 MSG_ERR(0x1A15,"SDO Size of data too large. Exceed SDO_MAX_LENGTH_TRANSFERT", nbBytes);
202 offset = d->transfers[line].offset;
203 for (i = 0 ; i < nbBytes ; i++)
204 d->transfers[line].data[offset + i] = * (data + i);
205 d->transfers[line].offset = d->transfers[line].offset + nbBytes;
209 /***************************************************************************/
210 UNS8 failedSDO (CO_Data* d, UNS8 nodeId, UNS8 whoami, UNS16 index,
211 UNS8 subIndex, UNS32 abortCode)
215 err = getSDOlineOnUse( d, nodeId, whoami, &line );
216 if (!err) // If a line on use have been found.
217 MSG_WAR(0x3A20, "FailedSDO : line found : ", line);
218 if ((! err) && (whoami == SDO_SERVER)) {
219 resetSDOline( d, line );
220 MSG_WAR(0x3A21, "FailedSDO : line released : ", line);
222 if ((! err) && (whoami == SDO_CLIENT)) {
224 d->transfers[line].state = SDO_ABORTED_INTERNAL;
226 MSG_WAR(0x3A22, "Sending SDO abort ", 0);
227 err = sendSDOabort(d, whoami, index, subIndex, abortCode);
229 MSG_WAR(0x3A23, "Unable to send the SDO abort", 0);
235 /***************************************************************************/
236 void resetSDOline ( CO_Data* d, UNS8 line )
239 MSG_WAR(0x3A25, "reset SDO line nb : ", line);
240 initSDOline(d, line, 0, 0, 0, SDO_RESET);
241 for (i = 0 ; i < SDO_MAX_LENGTH_TRANSFERT ; i++)
242 d->transfers[line].data[i] = 0;
245 /***************************************************************************/
246 UNS8 initSDOline (CO_Data* d, UNS8 line, UNS8 nodeId, UNS16 index, UNS8 subIndex, UNS8 state)
248 MSG_WAR(0x3A25, "init SDO line nb : ", line);
249 if (state == SDO_DOWNLOAD_IN_PROGRESS || state == SDO_UPLOAD_IN_PROGRESS){
254 d->transfers[line].nodeId = nodeId;
255 d->transfers[line].index = index;
256 d->transfers[line].subIndex = subIndex;
257 d->transfers[line].state = state;
258 d->transfers[line].toggle = 0;
259 d->transfers[line].count = 0;
260 d->transfers[line].offset = 0;
261 d->transfers[line].dataType = 0;
262 d->transfers[line].Callback = NULL;
266 /***************************************************************************/
267 UNS8 getSDOfreeLine ( CO_Data* d, UNS8 whoami, UNS8 *line )
272 for (i = 0 ; i < SDO_MAX_SIMULTANEOUS_TRANSFERTS ; i++){
273 if ( d->transfers[i].state == SDO_RESET ) {
275 d->transfers[i].whoami = whoami;
279 MSG_ERR(0x1A25, "Too many SDO in progress. Aborted.", i);
283 /***************************************************************************/
284 UNS8 getSDOlineOnUse (CO_Data* d, UNS8 nodeId, UNS8 whoami, UNS8 *line)
289 for (i = 0 ; i < SDO_MAX_SIMULTANEOUS_TRANSFERTS ; i++){
290 if ( (d->transfers[i].state != SDO_RESET) &&
291 (d->transfers[i].nodeId == nodeId) &&
292 (d->transfers[i].whoami == whoami) ) {
300 /***************************************************************************/
301 UNS8 closeSDOtransfer (CO_Data* d, UNS8 nodeId, UNS8 whoami)
305 err = getSDOlineOnUse(d, nodeId, whoami, &line);
307 MSG_WAR(0x2A30, "No SDO communication to close for node : ", nodeId);
310 resetSDOline(d, line);
314 /***************************************************************************/
315 UNS8 getSDOlineRestBytes (CO_Data* d, UNS8 line, UNS8 * nbBytes)
317 if (d->transfers[line].count == 0) // if received initiate SDO protocol with e=0 and s=0
320 * nbBytes = d->transfers[line].count - d->transfers[line].offset;
324 /***************************************************************************/
325 UNS8 setSDOlineRestBytes (CO_Data* d, UNS8 line, UNS8 nbBytes)
327 if (nbBytes > SDO_MAX_LENGTH_TRANSFERT) {
328 MSG_ERR(0x1A35,"SDO Size of data too large. Exceed SDO_MAX_LENGTH_TRANSFERT", nbBytes);
331 d->transfers[line].count = nbBytes;
336 /***************************************************************************/
337 UNS8 sendSDO (CO_Data* d, UNS8 whoami, s_SDO sdo)
344 UNS32 * pwCobId = NULL;
345 UNS8 * pwNodeId = NULL;
350 MSG_WAR(0x3A38, "sendSDO",0);
351 if( !((d->nodeState == Operational) || (d->nodeState == Pre_operational ))) {
352 MSG_WAR(0x2A39, "unable to send the SDO (not in op or pre-op mode", d->nodeState);
356 /*get the server->client cobid*/
357 if ( whoami == SDO_SERVER ) {/*case server. Easy because today only one server SDO is authorized in CanFestival*/
358 offset = d->firstIndex->SDO_SVR;
360 MSG_ERR(0x1A42, "SendSDO : No SDO server found", 0);
363 pwCobId = d->objdict[offset].pSubindex[2].pObject;
364 MSG_WAR(0x3A41, "I am server. cobId : ", *pwCobId);
366 else { /*case client*/
367 /* Get the client->server cobid.*/
369 offset = d->firstIndex->SDO_CLT;
370 lastIndex = d->lastIndex->SDO_CLT;
372 MSG_ERR(0x1A42, "SendSDO : No SDO client index found", 0);
375 /* First, have to find at the index where is defined the communication with the server node */
376 while (offset <= lastIndex){
377 MSG_WAR(0x3A43,"Reading index : ", 0x1280 + sdoNum);
378 if (d->objdict[offset].bSubCount <= 3) {
379 MSG_ERR(0x1A28, "Subindex 3 not found at index ", 0x1280 + sdoNum);
382 pwNodeId = d->objdict[offset].pSubindex[3].pObject;
383 MSG_WAR(0x3A44, "Found nodeId server = ", *pwNodeId);
384 if(*pwNodeId == sdo.nodeId) {
392 MSG_WAR (0x2A45, "No SDO client corresponds to the mesage to send to node ", sdo.nodeId);
395 /* Second, read the cobid client->server */
396 pwCobId = d->objdict[offset].pSubindex[1].pObject;
398 /* message copy for sending */
399 m.cob_id.w = *pwCobId;
400 m.rtr = NOT_A_REQUEST;
401 //the length of SDO must be 8
403 for (i = 0 ; i < 8 ; i++) {
404 m.data[i] = sdo.body.data[i];
406 return (*d->canSend)(&m);
409 /***************************************************************************/
410 UNS8 sendSDOabort (CO_Data* d, UNS8 whoami, UNS16 index, UNS8 subIndex, UNS32 abortCode)
414 MSG_WAR(0x2A50,"Sending SDO abort ", abortCode);
415 sdo.nodeId = *d->bDeviceNodeId;
416 sdo.body.data[0] = 0x80;
418 sdo.body.data[1] = index & 0xFF; // LSB
419 sdo.body.data[2] = (index >> 8) & 0xFF; // MSB
421 sdo.body.data[3] = subIndex;
423 sdo.body.data[4] = (UNS8)(abortCode & 0xFF);
424 sdo.body.data[5] = (UNS8)((abortCode >> 8) & 0xFF);
425 sdo.body.data[6] = (UNS8)((abortCode >> 16) & 0xFF);
426 sdo.body.data[7] = (UNS8)((abortCode >> 24) & 0xFF);
427 ret = sendSDO(d, whoami, sdo);
432 /***************************************************************************/
433 UNS8 proceedSDO (CO_Data* d, Message *m)
437 UNS8 nbBytes; // received or to be transmited.
438 UNS8 nodeId = 0; // The node from which the SDO is received
439 UNS8 *pNodeId = NULL;
440 UNS8 whoami = SDO_UNKNOWN; // SDO_SERVER or SDO_CLIENT.
441 UNS32 errorCode; // while reading or writing in the local object dictionary.
442 s_SDO sdo; // SDO to transmit
447 UNS32 * pCobId = NULL;
455 MSG_WAR(0x3A60, "proceedSDO ", 0);
456 whoami = SDO_UNKNOWN;
457 // Looking for the cobId in the object dictionary.
459 offset = d->firstIndex->SDO_SVR;
460 lastIndex = d->lastIndex->SDO_SVR;
462 if(offset) while (offset <= lastIndex) {
463 if (d->objdict[offset].bSubCount <= 1) {
464 MSG_ERR(0x1A61, "Subindex 1 not found at index ", 0x1200 + j);
467 pCobId = d->objdict[offset].pSubindex[1].pObject;
468 if ( *pCobId == (*m).cob_id.w ) {
470 MSG_WAR(0x3A62, "proceedSDO. I am server. index : ", 0x1200 + j);
471 // In case of server, the node id of the client may be unknown. So we put the index minus offset
472 // 0x1200 where the cobid received is defined.
479 if (whoami == SDO_UNKNOWN) {
481 offset = d->firstIndex->SDO_CLT;
482 lastIndex = d->lastIndex->SDO_CLT;
484 if(offset) while (offset <= lastIndex) {
485 if (d->objdict[offset].bSubCount <= 3) {
486 MSG_ERR(0x1A63, "Subindex 3 not found at index ", 0x1280 + j);
489 // a) Looking for the cobid received.
490 pCobId = d->objdict[offset].pSubindex[2].pObject;
491 if (*pCobId == (*m).cob_id.w ) {
492 // b) cobid found, so reading the node id of the server.
493 pNodeId = d->objdict[offset].pSubindex[3].pObject;
496 MSG_WAR(0x3A64, "proceedSDO. I am server. index : ", 0x1280 + j);
497 MSG_WAR(0x3A65, " Server nodeId : ", nodeId);
504 if (whoami == SDO_UNKNOWN) {
505 return 0xFF;// This SDO was not for us !
508 // Test if the size of the SDO is ok
509 if ( (*m).len != 8) {
510 MSG_ERR(0x1A67, "Error size SDO. CobId : ", (*m).cob_id.w);
511 failedSDO(d, nodeId, whoami, 0, 0, SDOABT_GENERAL_ERROR);
515 if (whoami == SDO_CLIENT) {
516 MSG_WAR(0x3A68, "I am CLIENT. Received SDO from nodeId : ", nodeId);
519 MSG_WAR(0x3A69, "I am SERVER. Received SDO cobId : ", (*m).cob_id.w);
522 // Testing the command specifier
523 // Allowed : cs = 0, 1, 2, 3, 4. (= all except those for block tranfert).
524 // cs = other : Not allowed -> abort.
525 switch (getSDOcs(m->data[0])) {
529 if (whoami == SDO_SERVER) {
530 // Receiving a download segment data.
531 // A SDO transfert should have been yet initiated.
532 err = getSDOlineOnUse( d, nodeId, whoami, &line );
534 err = d->transfers[line].state != SDO_DOWNLOAD_IN_PROGRESS;
536 MSG_ERR(0x1A70, "SDO error : Received download segment for unstarted trans. index 0x1200 + ",
538 failedSDO(d, nodeId, whoami, 0, 0, SDOABT_LOCAL_CTRL_ERROR);
542 RestartSDO_TIMER(line)
543 MSG_WAR(0x3A71, "Received SDO download segment defined at index 0x1200 + ", nodeId);
544 index = d->transfers[line].index;
545 subIndex = d->transfers[line].subIndex;
547 if (d->transfers[line].toggle != getSDOt(m->data[0])) {
548 MSG_ERR(0x1A72, "SDO error : Toggle error : ", getSDOt(m->data[0]));
549 failedSDO(d, nodeId, whoami, index, subIndex, SDOABT_TOGGLE_NOT_ALTERNED);
552 // Nb of data to be downloaded
553 nbBytes = 7 - getSDOn3(m->data[0]);
554 // Store the data in the transfert structure.
555 err = SDOtoLine(d, line, nbBytes, (*m).data + 1);
557 failedSDO(d, nodeId, whoami, index, subIndex, SDOABT_GENERAL_ERROR);
560 // Sending the SDO response, CS = 1
561 sdo.nodeId = *d->bDeviceNodeId; // The node id of the server, (here it is the sender).
562 sdo.body.data[0] = (1 << 5) | (d->transfers[line].toggle << 4);
563 for (i = 1 ; i < 8 ; i++)
564 sdo.body.data[i] = 0;
565 MSG_WAR(0x3A73, "SDO. Send response to download request defined at index 0x1200 + ", nodeId);
566 sendSDO(d, whoami, sdo);
567 // Inverting the toggle for the next segment.
568 d->transfers[line].toggle = ! d->transfers[line].toggle & 1;
569 // If it was the last segment,
570 if (getSDOc(m->data[0])) {
571 // Transfering line data to object dictionary.
572 // The code does not use the "d" of initiate frame. So it is safe if e=s=0
573 errorCode = SDOlineToObjdict(d, line);
575 MSG_ERR(0x1A54, "SDO error : Unable to copy the data in the object dictionary", 0);
576 failedSDO(d, nodeId, whoami, index, subIndex, errorCode);
579 // Release of the line
580 resetSDOline(d, line);
581 MSG_WAR(0x3A74, "SDO. End of download defined at index 0x1200 + ", nodeId);
586 // It is a request for a previous upload segment. We should find a line opened for this.
587 err = getSDOlineOnUse( d, nodeId, whoami, &line);
589 err = d->transfers[line].state != SDO_UPLOAD_IN_PROGRESS;
591 MSG_ERR(0x1A75, "SDO error : Received segment response for unknown trans. from nodeId", nodeId);
592 failedSDO(d, nodeId, whoami, 0, 0, SDOABT_LOCAL_CTRL_ERROR);
596 RestartSDO_TIMER(line)
597 index = d->transfers[line].index;
598 subIndex = d->transfers[line].subIndex;
599 // test of the toggle;
600 if (d->transfers[line].toggle != getSDOt(m->data[0])) {
601 MSG_ERR(0x1A76, "SDO error : Received segment response Toggle error. from nodeId", nodeId);
602 failedSDO(d, nodeId, whoami, index, subIndex, SDOABT_TOGGLE_NOT_ALTERNED);
605 // nb of data to be uploaded
606 nbBytes = 7 - getSDOn3(m->data[0]);
607 // Storing the data in the line structure.
608 err = SDOtoLine(d, line, nbBytes, (*m).data + 1);
610 failedSDO(d, nodeId, whoami, index, subIndex, SDOABT_GENERAL_ERROR);
613 // Inverting the toggle for the next segment.
614 d->transfers[line].toggle = ! d->transfers[line].toggle & 1;
615 // If it was the last segment,
616 if ( getSDOc(m->data[0])) {
617 // Put in state finished
618 // The code is safe for the case e=s=0 in initiate frame.
620 d->transfers[line].state = SDO_FINISHED;
621 if(d->transfers[line].Callback) (*d->transfers[line].Callback)(d,nodeId);
623 MSG_WAR(0x3A77, "SDO. End of upload from node : ", nodeId);
625 else { // more segments to receive
626 // Sending the request for the next segment.
628 sdo.body.data[0] = (3 << 5) | (d->transfers[line].toggle << 4);
629 for (i = 1 ; i < 8 ; i++)
630 sdo.body.data[i] = 0;
631 sendSDO(d, whoami, sdo);
632 MSG_WAR(0x3A78, "SDO send upload segment request to nodeId", nodeId);
639 // Receive of an initiate download
640 if (whoami == SDO_SERVER) {
641 index = getSDOindex(m->data[1],m->data[2]);
642 subIndex = getSDOsubIndex(m->data[3]);
643 MSG_WAR(0x3A79, "Received SDO Initiate Download (to store data) defined at index 0x1200 + ",
645 MSG_WAR(0x3A80, "Writing at index : ", index);
646 MSG_WAR(0x3A80, "Writing at subIndex : ", subIndex);
648 // Search if a SDO transfert have been yet initiated
649 err = getSDOlineOnUse( d, nodeId, whoami, &line );
651 MSG_ERR(0x1A81, "SDO error : Transmission yet started.", 0);
652 failedSDO(d, nodeId, whoami, index, subIndex, SDOABT_LOCAL_CTRL_ERROR);
655 // No line on use. Great !
656 // Try to open a new line.
657 err = getSDOfreeLine( d, whoami, &line );
659 MSG_ERR(0x1A82, "SDO error : No line free, too many SDO in progress. Aborted.", 0);
660 failedSDO(d, nodeId, whoami, index, subIndex, SDOABT_LOCAL_CTRL_ERROR);
663 initSDOline(d, line, nodeId, index, subIndex, SDO_DOWNLOAD_IN_PROGRESS);
665 if (getSDOe(m->data[0])) { // If SDO expedited
666 // nb of data to be downloaded
667 nbBytes = 4 - getSDOn2(m->data[0]);
668 // Storing the data in the line structure.
669 d->transfers[line].count = nbBytes;
670 err = SDOtoLine(d, line, nbBytes, (*m).data + 4);
673 failedSDO(d, nodeId, whoami, index, subIndex, SDOABT_GENERAL_ERROR);
677 // SDO expedited -> transfert finished. Data can be stored in the dictionary.
678 // The line will be reseted when it is downloading in the dictionary.
679 MSG_WAR(0x3A83, "SDO Initiate Download is an expedited transfert. Finished.: ", nodeId);
680 // Transfering line data to object dictionary.
681 errorCode = SDOlineToObjdict(d, line);
683 MSG_ERR(0x1A84, "SDO error : Unable to copy the data in the object dictionary", 0);
684 failedSDO(d, nodeId, whoami, index, subIndex, errorCode);
687 // Release of the line.
688 resetSDOline(d, line);
690 else {// So, if it is not an expedited transfert
691 if (getSDOs(m->data[0])) {
692 // TODO : if e and s = 0, not reading m->data[4] but put nbBytes = 0
693 nbBytes = m->data[4]; // Transfert limited to 255 bytes.
694 err = setSDOlineRestBytes(d, nodeId, nbBytes);
696 failedSDO(d, nodeId, whoami, index, subIndex, SDOABT_GENERAL_ERROR);
701 //Sending a SDO, cs=3
702 sdo.nodeId = *d->bDeviceNodeId; // The node id of the server, (here it is the sender).
703 sdo.body.data[0] = 3 << 5;
704 sdo.body.data[1] = index & 0xFF; // LSB
705 sdo.body.data[2] = (index >> 8) & 0xFF; // MSB
706 sdo.body.data[3] = subIndex;
707 for (i = 4 ; i < 8 ; i++)
708 sdo.body.data[i] = 0;
709 sendSDO(d, whoami, sdo);
710 } // end if I am SERVER
713 // It is a response for a previous download segment. We should find a line opened for this.
714 err = getSDOlineOnUse( d, nodeId, whoami, &line);
716 err = d->transfers[line].state != SDO_DOWNLOAD_IN_PROGRESS;
718 MSG_ERR(0x1A85, "SDO error : Received segment response for unknown trans. from nodeId", nodeId);
719 failedSDO(d, nodeId, whoami, 0, 0, SDOABT_LOCAL_CTRL_ERROR);
723 RestartSDO_TIMER(line)
724 index = d->transfers[line].index;
725 subIndex = d->transfers[line].subIndex;
726 // test of the toggle;
727 if (d->transfers[line].toggle != getSDOt(m->data[0])) {
728 MSG_ERR(0x1A86, "SDO error : Received segment response Toggle error. from nodeId", nodeId);
729 failedSDO(d, nodeId, whoami, index, subIndex, SDOABT_TOGGLE_NOT_ALTERNED);
733 // End transmission or downloading next segment. We need to know if it will be the last one.
734 getSDOlineRestBytes(d, line, &nbBytes);
736 MSG_WAR(0x3A87, "SDO End download. segment response received. OK. from nodeId", nodeId);
738 d->transfers[line].state = SDO_FINISHED;
739 if(d->transfers[line].Callback) (*d->transfers[line].Callback)(d,nodeId);
742 // At least one transfer to send.
744 // several segments to download.
745 // code to send the next segment. (cs = 0; c = 0)
746 d->transfers[line].toggle = ! d->transfers[line].toggle & 1;
747 sdo.nodeId = nodeId; // The server node Id;
748 sdo.body.data[0] = (d->transfers[line].toggle << 4);
749 err = lineToSDO(d, line, 7, sdo.body.data + 1);
751 failedSDO(d, nodeId, whoami, index, subIndex, SDOABT_GENERAL_ERROR);
757 // code to send the last segment. (cs = 0; c = 1)
758 d->transfers[line].toggle = ! d->transfers[line].toggle & 1;
759 sdo.nodeId = nodeId; // The server node Id;
760 sdo.body.data[0] = (d->transfers[line].toggle << 4) | ((7 - nbBytes) << 1) | 1;
761 err = lineToSDO(d, line, nbBytes, sdo.body.data + 1);
763 failedSDO(d, nodeId, whoami, index, subIndex, SDOABT_GENERAL_ERROR);
766 for (i = nbBytes + 1 ; i < 8 ; i++)
767 sdo.body.data[i] = 0;
769 MSG_WAR(0x3A88, "SDO sending download segment to nodeId", nodeId);
770 sendSDO(d, whoami, sdo);
771 } // end if I am a CLIENT
776 // Receive of an initiate upload.
777 if (whoami == SDO_SERVER) {
778 index = getSDOindex(m->data[1],m->data[2]);
779 subIndex = getSDOsubIndex(m->data[3]);
780 MSG_WAR(0x3A89, "Received SDO Initiate upload (to send data) defined at index 0x1200 + ",
782 MSG_WAR(0x3A90, "Reading at index : ", index);
783 MSG_WAR(0x3A91, "Reading at subIndex : ", subIndex);
784 // Search if a SDO transfert have been yet initiated
785 err = getSDOlineOnUse( d, nodeId, whoami, &line );
787 MSG_ERR(0x1A92, "SDO error : Transmission yet started at line : ", line);
788 MSG_WAR(0x3A93, "nodeId = ", nodeId);
789 failedSDO(d, nodeId, whoami, index, subIndex, SDOABT_LOCAL_CTRL_ERROR);
792 // No line on use. Great !
793 // Try to open a new line.
794 err = getSDOfreeLine( d, whoami, &line );
796 MSG_ERR(0x1A71, "SDO error : No line free, too many SDO in progress. Aborted.", 0);
797 failedSDO(d, nodeId, whoami, index, subIndex, SDOABT_LOCAL_CTRL_ERROR);
800 initSDOline(d, line, nodeId, index, subIndex, SDO_UPLOAD_IN_PROGRESS);
801 // Transfer data from dictionary to the line structure.
802 errorCode = objdictToSDOline(d, line);
805 MSG_ERR(0x1A94, "SDO error : Unable to copy the data from object dictionary. Err code : ",
807 failedSDO(d, nodeId, whoami, index, subIndex, errorCode);
810 // Preparing the response.
811 getSDOlineRestBytes(d, line, &nbBytes); // Nb bytes to transfer ?
812 sdo.nodeId = nodeId; // The server node Id;
814 // normal transfert. (segmented).
815 // code to send the initiate upload response. (cs = 2)
816 sdo.body.data[0] = (2 << 5) | 1;
817 sdo.body.data[1] = index & 0xFF; // LSB
818 sdo.body.data[2] = (index >> 8) & 0xFF; // MSB
819 sdo.body.data[3] = subIndex;
820 sdo.body.data[4] = nbBytes; // Limitation of canfestival2 : Max tranfert is 256 bytes.
821 // It takes too much memory to upgrate to 2^32 because the size of data is also coded
822 // in the object dictionary, at every index and subindex.
823 for (i = 5 ; i < 8 ; i++)
824 sdo.body.data[i] = 0;
825 MSG_WAR(0x3A95, "SDO. Sending normal upload initiate response defined at index 0x1200 + ", nodeId);
826 sendSDO(d, whoami, sdo);
829 // Expedited upload. (cs = 2 ; e = 1)
830 sdo.body.data[0] = (2 << 5) | ((4 - nbBytes) << 2) | 3;
831 sdo.body.data[1] = index & 0xFF; // LSB
832 sdo.body.data[2] = (index >> 8) & 0xFF; // MSB
833 sdo.body.data[3] = subIndex;
834 err = lineToSDO(d, line, nbBytes, sdo.body.data + 4);
836 failedSDO(d, nodeId, whoami, index, subIndex, SDOABT_GENERAL_ERROR);
839 for (i = 4 + nbBytes ; i < 8 ; i++)
840 sdo.body.data[i] = 0;
841 MSG_WAR(0x3A96, "SDO. Sending expedited upload initiate response defined at index 0x1200 + ",
843 sendSDO(d, whoami, sdo);
845 resetSDOline(d, line);
847 } // end if I am SERVER
850 // It is the response for the previous initiate upload request.
851 // We should find a line opened for this.
852 err = getSDOlineOnUse( d, nodeId, whoami, &line);
854 err = d->transfers[line].state != SDO_UPLOAD_IN_PROGRESS;
856 MSG_ERR(0x1A97, "SDO error : Received response for unknown upload request from nodeId", nodeId);
857 failedSDO(d, nodeId, whoami, 0, 0, SDOABT_LOCAL_CTRL_ERROR);
861 RestartSDO_TIMER(line)
862 index = d->transfers[line].index;
863 subIndex = d->transfers[line].subIndex;
865 if (getSDOe(m->data[0])) { // If SDO expedited
866 // nb of data to be uploaded
867 nbBytes = 4 - getSDOn2(m->data[0]);
868 // Storing the data in the line structure.
869 err = SDOtoLine(d, line, nbBytes, (*m).data + 4);
871 failedSDO(d, nodeId, whoami, index, subIndex, SDOABT_GENERAL_ERROR);
874 // SDO expedited -> transfert finished. data are available via getReadResultNetworkDict().
875 MSG_WAR(0x3A98, "SDO expedited upload finished. Response received from node : ", nodeId);
877 d->transfers[line].count = nbBytes;
878 d->transfers[line].state = SDO_FINISHED;
879 if(d->transfers[line].Callback) (*d->transfers[line].Callback)(d,nodeId);
882 else { // So, if it is not an expedited transfert
883 // Storing the nb of data to receive.
884 if (getSDOs(m->data[0])) {
885 nbBytes = m->data[4]; // Remember the limitation to 255 bytes to transfert
886 err = setSDOlineRestBytes(d, line, nbBytes);
888 failedSDO(d, nodeId, whoami, index, subIndex, SDOABT_GENERAL_ERROR);
892 // Requesting next segment. (cs = 3)
894 sdo.body.data[0] = 3 << 5;
895 for (i = 1 ; i < 8 ; i++)
896 sdo.body.data[i] = 0;
897 MSG_WAR(0x3A99, "SDO. Sending upload segment request to node : ", nodeId);
898 sendSDO(d, whoami, sdo);
905 if (whoami == SDO_SERVER) {
906 // Receiving a upload segment.
907 // A SDO transfert should have been yet initiated.
908 err = getSDOlineOnUse( d, nodeId, whoami, &line );
910 err = d->transfers[line].state != SDO_UPLOAD_IN_PROGRESS;
912 MSG_ERR(0x1AA0, "SDO error : Received upload segment for unstarted trans. index 0x1200 + ",
914 failedSDO(d, nodeId, whoami, 0, 0, SDOABT_LOCAL_CTRL_ERROR);
918 RestartSDO_TIMER(line)
919 MSG_WAR(0x3AA1, "Received SDO upload segment defined at index 0x1200 + ", nodeId);
920 index = d->transfers[line].index;
921 subIndex = d->transfers[line].subIndex;
923 if (d->transfers[line].toggle != getSDOt(m->data[0])) {
924 MSG_ERR(0x1AA2, "SDO error : Toggle error : ", getSDOt(m->data[0]));
925 failedSDO(d, nodeId, whoami, index, subIndex, SDOABT_TOGGLE_NOT_ALTERNED);
928 // Uploading next segment. We need to know if it will be the last one.
929 getSDOlineRestBytes(d, line, &nbBytes);
931 // The segment to transfer is not the last one.
932 // code to send the next segment. (cs = 0; c = 0)
933 sdo.nodeId = nodeId; // The server node Id;
934 sdo.body.data[0] = (d->transfers[line].toggle << 4);
935 err = lineToSDO(d, line, 7, sdo.body.data + 1);
937 failedSDO(d, nodeId, whoami, index, subIndex, SDOABT_GENERAL_ERROR);
940 // Inverting the toggle for the next tranfert.
941 d->transfers[line].toggle = ! d->transfers[line].toggle & 1;
942 MSG_WAR(0x3AA3, "SDO. Sending upload segment defined at index 0x1200 + ", nodeId);
943 sendSDO(d, whoami, sdo);
947 // code to send the last segment. (cs = 0; c = 1)
948 sdo.nodeId = nodeId; // The server node Id;
949 sdo.body.data[0] = (d->transfers[line].toggle << 4) | ((7 - nbBytes) << 1) | 1;
950 err = lineToSDO(d, line, nbBytes, sdo.body.data + 1);
952 failedSDO(d, nodeId, whoami, index, subIndex, SDOABT_GENERAL_ERROR);
955 for (i = nbBytes + 1 ; i < 8 ; i++)
956 sdo.body.data[i] = 0;
957 MSG_WAR(0x3AA4, "SDO. Sending last upload segment defined at index 0x1200 + ", nodeId);
958 sendSDO(d, whoami, sdo);
960 resetSDOline(d, line);
965 // It is the response for the previous initiate download request.
966 // We should find a line opened for this.
967 err = getSDOlineOnUse( d, nodeId, whoami, &line);
969 err = d->transfers[line].state != SDO_DOWNLOAD_IN_PROGRESS;
971 MSG_ERR(0x1AA5, "SDO error : Received response for unknown download request from nodeId", nodeId);
972 failedSDO(d, nodeId, whoami, 0, 0, SDOABT_LOCAL_CTRL_ERROR);
976 RestartSDO_TIMER(line)
977 index = d->transfers[line].index;
978 subIndex = d->transfers[line].subIndex;
979 // End transmission or requesting next segment.
980 getSDOlineRestBytes(d, line, &nbBytes);
982 MSG_WAR(0x3AA6, "SDO End download expedited. Response received. from nodeId", nodeId);
984 d->transfers[line].state = SDO_FINISHED;
985 if(d->transfers[line].Callback) (*d->transfers[line].Callback)(d,nodeId);
989 // more than one request to send
990 // code to send the next segment. (cs = 0; c = 0)
991 sdo.nodeId = nodeId; // The server node Id;
992 sdo.body.data[0] = (d->transfers[line].toggle << 4);
993 err = lineToSDO(d, line, 7, sdo.body.data + 1);
995 failedSDO(d, nodeId, whoami, index, subIndex, SDOABT_GENERAL_ERROR);
1001 // code to send the last segment. (cs = 0; c = 1)
1002 sdo.nodeId = nodeId; // The server node Id;
1003 sdo.body.data[0] = (d->transfers[line].toggle << 4) | ((7 - nbBytes) << 1) | 1;
1004 err = lineToSDO(d, line, nbBytes, sdo.body.data + 1);
1006 failedSDO(d, nodeId, whoami, index, subIndex, SDOABT_GENERAL_ERROR);
1009 for (i = nbBytes + 1 ; i < 8 ; i++)
1010 sdo.body.data[i] = 0;
1012 MSG_WAR(0x3AA7, "SDO sending download segment to nodeId", nodeId);
1013 sendSDO(d, whoami, sdo);
1015 } // end if I am a CLIENT
1019 abortCode = (*m).data[3] |
1021 (m->data[6] << 16) |
1023 // Received SDO abort.
1024 // Looking for the line concerned.
1025 if (whoami == SDO_SERVER) {
1026 err = getSDOlineOnUse( d, nodeId, whoami, &line );
1028 resetSDOline( d, line );
1029 MSG_WAR(0x3AA8, "SD0. Received SDO abort. Line released. Code : ", abortCode);
1032 MSG_WAR(0x3AA9, "SD0. Received SDO abort. No line found. Code : ", abortCode);
1033 // Tips : The end user has no way to know that the server node has received an abort SDO.
1034 // Its is ok, I think.
1036 else { // If I am CLIENT
1037 err = getSDOlineOnUse( d, nodeId, whoami, &line );
1039 // The line *must* be released by the core program.
1041 d->transfers[line].state = SDO_ABORTED_RCV;
1042 MSG_WAR(0x3AB0, "SD0. Received SDO abort. Line state ABORTED. Code : ", abortCode);
1045 MSG_WAR(0x3AB1, "SD0. Received SDO abort. No line found. Code : ", abortCode);
1049 // Error : Unknown cs
1050 MSG_ERR(0x1AB2, "SDO. Received unknown command specifier : ", getSDOcs(m->data[0]));
1057 /*******************************************************************)******/
1058 inline UNS8 _writeNetworkDict (CO_Data* d, UNS8 nodeId, UNS16 index,
1059 UNS8 subIndex, UNS8 count, UNS8 dataType, void *data, SDOCallback_t Callback)
1064 s_SDO sdo; // SDO to transmit
1070 UNS8 *pNodeIdServer;
1074 MSG_WAR(0x3AC0, "Send SDO to write in the dictionary of node : ", nodeId);
1075 MSG_WAR(0x3AC1, " At index : ", index);
1076 MSG_WAR(0x3AC2, " subIndex : ", subIndex);
1077 MSG_WAR(0x3AC3, " nb bytes : ", count);
1079 // Verify that there is no SDO communication yet.
1080 err = getSDOlineOnUse(d, nodeId, SDO_CLIENT, &line);
1082 MSG_ERR(0x1AC4, "SDO error : Communication yet established. with node : ", nodeId);
1085 // Taking the line ...
1086 err = getSDOfreeLine( d, SDO_CLIENT, &line );
1088 MSG_ERR(0x1AC5, "SDO error : No line free, too many SDO in progress. Aborted for node : ", nodeId);
1091 // Check which SDO to use to communicate with the node
1092 offset = d->firstIndex->SDO_CLT;
1093 lastIndex = d->lastIndex->SDO_CLT;
1095 MSG_ERR(0x1AC6, "writeNetworkDict : No SDO client index found", 0);
1099 while (offset <= lastIndex) {
1100 if (d->objdict[offset].bSubCount <= 3) {
1101 MSG_ERR(0x1AC8, "Subindex 3 not found at index ", 0x1280 + i);
1104 // looking for the nodeId server
1105 pNodeIdServer = d->objdict[offset].pSubindex[3].pObject;
1106 nodeIdServer = *pNodeIdServer;
1107 MSG_WAR(0x1AD2, "index : ", 0x1280 + i);
1108 MSG_WAR(0x1AD3, "nodeIdServer : ", nodeIdServer);
1110 if(nodeIdServer == nodeId) {
1118 MSG_ERR(0x1AC9, "SDO. Error. No client found to communicate with node : ", nodeId);
1121 MSG_WAR(0x3AD0," SDO client defined at index : ", 0x1280 + i);
1122 initSDOline(d, line, nodeId, index, subIndex, SDO_DOWNLOAD_IN_PROGRESS);
1123 d->transfers[line].count = count;
1124 d->transfers[line].dataType = dataType;
1126 // Copy data to transfers structure.
1127 for (j = 0 ; j < count ; j++) {
1128 # ifdef CANOPEN_BIG_ENDIAN
1130 d->transfers[line].data[count - 1 - j] = ((char *)data)[j];
1131 else // String of bytes.
1132 d->transfers[line].data[j] = ((char *)data)[j];
1134 d->transfers[line].data[j] = ((char *)data)[j];
1137 // Send the SDO to the server. Initiate download, cs=1.
1138 sdo.nodeId = nodeId;
1139 if (count <= 4) { // Expedited transfert
1140 sdo.body.data[0] = (1 << 5) | ((4 - count) << 2) | 3;
1141 for (i = 4 ; i < 8 ; i++)
1142 sdo.body.data[i] = d->transfers[line].data[i - 4];
1143 d->transfers[line].offset = count;
1145 else { // Normal transfert
1146 sdo.body.data[0] = (1 << 5) | 1;
1147 sdo.body.data[4] = count; // nb of byte to transmit. Max = 255. (canfestival2 limitation).
1148 for (i = 5 ; i < 8 ; i++)
1149 sdo.body.data[i] = 0;
1151 sdo.body.data[1] = index & 0xFF; // LSB
1152 sdo.body.data[2] = (index >> 8) & 0xFF; // MSB
1153 sdo.body.data[3] = subIndex;
1155 err = sendSDO(d, SDO_CLIENT, sdo);
1157 MSG_ERR(0x1AD1, "SDO. Error while sending SDO to node : ", nodeId);
1159 resetSDOline(d, line);
1162 d->transfers[line].Callback = Callback;
1166 /*--------------------------------------------------------------------------*/
1168 UNS8 writeNetworkDict (CO_Data* d, UNS8 nodeId, UNS16 index,
1169 UNS8 subIndex, UNS8 count, UNS8 dataType, void *data)
1171 return _writeNetworkDict (d, nodeId, index, subIndex, count, dataType, data, NULL);
1174 /*--------------------------------------------------------------------------*/
1176 UNS8 writeNetworkDictCallBack (CO_Data* d, UNS8 nodeId, UNS16 index,
1177 UNS8 subIndex, UNS8 count, UNS8 dataType, void *data, SDOCallback_t Callback)
1179 return _writeNetworkDict (d, nodeId, index, subIndex, count, dataType, data, Callback);
1183 /***************************************************************************/
1184 inline UNS8 _readNetworkDict (CO_Data* d, UNS8 nodeId, UNS16 index, UNS8 subIndex, UNS8 dataType, SDOCallback_t Callback)
1192 s_SDO sdo; // SDO to transmit
1193 UNS8 *pNodeIdServer;
1198 MSG_WAR(0x3AD5, "Send SDO to read in the dictionary of node : ", nodeId);
1199 MSG_WAR(0x3AD6, " At index : ", index);
1200 MSG_WAR(0x3AD7, " subIndex : ", subIndex);
1203 // Verify that there is no SDO communication yet.
1204 err = getSDOlineOnUse(d, nodeId, SDO_CLIENT, &line);
1206 MSG_ERR(0x1AD8, "SDO error : Communication yet established. with node : ", nodeId);
1209 // Taking the line ...
1210 err = getSDOfreeLine( d, SDO_CLIENT, &line );
1212 MSG_ERR(0x1AD9, "SDO error : No line free, too many SDO in progress. Aborted for node : ", nodeId);
1216 MSG_WAR(0x3AE0, "Transmission on line : ", line);
1218 // Check which SDO to use to communicate with the node
1219 offset = d->firstIndex->SDO_CLT;
1220 lastIndex = d->lastIndex->SDO_CLT;
1222 MSG_ERR(0x1AE1, "writeNetworkDict : No SDO client index found", 0);
1226 while (offset <= lastIndex) {
1227 if (d->objdict[offset].bSubCount <= 3) {
1228 MSG_ERR(0x1AE2, "Subindex 3 not found at index ", 0x1280 + i);
1231 // looking for the nodeId server
1232 pNodeIdServer = d->objdict[offset].pSubindex[3].pObject;
1233 nodeIdServer = *pNodeIdServer;
1235 if(nodeIdServer == nodeId) {
1243 MSG_ERR(0x1AE3, "SDO. Error. No client found to communicate with node : ", nodeId);
1246 MSG_WAR(0x3AE4," SDO client defined at index : ", 0x1280 + i);
1247 initSDOline(d, line, nodeId, index, subIndex, SDO_UPLOAD_IN_PROGRESS);
1248 getSDOlineOnUse(d, nodeId, SDO_CLIENT, &line);
1249 sdo.nodeId = nodeId;
1250 // Send the SDO to the server. Initiate upload, cs=2.
1251 d->transfers[line].dataType = dataType;
1252 sdo.body.data[0] = (2 << 5);
1253 sdo.body.data[1] = index & 0xFF; // LSB
1254 sdo.body.data[2] = (index >> 8) & 0xFF; // MSB
1255 sdo.body.data[3] = subIndex;
1256 for (i = 4 ; i < 8 ; i++)
1257 sdo.body.data[i] = 0;
1258 err = sendSDO(d, SDO_CLIENT, sdo);
1260 MSG_ERR(0x1AE5, "SDO. Error while sending SDO to node : ", nodeId);
1262 resetSDOline(d, line);
1265 d->transfers[line].Callback = Callback;
1269 /*--------------------------------------------------------------------------*/
1271 UNS8 readNetworkDict (CO_Data* d, UNS8 nodeId, UNS16 index, UNS8 subIndex, UNS8 dataType)
1273 return _readNetworkDict (d, nodeId, index, subIndex, dataType, NULL);
1276 /*--------------------------------------------------------------------------*/
1277 UNS8 readNetworkDictCallback (CO_Data* d, UNS8 nodeId, UNS16 index, UNS8 subIndex, UNS8 dataType, SDOCallback_t Callback)
1279 return _readNetworkDict (d, nodeId, index, subIndex, dataType, Callback);
1282 /***************************************************************************/
1284 UNS8 getReadResultNetworkDict (CO_Data* d, UNS8 nodeId, void* data, UNS8 *size,
1292 // Looking for the line tranfert.
1293 err = getSDOlineOnUse(d, nodeId, SDO_CLIENT, &line);
1295 MSG_ERR(0x1AF0, "SDO error : No line found for communication with node : ", nodeId);
1296 return SDO_ABORTED_INTERNAL;
1298 if (d->transfers[line].state != SDO_FINISHED)
1299 return d->transfers[line].state;
1301 // Transfert is finished. Put the value in the data.
1302 * size = d->transfers[line].count;
1303 for ( i = 0 ; i < *size ; i++) {
1304 # ifdef CANOPEN_BIG_ENDIAN
1305 if (d->transfers[line].dataType != visible_string)
1306 ( (char *) data)[*size - 1 - i] = d->transfers[line].data[i];
1307 else // String of bytes.
1308 ( (char *) data)[i] = d->transfers[line].data[i];
1310 ( (char *) data)[i] = d->transfers[line].data[i];
1313 return SDO_FINISHED;
1316 /***************************************************************************/
1318 UNS8 getWriteResultNetworkDict (CO_Data* d, UNS8 nodeId, UNS32 * abortCode)
1324 // Looking for the line tranfert.
1325 err = getSDOlineOnUse(d, nodeId, SDO_CLIENT, &line);
1327 MSG_ERR(0x1AF1, "SDO error : No line found for communication with node : ", nodeId);
1328 return SDO_ABORTED_INTERNAL;
1330 * abortCode = d->transfers[line].abortCode;
1331 return d->transfers[line].state;