ublox_worker.c 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670
  1. #include "ublox_worker_i.h"
  2. #define TAG "UbloxWorker"
  3. UbloxWorker* ublox_worker_alloc() {
  4. UbloxWorker* ublox_worker = malloc(sizeof(UbloxWorker));
  5. ublox_worker->thread =
  6. furi_thread_alloc_ex("UbloxWorker", 2 * 1024, ublox_worker_task, ublox_worker);
  7. ublox_worker->callback = NULL;
  8. ublox_worker->context = NULL;
  9. ublox_worker_change_state(ublox_worker, UbloxWorkerStateReady);
  10. return ublox_worker;
  11. }
  12. void ublox_worker_free(UbloxWorker* ublox_worker) {
  13. furi_assert(ublox_worker);
  14. furi_thread_free(ublox_worker->thread);
  15. free(ublox_worker);
  16. }
  17. UbloxWorkerState ublox_worker_get_state(UbloxWorker* ublox_worker) {
  18. return ublox_worker->state;
  19. }
  20. void ublox_worker_start(
  21. UbloxWorker* ublox_worker,
  22. UbloxWorkerState state,
  23. UbloxWorkerCallback callback,
  24. void* context) {
  25. furi_assert(ublox_worker);
  26. ublox_worker->callback = callback;
  27. ublox_worker->context = context;
  28. ublox_worker_change_state(ublox_worker, state);
  29. furi_thread_start(ublox_worker->thread);
  30. }
  31. void ublox_worker_stop(UbloxWorker* ublox_worker) {
  32. furi_assert(ublox_worker);
  33. furi_assert(ublox_worker->thread);
  34. Ublox* ublox = ublox_worker->context;
  35. furi_assert(ublox);
  36. //FURI_LOG_I(TAG, "worker_stop: %p", ublox);
  37. /*FuriThreadState state = furi_thread_get_state(ublox_worker->thread);
  38. if (state == FuriThreadStateStopped) {
  39. FURI_LOG_I(TAG, "worker state stopped");
  40. } else if (state == FuriThreadStateStarting) {
  41. FURI_LOG_I(TAG, "worker state starting");
  42. } else if (state == FuriThreadStateRunning) {
  43. FURI_LOG_I(TAG, "worker state running");
  44. }*/
  45. // close the logfile if currently logging
  46. //FURI_LOG_I(TAG, "log state: %d", ublox->log_state);
  47. if(ublox->log_state == UbloxLogStateLogging) {
  48. FURI_LOG_I(TAG, "closing log file on worker stop");
  49. ublox->log_state = UbloxLogStateNone;
  50. if(!kml_close_file(&(ublox->kmlfile))) {
  51. FURI_LOG_E(TAG, "failed to close KML file!");
  52. }
  53. }
  54. if(furi_thread_get_state(ublox_worker->thread) != FuriThreadStateStopped) {
  55. ublox_worker_change_state(ublox_worker, UbloxWorkerStateStop);
  56. furi_thread_join(ublox_worker->thread);
  57. }
  58. }
  59. void ublox_worker_change_state(UbloxWorker* ublox_worker, UbloxWorkerState state) {
  60. ublox_worker->state = state;
  61. }
  62. void clear_ublox_data() {
  63. int fails = 0;
  64. if(!furi_hal_i2c_is_device_ready(
  65. &furi_hal_i2c_handle_external,
  66. UBLOX_I2C_ADDRESS << 1,
  67. furi_ms_to_ticks(I2C_TIMEOUT_MS))) {
  68. FURI_LOG_E(TAG, "clear_ublox_data(): device not ready");
  69. return;
  70. }
  71. uint8_t tx[] = {0xff};
  72. uint8_t response = 0;
  73. while(response != 0xff && fails < 30) {
  74. if(!furi_hal_i2c_trx(
  75. &furi_hal_i2c_handle_external,
  76. UBLOX_I2C_ADDRESS << 1,
  77. tx,
  78. 1,
  79. &response,
  80. 1,
  81. furi_ms_to_ticks(I2C_TIMEOUT_MS))) {
  82. // if the GPS is disconnected during this loop, this will
  83. // loop forever, we must make that not happen. 30 loops is
  84. // plenty, and if the clearing doesn't work, the requisite
  85. // error will be generated by the caller on the next
  86. // actual attempt to reach the GPS.
  87. fails++;
  88. FURI_LOG_E(TAG, "clear_ublox_data(): error clearing ublox data");
  89. }
  90. }
  91. }
  92. int32_t ublox_worker_task(void* context) {
  93. UbloxWorker* ublox_worker = context;
  94. furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
  95. if(ublox_worker->state == UbloxWorkerStateRead) {
  96. ublox_worker_read_nav_messages(context);
  97. } else if(ublox_worker->state == UbloxWorkerStateSyncTime) {
  98. FURI_LOG_I(TAG, "sync time");
  99. ublox_worker_sync_to_gps_time(ublox_worker);
  100. } else if(ublox_worker->state == UbloxWorkerStateResetOdometer) {
  101. ublox_worker_reset_odo(ublox_worker);
  102. } else if(ublox_worker->state == UbloxWorkerStateStop) {
  103. FURI_LOG_D(TAG, "state stop");
  104. }
  105. ublox_worker_change_state(ublox_worker, UbloxWorkerStateStop);
  106. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  107. return 0;
  108. }
  109. void ublox_worker_read_nav_messages(void* context) {
  110. // this function is fairly complicated: it inits the GPS, handles
  111. // logging states, and reads data from the GPS to push it to the
  112. // main app struct.
  113. // IMPORTANT NOTE: we don't use a timer that continually respawns
  114. // the thread because that causes a memory leak.
  115. UbloxWorker* ublox_worker = context;
  116. Ublox* ublox = ublox_worker->context;
  117. // We only start logging at the same time we restart the worker.
  118. if(ublox->log_state == UbloxLogStateStartLogging) {
  119. FURI_LOG_I(TAG, "start logging");
  120. // assemble full logfile pathname
  121. FuriString* fullname = furi_string_alloc();
  122. path_concat(furi_string_get_cstr(ublox->logfile_folder), ublox->text_store, fullname);
  123. FURI_LOG_I(TAG, "fullname is %s", furi_string_get_cstr(fullname));
  124. if(!kml_open_file(ublox->storage, &(ublox->kmlfile), furi_string_get_cstr(fullname))) {
  125. FURI_LOG_E(TAG, "failed to open KML file %s!", furi_string_get_cstr(fullname));
  126. ublox->log_state = UbloxLogStateNone;
  127. }
  128. ublox->log_state = UbloxLogStateLogging;
  129. furi_string_free(fullname);
  130. ublox_worker->callback(UbloxWorkerEventLogStateChanged, ublox_worker->context);
  131. }
  132. while(!ublox->gps_initted) {
  133. if(ublox_worker->state != UbloxWorkerStateRead) {
  134. return;
  135. }
  136. // have to clear right before init to make retrying init work
  137. clear_ublox_data();
  138. if(ublox_worker_init_gps(ublox_worker)) {
  139. ublox->gps_initted = true;
  140. break;
  141. } else {
  142. ublox_worker->callback(UbloxWorkerEventFailed, ublox_worker->context);
  143. FURI_LOG_E(TAG, "init GPS failed, try again");
  144. }
  145. uint32_t ticks = furi_get_tick();
  146. // don't try constantly, no reason to
  147. while(furi_get_tick() - ticks < furi_ms_to_ticks(500)) {
  148. if(ublox_worker->state != UbloxWorkerStateRead) {
  149. return;
  150. }
  151. }
  152. //furi_delay_ms(500);
  153. }
  154. // clear data so we don't an error on startup
  155. clear_ublox_data();
  156. // break the loop when the thread state changes
  157. while(ublox_worker->state == UbloxWorkerStateRead) {
  158. // reading takes a little time, measure here
  159. uint32_t ticks = furi_get_tick();
  160. // we interrupt with checking the state to help reduce
  161. // lag. it's not perfect, but it does overall improve things.
  162. bool pvt = ublox_worker_read_pvt(ublox_worker);
  163. if(ublox_worker->state != UbloxWorkerStateRead) break;
  164. // clearing makes the second read much faster
  165. clear_ublox_data();
  166. if(ublox_worker->state != UbloxWorkerStateRead) break;
  167. bool odo = ublox_worker_read_odo(ublox_worker);
  168. if(pvt && odo) {
  169. ublox_worker->callback(UbloxWorkerEventDataReady, ublox_worker->context);
  170. if(ublox->log_state == UbloxLogStateLogging) {
  171. if(!kml_add_path_point(
  172. &(ublox->kmlfile),
  173. // ublox returns values as floats * 1e7 in int form
  174. (double)(ublox->nav_pvt.lat) / (double)1e7,
  175. (double)(ublox->nav_pvt.lon) / (double)1e7,
  176. ublox->nav_pvt.hMSL / 1e3)) { // convert altitude to meters
  177. FURI_LOG_E(TAG, "failed to write line to file");
  178. }
  179. }
  180. } else {
  181. ublox_worker->callback(UbloxWorkerEventFailed, ublox_worker->context);
  182. }
  183. while(furi_get_tick() - ticks <
  184. furi_ms_to_ticks(((ublox->data_display_state).refresh_rate * 1000))) {
  185. // putting this here (should) make the logging response faster
  186. if(ublox->log_state == UbloxLogStateStopLogging) {
  187. FURI_LOG_I(TAG, "stop logging");
  188. if(!kml_close_file(&(ublox->kmlfile))) {
  189. FURI_LOG_E(TAG, "failed to close KML file!");
  190. }
  191. ublox->log_state = UbloxLogStateNone;
  192. ublox_worker->callback(UbloxWorkerEventLogStateChanged, ublox_worker->context);
  193. }
  194. if(ublox_worker->state != UbloxWorkerStateRead) {
  195. return;
  196. }
  197. }
  198. }
  199. }
  200. void ublox_worker_sync_to_gps_time(void* context) {
  201. UbloxWorker* ublox_worker = context;
  202. Ublox* ublox = ublox_worker->context;
  203. UbloxFrame frame_tx;
  204. frame_tx.class = UBX_NAV_CLASS;
  205. frame_tx.id = UBX_NAV_TIMEUTC_MESSAGE;
  206. frame_tx.len = 0;
  207. frame_tx.payload = NULL;
  208. UbloxMessage* message_tx = ublox_frame_to_bytes(&frame_tx);
  209. UbloxMessage* message_rx =
  210. ublox_worker_i2c_transfer(message_tx, UBX_NAV_TIMEUTC_MESSAGE_LENGTH);
  211. if(message_rx == NULL) {
  212. FURI_LOG_E(TAG, "get_gps_time transfer failed");
  213. ublox_worker_change_state(ublox_worker, UbloxWorkerStateStop);
  214. ublox_worker->callback(UbloxWorkerEventFailed, ublox_worker->context);
  215. return;
  216. }
  217. FURI_LOG_I(TAG, "got message");
  218. UbloxFrame* frame_rx = ublox_bytes_to_frame(message_rx);
  219. ublox_message_free(message_rx);
  220. if(frame_rx == NULL) {
  221. FURI_LOG_E(TAG, "NULL pointer, something wrong with NAV-TIMEUTC message!");
  222. ublox_worker_change_state(ublox_worker, UbloxWorkerStateStop);
  223. ublox_worker->callback(UbloxWorkerEventFailed, ublox_worker->context);
  224. return;
  225. } else {
  226. Ublox_NAV_TIMEUTC_Message nav_timeutc = {
  227. .iTOW = (frame_rx->payload[0]) | (frame_rx->payload[1] << 8) |
  228. (frame_rx->payload[2] << 16) | (frame_rx->payload[3] << 24),
  229. .tAcc = (frame_rx->payload[4]) | (frame_rx->payload[5] << 8) |
  230. (frame_rx->payload[6] << 16) | (frame_rx->payload[7] << 24),
  231. .nano = (frame_rx->payload[8]) | (frame_rx->payload[9] << 8) |
  232. (frame_rx->payload[10] << 16) | (frame_rx->payload[11] << 24),
  233. .year = (frame_rx->payload[12]) | (frame_rx->payload[13] << 8),
  234. .month = frame_rx->payload[14],
  235. .day = frame_rx->payload[15],
  236. .hour = frame_rx->payload[16],
  237. .min = frame_rx->payload[17],
  238. .sec = frame_rx->payload[18],
  239. .valid = frame_rx->payload[19],
  240. };
  241. ublox->nav_timeutc = nav_timeutc;
  242. ublox_frame_free(frame_rx);
  243. ublox_worker->callback(UbloxWorkerEventDataReady, ublox_worker->context);
  244. }
  245. }
  246. FuriString* print_uint8_array(uint8_t* array, int length) {
  247. FuriString* s = furi_string_alloc();
  248. for(int i = 0; i < length - 1; i++) {
  249. furi_string_cat_printf(s, "%x, ", array[i]);
  250. }
  251. furi_string_cat_printf(s, "%x", array[length - 1]);
  252. return s;
  253. }
  254. UbloxMessage* ublox_worker_i2c_transfer(UbloxMessage* message_tx, uint8_t read_length) {
  255. if(!furi_hal_i2c_is_device_ready(
  256. &furi_hal_i2c_handle_external,
  257. UBLOX_I2C_ADDRESS << 1,
  258. furi_ms_to_ticks(I2C_TIMEOUT_MS))) {
  259. FURI_LOG_E(TAG, "device not ready");
  260. return NULL;
  261. }
  262. // Either our I2C implementation is broken or the GPS's is, so we
  263. // end up reading a lot more data than we need to. That means that
  264. // the I2C comm code for this app is a little bit of a hack, but
  265. // it works fine and is fast enough, so I don't really care. It
  266. // certainly doesn't break the GPS.
  267. if(!furi_hal_i2c_tx(
  268. &furi_hal_i2c_handle_external,
  269. UBLOX_I2C_ADDRESS << 1,
  270. message_tx->message,
  271. message_tx->length,
  272. furi_ms_to_ticks(I2C_TIMEOUT_MS))) {
  273. FURI_LOG_E(TAG, "error writing message to GPS");
  274. return NULL;
  275. }
  276. uint8_t* response = malloc((size_t)read_length);
  277. // The GPS sends 0xff until it has a complete message to respond
  278. // with. We have to wait until it stops sending that. (Why this
  279. // works is a little bit...uh, well, I don't know. Shouldn't reading
  280. // more bytes make it so that the data is completely read out and no
  281. // longer available?)
  282. //FURI_LOG_I(TAG, "start ticks at %lu", furi_get_tick()); // returns ms
  283. while(true) {
  284. if(!furi_hal_i2c_rx(
  285. &furi_hal_i2c_handle_external,
  286. UBLOX_I2C_ADDRESS << 1,
  287. response,
  288. 1,
  289. furi_ms_to_ticks(I2C_TIMEOUT_MS))) {
  290. FURI_LOG_E(TAG, "error reading first byte of response");
  291. free(response);
  292. return NULL;
  293. }
  294. // checking with 0xb5 prevents strange bursts of junk data from becoming an issue.
  295. if(response[0] != 0xff && response[0] == 0xb5) {
  296. //FURI_LOG_I(TAG, "read rest of message at %lu", furi_get_tick());
  297. if(!furi_hal_i2c_rx(
  298. &furi_hal_i2c_handle_external,
  299. UBLOX_I2C_ADDRESS << 1,
  300. &(response[1]),
  301. read_length - 1, // first byte already read
  302. furi_ms_to_ticks(I2C_TIMEOUT_MS))) {
  303. FURI_LOG_E(TAG, "error reading rest of response");
  304. free(response);
  305. return NULL;
  306. }
  307. break;
  308. }
  309. furi_delay_ms(1);
  310. }
  311. UbloxMessage* message_rx = malloc(sizeof(UbloxMessage));
  312. message_rx->message = response;
  313. message_rx->length = read_length;
  314. return message_rx; // message_rx->message needs to be freed later
  315. }
  316. bool ublox_worker_read_pvt(UbloxWorker* ublox_worker) {
  317. //FURI_LOG_I(TAG, "mem free before PVT read: %u", memmgr_get_free_heap());
  318. Ublox* ublox = ublox_worker->context;
  319. // Read NAV-PVT by sending NAV-PVT with no payload
  320. UbloxFrame* frame_tx = malloc(sizeof(UbloxFrame));
  321. frame_tx->class = UBX_NAV_CLASS;
  322. frame_tx->id = UBX_NAV_PVT_MESSAGE;
  323. frame_tx->len = 0;
  324. frame_tx->payload = NULL;
  325. UbloxMessage* message_tx = ublox_frame_to_bytes(frame_tx);
  326. ublox_frame_free(frame_tx);
  327. UbloxMessage* message_rx = ublox_worker_i2c_transfer(message_tx, UBX_NAV_PVT_MESSAGE_LENGTH);
  328. ublox_message_free(message_tx);
  329. if(message_rx == NULL) {
  330. FURI_LOG_E(TAG, "read_pvt transfer failed");
  331. //ublox_worker_change_state(ublox_worker, UbloxWorkerStateStop);
  332. return false;
  333. }
  334. UbloxFrame* frame_rx = ublox_bytes_to_frame(message_rx);
  335. ublox_message_free(message_rx);
  336. if(frame_rx == NULL) {
  337. FURI_LOG_E(TAG, "NULL pointer, something wrong with NAV-PVT message!");
  338. //ublox_worker_change_state(ublox_worker, UbloxWorkerStateStop);
  339. return false;
  340. } else {
  341. // build nav-pvt struct. this is very ugly and there's not much I can do about it.
  342. Ublox_NAV_PVT_Message nav_pvt = {
  343. .iTOW = (frame_rx->payload[0]) | (frame_rx->payload[1] << 8) |
  344. (frame_rx->payload[2] << 16) | (frame_rx->payload[3] << 24),
  345. .year = (frame_rx->payload[4]) | (frame_rx->payload[5] << 8),
  346. .month = frame_rx->payload[6],
  347. .day = frame_rx->payload[7],
  348. .hour = frame_rx->payload[8],
  349. .min = frame_rx->payload[9],
  350. .sec = frame_rx->payload[10],
  351. .valid = frame_rx->payload[11],
  352. .tAcc = (frame_rx->payload[12]) | (frame_rx->payload[13] << 8) |
  353. (frame_rx->payload[14] << 16) | (frame_rx->payload[15] << 24),
  354. .nano = (frame_rx->payload[16]) | (frame_rx->payload[17] << 8) |
  355. (frame_rx->payload[18] << 16) | (frame_rx->payload[19] << 24),
  356. .fixType = frame_rx->payload[20],
  357. .flags = frame_rx->payload[21],
  358. .flags2 = frame_rx->payload[22],
  359. .numSV = frame_rx->payload[23],
  360. .lon = (frame_rx->payload[24]) | (frame_rx->payload[25] << 8) |
  361. (frame_rx->payload[26] << 16) | (frame_rx->payload[27] << 24),
  362. .lat = (frame_rx->payload[28]) | (frame_rx->payload[29] << 8) |
  363. (frame_rx->payload[30] << 16) | (frame_rx->payload[31] << 24),
  364. .height = (frame_rx->payload[32]) | (frame_rx->payload[33] << 8) |
  365. (frame_rx->payload[34] << 16) | (frame_rx->payload[35] << 24),
  366. .hMSL = (frame_rx->payload[36]) | (frame_rx->payload[37] << 8) |
  367. (frame_rx->payload[38] << 16) | (frame_rx->payload[39] << 24),
  368. .hAcc = (frame_rx->payload[40]) | (frame_rx->payload[41] << 8) |
  369. (frame_rx->payload[42] << 16) | (frame_rx->payload[43] << 24),
  370. .vAcc = (frame_rx->payload[44]) | (frame_rx->payload[45] << 8) |
  371. (frame_rx->payload[46] << 16) | (frame_rx->payload[47] << 24),
  372. .velN = (frame_rx->payload[48]) | (frame_rx->payload[49] << 8) |
  373. (frame_rx->payload[50] << 16) | (frame_rx->payload[51] << 24),
  374. .velE = (frame_rx->payload[52]) | (frame_rx->payload[53] << 8) |
  375. (frame_rx->payload[54] << 16) | (frame_rx->payload[55] << 24),
  376. .velD = (frame_rx->payload[56]) | (frame_rx->payload[57] << 8) |
  377. (frame_rx->payload[58] << 16) | (frame_rx->payload[59] << 24),
  378. .gSpeed = (frame_rx->payload[60]) | (frame_rx->payload[61] << 8) |
  379. (frame_rx->payload[62] << 16) | (frame_rx->payload[63] << 24),
  380. .headMot = (frame_rx->payload[64]) | (frame_rx->payload[65] << 8) |
  381. (frame_rx->payload[66] << 16) | (frame_rx->payload[67] << 24),
  382. .sAcc = (frame_rx->payload[68]) | (frame_rx->payload[69] << 8) |
  383. (frame_rx->payload[70] << 16) | (frame_rx->payload[71] << 24),
  384. .headAcc = (frame_rx->payload[72]) | (frame_rx->payload[73] << 8) |
  385. (frame_rx->payload[74] << 16) | (frame_rx->payload[75] << 24),
  386. .pDOP = (frame_rx->payload[76]) | (frame_rx->payload[77] << 8),
  387. .flags3 = (frame_rx->payload[78]) | (frame_rx->payload[79] << 8),
  388. .reserved1 = frame_rx->payload[80],
  389. .reserved2 = frame_rx->payload[81],
  390. .reserved3 = frame_rx->payload[82],
  391. .reserved4 = frame_rx->payload[83],
  392. .headVeh = (frame_rx->payload[84]) | (frame_rx->payload[85] << 8) |
  393. (frame_rx->payload[86] << 16) | (frame_rx->payload[87] << 24),
  394. .magDec = (frame_rx->payload[88]) | (frame_rx->payload[89] << 8),
  395. .magAcc = (frame_rx->payload[90]) | (frame_rx->payload[91] << 8),
  396. };
  397. // Using a local variable for nav_pvt is fine, because nav_pvt in
  398. // the Ublox struct is also not a pointer, so this assignment
  399. // effectively compiles to a memcpy.
  400. ublox->nav_pvt = nav_pvt;
  401. ublox_frame_free(frame_rx);
  402. return true;
  403. }
  404. return false;
  405. }
  406. bool ublox_worker_read_odo(UbloxWorker* ublox_worker) {
  407. Ublox* ublox = ublox_worker->context;
  408. UbloxFrame* frame_tx = malloc(sizeof(UbloxFrame));
  409. frame_tx->class = UBX_NAV_CLASS;
  410. frame_tx->id = UBX_NAV_ODO_MESSAGE;
  411. frame_tx->len = 0;
  412. frame_tx->payload = NULL;
  413. UbloxMessage* message_tx = ublox_frame_to_bytes(frame_tx);
  414. ublox_frame_free(frame_tx);
  415. UbloxMessage* message_rx = ublox_worker_i2c_transfer(message_tx, UBX_NAV_ODO_MESSAGE_LENGTH);
  416. ublox_message_free(message_tx);
  417. if(message_rx == NULL) {
  418. FURI_LOG_E(TAG, "read_odo transfer failed");
  419. return false;
  420. }
  421. UbloxFrame* frame_rx = ublox_bytes_to_frame(message_rx);
  422. ublox_message_free(message_rx);
  423. if(frame_rx == NULL) {
  424. FURI_LOG_E(TAG, "NULL pointer, something wrong with NAV-ODO message!");
  425. return false;
  426. } else {
  427. Ublox_NAV_ODO_Message nav_odo = {
  428. .version = frame_rx->payload[0],
  429. .reserved1 = frame_rx->payload[1],
  430. .reserved2 = frame_rx->payload[2],
  431. .reserved3 = frame_rx->payload[3],
  432. .iTOW = (frame_rx->payload[4]) | (frame_rx->payload[5] << 8) |
  433. (frame_rx->payload[6] << 16) | (frame_rx->payload[7] << 24),
  434. .distance = (frame_rx->payload[8]) | (frame_rx->payload[9] << 8) |
  435. (frame_rx->payload[10] << 16) | (frame_rx->payload[11] << 24),
  436. .totalDistance = (frame_rx->payload[12]) | (frame_rx->payload[13] << 8) |
  437. (frame_rx->payload[14] << 16) | (frame_rx->payload[15] << 24),
  438. .distanceStd = (frame_rx->payload[16]) | (frame_rx->payload[17] << 8) |
  439. (frame_rx->payload[18] << 16) | (frame_rx->payload[19] << 24),
  440. };
  441. //FURI_LOG_I(TAG, "odo (m): %lu", nav_odo.distance);
  442. ublox->nav_odo = nav_odo;
  443. ublox_frame_free(frame_rx);
  444. return true;
  445. }
  446. }
  447. /**
  448. * Set the power mode to "Balanced", enable the odometer, and
  449. * configure odometer and dynamic platform model according to user
  450. * settings.
  451. */
  452. bool ublox_worker_init_gps(UbloxWorker* ublox_worker) {
  453. Ublox* ublox = ublox_worker->context;
  454. // Set power mode
  455. /*** read initial cfg-pms configuration first ***/
  456. UbloxFrame pms_frame_tx;
  457. pms_frame_tx.class = UBX_CFG_CLASS;
  458. pms_frame_tx.id = UBX_CFG_PMS_MESSAGE;
  459. pms_frame_tx.len = 0;
  460. pms_frame_tx.payload = NULL;
  461. UbloxMessage* pms_message_tx = ublox_frame_to_bytes(&pms_frame_tx);
  462. UbloxMessage* pms_message_rx =
  463. ublox_worker_i2c_transfer(pms_message_tx, UBX_CFG_PMS_MESSAGE_LENGTH);
  464. ublox_message_free(pms_message_tx);
  465. if(pms_message_rx == NULL) {
  466. FURI_LOG_E(TAG, "CFG-PMS read transfer failed");
  467. return false;
  468. }
  469. // set power setup value to "balanced"
  470. pms_message_rx->message[6 + 1] = 0x01;
  471. pms_frame_tx.class = UBX_CFG_CLASS;
  472. pms_frame_tx.id = UBX_CFG_PMS_MESSAGE;
  473. pms_frame_tx.len = 8;
  474. pms_frame_tx.payload = pms_message_rx->message;
  475. pms_message_tx = ublox_frame_to_bytes(&pms_frame_tx);
  476. UbloxMessage* ack = ublox_worker_i2c_transfer(pms_message_tx, UBX_ACK_ACK_MESSAGE_LENGTH);
  477. if(ack == NULL) {
  478. FURI_LOG_E(TAG, "ACK after CFG-PMS set transfer failed");
  479. return false;
  480. }
  481. FURI_LOG_I(
  482. TAG, "CFG-PMS ack: id = %u, type = %s", ack->message[3], ack->message[3] ? "ACK" : "NAK");
  483. ublox_message_free(pms_message_tx);
  484. ublox_message_free(pms_message_rx);
  485. ublox_message_free(ack);
  486. /***** Odometer *****/
  487. // Enable odometer by changing CFG-ODO.
  488. UbloxFrame odo_frame_tx;
  489. odo_frame_tx.class = UBX_CFG_CLASS;
  490. odo_frame_tx.id = UBX_CFG_ODO_MESSAGE;
  491. odo_frame_tx.len = 0;
  492. odo_frame_tx.payload = NULL;
  493. UbloxMessage* odo_message_tx = ublox_frame_to_bytes(&odo_frame_tx);
  494. UbloxMessage* odo_message_rx =
  495. ublox_worker_i2c_transfer(odo_message_tx, UBX_CFG_ODO_MESSAGE_LENGTH);
  496. ublox_message_free(odo_message_tx);
  497. if(odo_message_rx == NULL) {
  498. FURI_LOG_E(TAG, "CFG-ODO transfer failed");
  499. return false;
  500. }
  501. odo_frame_tx.class = UBX_CFG_CLASS;
  502. odo_frame_tx.id = UBX_CFG_ODO_MESSAGE;
  503. odo_frame_tx.len = 20;
  504. odo_frame_tx.payload = odo_message_rx->message;
  505. // TODO: low-pass filters in settings?
  506. // enable useODO bit in flags
  507. odo_frame_tx.payload[4] |= 1;
  508. odo_frame_tx.payload[5] = (ublox->device_state).odometer_mode;
  509. odo_message_tx = ublox_frame_to_bytes(&odo_frame_tx);
  510. ack = ublox_worker_i2c_transfer(odo_message_tx, UBX_ACK_ACK_MESSAGE_LENGTH);
  511. if(ack == NULL) {
  512. FURI_LOG_E(TAG, "ACK after CFG-ODO set transfer failed");
  513. return false;
  514. }
  515. FURI_LOG_I(
  516. TAG, "CFG-ODO ack: id = %u, type = %s", ack->message[3], ack->message[3] ? "ACK" : "NAK");
  517. ublox_message_free(odo_message_tx);
  518. ublox_message_free(odo_message_rx);
  519. ublox_message_free(ack);
  520. // finally configure the navigation engine
  521. UbloxFrame nav5_frame_tx;
  522. nav5_frame_tx.class = UBX_CFG_CLASS;
  523. nav5_frame_tx.id = UBX_CFG_NAV5_MESSAGE;
  524. nav5_frame_tx.len = 0;
  525. nav5_frame_tx.payload = NULL;
  526. UbloxMessage* nav5_message_tx = ublox_frame_to_bytes(&nav5_frame_tx);
  527. UbloxMessage* nav5_message_rx =
  528. ublox_worker_i2c_transfer(nav5_message_tx, UBX_CFG_NAV5_MESSAGE_LENGTH);
  529. ublox_message_free(nav5_message_tx);
  530. if(nav5_message_rx == NULL) {
  531. FURI_LOG_E(TAG, "CFG-NAV5 transfer failed");
  532. return false;
  533. }
  534. // first two bytes tell the GPS what changes to apply, setting this
  535. // bit tells it to apply the dynamic platfrom model settings.
  536. nav5_frame_tx.class = UBX_CFG_CLASS;
  537. nav5_frame_tx.id = UBX_CFG_NAV5_MESSAGE;
  538. nav5_frame_tx.len = 36;
  539. nav5_frame_tx.payload = nav5_message_rx->message;
  540. nav5_frame_tx.payload[0] = 1; // tell GPS to apply only the platform model settings
  541. nav5_frame_tx.payload[2] = (ublox->device_state).platform_model;
  542. nav5_message_tx = ublox_frame_to_bytes(&nav5_frame_tx);
  543. ack = ublox_worker_i2c_transfer(nav5_message_tx, UBX_ACK_ACK_MESSAGE_LENGTH);
  544. if(ack == NULL) {
  545. FURI_LOG_E(TAG, "ACK after CFG-NAV5 set transfer failed");
  546. return false;
  547. }
  548. FURI_LOG_I(
  549. TAG, "CFG-NAV5 ack: id = %u, type = %s", ack->message[3], ack->message[3] ? "ACK" : "NAK");
  550. ublox_message_free(nav5_message_rx);
  551. ublox_message_free(ack);
  552. return true;
  553. }
  554. // this one is being kind of slow
  555. void ublox_worker_reset_odo(UbloxWorker* ublox_worker) {
  556. FURI_LOG_I(TAG, "ublox_worker_reset_odo");
  557. UbloxFrame odo_frame_tx;
  558. odo_frame_tx.class = UBX_NAV_CLASS;
  559. odo_frame_tx.id = UBX_NAV_RESETODO_MESSAGE;
  560. odo_frame_tx.len = 0;
  561. odo_frame_tx.payload = NULL;
  562. UbloxMessage* odo_message_tx = ublox_frame_to_bytes(&odo_frame_tx);
  563. UbloxMessage* ack = ublox_worker_i2c_transfer(odo_message_tx, UBX_ACK_ACK_MESSAGE_LENGTH);
  564. ublox_message_free(odo_message_tx);
  565. if(ack == NULL) {
  566. FURI_LOG_E(TAG, "ACK after NAV-RESETODO set transfer failed");
  567. ublox_worker->callback(UbloxWorkerEventFailed, ublox_worker->context);
  568. return;
  569. } else {
  570. FURI_LOG_I(
  571. TAG,
  572. "NAV-RESETODO ack: id = %u, type = %s",
  573. ack->message[3],
  574. ack->message[3] ? "ACK" : "NAK");
  575. ublox_message_free(ack);
  576. }
  577. ublox_worker->callback(UbloxWorkerEventOdoReset, ublox_worker->context);
  578. // no reason to trigger an event on success, the user will see that
  579. // the odometer has been reset on the next update.
  580. }