您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

main.cpp 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686
  1. /*
  2. radangeld -- a linux daemon to readout and distribute data measured by a Kromek
  3. RadAngel γ-spectrometer
  4. Copyright (C) 2019 Matthias Janke <matthias.janke@physi.uni-heidelberg.de>
  5. This program is free software: you can redistribute it and/or modify it under
  6. the terms of the GNU Affero General Public License as published by the Free
  7. Software Foundation, version 3.
  8. This program is distributed in the hope that it will be useful, but WITHOUT ANY
  9. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
  10. PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
  11. You should have received a copy of the GNU Affero General Public License along
  12. with this program. If not, see <https://www.gnu.org/licenses/>
  13. */
  14. // SPDX-License-Identifier: AGPL-3.0-only
  15. #include <iostream>
  16. #include <fstream>
  17. #include <chrono>
  18. #include <array>
  19. #include <limits>
  20. #include <thread>
  21. #include <atomic>
  22. #include <csignal>
  23. #include <cstdio>
  24. #include <wchar.h>
  25. // generate timestamps that are identifiable
  26. #include "date/tz.h"
  27. // program option parser
  28. #include <boost/program_options.hpp>
  29. // HID device access helper library
  30. #include <hidapi.h>
  31. // configuration file library
  32. #include <libconfig.h++>
  33. // thread/ipc/tcp communications
  34. #include <zmqpp/zmqpp.hpp>
  35. // systemd new-style daemon library
  36. #include <systemd/sd-daemon.h>
  37. namespace po = boost::program_options;
  38. namespace
  39. {
  40. volatile std::sig_atomic_t terminate{0};
  41. }
  42. void signal_handler(int signal)
  43. {
  44. if(signal==SIGTERM)
  45. terminate = 1;
  46. }
  47. struct measurement_properties
  48. {
  49. bool eventswrite{true};
  50. bool reportswrite{false};
  51. bool demonize{false};
  52. int eventswritten{0};
  53. int reportswritten{0};
  54. std::chrono::seconds maxduration{30};
  55. unsigned int readreports{0};
  56. std::string fileprefix{"radangel"};
  57. std::string serial{""};
  58. hid_device *handle;
  59. std::chrono::time_point<std::chrono::system_clock> starttime;
  60. std::chrono::time_point<std::chrono::system_clock> endtime;
  61. std::chrono::time_point<std::chrono::steady_clock> starterrortime;
  62. std::chrono::time_point<std::chrono::steady_clock> startmeastime;
  63. };
  64. std::string ws2mbs (const wchar_t *ws)
  65. {
  66. std::mbstate_t state = std::mbstate_t();
  67. const int len = 1 + std::wcsrtombs (nullptr, &ws, 0, &state);
  68. std::vector<char> mbs (len);
  69. std::wcsrtombs (&mbs[0], &ws, mbs.size(), &state);
  70. return std::string (mbs.data(), mbs.size() );
  71. }
  72. int writeprotocol (const std::string &filename, measurement_properties &mp)
  73. {
  74. const int MAX_STR = 255;
  75. int res;
  76. wchar_t wstr[MAX_STR];
  77. libconfig::Config cfg;
  78. libconfig::Setting &root = cfg.getRoot();
  79. libconfig::Setting &measurement = root.add ("Measurement", libconfig::Setting::TypeGroup);
  80. measurement.add ("StartTime", libconfig::Setting::TypeString) = date::format ("%F %T ", date::make_zoned (date::current_zone(), mp.starttime) ) + date::current_zone()->name();
  81. measurement.add ("StartTimeError_ns", libconfig::Setting::TypeInt64) = std::chrono::duration_cast<std::chrono::nanoseconds>(mp.startmeastime-mp.starterrortime).count()/2;
  82. measurement.add ("EndTime", libconfig::Setting::TypeString) = date::format ("%F %T ", date::make_zoned (date::current_zone(), mp.endtime) ) + date::current_zone()->name();
  83. measurement.add ("RunAsDaemon", libconfig::Setting::TypeBoolean) = mp.demonize;
  84. measurement.add ("MaxIntegrationTime_s", libconfig::Setting::TypeInt) = static_cast<int> (mp.maxduration.count() );
  85. measurement.add ("SavedFilePrefix", libconfig::Setting::TypeString) = mp.fileprefix;
  86. measurement.add ("CountedReports", libconfig::Setting::TypeInt) = static_cast<int> (mp.readreports);
  87. libconfig::Setting &detector = measurement.add ("Detector", libconfig::Setting::TypeGroup);
  88. libconfig::Setting &usb = detector.add ("USB", libconfig::Setting::TypeGroup);
  89. // Read the Manufacturer String
  90. wstr[0] = 0x0000;
  91. res = hid_get_manufacturer_string (mp.handle, wstr, MAX_STR);
  92. if (res == 0)
  93. usb.add ("Manufacturer", libconfig::Setting::TypeString) = ws2mbs (wstr);
  94. // Read the Product String
  95. wstr[0] = 0x0000;
  96. res = hid_get_product_string (mp.handle, wstr, MAX_STR);
  97. if (res == 0)
  98. usb.add ("Product", libconfig::Setting::TypeString) = ws2mbs (wstr);
  99. // Read the Serial Number String
  100. wstr[0] = 0x0000;
  101. res = hid_get_serial_number_string (mp.handle, wstr, MAX_STR);
  102. if (res == 0)
  103. usb.add ("Serial", libconfig::Setting::TypeString) = ws2mbs (wstr);
  104. libconfig::Setting &manual = detector.add ("Manual", libconfig::Setting::TypeGroup);
  105. manual.add ("Serial", libconfig::Setting::TypeString) = mp.serial;
  106. libconfig::Setting &clock = measurement.add ("Clock", libconfig::Setting::TypeGroup);
  107. clock.add ("Steady", libconfig::Setting::TypeBoolean) = std::chrono::steady_clock::is_steady;
  108. std::chrono::steady_clock::duration unit (1);
  109. clock.add ("Granularity_ns", libconfig::Setting::TypeInt64) = std::chrono::nanoseconds (unit).count() ;
  110. std::array<std::chrono::time_point<std::chrono::steady_clock>, 1000000> vec;
  111. for (auto &elem : vec)
  112. elem = std::chrono::steady_clock::now();
  113. std::vector<std::chrono::nanoseconds> differences (vec.size() - 1);
  114. for (auto i = 0u; i < differences.size(); i++)
  115. differences[i] = vec[i + 1] - vec[i];
  116. auto result = std::min_element (std::begin (differences), std::end (differences) );
  117. clock.add ("MaximumAccuracy_ns", libconfig::Setting::TypeInt64) = result->count();
  118. libconfig::Setting &files = measurement.add ("Files", libconfig::Setting::TypeGroup);
  119. libconfig::Setting &data = files.add ("Events", libconfig::Setting::TypeList);
  120. data.add (libconfig::Setting::TypeBoolean) = mp.eventswrite;
  121. data.add (libconfig::Setting::TypeInt) = mp.eventswritten;
  122. libconfig::Setting &report = files.add ("Reports", libconfig::Setting::TypeList);
  123. report.add (libconfig::Setting::TypeBoolean) = mp.reportswrite;
  124. report.add (libconfig::Setting::TypeInt) = mp.reportswritten;
  125. cfg.writeFile ( (filename + "-protocol.conf").c_str() );
  126. return 0;
  127. }
  128. void read_hid_reports (measurement_properties &mp, zmqpp::context &zmqc)
  129. {
  130. int ret = 0 ;
  131. // buffer to transfer data via HID IN-descriptor
  132. std::array<uint8_t, 63> reportdata{0};// maximum report length specified by RadAngel HID descriptor report 4
  133. zmqpp::socket reportsocket (zmqc, mp.reportswrite ? zmqpp::socket_type::publish : zmqpp::socket_type::pair);
  134. reportsocket.bind ("inproc://rawreports");
  135. zmqpp::message report;
  136. if (mp.demonize)
  137. sd_notifyf(0, "READY=1\nSTATUS=Processing events…\nMAINPID=%lu", (unsigned long) getpid());
  138. mp.starterrortime = std::chrono::steady_clock::now();
  139. mp.starttime = std::chrono::system_clock::now();
  140. mp.startmeastime = std::chrono::steady_clock::now();
  141. do
  142. {
  143. do
  144. {
  145. ret = hid_read (mp.handle, reportdata.data(), reportdata.size() );
  146. if (ret == 0)
  147. std::this_thread::sleep_for (std::chrono::microseconds (500) );
  148. else if (ret > 0)
  149. break;
  150. else
  151. std::cerr << "Unable to read() HID reports!\n";
  152. }
  153. while (ret == 0);
  154. if (reportdata[0] > 0)
  155. {
  156. report << "report"
  157. << std::chrono::time_point_cast<std::chrono::microseconds> (std::chrono::steady_clock::now() ).time_since_epoch().count()
  158. << ret;
  159. for (const auto &element : reportdata)
  160. report << element;
  161. reportsocket.send (report);
  162. mp.readreports++;
  163. }
  164. }
  165. while ( (ret >= 0) && (mp.demonize || ( (std::chrono::steady_clock::now() - mp.startmeastime) < mp.maxduration) ) && !terminate );
  166. mp.endtime = std::chrono::system_clock::now();
  167. report << "done";
  168. reportsocket.send (report);
  169. if(mp.demonize)
  170. sd_notify(0, "STOPPING=1");
  171. }
  172. void save_hid_reports (measurement_properties &mp, zmqpp::context &zmqc)
  173. {
  174. mp.reportswritten = 0;
  175. if (mp.reportswrite)
  176. {
  177. int64_t time = 0;
  178. int validbytes = 0 ;
  179. std::string reporttype;
  180. // buffer to transfer data via HID IN-descriptor
  181. std::array<uint8_t, 63> reportdata{0};// maximum report length specified by RadAngel HID descriptor report 4
  182. zmqpp::socket reportsocket (zmqc, zmqpp::socket_type::subscribe);
  183. reportsocket.connect ("inproc://rawreports");
  184. reportsocket.subscribe ("");
  185. zmqpp::message reports;
  186. std::ofstream reportsfile;
  187. do
  188. {
  189. reportsocket.receive (reports);
  190. reports >> reporttype;
  191. if (reporttype == "report")
  192. {
  193. if (!reportsfile.is_open())
  194. {
  195. reportsfile.open (mp.fileprefix + date::format ("-%F-%T-%Z", date::make_zoned (date::current_zone(), mp.starttime) ) + "-reports.csv", std::ios::app);
  196. reportsfile << "\"relative time since start [µs]\",\"number of valid report bytes\"";
  197. for (auto i = 0u; i < 63; i++)
  198. reportsfile << ",\"report byte " << i << "\"";
  199. reportsfile << "\n";
  200. }
  201. reports >> time
  202. >> validbytes;
  203. for (auto &element : reportdata)
  204. reports >> element;
  205. reportsfile << std::chrono::duration_cast<std::chrono::microseconds> (std::chrono::steady_clock::time_point{std::chrono::microseconds{time}} - mp.startmeastime).count() << ","
  206. << validbytes;
  207. for (auto &element : reportdata)
  208. reportsfile << "," << static_cast<int> (element);
  209. reportsfile << "\n";
  210. mp.reportswritten++;
  211. }
  212. else if (reporttype == "done")
  213. break;
  214. else
  215. {
  216. std::cerr << "reporttype \"" << reporttype << "\" unknown" << std::endl;
  217. break;
  218. }
  219. }
  220. while (true);
  221. if (reportsfile.is_open())
  222. {
  223. reportsfile.flush();
  224. reportsfile.close();
  225. }
  226. }
  227. }
  228. void decode_hid_reports (measurement_properties &mp, zmqpp::context &zmqc)
  229. {
  230. auto init = true;
  231. auto reportcounter = 0u;
  232. uint8_t validevents = 0;
  233. uint8_t invalidevents = 0;
  234. int64_t time;
  235. int validbytes;
  236. std::string reporttype;
  237. // buffer to transfer data via HID IN-descriptor
  238. std::array<uint8_t, 63> reportdata{0};// maximum report length specified by RadAngel HID descriptor report 4
  239. std::array<std::pair<uint16_t, uint8_t>, 31> currentevents;
  240. std::array<std::array<std::pair<uint16_t, uint8_t>, 31>, 2> oldevents{{{std::make_pair (0, 0) }}};
  241. zmqpp::socket reportsocket (zmqc, mp.reportswrite ? zmqpp::socket_type::subscribe : zmqpp::socket_type::pair);
  242. reportsocket.connect ("inproc://rawreports");
  243. if (mp.reportswrite)
  244. reportsocket.subscribe ("");
  245. zmqpp::message reports;
  246. zmqpp::socket eventssocket (zmqc, zmqpp::socket_type::publish);
  247. eventssocket.bind ("inproc://events");
  248. zmqpp::message events;
  249. do
  250. {
  251. reportsocket.receive (reports);
  252. reports >> reporttype;
  253. if (reporttype == "report")
  254. {
  255. reports >> time
  256. >> validbytes;
  257. for (auto &element : reportdata)
  258. reports >> element;
  259. if (reportdata[0] == 4)
  260. {
  261. if (validbytes == 63)
  262. {
  263. for (auto i = 0u; i < currentevents.size(); i++)
  264. currentevents[i] = std::make_pair ( ( (reportdata[2 * i + 1] << 8) | reportdata[2 * i + 2]) >> 4, 0x0f & reportdata[2 * i + 2]);
  265. validevents = 0;
  266. invalidevents = 0;
  267. for (auto i = 0u; i < currentevents.size(); i++)
  268. if (currentevents[i] != oldevents[reportcounter % 2][i])
  269. {
  270. oldevents[reportcounter % 2][i] = currentevents[i];
  271. if (currentevents[i].second != 0)
  272. validevents++;
  273. else
  274. invalidevents++;
  275. }
  276. else
  277. {
  278. oldevents[reportcounter % 2][i] = currentevents[i];
  279. currentevents[i].second = 0;
  280. }
  281. if (!init)
  282. {
  283. events << "events"
  284. << std::chrono::duration_cast<std::chrono::microseconds> (std::chrono::steady_clock::time_point{std::chrono::microseconds{time}} - mp.startmeastime).count()
  285. << invalidevents
  286. << validevents;
  287. for (const auto &element : currentevents)
  288. if (element.second != 0)
  289. events << element.first;
  290. eventssocket.send (events);
  291. }
  292. reportcounter++;
  293. if (init && (reportcounter == 2) )
  294. init = false;
  295. }
  296. else
  297. std::cerr << "invalid reportsize! received: " << validbytes << " expected: 63\n";
  298. }
  299. }
  300. else if (reporttype == "done")
  301. break;
  302. else
  303. {
  304. std::cerr << "reporttype \"" << reporttype << "\" unknown" << std::endl;
  305. break;
  306. }
  307. }
  308. while (true);
  309. events << "done";
  310. eventssocket.send (events);
  311. }
  312. void save_events (measurement_properties &mp, zmqpp::context &zmqc)
  313. {
  314. mp.eventswritten = 0;
  315. if (mp.eventswrite)
  316. {
  317. uint8_t validevents;
  318. uint8_t invalidevents;
  319. int64_t timestamp;
  320. std::string eventtype;
  321. std::array<uint16_t, 31> currentevents{0};
  322. zmqpp::socket eventssocket (zmqc, zmqpp::socket_type::subscribe);
  323. eventssocket.connect ("inproc://events");
  324. eventssocket.subscribe ("");
  325. zmqpp::message events;
  326. std::ofstream eventfile;
  327. do
  328. {
  329. eventssocket.receive (events);
  330. events >> eventtype;
  331. if (eventtype == "events")
  332. {
  333. if (!eventfile.is_open())
  334. {
  335. eventfile.open (mp.fileprefix + date::format ("-%F-%T-%Z", date::make_zoned (date::current_zone(), mp.starttime) ) + "-events.csv", std::ios::app);
  336. eventfile << "\"relative time since start [µs]\",\"number of invalid events\",\"number of valid events\"";
  337. for (auto i = 0u; i < 31; i++)
  338. eventfile << ",\"event " << i << "\"";
  339. eventfile << "\n";
  340. }
  341. events >> timestamp
  342. >> invalidevents
  343. >> validevents;
  344. for (auto i = 0u; i < validevents; i++)
  345. events >> currentevents[i];
  346. eventfile << timestamp << ","
  347. << static_cast<int> (invalidevents) << ","
  348. << static_cast<int> (validevents);
  349. for (auto i = 0u; i < validevents; i++)
  350. eventfile << "," << static_cast<int> (currentevents[i]);
  351. eventfile << "\n";
  352. mp.eventswritten++;
  353. }
  354. else if (eventtype == "done")
  355. break;
  356. else
  357. {
  358. std::cerr << "save_events: eventtype \"" << eventtype << "\" unknown" << std::endl;
  359. break;
  360. }
  361. }
  362. while (true);
  363. if (eventfile.is_open() )
  364. {
  365. eventfile.flush();
  366. eventfile.close();
  367. }
  368. }
  369. }
  370. void publish_events (zmqpp::context &zmqc)
  371. {
  372. uint8_t validevents;
  373. uint8_t invalidevents;
  374. uint16_t currentevent;
  375. int64_t timestamp;
  376. std::string eventtype;
  377. zmqpp::socket eventssocket (zmqc, zmqpp::socket_type::subscribe);
  378. eventssocket.connect ("inproc://events");
  379. eventssocket.subscribe ("");
  380. zmqpp::message events;
  381. zmqpp::socket eventpublisher (zmqc, zmqpp::socket_type::publish);
  382. eventpublisher.bind ("ipc://radangel.events");
  383. zmqpp::message published_events;
  384. do
  385. {
  386. eventssocket.receive (events);
  387. events >> eventtype;
  388. if (eventtype == "events")
  389. {
  390. events >> timestamp
  391. >> invalidevents
  392. >> validevents;
  393. for (auto i = 0u; i < validevents; i++)
  394. {
  395. events >> currentevent;
  396. published_events << currentevent
  397. << timestamp;
  398. eventpublisher.send (published_events);
  399. }
  400. }
  401. else if (eventtype == "done")
  402. break;
  403. else
  404. {
  405. std::cerr << "eventtype \"" << eventtype << "\" unknown" << std::endl;
  406. break;
  407. }
  408. }
  409. while (true);
  410. eventpublisher.close();
  411. std::remove("radangel.events");
  412. }
  413. void publish_cpm (zmqpp::context &zmqc)
  414. {
  415. uint8_t validevents;
  416. uint8_t invalidevents;
  417. int64_t timestamp;
  418. int64_t oldtimestamp = 0;
  419. std::string eventtype;
  420. zmqpp::socket eventssocket (zmqc, zmqpp::socket_type::subscribe);
  421. eventssocket.connect ("inproc://events");
  422. eventssocket.subscribe ("");
  423. zmqpp::message events;
  424. zmqpp::socket cpmsocket (zmqc, zmqpp::socket_type::publish);
  425. cpmsocket.bind ("ipc://radangel.cpm");
  426. zmqpp::message cpm;
  427. do
  428. {
  429. eventssocket.receive (events);
  430. events >> eventtype;
  431. if (eventtype == "events")
  432. {
  433. events >> timestamp
  434. >> invalidevents
  435. >> validevents;
  436. if (validevents > 0)
  437. {
  438. cpm << timestamp
  439. << static_cast<double> (validevents / ((timestamp - oldtimestamp) / 1000.0 / 1000.0 ) * 60.0);
  440. cpmsocket.send (cpm);
  441. oldtimestamp = timestamp;
  442. }
  443. }
  444. else if (eventtype == "done")
  445. break;
  446. else
  447. {
  448. std::cerr << "eventtype \"" << eventtype << "\" unknown" << std::endl;
  449. break;
  450. }
  451. }
  452. while (true);
  453. cpmsocket.close();
  454. std::remove("radangel.cpm");
  455. }
  456. int main (int argc, char *argv[])
  457. {
  458. zmqpp::context zmqc;
  459. measurement_properties mp;
  460. unsigned int integration_time;
  461. po::options_description desc ("Available options:");
  462. desc.add_options()
  463. ("help,?", "Prints this list of available options.")
  464. ("events,e", po::value<bool> (&mp.eventswrite)->implicit_value (true)->default_value (true), "Save events data to disk. File postfix is -data.csv.")
  465. ("reports,r", po::value<bool> (&mp.reportswrite)->implicit_value (true)->default_value (false), "Save received raw usb hid reports to disk. File postfix is -reports.csv.")
  466. ("integrate,i", po::value<unsigned int> (&integration_time)->default_value (30), "Maximum duration of a measurement in seconds.")
  467. ("daemon,d", po::value<bool> (&mp.demonize)->implicit_value (true)->default_value (false), "Run endless as daemon.")
  468. ("prefix,p", po::value<std::string> (&mp.fileprefix)->default_value ("radangel"), "Prefix for the files generated.")
  469. ("serial,s", po::value<std::string> (&mp.serial), "Serial number of detector.")
  470. ;
  471. po::positional_options_description p;
  472. p.add ("prefix", -1);
  473. po::variables_map vm;
  474. po::store (po::command_line_parser (argc, argv).options (desc).positional (p).run(), vm);
  475. po::notify (vm);
  476. if (vm.count ("help") )
  477. {
  478. std::cout << "Usage: " << argv[0] << " [options] [prefix]\n";
  479. std::cout << desc;
  480. return 0;
  481. }
  482. mp.maxduration = std::chrono::seconds{integration_time};
  483. if (vm.count ("daemon") && !vm["daemon"].defaulted() && vm["integrate"].defaulted() )
  484. mp.maxduration = std::numeric_limits<std::chrono::seconds>::max();
  485. if (hid_init() )
  486. return 1;
  487. mp.handle = hid_open (0x4d8, 0x100, NULL);
  488. if (!mp.handle)
  489. {
  490. std::cerr << "unable to open device\n";
  491. return 4;
  492. }
  493. // Set the hid_read() function to be non-blocking.
  494. hid_set_nonblocking (mp.handle, 1);
  495. std::signal (SIGTERM, signal_handler);
  496. if(!terminate)
  497. {
  498. std::thread publishCPM (publish_cpm, std::ref (zmqc) );
  499. std::thread publishEvents (publish_events, std::ref (zmqc) );
  500. std::thread saveEvents (save_events, std::ref (mp), std::ref (zmqc) );
  501. std::thread decodeReports (decode_hid_reports, std::ref (mp), std::ref (zmqc) );
  502. std::thread saveHIDReports (save_hid_reports, std::ref (mp), std::ref (zmqc) );
  503. std::thread readHIDReports (read_hid_reports, std::ref (mp), std::ref (zmqc) );
  504. publishCPM.join();
  505. publishEvents.join();
  506. saveEvents.join();
  507. decodeReports.join();
  508. saveHIDReports.join();
  509. readHIDReports.join();
  510. // write measurement protocol
  511. writeprotocol (mp.fileprefix + date::format ("-%F-%T-%Z", date::make_zoned (date::current_zone(), mp.starttime) ), mp);
  512. }
  513. hid_close (mp.handle);
  514. // Free static HIDAPI objects.
  515. hid_exit();
  516. return 0;
  517. }