ublox_worker.c 23 KB

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