863 lines
28 KiB
C
863 lines
28 KiB
C
#include "channel.h"
|
|
#include "export.h"
|
|
|
|
#ifndef _BPS_EXPORT_CSV_H_
|
|
#define _BPS_EXPORT_CSV_H_
|
|
|
|
typedef struct {
|
|
int temp_channel;
|
|
double temp_time_step;
|
|
unsigned int temp_line_number;
|
|
FILE * temp_file;
|
|
} ExportCsvTempFileEntry;
|
|
|
|
/**
|
|
* used in ExportCsvOptions.device_identify
|
|
*/
|
|
#define DEVICE_ID_NORMAL 0
|
|
#define DEVICE_ID_ELITE 1
|
|
|
|
/**
|
|
* export_csv options
|
|
*/
|
|
typedef struct {
|
|
// export function options, exported fields
|
|
unsigned int resample_rate;
|
|
unsigned int resample_method;
|
|
unsigned int limit_column_time;
|
|
unsigned int at_least_row_number;
|
|
IntSet * channel_mask;
|
|
unsigned char include_header; // boolean
|
|
unsigned char shift_channel; // boolean
|
|
|
|
// below are export function internal fields
|
|
|
|
// export function use
|
|
FILE * export_file;
|
|
double time_stamp;
|
|
double sample_rate;
|
|
int valid_time_stamp; // workaround for duplicate data
|
|
|
|
// export function cache
|
|
|
|
/**
|
|
* identify which device it is. it should be one of the macro DEVICE_ID_*
|
|
*/
|
|
unsigned char device_identify;
|
|
|
|
/**
|
|
* device ID number.
|
|
*/
|
|
unsigned int device;
|
|
unsigned int entry_size;
|
|
ReSampleEntry **entries;
|
|
|
|
// export temp file
|
|
unsigned int temp_file_size;
|
|
ExportCsvTempFileEntry *temp_file_entry;
|
|
} ExportCsvOptions;
|
|
|
|
static int ExportCsvOptions_init(ExportCsvOptions *options) {
|
|
options->export_file = NULL;
|
|
options->device_identify = DEVICE_ID_NORMAL;
|
|
options->device = 0;
|
|
options->time_stamp = 0;
|
|
options->entry_size = 0;
|
|
options->entries = NULL;
|
|
options->temp_file_size = 0;
|
|
options->temp_file_entry = NULL;
|
|
|
|
// set mode
|
|
switch (options->resample_method) {
|
|
case SAMPLE_MODE_RAW:
|
|
options->resample_method = SAMPLE_MODE_RAW;
|
|
break;
|
|
case SAMPLE_MODE_FIRST:
|
|
default:
|
|
options->resample_method = SAMPLE_MODE_FIRST;
|
|
break;
|
|
case SAMPLE_MODE_AVG:
|
|
options->resample_method = SAMPLE_MODE_AVG;
|
|
break;
|
|
}
|
|
|
|
// resample entry
|
|
if (options->resample_method > SAMPLE_MODE_RAW) {
|
|
// check resample rate
|
|
if (options->resample_rate == 0) {
|
|
PyErr_SetString(PyExc_ValueError, "zero sample rate");
|
|
return 1;
|
|
}
|
|
|
|
options->entry_size = 16;
|
|
options->entries = (ReSampleEntry **)PyMem_Malloc(options->entry_size * sizeof(ReSampleEntry *));
|
|
|
|
if (options->entries == NULL) {
|
|
options->entry_size = 0;
|
|
PyErr_SetString(PyExc_RuntimeError, "PyMem_Malloc ReSampleEntry[] fail");
|
|
return 1;
|
|
}
|
|
|
|
for (unsigned int i = 0; i < options->entry_size; i++) {
|
|
options->entries[i] = NULL;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ExportCsvOptions_free(ExportCsvOptions *options) {
|
|
if (options->entries != NULL) {
|
|
for (unsigned int i = 0; i < options->entry_size; i++) {
|
|
ReSampleEntry *entry = options->entries[i];
|
|
if (entry != NULL) {
|
|
PyMem_Free(entry);
|
|
options->entries[i] = NULL;
|
|
}
|
|
}
|
|
PyMem_Free(options->entries);
|
|
options->entries = NULL;
|
|
}
|
|
|
|
if (options->temp_file_size > 0) {
|
|
for (unsigned int i = 0; i < options->temp_file_size; i++) {
|
|
ExportCsvTempFileEntry *entry = options->temp_file_entry + i;
|
|
FILE * temp = entry->temp_file;
|
|
|
|
if (temp != NULL) {
|
|
fclose(temp);
|
|
}
|
|
|
|
entry->temp_file = NULL;
|
|
}
|
|
PyMem_Free(options->temp_file_entry);
|
|
options->temp_file_entry = NULL;
|
|
options->temp_file_size = 0;
|
|
}
|
|
}
|
|
|
|
static int ExportCsvOptions_extend(ExportCsvOptions *options, unsigned int at_least_size) {
|
|
unsigned int new_size = options->entry_size;
|
|
|
|
while (new_size < at_least_size) {
|
|
new_size += 4;
|
|
}
|
|
|
|
ReSampleEntry **new_entries = (ReSampleEntry **)PyMem_Realloc(options->entries, new_size * sizeof(ReSampleEntry *));
|
|
if (new_entries == NULL) {
|
|
PyErr_SetString(PyExc_RuntimeError, "PyMem_Malloc ReSampleEntry fail");
|
|
return 1;
|
|
}
|
|
|
|
options->entry_size = new_size;
|
|
options->entries = new_entries;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @return: 1 on success, assign result to *index*.
|
|
*/
|
|
static int ExportCsvOptions_get_temp_index(ExportCsvOptions *options, unsigned int channel, int *index) {
|
|
if (options->temp_file_size == 0) return 0;
|
|
|
|
for (int i = options->temp_file_size - 1; i >= 0; i--) {
|
|
int temp_channel = (options->temp_file_entry + i)->temp_channel;
|
|
if (temp_channel >= 0 && (unsigned int)temp_channel == channel) {
|
|
*index = i;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @return: 1 on success, -1 on error
|
|
*/
|
|
static int ExportCsvOptions_get_empty_temp_index(ExportCsvOptions *options, unsigned int channel, int *index) {
|
|
unsigned int size = options->channel_mask->size;
|
|
int mod = int_set_indexof(options->channel_mask, channel);
|
|
|
|
if (mod < 0) {
|
|
PyErr_SetString(PyExc_RuntimeError, "channel not found");
|
|
return -1;
|
|
}
|
|
|
|
for (unsigned int i = mod; i < options->temp_file_size; i += size) {
|
|
int temp_channel = (options->temp_file_entry + i)->temp_channel;
|
|
if (temp_channel == -1) {
|
|
*index = i;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @return: 1 on success
|
|
*/
|
|
static int ExportCsvOptions_new_temp(ExportCsvOptions *options, unsigned int channel, int *index) {
|
|
int result = ExportCsvOptions_get_empty_temp_index(options, channel, index);
|
|
|
|
ExportCsvTempFileEntry *last_entry = NULL;
|
|
|
|
if (result == -1) {
|
|
// channel not found, with error
|
|
return 0;
|
|
} else if (result == 1) {
|
|
last_entry = options->temp_file_entry + *index;
|
|
} else {
|
|
// channel not found, extend array size
|
|
|
|
unsigned int size = options->channel_mask->size;
|
|
unsigned int new_size = options->temp_file_size + size;
|
|
|
|
ExportCsvTempFileEntry *new_entry = (ExportCsvTempFileEntry *)PyMem_Realloc(options->temp_file_entry, //
|
|
new_size * sizeof(ExportCsvTempFileEntry));
|
|
|
|
if (new_entry == NULL) {
|
|
ensure_error(PyErr_SetString(PyExc_RuntimeError, "ExportCsvOptions_new_temp PyMem_Realloc fail"));
|
|
return 0;
|
|
}
|
|
|
|
// init channel value
|
|
for (unsigned int i = options->temp_file_size; i < new_size; i++) {
|
|
ExportCsvTempFileEntry *tmp_entry = new_entry + i;
|
|
|
|
tmp_entry->temp_channel = -1;
|
|
tmp_entry->temp_time_step = 0;
|
|
tmp_entry->temp_line_number = 0;
|
|
tmp_entry->temp_file = NULL;
|
|
|
|
FILE *temp = tmpfile();
|
|
|
|
if (temp == NULL) {
|
|
ensure_error(PyErr_Format(PyExc_RuntimeError, "ExportCsvOptions_new_temp tmpfile() for column : %u", i));
|
|
return 0;
|
|
}
|
|
|
|
tmp_entry->temp_file = temp;
|
|
|
|
// add header
|
|
unsigned int tmp_channel = *(options->channel_mask->storage + i % size);
|
|
|
|
if (options->shift_channel) tmp_channel++;
|
|
|
|
if (options->device_identify == DEVICE_ID_ELITE) {
|
|
if (i == 0) {
|
|
fprintf(temp, "#channel,%d\n", tmp_channel);
|
|
fprintf(temp, "#time[ms],current[nA]\n");
|
|
} else if (i % 3 == 0) {
|
|
fprintf(temp, "channel,%d\n", tmp_channel);
|
|
fprintf(temp, "time[ms],current[nA]\n");
|
|
} else if (i % 3 == 1) {
|
|
fprintf(temp, "channel,%d\n", tmp_channel);
|
|
fprintf(temp, "time[ms],voltage[uV]\n");
|
|
} else {
|
|
fprintf(temp, "channel,%d\n", tmp_channel);
|
|
fprintf(temp, "time[ms],resister[kohm]\n");
|
|
}
|
|
} else {
|
|
fprintf(temp, "#channel,%d\n", tmp_channel);
|
|
fprintf(temp, "#time[ms],voltage[uV]\n");
|
|
}
|
|
}
|
|
|
|
options->temp_file_size = new_size;
|
|
options->temp_file_entry = new_entry;
|
|
|
|
// redo
|
|
result = ExportCsvOptions_get_empty_temp_index(options, channel, index);
|
|
|
|
if (result == -1) {
|
|
// channel not found, with error
|
|
return 0;
|
|
} else if (result == 1) {
|
|
last_entry = options->temp_file_entry + *index;
|
|
} else {
|
|
ensure_error(PyErr_SetString(PyExc_RuntimeError, "channel should be found"));
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// init field
|
|
last_entry->temp_channel = channel;
|
|
last_entry->temp_time_step = 0;
|
|
last_entry->temp_line_number = 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* RecordingDataInitFunction
|
|
* @return: 1 on fail
|
|
*/
|
|
static int _export_csv_decode_init(RecordingDataDecodeState *state, //
|
|
void * _data) {
|
|
if (_data == NULL) {
|
|
ensure_error(PyErr_SetString(PyExc_RuntimeError, "_export_csv_decode_init(_data=NULL)"));
|
|
return 1;
|
|
}
|
|
|
|
ExportCsvOptions *options = (ExportCsvOptions *)_data;
|
|
|
|
if (options->time_stamp > 0 && state->time_stamp <= options->time_stamp) {
|
|
options->valid_time_stamp = 0;
|
|
} else {
|
|
options->valid_time_stamp = 1;
|
|
options->time_stamp = state->time_stamp;
|
|
}
|
|
options->sample_rate = state->sample_rate;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @return: 1 on fail
|
|
*/
|
|
static int _export_csv_append_line(ExportCsvOptions *options, //
|
|
unsigned int channel,
|
|
double time_stamp,
|
|
double value) {
|
|
// get last column
|
|
int index = 0;
|
|
double time_step = 0;
|
|
ExportCsvTempFileEntry *entry = NULL;
|
|
if (ExportCsvOptions_get_temp_index(options, channel, &index)) {
|
|
entry = options->temp_file_entry + index;
|
|
time_step = entry->temp_time_step;
|
|
}
|
|
|
|
// next column?
|
|
int new_column = 0;
|
|
if (options->limit_column_time > 0 && entry != NULL) {
|
|
double step = (time_stamp / options->limit_column_time / 1000.0);
|
|
if (step > time_step && entry->temp_line_number > options->at_least_row_number) {
|
|
new_column = 1;
|
|
time_step = step;
|
|
}
|
|
}
|
|
|
|
// new column
|
|
if (entry == NULL || new_column) {
|
|
if (ExportCsvOptions_new_temp(options, channel, &index)) {
|
|
entry = options->temp_file_entry + index;
|
|
entry->temp_time_step = time_step;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
FILE *export_file = entry->temp_file;
|
|
|
|
if (export_file == NULL) {
|
|
ensure_error(PyErr_SetString(PyExc_RuntimeError, "ExportCsvOptions->temp_file[channel] = NULL"));
|
|
return 1;
|
|
}
|
|
|
|
// data
|
|
|
|
if (options->device_identify == DEVICE_ID_ELITE && (channel % 3) == 1) {
|
|
fprintf(export_file, "%.4lf,%.3f\n", time_stamp, value);
|
|
} else if (options->device_identify == DEVICE_ID_ELITE && (channel % 3) == 2) {
|
|
fprintf(export_file, "%.4lf,%.3f\n", time_stamp, value / 1000);
|
|
} else {
|
|
fprintf(export_file, "%.4lf,%f\n", time_stamp, value);
|
|
}
|
|
entry->temp_line_number++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* RecordingDataAppendFunction
|
|
* @return: 1 on fail
|
|
*/
|
|
static int _export_csv_append_raw(RecordingDataDecodeState *state, //
|
|
unsigned int data_index,
|
|
unsigned int device,
|
|
unsigned int channel,
|
|
int * value,
|
|
void * _data) {
|
|
if (_data == NULL) {
|
|
ensure_error(PyErr_SetString(PyExc_RuntimeError, "_export_csv_append_raw(_data=NULL)"));
|
|
return 1;
|
|
}
|
|
|
|
if (value == NULL) {
|
|
// ignore invalid data
|
|
return 0;
|
|
}
|
|
|
|
ExportCsvOptions *options = (ExportCsvOptions *)_data;
|
|
|
|
if (!options->valid_time_stamp) {
|
|
// ignore duplicate data section
|
|
return 0;
|
|
}
|
|
|
|
// filter channel
|
|
if (!int_set_contain(options->channel_mask, channel)) {
|
|
return 0;
|
|
}
|
|
|
|
// calculate time stamp
|
|
double time_stamp;
|
|
if (options->sample_rate == 0) {
|
|
time_stamp = options->time_stamp;
|
|
} else {
|
|
time_stamp = options->time_stamp + data_index * 1000.0 / options->sample_rate;
|
|
// printf("@@@@@ Time stamp = %lf\n", time_stamp);
|
|
// printf("@@@@@ data_index = %d\n", data_index);
|
|
// printf("@@@@@ options->sample_rate = %lf\n", options->sample_rate);
|
|
}
|
|
|
|
return _export_csv_append_line(options, channel, time_stamp, (double)*value);
|
|
}
|
|
|
|
/**
|
|
* RecordingDataAppendFunction
|
|
* @return: 1 on fail
|
|
*/
|
|
static int _export_csv_append_resample(RecordingDataDecodeState *state, //
|
|
unsigned int data_index,
|
|
unsigned int device,
|
|
unsigned int channel,
|
|
int * value,
|
|
void * _data) {
|
|
if (_data == NULL) {
|
|
ensure_error(PyErr_SetString(PyExc_RuntimeError, "_export_csv_append_resample(_data=NULL)"));
|
|
return 1;
|
|
}
|
|
|
|
if (value == NULL) {
|
|
// ignore invalid data
|
|
return 0;
|
|
}
|
|
|
|
ExportCsvOptions *options = (ExportCsvOptions *)_data;
|
|
|
|
if (!options->valid_time_stamp) {
|
|
// ignore duplicate data section
|
|
return 0;
|
|
}
|
|
|
|
// filter channel
|
|
if (!int_set_contain(options->channel_mask, channel)) {
|
|
return 0;
|
|
}
|
|
|
|
// calculate time stamp
|
|
double time_delta = 1000.0 / options->resample_rate;
|
|
double time_stamp;
|
|
if (options->sample_rate == 0) {
|
|
time_stamp = options->time_stamp;
|
|
} else {
|
|
time_stamp = options->time_stamp + data_index * 1000.0 / options->sample_rate;
|
|
// printf("##### Time stamp = %lf\n", time_stamp);
|
|
// printf("##### data_index = %d\n", data_index);
|
|
// printf("##### options->sample_rate = %lf\n", options->sample_rate);
|
|
}
|
|
|
|
// check entry size
|
|
if (options->entry_size <= channel) {
|
|
ExportCsvOptions_extend(options, channel);
|
|
}
|
|
|
|
ReSampleEntry *entry = options->entries[channel];
|
|
|
|
// check entry
|
|
if (entry == NULL) {
|
|
// new entry
|
|
entry = (ReSampleEntry *)PyMem_Malloc(sizeof(ReSampleEntry));
|
|
if (entry == NULL) return 1;
|
|
|
|
options->entries[channel] = entry;
|
|
|
|
entry->time_step = (time_stamp / time_delta);
|
|
entry->count = 0;
|
|
entry->value = 0;
|
|
}
|
|
|
|
// check time range
|
|
double base_time_stamp = options->time_stamp + entry->time_step * time_delta;
|
|
double double_value;
|
|
if (base_time_stamp + time_delta < time_stamp) {
|
|
if (ReSampleEntry_value(options->resample_method, entry, &double_value)) {
|
|
if (_export_csv_append_line(options, channel, base_time_stamp, double_value)) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
entry->time_step = (time_stamp / time_delta);
|
|
entry->count = 0;
|
|
entry->value = 0;
|
|
}
|
|
// add value
|
|
ReSampleEntry_add_value(options->resample_method, entry, *value);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* RecordingMetaExportInit
|
|
* @return: 1 on fail
|
|
*/
|
|
static int export_csv_init(RecordingMeta *recording_meta, //
|
|
FILE * export_file,
|
|
void * _options) {
|
|
if (recording_meta == NULL) {
|
|
PyErr_SetString(PyExc_RuntimeError, "export_csv_init(recording_meta=NULL)");
|
|
return 1;
|
|
}
|
|
|
|
if (export_file == NULL) {
|
|
PyErr_SetString(PyExc_RuntimeError, "export_csv_init(export_file=NULL)");
|
|
return 1;
|
|
}
|
|
|
|
if (_options == NULL) {
|
|
PyErr_SetString(PyExc_RuntimeError, "export_csv_init(options=NULL)");
|
|
return 1;
|
|
}
|
|
|
|
#define SIZE 256
|
|
char buf[SIZE];
|
|
|
|
ExportCsvOptions *options = (ExportCsvOptions *)_options;
|
|
|
|
// data format
|
|
if (strncmp(DATA_FORMAT_REC, recording_meta->data_format, DATA_FORMAT_SIZE) != 0) {
|
|
PyErr_SetString(PyExc_RuntimeError, "illegal recording meta encode data format");
|
|
return 1;
|
|
}
|
|
|
|
options->export_file = export_file;
|
|
|
|
// detect which device it is
|
|
#define serial_eq(P, p, V, v, S, s) \
|
|
({ \
|
|
char * __s = recording_meta->device_serial; \
|
|
__s[0] == P &&__s[1] == p &&__s[2] == V &&__s[3] == v &&__s[4] == S &&__s[5] == s; \
|
|
})
|
|
|
|
if(*(recording_meta->device_name) == 'E'){
|
|
options->device_identify = DEVICE_ID_ELITE;
|
|
}
|
|
|
|
if (serial_eq(0, 2, 1, 2, 0, 0) // EliteZM
|
|
|| serial_eq(0, 2, 1, 3, 0, 0) // EliteZMS
|
|
|| serial_eq(0, 2, 1, 4, 0, 0) // EliteZML
|
|
|| serial_eq(0, 2, 1, 5, 0, 0) // EliteZML
|
|
) {
|
|
// is Elite device
|
|
options->device_identify = DEVICE_ID_ELITE;
|
|
}
|
|
#undef serial_eq
|
|
|
|
// print header
|
|
if (options->include_header) {
|
|
#ifndef _RASPBERRY_PI3_
|
|
{
|
|
unsigned long create_time = recording_meta->create_time;
|
|
time_t create_time_t = (time_t)(create_time / 1000);
|
|
|
|
int read = strftime(buf, SIZE, "%F %T", localtime(&create_time_t));
|
|
if (read == 0) return 1;
|
|
|
|
fprintf(export_file, "#create_time,%s\n", buf);
|
|
}
|
|
#endif
|
|
|
|
// device name
|
|
fprintf(export_file, "#device,%s,", recording_meta->device_name);
|
|
|
|
// device mac address
|
|
fprintf(export_file, //
|
|
"%02hhX:%02hhX:%02hhX:%02hhX:%02hhX:%02hhX\n",
|
|
recording_meta->device_address[5],
|
|
recording_meta->device_address[4],
|
|
recording_meta->device_address[3],
|
|
recording_meta->device_address[2],
|
|
recording_meta->device_address[1],
|
|
recording_meta->device_address[0]);
|
|
|
|
// recording parameter
|
|
fprintf(export_file, "#parameter\n");
|
|
|
|
// recording parameter
|
|
for (unsigned int parameter_index = 0; parameter_index < recording_meta->parameter_count; parameter_index++) {
|
|
RecordingParameter *parameter = recording_meta->parameter + parameter_index;
|
|
|
|
// parameter name
|
|
if (!file_seek_abs(recording_meta->meta_file, parameter->name_seek_pos, "parameter name")) {
|
|
return 1;
|
|
}
|
|
|
|
if (!file_read_string(recording_meta->meta_file, buf, "parameter name")) {
|
|
return 1;
|
|
}
|
|
|
|
fprintf(export_file, "#%s,", buf);
|
|
|
|
// parameter value
|
|
if (parameter->parameter_type == 0 || parameter->data_length == 0) {
|
|
fprintf(export_file, "\n");
|
|
} else {
|
|
if (!file_seek_abs(recording_meta->meta_file, parameter->value_seek_pos, "parameter value")) {
|
|
return 1;
|
|
}
|
|
|
|
int shift_channel = (options->shift_channel && (strcmp("CHANNEL", buf) == 0)) ? 1 : 0;
|
|
|
|
for (unsigned int i = 0; i < parameter->data_length; i++) {
|
|
if (i > 0) fprintf(export_file, ",");
|
|
|
|
switch (parameter->parameter_type) {
|
|
case '?': // bool
|
|
if (fgetc(recording_meta->meta_file)) {
|
|
fprintf(export_file, "True");
|
|
} else {
|
|
fprintf(export_file, "False");
|
|
}
|
|
break;
|
|
case 'p': // str
|
|
file_read_string(recording_meta->meta_file, buf, "parameter value");
|
|
fprintf(export_file, "%s", buf);
|
|
break;
|
|
case 'b': // i8
|
|
case 'B': // u8
|
|
file_read(recording_meta->meta_file, buf, 1, "parameter value");
|
|
if (parameter->parameter_type == 'B') {
|
|
fprintf(export_file, "%u", buf_u8(buf) + shift_channel);
|
|
} else {
|
|
fprintf(export_file, "%d", buf_i8(buf) + shift_channel);
|
|
}
|
|
break;
|
|
case 'h': // i16
|
|
case 'H': // u16
|
|
file_read(recording_meta->meta_file, buf, 2, "parameter value");
|
|
if (parameter->parameter_type == 'H') {
|
|
fprintf(export_file, "%u", buf_u16B(buf) + shift_channel);
|
|
} else {
|
|
fprintf(export_file, "%d", buf_i16B(buf) + shift_channel);
|
|
}
|
|
break;
|
|
case 'i': // i32
|
|
case 'I': // u32
|
|
file_read(recording_meta->meta_file, buf, 4, "parameter value");
|
|
if (parameter->parameter_type == 'I') {
|
|
fprintf(export_file, "%u", buf_u32B(buf) + shift_channel);
|
|
} else {
|
|
fprintf(export_file, "%d", buf_i32B(buf) + shift_channel);
|
|
}
|
|
break;
|
|
case 'f': // float
|
|
file_read(recording_meta->meta_file, buf, 4, "parameter value");
|
|
fprintf(export_file, "%f", buf_f32B(buf));
|
|
break;
|
|
case 'd': // XXX double support
|
|
default:
|
|
return 1;
|
|
}
|
|
}
|
|
fprintf(export_file, "\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
#undef SIZE
|
|
}
|
|
|
|
/**
|
|
* RecordingMetaExportFunc
|
|
* @return: 1 on fail
|
|
*/
|
|
static int export_csv_func(RecordingMeta *recording_meta, //
|
|
FILE * export_file,
|
|
int file_index,
|
|
FILE * recording_file,
|
|
void * _options) {
|
|
if (recording_meta == NULL) {
|
|
PyErr_SetString(PyExc_RuntimeError, "export_csv_func(recording_meta=NULL)");
|
|
return 1;
|
|
}
|
|
|
|
if (export_file == NULL) {
|
|
PyErr_SetString(PyExc_RuntimeError, "export_csv_func(export_file=NULL)");
|
|
return 1;
|
|
}
|
|
|
|
if (_options == NULL) {
|
|
PyErr_SetString(PyExc_RuntimeError, "export_csv_func(options=NULL)");
|
|
return 1;
|
|
}
|
|
|
|
#define _file_seek(_offset, _message) \
|
|
do { \
|
|
if (!file_seek_rel(recording_file, _offset, _message)) { \
|
|
return 1; \
|
|
} \
|
|
} while (0)
|
|
|
|
#define SIZE 256
|
|
char buf[SIZE];
|
|
|
|
ExportCsvOptions *options = (ExportCsvOptions *)_options;
|
|
|
|
if (file_index >= 0) {
|
|
// move to beginning
|
|
if (!file_seek_abs(recording_file, 0, "recording file")) {
|
|
return 1;
|
|
}
|
|
|
|
_file_seek(4, "magic number");
|
|
_file_seek(4, "version number");
|
|
_file_seek(8, "create time");
|
|
_file_seek(16, "file uuid");
|
|
_file_seek(1, "file serial number");
|
|
|
|
// data_format
|
|
file_read(recording_file, buf, DATA_FORMAT_SIZE, "data format");
|
|
if (strncmp(DATA_FORMAT_REC, buf, DATA_FORMAT_SIZE) != 0) {
|
|
PyErr_SetString(PyExc_RuntimeError, "illegal recording file encode data format");
|
|
return 1;
|
|
}
|
|
|
|
RecordingDataInitFunction * init_func = &_export_csv_decode_init;
|
|
RecordingDataAppendFunction *append_func;
|
|
|
|
if (options->resample_rate == SAMPLE_MODE_RAW) {
|
|
append_func = _export_csv_append_raw;
|
|
} else {
|
|
append_func = _export_csv_append_resample;
|
|
}
|
|
|
|
if (decode_recording_file_ALL(recording_file, //
|
|
init_func,
|
|
append_func,
|
|
options)) {
|
|
return 1;
|
|
}
|
|
} else {
|
|
// end of recording file
|
|
|
|
if (options->entries != NULL) {
|
|
double time_delta = 1000.0 / options->resample_rate;
|
|
|
|
for (unsigned int channel = 0; channel < options->entry_size; channel++) {
|
|
// get entry
|
|
ReSampleEntry *entry = options->entries[channel];
|
|
if (entry != NULL) {
|
|
// get value
|
|
double double_value;
|
|
double base_time_stamp = options->time_stamp + entry->time_step * time_delta;
|
|
if (ReSampleEntry_value(options->resample_method, entry, &double_value)) {
|
|
_export_csv_append_line(options, channel, base_time_stamp, double_value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// merge temp file
|
|
for (unsigned int i = 0; i < options->temp_file_size; i++) {
|
|
FILE *temp_file = (options->temp_file_entry + i)->temp_file;
|
|
if (temp_file != NULL) {
|
|
rewind(temp_file);
|
|
}
|
|
}
|
|
|
|
#define _file_read_line(_file) \
|
|
({ \
|
|
int __c; \
|
|
int read = 0; \
|
|
for (;;) { \
|
|
__c = fgetc(_file); \
|
|
if (__c == EOF || __c == '\n') { \
|
|
buf[read] = '\0'; \
|
|
break; \
|
|
} else { \
|
|
buf[read++] = (char)__c; \
|
|
} \
|
|
} \
|
|
__c; \
|
|
})
|
|
|
|
//
|
|
unsigned int eof_file;
|
|
do {
|
|
eof_file = 0;
|
|
for (unsigned int i = 0; i < options->temp_file_size; i++) {
|
|
FILE *temp_file = (options->temp_file_entry + i)->temp_file;
|
|
if (temp_file == NULL) {
|
|
fprintf(export_file, (i == 0) ? "," : ",,");
|
|
eof_file++;
|
|
} else if (_file_read_line(temp_file) == EOF) {
|
|
fprintf(export_file, (i == 0) ? "," : ",,");
|
|
eof_file++;
|
|
} else {
|
|
fprintf(export_file, "%s%s", (i == 0) ? "" : ",", buf);
|
|
}
|
|
}
|
|
fprintf(export_file, "\n");
|
|
// all file EOF?
|
|
} while (eof_file < options->temp_file_size);
|
|
|
|
// close
|
|
for (unsigned int i = 0; i < options->temp_file_size; i++) {
|
|
FILE *temp_file = (options->temp_file_entry + i)->temp_file;
|
|
if (temp_file != NULL) {
|
|
fclose(temp_file);
|
|
}
|
|
(options->temp_file_entry + i)->temp_file = NULL;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
|
|
#undef SIZE
|
|
#undef _file_seek
|
|
#undef _file_read_line
|
|
}
|
|
|
|
static PyObject *export_csv(const char * meta_filepath, //
|
|
const char * export_path,
|
|
unsigned int resample_rate,
|
|
unsigned int resample_method,
|
|
unsigned int limit_column_time,
|
|
IntSet * channel_mask,
|
|
unsigned char include_header /*boolean*/,
|
|
unsigned char shift_channel /*boolean*/) {
|
|
if (meta_filepath == NULL) return NULL;
|
|
if (export_path == NULL) return NULL;
|
|
|
|
ExportCsvOptions options = {
|
|
// clang-format off
|
|
.limit_column_time = limit_column_time,
|
|
.at_least_row_number = 100000,
|
|
.channel_mask = channel_mask,
|
|
.resample_rate = resample_rate,
|
|
.resample_method = resample_method,
|
|
.include_header = include_header,
|
|
.shift_channel = shift_channel
|
|
// clang-format on
|
|
};
|
|
|
|
ExportCsvOptions_init(&options);
|
|
|
|
PyObject *ret = export_recording_meta(meta_filepath, //
|
|
export_path,
|
|
&export_csv_init,
|
|
&export_csv_func,
|
|
&options);
|
|
|
|
ExportCsvOptions_free(&options);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#endif
|