PageRenderTime 51ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/core/uemis.c

https://github.com/dirkhh/subsurface
C | 391 lines | 283 code | 28 blank | 80 comment | 71 complexity | eea95d51742f7fde5047706f4c0a8006 MD5 | raw file
  1. // SPDX-License-Identifier: MIT
  2. /*
  3. * uemis.c
  4. *
  5. * UEMIS SDA file importer
  6. * AUTHOR: Dirk Hohndel - Copyright 2011
  7. *
  8. * Licensed under the MIT license.
  9. */
  10. #include <stdio.h>
  11. #include <string.h>
  12. #include "gettext.h"
  13. #include "uemis.h"
  14. #include "divesite.h"
  15. #include "sample.h"
  16. #include <libdivecomputer/parser.h>
  17. #include <libdivecomputer/version.h>
  18. /*
  19. * following code is based on code found in at base64.sourceforge.net/b64.c
  20. * AUTHOR: Bob Trower 08/04/01
  21. * COPYRIGHT: Copyright (c) Trantor Standard Systems Inc., 2001
  22. * NOTE: This source code may be used as you wish, subject to
  23. * the MIT license.
  24. */
  25. /*
  26. * Translation Table to decode (created by Bob Trower)
  27. */
  28. static const char cd64[] = "|$$$}rstuvwxyz{$$$$$$$>?@ABCDEFGHIJKLMNOPQRSTUVW$$$$$$XYZ[\\]^_`abcdefghijklmnopq";
  29. /*
  30. * decodeblock -- decode 4 '6-bit' characters into 3 8-bit binary bytes
  31. */
  32. static void decodeblock(unsigned char in[4], unsigned char out[3])
  33. {
  34. out[0] = (unsigned char)(in[0] << 2 | in[1] >> 4);
  35. out[1] = (unsigned char)(in[1] << 4 | in[2] >> 2);
  36. out[2] = (unsigned char)(((in[2] << 6) & 0xc0) | in[3]);
  37. }
  38. /*
  39. * decode a base64 encoded stream discarding padding, line breaks and noise
  40. */
  41. static void decode(uint8_t *inbuf, uint8_t *outbuf, int inbuf_len)
  42. {
  43. uint8_t in[4], out[3], v;
  44. int i, len, indx_in = 0, indx_out = 0;
  45. while (indx_in < inbuf_len) {
  46. for (len = 0, i = 0; i < 4 && (indx_in < inbuf_len); i++) {
  47. v = 0;
  48. while ((indx_in < inbuf_len) && v == 0) {
  49. v = inbuf[indx_in++];
  50. v = ((v < 43 || v > 122) ? 0 : cd64[v - 43]);
  51. if (v)
  52. v = ((v == '$') ? 0 : v - 61);
  53. }
  54. if (indx_in < inbuf_len) {
  55. len++;
  56. if (v)
  57. in[i] = (v - 1);
  58. } else
  59. in[i] = 0;
  60. }
  61. if (len) {
  62. decodeblock(in, out);
  63. for (i = 0; i < len - 1; i++)
  64. outbuf[indx_out++] = out[i];
  65. }
  66. }
  67. }
  68. /* end code from Bob Trower */
  69. /*
  70. * convert the base64 data blog
  71. */
  72. static int uemis_convert_base64(char *base64, uint8_t **data)
  73. {
  74. int len, datalen;
  75. len = strlen(base64);
  76. datalen = (len / 4 + 1) * 3;
  77. if (datalen < 0x123 + 0x25)
  78. /* less than header + 1 sample??? */
  79. fprintf(stderr, "suspiciously short data block %d\n", datalen);
  80. *data = malloc(datalen);
  81. if (!*data) {
  82. fprintf(stderr, "Out of memory\n");
  83. return 0;
  84. }
  85. decode((unsigned char *)base64, *data, len);
  86. if (memcmp(*data, "Dive\01\00\00", 7))
  87. fprintf(stderr, "Missing Dive100 header\n");
  88. return datalen;
  89. }
  90. struct uemis_helper {
  91. uint32_t diveid;
  92. int lbs;
  93. int divespot;
  94. struct dive_site *dive_site;
  95. struct uemis_helper *next;
  96. };
  97. static struct uemis_helper *uemis_helper = NULL;
  98. static struct uemis_helper *uemis_get_helper(uint32_t diveid)
  99. {
  100. struct uemis_helper **php = &uemis_helper;
  101. struct uemis_helper *hp = *php;
  102. while (hp) {
  103. if (hp->diveid == diveid)
  104. return hp;
  105. if (hp->next) {
  106. hp = hp->next;
  107. continue;
  108. }
  109. php = &hp->next;
  110. break;
  111. }
  112. hp = *php = calloc(1, sizeof(struct uemis_helper));
  113. hp->diveid = diveid;
  114. hp->next = NULL;
  115. return hp;
  116. }
  117. static void uemis_weight_unit(int diveid, int lbs)
  118. {
  119. struct uemis_helper *hp = uemis_get_helper(diveid);
  120. if (hp)
  121. hp->lbs = lbs;
  122. }
  123. int uemis_get_weight_unit(uint32_t diveid)
  124. {
  125. struct uemis_helper *hp = uemis_helper;
  126. while (hp) {
  127. if (hp->diveid == diveid)
  128. return hp->lbs;
  129. hp = hp->next;
  130. }
  131. /* odd - we should have found this; default to kg */
  132. return 0;
  133. }
  134. void uemis_mark_divelocation(int diveid, int divespot, struct dive_site *ds)
  135. {
  136. struct uemis_helper *hp = uemis_get_helper(diveid);
  137. hp->divespot = divespot;
  138. hp->dive_site = ds;
  139. }
  140. /* support finding a dive spot based on the diveid */
  141. int uemis_get_divespot_id_by_diveid(uint32_t diveid)
  142. {
  143. struct uemis_helper *hp = uemis_helper;
  144. while (hp) {
  145. if (hp->diveid == diveid)
  146. return hp->divespot;
  147. hp = hp->next;
  148. }
  149. return -1;
  150. }
  151. void uemis_set_divelocation(int divespot, char *text, double longitude, double latitude)
  152. {
  153. struct uemis_helper *hp = uemis_helper;
  154. while (hp) {
  155. if (hp->divespot == divespot) {
  156. struct dive_site *ds = hp->dive_site;
  157. if (ds) {
  158. ds->name = strdup(text);
  159. ds->location = create_location(latitude, longitude);
  160. }
  161. }
  162. hp = hp->next;
  163. }
  164. }
  165. /* Create events from the flag bits and other data in the sample;
  166. * These bits basically represent what is displayed on screen at sample time.
  167. * Many of these 'warnings' are way hyper-active and seriously clutter the
  168. * profile plot - so these are disabled by default
  169. *
  170. * we mark all the strings for translation, but we store the untranslated
  171. * strings and only convert them when displaying them on screen - this way
  172. * when we write them to the XML file we'll always have the English strings,
  173. * regardless of locale
  174. */
  175. static void uemis_event(struct dive *dive, struct divecomputer *dc, struct sample *sample, uemis_sample_t *u_sample)
  176. {
  177. uint8_t *flags = u_sample->flags;
  178. int stopdepth;
  179. static int lastndl;
  180. if (flags[1] & 0x01)
  181. add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Safety stop violation"));
  182. if (flags[1] & 0x08)
  183. add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Speed alarm"));
  184. #if WANT_CRAZY_WARNINGS
  185. if (flags[1] & 0x06) /* both bits 1 and 2 are a warning */
  186. add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Speed warning"));
  187. if (flags[1] & 0x10)
  188. add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "pO₂ green warning"));
  189. #endif
  190. if (flags[1] & 0x20)
  191. add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "pO₂ ascend warning"));
  192. if (flags[1] & 0x40)
  193. add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "pO₂ ascend alarm"));
  194. /* flags[2] reflects the deco / time bar
  195. * flags[3] reflects more display details on deco and pO2 */
  196. if (flags[4] & 0x01)
  197. add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Tank pressure info"));
  198. if (flags[4] & 0x04)
  199. add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "RGT warning"));
  200. if (flags[4] & 0x08)
  201. add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "RGT alert"));
  202. if (flags[4] & 0x40)
  203. add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Tank change suggested"));
  204. if (flags[4] & 0x80)
  205. add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Depth limit exceeded"));
  206. if (flags[5] & 0x01)
  207. add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Max deco time warning"));
  208. if (flags[5] & 0x04)
  209. add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Dive time info"));
  210. if (flags[5] & 0x08)
  211. add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Dive time alert"));
  212. if (flags[5] & 0x10)
  213. add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Marker"));
  214. if (flags[6] & 0x02)
  215. add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "No tank data"));
  216. if (flags[6] & 0x04)
  217. add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Low battery warning"));
  218. if (flags[6] & 0x08)
  219. add_event(dc, sample->time.seconds, 0, 0, 0, QT_TRANSLATE_NOOP("gettextFromC", "Low battery alert"));
  220. /* flags[7] reflects the little on screen icons that remind of previous
  221. * warnings / alerts - not useful for events */
  222. #if UEMIS_DEBUG & 32
  223. int i, j;
  224. for (i = 0; i < 8; i++) {
  225. printf(" %d: ", 29 + i);
  226. for (j = 7; j >= 0; j--)
  227. printf("%c", flags[i] & 1 << j ? '1' : '0');
  228. }
  229. printf("\n");
  230. #endif
  231. /* now add deco / NDL
  232. * we don't use events but store this in the sample - that makes much more sense
  233. * for the way we display this information
  234. * What we know about the encoding so far:
  235. * flags[3].bit0 | flags[5].bit1 != 0 ==> in deco
  236. * flags[0].bit7 == 1 ==> Safety Stop
  237. * otherwise NDL */
  238. stopdepth = rel_mbar_to_depth(u_sample->hold_depth, dive);
  239. if ((flags[3] & 1) | (flags[5] & 2)) {
  240. /* deco */
  241. sample->in_deco = true;
  242. sample->stopdepth.mm = stopdepth;
  243. sample->stoptime.seconds = u_sample->hold_time * 60;
  244. sample->ndl.seconds = 0;
  245. } else if (flags[0] & 128) {
  246. /* safety stop - distinguished from deco stop by having
  247. * both ndl and stop information */
  248. sample->in_deco = false;
  249. sample->stopdepth.mm = stopdepth;
  250. sample->stoptime.seconds = u_sample->hold_time * 60;
  251. sample->ndl.seconds = lastndl;
  252. } else {
  253. /* NDL */
  254. sample->in_deco = false;
  255. lastndl = sample->ndl.seconds = u_sample->hold_time * 60;
  256. sample->stopdepth.mm = 0;
  257. sample->stoptime.seconds = 0;
  258. }
  259. #if UEMIS_DEBUG & 32
  260. printf("%dm:%ds: p_amb_tol:%d surface:%d holdtime:%d holddepth:%d/%d ---> stopdepth:%d stoptime:%d ndl:%d\n",
  261. sample->time.seconds / 60, sample->time.seconds % 60, u_sample->p_amb_tol, dive->dc.surface_pressure.mbar,
  262. u_sample->hold_time, u_sample->hold_depth, stopdepth, sample->stopdepth.mm, sample->stoptime.seconds, sample->ndl.seconds);
  263. #endif
  264. }
  265. /*
  266. * parse uemis base64 data blob into struct dive
  267. */
  268. void uemis_parse_divelog_binary(char *base64, void *datap)
  269. {
  270. int datalen;
  271. int i;
  272. uint8_t *data;
  273. struct sample *sample = NULL;
  274. uemis_sample_t *u_sample;
  275. struct dive *dive = datap;
  276. struct divecomputer *dc = &dive->dc;
  277. int template, gasoffset;
  278. uint8_t active = 0;
  279. datalen = uemis_convert_base64(base64, &data);
  280. dive->dc.airtemp.mkelvin = C_to_mkelvin((*(uint16_t *)(data + 45)) / 10.0);
  281. dive->dc.surface_pressure.mbar = *(uint16_t *)(data + 43);
  282. if (*(uint8_t *)(data + 19))
  283. dive->dc.salinity = SEAWATER_SALINITY; /* avg grams per 10l sea water */
  284. else
  285. dive->dc.salinity = FRESHWATER_SALINITY; /* grams per 10l fresh water */
  286. /* this will allow us to find the last dive read so far from this computer */
  287. dc->model = strdup("Uemis Zurich");
  288. dc->deviceid = *(uint32_t *)(data + 9);
  289. dc->diveid = *(uint16_t *)(data + 7);
  290. /* remember the weight units used in this dive - we may need this later when
  291. * parsing the weight */
  292. uemis_weight_unit(dc->diveid, *(uint8_t *)(data + 24));
  293. /* dive template in use:
  294. 0 = air
  295. 1 = nitrox (B)
  296. 2 = nitrox (B+D)
  297. 3 = nitrox (B+T+D)
  298. uemis cylinder data is insane - it stores seven tank settings in a block
  299. and the template tells us which of the four groups of tanks we need to look at
  300. */
  301. gasoffset = template = *(uint8_t *)(data + 115);
  302. if (template == 3)
  303. gasoffset = 4;
  304. if (template == 0)
  305. template = 1;
  306. for (i = 0; i < template; i++) {
  307. float volume = *(float *)(data + 116 + 25 * (gasoffset + i)) * 1000.0f;
  308. /* uemis always assumes a working pressure of 202.6bar (!?!?) - I first thought
  309. * it was 3000psi, but testing against all my dives gets me that strange number.
  310. * Still, that's of course completely bogus and shows they don't get how
  311. * cylinders are named in non-metric parts of the world...
  312. * we store the incorrect working pressure to get the SAC calculations "close"
  313. * but the user will have to correct this manually
  314. */
  315. cylinder_t *cyl = get_or_create_cylinder(dive, i);
  316. cyl->type.size.mliter = lrintf(volume);
  317. cyl->type.workingpressure.mbar = 202600;
  318. cyl->gasmix.o2.permille = *(uint8_t *)(data + 120 + 25 * (gasoffset + i)) * 10;
  319. cyl->gasmix.he.permille = 0;
  320. }
  321. /* first byte of divelog data is at offset 0x123 */
  322. i = 0x123;
  323. u_sample = (uemis_sample_t *)(data + i);
  324. while ((i <= datalen) && (data[i] != 0 || data[i + 1] != 0)) {
  325. if (u_sample->active_tank != active) {
  326. if (u_sample->active_tank >= dive->cylinders.nr) {
  327. fprintf(stderr, "got invalid sensor #%d was #%d\n", u_sample->active_tank, active);
  328. } else {
  329. active = u_sample->active_tank;
  330. add_gas_switch_event(dive, dc, u_sample->dive_time, active);
  331. }
  332. }
  333. sample = prepare_sample(dc);
  334. sample->time.seconds = u_sample->dive_time;
  335. sample->depth.mm = rel_mbar_to_depth(u_sample->water_pressure, dive);
  336. sample->temperature.mkelvin = C_to_mkelvin(u_sample->dive_temperature / 10.0);
  337. add_sample_pressure(sample, active, (u_sample->tank_pressure_high * 256 + u_sample->tank_pressure_low) * 10);
  338. sample->cns = u_sample->cns;
  339. uemis_event(dive, dc, sample, u_sample);
  340. finish_sample(dc);
  341. i += 0x25;
  342. u_sample++;
  343. }
  344. if (sample)
  345. dive->dc.duration.seconds = sample->time.seconds - 1;
  346. /* get data from the footer */
  347. char buffer[24];
  348. snprintf(buffer, sizeof(buffer), "%1u.%02u", data[18], data[17]);
  349. add_extra_data(dc, "FW Version", buffer);
  350. snprintf(buffer, sizeof(buffer), "%08x", *(uint32_t *)(data + 9));
  351. add_extra_data(dc, "Serial", buffer);
  352. snprintf(buffer, sizeof(buffer), "%d", *(uint16_t *)(data + i + 35));
  353. add_extra_data(dc, "main battery after dive", buffer);
  354. snprintf(buffer, sizeof(buffer), "%0u:%02u", FRACTION(*(uint16_t *)(data + i + 24), 60));
  355. add_extra_data(dc, "no fly time", buffer);
  356. snprintf(buffer, sizeof(buffer), "%0u:%02u", FRACTION(*(uint16_t *)(data + i + 26), 60));
  357. add_extra_data(dc, "no dive time", buffer);
  358. snprintf(buffer, sizeof(buffer), "%0u:%02u", FRACTION(*(uint16_t *)(data + i + 28), 60));
  359. add_extra_data(dc, "desat time", buffer);
  360. snprintf(buffer, sizeof(buffer), "%u", *(uint16_t *)(data + i + 30));
  361. add_extra_data(dc, "allowed altitude", buffer);
  362. return;
  363. }