Works now with Windows Visual Studio C++ too
[owTools.git] / src / owARDUINOInterface.cpp
1 // Copyright (c) 2017, Tobias Mueller tm(at)tm3d.de\r
2 // All rights reserved.\r
3 //\r
4 // Redistribution and use in source and binary forms, with or without\r
5 // modification, are permitted provided that the following conditions are\r
6 // met:\r
7 //\r
8 //  * Redistributions of source code must retain the above copyright\r
9 //    notice, this list of conditions and the following disclaimer.\r
10 //  * Redistributions in binary form must reproduce the above copyright\r
11 //    notice, this list of conditions and the following disclaimer in the\r
12 //    documentation and/or other materials provided with the\r
13 //    distribution.\r
14 //  * All advertising materials mentioning features or use of this\r
15 //    software must display the following acknowledgement: This product\r
16 //    includes software developed by tm3d.de and its contributors.\r
17 //  * Neither the name of tm3d.de nor the names of its contributors may\r
18 //    be used to endorse or promote products derived from this software\r
19 //    without specific prior written permission.\r
20 //\r
21 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\r
22 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\r
23 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\r
24 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\r
25 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\r
26 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\r
27 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\r
28 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\r
29 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\r
30 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\r
31 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
32 \r
33 #ifdef LINUX\r
34 #include <unistd.h>\r
35 #include <sys/select.h>\r
36 #include <sys/ioctl.h>\r
37 #include <termios.h>\r
38 #include <sys/time.h>\r
39 #endif\r
40 #include <sys/types.h>\r
41 #include <sys/stat.h>\r
42 #include <fcntl.h>\r
43 #include <time.h>\r
44 #include <errno.h>\r
45 #include "owARDUINOInterface.h"\r
46 \r
47 \r
48 #define COM_IDENTIFER 1\r
49 #define COM_RESET 2\r
50 #define COM_SEARCH_INIT 3\r
51 #define COM_SEARCH_NEXT 4\r
52 #define COM_BLOCK 5\r
53 #define COM_SBYTE 6\r
54 #define COM_RBYTE 7\r
55 #define COM_SBIT 8\r
56 #define COM_RBIT 9\r
57 \r
58 //---------------------------------------------------------------------------\r
59 //  Description:\r
60 //     flush the rx and tx buffers\r
61 //\r
62 // 'portnum'  - number 0 to MAX_PORTNUM-1.  This number was provided to\r
63 //              OpenCOM to indicate the port number.\r
64 //\r
65 void owARDUINOInterface::FlushCOM() {\r
66 #ifdef LINUX\r
67    tcflush(fd, TCIOFLUSH);\r
68 #endif \r
69 #ifdef WIN\r
70    PurgeComm(fd, PURGE_TXABORT | PURGE_RXABORT |\r
71            PURGE_TXCLEAR | PURGE_RXCLEAR);\r
72 #endif\r
73 }\r
74 \r
75 #ifdef LINUX\r
76 int \r
77 #endif\r
78 #ifdef WIN\r
79 HANDLE \r
80 #endif\r
81 owARDUINOInterface::OpenCOM(uint8_t comnr)\r
82 {\r
83         char port_zstr[100];\r
84 #ifdef LINUX\r
85         struct termios t;               // see man termios - declared as above\r
86         int rc;\r
87         //int fd;\r
88         \r
89         if (com_init) return fd;\r
90 \r
91         sprintf(port_zstr,"/dev/ttyUSB%i",comnr);\r
92 \r
93    fd = open(port_zstr, O_RDWR|O_NONBLOCK| O_NOCTTY );\r
94    if (fd<0)\r
95    {\r
96            log->set(OWLOG_ERROR,"ERROR open Com %s return %i",port_zstr,fd);\r
97       return fd;\r
98    }\r
99    rc = tcgetattr (fd, &t);\r
100    if (rc < 0)\r
101    {\r
102       int tmp;\r
103       tmp = errno;\r
104       close(fd);\r
105       errno = tmp;\r
106       log->set(OWLOG_ERROR,"OWERROR_SYSTEM_RESOURCE_INIT_FAILED %s",port_zstr);\r
107       return rc; // changed (2.00), used to return rc;\r
108    }\r
109 \r
110    cfsetospeed(&t, B9600);\r
111    cfsetispeed (&t, B9600);\r
112 \r
113    // Get terminal parameters. (2.00) removed raw\r
114    tcgetattr(fd,&t);\r
115    // Save original settings.\r
116    origterm = t;\r
117 \r
118         t.c_cflag     &=  ~PARENB;            // Make 8n1\r
119         t.c_cflag     &=  ~CSTOPB;\r
120         t.c_cflag     &=  ~CSIZE;\r
121         t.c_cflag     |=  CS8;\r
122 \r
123         t.c_cflag     &=  ~CRTSCTS;           // no flow control\r
124         t.c_cc[VMIN]   =  1;                  // read doesn't block\r
125         t.c_cc[VTIME]  =  5;                  // 0.5 seconds read timeout\r
126         t.c_cflag     |=  CREAD | CLOCAL;     // turn on READ & ignore ctrl lines\r
127         t.c_cflag     &=  ~CRTSCTS;       // no flow control\r
128         t.c_iflag     &=  ~(IXON | IXOFF | IXANY);// turn off s/w flow ctrl\r
129 /* Make raw */\r
130         cfmakeraw(&t);\r
131          tcflush(fd,TCIOFLUSH);\r
132 \r
133    rc = tcsetattr(fd, TCSAFLUSH, &t);\r
134  \r
135    if (rc < 0)\r
136    {\r
137       int tmp;\r
138       tmp = errno;\r
139       close(fd);\r
140       errno = tmp;\r
141       log->set(OWLOG_ERROR,"OWERROR_SYSTEM_RESOURCE_INIT_FAILED %s",port_zstr);\r
142       return rc; // changed (2.00), used to return rc;\r
143    }\r
144         com_init=1;\r
145    return fd; // changed (2.00), used to return fd;\r
146 #endif\r
147 #ifdef WIN\r
148    short fRetVal;\r
149    COMMTIMEOUTS CommTimeOuts;\r
150    DCB dcb;\r
151    int comnr1 = comnr;\r
152    sprintf_s(port_zstr,100, "COM%d", comnr1);\r
153    if (fd <= 0) {\r
154            if ((fd =  CreateFileA(port_zstr, GENERIC_READ | GENERIC_WRITE,\r
155                            0,\r
156                            NULL,                 // no security attrs\r
157                            OPEN_EXISTING,\r
158                            FILE_FLAG_OVERLAPPED, // overlapped I/O\r
159                            NULL)) == (HANDLE)-1) {\r
160                    fd = 0;\r
161                    log->set(OWLOG_ERROR, "ERROR open Com %s return %i", port_zstr, fd);\r
162                    return (FALSE);\r
163            } else  {\r
164                    // get any early notifications\r
165                    SetCommMask(fd, EV_RXCHAR | EV_TXEMPTY | EV_ERR | EV_BREAK);\r
166                    SetupComm(fd, 2048, 2048);\r
167                    // purge any information in the buffer\r
168                    PurgeComm(fd, PURGE_TXABORT | PURGE_RXABORT |   PURGE_TXCLEAR | PURGE_RXCLEAR);\r
169                    // set up for overlapped non-blocking I/O\r
170                    CommTimeOuts.ReadIntervalTimeout = 0;\r
171                    CommTimeOuts.ReadTotalTimeoutMultiplier = 20;\r
172                    CommTimeOuts.ReadTotalTimeoutConstant = 40;\r
173                    CommTimeOuts.WriteTotalTimeoutMultiplier = 20;\r
174                    CommTimeOuts.WriteTotalTimeoutConstant = 40;\r
175                    SetCommTimeouts(fd, &CommTimeOuts);\r
176                    // setup the com port\r
177                    GetCommState(fd, &dcb);\r
178                    dcb.BaudRate = CBR_9600;               // current baud rate\r
179                    dcb.fBinary = TRUE;                    // binary mode, no EOF check\r
180                    dcb.fParity = FALSE;                   // enable parity checking\r
181                    dcb.fOutxCtsFlow = FALSE;              // CTS output flow control\r
182                    dcb.fOutxDsrFlow = FALSE;              // DSR output flow control\r
183                    dcb.fDtrControl = FALSE;  // DTR flow control type\r
184                    dcb.fDsrSensitivity = FALSE;           // DSR sensitivity\r
185                    dcb.fTXContinueOnXoff = FALSE;          // XOFF continues Tx\r
186                    dcb.fOutX = FALSE;                     // XON/XOFF out flow control\r
187                    dcb.fInX = FALSE;                      // XON/XOFF in flow control\r
188                    dcb.fErrorChar = FALSE;                // enable error replacement\r
189                    dcb.fNull = FALSE;                     // enable null stripping\r
190                    dcb.fRtsControl = FALSE;  // RTS flow control\r
191                    dcb.fAbortOnError = FALSE;             // abort reads/writes on error\r
192                    dcb.XonLim = 0;                        // transmit XON threshold\r
193                    dcb.XoffLim = 0;                       // transmit XOFF threshold\r
194                    dcb.ByteSize = 8;                      // number of bits/byte, 4-8\r
195                    dcb.Parity = NOPARITY;                 // 0-4=no,odd,even,mark,space\r
196                    dcb.StopBits = ONESTOPBIT;             // 0,1,2 = 1, 1.5, 2\r
197                    dcb.XonChar = 0;                       // Tx and Rx XON character\r
198                    dcb.XoffChar = 0;                      // Tx and Rx XOFF character\r
199                    dcb.ErrorChar = 0;                     // error replacement character\r
200                    dcb.EofChar = 0;                       // end of input character\r
201                    dcb.EvtChar = 0;                       // received event character\r
202                    fRetVal = SetCommState(fd, &dcb);\r
203 \r
204            }\r
205            // check if successfull\r
206            if (!fRetVal)   {\r
207                    CloseHandle(fd);\r
208                    fd = 0;\r
209                    log->set(OWLOG_ERROR, "OWERROR_SYSTEM_RESOURCE_INIT_FAILED %s", port_zstr);\r
210            }\r
211            return (fd);\r
212    }\r
213    return 0;\r
214 #endif\r
215 }\r
216 \r
217 \r
218 //---------------------------------------------------------------------------\r
219 // Closes the connection to the port.\r
220 //\r
221 // 'portnum'  - number 0 to MAX_PORTNUM-1.  This number was provided to\r
222 //              OpenCOM to indicate the port number.\r
223 //\r
224 void owARDUINOInterface::CloseCOM()\r
225 {\r
226 #ifdef LINUX\r
227    // restore tty settings\r
228    tcsetattr(fd, TCSAFLUSH, &origterm);\r
229    FlushCOM();\r
230    close(fd);\r
231 #endif\r
232 #ifdef WIN\r
233    // disable event notification and wait for thread\r
234    // to halt\r
235    SetCommMask(fd, 0);\r
236 \r
237    PurgeComm(fd, PURGE_TXABORT | PURGE_RXABORT |           PURGE_TXCLEAR | PURGE_RXCLEAR);\r
238    CloseHandle(fd);\r
239    fd = 0;\r
240 #endif\r
241    com_init = 0;\r
242 \r
243 }\r
244 \r
245 \r
246 //--------------------------------------------------------------------------\r
247 // Write an array of bytes to the COM port, verify that it was\r
248 // sent out.  Assume that baud rate has been set.\r
249 //\r
250 // 'portnum'   - number 0 to MAX_PORTNUM-1.  This number provided will\r
251 //               be used to indicate the port number desired when calling\r
252 //               all other functions in this library.\r
253 // Returns 1 for success and 0 for failure\r
254 //\r
255 int owARDUINOInterface::WriteCOM( int outlen, uint8_t *outbuf) {\r
256 #ifdef LINUX\r
257    long count = outlen;\r
258    int i=0;\r
259    for (int k=0;k<outlen;k++) {\r
260                  i+= write(fd, outbuf+k, 1);\r
261 //               sleep(1);\r
262         }\r
263 \r
264    tcdrain(fd);\r
265    return (i == count);\r
266 #endif\r
267 #ifdef WIN\r
268    BOOL fWriteStat;\r
269    DWORD dwBytesWritten = 0;\r
270    DWORD ler = 0, to;\r
271    OVERLAPPED osWrite = { 0 };\r
272    // calculate a timeout\r
273    to = 20 * outlen + 60;\r
274    // write the byte\r
275    osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);\r
276    ResetEvent(osWrite.hEvent);\r
277    fWriteStat = WriteFile(fd, (LPSTR)&outbuf[0],  outlen, &dwBytesWritten, &osWrite);\r
278    // check for an error\r
279   // Sleep(100);\r
280    if (!fWriteStat)\r
281            ler = GetLastError();\r
282    // if not done writting then wait\r
283    if (!fWriteStat && ler == ERROR_IO_PENDING) {\r
284           // log->set(OWLOG_ERROR, "SERIAL_ERROR_IO_PENDING");\r
285            WaitForSingleObject(osWrite.hEvent, to);\r
286            // verify all is written correctly\r
287 \r
288            fWriteStat = GetOverlappedResult(fd, &osWrite,  &dwBytesWritten, FALSE);\r
289    }\r
290    // check results of write\r
291    if (!fWriteStat || (dwBytesWritten != (DWORD)outlen))\r
292            return 0;\r
293    else\r
294            return 1;\r
295 #endif\r
296 }\r
297 \r
298 \r
299 //--------------------------------------------------------------------------\r
300 // Read an array of bytes to the COM port, verify that it was\r
301 // sent out.  Assume that baud rate has been set.\r
302 //\r
303 // 'portnum'  - number 0 to MAX_PORTNUM-1.  This number was provided to\r
304 //              OpenCOM to indicate the port number.\r
305 // 'outlen'   - number of bytes to write to COM port\r
306 // 'outbuf'   - pointer ot an array of bytes to write\r
307 //\r
308 // Returns:  TRUE(1)  - success\r
309 //           FALSE(0) - failure\r
310 //\r
311 int owARDUINOInterface::ReadCOM( int inlen, uint8_t *inbuf)\r
312 {\r
313 #ifdef LINUX\r
314    fd_set         filedescr;\r
315    struct timeval tval;\r
316    int            cnt;\r
317 \r
318    // loop to wait until each byte is available and read it\r
319    for (cnt = 0; cnt < inlen; cnt++)\r
320    {\r
321       // set a descriptor to wait for a character available\r
322       FD_ZERO(&filedescr);\r
323       FD_SET(fd,&filedescr);\r
324       // set timeout to 10ms\r
325       tval.tv_sec = 10;\r
326       tval.tv_usec = 10000;\r
327 \r
328       // if byte available read or return bytes read\r
329       if (select(fd+1,&filedescr,NULL,NULL,&tval) != 0)\r
330       {\r
331          if (read(fd,&inbuf[cnt],1) != 1) {\r
332                         log->set(OWLOG_ERROR,"Read Error on Serial");\r
333 \r
334             return cnt;\r
335          }\r
336       }\r
337       else {\r
338                 log->set(OWLOG_ERROR,"Read Error on Serial (select)");\r
339         return cnt;\r
340         }\r
341    }\r
342 \r
343    \r
344    // success, so return desired length\r
345    return inlen;\r
346 #endif\r
347 #ifdef WIN\r
348    DWORD dwLength = 0;\r
349    BOOL fReadStat;\r
350    DWORD ler = 0, to;\r
351    OVERLAPPED osReader = { 0 };\r
352    // calculate a timeout\r
353    to = 20 * inlen + 60;\r
354    // read\r
355    osReader.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);\r
356    ResetEvent(osReader.hEvent);\r
357    fReadStat = ReadFile(fd, (LPSTR)&inbuf[0],  inlen, &dwLength, &osReader);\r
358    //Sleep(100);\r
359    // check for an error\r
360    if (!fReadStat) {\r
361            ler = GetLastError();\r
362         //   log->set(OWLOG_ERROR, "Read Error on Serial");\r
363    }\r
364    // if not done writing then wait\r
365    int e = 0;\r
366    while (!fReadStat && ler == ERROR_IO_PENDING) {\r
367            // wait until everything is read\r
368           // log->set(OWLOG_ERROR, "Read Error on Serial");\r
369           // printf("try Read %i\n", e); e++;\r
370            WaitForSingleObject(osReader.hEvent, to);\r
371            // verify all is read correctly\r
372            fReadStat = GetOverlappedResult(fd, &osReader,   &dwLength, FALSE);\r
373            //printf("%i %i\n", dwLength, inlen);\r
374            if (dwLength == inlen) break;\r
375            if (e == 3) break;\r
376            printf("repeat\n");\r
377 \r
378    }\r
379    // check results\r
380    if (fReadStat)\r
381            return dwLength;\r
382    else\r
383            return 0;\r
384 #endif\r
385 }\r
386 \r
387 \r
388 /*\r
389 \r
390 //--------------------------------------------------------------------------\r
391 //  Description:\r
392 //     Send a break on the com port for at least 2 ms\r
393 //\r
394 // 'portnum'  - number 0 to MAX_PORTNUM-1.  This number was provided to\r
395 //              OpenCOM to indicate the port number.\r
396 //\r
397 void owARDUINOInterface::BreakCOM()\r
398 {\r
399    int duration = 0;              // see man termios break may be\r
400    tcsendbreak(fd, duration);     // too long\r
401 }\r
402 \r
403 */\r
404 \r
405 \r
406 \r
407 \r
408 \r
409 \r
410 \r
411 \r
412 \r
413 int owARDUINOInterface::InitAdapter(uint8_t nr) {\r
414         // attempt to open the communications port\r
415         if ((fd = OpenCOM(nr)) < 0)\r
416         {\r
417                 log->set(OWLOG_ERROR,"OWERROR_OPENCOM_FAILED");\r
418                 return -1;\r
419         }\r
420 #ifdef WIN\r
421         Sleep(2000);\r
422 #endif\r
423 #ifdef LINUX\r
424         sleep(2);\r
425 #endif\r
426         uint8_t readbuffer[20],sendpacket[20];\r
427         uint8_t sendlen=0;\r
428         sendpacket[sendlen++]=0x01;\r
429         sendpacket[sendlen++]=0x00;\r
430         sendpacket[sendlen++]=0x03;\r
431         if (WriteCOM(sendlen,sendpacket)) {\r
432       if ((sendlen=ReadCOM(3,readbuffer)) == 3) {\r
433                 //      printf("%02X %02X %02X\n",readbuffer[0],readbuffer[1],readbuffer[2] );\r
434       }  else {\r
435         log->set(OWLOG_ERROR,"OWERROR_READCOM_FAILED");\r
436       }\r
437    }   else  log->set(OWLOG_ERROR,"OWERROR_WRITECOM_FAILED");            \r
438         com_init=1;\r
439         return 1;       \r
440 }\r
441 \r
442 \r
443 int owARDUINOInterface::owFirst() {\r
444         uint8_t readbuffer[20],sendpacket[20];\r
445         uint8_t sendlen=0;\r
446         sendpacket[sendlen++]=0x03;\r
447         sendpacket[sendlen++]=0x00;     \r
448         sendpacket[sendlen++]=0x08;\r
449         if (WriteCOM(sendlen,sendpacket)) {\r
450                 if ((sendlen=ReadCOM(8,readbuffer)) == 8) {\r
451                         if (readbuffer[0]!=0) {\r
452                                 for (int i=0;i<8;i++) ROM_NO[i]=readbuffer[i];\r
453                                 return 1;\r
454                         }\r
455                 }\r
456         }\r
457         return 0;\r
458 \r
459 }\r
460 int owARDUINOInterface::owNext() {\r
461         uint8_t readbuffer[20],sendpacket[20];\r
462         uint8_t sendlen=0;\r
463         sendpacket[sendlen++]=0x04;\r
464         sendpacket[sendlen++]=0x00;     \r
465         sendpacket[sendlen++]=0x08;\r
466         if (WriteCOM(sendlen,sendpacket)) {\r
467                 if ((sendlen=ReadCOM(8,readbuffer)) == 8) {\r
468                         if (readbuffer[0]!=0) {\r
469                                 for (int i=0;i<8;i++) ROM_NO[i]=readbuffer[i];\r
470                                 return 1;\r
471                         }\r
472                 }\r
473         }\r
474         return 0;\r
475 \r
476 }\r
477 \r
478 \r
479 void owARDUINOInterface::ReleaseAdapter() {\r
480         CloseCOM();\r
481 }\r
482 \r
483 \r
484 int owARDUINOInterface::Reset() {\r
485         uint8_t readbuffer[5],sendpacket[5];\r
486         uint8_t sendlen=0;\r
487         sendlen=0;\r
488         sendpacket[sendlen++]=0x02;\r
489         sendpacket[sendlen++]=0x00;\r
490         sendpacket[sendlen++]=0x01;\r
491         if (WriteCOM(sendlen,sendpacket)) {\r
492                 if ((sendlen=ReadCOM(1,readbuffer)) == 1) {\r
493                         if (readbuffer[0]) return 1;\r
494                 }\r
495         }\r
496 \r
497         return 0;\r
498 }\r
499 \r
500 \r
501 uint8_t owARDUINOInterface::sendrecivByte(uint8_t byte) {\r
502         \r
503         uint8_t readbuffer[5],sendpacket[5];\r
504         uint8_t sendlen=0;\r
505         if (byte!=0xFF) {\r
506                 sendlen=0;\r
507                 sendpacket[sendlen++]=COM_SBYTE;\r
508                 sendpacket[sendlen++]=0x01;\r
509                 sendpacket[sendlen++]=0x01;\r
510                 sendpacket[sendlen++]=byte;\r
511                 if (WriteCOM(sendlen,sendpacket)) {\r
512                         if ((sendlen=ReadCOM(1,readbuffer)) == 1) {\r
513                                 return byte;\r
514                         }\r
515                 }\r
516         } else {\r
517                 sendlen=0;\r
518                 sendpacket[sendlen++]=COM_RBYTE;\r
519                 sendpacket[sendlen++]=0x00;\r
520                 sendpacket[sendlen++]=0x01;\r
521                 if (WriteCOM(sendlen,sendpacket)) {\r
522                         if ((sendlen=ReadCOM(1,readbuffer)) == 1) {\r
523                                 return readbuffer[0];\r
524                         }\r
525                 }\r
526                 \r
527         }\r
528 \r
529    return 0;                            \r
530 }\r
531 uint8_t owARDUINOInterface::sendrecivBit(uint8_t bit) {\r
532         return 0;\r
533 }\r
534 \r
535 int owARDUINOInterface::Communicate(std::vector<uint8_t> *data, int scount, int rcount) {\r
536         int i=0;\r
537         data->resize(scount);\r
538         uint8_t readbuffer[128],sendpacket[128+3];\r
539         uint8_t sendlen=0;\r
540         sendpacket[sendlen++]=COM_BLOCK;\r
541         sendpacket[sendlen++]=scount;\r
542         sendpacket[sendlen++]=rcount;\r
543         for(uint8_t v:*data) {\r
544                 sendpacket[sendlen++]=v;\r
545                 (*data)[i]=v;i++;\r
546         }\r
547 \r
548         if (WriteCOM(sendlen,sendpacket)) {\r
549                 if ((sendlen=ReadCOM(rcount,readbuffer)) == rcount) {\r
550                                 for(i=0;i<rcount;i++) {\r
551                                         data->push_back(readbuffer[i]);\r
552                                 }\r
553                         } else {\r
554                                 for (i = 0; i<rcount; i++) \r
555                                         data->push_back(0xFF);\r
556                         }\r
557                 }\r
558         return 0;\r
559 }\r
560         \r