ykush3.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857
  1. /*******************************************************************************
  2. Copyright 2017 Yepkit Lda (www.yepkit.com)
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. *******************************************************************************/
  13. #include <ykush3.h>
  14. #include <stdio.h>
  15. #include <ykush_help.h>
  16. #include <command_parser.h>
  17. #include <iostream>
  18. #include <string>
  19. #include <string2val.h>
  20. #include <cstring>
  21. extern char *app_exc_name;
  22. int
  23. ykush3_cmd_parser (int argc, char** argv)
  24. {
  25. char bySerialFlag = 0;
  26. enum ykush3Action action = YKUSH3_HELP;
  27. Ykush3 ykush3;
  28. char port;
  29. char value;
  30. char status_response = 0;
  31. bool action_taken = false;
  32. if (argc < 3)
  33. {
  34. ykush3.print_help ();
  35. return -1;
  36. }
  37. std::string str = argv[2];
  38. if (str.compare ("-s") == 0)
  39. {
  40. //BY SERIAL
  41. if (argc < 5)
  42. {
  43. ykush3.print_help ();
  44. return -1;
  45. }
  46. // set serial in object
  47. ykush3.set_usb_serial(argv[3]);
  48. str = argv[4];
  49. bySerialFlag = 1;
  50. if (str.compare ("-u") == 0 )
  51. {
  52. action = YKUSH3_PORT_UP;
  53. port = argv[5][0];
  54. }
  55. else if (str.compare ("-d") == 0 )
  56. {
  57. action = YKUSH3_PORT_DOWN;
  58. port = argv[5][0];
  59. }
  60. else if (str.compare ("-g") == 0 )
  61. {
  62. action = YKUSH3_GET_STATUS;
  63. port = argv[5][0];
  64. }
  65. else if (str.compare ("-on") == 0 )
  66. {
  67. action = YKUSH3_EXT_CTRL_ON;
  68. }
  69. else if (str.compare ("-off") == 0 )
  70. {
  71. action = YKUSH3_EXT_CTRL_OFF;
  72. }
  73. else if (str.compare ("-w") == 0 )
  74. {
  75. if (argc < 7)
  76. {
  77. ykush3.print_help ();
  78. return -1;
  79. }
  80. action = YKUSH3_WRITE_IO;
  81. port = argv[5][0];
  82. value = argv[6][0];
  83. }
  84. else if (str.compare ("-r") == 0)
  85. {
  86. action = YKUSH3_READ_IO;
  87. port = argv[5][0];
  88. }
  89. else if (str.compare ("-c") == 0)
  90. {
  91. action = YKUSH3_CONFIG;
  92. port = argv[5][0];
  93. value = argv[6][0];
  94. }
  95. else if (str.compare("--reset") == 0)
  96. {
  97. action = YKUSH3_RESET;
  98. }
  99. else if (str.compare ("--gpio") == 0)
  100. {
  101. std::string str2 = argv[5];
  102. if (str2.compare ("enable") == 0)
  103. {
  104. action = YKUSH3_GPIO_EN;
  105. }
  106. else if (str2.compare ("disable") == 0 )
  107. {
  108. action = YKUSH3_GPIO_DIS;
  109. }
  110. else
  111. {
  112. ykush3.print_help ();
  113. return -1;
  114. }
  115. }
  116. else if (str.compare ("--boot") == 0)
  117. {
  118. action = YKUSH3_ENTER_BOOTLOADER;
  119. }
  120. else if (str.compare ("--firmware-version") == 0)
  121. {
  122. action = YKUSH3_FIRMWARE_VERSION;
  123. }
  124. else if (str.compare ("--bootloader-version") == 0)
  125. {
  126. action = YKUSH3_BOOTLOADER_VERSION;
  127. }
  128. else
  129. {
  130. ykush3.print_help ();
  131. return -1;
  132. }
  133. }
  134. // Without serial prefix
  135. else if (str.compare ("-u") == 0)
  136. {
  137. if (argc < 3)
  138. {
  139. ykush3.print_help ();
  140. return -1;
  141. }
  142. action = YKUSH3_PORT_UP;
  143. port = argv[3][0];
  144. }
  145. else if (str.compare ("-d") == 0)
  146. {
  147. if (argc < 4)
  148. {
  149. ykush3.print_help ();
  150. return -1;
  151. }
  152. action = YKUSH3_PORT_DOWN;
  153. port = argv[3][0];
  154. }
  155. else if (str.compare ("-l") == 0)
  156. {
  157. action = YKUSH3_LIST_BOARDS;
  158. }
  159. else if (str.compare ("-g") == 0)
  160. {
  161. if (argc < 4)
  162. {
  163. ykush3.print_help ();
  164. return -1;
  165. }
  166. action = YKUSH3_GET_STATUS;
  167. port = argv[3][0];
  168. }
  169. else if (str.compare ("-on") == 0)
  170. {
  171. action = YKUSH3_EXT_CTRL_ON;
  172. }
  173. else if (str.compare ("-off") == 0)
  174. {
  175. action = YKUSH3_EXT_CTRL_OFF;
  176. }
  177. else if (str.compare ("-w") == 0)
  178. {
  179. if (argc < 5)
  180. {
  181. ykush3.print_help ();
  182. return -1;
  183. }
  184. action = YKUSH3_WRITE_IO;
  185. port = argv[3][0];
  186. value = argv[4][0];
  187. }
  188. else if (str.compare ("-r") == 0)
  189. {
  190. action = YKUSH3_READ_IO;
  191. port = argv[3][0];
  192. }
  193. else if (str.compare ("-c") == 0)
  194. {
  195. if (argc < 5)
  196. {
  197. ykush3.print_help ();
  198. return -1;
  199. }
  200. action = YKUSH3_CONFIG;
  201. port = argv[3][0];
  202. value = argv[4][0];
  203. }
  204. else if (str.compare ("--reset") == 0)
  205. {
  206. action = YKUSH3_RESET;
  207. }
  208. else if (str.compare ("--boot") == 0)
  209. {
  210. action = YKUSH3_ENTER_BOOTLOADER;
  211. }
  212. else if (str.compare ("--gpio") == 0)
  213. {
  214. std::string str2 = argv[3];
  215. if (str2.compare ("enable") == 0)
  216. {
  217. action = YKUSH3_GPIO_EN;
  218. }
  219. else if (str2.compare ("disable") == 0)
  220. {
  221. action = YKUSH3_GPIO_DIS;
  222. }
  223. else
  224. {
  225. ykush3.print_help ();
  226. return -1;
  227. }
  228. }
  229. else if (str.compare ("--firmware-version") == 0)
  230. {
  231. action = YKUSH3_FIRMWARE_VERSION;
  232. }
  233. else if (str.compare ("--bootloader-version") == 0)
  234. {
  235. action = YKUSH3_BOOTLOADER_VERSION;
  236. }
  237. else
  238. {
  239. ykush3.print_help ();
  240. return -1;
  241. }
  242. switch (action)
  243. {
  244. case YKUSH3_PORT_UP:
  245. if (bySerialFlag)
  246. return ykush3.port_up (argv[3], port);
  247. else
  248. return ykush3.port_up (NULL, port);
  249. break;
  250. case YKUSH3_PORT_DOWN:
  251. if (bySerialFlag)
  252. return ykush3.port_down (argv[3], port);
  253. else
  254. return ykush3.port_down (NULL, port);
  255. break;
  256. case YKUSH3_LIST_BOARDS:
  257. ykush3_list_attached ();
  258. break;
  259. case YKUSH3_GET_STATUS:
  260. if (bySerialFlag)
  261. {
  262. status_response = ykush3.get_port_status (argv[3], port);
  263. if (status_response < 0)
  264. return status_response;
  265. if (status_response >> 4)
  266. printf ("\n\nDownstream port %d is ON\n\n", status_response & 0x0F);
  267. else
  268. printf ("\n\nDownstream port %d is OFF\n\n", status_response & 0x0F);
  269. return 0;
  270. }
  271. else
  272. {
  273. status_response = ykush3.get_port_status (NULL, port);
  274. if (status_response < 0)
  275. return status_response;
  276. if (status_response >> 4)
  277. printf ("\n\nDownstream port %d is ON\n\n", status_response & 0x0F);
  278. else
  279. printf ("\n\nDownstream port %d is OFF\n\n", status_response & 0x0F);
  280. return 0;
  281. }
  282. break;
  283. case YKUSH3_EXT_CTRL_ON:
  284. if (bySerialFlag)
  285. return ykush3.port_up (argv[3], '4');
  286. else
  287. return ykush3.port_up (NULL, '4');
  288. break;
  289. case YKUSH3_EXT_CTRL_OFF:
  290. if (bySerialFlag)
  291. return ykush3.port_down (argv[3], '4');
  292. else
  293. return ykush3.port_down (NULL, '4');
  294. break;
  295. case YKUSH3_WRITE_IO:
  296. if (bySerialFlag)
  297. return ykush3.write_io (argv[3], port, value);
  298. else
  299. return ykush3.write_io (NULL, port, value);
  300. return 0;
  301. break;
  302. case YKUSH3_READ_IO:
  303. if (bySerialFlag)
  304. printf ("\n%d\n", ykush3.read_io(argv[3], port));
  305. else
  306. printf ("\n%d\n", ykush3.read_io(NULL, port));
  307. return 0;
  308. break;
  309. case YKUSH3_CONFIG:
  310. if (bySerialFlag)
  311. return ykush3.config_port (argv[3], port, value);
  312. else
  313. return ykush3.config_port (NULL, port, value);
  314. break;
  315. case YKUSH3_RESET:
  316. if (bySerialFlag)
  317. return ykush3.reset (argv[3]);
  318. else
  319. return ykush3.reset (NULL);
  320. break;
  321. case YKUSH3_GPIO_EN:
  322. if (bySerialFlag)
  323. return ykush3.gpio_ctrl_enable (argv[3]);
  324. else
  325. return ykush3.gpio_ctrl_enable (NULL);
  326. break;
  327. case YKUSH3_GPIO_DIS:
  328. if (bySerialFlag)
  329. return ykush3.gpio_ctrl_disable (argv[3]);
  330. else
  331. return ykush3.gpio_ctrl_disable (NULL);
  332. break;
  333. case YKUSH3_ENTER_BOOTLOADER:
  334. if (bySerialFlag)
  335. return ykush3.enter_bootloader (argv[3]);
  336. else
  337. return ykush3.enter_bootloader (NULL);
  338. break;
  339. case YKUSH3_FIRMWARE_VERSION:
  340. return ykush3.display_version_firmware ();
  341. break;
  342. case YKUSH3_BOOTLOADER_VERSION:
  343. return ykush3.display_version_bootloader ();
  344. break;
  345. default:
  346. ykush3.print_help ();
  347. return -1;
  348. break;
  349. }
  350. return 0;
  351. }
  352. int Ykush3::port_up(char *serial, char port)
  353. {
  354. switch(port) {
  355. case '1':
  356. hid_report_out[0] = 0x11;
  357. break;
  358. case '2':
  359. hid_report_out[0] = 0x12;
  360. break;
  361. case '3':
  362. hid_report_out[0] = 0x13;
  363. break;
  364. case 'a':
  365. hid_report_out[0] = 0x1a;
  366. break;
  367. case '4':
  368. hid_report_out[0] = 0x14;
  369. break;
  370. default:
  371. return -1;
  372. break;
  373. }
  374. //send HID report to board
  375. return sendHidReport(serial, hid_report_out, hid_report_in, 64);
  376. }
  377. int Ykush3::port_down(char *serial, char port)
  378. {
  379. switch(port)
  380. {
  381. case '1':
  382. hid_report_out[0] = 0x01;
  383. break;
  384. case '2':
  385. hid_report_out[0] = 0x02;
  386. break;
  387. case '3':
  388. hid_report_out[0] = 0x03;
  389. break;
  390. case 'a':
  391. hid_report_out[0] = 0x0a;
  392. break;
  393. case '4':
  394. hid_report_out[0] = 0x04;
  395. break;
  396. default:
  397. return 0;
  398. break;
  399. }
  400. //send HID report to board
  401. return sendHidReport(serial, hid_report_out, hid_report_in, 64);
  402. }
  403. int Ykush3::get_port_status(char *serial, char port)
  404. {
  405. int status;
  406. switch(port)
  407. {
  408. case '1':
  409. hid_report_out[0] = 0x21;
  410. break;
  411. case '2':
  412. hid_report_out[0] = 0x22;
  413. break;
  414. case '3':
  415. hid_report_out[0] = 0x23;
  416. break;
  417. case '4':
  418. hid_report_out[0] = 0x24;
  419. break;
  420. default:
  421. return 0;
  422. break;
  423. }
  424. //send HID report to board
  425. sendHidReport(serial, hid_report_out, hid_report_in, 64);
  426. //handle board response HID report
  427. status = hid_report_in[1];
  428. return status;
  429. }
  430. int Ykush3::write_io(char *serial, char port, char value)
  431. {
  432. hid_report_out[0] = 0x31;
  433. if(value=='0'){
  434. hid_report_out[2] = 0;
  435. } else {
  436. hid_report_out[2] = 1;
  437. }
  438. switch(port)
  439. {
  440. case '1':
  441. hid_report_out[1] = 0x01;
  442. break;
  443. case '2':
  444. hid_report_out[1] = 0x02;
  445. break;
  446. case '3':
  447. hid_report_out[1] = 0x03;
  448. break;
  449. default:
  450. return 0;
  451. break;
  452. }
  453. //send HID report to board
  454. return sendHidReport(serial, hid_report_out, hid_report_in, 64);
  455. }
  456. int Ykush3::read_io(char *serial, char port)
  457. {
  458. hid_report_out[0] = 0x30;
  459. switch(port)
  460. {
  461. case '1':
  462. hid_report_out[1] = 0x01;
  463. break;
  464. case '2':
  465. hid_report_out[1] = 0x02;
  466. break;
  467. case '3':
  468. hid_report_out[1] = 0x03;
  469. break;
  470. default:
  471. return 0;
  472. break;
  473. }
  474. //send HID report to board
  475. sendHidReport(serial, hid_report_out, hid_report_in, 64);
  476. //process response
  477. return hid_report_in[3];
  478. }
  479. int Ykush3::config_port(char *serial, char port, char value)
  480. {
  481. hid_report_out[0] = 0x41;
  482. if ( value == '0' )
  483. hid_report_out[2] = 0;
  484. else if ( value == '1' )
  485. hid_report_out[2] = 1;
  486. else
  487. hid_report_out[2] = 2; //Persist mode
  488. switch ( port ) {
  489. case '1':
  490. hid_report_out[1] = 0x01;
  491. break;
  492. case '2':
  493. hid_report_out[1] = 0x02;
  494. break;
  495. case '3':
  496. hid_report_out[1] = 0x03;
  497. break;
  498. case 'e':
  499. hid_report_out[1] = 0x04;
  500. break;
  501. default:
  502. return 0;
  503. break;
  504. }
  505. //send HID report to board
  506. return sendHidReport(serial, hid_report_out, hid_report_in, 64);
  507. }
  508. int Ykush3::reset(char *serial)
  509. {
  510. hid_report_out[0] = 0x55;
  511. //send HID report to board
  512. sendHidReport(serial, hid_report_out, hid_report_in, 64);
  513. return 0;
  514. }
  515. int
  516. Ykush3::gpio_ctrl_enable (char *serial)
  517. {
  518. hid_report_out[0] = 0x32;
  519. hid_report_out[1] = 0x01;
  520. //send HID report to board
  521. sendHidReport (serial, hid_report_out, hid_report_in, 64);
  522. return 0;
  523. }
  524. int
  525. Ykush3::gpio_ctrl_disable (char *serial)
  526. {
  527. hid_report_out[0] = 0x32;
  528. hid_report_out[1] = 0x00;
  529. //send HID report to board
  530. sendHidReport (serial, hid_report_out, hid_report_in, 64);
  531. return 0;
  532. }
  533. int
  534. Ykush3::enter_bootloader (char *serial)
  535. {
  536. hid_report_out[0] = 0x42;
  537. //send HID report to board
  538. sendHidReport (serial, hid_report_out, hid_report_in, 64);
  539. return 0;
  540. }
  541. int Ykush3::i2c_enable_disable_control(bool enable_flag)
  542. {
  543. hid_report_out[0] = 0x51;
  544. hid_report_out[1] = 0x01;
  545. if ( enable_flag )
  546. hid_report_out[2] = 0x01;
  547. else
  548. hid_report_out[2] = 0x00;
  549. sendHidReport(usb_serial, hid_report_out, hid_report_in, 64);
  550. if ( (hid_report_in[0] == 0x01) && (hid_report_in[1] == 0x51) ) {
  551. //command executed with success
  552. return 0;
  553. }
  554. return 1;
  555. }
  556. int Ykush3::i2c_enable_disable_gateway(bool enable_flag)
  557. {
  558. hid_report_out[0] = 0x51;
  559. hid_report_out[1] = 0x02;
  560. if ( enable_flag )
  561. hid_report_out[2] = 0x01;
  562. else
  563. hid_report_out[2] = 0x00;
  564. sendHidReport(usb_serial, hid_report_out, hid_report_in, 64);
  565. if ( (hid_report_in[0] == 0x01) && (hid_report_in[1] == 0x51) ) {
  566. //command executed with success
  567. return 0;
  568. }
  569. return 1;
  570. }
  571. int Ykush3::i2c_set_address(char *i2c_address)
  572. {
  573. std::cout << "i2c_set_address com address: " << i2c_address << std::endl;
  574. hid_report_out[0] = 0x51;
  575. hid_report_out[1] = 0x03;
  576. hex2bin(i2c_address + 2, &hid_report_out[2], 2);
  577. sendHidReport(usb_serial, hid_report_out, hid_report_in, 64);
  578. if ( (hid_report_in[0] == 0x01) && (hid_report_in[1] == 0x51) ) {
  579. //command executed with success
  580. return 0;
  581. }
  582. return 1;
  583. }
  584. int Ykush3::i2c_write(char *i2c_address_ASCII, char *num_bytes_ASCII, char **data_to_write_ASCII)
  585. {
  586. hid_report_out[0] = 0x52;
  587. hid_report_out[1] = 0x01;
  588. //convert i2c_address_ASCII to binary
  589. hex2bin(i2c_address_ASCII + 2, &hid_report_out[2], 2);
  590. //convert num_bytes_ASCII to binary
  591. int size = strlen(num_bytes_ASCII);
  592. if ( size <= 0 )
  593. return 1;
  594. dec2bin(num_bytes_ASCII, &hid_report_out[3], size);
  595. if ( hid_report_out[3] > 60 )
  596. return 2;
  597. //convert data_to_write_ASCII to binary
  598. for ( int i = 0; i < hid_report_out[3]; i++ ) {
  599. hex2bin(num_bytes_ASCII + 2, &hid_report_out[i + 4], 2);
  600. }
  601. sendHidReport(usb_serial, hid_report_out, hid_report_in, 64);
  602. if ( (hid_report_in[0] == 0x01) && (hid_report_in[1] == 0x52) ) {
  603. //command executed with success
  604. return 0;
  605. }
  606. return 0;
  607. }
  608. int Ykush3::i2c_read(char *i2c_address_ASCII, char *num_bytes_ASCII, unsigned char *data_buffer, int *bytes_read)
  609. {
  610. hid_report_out[0] = 0x52;
  611. hid_report_out[1] = 0x02;
  612. //convert i2c_address_ASCII to binary
  613. hex2bin(i2c_address_ASCII + 2, &hid_report_out[2], 2);
  614. //convert num_bytes_ASCII to binary
  615. int size = strlen(num_bytes_ASCII);
  616. if ( size <= 0 )
  617. return 1;
  618. dec2bin(num_bytes_ASCII, &hid_report_out[3], size);
  619. sendHidReport(usb_serial, hid_report_out, hid_report_in, 64);
  620. //handle response message
  621. if ((hid_report_in[0] == 0x01) && (hid_report_in[1] == 0x52)) {
  622. //get num_bytes
  623. if (hid_report_in[2] < 0)
  624. return 1;
  625. int i;
  626. for (i = 0; (i < hid_report_in[2]) && (i < 60); i++) {
  627. data_buffer[i] = hid_report_in[i + 4];
  628. }
  629. *bytes_read = i;
  630. } else {
  631. return 1;
  632. }
  633. return 0;
  634. }
  635. int
  636. Ykush3::display_version_bootloader (void)
  637. {
  638. int major, minor, patch;
  639. hid_report_out[0] = 0x61;
  640. hid_report_out[1] = 0x01;
  641. if (sendHidReport (usb_serial, hid_report_out, hid_report_in, 64) != 0 )
  642. {
  643. std::cout << "Unable to get bootloader version \n";
  644. return 1;
  645. }
  646. //print response
  647. if ((hid_report_in[0] != 0x01) && (hid_report_in[0] != 0x61))
  648. {
  649. std::cout << "Bootloader version-0.10.0\n";
  650. return 0;
  651. }
  652. std::cout << "Bootloader version-" << (int) hid_report_in[2] << "." << (int) hid_report_in[3] << "." << (int) hid_report_in[4] << std::endl;
  653. return 0;
  654. }
  655. int
  656. Ykush3::display_version_firmware (void)
  657. {
  658. hid_report_out[0] = 0x61;
  659. hid_report_out[1] = 0x02;
  660. sendHidReport(usb_serial, hid_report_out, hid_report_in, 64);
  661. //print response
  662. if (( hid_report_in[0] != 0x01) && (hid_report_in[0] != 0x61))
  663. {
  664. std::cout << "Firmware version-1.0.0\n";
  665. return 0;
  666. }
  667. std::cout << "Firmware version-" << (int) hid_report_in[2] << "." << (int) hid_report_in[3] << "." << (int) hid_report_in[4] << std::endl;
  668. return 0;
  669. }
  670. int Ykush3::set_usb_serial(char *serial)
  671. {
  672. usb_serial = serial;
  673. return 0;
  674. }
  675. int ykush3_list_attached()
  676. {
  677. Ykush3 ykush3;
  678. printf("\n\nAttached YKUSH 3 Boards:\n");
  679. if( ykush3.listConnected() == 0 )
  680. printf("\n\nNo YKUSH 3 boards found.");
  681. printf("\n\n");
  682. return 0;
  683. }
  684. void Ykush3::print_help(void)
  685. {
  686. Help help(app_exc_name);
  687. help.print_version();
  688. help.print_usage();
  689. help.print_ykush3();
  690. }