X-Git-Url: http://git.tdb.fi/?a=blobdiff_plain;f=profile.c;h=ad158fe8a1f9aaafb40b792f31dea13667594f5f;hb=edbba678b3cdc8f06a84a965b04f13ef1f4ecaab;hp=eef68f20bbd9a12b9424e20590c548cc5a8a578f;hpb=bf1dc48dfe6af65de5cd3f7b474130fcbc26bcc9;p=ext%2Fsubsurface.git diff --git a/profile.c b/profile.c index eef68f2..ad158fe 100644 --- a/profile.c +++ b/profile.c @@ -26,8 +26,12 @@ struct plot_info { int mintemp, maxtemp; struct plot_data { unsigned int same_cylinder:1; + unsigned int cylinderindex; int sec; - int pressure, temperature; + /* pressure[0] is sensor pressure + * pressure[1] is interpolated pressure */ + int pressure[2]; + int temperature; /* Depth info */ int depth; int smoothed; @@ -37,6 +41,10 @@ struct plot_info { int avg[3]; } entry[]; }; +#define SENSOR_PR 0 +#define INTERPOLATED_PR 1 +#define SENSOR_PRESSURE(_entry) (_entry)->pressure[SENSOR_PR] +#define INTERPOLATED_PRESSURE(_entry) (_entry)->pressure[INTERPOLATED_PR] /* convert velocity to colors */ typedef struct { double r, g, b; } rgb_t; @@ -158,11 +166,62 @@ static void plot_text(struct graphics_context *gc, const text_render_options_t * cairo_show_text(cr, buffer); } +struct ev_select { + char *ev_name; + gboolean plot_ev; +}; +static struct ev_select *ev_namelist; +static int evn_allocated; +static int evn_used; + +void evn_foreach(void (*callback)(const char *, int *, void *), void *data) +{ + int i; + + for (i = 0; i < evn_used; i++) { + callback(ev_namelist[i].ev_name, &ev_namelist[i].plot_ev, data); + } +} + +void remember_event(const char *eventname) +{ + int i=0, len; + + if (!eventname || (len = strlen(eventname)) == 0) + return; + while (i < evn_used) { + if (!strncmp(eventname,ev_namelist[i].ev_name,len)) + return; + i++; + } + if (evn_used == evn_allocated) { + evn_allocated += 10; + ev_namelist = realloc(ev_namelist, evn_allocated * sizeof(struct ev_select)); + if (! ev_namelist) + /* we are screwed, but let's just bail out */ + return; + } + ev_namelist[evn_used].ev_name = strdup(eventname); + ev_namelist[evn_used].plot_ev = TRUE; + evn_used++; +} + static void plot_one_event(struct graphics_context *gc, struct plot_info *pi, struct event *event, const text_render_options_t *tro) { int i, depth = 0; int x,y; + /* is plotting this event disabled? */ + if (event->name) { + for (i = 0; i < evn_used; i++) { + if (! strcmp(event->name, ev_namelist[i].ev_name)) { + if (ev_namelist[i].plot_ev) + break; + else + return; + } + } + } for (i = 0; i < pi->nr; i++) { struct plot_data *data = pi->entry + i; if (event->time.seconds < data->sec) @@ -217,6 +276,7 @@ static void plot_text_samples(struct graphics_context *gc, struct plot_info *pi) static const text_render_options_t deep = {14, 1.0, 0.2, 0.2, CENTER, TOP}; static const text_render_options_t shallow = {14, 1.0, 0.2, 0.2, CENTER, BOTTOM}; int i; + int last = -1; for (i = 0; i < pi->nr; i++) { struct plot_data *entry = pi->entry + i; @@ -224,11 +284,18 @@ static void plot_text_samples(struct graphics_context *gc, struct plot_info *pi) if (entry->depth < 2000) continue; - if (entry == entry->max[2]) + if ((entry == entry->max[2]) && entry->depth != last) { render_depth_sample(gc, entry, &deep); + last = entry->depth; + } - if (entry == entry->min[2]) + if ((entry == entry->min[2]) && entry->depth != last) { render_depth_sample(gc, entry, &shallow); + last = entry->depth; + } + + if (entry->depth != last) + last = -1; } } @@ -291,23 +358,43 @@ static void plot_minmax_profile(struct graphics_context *gc, struct plot_info *p static void plot_depth_profile(struct graphics_context *gc, struct plot_info *pi) { - int i; + int i, incr; cairo_t *cr = gc->cr; int sec, depth; struct plot_data *entry; int maxtime, maxdepth, marker; + int increments[4] = { 5*60, 10*60, 15*60, 30*60 }; /* Get plot scaling limits */ maxtime = get_maxtime(pi); maxdepth = get_maxdepth(pi); - /* Time markers: every 5 min */ + /* Time markers: at most every 5 min, but no more than 12 markers + * and for convenience we do 5, 10, 15 or 30 min intervals. + * This allows for 6h dives - enough (I hope) for even the craziest + * divers - but just in case, for those 8h depth-record-breaking dives, + * we double the interval if this still doesn't get us to 12 or fewer + * time markers */ + i = 0; + while (maxtime / increments[i] > 12 && i < 4) + i++; + incr = increments[i]; + while (maxtime / incr > 12) + incr *= 2; + gc->leftx = 0; gc->rightx = maxtime; gc->topy = 0; gc->bottomy = 1.0; - for (i = 5*60; i < maxtime; i += 5*60) { + set_source_rgba(gc, 1, 1, 1, 0.5); + for (i = incr; i < maxtime; i += incr) { move_to(gc, i, 0); line_to(gc, i, 1); } + cairo_stroke(cr); + + /* now the text on every second time marker */ + text_render_options_t tro = {10, 0.2, 1.0, 0.2, CENTER, TOP}; + for (i = incr; i < maxtime; i += 2 * incr) + plot_text(gc, &tro, i, 1, "%d", i/60); /* Depth markers: every 30 ft or 10 m*/ gc->leftx = 0; gc->rightx = 1.0; @@ -406,19 +493,13 @@ static int setup_temperature_limits(struct graphics_context *gc, struct plot_inf static void plot_single_temp_text(struct graphics_context *gc, int sec, int mkelvin) { - int deg; + double deg; const char *unit; static const text_render_options_t tro = {12, 0.2, 0.2, 1.0, LEFT, TOP}; - temperature_t temperature = { mkelvin }; - if (output_units.temperature == FAHRENHEIT) { - deg = to_F(temperature); - unit = UTF8_DEGREE "F"; - } else { - deg = to_C(temperature); - unit = UTF8_DEGREE "C"; - } - plot_text(gc, &tro, sec, temperature.mkelvin, "%d%s", deg, unit); + deg = get_temp_units(mkelvin, &unit); + + plot_text(gc, &tro, sec, mkelvin, "%d%s", (int)(deg + 0.5), unit); } static void plot_temperature_text(struct graphics_context *gc, struct plot_info *pi) @@ -487,72 +568,109 @@ static int get_cylinder_pressure_range(struct graphics_context *gc, struct plot_ return pi->maxpressure != 0; } -static void plot_cylinder_pressure(struct graphics_context *gc, struct plot_info *pi) +static void plot_pressure_helper(struct graphics_context *gc, struct plot_info *pi, int type) { int i; - int have_pressure = FALSE; - - if (!get_cylinder_pressure_range(gc, pi)) - return; - - set_source_rgba(gc, 0.2, 1.0, 0.2, 0.80); + int lift_pen = FALSE; for (i = 0; i < pi->nr; i++) { int mbar; struct plot_data *entry = pi->entry + i; - mbar = entry->pressure; - if (!mbar) + mbar = entry->pressure[type]; + if (!entry->same_cylinder) + lift_pen = TRUE; + if (!mbar) { + lift_pen = TRUE; continue; - have_pressure = TRUE; - if (entry->same_cylinder) - line_to(gc, entry->sec, mbar); + } + if (lift_pen) { + if (i > 0 && entry->same_cylinder) { + /* if we have a previous event from the same tank, + * draw at least a short line . + * This uses the implementation detail that the + * type is either 0 or 1 */ + int prev_pr; + prev_pr = (entry-1)->pressure[type] ? : (entry-1)->pressure[1 - type]; + move_to(gc, (entry-1)->sec, prev_pr); + line_to(gc, entry->sec, mbar); + } else + move_to(gc, entry->sec, mbar); + lift_pen = FALSE; + } else - move_to(gc, entry->sec, mbar); + line_to(gc, entry->sec, mbar); } - /* if we have valid samples, we don't want to draw a line to the minpressure - * but just end wherever the dive ended (think valve shutdowns during dive) - * but that doesn't work so well if we have only max and min - */ - if (! have_pressure) - line_to(gc, pi->maxtime, pi->minpressure); cairo_stroke(gc->cr); + } -static int mbar_to_PSI(int mbar) +static void plot_cylinder_pressure(struct graphics_context *gc, struct plot_info *pi) { - pressure_t p = {mbar}; - return to_PSI(p); + if (!get_cylinder_pressure_range(gc, pi)) + return; + + /* first plot the pressure readings we have from the dive computer */ + set_source_rgba(gc, 0.2, 1.0, 0.2, 0.80); + plot_pressure_helper(gc, pi, SENSOR_PR); + + /* then, in a different color, the interpolated values */ + set_source_rgba(gc, 1.0, 1.0, 0.2, 0.80); + plot_pressure_helper(gc, pi, INTERPOLATED_PR); +} + +static void plot_pressure_value(struct graphics_context *gc, int mbar, int sec, + int xalign, int yalign) +{ + int pressure; + const char *unit; + + pressure = get_pressure_units(mbar, &unit); + text_render_options_t tro = {10, 0.2, 1.0, 0.2, xalign, yalign}; + plot_text(gc, &tro, sec, mbar, "%d %s", pressure, unit); } static void plot_cylinder_pressure_text(struct graphics_context *gc, struct plot_info *pi) { - if (get_cylinder_pressure_range(gc, pi)) { - int start, end; - const char *unit = "bar"; + int i; + int mbar, cyl; + int seen_cyl[MAX_CYLINDERS] = { FALSE, }; + int last_pressure[MAX_CYLINDERS] = { 0, }; + int last_time[MAX_CYLINDERS] = { 0, }; + struct plot_data *entry; - switch (output_units.pressure) { - case PASCAL: - start = pi->maxpressure * 100; - end = pi->endpressure * 100; - unit = "pascal"; - break; - case BAR: - start = (pi->maxpressure + 500) / 1000; - end = (pi->endpressure + 500) / 1000; - unit = "bar"; - break; - case PSI: - start = mbar_to_PSI(pi->maxpressure); - end = mbar_to_PSI(pi->endpressure); - unit = "psi"; - break; + if (!get_cylinder_pressure_range(gc, pi)) + return; + + /* only loop over the actual events from the dive computer */ + for (i = 2; i < pi->nr; i++) { + entry = pi->entry + i; + + if (!entry->same_cylinder) { + cyl = entry->cylinderindex; + if (!seen_cyl[cyl]) { + mbar = SENSOR_PRESSURE(entry) ? : INTERPOLATED_PRESSURE(entry); + plot_pressure_value(gc, mbar, entry->sec, LEFT, BOTTOM); + seen_cyl[cyl] = TRUE; + } + if (i > 2) { + /* remember the last pressure and time of + * the previous cylinder */ + cyl = (entry - 1)->cylinderindex; + last_pressure[cyl] = + SENSOR_PRESSURE(entry - 1) ? : INTERPOLATED_PRESSURE(entry - 1); + last_time[cyl] = (entry - 1)->sec; + } } + } + cyl = entry->cylinderindex; + last_pressure[cyl] = SENSOR_PRESSURE(entry) ? : INTERPOLATED_PRESSURE(entry); + last_time[cyl] = entry->sec; - text_render_options_t tro = {10, 0.2, 1.0, 0.2, LEFT, TOP}; - plot_text(gc, &tro, 0, pi->maxpressure, "%d %s", start, unit); - plot_text(gc, &tro, pi->maxtime, pi->endpressure, - "%d %s", end, unit); + for (cyl = 0; cyl < MAX_CYLINDERS; cyl++) { + if (last_time[cyl]) { + plot_pressure_value(gc, last_pressure[cyl], last_time[cyl], CENTER, TOP); + } } } @@ -633,7 +751,7 @@ static struct plot_info *analyze_plot_info(struct plot_info *pi) /* Do pressure min/max based on the non-surface data */ for (i = 0; i < nr; i++) { struct plot_data *entry = pi->entry+i; - int pressure = entry->pressure; + int pressure = SENSOR_PRESSURE(entry) ? : INTERPOLATED_PRESSURE(entry); int temperature = entry->temperature; if (pressure) { @@ -685,6 +803,181 @@ static struct plot_info *analyze_plot_info(struct plot_info *pi) return pi; } +/* + * simple structure to track the beginning and end tank pressure as + * well as the integral of depth over time spent while we have no + * pressure reading from the tank */ +typedef struct pr_track_struct pr_track_t; +struct pr_track_struct { + int start; + int end; + int t_start; + int t_end; + double pressure_time; + pr_track_t *next; +}; + +static pr_track_t *pr_track_alloc(int start, int t_start) { + pr_track_t *pt = malloc(sizeof(pr_track_t)); + pt->start = start; + pt->t_start = t_start; + pt->end = 0; + pt->t_end = 0; + pt->pressure_time = 0.0; + pt->next = NULL; + return pt; +} + +/* poor man's linked list */ +static pr_track_t *list_last(pr_track_t *list) +{ + pr_track_t *tail = list; + if (!tail) + return NULL; + while (tail->next) { + tail = tail->next; + } + return tail; +} + +static pr_track_t *list_add(pr_track_t *list, pr_track_t *element) +{ + pr_track_t *tail = list_last(list); + if (!tail) + return element; + tail->next = element; + return list; +} + +static void list_free(pr_track_t *list) +{ + if (!list) + return; + list_free(list->next); + free(list); +} + +static void fill_missing_tank_pressures(struct dive *dive, struct plot_info *pi, + pr_track_t **track_pr) +{ + pr_track_t *list = NULL; + pr_track_t *nlist = NULL; + double pt, magic; + int cyl, i; + struct plot_data *entry; + int cur_pr[MAX_CYLINDERS]; + + for (cyl = 0; cyl < MAX_CYLINDERS; cyl++) { + cur_pr[cyl] = track_pr[cyl]->start; + } + + /* The first two are "fillers" */ + for (i = 2; i < pi->nr; i++) { + entry = pi->entry + i; + if (SENSOR_PRESSURE(entry)) { + cur_pr[entry->cylinderindex] = SENSOR_PRESSURE(entry); + } else { + if(!list || list->t_end < entry->sec) { + nlist = track_pr[entry->cylinderindex]; + list = NULL; + while (nlist && nlist->t_start <= entry->sec) { + list = nlist; + nlist = list->next; + } + /* there may be multiple segments - so + * let's assemble the length */ + nlist = list; + pt = list->pressure_time; + while (!nlist->end) { + nlist = nlist->next; + if (!nlist) { + /* oops - we have no end pressure, + * so this means this is a tank without + * gas consumption information */ + break; + } + pt += nlist->pressure_time; + } + if (!nlist) { + /* just continue without calculating + * interpolated values */ + list = NULL; + continue; + } + magic = (nlist->end - cur_pr[entry->cylinderindex]) / pt; } + if (pt != 0.0) { + double cur_pt = (entry->sec - (entry-1)->sec) * + (1 + entry->depth / 10000.0); + INTERPOLATED_PRESSURE(entry) = + cur_pr[entry->cylinderindex] + cur_pt * magic; + cur_pr[entry->cylinderindex] = INTERPOLATED_PRESSURE(entry); + } + } + } +} + +static int get_cylinder_index(struct dive *dive, struct event *ev) +{ + int i; + + /* + * Try to find a cylinder that matches the O2 percentage + * in the gas change event 'value' field. + * + * Crazy suunto gas change events. We really should do + * this in libdivecomputer or something. + */ + for (i = 0; i < MAX_CYLINDERS; i++) { + cylinder_t *cyl = dive->cylinder+i; + int o2 = (cyl->gasmix.o2.permille + 5) / 10; + if (o2 == ev->value) + return i; + } + + return 0; +} + +static struct event *get_next_gaschange(struct event *event) +{ + while (event) { + if (!strcmp(event->name, "gaschange")) + return event; + event = event->next; + } + return event; +} + +static int set_cylinder_index(struct plot_info *pi, int i, int cylinderindex, unsigned int end) +{ + while (i < pi->nr) { + struct plot_data *entry = pi->entry+i; + if (entry->sec > end) + break; + if (entry->cylinderindex != cylinderindex) { + entry->cylinderindex = cylinderindex; + entry->pressure[0] = 0; + } + i++; + } + return i; +} + +static void check_gas_change_events(struct dive *dive, struct plot_info *pi) +{ + int i = 0, cylinderindex = 0; + struct event *ev = get_next_gaschange(dive->events); + + if (!ev) + return; + + do { + i = set_cylinder_index(pi, i, cylinderindex, ev->time.seconds); + cylinderindex = get_cylinder_index(dive, ev); + ev = get_next_gaschange(ev->next); + } while (ev); + set_cylinder_index(pi, i, cylinderindex, ~0u); +} + /* * Create a plot-info with smoothing and ranged min/max * @@ -692,13 +985,17 @@ static struct plot_info *analyze_plot_info(struct plot_info *pi) * sides, so that you can do end-points without having to worry * about it. */ -static struct plot_info *create_plot_info(struct dive *dive) +static struct plot_info *create_plot_info(struct dive *dive, int nr_samples, struct sample *dive_sample) { int cylinderindex = -1; int lastdepth, lastindex; - int i, nr = dive->samples + 4, sec; + int i, nr = nr_samples + 4, sec, cyl; size_t alloc_size = plot_info_size(nr); struct plot_info *pi; + pr_track_t *track_pr[MAX_CYLINDERS] = {NULL, }; + pr_track_t *pr_track, *current; + gboolean missing_pr = FALSE; + struct plot_data *entry = NULL; pi = malloc(alloc_size); if (!pi) @@ -708,19 +1005,15 @@ static struct plot_info *create_plot_info(struct dive *dive) sec = 0; lastindex = 0; lastdepth = -1; - for (i = 0; i < dive->samples; i++) { + for (i = 0; i < nr_samples; i++) { int depth; - struct sample *sample = dive->sample+i; - struct plot_data *entry = pi->entry + i + 2; + struct sample *sample = dive_sample+i; + entry = pi->entry + i + 2; sec = entry->sec = sample->time.seconds; depth = entry->depth = sample->depth.mm; - - entry->same_cylinder = sample->cylinderindex == cylinderindex; - cylinderindex = sample->cylinderindex; - entry->pressure = sample->cylinderpressure.mbar; - if (!entry->same_cylinder && !entry->pressure) - entry->pressure = dive->cylinder[cylinderindex].start.mbar; + entry->cylinderindex = sample->cylinderindex; + SENSOR_PRESSURE(entry) = sample->cylinderpressure.mbar; entry->temperature = sample->temperature.mkelvin; if (depth || lastdepth) @@ -730,17 +1023,55 @@ static struct plot_info *create_plot_info(struct dive *dive) if (depth > pi->maxdepth) pi->maxdepth = depth; } - if (lastdepth) - lastindex = i + 2; + + check_gas_change_events(dive, pi); + + for (cyl = 0; cyl < MAX_CYLINDERS; cyl++) /* initialize the start pressures */ + track_pr[cyl] = pr_track_alloc(dive->cylinder[cyl].start.mbar, -1); + current = track_pr[pi->entry[2].cylinderindex]; + for (i = 0; i < nr_samples; i++) { + entry = pi->entry + i + 2; + + entry->same_cylinder = entry->cylinderindex == cylinderindex; + cylinderindex = entry->cylinderindex; + + /* track the segments per cylinder and their pressure/time integral */ + if (!entry->same_cylinder) { + current->end = SENSOR_PRESSURE(entry-1); + current->t_end = (entry-1)->sec; + current = pr_track_alloc(SENSOR_PRESSURE(entry), entry->sec); + track_pr[cylinderindex] = list_add(track_pr[cylinderindex], current); + } else { /* same cylinder */ + if ((!SENSOR_PRESSURE(entry) && SENSOR_PRESSURE(entry-1)) || + (SENSOR_PRESSURE(entry) && !SENSOR_PRESSURE(entry-1))) { + /* transmitter changed its working status */ + current->end = SENSOR_PRESSURE(entry-1); + current->t_end = (entry-1)->sec; + current = pr_track_alloc(SENSOR_PRESSURE(entry), entry->sec); + track_pr[cylinderindex] = + list_add(track_pr[cylinderindex], current); + } + } + /* finally, do the discrete integration to get the SAC rate equivalent */ + current->pressure_time += (entry->sec - (entry-1)->sec) * + (1 + entry->depth / 10000.0); + missing_pr |= !SENSOR_PRESSURE(entry); + } + + if (entry) + current->t_end = entry->sec; + + for (cyl = 0; cyl < MAX_CYLINDERS; cyl++) { /* initialize the end pressures */ + int pr = dive->cylinder[cyl].end.mbar; + if (pr && track_pr[cyl]) { + pr_track = list_last(track_pr[cyl]); + pr_track->end = pr; + } + } /* Fill in the last two entries with empty values but valid times */ - i = dive->samples + 2; + i = nr_samples + 2; pi->entry[i].sec = sec + 20; pi->entry[i+1].sec = sec + 40; - if (cylinderindex >= 0) { - pi->entry[i].pressure = dive->cylinder[cylinderindex].end.mbar; - pi->entry[i].same_cylinder = 1; - } - pi->nr = lastindex+1; pi->maxtime = pi->entry[lastindex].sec; @@ -749,12 +1080,34 @@ static struct plot_info *create_plot_info(struct dive *dive) pi->meandepth = dive->meandepth.mm; + if (missing_pr) { + fill_missing_tank_pressures(dive, pi, track_pr); + } + for (cyl = 0; cyl < MAX_CYLINDERS; cyl++) + list_free(track_pr[cyl]); return analyze_plot_info(pi); } void plot(struct graphics_context *gc, cairo_rectangle_int_t *drawing_area, struct dive *dive) { - struct plot_info *pi = create_plot_info(dive); + struct plot_info *pi; + static struct sample fake[4]; + struct sample *sample = dive->sample; + int nr = dive->samples; + + if (!nr) { + int duration = dive->duration.seconds; + int maxdepth = dive->maxdepth.mm; + sample = fake; + fake[1].time.seconds = duration * 0.05; + fake[1].depth.mm = maxdepth; + fake[2].time.seconds = duration * 0.95; + fake[2].depth.mm = maxdepth; + fake[3].time.seconds = duration * 1.00; + nr = 4; + } + + pi = create_plot_info(dive, nr, sample); cairo_translate(gc->cr, drawing_area->x, drawing_area->y); cairo_set_line_width(gc->cr, 2);