mirror of
https://gitlab.com/pholy/OSCAR-code.git
synced 2025-04-16 10:40:45 +00:00
First pass at actual F3V6 (1030X, 1130X) summary and settings support based on sample data. Events are still broken.
This commit is contained in:
parent
872fe74008
commit
9e54b98cf6
@ -3558,6 +3558,291 @@ bool PRS1DataChunk::ParseSummaryF3V3(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Originally based on ParseSummaryF5V3, with changes observed in ventilator sample data
|
||||||
|
//
|
||||||
|
// TODO: surely there will be a way to merge ParseSummary (FV3) loops and abstract the machine-specific
|
||||||
|
// encodings into another function or class, but that's probably worth pursuing only after
|
||||||
|
// the details have been figured out.
|
||||||
|
bool PRS1DataChunk::ParseSummaryF3V6(void)
|
||||||
|
{
|
||||||
|
if (this->family != 3 || this->familyVersion != 6) {
|
||||||
|
qWarning() << "ParseSummaryF3V6 called with family" << this->family << "familyVersion" << this->familyVersion;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const unsigned char * data = (unsigned char *)this->m_data.constData();
|
||||||
|
int chunk_size = this->m_data.size();
|
||||||
|
static const int minimum_sizes[] = { 1, 0x2e, 9, 7, 4, 2, 1, 2, 2, 1, 0x18, 2, 4 }; // F5V3 = { 1, 0x38, 4, 2, 4, 0x1e, 2, 4, 9 };
|
||||||
|
static const int ncodes = sizeof(minimum_sizes) / sizeof(int);
|
||||||
|
// NOTE: The sizes contained in hblock can vary, even within a single machine, as can the length of hblock itself!
|
||||||
|
|
||||||
|
// TODO: hardcoding this is ugly, think of a better approach
|
||||||
|
if (chunk_size < minimum_sizes[0] + minimum_sizes[1] + minimum_sizes[2]) {
|
||||||
|
qWarning() << this->sessionid << "summary data too short:" << chunk_size;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// We've once seen a short summary with no mask-on/off: just equipment-on, settings, 9, equipment-off
|
||||||
|
if (chunk_size < 75) UNEXPECTED_VALUE(chunk_size, ">= 75");
|
||||||
|
|
||||||
|
bool ok = true;
|
||||||
|
int pos = 0;
|
||||||
|
int code, size;
|
||||||
|
int tt = 0;
|
||||||
|
do {
|
||||||
|
code = data[pos++];
|
||||||
|
if (!this->hblock.contains(code)) {
|
||||||
|
qWarning() << this->sessionid << "missing hblock entry for" << code;
|
||||||
|
ok = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
size = this->hblock[code];
|
||||||
|
if (code < ncodes) {
|
||||||
|
// make sure the handlers below don't go past the end of the buffer
|
||||||
|
if (size < minimum_sizes[code]) {
|
||||||
|
qWarning() << this->sessionid << "slice" << code << "too small" << size << "<" << minimum_sizes[code];
|
||||||
|
ok = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} // else if it's past ncodes, we'll log its information below (rather than handle it)
|
||||||
|
if (pos + size > chunk_size) {
|
||||||
|
qWarning() << this->sessionid << "slice" << code << "@" << pos << "longer than remaining chunk";
|
||||||
|
ok = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (code) {
|
||||||
|
case 0: // Equipment On
|
||||||
|
CHECK_VALUE(pos, 1); // Always first?
|
||||||
|
//CHECK_VALUE(data[pos], 0x10); // usually 0x10 for 1030X, sometimes 0x40 or 0x80 are set in addition or instead
|
||||||
|
CHECK_VALUE(size, 1);
|
||||||
|
break;
|
||||||
|
case 1: // Settings
|
||||||
|
ok = this->ParseSettingsF3V6(data + pos, size);
|
||||||
|
break;
|
||||||
|
case 2: // seems equivalent to F5V3 #9, comes right after settings, 9 bytes, identical values
|
||||||
|
CHECK_VALUE(data[pos], 0);
|
||||||
|
CHECK_VALUE(data[pos+1], 1);
|
||||||
|
CHECK_VALUE(data[pos+2], 0);
|
||||||
|
CHECK_VALUE(data[pos+3], 1);
|
||||||
|
CHECK_VALUE(data[pos+4], 1);
|
||||||
|
CHECK_VALUE(data[pos+5], 0);
|
||||||
|
CHECK_VALUE(data[pos+6], 2);
|
||||||
|
CHECK_VALUE(data[pos+7], 1);
|
||||||
|
CHECK_VALUES(data[pos+8], 0, 1); // 1 = patient disconnect alarm of 15 sec on F5V3, not sure where time is encoded
|
||||||
|
break;
|
||||||
|
case 4: // Mask On
|
||||||
|
tt += data[pos] | (data[pos+1] << 8);
|
||||||
|
this->AddEvent(new PRS1ParsedSliceEvent(tt, MaskOn));
|
||||||
|
this->ParseHumidifierSettingV3(data[pos+2], data[pos+3]);
|
||||||
|
break;
|
||||||
|
case 5: // Mask Off
|
||||||
|
tt += data[pos] | (data[pos+1] << 8);
|
||||||
|
this->AddEvent(new PRS1ParsedSliceEvent(tt, MaskOff));
|
||||||
|
break;
|
||||||
|
case 7: // Ventilator EPAP stats, presumably per mask-on slice
|
||||||
|
//CHECK_VALUE(data[pos], 0x69); // Average EPAP
|
||||||
|
//CHECK_VALUE(data[pos+1], 0x80); // Average 90% EPAP
|
||||||
|
break;
|
||||||
|
case 8: // Ventilator IPAP stats, presumably per mask-on slice
|
||||||
|
//CHECK_VALUE(data[pos], 0x86); // Average IPAP
|
||||||
|
//CHECK_VALUE(data[pos+1], 0xA8); // Average 90% IPAP
|
||||||
|
break;
|
||||||
|
case 0xa: // Patient statistics, presumably per mask-on slice
|
||||||
|
//CHECK_VALUE(data[pos], 0x00); // 16-bit OA count
|
||||||
|
CHECK_VALUE(data[pos+1], 0x00);
|
||||||
|
//CHECK_VALUE(data[pos+2], 0x00); // 16-bit CA count
|
||||||
|
CHECK_VALUE(data[pos+3], 0x00);
|
||||||
|
//CHECK_VALUE(data[pos+4], 0x00); // 16-bit minutes in LL
|
||||||
|
CHECK_VALUE(data[pos+5], 0x00);
|
||||||
|
//CHECK_VALUE(data[pos+6], 0x0A); // 16-bit VS count
|
||||||
|
CHECK_VALUE(data[pos+7], 0x00);
|
||||||
|
//CHECK_VALUE(data[pos+8], 0x01); // 16-bit H count (partial)
|
||||||
|
CHECK_VALUE(data[pos+9], 0x00);
|
||||||
|
//CHECK_VALUE(data[pos+0xa], 0x00); // 16-bit H count (partial)
|
||||||
|
CHECK_VALUE(data[pos+0xb], 0x00);
|
||||||
|
//CHECK_VALUE(data[pos+0xc], 0x00); // 16-bit RE count
|
||||||
|
CHECK_VALUE(data[pos+0xd], 0x00);
|
||||||
|
//CHECK_VALUE(data[pos+0xe], 0x3e); // average total leak
|
||||||
|
//CHECK_VALUE(data[pos+0xf], 0x03); // 16-bit H count (partial)
|
||||||
|
CHECK_VALUE(data[pos+0x10], 0x00);
|
||||||
|
//CHECK_VALUE(data[pos+0x11], 0x11); // average breath rate
|
||||||
|
//CHECK_VALUE(data[pos+0x12], 0x41); // average TV / 10
|
||||||
|
//CHECK_VALUE(data[pos+0x13], 0x60); // average % PTB
|
||||||
|
//CHECK_VALUE(data[pos+0x14], 0x0b); // average minute vent
|
||||||
|
//CHECK_VALUE(data[pos+0x15], 0x1d); // average leak? (similar position to F5V3, similar delta to total leak)
|
||||||
|
//CHECK_VALUE(data[pos+0x16], 0x00); // 16-bit minutes in PB
|
||||||
|
CHECK_VALUE(data[pos+0x17], 0x00);
|
||||||
|
break;
|
||||||
|
case 3: // Equipment Off
|
||||||
|
tt += data[pos] | (data[pos+1] << 8);
|
||||||
|
this->AddEvent(new PRS1ParsedSliceEvent(tt, EquipmentOff));
|
||||||
|
//CHECK_VALUES(data[pos+2], 1, 4); // bitmask, have seen 1, 4, 6, 0x41
|
||||||
|
//CHECK_VALUE(data[pos+3], 0x17); // 0x16, etc.
|
||||||
|
CHECK_VALUES(data[pos+4], 0, 1);
|
||||||
|
//CHECK_VALUE(data[pos+5], 0x15); // 0x16, etc.
|
||||||
|
//CHECK_VALUES(data[pos+6], 0, 1); // or 2
|
||||||
|
break;
|
||||||
|
case 0xc: // Humidier setting change
|
||||||
|
tt += data[pos] | (data[pos+1] << 8); // This adds to the total duration (otherwise it won't match report)
|
||||||
|
this->ParseHumidifierSettingV3(data[pos+2], data[pos+3]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
UNEXPECTED_VALUE(code, "known slice code");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pos += size;
|
||||||
|
} while (ok && pos < chunk_size);
|
||||||
|
|
||||||
|
this->duration = tt;
|
||||||
|
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Based initially on ParseSettingsF5V3. Many of the codes look the same, like always starting with 0, 0x35 looking like
|
||||||
|
// a humidifier setting, etc., but the contents are sometimes a bit different, such as mode values and pressure settings.
|
||||||
|
//
|
||||||
|
// new settings to find: ...
|
||||||
|
bool PRS1DataChunk::ParseSettingsF3V6(const unsigned char* data, int size)
|
||||||
|
{
|
||||||
|
static const QMap<int,int> expected_lengths = { {0x1e,3}, {0x35,2} };
|
||||||
|
bool ok = true;
|
||||||
|
|
||||||
|
CPAPMode cpapmode = MODE_UNKNOWN;
|
||||||
|
|
||||||
|
// F5V3 and F3V6 use a gain of 0.125 rather than 0.1 to allow for a maximum value of 30 cmH2O
|
||||||
|
static const float GAIN = 0.125; // TODO: parameterize this somewhere better
|
||||||
|
|
||||||
|
int fixed_epap = 0;
|
||||||
|
int fixed_ipap = 0;
|
||||||
|
int min_ipap = 0;
|
||||||
|
int max_ipap = 0;
|
||||||
|
|
||||||
|
// Parse the nested data structure which contains settings
|
||||||
|
int pos = 0;
|
||||||
|
do {
|
||||||
|
int code = data[pos++];
|
||||||
|
int len = data[pos++];
|
||||||
|
|
||||||
|
int expected_len = 1;
|
||||||
|
if (expected_lengths.contains(code)) {
|
||||||
|
expected_len = expected_lengths[code];
|
||||||
|
}
|
||||||
|
//CHECK_VALUE(len, expected_len);
|
||||||
|
if (len < expected_len) {
|
||||||
|
qWarning() << this->sessionid << "setting" << code << "too small" << len << "<" << expected_len;
|
||||||
|
ok = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (pos + len > size) {
|
||||||
|
qWarning() << this->sessionid << "setting" << code << "@" << pos << "longer than remaining slice";
|
||||||
|
ok = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (code) {
|
||||||
|
case 0: // Device Mode
|
||||||
|
CHECK_VALUE(pos, 2); // always first?
|
||||||
|
// TODO: We may need additional enums for these modes, the below are just a rough guess mapping for now.
|
||||||
|
switch (data[pos]) {
|
||||||
|
case 1: cpapmode = MODE_BILEVEL_FIXED; break; // TODO This is marked "S - Bi-Flex" on reports.
|
||||||
|
case 2: cpapmode = MODE_ASV; break; // TODO: This is marked as "S/T" on reports, is that spontaneous/timed? Pressure also seems variable!
|
||||||
|
case 4: cpapmode = MODE_AVAPS; break; // "PC - AVAPS" on reports
|
||||||
|
default:
|
||||||
|
UNEXPECTED_VALUE(data[pos], "known device mode");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_CPAP_MODE, (int) cpapmode));
|
||||||
|
break;
|
||||||
|
case 1: // ???
|
||||||
|
if (cpapmode == MODE_AVAPS) {
|
||||||
|
CHECK_VALUE(data[pos], 2); // 2 when in AVAPS
|
||||||
|
} else {
|
||||||
|
CHECK_VALUES(data[pos], 0, 1); // 1 when in S - Bi-Flex, 0 when in S/T
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2: // ???
|
||||||
|
CHECK_VALUE(data[pos], 0);
|
||||||
|
break;
|
||||||
|
case 4: // EPAP Pressure
|
||||||
|
// pressures seem variable on practice, maybe due to ramp or leaks?
|
||||||
|
fixed_epap = data[pos];
|
||||||
|
this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_EPAP, fixed_epap, GAIN));
|
||||||
|
break;
|
||||||
|
case 7: // IPAP Pressure
|
||||||
|
// pressures seem variable on practice, maybe due to ramp or leaks?
|
||||||
|
fixed_ipap = data[pos];
|
||||||
|
this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP, fixed_ipap, GAIN));
|
||||||
|
break;
|
||||||
|
case 8: // Min IPAP
|
||||||
|
min_ipap = data[pos];
|
||||||
|
this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP_MIN, min_ipap, GAIN));
|
||||||
|
break;
|
||||||
|
case 9: // Max IPAP
|
||||||
|
max_ipap = data[pos];
|
||||||
|
this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP_MAX, max_ipap, GAIN));
|
||||||
|
break;
|
||||||
|
case 0x19: // Tidal Volume (AVAPS)
|
||||||
|
CHECK_VALUE(data[pos], 47); // gain 10.0
|
||||||
|
break;
|
||||||
|
case 0x1e: // Backup rate (S/T and AVAPS)
|
||||||
|
CHECK_VALUES(cpapmode, MODE_ASV, MODE_AVAPS);
|
||||||
|
// TODO: Does mode breath rate off mean this is essentially bilevel? The pressure graphs are confusing.
|
||||||
|
CHECK_VALUES(data[pos], 0, 2); // 0 = Breath Rate off (S), 2 = fixed BPM (1 = auto on F5V3 setting 0x14)
|
||||||
|
//CHECK_VALUE(data[pos+1], 10); // BPM for mode 2
|
||||||
|
//CHECK_VALUE(data[pos+2], 10); // timed inspiration for mode 2 (gain 0.1)
|
||||||
|
break;
|
||||||
|
case 0x2c: // Ramp Time
|
||||||
|
if (data[pos] != 0) { // 0 == ramp off, and ramp pressure setting doesn't appear
|
||||||
|
this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_RAMP_TIME, data[pos]));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0x2d: // Ramp Pressure (with ASV/ventilator pressure encoding)
|
||||||
|
this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_RAMP_PRESSURE, data[pos], GAIN));
|
||||||
|
break;
|
||||||
|
case 0x2e: // Bi-Flex level or Rise Time
|
||||||
|
// On F5V3 the first byte could specify Bi-Flex or Rise Time, and second byte contained the value.
|
||||||
|
// On F3V6 there's only one byte, which seems to correspond to Rise Time on the reports in modes 2 and 4,
|
||||||
|
// and to Bi-Flex Setting (level) on mode 1.
|
||||||
|
break;
|
||||||
|
case 0x2f: // Rise Time lock? (was flex lock on F0V6, 0x80 for locked)
|
||||||
|
CHECK_VALUE(data[pos], 0);
|
||||||
|
break;
|
||||||
|
case 0x35: // Humidifier setting
|
||||||
|
this->ParseHumidifierSettingV3(data[pos], data[pos+1], true);
|
||||||
|
break;
|
||||||
|
case 0x36: // Mask Resistance Lock
|
||||||
|
CHECK_VALUE(data[pos], 0); // 0x80 = locked on F5V3, not yet observed on F3V6
|
||||||
|
break;
|
||||||
|
case 0x38: // Mask Resistance
|
||||||
|
if (data[pos] != 0) { // 0 == mask resistance off
|
||||||
|
this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_SYSTEMONE_RESIST_SETTING, data[pos]));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0x39:
|
||||||
|
CHECK_VALUE(data[pos], 0);
|
||||||
|
break;
|
||||||
|
case 0x3b: // Tubing Type
|
||||||
|
if (data[pos] != 0) {
|
||||||
|
CHECK_VALUES(data[pos], 2, 1); // 15HT = 2, 15 = 1, 22 = 0, though report only says "15" for 15HT
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0x3c: // View Optional Screens
|
||||||
|
CHECK_VALUES(data[pos], 0, 0x80);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
qDebug() << "Unknown setting:" << hex << code << "in" << this->sessionid << "at" << pos;
|
||||||
|
this->AddEvent(new PRS1UnknownDataEvent(QByteArray((const char*) data, size), pos, len));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
pos += len;
|
||||||
|
} while (ok && pos + 2 <= size);
|
||||||
|
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if 0
|
||||||
bool PRS1DataChunk::ParseSummaryF3V6(void)
|
bool PRS1DataChunk::ParseSummaryF3V6(void)
|
||||||
{
|
{
|
||||||
CPAPMode mode = MODE_UNKNOWN;
|
CPAPMode mode = MODE_UNKNOWN;
|
||||||
@ -3656,6 +3941,7 @@ bool PRS1DataChunk::ParseSummaryF3V6(void)
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
bool PRS1DataChunk::ParseSummaryF5V012(void)
|
bool PRS1DataChunk::ParseSummaryF5V012(void)
|
||||||
|
@ -209,6 +209,9 @@ protected:
|
|||||||
|
|
||||||
//! \brief Parse a settings slice from a .000 and .001 file
|
//! \brief Parse a settings slice from a .000 and .001 file
|
||||||
bool ParseSettingsF5V3(const unsigned char* data, int size);
|
bool ParseSettingsF5V3(const unsigned char* data, int size);
|
||||||
|
|
||||||
|
//! \brief Parse a settings slice from a .000 and .001 file
|
||||||
|
bool ParseSettingsF3V6(const unsigned char* data, int size);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user