summaryrefslogblamecommitdiffstats
path: root/liquivision.c
blob: 2ba9b2d9f2b74a339b6b36d14d1e0f3febe180c4 (plain) (tree)























































































































































































































                                                                                                    
                                                                                                          




                                                                                                             
                                                                                                                                             































                                                                                                              
                                                                                                                                   


























































































                                                                                                               
#include <string.h>

#include "dive.h"
#include "divelist.h"
#include "file.h"


// Convert bytes into an INT
#define array_uint16_le(p) ((unsigned int) (p)[0] \
							+ ((p)[1]<<8) )
#define array_uint32_le(p) ((unsigned int) (p)[0] \
							+ ((p)[1]<<8) + ((p)[2]<<16) \
							+ ((p)[3]<<24))


static void
parse_dives (int log_version, const unsigned char *buf, unsigned int buf_size) {
	unsigned int ptr = 0;
	unsigned char model;

	struct dive *dive;
	struct divecomputer *dc;
	struct sample *sample;

	while (ptr < buf_size) {
		dive = alloc_dive();
		dc = &dive->dc;

		// Model 0=Xen, 1,2=Xeo, 4=Lynx, other=Liquivision
		model = *(buf + ptr);
		switch (model) {
		case 0:
			dc->model = "Xen";
			break;
		case 1:
		case 2:
			dc->model = "Xeo";
			break;
		case 4:
			dc->model = "Lynx";
			break;
		default:
			dc->model = "LiquiVision";
			break;
		}
		ptr++;

		// Dive location, assemble Location and Place
		unsigned int len, place_len;
		len = array_uint32_le(buf + ptr);
		ptr += 4;
		place_len = array_uint32_le(buf + ptr + len);

		if (len && place_len) {
			dive->location = malloc(len + place_len + 4);
			memset(dive->location, 0, len + place_len + 4);
			memcpy(dive->location, buf + ptr, len);
			memcpy(dive->location + len, ", ", 2);
			memcpy(dive->location + len + 2, buf + ptr + len + 4, place_len);
		} else if (len) {
			dive->location = strndup(buf + ptr, len);
		} else if (place_len) {
			dive->location = strndup(buf + ptr + len + 4, place_len);
		}

		ptr += len + 4 + place_len;

		// Dive comment
		len = array_uint32_le(buf + ptr);
		ptr += 4;

		// Blank notes are better than the default text
		if (len && strncmp(buf + ptr, "Comment ...", 11)) {
			dive->notes = strndup(buf + ptr, len);
		}
		ptr += len;

		dive->id = array_uint32_le(buf + ptr);
		ptr += 4;

		dive->number = array_uint16_le(buf + ptr) + 1;
		ptr += 2;

		dive->duration.seconds = array_uint32_le(buf + ptr);	// seconds
		ptr += 4;

		dive->maxdepth.mm = array_uint16_le(buf + ptr) * 10;	// cm->mm
		ptr += 2;

		dive->meandepth.mm = array_uint16_le(buf + ptr) * 10;	// cm->mm
		ptr += 2;

		dive->when = array_uint32_le(buf + ptr);
		ptr += 4;

		//unsigned int end_time = array_uint32_le(buf + ptr);
		ptr += 4;

		//unsigned int sit = array_uint32_le(buf + ptr);
		ptr += 4;
		//if (sit == 0xffffffff) {
		//}

		dive->surface_pressure.mbar = array_uint16_le(buf + ptr);		// ???
		ptr += 2;

		//unsigned int rep_dive = array_uint16_le(buf + ptr);
		ptr += 2;

		dive->mintemp.mkelvin =  C_to_mkelvin((float)array_uint16_le(buf + ptr)/10);// C->mK
		ptr += 2;

		dive->maxtemp.mkelvin =  C_to_mkelvin((float)array_uint16_le(buf + ptr)/10);// C->mK
		ptr += 2;

		dive->salinity = *(buf + ptr);	// ???
		ptr += 1;

		unsigned int sample_count = array_uint32_le(buf + ptr);
		ptr += 4;

		// Sample interval
		unsigned char sample_interval;
		sample_interval = 1;

		unsigned char intervals[6] = {1,2,5,10,30,60};
		if (*(buf + ptr) < 6)
			sample_interval = intervals[*(buf + ptr)];
		ptr += 1;

		float start_cns = 0;
		unsigned char dive_mode = 0, algorithm = 0;
		if (array_uint32_le(buf + ptr) != sample_count) {
			// Xeo, with CNS and OTU
			start_cns = *(float *) (buf + ptr);
			ptr += 4;
			dive->cns = *(float *) (buf + ptr);	// end cns
			ptr += 4;
			dive->otu = *(float *) (buf + ptr);
			ptr += 4;
			dive_mode = *(buf + ptr++);	// 0=Deco, 1=Gauge, 2=None
			algorithm = *(buf + ptr++);	// 0=ZH-L16C+GF
			sample_count = array_uint32_le(buf + ptr);
		}
		ptr += 4;

		// Parse dive samples
		const unsigned char *ds = buf + ptr;
		const unsigned char *ts = buf + ptr + sample_count * 2 + 4;
		const unsigned char *ps = buf + ptr + sample_count * 4 + 4;
		unsigned int ps_count = array_uint32_le(ps);
		ps += 4;

		// Bump ptr
		ptr += sample_count * 4 + 4;

		// Handle events
		unsigned int event;
		unsigned int ps_ptr;
		ps_ptr = 0;

		unsigned int d = 0, e;
		int event_time, mbar, sensor;

		// Loop through events
		for (e = 0; e < ps_count; e++) {
			// Get event
			event = array_uint16_le(ps + ps_ptr);
			ps_ptr += 2;

			switch (event) {
			case 0x0002:	//	Unknown
			case 0x0004:	//	Unknown
				ps_ptr +=  4;
				continue;
			case 0x0005:	// Unknown
				ps_ptr += 6;
				continue;
			case 0x0007:	// Gas
				// 4 byte time
				// 1 byte O2, 1 bye He
				ps_ptr += 6;
				continue;
			case 0x0008:
				// 4 byte time
				// 2 byte gas set point 2
				ps_ptr += 6;
				continue;
			case 0x000f:
				// Tank pressure
				event_time = array_uint32_le(ps + ps_ptr);
				sensor = 0; //array_uint16_le(ps + ps_ptr + 4);
				mbar = array_uint16_le(ps + ps_ptr + 6) * 10; // cb->mb
				// 1 byte PSR
				// 1 byte ST
				ps_ptr += 10;
				break;
			case 0x0010:
				ps_ptr += 26;
				continue;
			case 0x0015:	// Unknown
				ps_ptr += 2;
				continue;
			default:
				ps_ptr += 4;
				continue;
			}

			int sample_time, next_time, last_time;
			int depth_mm, last_depth, temp_mk, last_temp;

			while (true) {
				sample = prepare_sample(dc);

				// Get sample times
				sample_time = d * sample_interval;
				depth_mm = array_uint16_le(ds + d * 2) * 10; // cm->mm
				temp_mk = C_to_mkelvin((float)array_uint16_le(ts + d * 2) / 10); // dC->mK
				next_time = (d < sample_count - 1 ? (d + 1) * sample_interval : sample_time);
				last_time = (d ? (d - 1) * sample_interval : 0);

				if (d == sample_count) {
					// We still have events to record
					sample->time.seconds = event_time;
					sample->depth.mm = array_uint16_le(ds + (d - 1) * 2) * 10; // cm->mm
					sample->temperature.mkelvin = C_to_mkelvin((float) array_uint16_le(ts + (d - 1) * 2) / 10); // dC->mK
					sample->sensor = sensor;
					sample->cylinderpressure.mbar = mbar;
					finish_sample(dc);

					break;
				} else if (event_time > sample_time) {
					// Record sample and loop
					sample->time.seconds = sample_time;
					sample->depth.mm = depth_mm;
					sample->temperature.mkelvin = temp_mk;
					finish_sample(dc);
					d++;

					continue;
				} else if (event_time == sample_time) {
					sample->time.seconds = sample_time;
					sample->depth.mm = depth_mm;
					sample->temperature.mkelvin = temp_mk;
					sample->sensor = sensor;
					sample->cylinderpressure.mbar = mbar;
					finish_sample(dc);

					break;
				} else {	// Event is prior to sample
					sample->time.seconds = event_time;
					sample->sensor = sensor;
					sample->cylinderpressure.mbar = mbar;
					if (last_time == sample_time) {
						sample->depth.mm = depth_mm;
						sample->temperature.mkelvin = temp_mk;
					} else {
						// Extrapolate
						last_depth = array_uint16_le(ds + (d - 1) * 2) * 10; // cm->mm
						last_temp = C_to_mkelvin((float) array_uint16_le(ts + (d - 1) * 2) / 10); // dC->mK
						sample->depth.mm = last_depth + (depth_mm - last_depth)
							* (event_time - last_time) / sample_interval;
						sample->temperature.mkelvin = last_temp + (temp_mk - last_temp)
							* (event_time - last_time) / sample_interval;
					}
					finish_sample(dc);

					break;
				}
			} // while (true);
		} // for each event sample

		// record trailing depth samples
		for ( ;d < sample_count; d++) {
			sample = prepare_sample(dc);
			sample->time.seconds = d * sample_interval;

			sample->depth.mm = array_uint16_le(ds + d * 2) * 10; // cm->mm
			sample->temperature.mkelvin =
				C_to_mkelvin((float)array_uint16_le(ts + d * 2) / 10);
			finish_sample(dc);
		}

		if (log_version == 3 && model == 4) {
			// Advance to begin of next dive
			switch (array_uint16_le(ps + ps_ptr)) {
			case 0x0000:
				ps_ptr += 5;
				break;
			case 0x0100:
				ps_ptr += 7;
				break;
			case 0x0200:
				ps_ptr += 9;
				break;
			case 0x0300:
				ps_ptr += 11;
				break;
			case 0x0b0b:
				ps_ptr += 27;
				break;
			}

			while (*(ps + ps_ptr) != 0x04)
				ps_ptr++;
		}

		// End dive
		dive->downloaded = true;
		record_dive(dive);
		mark_divelist_changed(true);

		// Advance ptr for next dive
		ptr += ps_ptr + 4;
	} // while

	save_dives("/tmp/test.xml");
}


int
try_to_open_liquivision(const char *filename, struct memblock *mem)
{
	void *name;
	const unsigned char *buf = mem->buffer;
	unsigned int buf_size = mem->size;
	unsigned int ptr;
	int log_version;

	// Get name
	unsigned int len = array_uint32_le(buf);
	if (len) {
		name = malloc(len);
		strncpy(name, buf + 4, len);
	}
	ptr = 4 + len;

	unsigned int dive_count = array_uint32_le(buf + ptr);
	if (dive_count == 0xffffffff) {
		// File version 3.0
		log_version = 3;
		ptr += 6;
		dive_count = array_uint32_le(buf + ptr);
	} else {
		log_version = 2;
	}
	ptr += 4;

	parse_dives(log_version, buf + ptr, buf_size - ptr);

	return 1;
}