You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

mtk_logger.cc 48KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753
  1. /*
  2. Download track data from GPS loggers based in MTK chipset.
  3. Copyright (C) 2007 Per Borgentun, e4borgen(at)yahoo.com
  4. With lot of inspiration from wbt-200.c
  5. Copyright (C) 2006 Andy Armstrong
  6. This program is free software; you can redistribute it and/or modify
  7. it under the terms of the GNU General Public License as published by
  8. the Free Software Foundation; either version 2 of the License, or
  9. (at your option) any later version.
  10. This program is distributed in the hope that it will be useful,
  11. but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. GNU General Public License for more details.
  14. You should have received a copy of the GNU General Public License
  15. along with this program; if not, write to the Free Software
  16. Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA
  17. */
  18. /*
  19. --------------------------------------------------------------
  20. This module will download track data from a MTK chipset based GPS logger.
  21. It will also convert the raw data.bin file to MTK compatible CSV and
  22. gpsbabel output of your choice.
  23. It has been tested with Transystem i-Blue 747 but other devices should
  24. work as well (Qstarz BT-Q1000, iTrek Z1, ...)
  25. For more info and tweaks on MTK based loggers
  26. <http://www.gpspassion.com/forumsen/topic.asp?TOPIC_ID=81990>
  27. <http://www.gpspassion.com/forumsen/topic.asp?TOPIC_ID=81315>
  28. For info about the used log format:
  29. <http://spreadsheets.google.com/pub?key=pyCLH-0TdNe-5N-5tBokuOA&gid=5>
  30. Module updated 2008-2009. Now also handles Holux M-241 and
  31. Holux GR-245 aka. GPSport-245 devices. These devices have small differences
  32. in the log format and use a lower baudrate to transfer the data.
  33. Example usage::
  34. # Read from USB port, output trackpoints & waypoints in GPX format
  35. ./gpsbabel -D 2 -t -w -i mtk -f /dev/ttyUSB0 -o gpx -F out.gpx
  36. # Parse an existing .bin file (data_2007_09_04.bin), output trackpoints as
  37. # both CSV file and GPX, discard points without fix
  38. ./gpsbabel -D 2 -t -i mtk-bin,csv=data__2007_09_04.csv -f data_2007_09_04.bin -x discard,fixnone -o gpx -F out.gpx
  39. Tip: Check out the -x height,wgs84tomsl filter to correct the altitude.
  40. Todo:
  41. o ....
  42. */
  43. #include "defs.h"
  44. #include "gbser.h"
  45. #include "gbfile.h" /* used for csv output */
  46. #include <QtCore/QDir>
  47. #include <errno.h>
  48. #include <math.h>
  49. #include <stdlib.h>
  50. #if __WIN32__
  51. #include <io.h>
  52. #else
  53. #include <unistd.h>
  54. #endif
  55. #define MYNAME "mtk_logger"
  56. /* MTK packet id's -- currently unused... */
  57. enum MTK_NMEA_PACKET {
  58. PMTK_TEST = 0,
  59. PMTK_ACK = 1,
  60. PMTK_SYS_MSG = 10,
  61. PMTK_CMD_HOT_START = 101,
  62. PMTK_CMD_WARM_START = 102,
  63. PMTK_CMD_COLD_START = 103,
  64. PMTK_CMD_FULL_COLD_START = 104,
  65. PMTK_CMD_LOG = 182, /* Data log commands */
  66. PMTK_SET_NMEA_BAUDRATE = 251,
  67. PMTK_API_SET_DGPS_MODE = 301,
  68. PMTK_API_SET_SBAS_ENABLED = 313,
  69. PMTK_API_SET_NMEA_OUTPUT = 314,
  70. PMTK_API_SET_PWR_SAV_MODE = 320,
  71. PMTK_API_SET_DATUM = 330,
  72. PMTK_API_SET_DATUM_ADVANCE = 331,
  73. PMTK_API_SET_USER_OPTION = 390,
  74. PMTK_API_Q_FIX_CTL = 400,
  75. PMTK_API_Q_DGPS_MODE = 401,
  76. PMTK_API_Q_SBAS_ENABLED = 413,
  77. PMTK_API_Q_NMEA_OUTPUT = 414,
  78. PMTK_API_Q_PWR_SAV_MODE = 420,
  79. PMTK_API_Q_DATUM = 430,
  80. PMTK_API_Q_DATUM_ADVANCE = 431,
  81. PMTK_API_GET_USER_OPTION = 490,
  82. PMTK_DT_FIX_CTL = 500,
  83. PMTK_DT_DGPS_MODE = 501,
  84. PMTK_DT_SBAS_ENABLED = 513,
  85. PMTK_DT_NMEA_OUTPUT = 514,
  86. PMTK_DT_PWR_SAV_MODE = 520,
  87. PMTK_DT_DATUM = 530,
  88. PMTK_DT_FLASH_USER_OPTION = 590,
  89. PMTK_Q_VERSION = 604,
  90. PMTK_Q_RELEASE = 605,
  91. PMTK_DT_VERSION = 704,
  92. PMTK_DT_RELEASE = 705
  93. };
  94. static const unsigned char LOG_RST[16] = {
  95. 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, /* start marker */
  96. 0x00, 0x00, 0x00, 0x00, 0x00, /* data */
  97. 0xbb, 0xbb, 0xbb, 0xbb
  98. }; /* end marker */
  99. static const char* MTK_ACK[] = { /* Flags returned from PMTK001 ack packet */
  100. "Invalid packet", "Unsupported packet type",
  101. "Valid packet but action failed", "Valid packet, action success"
  102. };
  103. #define MTK_EVT_BITMASK (1<<0x02)
  104. #define MTK_EVT_PERIOD (1<<0x03)
  105. #define MTK_EVT_DISTANCE (1<<0x04)
  106. #define MTK_EVT_SPEED (1<<0x05)
  107. #define MTK_EVT_START (1<<0x07)
  108. #define MTK_EVT_WAYPT (1<<0x10) /* Holux waypoint follows... */
  109. /* *************************************** */
  110. /* Data id, type, sizes used by MTK chipset - don't touch.... */
  111. enum {
  112. UTC = 0,
  113. VALID,
  114. LATITUDE,
  115. LONGITUDE,
  116. HEIGHT,
  117. SPEED,
  118. HEADING,
  119. DSTA,
  120. DAGE,
  121. PDOP,
  122. HDOP,
  123. VDOP,
  124. NSAT,
  125. SID,
  126. ELEVATION,
  127. AZIMUTH,
  128. SNR,
  129. RCR,
  130. MILLISECOND,
  131. DISTANCE,
  132. } /* DATA_TYPES */;
  133. struct log_type {
  134. int id;
  135. int size;
  136. const char* name;
  137. } log_type[32] = {
  138. { 0, 4, "UTC" },
  139. { 1, 2, "VALID" },
  140. { 2, 8, "LATITUDE,N/S"},
  141. { 3, 8, "LONGITUDE,E/W"},
  142. { 4, 4, "HEIGHT" },
  143. { 5, 4, "SPEED" },
  144. { 6, 4, "HEADING" },
  145. { 7, 2, "DSTA" },
  146. { 8, 4, "DAGE" },
  147. { 9, 2, "PDOP" },
  148. { 10, 2, "HDOP"},
  149. { 11, 2, "VDOP"},
  150. { 12, 2, "NSAT (USED/VIEW)"},
  151. { 13, 4, "SID",},
  152. { 14, 2, "ELEVATION" },
  153. { 15, 2, "AZIMUTH" },
  154. { 16, 2, "SNR"},
  155. { 17, 2, "RCR"},
  156. { 18, 2, "MILLISECOND"},
  157. { 19, 8, "DISTANCE" },
  158. { 20, 0, NULL},
  159. };
  160. struct sat_info {
  161. char id, used;
  162. short elevation, azimut, snr;
  163. } sat_info;
  164. struct data_item {
  165. time_t timestamp;
  166. short valid;
  167. double lat;
  168. double lon;
  169. float height;
  170. float speed;
  171. float heading;
  172. short dsta; // differential station id
  173. float dage; // differential data age
  174. float pdop, hdop, vdop;
  175. char sat_used, sat_view, sat_count;
  176. short rcr;
  177. unsigned short timestamp_ms;
  178. double distance;
  179. struct sat_info sat_data[32];
  180. } data_item;
  181. struct mtk_loginfo {
  182. unsigned int bitmask;
  183. int logLen;
  184. int period, distance, speed; /* in 10:ths of sec, m, km/h */
  185. int track_event;
  186. } mtk_loginfo;
  187. /* *************************************** */
  188. /* MTK chip based devices with different baudrate, tweaks, ... */
  189. enum MTK_DEVICE_TYPE {
  190. MTK_LOGGER,
  191. HOLUX_M241,
  192. HOLUX_GR245
  193. };
  194. #define TIMEOUT 1500
  195. #define MTK_BAUDRATE 115200
  196. #define MTK_BAUDRATE_M241 38400
  197. #define HOLUX245_MASK (1 << 27)
  198. static void* fd; /* serial fd */
  199. static FILE* fl; /* bin.file fd */
  200. static char* port; /* serial port name */
  201. static char* OPT_erase; /* erase ? command option */
  202. static char* OPT_erase_only; /* erase_only ? command option */
  203. static char* OPT_log_enable; /* enable ? command option */
  204. static char* csv_file; /* csv ? command option */
  205. static char* OPT_block_size_kb; /* block_size_kb ? command option */
  206. static enum MTK_DEVICE_TYPE mtk_device = MTK_LOGGER;
  207. struct mtk_loginfo mtk_info;
  208. const char LIVE_CHAR[4] = {'-', '\\','|','/'};
  209. const char CMD_LOG_DISABLE[]= "$PMTK182,5*20\r\n";
  210. const char CMD_LOG_ENABLE[] = "$PMTK182,4*21\r\n";
  211. const char CMD_LOG_FORMAT[] = "$PMTK182,2,2*39\r\n";
  212. const char CMD_LOG_ERASE[] = "$PMTK182,6,1*3E\r\n";
  213. const char CMD_LOG_STATUS[] = "$PMTK182,2,7*3C\r\n";
  214. static int mtk_log_len(unsigned int bitmask);
  215. static void mtk_rd_init(const QString& fname);
  216. static void file_init(const QString& fname);
  217. static void file_deinit(void) ;
  218. static void holux245_init(void);
  219. static void file_read(void);
  220. static int mtk_parse_info(const unsigned char* data, int dataLen);
  221. // Arguments for log fetch 'mtk' command..
  222. static arglist_t mtk_sargs[] = {
  223. {
  224. "erase", &OPT_erase, "Erase device data after download",
  225. "0", ARGTYPE_BOOL, ARG_NOMINMAX
  226. },
  227. {
  228. "erase_only", &OPT_erase_only, "Only erase device data, do not download anything",
  229. "0", ARGTYPE_BOOL, ARG_NOMINMAX
  230. },
  231. {
  232. "log_enable", &OPT_log_enable, "Enable logging after download",
  233. "0", ARGTYPE_BOOL, ARG_NOMINMAX
  234. },
  235. {
  236. "csv", &csv_file, "MTK compatible CSV output file",
  237. NULL, ARGTYPE_STRING, ARG_NOMINMAX
  238. },
  239. {
  240. "block_size_kb", &OPT_block_size_kb, "Size of blocks in KB to request from device",
  241. "1", ARGTYPE_INT, "1", "64"
  242. },
  243. ARG_TERMINATOR
  244. };
  245. static void dbg(int l, const char* msg, ...)
  246. {
  247. va_list ap;
  248. va_start(ap, msg);
  249. if (global_opts.debug_level >= l) {
  250. vfprintf(stderr,msg, ap);
  251. fflush(stderr);
  252. }
  253. va_end(ap);
  254. }
  255. // Returns a fully qualified pathname to a temporary file that is a copy
  256. // of the data downloaded from the device. Only two copies are ever in play,
  257. // the primary (e.g. "/tmp/data.bin") and the backup ("/tmp/data_old.bin").
  258. //
  259. // It returns a temporary C string - it's totally kludged in to replace
  260. // TEMP_DATA_BIN being string constants.
  261. static const char* GetTempName(bool backup) {
  262. const char kData[]= "data.bin";
  263. const char kDataBackup[]= "data_old.bin";
  264. QString t = QDir::tempPath();
  265. t += QDir::separator();
  266. t += backup ? kDataBackup : kData;
  267. // If your temp directory isn't representable in Latin1, you're going to
  268. // have a bad day.
  269. return t.toLatin1();
  270. }
  271. #define TEMP_DATA_BIN GetTempName(false)
  272. #define TEMP_DATA_BIN_OLD GetTempName(true)
  273. static int do_send_cmd(const char* cmd, int cmdLen)
  274. {
  275. int rc;
  276. dbg(6, "Send %s ", cmd);
  277. rc = gbser_print(fd, cmd);
  278. if (rc != gbser_OK) {
  279. fatal(MYNAME ": Write error (%d)\n", rc);
  280. }
  281. return cmdLen;
  282. }
  283. static int do_cmd(const char* cmd, const char* expect, char** rslt, time_t timeout_sec)
  284. {
  285. char line[256];
  286. int len, done, loops, cmd_erase;
  287. int expect_len;
  288. time_t tout;
  289. if (expect) {
  290. expect_len = strlen(expect);
  291. } else {
  292. expect_len = 0;
  293. }
  294. time(&tout);
  295. if (timeout_sec > 0) {
  296. tout += timeout_sec;
  297. } else {
  298. tout += 1;
  299. }
  300. cmd_erase = 0;
  301. if (strncmp(cmd, CMD_LOG_ERASE, 12) == 0) {
  302. cmd_erase = 1;
  303. if (global_opts.verbose_status || global_opts.debug_level > 0) {
  304. fprintf(stderr, "Erasing ");
  305. }
  306. }
  307. // dbg(6, "## Send '%s' -- Expect '%s' in %d sec\n", cmd, expect, timeout_sec);
  308. do_send_cmd(cmd, strlen(cmd)); // success or fatal()...
  309. done = 0;
  310. loops = 0;
  311. memset(line, '\0', sizeof(line));
  312. do {
  313. int rc;
  314. rc = gbser_read_line(fd, line, sizeof(line)-1, TIMEOUT, 0x0A, 0x0D);
  315. if (rc != gbser_OK) {
  316. if (rc == gbser_TIMEOUT && time(NULL) > tout) {
  317. dbg(2, "NMEA command '%s' timeout !\n", cmd);
  318. return -1;
  319. // fatal(MYNAME "do_cmd(): Read error (%d)\n", rc);
  320. }
  321. len = -1;
  322. } else {
  323. len = strlen(line);
  324. }
  325. loops++;
  326. dbg(8, "Read %d bytes: '%s'\n", len, line);
  327. if (cmd_erase && (global_opts.verbose_status || (global_opts.debug_level > 0 && global_opts.debug_level <= 3))) {
  328. // erase cmd progress wheel -- only for debug level 1-3
  329. fprintf(stderr,"\b%c", LIVE_CHAR[loops%4]);
  330. fflush(stderr);
  331. }
  332. if (len > 5 && line[0] == '$') {
  333. if (expect_len > 0 && strncmp(&line[1], expect, expect_len) == 0) {
  334. if (cmd_erase && (global_opts.verbose_status || global_opts.debug_level > 0)) {
  335. fprintf(stderr,"\n");
  336. }
  337. dbg(6, "NMEA command success !\n");
  338. if ((len - 4) > expect_len) { // alloc and copy data segment...
  339. if (line[len-3] == '*') {
  340. line[len-3] = '\0';
  341. }
  342. // printf("Data segment: #%s#\n", &line[expect_len+1]);
  343. if (rslt) {
  344. *rslt = (char*) xmalloc(len-3-expect_len+1);
  345. strcpy(*rslt, &line[expect_len+1]);
  346. }
  347. }
  348. done = 1;
  349. } else if (strncmp(line, "$PMTK", 5) == 0) {
  350. /* A quick parser for ACK packets */
  351. if (!cmd_erase && strncmp(line, "$PMTK001,", 9) == 0 && line[9] != '\0') {
  352. char* pType, *pRslt;
  353. int pAck;
  354. pType = &line[9];
  355. pRslt = strchr(&line[9], ',') + 1;
  356. if (memcmp(&cmd[5], pType, 3) == 0 && pRslt != NULL && *pRslt != '\0') {
  357. pAck = *pRslt - '0';
  358. if (pAck != 3 && pAck >= 0 && pAck < 4) { // Erase will return '2'
  359. dbg(1, "NMEA command '%s' failed - %s\n", cmd, MTK_ACK[pAck]);
  360. return -1;
  361. }
  362. }
  363. }
  364. dbg(6, "RECV: '%s'\n", line);
  365. }
  366. }
  367. if (!done && time(NULL) > tout) {
  368. dbg(1, "NMEA command '%s' timeout !\n", cmd);
  369. return -1;
  370. }
  371. } while (len != 0 && loops > 0 && !done);
  372. return done?0:1;
  373. }
  374. /*******************************************************************************
  375. * %%% global callbacks called by gpsbabel main process %%% *
  376. *******************************************************************************/
  377. static void mtk_rd_init_m241(const QString& fname)
  378. {
  379. mtk_device = HOLUX_M241;
  380. mtk_rd_init(fname);
  381. }
  382. static void mtk_rd_init(const QString& fname)
  383. {
  384. int rc;
  385. char* model;
  386. port = xstrdup(qPrintable(fname));
  387. errno = 0;
  388. dbg(1, "Opening port %s...\n", port);
  389. if ((fd = gbser_init(port)) == NULL) {
  390. fatal(MYNAME ": Can't initialise port \"%s\" (%s)\n", port, strerror(errno));
  391. }
  392. // verify that we have a MTK based logger...
  393. dbg(1, "Verifying MTK based device...\n");
  394. switch (mtk_device) {
  395. case HOLUX_M241:
  396. case HOLUX_GR245:
  397. log_type[LATITUDE].size = log_type[LONGITUDE].size = 4;
  398. log_type[HEIGHT].size = 3;
  399. rc = gbser_set_port(fd, MTK_BAUDRATE_M241, 8, 0, 1);
  400. break;
  401. case MTK_LOGGER:
  402. default:
  403. rc = gbser_set_port(fd, MTK_BAUDRATE, 8, 0, 1);
  404. break;
  405. }
  406. if (rc) {
  407. dbg(1, "Set baud rate to %d failed (%d)\n", MTK_BAUDRATE, rc);
  408. fatal(MYNAME ": Failed to set baudrate !\n");
  409. }
  410. rc = do_cmd("$PMTK605*31\r\n", "PMTK705,", &model, 10);
  411. if (rc != 0) {
  412. fatal(MYNAME ": This is not a MTK based GPS ! (or is device turned off ?)\n");
  413. }
  414. // say hello to GR245 to make it display "USB PROCESSING"
  415. if (strstr(model, "GR-245")) {
  416. holux245_init(); // remember we have a GR245 for mtk_rd_deinit()
  417. rc |= do_cmd("$PHLX810*35\r\n", "PHLX852,", NULL, 10);
  418. rc |= do_cmd("$PHLX826*30\r\n", "PHLX859*38", NULL, 10);
  419. if (rc != 0) {
  420. dbg(2, "Greeting not successfull.\n");
  421. }
  422. }
  423. xfree(model);
  424. }
  425. static void mtk_rd_deinit(void)
  426. {
  427. if (mtk_device == HOLUX_GR245) {
  428. int rc = do_cmd("$PHLX827*31\r\n", "PHLX860*32", NULL, 10);
  429. if (rc != 0) {
  430. dbg(2, "Goodbye not successfull.\n");
  431. }
  432. }
  433. dbg(3, "Closing port...\n");
  434. gbser_deinit(fd);
  435. fd = NULL;
  436. xfree(port);
  437. }
  438. static int mtk_erase(void)
  439. {
  440. int log_status, log_mask, err;
  441. char* lstatus = NULL;
  442. log_status = 0;
  443. // check log status - is logging disabled ?
  444. do_cmd(CMD_LOG_STATUS, "PMTK182,3,7,", &lstatus, 2);
  445. if (lstatus) {
  446. log_status = atoi(lstatus);
  447. dbg(3, "LOG Status '%s'\n", lstatus);
  448. xfree(lstatus);
  449. lstatus = NULL;
  450. }
  451. do_cmd(CMD_LOG_FORMAT, "PMTK182,3,2,", &lstatus, 2);
  452. if (lstatus) {
  453. log_mask = strtoul(lstatus, NULL, 16);
  454. dbg(3, "LOG Mask '%s' - 0x%.8x \n", lstatus, log_mask);
  455. xfree(lstatus);
  456. lstatus = NULL;
  457. }
  458. dbg(1, "Start flash erase..\n");
  459. do_cmd(CMD_LOG_DISABLE, "PMTK001,182,5,3", NULL, 1);
  460. gb_sleep(10*1000);
  461. // Erase log....
  462. do_cmd(CMD_LOG_ERASE, "PMTK001,182,6", NULL, 30);
  463. gb_sleep(100*1000);
  464. if ((log_status & 2)) { // auto-log were enabled before..re-enable log.
  465. err = do_cmd(CMD_LOG_ENABLE, "PMTK001,182,4,3", NULL, 2);
  466. dbg(3, "re-enable log %s\n", err==0?"Success":"Fail");
  467. }
  468. return 0;
  469. }
  470. static void mtk_read(void)
  471. {
  472. char cmd[256];
  473. char* line = NULL;
  474. unsigned char crc, *data = NULL;
  475. int cmdLen, i, len, rc, init_scan, retry_cnt, log_enabled;
  476. unsigned int j, bsize, scan_bsize, read_bsize_kb, read_bsize, scan_step, ff_len, null_len, chunk_size;
  477. unsigned int line_size, data_size, data_addr, addr, addr_max, rcvd_addr, rcvd_bsize;
  478. unsigned long dsize, dpos = 0;
  479. FILE* dout;
  480. char* fusage = NULL;
  481. if (*OPT_erase_only != '0') {
  482. mtk_erase();
  483. return;
  484. }
  485. log_enabled = 0;
  486. init_scan = 0;
  487. dout = fopen(TEMP_DATA_BIN, "r+b");
  488. if (dout == NULL) {
  489. dout = fopen(TEMP_DATA_BIN, "wb");
  490. if (dout == NULL) {
  491. fatal(MYNAME ": Can't create temporary file %s", TEMP_DATA_BIN);
  492. return;
  493. }
  494. }
  495. fseek(dout, 0L,SEEK_END);
  496. dsize = ftell(dout);
  497. if (dsize > 1024) {
  498. dbg(1, "Temp %s file exists. with size %d\n", TEMP_DATA_BIN, dsize);
  499. dpos = 0;
  500. init_scan = 1;
  501. }
  502. dbg(1, "Download %s -> %s\n", port, TEMP_DATA_BIN);
  503. // check log status - is logging disabled ?
  504. do_cmd(CMD_LOG_STATUS, "PMTK182,3,7,", &fusage, 2);
  505. if (fusage) {
  506. log_enabled = (atoi(fusage) & 2)?1:0;
  507. dbg(3, "LOG Status '%s' -- log %s \n", fusage, log_enabled?"enabled":"disabled");
  508. xfree(fusage);
  509. fusage = NULL;
  510. }
  511. gb_sleep(10*1000);
  512. if (1 || log_enabled) {
  513. i = do_cmd(CMD_LOG_DISABLE, "PMTK001,182,5,3", NULL, 2);
  514. dbg(3, " ---- LOG DISABLE ---- %s\n", i==0?"Success":"Fail");
  515. }
  516. gb_sleep(100*1000);
  517. addr_max = 0;
  518. // get flash usage, current log address..cmd only works if log disabled.
  519. do_cmd("$PMTK182,2,8*33\r\n", "PMTK182,3,8,", &fusage, 2);
  520. if (fusage) {
  521. addr_max = strtoul(fusage, NULL, 16);
  522. if (addr_max > 0) {
  523. addr_max = addr_max - addr_max%65536 + 65535;
  524. }
  525. xfree(fusage);
  526. }
  527. if (addr_max == 0) { // get flash usage failed...
  528. addr_max = 0x200000; // 16Mbit/2Mbyte/32x64kByte block. -- fixme Q1000-ng has 32Mbit
  529. init_scan = 1;
  530. }
  531. dbg(1, "Download %dkB from device\n", (addr_max+1) >> 10);
  532. if (dsize > addr_max) {
  533. dbg(1, "Temp %s file (%ld) is larger than data size %d. Data erased since last download !\n", TEMP_DATA_BIN, dsize, addr_max);
  534. fclose(dout);
  535. dsize = 0;
  536. init_scan = 0;
  537. rename(TEMP_DATA_BIN, TEMP_DATA_BIN_OLD);
  538. dout = fopen(TEMP_DATA_BIN, "wb");
  539. if (dout == NULL) {
  540. fatal(MYNAME ": Can't create temporary file %s", TEMP_DATA_BIN);
  541. return;
  542. }
  543. }
  544. scan_step = 0x10000;
  545. scan_bsize = 0x0400;
  546. read_bsize_kb = strtol(OPT_block_size_kb, NULL, 10);
  547. if (errno == ERANGE || read_bsize_kb < 1) {
  548. read_bsize_kb = 1;
  549. } else if (read_bsize_kb > 64) {
  550. read_bsize_kb = 64;
  551. }
  552. read_bsize = read_bsize_kb * 1024;
  553. dbg(2, "Download block size is %d bytes\n", read_bsize);
  554. if (init_scan) {
  555. bsize = scan_bsize;
  556. } else {
  557. bsize = read_bsize;
  558. }
  559. addr = 0x0000;
  560. line_size = 2*read_bsize + 32; // logdata as nmea/hex.
  561. data_size = read_bsize + 32;
  562. if ((line = (char*) xmalloc(line_size)) == NULL) {
  563. fatal(MYNAME ": Can't allocate %u bytes for NMEA buffer\n", line_size);
  564. }
  565. if ((data = (unsigned char*) xmalloc(data_size)) == NULL) {
  566. fatal(MYNAME ": Can't allocate %u bytes for data buffer\n", data_size);
  567. }
  568. memset(line, '\0', line_size);
  569. memset(data, '\0', data_size);
  570. retry_cnt = 0;
  571. while (init_scan || addr < addr_max) {
  572. // generate - read address NMEA command, add crc.
  573. crc = 0;
  574. cmdLen = snprintf(cmd, sizeof(cmd), "$PMTK182,7,%.8x,%.8x", addr, bsize);
  575. for (i=1; i<cmdLen; i++) {
  576. crc ^= cmd[i];
  577. }
  578. cmdLen += snprintf(&cmd[cmdLen], sizeof(cmd)-cmdLen, "*%.2X\r\n", crc);
  579. mtk_retry:
  580. do_send_cmd(cmd, cmdLen);
  581. memset(line, '\0', line_size);
  582. rcvd_addr = addr;
  583. do {
  584. rc = gbser_read_line(fd, line, line_size-1, TIMEOUT, 0x0A, 0x0D);
  585. if (rc != gbser_OK) {
  586. if (rc == gbser_TIMEOUT && retry_cnt < 3) {
  587. dbg(2, "\nRetry %d at 0x%.8x\n", retry_cnt, addr);
  588. retry_cnt++;
  589. goto mtk_retry;
  590. } // else
  591. fatal(MYNAME "mtk_read(): Read error (%d)\n", rc);
  592. }
  593. len = strlen(line);
  594. dbg(8, "Read %d bytes: '%s'\n", len, line);
  595. if (len > 0) {
  596. line[len] = '\0';
  597. if (strncmp(line, "$PMTK182,8", 10) == 0) { // $PMTK182,8,00005000,FFFFFFF
  598. retry_cnt = 0;
  599. data_addr = strtoul(&line[11], NULL, 16);
  600. // fixme - we should check if all data before data_addr is already received
  601. i = 20;
  602. j = data_addr - addr;
  603. ff_len = 0; // number of 0xff bytes.
  604. null_len = 0; // number of 0x00 bytes.
  605. while (i + 3 < len && j < data_size) {
  606. data[j] = (isdigit(line[i])?(line[i]-'0'):(line[i]-'A'+0xA))*0x10 +
  607. (isdigit(line[i+1])?(line[i+1]-'0'):(line[i+1]-'A'+0xA));
  608. if (data[j] == 0xff) {
  609. ff_len++;
  610. }
  611. if (data[j] == 0x00) {
  612. null_len++;
  613. }
  614. i += 2;
  615. j++;
  616. }
  617. rcvd_addr = addr + j;
  618. chunk_size = rcvd_addr - data_addr;
  619. if (init_scan) {
  620. if (ff_len == chunk_size) { // data in sector - we've found max sector..
  621. addr_max = data_addr;
  622. rcvd_addr = data_addr;
  623. dbg(1, "Initial scan done - Download %dkB from device\n", (addr_max+1) >> 10);
  624. break;
  625. }
  626. } else {
  627. if (null_len == chunk_size) { // 0x00 block - bad block....
  628. fprintf(stderr, "FIXME -- read bad block at 0x%.6x - retry ? skip ?\n%s\n", data_addr, line);
  629. }
  630. if (ff_len == chunk_size) { // 0xff block - read complete...
  631. len = ff_len;
  632. addr_max = data_addr;
  633. rcvd_addr = data_addr;
  634. break;
  635. }
  636. }
  637. } else if (strncmp(line, "$PMTK001,182,7,", 15) == 0) { // Command ACK
  638. if (line[15] != '3') {
  639. // fixme - we should timeout here when no log data has been received...
  640. dbg(2, "\nLog req. failed (%c)\n", line[15]);
  641. gb_sleep(10*1000);
  642. retry_cnt++;
  643. goto mtk_retry;
  644. }
  645. }
  646. }
  647. } while (rcvd_addr < addr + bsize);
  648. rcvd_bsize = rcvd_addr - addr;
  649. dbg(2, "Received %d bytes\n", rcvd_bsize);
  650. if (init_scan) {
  651. if (dsize > 0 && addr < dsize) {
  652. fseek(dout, addr, SEEK_SET);
  653. if (fread(line, 1, rcvd_bsize, dout) == rcvd_bsize && memcmp(line, data, rcvd_bsize) == 0) {
  654. dpos = addr;
  655. dbg(2, "%s same at %d\n", TEMP_DATA_BIN, addr);
  656. } else {
  657. dbg(2, "%s differs at %d\n", TEMP_DATA_BIN, addr);
  658. init_scan = 0;
  659. addr = dpos;
  660. bsize = read_bsize;
  661. }
  662. }
  663. if (init_scan) {
  664. addr += scan_step;
  665. if (addr >= addr_max) { // initial scan complete...
  666. init_scan = 0;
  667. addr = dpos;
  668. bsize = read_bsize;
  669. }
  670. }
  671. } else {
  672. fseek(dout, addr, SEEK_SET);
  673. if (fwrite(data, 1, rcvd_bsize, dout) != rcvd_bsize) {
  674. fatal(MYNAME ": Failed to write temp. binary file\n");
  675. }
  676. addr += rcvd_bsize;
  677. if (global_opts.verbose_status || (global_opts.debug_level >= 2 && global_opts.debug_level < 5)) {
  678. int perc;
  679. perc = 100 - 100*(addr_max-addr)/addr_max;
  680. if (addr >= addr_max) {
  681. perc = 100;
  682. }
  683. fprintf(stderr, "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\bReading 0x%.6x %3d %%", addr, perc);
  684. }
  685. }
  686. }
  687. if (dout != NULL) {
  688. #if __WIN32__
  689. _chsize(fileno(dout), addr_max);
  690. #else
  691. ftruncate(fileno(dout), addr_max);
  692. #endif
  693. fclose(dout);
  694. }
  695. if (global_opts.verbose_status || (global_opts.debug_level >= 2 && global_opts.debug_level < 5)) {
  696. fprintf(stderr,"\n");
  697. }
  698. // Fixme - Order or. Enable - parse - erase ??
  699. if (log_enabled || *OPT_log_enable=='1') {
  700. i = do_cmd(CMD_LOG_ENABLE, "PMTK001,182,4,3", NULL, 2);
  701. dbg(3, " ---- LOG ENABLE ----%s\n", i==0?"Success":"Fail");
  702. } else {
  703. dbg(1, "Note !!! -- Logging is DISABLED !\n");
  704. }
  705. if (line != NULL) {
  706. xfree(line);
  707. }
  708. if (data != NULL) {
  709. xfree(data);
  710. }
  711. file_init(TEMP_DATA_BIN);
  712. file_read();
  713. file_deinit();
  714. /* fixme -- we're assuming all went well - erase flash.... */
  715. if (*OPT_erase != '0') {
  716. mtk_erase();
  717. }
  718. return;
  719. }
  720. static route_head* trk_head = NULL;
  721. static int add_trackpoint(int idx, unsigned long bmask, struct data_item* itm)
  722. {
  723. Waypoint* trk = new Waypoint;
  724. if (global_opts.masked_objective& TRKDATAMASK && (trk_head == NULL || (mtk_info.track_event & MTK_EVT_START))) {
  725. char spds[50];
  726. trk_head = route_head_alloc();
  727. trk_head->rte_name = QString("track-%1").arg(1 + track_count());
  728. spds[0] = '\0';
  729. if (mtk_info.speed > 0) {
  730. sprintf(spds, " when moving above %.0f km/h", mtk_info.speed/10.);
  731. }
  732. trk_head->rte_desc = QString().sprintf("Log every %.0f sec, %.0f m%s"
  733. , mtk_info.period/10., mtk_info.distance/10., spds);
  734. track_add_head(trk_head);
  735. }
  736. if (bmask & (1<<LATITUDE) && bmask & (1<<LONGITUDE)) {
  737. trk->latitude = itm->lat;
  738. trk->longitude = itm->lon;
  739. } else {
  740. delete trk;
  741. return -1; // GPX requires lat/lon...
  742. }
  743. if (bmask & (1<<HEIGHT)) {
  744. trk->altitude = itm->height;
  745. }
  746. trk->SetCreationTime(itm->timestamp); // in UTC..
  747. if (bmask & (1<<MILLISECOND)) {
  748. trk->creation_time = trk->creation_time.addMSecs(itm->timestamp_ms);
  749. }
  750. if (bmask & (1<<PDOP)) {
  751. trk->pdop = itm->pdop;
  752. }
  753. if (bmask & (1<<HDOP)) {
  754. trk->hdop = itm->hdop;
  755. }
  756. if (bmask & (1<<VDOP)) {
  757. trk->vdop = itm->vdop;
  758. }
  759. if (bmask & (1<<HEADING)) {
  760. WAYPT_SET(trk, course, itm->heading);
  761. }
  762. if (bmask & (1<<SPEED)) {
  763. WAYPT_SET(trk, speed, KPH_TO_MPS(itm->speed));
  764. }
  765. if (bmask & (1<<VALID)) {
  766. switch (itm->valid) {
  767. case 0x0040:
  768. trk->fix = fix_unknown;
  769. break; /* Estimated mode */
  770. case 0x0001:
  771. trk->fix = fix_none;
  772. break; /* "No Fix" */
  773. case 0x0002:
  774. trk->fix = fix_3d;
  775. break; /* "SPS" - 2d/3d ?*/
  776. case 0x0004:
  777. trk->fix = fix_dgps;
  778. break;
  779. case 0x0008:
  780. trk->fix = fix_pps;
  781. break; /* Military GPS */
  782. case 0x0010: /* "RTK" */
  783. case 0x0020: /* "FRTK" */
  784. case 0x0080: /* "Manual input mode" */
  785. case 0x0100: /* "Simulator";*/
  786. default:
  787. trk->fix = fix_unknown;
  788. break;
  789. }
  790. /* This is a flagrantly bogus position; don't queue it.
  791. * The 747 does log some "real" positions with fix_none, though,
  792. * so keep those.
  793. */
  794. if ((trk->fix == fix_unknown || trk->fix == fix_none) &&
  795. trk->latitude - 90.0 < .000001 && trk->longitude < 0.000001) {
  796. delete trk;
  797. return -1;
  798. }
  799. }
  800. if (bmask & (1<<NSAT)) {
  801. trk->sat = itm->sat_used;
  802. }
  803. // RCR is a bitmask of possibly several log reasons..
  804. // Holux devics use a Event prefix for each waypt.
  805. if (global_opts.masked_objective & WPTDATAMASK
  806. && ((bmask & (1<<RCR) && itm->rcr & 0x0008)
  807. || (mtk_info.track_event & MTK_EVT_WAYPT)
  808. )
  809. ) {
  810. /* Button press -- create waypoint, start count at 1 */
  811. Waypoint* w = new Waypoint(*trk);
  812. w->shortname = QString().sprintf("WP%06d", waypt_count()+1);
  813. waypt_add(w);
  814. }
  815. // In theory we would not add the waypoint to the list of
  816. // trackpoints. But as the MTK logger restart the
  817. // log session from the button press we would loose a
  818. // trackpoint unless we include/duplicate it.
  819. if (global_opts.masked_objective & TRKDATAMASK) {
  820. trk->shortname = QString().sprintf("TP%06d", idx);
  821. track_add_wpt(trk_head, trk);
  822. } else {
  823. delete trk;
  824. }
  825. return 0;
  826. }
  827. /********************** MTK Logger -- CSV output *************************/
  828. static gbfile* cd;
  829. static void mtk_csv_init(char* csv_fname, unsigned long bitmask)
  830. {
  831. int i;
  832. FILE* cf;
  833. dbg(1, "Opening csv output file %s...\n", csv_fname);
  834. // can't use gbfopen here - it will fatal() if file doesn't exist
  835. if ((cf = fopen(csv_fname, "r")) != NULL) {
  836. fclose(cf);
  837. warning(MYNAME ": CSV file %s already exist ! Cowardly refusing to overwrite.\n", csv_fname);
  838. return;
  839. }
  840. if ((cd = gbfopen(csv_fname, "w", MYNAME)) == NULL) {
  841. fatal(MYNAME ": Can't open csv file '%s'\n", csv_fname);
  842. }
  843. /* Add the header line */
  844. gbfprintf(cd, "INDEX,%s%s", ((1<<RCR) & bitmask)?"RCR,":"",
  845. ((1<<UTC) & bitmask)?"DATE,TIME,":"");
  846. for (i=0; i<32; i++) {
  847. if ((1<<i) & bitmask) {
  848. switch (i) {
  849. case RCR:
  850. case UTC:
  851. case MILLISECOND:
  852. break;
  853. case SID:
  854. gbfprintf(cd, "SAT INFO (SID");
  855. break;
  856. case ELEVATION:
  857. gbfprintf(cd, "-ELE");
  858. break;
  859. case AZIMUTH:
  860. gbfprintf(cd, "-AZI");
  861. break;
  862. case SNR:
  863. gbfprintf(cd, "-SNR");
  864. break;
  865. default:
  866. gbfprintf(cd, "%s,", log_type[i].name);
  867. break;
  868. }
  869. }
  870. if (i == SNR && (1<<SID) & bitmask) {
  871. gbfprintf(cd, "),");
  872. }
  873. }
  874. gbfprintf(cd, "\n");
  875. }
  876. static void mtk_csv_deinit(void)
  877. {
  878. if (cd != NULL) {
  879. gbfclose(cd);
  880. cd = NULL;
  881. }
  882. }
  883. /* Output a single data line in MTK application compatible format - i.e ignore any locale settings... */
  884. static int csv_line(gbfile* csvFile, int idx, unsigned long bmask, struct data_item* itm)
  885. {
  886. struct tm* ts_tm;
  887. char ts_str[30];
  888. const char* fix_str = "";
  889. ts_tm = gmtime(&(itm->timestamp));
  890. strftime(ts_str, sizeof(ts_str)-1, "%Y/%m/%d,%H:%M:%S", ts_tm);
  891. if (bmask & (1<<VALID)) {
  892. switch (itm->valid) {
  893. case 0x0001:
  894. fix_str = "No fix";
  895. break;
  896. case 0x0002:
  897. fix_str = "SPS";
  898. break;
  899. case 0x0004:
  900. fix_str = "DGPS";
  901. break;
  902. case 0x0008:
  903. fix_str = "PPS";
  904. break; /* Military GPS */
  905. case 0x0010:
  906. fix_str = "RTK";
  907. break; /* RealTime Kinematic */
  908. case 0x0020:
  909. fix_str = "FRTK";
  910. break;
  911. case 0x0040:
  912. fix_str = "Estimated mode";
  913. break;
  914. case 0x0080:
  915. fix_str = "Manual input mode";
  916. break;
  917. case 0x0100:
  918. fix_str = "Simulator";
  919. break;
  920. default:
  921. fix_str = "???";
  922. break;
  923. }
  924. }
  925. gbfprintf(csvFile, "%d,", idx);
  926. // RCR is a bitmask of possibly several log reasons..
  927. if (bmask & (1<<RCR))
  928. gbfprintf(csvFile, "%s%s%s%s,"
  929. , itm->rcr&0x0001?"T":"",itm->rcr&0x0002?"S":""
  930. , itm->rcr&0x0004?"D":"",itm->rcr&0x0008?"B":"");
  931. if (bmask & (1<<UTC)) {
  932. gbfprintf(csvFile, "%s.%.3d,", ts_str, (bmask & (1<<MILLISECOND))?itm->timestamp_ms:0);
  933. }
  934. if (bmask & (1<<VALID)) {
  935. gbfprintf(csvFile, "%s,", fix_str);
  936. }
  937. if (bmask & (1<<LATITUDE | 1<<LONGITUDE))
  938. gbfprintf(csvFile, "%.6f,%c,%.6f,%c,", fabs(itm->lat), itm->lat>0?'N':'S',
  939. fabs(itm->lon), itm->lon>0?'E':'W');
  940. if (bmask & (1<<HEIGHT)) {
  941. gbfprintf(csvFile, "%.3f m,", itm->height);
  942. }
  943. if (bmask & (1<<SPEED)) {
  944. gbfprintf(csvFile, "%.3f km/h,", itm->speed);
  945. }
  946. if (bmask & (1<<HEADING)) {
  947. gbfprintf(csvFile, "%.6f,", itm->heading);
  948. }
  949. if (bmask & (1<<DSTA)) {
  950. gbfprintf(csvFile, "%d,", itm->dsta);
  951. }
  952. if (bmask & (1<<DAGE)) {
  953. gbfprintf(csvFile, "%.6f,", itm->dage);
  954. }
  955. if (bmask & (1<<PDOP)) {
  956. gbfprintf(csvFile, "%.2f,", itm->pdop);
  957. }
  958. if (bmask & (1<<HDOP)) {
  959. gbfprintf(csvFile, "%.2f,", itm->hdop); // note bug in MTK appl. 1.02 is output as 1.2 !
  960. }
  961. if (bmask & (1<<VDOP)) {
  962. gbfprintf(csvFile, "%.2f,", itm->vdop);
  963. }
  964. if (bmask & (1<<NSAT)) {
  965. gbfprintf(csvFile, "%d(%d),", itm->sat_used, itm->sat_view);
  966. }
  967. if (bmask & (1<<SID)) {
  968. int l, slen, do_sc = 0;
  969. char sstr[40];
  970. for (l=0; l<itm->sat_count; l++) {
  971. slen = 0;
  972. slen += sprintf(&sstr[slen], "%s%.2d"
  973. , itm->sat_data[l].used?"#":""
  974. , itm->sat_data[l].id);
  975. if (bmask & (1<<ELEVATION)) {
  976. slen += sprintf(&sstr[slen], "-%.2d", itm->sat_data[l].elevation);
  977. }
  978. if (bmask & (1<<AZIMUTH)) {
  979. slen += sprintf(&sstr[slen], "-%.2d", itm->sat_data[l].azimut);
  980. }
  981. if (bmask & (1<<SNR)) {
  982. slen += sprintf(&sstr[slen], "-%.2d", itm->sat_data[l].snr);
  983. }
  984. gbfprintf(csvFile, "%s%s" , do_sc?";":"", sstr);
  985. do_sc = 1;
  986. }
  987. gbfprintf(csvFile, ",");
  988. }
  989. if (bmask & (1<<DISTANCE)) {
  990. gbfprintf(csvFile, "%10.2f m,", itm->distance);
  991. }
  992. gbfprintf(csvFile, "\n");
  993. return 0;
  994. }
  995. /********************* MTK Logger -- Parse functions *********************/
  996. int mtk_parse(unsigned char* data, int dataLen, unsigned int bmask)
  997. {
  998. static int count = 0;
  999. int i, k, sat_id, hspd;
  1000. unsigned char crc, hbuf[4];
  1001. struct data_item itm;
  1002. dbg(5,"Entering mtk_parse, count = %i, dataLen = %i\n", count, dataLen);
  1003. if (global_opts.debug_level > 5) {
  1004. int j;
  1005. fprintf(stderr,"# Data block:");
  1006. for (j=0; j<dataLen; j++) {
  1007. fprintf(stderr,"%.2x ", data[j]);
  1008. }
  1009. fprintf(stderr,"\n");
  1010. fflush(stderr);
  1011. }
  1012. memset(&itm, 0, sizeof(itm));
  1013. i = 0;
  1014. crc = 0;
  1015. for (k=0; k<32; k++) {
  1016. switch (((1<<k) & bmask)) {
  1017. case 1<<UTC:
  1018. itm.timestamp = le_read32(data + i);
  1019. break;
  1020. case 1<<VALID:
  1021. itm.valid = le_read16(data + i);
  1022. break;
  1023. case 1<<LATITUDE:
  1024. if (log_type[LATITUDE].size == 4) {
  1025. itm.lat = endian_read_float(data + i, 1 /* le */); // M-241
  1026. } else {
  1027. itm.lat = endian_read_double(data + i, 1 /* le */);
  1028. }
  1029. break;
  1030. case 1<<LONGITUDE:
  1031. if (log_type[LONGITUDE].size == 4) {
  1032. itm.lon = endian_read_float(data + i, 1 /* le */); // M-241
  1033. } else {
  1034. itm.lon = endian_read_double(data + i, 1 /* le */);
  1035. }
  1036. break;
  1037. case 1<<HEIGHT:
  1038. switch (mtk_device) {
  1039. case HOLUX_GR245: // Stupid Holux GPsport 245 - log speed as centimeters/sec. (in height position !)
  1040. hspd = data[i] + data[i+1]*0x100 + data[i+2]*0x10000 + data[i+3]*0x1000000;
  1041. itm.speed = MPS_TO_KPH(hspd)/100.; // convert to km/h..
  1042. break;
  1043. case HOLUX_M241:
  1044. hbuf[0] = 0x0;
  1045. hbuf[1] = *(data + i);
  1046. hbuf[2] = *(data + i + 1);
  1047. hbuf[3] = *(data + i + 2);
  1048. itm.height = endian_read_float(hbuf, 1 /* le */);
  1049. break;
  1050. case MTK_LOGGER:
  1051. default:
  1052. itm.height = endian_read_float(data + i, 1 /* le */);
  1053. break;
  1054. }
  1055. break;
  1056. case 1<<SPEED:
  1057. if (mtk_device == HOLUX_GR245) { // Stupid Holux GPsport 245 - log height in speed position...
  1058. hbuf[0] = 0x0;
  1059. hbuf[1] = *(data + i);
  1060. hbuf[2] = *(data + i + 1);
  1061. hbuf[3] = *(data + i + 2);
  1062. itm.height = endian_read_float(hbuf, 1 /* le */);
  1063. } else {
  1064. itm.speed = endian_read_float(data + i, 1 /* le */);
  1065. }
  1066. break;
  1067. case 1<<HEADING:
  1068. itm.heading = endian_read_float(data + i, 1 /* le */);
  1069. break;
  1070. case 1<<DSTA:
  1071. itm.dsta = le_read16(data + i);
  1072. break;
  1073. case 1<<DAGE: // ?? fixme - is this a float ?
  1074. itm.dage = endian_read_float(data + i, 1 /* le */);
  1075. break;
  1076. case 1<<PDOP:
  1077. itm.pdop = le_read16(data + i) / 100.;
  1078. break;
  1079. case 1<<HDOP:
  1080. itm.hdop = le_read16(data + i) / 100.;
  1081. break;
  1082. case 1<<VDOP:
  1083. itm.vdop = le_read16(data + i) / 100.;
  1084. break;
  1085. case 1<<NSAT:
  1086. itm.sat_view = data[i];
  1087. itm.sat_used = data[i+1];
  1088. break;
  1089. case 1<<SID: {
  1090. int sat_count, sat_idx, sid_size, l;
  1091. int azoffset, snroffset;
  1092. sat_count = le_read16(data + i + 2);
  1093. if (sat_count > 32) {
  1094. sat_count = 32; // this can't happen ? or...
  1095. }
  1096. itm.sat_count = sat_count;
  1097. sid_size = log_type[SID].size;
  1098. azoffset = 0;
  1099. snroffset = 0;
  1100. if (sat_count > 0) { // handle 'Zero satellites in view issue'
  1101. if (bmask & (1<<ELEVATION)) {
  1102. sid_size += log_type[ELEVATION].size;
  1103. azoffset += log_type[ELEVATION].size;
  1104. snroffset += log_type[ELEVATION].size;
  1105. }
  1106. if (bmask & (1<<AZIMUTH)) {
  1107. sid_size += log_type[AZIMUTH].size;
  1108. snroffset += log_type[AZIMUTH].size;
  1109. }
  1110. if (bmask & (1<<SNR)) {
  1111. sid_size += log_type[SNR].size;
  1112. }
  1113. }
  1114. l = 0;
  1115. sat_idx = 0;
  1116. do {
  1117. sat_id = data[i];
  1118. itm.sat_data[sat_idx].id = sat_id;
  1119. itm.sat_data[sat_idx].used = data[i + 1];
  1120. // get_word(&data[i+2], &smask); // assume - nr of satellites...
  1121. if (sat_count > 0) {
  1122. if (bmask & (1<<ELEVATION)) {
  1123. itm.sat_data[sat_idx].elevation = le_read16(data + i + 4);
  1124. }
  1125. if (bmask & (1<<AZIMUTH)) {
  1126. itm.sat_data[sat_idx].azimut = le_read16(data + i + 4 + azoffset);
  1127. }
  1128. if (bmask & (1<<SNR)) {
  1129. itm.sat_data[sat_idx].snr = le_read16(data + i + 4 + snroffset);
  1130. }
  1131. }
  1132. sat_idx++;
  1133. // duplicated checksum and length calculations...for simplicity...
  1134. for (l = 0; l < sid_size; l++) {
  1135. crc ^= data[i + l];
  1136. }
  1137. i += sid_size;
  1138. sat_count--;
  1139. } while (sat_count > 0);
  1140. }
  1141. continue; // dont do any more checksum calc..
  1142. break;
  1143. case 1<<ELEVATION:
  1144. case 1<<AZIMUTH:
  1145. case 1<<SNR:
  1146. // handled in SID
  1147. continue; // avoid checksum calc
  1148. break;
  1149. case 1<<RCR:
  1150. itm.rcr = le_read16(data + i);
  1151. break;
  1152. case 1<<MILLISECOND:
  1153. itm.timestamp_ms = le_read16(data + i);
  1154. break;
  1155. case 1<<DISTANCE:
  1156. itm.distance = endian_read_double(data + i, 1 /* le */);
  1157. break;
  1158. default:
  1159. // if ( ((1<<k) & bmask) )
  1160. // printf("Unknown ID %d: %.2x %.2x %.2x %.2x\n", k, data[i], data[i+1], data[i+2], data[i+3]);
  1161. break;
  1162. } /* End: switch (bmap) */
  1163. /* update item checksum and length */
  1164. if (((1<<k) & bmask)) {
  1165. int j;
  1166. for (j=0; j<log_type[k].size; j++) {
  1167. crc ^= data[i+j];
  1168. }
  1169. i += log_type[k].size;
  1170. }
  1171. } /* for (bmap,...) */
  1172. if (mtk_device == MTK_LOGGER) { // Holux skips '*' checksum separator
  1173. if (data[i] == '*') {
  1174. i++; // skip '*' separator
  1175. } else {
  1176. dbg(1,"Missing '*' !\n");
  1177. if (data[i] == 0xff) { // in some case star-crc hasn't been written on power off.
  1178. dbg(1, "Bad data point @0x%.6x - skip %d bytes\n", (fl!=NULL)?ftell(fl):-1, i+2);
  1179. return i+2; // include '*' and crc
  1180. }
  1181. }
  1182. }
  1183. if (memcmp(&data[0], &LOG_RST[0], 6) == 0
  1184. && memcmp(&data[12], &LOG_RST[12], 4) == 0) {
  1185. mtk_parse_info(data, dataLen);
  1186. dbg(1," Missed Log restart ?? skipping 16 bytes\n");
  1187. return 16;
  1188. }
  1189. if (data[i] != crc) {
  1190. dbg(0,"%2d: Bad CRC %.2x != %.2x (pos 0x%.6x)\n", count, data[i], crc, (fl!=NULL)?ftell(fl):-1);
  1191. }
  1192. i++; // crc
  1193. count++;
  1194. if (cd != NULL) {
  1195. csv_line(cd, count, bmask, &itm);
  1196. }
  1197. add_trackpoint(count, bmask, &itm);
  1198. mtk_info.track_event = 0;
  1199. return i;
  1200. }
  1201. /*
  1202. Description: Parse an info block
  1203. Globals: mtk_info - bitmask/period/speed/... may be affected if updated.
  1204. */
  1205. static int mtk_parse_info(const unsigned char* data, int dataLen)
  1206. {
  1207. unsigned short cmd;
  1208. unsigned int bm;
  1209. if (dataLen >= 16
  1210. && memcmp(&data[0], &LOG_RST[0], 6) == 0
  1211. && memcmp(&data[12], &LOG_RST[12], 4) == 0) {
  1212. cmd = le_read16(data + 8);
  1213. switch (data[7]) {
  1214. case 0x02:
  1215. bm = le_read32(data + 8);
  1216. dbg(1, "# Log bitmask is: %.8x\n", bm);
  1217. if (mtk_device != MTK_LOGGER) {
  1218. bm &= 0x7fffffffU;
  1219. }
  1220. if (mtk_device == HOLUX_GR245) {
  1221. bm &= ~HOLUX245_MASK;
  1222. }
  1223. if (mtk_info.bitmask != bm) {
  1224. dbg(1," ########## Bitmask Change %.8x -> %.8x ###########\n", mtk_info.bitmask, bm);
  1225. mtk_info.track_event |= MTK_EVT_BITMASK;
  1226. }
  1227. mtk_info.bitmask = bm;
  1228. mtk_info.logLen = mtk_log_len(mtk_info.bitmask);
  1229. break;
  1230. case 0x03:
  1231. dbg(1, "# Log period change %.0f sec\n", cmd/10.);
  1232. mtk_info.track_event |= MTK_EVT_PERIOD;
  1233. if (mtk_device != MTK_LOGGER)
  1234. {
  1235. mtk_info.track_event |= MTK_EVT_START;
  1236. }
  1237. mtk_info.period = cmd;
  1238. break;
  1239. case 0x04:
  1240. dbg(1, "# Log distance change %.1f m\n", cmd/10.);
  1241. mtk_info.track_event |= MTK_EVT_DISTANCE;
  1242. if (mtk_device != MTK_LOGGER)
  1243. {
  1244. mtk_info.track_event |= MTK_EVT_START;
  1245. }
  1246. mtk_info.distance = cmd;
  1247. break;
  1248. case 0x05:
  1249. dbg(1, "# Log speed change %.1f km/h\n", cmd/10.);
  1250. mtk_info.track_event |= MTK_EVT_SPEED;
  1251. mtk_info.speed = cmd;
  1252. break;
  1253. case 0x06:
  1254. dbg(1, "# Log policy change 0x%.4x\n", cmd);
  1255. if (cmd == 0x01) {
  1256. dbg(1, "# Log policy change to OVERWRITE\n");
  1257. }
  1258. if (cmd == 0x02) {
  1259. dbg(1, "# Log policy change to STOP\n");
  1260. }
  1261. break;
  1262. case 0x07:
  1263. if (cmd == 0x0106) {
  1264. dbg(5, "# GPS Logger# Turned On\n");
  1265. if (mtk_device == MTK_LOGGER)
  1266. {
  1267. mtk_info.track_event |= MTK_EVT_START;
  1268. }
  1269. }
  1270. if (cmd == 0x0104) {
  1271. dbg(5, "# GPS Logger# Log disabled\n");
  1272. }
  1273. break;
  1274. default:
  1275. dbg(1, "## Unknown INFO 0x%.2x\n", data[7]);
  1276. break;
  1277. }
  1278. } else {
  1279. if (global_opts.debug_level > 0) {
  1280. fprintf(stderr,"#!! Invalid INFO block !! %d bytes\n >> ", dataLen);
  1281. for (bm=0; bm<16; bm++) {
  1282. fprintf(stderr, "%.2x ", data[bm]);
  1283. }
  1284. fprintf(stderr,"\n");
  1285. }
  1286. return 0;
  1287. }
  1288. return 16;
  1289. }
  1290. static int mtk_log_len(unsigned int bitmask)
  1291. {
  1292. int i, len;
  1293. /* calculate the length of a binary log item. */
  1294. switch (mtk_device) {
  1295. case HOLUX_M241:
  1296. case HOLUX_GR245:
  1297. len = 1; // add crc
  1298. break;
  1299. case MTK_LOGGER:
  1300. default:
  1301. len = 2; // add '*' + crc
  1302. break;
  1303. }
  1304. for (i=0; i<32; i++) {
  1305. if ((1<<i) & bitmask) {
  1306. if (i > DISTANCE && global_opts.debug_level > 0) {
  1307. warning(MYNAME ": Unknown size/meaning of bit %d\n", i);
  1308. }
  1309. if ((i == SID || i == ELEVATION || i == AZIMUTH || i == SNR) && (1<<SID) & bitmask) {
  1310. len += log_type[i].size*32; // worst case, max sat. count..
  1311. } else {
  1312. len += log_type[i].size;
  1313. }
  1314. }
  1315. }
  1316. dbg(3, "Log item size %d bytes\n", len);
  1317. return len;
  1318. }
  1319. /********************** File-in interface ********************************/
  1320. static void file_init_m241(const QString& fname)
  1321. {
  1322. mtk_device = HOLUX_M241;
  1323. file_init(fname);
  1324. }
  1325. static void file_init(const QString& fname)
  1326. {
  1327. dbg(4, "Opening file %s...\n", qPrintable(fname));
  1328. if (fl = fopen(qPrintable(fname), "rb"), NULL == fl) {
  1329. fatal(MYNAME ": Can't open file '%s'\n", qPrintable(fname));
  1330. }
  1331. switch (mtk_device) {
  1332. case HOLUX_M241:
  1333. case HOLUX_GR245:
  1334. log_type[LATITUDE].size = log_type[LONGITUDE].size = 4;
  1335. log_type[HEIGHT].size = 3;
  1336. break;
  1337. default:
  1338. break;
  1339. }
  1340. }
  1341. static void file_deinit(void)
  1342. {
  1343. dbg(4, "Closing file...\n");
  1344. fclose(fl);
  1345. }
  1346. static void holux245_init(void)
  1347. {
  1348. mtk_device = HOLUX_GR245;
  1349. // stupid workaround for a broken Holux-245 device....
  1350. // Height & speed have changed position in bitmask and data on Holux 245 Argh !!!
  1351. log_type[HEIGHT].id = SPEED;
  1352. log_type[HEIGHT].size = 4; // speed size - unit: cm/sec
  1353. log_type[SPEED].id = HEIGHT;
  1354. log_type[SPEED].size = 3; // height size..
  1355. }
  1356. static int is_holux_string(const unsigned char* data, int dataLen)
  1357. {
  1358. if (mtk_device != MTK_LOGGER &&
  1359. dataLen >= 5 &&
  1360. data[0] == (0xff & 'H') &&
  1361. data[1] == (0xff & 'O') &&
  1362. data[2] == (0xff & 'L') &&
  1363. data[3] == (0xff & 'U') &&
  1364. data[4] == (0xff & 'X')) {
  1365. return 1;
  1366. }
  1367. return 0;
  1368. }
  1369. static void file_read(void)
  1370. {
  1371. long fsize, pos;
  1372. int i, j, k, bLen;
  1373. unsigned char buf[512];
  1374. memset(buf, '\0', sizeof(buf));
  1375. /* Get size of file to parse */
  1376. fseek(fl, 0L, SEEK_END);
  1377. fsize = ftell(fl);
  1378. if (fsize <= 0) {
  1379. fatal(MYNAME ": File has size %ld\n", fsize);
  1380. }
  1381. fseek(fl, 0L, SEEK_SET);
  1382. /* Header: 20 bytes
  1383. 47 05 | 7f 1e 0e 00 | 04 01 | 32 00 00 00 e8 03 00 00 00 00 00 00
  1384. u16: Log count 'this 64kByte block' - ffff if not complete.
  1385. u32: Bitmask for logging. (default mask)
  1386. u16; ?? ?? Overwrite/Stop policy
  1387. u32: log period, sec*10
  1388. u32: log distance , meters*10
  1389. u32: log speed , km/h*10
  1390. */
  1391. bLen = 0;
  1392. j = 0;
  1393. pos = 0;
  1394. /* get default bitmask, log period/speed/distance */
  1395. bLen = fread(buf, 1, 20, fl);
  1396. if (bLen == 20) {
  1397. unsigned int mask, log_period, log_distance, log_speed, log_policy;
  1398. log_policy = le_read16(buf + 6);
  1399. if (!(log_policy == 0x0104 || log_policy == 0x0106) && fsize > 0x10000) {
  1400. dbg(1, "Invalid initial log policy 0x%.4x - check next block\n", log_policy);
  1401. fseek(fl, 0x10000, SEEK_SET);
  1402. bLen = fread(buf, 1, 20, fl);
  1403. log_policy = le_read16(buf + 6);
  1404. }
  1405. mask = le_read32(buf + 2);
  1406. if (mtk_device != MTK_LOGGER) { // clear Holux-specific 'low precision' bit
  1407. mask &= 0x7fffffffU;
  1408. }
  1409. log_period = le_read32(buf + 8);
  1410. log_distance = le_read32(buf + 12);
  1411. log_speed = le_read32(buf + 16);
  1412. dbg(1, "Default Bitmask %.8x, Log every %.0f sec, %.0f m, %.0f km/h\n",
  1413. mask, log_period/10., log_distance/10., log_speed/10.);
  1414. mtk_info.bitmask = mask;
  1415. dbg(3, "Using initial bitmask %.8x for parsing the .bin file\n", mtk_info.bitmask);
  1416. mtk_info.period = log_period;
  1417. mtk_info.distance = log_distance;
  1418. mtk_info.speed = log_speed;
  1419. }
  1420. mtk_info.track_event = 0;
  1421. pos = 0x200; // skip header...first data position
  1422. fseek(fl, pos, SEEK_SET);
  1423. /* read initial info blocks -- if any */
  1424. do {
  1425. bLen = fread(buf, 1, 16, fl);
  1426. j = 0;
  1427. if (buf[0] == 0xaa) { // pre-validate to avoid error...
  1428. j = mtk_parse_info(buf, bLen);
  1429. pos += j;
  1430. } else if (is_holux_string(buf, bLen)) {
  1431. pos += j;
  1432. // Note -- Holux245 will have <SP><SP><SP><SP> here...handled below..
  1433. }
  1434. } while (j == 16);
  1435. j = bLen;
  1436. pos += j;
  1437. mtk_info.logLen = mtk_log_len(mtk_info.bitmask);
  1438. dbg(3, "Log item size %d bytes\n", mtk_info.logLen);
  1439. if (csv_file && *csv_file) {
  1440. mtk_csv_init(csv_file, mtk_info.bitmask);
  1441. }
  1442. while (pos < fsize && (bLen = fread(&buf[j], 1, sizeof(buf)-j, fl)) > 0) {
  1443. bLen += j;
  1444. i = 0;
  1445. while ((bLen - i) >= mtk_info.logLen) {
  1446. k = 0;
  1447. if ((bLen - i) >= 16 && memcmp(&buf[i], &LOG_RST[0], 6) == 0
  1448. && memcmp(&buf[i+12], &LOG_RST[12], 4) == 0) {
  1449. mtk_parse_info(&buf[i], (bLen-i));
  1450. k = 16;
  1451. } else if (is_holux_string(&buf[i], (bLen - i))) {
  1452. if (memcmp(&buf[i+10], "WAYPNT", 6) == 0) {
  1453. mtk_info.track_event |= MTK_EVT_WAYPT;
  1454. }
  1455. k = 16;
  1456. // m241 - HOLUXGR241LOGGER or HOLUXGR241WAYPNT or HOLUXGR241LOGGER<SP><SP><SP><SP>
  1457. // gr245 - HOLUXGR245LOGGER<SP><SP><SP><SP> or HOLUXGR245WAYPNT<SP><SP><SP><SP>
  1458. if (memcmp(&buf[i], "HOLUXGR245", 10) == 0) {
  1459. dbg(2, "Detected Holux GR245 !\n");
  1460. holux245_init();
  1461. }
  1462. // Tobias Verbree reports that an M-12ee is like a 245.
  1463. if (memcmp(&buf[i], "HOLUXM1200", 10) == 0) {
  1464. dbg(2, "Detected Holux HOLUXM1200 !\n");
  1465. holux245_init();
  1466. }
  1467. // skip the 4 spaces that may occur on every device
  1468. if (memcmp(&buf[i+16], " ", 4) == 0) { // Assume loglen >= 20...
  1469. k += 4;
  1470. }
  1471. } else if (buf[i] == 0xff && buf[i+1] == 0xff && buf[i+2] == 0xff && buf[i+3] == 0xff
  1472. /* && ((pos + 2*mtk_info.logLen) & 0xffff) < mtk_info.logLen */) {
  1473. /* End of 64k block segment -- realign to next data area */
  1474. k = ((pos+mtk_info.logLen+1024)/0x10000) *0x10000 + 0x200;
  1475. i = sizeof(buf);
  1476. if (k <= pos) {
  1477. k += 0x10000;
  1478. }
  1479. dbg(3, "Jump %ld -> %d / 0x%.6x (fsize %ld) --- \n", pos, k, k, fsize);
  1480. if (k > fsize) {
  1481. dbg(3, "File parse complete !\n");
  1482. pos = k;
  1483. break;
  1484. } else {
  1485. fseek(fl, k, SEEK_SET);
  1486. }
  1487. pos = k;
  1488. continue;
  1489. } else {
  1490. k = mtk_parse(&buf[i], mtk_info.logLen, mtk_info.bitmask);
  1491. }
  1492. i += k;
  1493. pos += k;
  1494. }
  1495. memmove(buf, &buf[i], sizeof(buf)-i);
  1496. j = sizeof(buf)-i;
  1497. }
  1498. mtk_csv_deinit();
  1499. }
  1500. /**************************************************************************/
  1501. // GPS logger will only handle tracks - neither waypoints or tracks...
  1502. // Actually, some of the Holux devices will read waypoints.
  1503. ff_vecs_t mtk_vecs = {
  1504. ff_type_serial,
  1505. {
  1506. ff_cap_read /* waypoints */,
  1507. ff_cap_read /* tracks */,
  1508. ff_cap_none /* routes */
  1509. },
  1510. mtk_rd_init,
  1511. NULL,
  1512. mtk_rd_deinit,
  1513. NULL,
  1514. mtk_read,
  1515. NULL,
  1516. NULL,
  1517. mtk_sargs,
  1518. CET_CHARSET_ASCII, 0 /* ascii is the expected character set */
  1519. /* not fixed, can be changed through command line parameter */
  1520. };
  1521. ff_vecs_t mtk_m241_vecs = {
  1522. ff_type_serial,
  1523. {
  1524. ff_cap_none /* waypoints */,
  1525. ff_cap_read /* tracks */,
  1526. ff_cap_none /* routes */
  1527. },
  1528. mtk_rd_init_m241,
  1529. NULL,
  1530. mtk_rd_deinit,
  1531. NULL,
  1532. mtk_read,
  1533. NULL,
  1534. NULL,
  1535. mtk_sargs,
  1536. CET_CHARSET_ASCII, 0 /* ascii is the expected character set */
  1537. /* not fixed, can be changed through command line parameter */
  1538. };
  1539. /* used for mtk-bin */
  1540. static arglist_t mtk_fargs[] = {
  1541. {
  1542. "csv", &csv_file, "MTK compatible CSV output file",
  1543. NULL, ARGTYPE_STRING, ARG_NOMINMAX
  1544. },
  1545. ARG_TERMINATOR
  1546. };
  1547. ff_vecs_t mtk_fvecs = {
  1548. ff_type_file,
  1549. { ff_cap_read, ff_cap_read, ff_cap_none },
  1550. file_init,
  1551. NULL,
  1552. file_deinit,
  1553. NULL,
  1554. file_read,
  1555. NULL,
  1556. NULL,
  1557. mtk_fargs,
  1558. CET_CHARSET_UTF8, 1 /* master process: don't convert anything | CET-REVIEW */
  1559. };
  1560. ff_vecs_t mtk_m241_fvecs = {
  1561. ff_type_file,
  1562. { ff_cap_read, ff_cap_read, ff_cap_none },
  1563. file_init_m241,
  1564. NULL,
  1565. file_deinit,
  1566. NULL,
  1567. file_read,
  1568. NULL,
  1569. NULL,
  1570. mtk_fargs,
  1571. CET_CHARSET_UTF8, 1 /* master process: don't convert anything | CET-REVIEW */
  1572. };
  1573. /* End file: mtk_logger.c */
  1574. /**************************************************************************/