1027 lines
32 KiB
C
1027 lines
32 KiB
C
#include <math.h>
|
|
#include <string.h>
|
|
#include "Python.h"
|
|
#include "channel.h"
|
|
#include "recording.h"
|
|
#include "sample.h"
|
|
#include "structmember.h"
|
|
|
|
typedef struct {
|
|
/**
|
|
* data capacity
|
|
*/
|
|
unsigned int capacity;
|
|
|
|
/**
|
|
* data size
|
|
*/
|
|
unsigned int size;
|
|
|
|
/**
|
|
* time stamp of first data
|
|
*/
|
|
double time_stamp;
|
|
|
|
/**
|
|
* data list
|
|
*/
|
|
ReSampleEntry *entries;
|
|
} ReSampleEntryList;
|
|
|
|
static ReSampleEntry *ReSampleEntryList_add(ReSampleEntryList *list) {
|
|
if (list->capacity == 0) {
|
|
list->entries = (ReSampleEntry *)PyMem_Malloc(10 * sizeof(ReSampleEntry));
|
|
|
|
if (list->entries == NULL) {
|
|
PyErr_SetString(PyExc_RuntimeError, "PyMem_Malloc ReSampleEntry fail");
|
|
return NULL;
|
|
}
|
|
|
|
list->capacity = 10;
|
|
list->size = 0;
|
|
|
|
} else if (list->size >= list->capacity) {
|
|
unsigned int new_size = 10 + list->capacity;
|
|
|
|
ReSampleEntry *entries = (ReSampleEntry *)PyMem_Realloc(list->entries, new_size * sizeof(ReSampleEntry));
|
|
if (entries == NULL) return NULL;
|
|
|
|
list->entries = entries;
|
|
list->capacity = new_size;
|
|
}
|
|
|
|
ReSampleEntry *ret = list->entries + (list->size++);
|
|
ret->time_step = 0;
|
|
ret->count = 0;
|
|
ret->value = 0.0;
|
|
|
|
return ret;
|
|
}
|
|
|
|
// class DataMessageEncoder
|
|
typedef struct {
|
|
PyObject_HEAD;
|
|
|
|
/**
|
|
* message header
|
|
*/
|
|
unsigned int header;
|
|
|
|
/**
|
|
* data width. 2 or 4
|
|
*/
|
|
unsigned int width;
|
|
|
|
/**
|
|
* mode.
|
|
*
|
|
* 0 : raw data (work like first value. difference is sample rate value)
|
|
* 1 : first value
|
|
* 2 : average value
|
|
*/
|
|
unsigned int mode;
|
|
|
|
/**
|
|
* sample rate in unit 1/sec
|
|
*/
|
|
unsigned int sample_rate;
|
|
|
|
unsigned int device;
|
|
|
|
IntSet *channel;
|
|
|
|
ReSampleEntryList *data;
|
|
|
|
} DataMessageEncoder;
|
|
|
|
static PyMemberDef DataMessageEncoderMembers[] = { //
|
|
// header:int
|
|
{"header", T_UINT, offsetof(DataMessageEncoder, header), READONLY, "message header"},
|
|
|
|
// width:int
|
|
{"width", T_UINT, offsetof(DataMessageEncoder, width), READONLY, "data width"},
|
|
|
|
// mode:int
|
|
{"mode", T_UINT, offsetof(DataMessageEncoder, mode), 0, "ReSample Mode"},
|
|
|
|
// device:int
|
|
{"device", T_UINT, offsetof(DataMessageEncoder, device), 0, "ReSample device"},
|
|
|
|
// sample_rate:int
|
|
{"sample_rate", T_UINT, offsetof(DataMessageEncoder, sample_rate), 0, "sample rate"},
|
|
{NULL}};
|
|
|
|
// DataMessageEncoder __init__
|
|
static PyObject *DataMessageEncoder_new(PyTypeObject *type, PyObject *args, PyObject *keywords);
|
|
static int DataMessageEncoder_init(DataMessageEncoder *self, PyObject *args, PyObject *kwargs);
|
|
static void DataMessageEncoder_dealloc(DataMessageEncoder *self);
|
|
|
|
// DataMessageEncoder property
|
|
static PyGetSetDef DataMessageEncoderGetSet[] = { //
|
|
{NULL}};
|
|
|
|
// DataMessageEncoder methods
|
|
|
|
static PyObject *DataMessageEncoder_contain_channel(DataMessageEncoder *self, PyObject *args, PyObject *kwargs);
|
|
static PyObject *DataMessageEncoder_channels(DataMessageEncoder *self);
|
|
static PyObject *DataMessageEncoder_append_entry(DataMessageEncoder *self, PyObject *args, PyObject *kwargs);
|
|
static PyObject *DataMessageEncoder_append_data(DataMessageEncoder *self, PyObject *args, PyObject *kwargs);
|
|
static PyObject *DataMessageEncoder_append_file(DataMessageEncoder *self, PyObject *args, PyObject *kwargs);
|
|
static PyObject *DataMessageEncoder_message(DataMessageEncoder *self);
|
|
|
|
static PyMethodDef DataMessageEncoderMethods[] = { //
|
|
// contain_channel(self, device_id:int, channel:int) -> bool
|
|
{"contain_channel", (PyCFunction)DataMessageEncoder_contain_channel, METH_VARARGS | METH_KEYWORDS, "DataMessageEncoder.contain_channel"},
|
|
|
|
{"channels", (PyCFunction)DataMessageEncoder_channels, METH_NOARGS, "DataMessageEncoder.channels"},
|
|
|
|
// append_entry(self, time_stamp:float, device_id:int, channel:int, value:int) -> None
|
|
{"append_entry", (PyCFunction)DataMessageEncoder_append_entry, METH_VARARGS | METH_KEYWORDS, "DataMessageEncoder.append_entry"},
|
|
|
|
// append_data(self, data:RecordingData, time_start:float = 0, time_stop:float = inf) -> None
|
|
{"append_data", (PyCFunction)DataMessageEncoder_append_data, METH_VARARGS | METH_KEYWORDS, "DataMessageEncoder.append_data"},
|
|
|
|
// append_file(self, file:RecordingFileReader, time_start:float = 0, time_stop:float = inf) -> None
|
|
{"append_file", (PyCFunction)DataMessageEncoder_append_file, METH_VARARGS | METH_KEYWORDS, "DataMessageEncoder.append_file"},
|
|
|
|
// message(self) -> bytes
|
|
{"message", (PyCFunction)DataMessageEncoder_message, METH_NOARGS, "DataMessageEncoder.message"},
|
|
{NULL}};
|
|
|
|
static PyTypeObject DataMessageEncoderType = {
|
|
PyVarObject_HEAD_INIT(NULL, 0) //,
|
|
.tp_name = "DataMessageEncoder",
|
|
.tp_doc = "Data Message Encoder Base",
|
|
.tp_basicsize = sizeof(DataMessageEncoder),
|
|
.tp_itemsize = 0,
|
|
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
|
|
.tp_new = DataMessageEncoder_new,
|
|
// __init__(mode:int, sample_rate:int, channel:List[int])
|
|
.tp_init = (initproc)DataMessageEncoder_init,
|
|
.tp_dealloc = (destructor)DataMessageEncoder_dealloc,
|
|
.tp_members = DataMessageEncoderMembers,
|
|
.tp_getset = DataMessageEncoderGetSet,
|
|
.tp_methods = DataMessageEncoderMethods,
|
|
};
|
|
|
|
// class RecordingFileReader
|
|
|
|
typedef struct {
|
|
PyObject_HEAD;
|
|
|
|
// meta file
|
|
RecordingMeta meta;
|
|
|
|
// cache
|
|
unsigned int *last_time_stamp;
|
|
} RecordingFileReader;
|
|
|
|
static PyMemberDef RecordingFileReaderMembers[] = { //
|
|
{"version_number", T_UINT, offsetof(RecordingFileReader, meta.version_number), READONLY, "version_number"},
|
|
{"create_time", T_ULONG, offsetof(RecordingFileReader, meta.create_time), READONLY, "create_time"},
|
|
{"parameter_count", T_UINT, offsetof(RecordingFileReader, meta.parameter_count), READONLY, "parameter_count"},
|
|
{"recording_file_size", T_UINT, offsetof(RecordingFileReader, meta.recording_file_size), READONLY, "recording_file_size"},
|
|
{NULL}};
|
|
|
|
// RecordingFileReader __init__
|
|
static PyObject *RecordingFileReader_new(PyTypeObject *type, PyObject *args, PyObject *keywords);
|
|
static int RecordingFileReader_init(RecordingFileReader *self, PyObject *args, PyObject *kwargs);
|
|
static void RecordingFileReader_dealloc(RecordingFileReader *self);
|
|
|
|
// RecordingFileReader property
|
|
static PyObject *RecordingFileReader_file_path(RecordingFileReader *self, void *ignore);
|
|
static PyObject *RecordingFileReader_file_uuid(RecordingFileReader *self, void *ignore);
|
|
static PyObject *RecordingFileReader_data_format(RecordingFileReader *self, void *ignore);
|
|
static PyObject *RecordingFileReader_device_name(RecordingFileReader *self, void *ignore);
|
|
static PyObject *RecordingFileReader_device_address(RecordingFileReader *self, void *ignore);
|
|
static PyObject *RecordingFileReader_device_serial(RecordingFileReader *self, void *ignore);
|
|
|
|
static PyGetSetDef RecordingFileReaderGetSet[] = { //
|
|
{"file_path", (getter)RecordingFileReader_file_path, NULL, "", NULL},
|
|
{"file_uuid", (getter)RecordingFileReader_file_uuid, NULL, "", NULL},
|
|
{"data_format", (getter)RecordingFileReader_data_format, NULL, "", NULL},
|
|
{"device_name", (getter)RecordingFileReader_device_name, NULL, "", NULL},
|
|
{"device_address", (getter)RecordingFileReader_device_address, NULL, "", NULL},
|
|
{"device_serial", (getter)RecordingFileReader_device_serial, NULL, "", NULL},
|
|
{NULL}};
|
|
|
|
// RecordingFileReader methods
|
|
static PyObject *RecordingFileReader_recording_file_name(RecordingFileReader *self, PyObject *args, PyObject *kwargs);
|
|
static PyObject *RecordingFileReader_parameter(RecordingFileReader *self, PyObject *args, PyObject *kwargs);
|
|
|
|
static PyMethodDef RecordingFileReaderMethods[] = { //
|
|
// recording_file_name(self, file_index:int) -> bytes
|
|
{"recording_file_name", (PyCFunction)RecordingFileReader_recording_file_name, METH_VARARGS | METH_KEYWORDS, "RecordingFileReader.recording_file_name"},
|
|
|
|
// parameter(self, index:int) -> (name:str, value:Any)
|
|
{"parameter", (PyCFunction)RecordingFileReader_parameter, METH_VARARGS | METH_KEYWORDS, "RecordingFileReader.parameter"},
|
|
{NULL}};
|
|
|
|
static PyTypeObject RecordingFileReaderType = {
|
|
PyVarObject_HEAD_INIT(NULL, 0) //,
|
|
.tp_name = "RecordingFileReader",
|
|
.tp_doc = "Recording File Reader",
|
|
.tp_basicsize = sizeof(RecordingFileReader),
|
|
.tp_itemsize = 0,
|
|
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
|
|
.tp_new = RecordingFileReader_new,
|
|
.tp_init = (initproc)RecordingFileReader_init,
|
|
.tp_dealloc = (destructor)RecordingFileReader_dealloc,
|
|
.tp_members = RecordingFileReaderMembers,
|
|
.tp_getset = RecordingFileReaderGetSet,
|
|
.tp_methods = RecordingFileReaderMethods,
|
|
};
|
|
|
|
// function implement
|
|
|
|
static PyObject *DataMessageEncoder_new(PyTypeObject *type, PyObject *args, PyObject *keywords) {
|
|
DataMessageEncoder *self = (DataMessageEncoder *)type->tp_alloc(type, 0);
|
|
|
|
if (self != NULL) {
|
|
self->header = 0;
|
|
self->width = 2;
|
|
self->mode = SAMPLE_MODE_FIRST;
|
|
self->sample_rate = 1;
|
|
self->device = 0;
|
|
self->channel = NULL;
|
|
self->data = NULL;
|
|
}
|
|
|
|
return (PyObject *)self;
|
|
}
|
|
|
|
static int DataMessageEncoder_init(DataMessageEncoder *self, PyObject *args, PyObject *kwargs) {
|
|
static char *keywords[] = {"header", "mode", "sample_rate", "device", "channel", NULL};
|
|
|
|
unsigned int header = 0;
|
|
unsigned int mode = SAMPLE_MODE_FIRST;
|
|
unsigned int sample_rate = 1;
|
|
unsigned int device = 0;
|
|
PyObject * channel = NULL;
|
|
|
|
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "IIIIO", keywords, &header, &mode, &sample_rate, &device, &channel)) {
|
|
return -1;
|
|
}
|
|
|
|
if (sample_rate == 0) {
|
|
PyErr_SetString(PyExc_ValueError, "sample rate 0");
|
|
return -1;
|
|
}
|
|
|
|
IntSet *channel_mask = int_set_new();
|
|
if (channel_mask == NULL) {
|
|
PyErr_SetString(PyExc_RuntimeError, "init IntSet fail");
|
|
return -1;
|
|
}
|
|
|
|
if (channel != NULL && channel != Py_None) {
|
|
if (!int_set_add_list(channel_mask, channel)) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
self->header = header;
|
|
self->width = 2;
|
|
self->mode = mode;
|
|
self->sample_rate = sample_rate;
|
|
self->device = device;
|
|
self->channel = channel_mask;
|
|
|
|
unsigned int size = channel_mask->size;
|
|
|
|
self->data = (ReSampleEntryList *)PyMem_Malloc(size * sizeof(ReSampleEntryList));
|
|
if (self->data == NULL) {
|
|
PyErr_SetString(PyExc_RuntimeError, "PyMem_Malloc ReSampleEntryList fail");
|
|
return -1;
|
|
}
|
|
|
|
for (unsigned int i = 0; i < size; i++) {
|
|
ReSampleEntryList *list = self->data + i;
|
|
list->capacity = 0;
|
|
list->size = 0;
|
|
list->time_stamp = 0.0;
|
|
list->entries = NULL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void DataMessageEncoder_dealloc(DataMessageEncoder *self) {
|
|
if (self->data != NULL) {
|
|
for (unsigned int i = 0, limit = self->channel->size; i < limit; i++) {
|
|
ReSampleEntryList *list = self->data + i;
|
|
|
|
if (list->entries != NULL) {
|
|
PyMem_Free(list->entries);
|
|
list->entries = NULL;
|
|
}
|
|
}
|
|
PyMem_Free(self->data);
|
|
self->data = NULL;
|
|
}
|
|
|
|
Py_TYPE(self)->tp_free((PyObject *)self);
|
|
}
|
|
|
|
/**
|
|
* does this encoder need to record this device and this channel?
|
|
*
|
|
* @param self:
|
|
* @param channel: channel ID
|
|
* @return: bool
|
|
*/
|
|
static int DataMessageEncoder_contain_channel0(DataMessageEncoder *self, unsigned int channel) {
|
|
return int_set_contain(self->channel, channel);
|
|
}
|
|
|
|
static PyObject *DataMessageEncoder_channels(DataMessageEncoder *self) {
|
|
return int_set_list(self->channel);
|
|
}
|
|
|
|
/**
|
|
* does this encoder need to record this device and this channel?
|
|
*
|
|
* @param self:
|
|
* @return: bool or raise error.
|
|
*/
|
|
static PyObject *DataMessageEncoder_contain_channel(DataMessageEncoder *self, PyObject *args, PyObject *kwargs) {
|
|
static char *keywords[] = {"channel", NULL};
|
|
|
|
unsigned int channel = 0;
|
|
|
|
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "I", keywords, &channel)) {
|
|
return NULL;
|
|
}
|
|
|
|
if (DataMessageEncoder_contain_channel0(self, channel)) {
|
|
Py_RETURN_TRUE;
|
|
} else {
|
|
Py_RETURN_FALSE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* add value into entry with certain time stamp.
|
|
*
|
|
* @param self:
|
|
* @param time_stamp: time stamp of the data
|
|
* @param channel:
|
|
* @param value: data value
|
|
* @return: 1 on error
|
|
*/
|
|
static int DataMessageEncoder_append_entry0(DataMessageEncoder *self, double time_stamp, unsigned int channel, int value) {
|
|
int channel_index = int_set_indexof(self->channel, channel);
|
|
if (channel_index == -1) {
|
|
if (PyErr_Occurred()) {
|
|
return 1;
|
|
} else {
|
|
// skip
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// data width
|
|
if (-0x7FFF <= value && value < 0x7FFF) {
|
|
} else if (-0x7FFFFFFF <= value && value <= 0x7FFFFFFF) {
|
|
// for any value over range, change data_width to 4
|
|
self->width = 4;
|
|
}
|
|
|
|
double time_delta = 1000.0 / self->sample_rate;
|
|
ReSampleEntryList *list = self->data + channel_index;
|
|
ReSampleEntry * p = NULL;
|
|
|
|
if (list->size == 0) {
|
|
p = ReSampleEntryList_add(list);
|
|
if (p == NULL) return 1;
|
|
|
|
list->time_stamp = time_stamp;
|
|
p->time_step = 0;
|
|
|
|
} else {
|
|
if (self->mode == SAMPLE_MODE_RAW && list->size == 1) {
|
|
// raw data mode: recalculate sample rate
|
|
time_delta = time_stamp - list->time_stamp;
|
|
self->sample_rate = (unsigned int)(1000.0 / time_delta);
|
|
}
|
|
|
|
p = list->entries + list->size - 1;
|
|
|
|
if (list->time_stamp + p->time_step * time_delta + time_delta <= time_stamp) {
|
|
p = ReSampleEntryList_add(list);
|
|
if (p == NULL) return 1;
|
|
|
|
p->time_step = (unsigned int)((time_stamp - list->time_stamp) / time_delta);
|
|
}
|
|
}
|
|
|
|
ReSampleEntry_add_value(self->mode, p, value);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static PyObject *DataMessageEncoder_append_entry(DataMessageEncoder *self, PyObject *args, PyObject *kwargs) {
|
|
static char *keywords[] = {"time_stamp", "channel", "value", NULL};
|
|
|
|
double time_stamp = 0.0;
|
|
unsigned int channel = 0;
|
|
int value = 0;
|
|
|
|
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "dIi", keywords, &time_stamp, &channel, &value)) {
|
|
return NULL;
|
|
}
|
|
|
|
if (DataMessageEncoder_append_entry0(self, time_stamp, channel, value)) {
|
|
return NULL;
|
|
} else {
|
|
Py_RETURN_NONE;
|
|
}
|
|
}
|
|
|
|
static PyObject *DataMessageEncoder_append_data(DataMessageEncoder *self, PyObject *args, PyObject *kwargs) {
|
|
static char *keywords[] = {"data", "time_start", "time_stop", NULL};
|
|
|
|
PyObject *data = NULL;
|
|
double time_start = 0.0;
|
|
double time_stop = INFINITY;
|
|
|
|
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|dd", keywords, &data, &time_start, &time_stop)) {
|
|
return NULL;
|
|
}
|
|
|
|
PyObject *iter = ({
|
|
PyObject *result = PyObject_CallMethod(data, "entry_iter", "(dd)", time_start, time_stop);
|
|
|
|
if (result == NULL) return NULL;
|
|
Py_INCREF(result);
|
|
|
|
PyObject *tmp = PyObject_GetIter(result);
|
|
|
|
// iter may equal to result, so do this first
|
|
Py_XINCREF(tmp);
|
|
// then do this
|
|
Py_DECREF(result);
|
|
|
|
if (tmp == NULL) return NULL;
|
|
|
|
tmp;
|
|
});
|
|
|
|
PyObject *item;
|
|
|
|
while ((item = PyIter_Next(iter))) {
|
|
double time_stamp = 0.0;
|
|
unsigned int device = 0;
|
|
unsigned int channel = 0;
|
|
int value = 0;
|
|
|
|
if (!PyArg_ParseTuple(item, "dIIi", &time_stamp, &device, &channel, &value)) {
|
|
break;
|
|
}
|
|
|
|
if (device == self->device) {
|
|
if (DataMessageEncoder_append_entry0(self, time_stamp, channel, value)) {
|
|
// error
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
Py_CLEAR(iter);
|
|
|
|
if (PyErr_Occurred()) {
|
|
return NULL;
|
|
} else {
|
|
Py_RETURN_NONE;
|
|
}
|
|
}
|
|
|
|
typedef struct {
|
|
DataMessageEncoder * encoder;
|
|
RecordingFileReader *reader;
|
|
double time_start;
|
|
double time_stop;
|
|
double time_stamp;
|
|
} DataMessageEncoderAppendFileData;
|
|
|
|
/**
|
|
* RecordingDataInitFunction
|
|
* @return: 1 on fail, 0 for success, -1 for skip
|
|
*/
|
|
static int DataMessageEncoder_append_file_init(RecordingDataDecodeState *state, //
|
|
void * _data) {
|
|
if (_data == NULL) {
|
|
ensure_error(PyErr_SetString(PyExc_RuntimeError, "DataMessageEncoder_append_file_init(_data=NULL)"));
|
|
return 1;
|
|
}
|
|
|
|
DataMessageEncoderAppendFileData *data = (DataMessageEncoderAppendFileData *)_data;
|
|
|
|
double time_stamp = (double)state->time_stamp;
|
|
data->time_stamp = time_stamp;
|
|
|
|
if (time_stamp < data->time_stop) {
|
|
return 0;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* RecordingDataAppendFunction
|
|
* @return: 1 on fail
|
|
*/
|
|
static int DataMessageEncoder_append_file_append(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, "DataMessageEncoder_append_file_append(_data=NULL)"));
|
|
return 1;
|
|
}
|
|
|
|
if (value == NULL) {
|
|
// ignore invalid data
|
|
return 0;
|
|
}
|
|
|
|
DataMessageEncoderAppendFileData *data = (DataMessageEncoderAppendFileData *)_data;
|
|
|
|
double time_stamp;
|
|
|
|
if (state->sample_rate == 0) {
|
|
time_stamp = state->time_stamp;
|
|
} else {
|
|
time_stamp = state->time_stamp + data_index * 1000.0 / state->sample_rate;
|
|
}
|
|
|
|
data->time_stamp = time_stamp;
|
|
|
|
// check time stamp in request range
|
|
if (data->time_start <= time_stamp && time_stamp < data->time_stop) {
|
|
// append entry
|
|
if (DataMessageEncoder_append_entry0(data->encoder, time_stamp, channel, *value)) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static PyObject *DataMessageEncoder_append_file(DataMessageEncoder *self, PyObject *args, PyObject *kwargs) {
|
|
static char *keywords[] = {"file", "time_start", "time_stop", NULL};
|
|
|
|
PyObject *file_ref = NULL;
|
|
double time_start = 0.0;
|
|
double time_stop = INFINITY;
|
|
|
|
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O|dd", keywords, &file_ref, &time_start, &time_stop)) {
|
|
return NULL;
|
|
}
|
|
|
|
int instance_test = PyObject_IsInstance(file_ref, (PyObject *)&RecordingFileReaderType);
|
|
|
|
if (instance_test == -1) {
|
|
return NULL;
|
|
} else if (instance_test == 0) {
|
|
PyErr_SetString(PyExc_TypeError, "file not a RecordingFileReader");
|
|
return NULL;
|
|
}
|
|
|
|
RecordingFileReader *reader = (RecordingFileReader *)file_ref;
|
|
|
|
DataMessageEncoderAppendFileData data = {
|
|
// clang-format off
|
|
.encoder = self,
|
|
.reader = reader,
|
|
.time_start = time_start,
|
|
.time_stop = time_stop
|
|
// clang-format on
|
|
};
|
|
|
|
for (unsigned int file_index = 0; file_index < reader->meta.recording_file_size; file_index++) {
|
|
unsigned int last_time_stamp = reader->last_time_stamp[file_index];
|
|
if (last_time_stamp > 0 && last_time_stamp < time_start) {
|
|
continue;
|
|
}
|
|
|
|
RecordingFile *recording_file = ensure_recording_file(&reader->meta, file_index);
|
|
|
|
if (recording_file == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
if (recording_file->recording_file == NULL) {
|
|
PyErr_SetString(PyExc_RuntimeError, "recording file not open");
|
|
return NULL;
|
|
}
|
|
|
|
if (!file_seek_abs(recording_file->recording_file, 37, "seek recording file")) {
|
|
return NULL;
|
|
}
|
|
|
|
if (decode_recording_file_ALL(recording_file->recording_file, //
|
|
&DataMessageEncoder_append_file_init,
|
|
&DataMessageEncoder_append_file_append,
|
|
&data)) {
|
|
return NULL;
|
|
}
|
|
|
|
// update last time stamp
|
|
reader->last_time_stamp[file_index] = (unsigned int)data.time_stamp;
|
|
|
|
if (time_stop <= data.time_stamp) break;
|
|
}
|
|
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
/**
|
|
* encode message into cache list.
|
|
*
|
|
* @param self:
|
|
* @param list: data cache list.
|
|
* @return data list, NULL if error
|
|
*/
|
|
static PyObject *DataMessageEncoder_message0(DataMessageEncoder *self, PyObject *list) {
|
|
unsigned int SIZE = 128;
|
|
unsigned int offset = 0;
|
|
char buf[SIZE];
|
|
|
|
#define _append() \
|
|
do { \
|
|
if (offset > 0) { \
|
|
PyObject *x = PyBytes_FromStringAndSize(buf, offset); \
|
|
if (x == NULL) return NULL; \
|
|
PyList_Append(list, x); \
|
|
offset = 0; \
|
|
} \
|
|
} while (0)
|
|
|
|
// unsigned 8 bits int, little endian
|
|
#define _u8(a) \
|
|
do { \
|
|
buf[offset++] = (char)(a); \
|
|
} while (0)
|
|
|
|
// unsigned 16 bits int, little endian
|
|
#define _u16(a) \
|
|
do { \
|
|
unsigned int __a = (unsigned int)(a); \
|
|
buf[offset++] = (char)(__a & 0xFF); \
|
|
buf[offset++] = (char)((__a >> 8) & 0xFF); \
|
|
} while (0)
|
|
|
|
// signed 16 bits int, little endian
|
|
#define _i16(a) \
|
|
do { \
|
|
unsigned int __a = (unsigned int)(a + 0x8000); \
|
|
buf[offset++] = (char)(__a & 0xFF); \
|
|
buf[offset++] = (char)((__a >> 8) & 0xFF); \
|
|
} while (0)
|
|
|
|
// unsigned 32 bits int, little endian
|
|
#define _u32(a) \
|
|
do { \
|
|
unsigned int __a = (unsigned int)(a); \
|
|
buf[offset++] = (char)(__a & 0xFF); \
|
|
buf[offset++] = (char)((__a >> 8) & 0xFF); \
|
|
buf[offset++] = (char)((__a >> 16) & 0xFF); \
|
|
buf[offset++] = (char)((__a >> 24) & 0xFF); \
|
|
} while (0)
|
|
|
|
// unsigned 32 bits int, little endian
|
|
#define _i32(a) \
|
|
do { \
|
|
unsigned int __a = (unsigned int)(a + 0x80000000); \
|
|
buf[offset++] = (char)(__a & 0xFF); \
|
|
buf[offset++] = (char)((__a >> 8) & 0xFF); \
|
|
buf[offset++] = (char)((__a >> 16) & 0xFF); \
|
|
buf[offset++] = (char)((__a >> 24) & 0xFF); \
|
|
} while (0)
|
|
|
|
// HEADER_DATA
|
|
// header for data message
|
|
_u8(self->header);
|
|
_u8(self->device);
|
|
_u16(self->sample_rate);
|
|
|
|
// channel count
|
|
unsigned int channel_count = 0;
|
|
for (unsigned int channel_index = 0, channel_limit = self->channel->size; channel_index < channel_limit; channel_index++) {
|
|
ReSampleEntryList *entries = self->data + channel_index;
|
|
if (entries->size > 0) {
|
|
channel_count++;
|
|
}
|
|
}
|
|
_u8(channel_count);
|
|
|
|
// channel data
|
|
for (unsigned int channel_index = 0, channel_limit = self->channel->size; channel_index < channel_limit; channel_index++) {
|
|
ReSampleEntryList *entries = self->data + channel_index;
|
|
if (entries->size == 0) continue;
|
|
|
|
_u8(self->channel->storage[channel_index]);
|
|
_u8(((self->width << 4) & 0xF0) | (self->mode & 0x0F));
|
|
_u16(entries->size);
|
|
_u32(entries->time_stamp);
|
|
|
|
for (unsigned int e = 0; e < entries->size; e++) {
|
|
ReSampleEntry *p = entries->entries + e;
|
|
double value = 0.0;
|
|
|
|
// we should make sure that evey entries has validate data
|
|
ReSampleEntry_value(self->mode, p, &value);
|
|
|
|
_u8(p->time_step);
|
|
if (self->width > 2) {
|
|
_i32(value);
|
|
} else {
|
|
_i16(value);
|
|
}
|
|
|
|
if (offset >= 120) _append();
|
|
}
|
|
|
|
if (offset >= 100) _append();
|
|
}
|
|
|
|
_append();
|
|
|
|
return list;
|
|
|
|
#undef _u8
|
|
#undef _u16
|
|
#undef _i16
|
|
#undef _u32
|
|
#undef _append
|
|
}
|
|
|
|
static PyObject *DataMessageEncoder_message(DataMessageEncoder *self) {
|
|
if (self->channel == 0) {
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
unsigned int sum = 0;
|
|
|
|
for (unsigned int i = 0, limit = self->channel->size; i < limit; i++) {
|
|
sum += (self->data + i)->size;
|
|
}
|
|
|
|
if (sum == 0) {
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
PyObject *ret = NULL;
|
|
PyObject *list = PyList_New(0);
|
|
if (list == NULL) return NULL;
|
|
|
|
if (DataMessageEncoder_message0(self, list) != NULL) {
|
|
PyObject *empty = PyBytes_FromString("");
|
|
ret = PyObject_CallMethod(empty, "join", "(O)", list);
|
|
Py_DECREF(empty);
|
|
}
|
|
|
|
Py_DECREF(list);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static PyObject *RecordingFileReader_new(PyTypeObject *type, PyObject *args, PyObject *keywords) {
|
|
RecordingFileReader *self = (RecordingFileReader *)type->tp_alloc(type, 0);
|
|
|
|
if (self != NULL) {
|
|
memset(&self->meta, 0, sizeof(RecordingMeta));
|
|
self->last_time_stamp = NULL;
|
|
}
|
|
|
|
return (PyObject *)self;
|
|
}
|
|
|
|
static int RecordingFileReader_init(RecordingFileReader *self, PyObject *args, PyObject *kwargs) {
|
|
static char *keywords[] = {"meta_file", NULL};
|
|
|
|
PyObject *meta_file_ref = NULL;
|
|
|
|
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O", keywords, &meta_file_ref)) return -1;
|
|
|
|
if (meta_file_ref == NULL) return -1;
|
|
|
|
char *meta_file = PyBytes_AsString(meta_file_ref);
|
|
|
|
if (meta_file == NULL) return -1;
|
|
|
|
strcpy(self->meta.file_path, meta_file);
|
|
|
|
if (open_recording_meta(&self->meta)) {
|
|
free_recording_meta(&self->meta);
|
|
return -1;
|
|
}
|
|
|
|
self->last_time_stamp = (unsigned int *)PyMem_Calloc(self->meta.recording_file_size, sizeof(unsigned int));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void RecordingFileReader_dealloc(RecordingFileReader *self) {
|
|
free_recording_meta0(&self->meta);
|
|
|
|
if (self->last_time_stamp != NULL) {
|
|
PyMem_Free(self->last_time_stamp);
|
|
self->last_time_stamp = NULL;
|
|
}
|
|
|
|
Py_TYPE(self)->tp_free((PyObject *)self);
|
|
}
|
|
|
|
static PyObject *RecordingFileReader_file_path(RecordingFileReader *self, void *ignore) {
|
|
return PyBytes_FromString(self->meta.file_path);
|
|
}
|
|
|
|
static PyObject *RecordingFileReader_file_uuid(RecordingFileReader *self, void *ignore) {
|
|
return PyBytes_FromStringAndSize(self->meta.file_uuid, 16);
|
|
}
|
|
|
|
static PyObject *RecordingFileReader_data_format(RecordingFileReader *self, void *ignore) {
|
|
return PyBytes_FromStringAndSize(self->meta.data_format, DATA_FORMAT_SIZE);
|
|
}
|
|
|
|
static PyObject *RecordingFileReader_device_name(RecordingFileReader *self, void *ignore) {
|
|
return PyBytes_FromString(self->meta.device_name);
|
|
}
|
|
|
|
static PyObject *RecordingFileReader_device_address(RecordingFileReader *self, void *ignore) {
|
|
return PyBytes_FromStringAndSize(self->meta.device_address, 6);
|
|
}
|
|
|
|
static PyObject *RecordingFileReader_device_serial(RecordingFileReader *self, void *ignore) {
|
|
return PyBytes_FromStringAndSize(self->meta.device_serial, 6);
|
|
}
|
|
|
|
static PyObject *RecordingFileReader_recording_file_name(RecordingFileReader *self, PyObject *args, PyObject *kwargs) {
|
|
static char *keywords[] = {"file_index", NULL};
|
|
|
|
unsigned int file_index = 0;
|
|
|
|
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "I", keywords, &file_index)) return NULL;
|
|
|
|
if (self->meta.recording_file_size <= file_index) {
|
|
PyErr_Format(PyExc_IndexError, "out of recording file array %d", file_index);
|
|
return NULL;
|
|
}
|
|
|
|
return PyBytes_FromString((self->meta.recording_file + file_index)->filename);
|
|
}
|
|
|
|
static PyObject *RecordingFileReader_parameter1(FILE *file, RecordingParameter *parameter) {
|
|
char buf[64];
|
|
|
|
switch (parameter->parameter_type) {
|
|
case '?':
|
|
if (fgetc(file)) {
|
|
Py_RETURN_FALSE;
|
|
} else {
|
|
Py_RETURN_TRUE;
|
|
}
|
|
break;
|
|
case 'p':
|
|
file_read_string(file, buf, "parameter value");
|
|
return PyBytes_FromString(buf);
|
|
case 'b':
|
|
case 'B':
|
|
file_read(file, buf, 1, "parameter value");
|
|
if (parameter->parameter_type == 'B') {
|
|
return PyLong_FromLong(buf_u8(buf));
|
|
} else {
|
|
return PyLong_FromLong(buf_i8(buf));
|
|
}
|
|
|
|
case 'h':
|
|
case 'H':
|
|
file_read(file, buf, 2, "parameter value");
|
|
if (parameter->parameter_type == 'H') {
|
|
return PyLong_FromLong(buf_u16B(buf));
|
|
} else {
|
|
return PyLong_FromLong(buf_i16B(buf));
|
|
}
|
|
case 'i':
|
|
case 'I':
|
|
file_read(file, buf, 4, "parameter value");
|
|
if (parameter->parameter_type == 'I') {
|
|
return PyLong_FromLong(buf_u32B(buf));
|
|
} else {
|
|
return PyLong_FromLong(buf_i32B(buf));
|
|
}
|
|
case 'f':
|
|
file_read(file, buf, 4, "parameter value");
|
|
return PyFloat_FromDouble((double)buf_f32B(buf));
|
|
default:
|
|
PyErr_SetString(PyExc_RuntimeError, "illegal parameter type");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
static PyObject *RecordingFileReader_parameter0(FILE *file, RecordingParameter *parameter) {
|
|
if (!file_seek_abs(file, parameter->value_seek_pos, "parameter value")) return NULL;
|
|
|
|
if (parameter->parameter_type == 0) {
|
|
Py_RETURN_NONE;
|
|
} else if (parameter->collection_type == '0') {
|
|
return RecordingFileReader_parameter1(file, parameter);
|
|
|
|
} else if (parameter->collection_type == 'L' || parameter->collection_type == 'S') {
|
|
PyObject *list = PyList_New(parameter->data_length);
|
|
|
|
for (unsigned int i = 0; i < parameter->data_length; i++) {
|
|
PyObject *ref = RecordingFileReader_parameter1(file, parameter);
|
|
|
|
if (ref == NULL) {
|
|
Py_CLEAR(list);
|
|
break;
|
|
} else {
|
|
PyList_SET_ITEM(list, i, ref);
|
|
}
|
|
}
|
|
|
|
if (list == NULL) {
|
|
return NULL;
|
|
} else if (parameter->collection_type == 'S') {
|
|
return PySet_New(list);
|
|
} else {
|
|
return list;
|
|
}
|
|
} else {
|
|
PyErr_SetString(PyExc_RuntimeError, "illegal collection type");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
static PyObject *RecordingFileReader_parameter(RecordingFileReader *self, PyObject *args, PyObject *kwargs) {
|
|
static char *keywords[] = {"index", NULL};
|
|
|
|
unsigned int index = 0;
|
|
|
|
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "I", keywords, &index)) return NULL;
|
|
|
|
if (self->meta.parameter_count <= index) {
|
|
PyErr_Format(PyExc_IndexError, "out of recording parameter array %d", index);
|
|
return NULL;
|
|
}
|
|
|
|
if (self->meta.meta_file == NULL) {
|
|
PyErr_SetString(PyExc_RuntimeError, "meta file closed");
|
|
return NULL;
|
|
}
|
|
|
|
char name[64];
|
|
|
|
RecordingMeta * meta = &self->meta;
|
|
RecordingParameter *parameter = meta->parameter + index;
|
|
|
|
// parameter name
|
|
if (!file_seek_abs(meta->meta_file, parameter->name_seek_pos, "parameter name")) return NULL;
|
|
if (!file_read_string(meta->meta_file, name, "parameter name")) return NULL;
|
|
|
|
// parameter value
|
|
PyObject *parameter_value = RecordingFileReader_parameter0(meta->meta_file, parameter);
|
|
if (parameter_value == NULL) return NULL;
|
|
|
|
return Py_BuildValue("(sO)", name, parameter_value);
|
|
}
|
|
|
|
// module init
|
|
|
|
static PyMethodDef moduleMethods[] = { //
|
|
{NULL, NULL, 0, NULL}};
|
|
|
|
static struct PyModuleDef MessageModule = { //
|
|
PyModuleDef_HEAD_INIT,
|
|
// module name
|
|
"message",
|
|
// module doc
|
|
NULL,
|
|
-1,
|
|
moduleMethods};
|
|
|
|
// module export
|
|
|
|
PyMODINIT_FUNC PyInit_message(void) {
|
|
PyObject *module;
|
|
|
|
module = PyModule_Create(&MessageModule);
|
|
if (module == NULL) return NULL;
|
|
|
|
if (PyType_Ready(&DataMessageEncoderType) < 0) return NULL;
|
|
|
|
Py_INCREF(&DataMessageEncoderType);
|
|
PyModule_AddObject(module, "DataMessageEncoder", (PyObject *)&DataMessageEncoderType);
|
|
|
|
if (PyType_Ready(&RecordingFileReaderType) < 0) return NULL;
|
|
|
|
Py_INCREF(&RecordingFileReaderType);
|
|
PyModule_AddObject(module, "RecordingFileReader", (PyObject *)&RecordingFileReaderType);
|
|
|
|
return module;
|
|
} |