ublox_worker.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  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 = furi_thread_alloc_ex("UbloxWorker", 2*1024, ublox_worker_task, ublox_worker);
  6. ublox_worker->callback = NULL;
  7. ublox_worker->context = NULL;
  8. ublox_worker_change_state(ublox_worker, UbloxWorkerStateReady);
  9. return ublox_worker;
  10. }
  11. void ublox_worker_free(UbloxWorker* ublox_worker) {
  12. furi_assert(ublox_worker);
  13. furi_thread_free(ublox_worker->thread);
  14. free(ublox_worker);
  15. }
  16. UbloxWorkerState ublox_worker_get_state(UbloxWorker* ublox_worker) {
  17. return ublox_worker->state;
  18. }
  19. void ublox_worker_start(UbloxWorker* ublox_worker,
  20. UbloxWorkerState state,
  21. UbloxWorkerCallback callback,
  22. void* context) {
  23. furi_assert(ublox_worker);
  24. ublox_worker->callback = callback;
  25. ublox_worker->context = context;
  26. ublox_worker_change_state(ublox_worker, state);
  27. furi_thread_start(ublox_worker->thread);
  28. }
  29. void ublox_worker_stop(UbloxWorker* ublox_worker) {
  30. furi_assert(ublox_worker);
  31. furi_assert(ublox_worker->thread);
  32. FURI_LOG_I(TAG, "worker_stop");
  33. if (furi_thread_get_state(ublox_worker->thread) != FuriThreadStateStopped) {
  34. FURI_LOG_I(TAG, "set thread state to stopped");
  35. ublox_worker_change_state(ublox_worker, UbloxWorkerStateStop);
  36. furi_thread_join(ublox_worker->thread);
  37. }
  38. }
  39. void ublox_worker_change_state(UbloxWorker* ublox_worker, UbloxWorkerState state) {
  40. ublox_worker->state = state;
  41. }
  42. void clear_ublox_data() {
  43. uint8_t tx[] = {0xff};
  44. uint8_t response = 0;
  45. while (response != 0xff) {
  46. if (!furi_hal_i2c_trx(&furi_hal_i2c_handle_external,
  47. UBLOX_I2C_ADDRESS << 1,
  48. tx, 1,
  49. &response, 1,
  50. furi_ms_to_ticks(I2C_TIMEOUT_MS))) {
  51. FURI_LOG_E(TAG, "error reading first byte of response");
  52. }
  53. }
  54. }
  55. int32_t ublox_worker_task(void* context) {
  56. UbloxWorker* ublox_worker = context;
  57. Ublox* ublox = ublox_worker->context;
  58. if (ublox_worker->state == UbloxWorkerStateRead) {
  59. furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
  60. if (!ublox->gps_initted) {
  61. if (ublox_worker_init_gps(ublox_worker)) {
  62. ublox->gps_initted = true;
  63. } else {
  64. ublox_worker->callback(UbloxWorkerEventFailed, ublox_worker->context);
  65. FURI_LOG_E(TAG, "init GPS failed");
  66. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  67. return 1;
  68. }
  69. // have to do this...don't know why, though, because the data
  70. // should already be cleared out (also why does this even work, it
  71. // seems like it should be capturing the first byte of the next
  72. // message)
  73. clear_ublox_data();
  74. }
  75. ublox_worker_read_pvt(ublox_worker);
  76. ublox_worker_read_odo(ublox_worker);
  77. furi_hal_i2c_release(&furi_hal_i2c_handle_external);
  78. ublox_worker->callback(UbloxWorkerEventDataReady, ublox_worker->context);
  79. /*if (ublox_worker_read_odo(ublox_worker)) {
  80. ublox_worker_read_pvt(ublox_worker);
  81. } else {
  82. next_state = UbloxWorkerStateStop;
  83. ublox_worker->callback(UbloxWorkerEventFailed, ublox_worker->context);
  84. }*/
  85. } else if (ublox_worker->state == UbloxWorkerStateResetOdometer) {
  86. ublox_worker_reset_odo(ublox_worker);
  87. } else if (ublox_worker->state == UbloxWorkerStateStop) {
  88. FURI_LOG_I(TAG, "state stop");
  89. } else if (ublox_worker->state == UbloxWorkerStateReady) {
  90. FURI_LOG_I(TAG, "state ready");
  91. }
  92. ublox_worker_change_state(ublox_worker, UbloxWorkerStateReady);
  93. //FURI_LOG_I(TAG, "mem free after: %u", memmgr_get_free_heap());
  94. return 0;
  95. }
  96. FuriString* print_uint8_array(uint8_t* array, int length) {
  97. FuriString* s = furi_string_alloc();
  98. for (int i = 0; i < length - 1; i++) {
  99. furi_string_cat_printf(s, "%x, ", array[i]);
  100. }
  101. furi_string_cat_printf(s, "%x", array[length - 1]);
  102. return s;
  103. }
  104. UbloxMessage* ublox_worker_i2c_transfer(UbloxMessage* message_tx, uint8_t read_length) {
  105. //FURI_LOG_I(TAG, "ublox_worker_i2c_transfer");
  106. if (!furi_hal_i2c_is_device_ready(&furi_hal_i2c_handle_external,
  107. UBLOX_I2C_ADDRESS << 1,
  108. furi_ms_to_ticks(I2C_TIMEOUT_MS))) {
  109. FURI_LOG_E(TAG, "GPS not found!");
  110. return NULL;
  111. }
  112. if (!furi_hal_i2c_tx(&furi_hal_i2c_handle_external,
  113. UBLOX_I2C_ADDRESS << 1,
  114. message_tx->message,
  115. message_tx->length,
  116. furi_ms_to_ticks(I2C_TIMEOUT_MS))) {
  117. FURI_LOG_I(TAG, "error writing message from GPS");
  118. return NULL;
  119. }
  120. uint8_t* response = malloc((size_t)read_length);
  121. // The GPS sends 0xff until it has a complete message to respond
  122. // with. We have to wait until it stops sending that. (Why this
  123. // works is a little bit...uh, well, I don't know. Shouldn't reading
  124. // more bytes make it so that the data is completely read out and no
  125. // longer available?)
  126. // Also, we know that this function is the traceable source of the
  127. // memory leak whenever it's run a second time.
  128. // ** The leak comes after this point.
  129. uint8_t tx[] = {0xff};
  130. while (true) {
  131. //FURI_LOG_I(TAG, "mem free in loop: %u", memmgr_get_free_heap());
  132. if (!furi_hal_i2c_trx(&furi_hal_i2c_handle_external,
  133. UBLOX_I2C_ADDRESS << 1,
  134. tx, 1,
  135. response, 1,
  136. furi_ms_to_ticks(I2C_TIMEOUT_MS))) {
  137. FURI_LOG_E(TAG, "error reading first byte of response");
  138. free(response);
  139. return NULL;
  140. }
  141. //FURI_LOG_I(TAG, "read one byte");
  142. // checking with 0xb5 prevents strange bursts of junk data from becoming an issue.
  143. if (response[0] != 0xff && response[0] == 0xb5) {
  144. //FURI_LOG_I(TAG, "mem free before final read: %u", memmgr_get_free_heap());
  145. //FURI_LOG_I(TAG, "got data that isn't 0xff");
  146. if (!furi_hal_i2c_trx(&furi_hal_i2c_handle_external,
  147. UBLOX_I2C_ADDRESS << 1,
  148. tx, 1,
  149. &(response[1]), read_length - 1, // first byte already read
  150. furi_ms_to_ticks(I2C_TIMEOUT_MS))) {
  151. FURI_LOG_E(TAG, "error reading rest of response");
  152. free(response);
  153. return NULL;
  154. }
  155. //FURI_LOG_I(TAG, "mem free after final read: %u", memmgr_get_free_heap());
  156. break;
  157. }
  158. }
  159. //FURI_LOG_I(TAG, "i2c_transfer: byte 0 = %d", response[0]);
  160. UbloxMessage* message_rx = malloc(sizeof(UbloxMessage));
  161. message_rx->message = response;
  162. message_rx->length = read_length;
  163. return message_rx; // message_rx->message needs to be freed later
  164. }
  165. void ublox_worker_read_pvt(UbloxWorker* ublox_worker) {
  166. //FURI_LOG_I(TAG, "mem free before PVT read: %u", memmgr_get_free_heap());
  167. Ublox* ublox = ublox_worker->context;
  168. // Read NAV-PVT by sending NAV-PVT with no payload
  169. UbloxFrame* frame_tx = malloc(sizeof(UbloxFrame));
  170. frame_tx->class = UBX_NAV_CLASS;
  171. frame_tx->id = UBX_NAV_PVT_MESSAGE;
  172. frame_tx->len = 0;
  173. frame_tx->payload = NULL;
  174. UbloxMessage* message_tx = ublox_frame_to_bytes(frame_tx);
  175. ublox_frame_free(frame_tx);
  176. UbloxMessage* message_rx = ublox_worker_i2c_transfer(message_tx, UBX_NAV_PVT_MESSAGE_LENGTH);
  177. ublox_message_free(message_tx);
  178. if (message_rx == NULL) {
  179. FURI_LOG_E(TAG, "read_pvt transfer failed");
  180. ublox_worker_change_state(ublox_worker, UbloxWorkerStateStop);
  181. ublox_worker->callback(UbloxWorkerEventFailed, ublox_worker->context);
  182. return;
  183. }
  184. UbloxFrame* frame_rx = ublox_bytes_to_frame(message_rx);
  185. ublox_message_free(message_rx);
  186. if (frame_rx == NULL) {
  187. FURI_LOG_E(TAG, "NULL pointer, something wrong with NAV-PVT message!");
  188. ublox_worker_change_state(ublox_worker, UbloxWorkerStateStop);
  189. ublox_worker->callback(UbloxWorkerEventFailed, ublox_worker->context);
  190. } else {
  191. // build nav-pvt struct. yes this is very ugly.
  192. Ublox_NAV_PVT_Message nav_pvt = {
  193. .iTOW = (frame_rx->payload[0]) | (frame_rx->payload[1] << 8) | (frame_rx->payload[2] << 16) | (frame_rx->payload[3] << 24),
  194. .year = (frame_rx->payload[4]) | (frame_rx->payload[5] << 8),
  195. .month = frame_rx->payload[6],
  196. .day = frame_rx->payload[7],
  197. .hour = frame_rx->payload[8],
  198. .min = frame_rx->payload[9],
  199. .sec = frame_rx->payload[10],
  200. .valid = frame_rx->payload[11],
  201. .tAcc = (frame_rx->payload[12]) | (frame_rx->payload[13] << 8) | (frame_rx->payload[14] << 16) | (frame_rx->payload[15] << 24),
  202. .nano = (frame_rx->payload[16]) | (frame_rx->payload[17] << 8) | (frame_rx->payload[18] << 16) | (frame_rx->payload[19] << 24),
  203. .fixType = frame_rx->payload[20],
  204. .flags = frame_rx->payload[21],
  205. .flags2 = frame_rx->payload[22],
  206. .numSV = frame_rx->payload[23],
  207. .lon = (frame_rx->payload[24]) | (frame_rx->payload[25] << 8) | (frame_rx->payload[26] << 16) | (frame_rx->payload[27] << 24),
  208. .lat = (frame_rx->payload[28]) | (frame_rx->payload[29] << 8) | (frame_rx->payload[30] << 16) | (frame_rx->payload[31] << 24),
  209. .height = (frame_rx->payload[32]) | (frame_rx->payload[33] << 8) | (frame_rx->payload[34] << 16) | (frame_rx->payload[35] << 24),
  210. .hMSL = (frame_rx->payload[36]) | (frame_rx->payload[37] << 8) | (frame_rx->payload[38] << 16) | (frame_rx->payload[39] << 24),
  211. .hAcc = (frame_rx->payload[40]) | (frame_rx->payload[41] << 8) | (frame_rx->payload[42] << 16) | (frame_rx->payload[43] << 24),
  212. .vAcc = (frame_rx->payload[44]) | (frame_rx->payload[45] << 8) | (frame_rx->payload[46] << 16) | (frame_rx->payload[47] << 24),
  213. .velN = (frame_rx->payload[48]) | (frame_rx->payload[49] << 8) | (frame_rx->payload[50] << 16) | (frame_rx->payload[51] << 24),
  214. .velE = (frame_rx->payload[52]) | (frame_rx->payload[53] << 8) | (frame_rx->payload[54] << 16) | (frame_rx->payload[55] << 24),
  215. .velD = (frame_rx->payload[56]) | (frame_rx->payload[57] << 8) | (frame_rx->payload[58] << 16) | (frame_rx->payload[59] << 24),
  216. .gSpeed = (frame_rx->payload[60]) | (frame_rx->payload[61] << 8) | (frame_rx->payload[62] << 16) | (frame_rx->payload[63] << 24),
  217. .headMot = (frame_rx->payload[64]) | (frame_rx->payload[65] << 8) | (frame_rx->payload[66] << 16) | (frame_rx->payload[67] << 24),
  218. .sAcc = (frame_rx->payload[68]) | (frame_rx->payload[69] << 8) | (frame_rx->payload[70] << 16) | (frame_rx->payload[71] << 24),
  219. .headAcc = (frame_rx->payload[72]) | (frame_rx->payload[73] << 8) | (frame_rx->payload[74] << 16) | (frame_rx->payload[75] << 24),
  220. .pDOP = (frame_rx->payload[76]) | (frame_rx->payload[77] << 8),
  221. .flags3 = (frame_rx->payload[78]) | (frame_rx->payload[79] << 8),
  222. .reserved1 = frame_rx->payload[80],
  223. .reserved2 = frame_rx->payload[81],
  224. .reserved3 = frame_rx->payload[82],
  225. .reserved4 = frame_rx->payload[83],
  226. .headVeh = (frame_rx->payload[84]) | (frame_rx->payload[85] << 8) | (frame_rx->payload[86] << 16) | (frame_rx->payload[87] << 24),
  227. .magDec = (frame_rx->payload[88]) | (frame_rx->payload[89] << 8),
  228. .magAcc = (frame_rx->payload[90]) | (frame_rx->payload[91] << 8),
  229. };
  230. // Using a local variable for nav_pvt is fine, because nav_pvt in
  231. // the Ublox struct is also not a pointer, so this assignment
  232. // effectively compiles to a memcpy.
  233. ublox->nav_pvt = nav_pvt;
  234. ublox_frame_free(frame_rx);
  235. //ublox_worker->callback(UbloxWorkerEventDataReady, ublox_worker->context);
  236. }
  237. //FURI_LOG_I(TAG, "mem free after PVT read: %u", memmgr_get_free_heap());
  238. }
  239. bool ublox_worker_read_odo(UbloxWorker* ublox_worker) {
  240. //FURI_LOG_I(TAG, "mem free before odo read: %u", memmgr_get_free_heap());
  241. Ublox* ublox = ublox_worker->context;
  242. UbloxFrame* frame_tx = malloc(sizeof(UbloxFrame));
  243. frame_tx->class = UBX_NAV_CLASS;
  244. frame_tx->id = UBX_NAV_ODO_MESSAGE;
  245. frame_tx->len = 0;
  246. frame_tx->payload = NULL;
  247. UbloxMessage* message_tx = ublox_frame_to_bytes(frame_tx);
  248. ublox_frame_free(frame_tx);
  249. UbloxMessage* message_rx = ublox_worker_i2c_transfer(message_tx, UBX_NAV_ODO_MESSAGE_LENGTH);
  250. ublox_message_free(message_tx);
  251. if (message_rx == NULL) {
  252. FURI_LOG_E(TAG, "read_odo transfer failed");
  253. return false;
  254. }
  255. UbloxFrame* frame_rx = ublox_bytes_to_frame(message_rx);
  256. ublox_message_free(message_rx);
  257. if (frame_rx == NULL) {
  258. FURI_LOG_E(TAG, "NULL pointer, something wrong with NAV-ODO message!");
  259. return false;
  260. } else {
  261. Ublox_NAV_ODO_Message nav_odo = {
  262. .version = frame_rx->payload[0],
  263. .reserved1 = frame_rx->payload[1],
  264. .reserved2 = frame_rx->payload[2],
  265. .reserved3 = frame_rx->payload[3],
  266. .iTOW = (frame_rx->payload[4]) | (frame_rx->payload[5] << 8) | (frame_rx->payload[6] << 16) | (frame_rx->payload[7] << 24),
  267. .distance = (frame_rx->payload[8]) | (frame_rx->payload[9] << 8) | (frame_rx->payload[10] << 16) | (frame_rx->payload[11] << 24),
  268. .totalDistance = (frame_rx->payload[12]) | (frame_rx->payload[13] << 8) | (frame_rx->payload[14] << 16) | (frame_rx->payload[15] << 24),
  269. .distanceStd = (frame_rx->payload[16]) | (frame_rx->payload[17] << 8) | (frame_rx->payload[18] << 16) | (frame_rx->payload[19] << 24),
  270. };
  271. ublox->nav_odo = nav_odo;
  272. ublox_frame_free(frame_rx);
  273. //FURI_LOG_I(TAG, "mem free after odo read: %u", memmgr_get_free_heap());
  274. return true;
  275. }
  276. }
  277. /** Set the power mode to "Aggressive with 1Hz", enable the odometer,
  278. and configure odometer and dynamic platform model. */
  279. bool ublox_worker_init_gps(UbloxWorker* ublox_worker) {
  280. Ublox* ublox = ublox_worker->context;
  281. // Set power mode
  282. /*** read initial cfg-pms configuration first ***/
  283. UbloxFrame* pms_frame_tx = malloc(sizeof(UbloxFrame));
  284. pms_frame_tx->class = UBX_CFG_CLASS;
  285. pms_frame_tx->id = UBX_CFG_PMS_MESSAGE;
  286. pms_frame_tx->len = 0;
  287. pms_frame_tx->payload = NULL;
  288. UbloxMessage* pms_message_tx = ublox_frame_to_bytes(pms_frame_tx);
  289. ublox_frame_free(pms_frame_tx);
  290. UbloxMessage* pms_message_rx = ublox_worker_i2c_transfer(pms_message_tx, UBX_CFG_PMS_MESSAGE_LENGTH);
  291. ublox_message_free(pms_message_tx);
  292. if (pms_message_rx == NULL) {
  293. FURI_LOG_E(TAG, "CFG-PMS read transfer failed");
  294. return false;
  295. }
  296. // set power setup value to "aggressive with 1Hz"
  297. pms_message_rx->message[6+1] = 0x03;
  298. pms_frame_tx = malloc(sizeof(UbloxFrame));
  299. pms_frame_tx->class = UBX_CFG_CLASS;
  300. pms_frame_tx->id = UBX_CFG_PMS_MESSAGE;
  301. pms_frame_tx->len = 8;
  302. pms_frame_tx->payload = pms_message_rx->message;
  303. pms_message_tx = ublox_frame_to_bytes(pms_frame_tx);
  304. ublox_frame_free(pms_frame_tx);
  305. UbloxMessage* ack = ublox_worker_i2c_transfer(pms_message_tx, UBX_ACK_ACK_MESSAGE_LENGTH);
  306. if (ack == NULL) {
  307. FURI_LOG_E(TAG, "ACK after CFG-PMS set transfer failed");
  308. return false;
  309. }
  310. FURI_LOG_I(TAG, "CFG-PMS ack: id = %u, type = %s", ack->message[3], ack->message[3] ? "ACK" : "NAK");
  311. ublox_message_free(pms_message_tx);
  312. ublox_message_free(pms_message_rx);
  313. ublox_message_free(ack);
  314. /***** Odometer *****/
  315. // Enable odometer by changing CFG-ODO.
  316. UbloxFrame* odo_frame_tx = malloc(sizeof(UbloxFrame));
  317. odo_frame_tx->class = UBX_CFG_CLASS;
  318. odo_frame_tx->id = UBX_CFG_ODO_MESSAGE;
  319. odo_frame_tx->len = 0;
  320. odo_frame_tx->payload = NULL;
  321. UbloxMessage* odo_message_tx = ublox_frame_to_bytes(odo_frame_tx);
  322. ublox_frame_free(odo_frame_tx);
  323. UbloxMessage* odo_message_rx = ublox_worker_i2c_transfer(odo_message_tx, UBX_CFG_ODO_MESSAGE_LENGTH);
  324. ublox_message_free(odo_message_tx);
  325. if (odo_message_rx == NULL) {
  326. FURI_LOG_E(TAG, "CFG-ODO transfer failed");
  327. return false;
  328. }
  329. odo_frame_tx = malloc(sizeof(UbloxFrame));
  330. odo_frame_tx->class = UBX_CFG_CLASS;
  331. odo_frame_tx->id = UBX_CFG_ODO_MESSAGE;
  332. odo_frame_tx->len = 20;
  333. odo_frame_tx->payload = odo_message_rx->message;
  334. // TODO: low-pass filters in settings?
  335. // enable useODO bit in flags
  336. odo_frame_tx->payload[4] |= 1;
  337. odo_frame_tx->payload[5] = (ublox->device_state).odometer_mode;
  338. odo_message_tx = ublox_frame_to_bytes(odo_frame_tx);
  339. ublox_frame_free(odo_frame_tx);
  340. ack = ublox_worker_i2c_transfer(odo_message_tx, UBX_ACK_ACK_MESSAGE_LENGTH);
  341. if (ack == NULL) {
  342. FURI_LOG_E(TAG, "ACK after CFG-ODO set transfer failed");
  343. return false;
  344. }
  345. FURI_LOG_I(TAG, "CFG-ODO ack: id = %u, type = %s", ack->message[3], ack->message[3] ? "ACK" : "NAK");
  346. ublox_message_free(odo_message_tx);
  347. ublox_message_free(odo_message_rx);
  348. ublox_message_free(ack);
  349. // finally configure the navigation engine
  350. UbloxFrame* nav5_frame_tx = malloc(sizeof(UbloxFrame));
  351. nav5_frame_tx->class = UBX_CFG_CLASS;
  352. nav5_frame_tx->id = UBX_CFG_NAV5_MESSAGE;
  353. nav5_frame_tx->len = 0;
  354. nav5_frame_tx->payload = NULL;
  355. UbloxMessage* nav5_message_tx = ublox_frame_to_bytes(nav5_frame_tx);
  356. ublox_frame_free(nav5_frame_tx);
  357. UbloxMessage* nav5_message_rx = ublox_worker_i2c_transfer(nav5_message_tx, UBX_CFG_NAV5_MESSAGE_LENGTH);
  358. if (nav5_message_rx == NULL) {
  359. FURI_LOG_E(TAG, "CFG-NAV5 transfer failed");
  360. return false;
  361. }
  362. // first two bytes tell the GPS what changes to apply, setting this
  363. // bit tells it to apply the dynamic platfrom model settings.
  364. nav5_frame_tx = malloc(sizeof(UbloxFrame));
  365. nav5_frame_tx->class = UBX_CFG_CLASS;
  366. nav5_frame_tx->id = UBX_CFG_NAV5_MESSAGE;
  367. nav5_frame_tx->len = 36;
  368. nav5_frame_tx->payload = nav5_message_rx->message;
  369. nav5_frame_tx->payload[0] |= 1;
  370. nav5_frame_tx->payload[2] = (ublox->device_state).platform_model;
  371. nav5_message_tx = ublox_frame_to_bytes(nav5_frame_tx);
  372. ublox_frame_free(nav5_frame_tx);
  373. ack = ublox_worker_i2c_transfer(nav5_message_tx, UBX_ACK_ACK_MESSAGE_LENGTH);
  374. if (ack == NULL) {
  375. FURI_LOG_E(TAG, "ACK after CFG-NAV5 set transfer failed");
  376. return false;
  377. }
  378. FURI_LOG_I(TAG, "CFG-NAV5 ack: id = %u, type = %s", ack->message[3], ack->message[3] ? "ACK" : "NAK");
  379. ublox_message_free(nav5_message_tx);
  380. ublox_message_free(nav5_message_rx);
  381. ublox_message_free(ack);
  382. return true;
  383. }
  384. void ublox_worker_reset_odo(UbloxWorker* ublox_worker) {
  385. FURI_LOG_I(TAG, "ublox_worker_reset_odo");
  386. UbloxFrame* odo_frame_tx = malloc(sizeof(UbloxFrame));
  387. odo_frame_tx->class = UBX_NAV_CLASS;
  388. odo_frame_tx->id = UBX_NAV_RESETODO_MESSAGE;
  389. odo_frame_tx->len = 0;
  390. odo_frame_tx->payload = NULL;
  391. UbloxMessage* odo_message_tx = ublox_frame_to_bytes(odo_frame_tx);
  392. ublox_frame_free(odo_frame_tx);
  393. UbloxMessage* ack = ublox_worker_i2c_transfer(odo_message_tx, UBX_ACK_ACK_MESSAGE_LENGTH);
  394. if (ack == NULL) {
  395. FURI_LOG_E(TAG, "ACK after NAV-RESETODO set transfer failed");
  396. ublox_worker->callback(UbloxWorkerEventFailed, ublox_worker->context);
  397. return;
  398. }
  399. FURI_LOG_I(TAG, "NAV-RESETODO ack: id = %u, type = %s", ack->message[3], ack->message[3] ? "ACK" : "NAK");
  400. ublox_message_free(odo_message_tx);
  401. ublox_message_free(ack);
  402. // no reason to trigger an event on success, the user will see that
  403. // the odometer has been reset on the next update.
  404. }
  405. /*FuriString* s = furi_string_alloc();
  406. for (int i = 0; i < 92+8; i++) {
  407. furi_string_cat_printf(s, "0x%x, ", message_rx->message[i]);
  408. }
  409. FURI_LOG_I(TAG, "array: %s", furi_string_get_cstr(s));
  410. furi_string_free(s);*/