mirror of
https://gitlab.com/pholy/OSCAR-code.git
synced 2025-04-05 18:50:44 +00:00
First pass at F3V3 summary and settings parsing based on 3 sample sessions.
This commit is contained in:
parent
d87dd6a0aa
commit
445c74a70e
@ -4495,21 +4495,220 @@ bool PRS1DataChunk::ParseSummaryF0V4(void)
|
||||
}
|
||||
|
||||
|
||||
// TODO: Add support for F3V3 (1061T, 1160P). This is just a stub.
|
||||
bool PRS1DataChunk::ParseSettingsF3V3(const unsigned char* /*data*/, int /*size*/)
|
||||
// Support for 1061T, 1160P
|
||||
// logic largely borrowed from ParseSettingsF3V6, values based on sample data
|
||||
bool PRS1DataChunk::ParseSettingsF3V3(const unsigned char* data, int /*size*/)
|
||||
{
|
||||
PRS1Mode cpapmode = PRS1_MODE_UNKNOWN;
|
||||
FlexMode flexmode = FLEX_Unknown;
|
||||
|
||||
switch (data[2]) {
|
||||
//case 1: cpapmode = PRS1_MODE_S; break; // "S" mode
|
||||
case 2: cpapmode = PRS1_MODE_ST; break; // "S/T" mode; pressure seems variable?
|
||||
//case 4: cpapmode = PRS1_MODE_PC; break; // "PC" mode? Usually "PC - AVAPS", see setting 1 below
|
||||
default:
|
||||
UNEXPECTED_VALUE(data[2], "known device mode");
|
||||
break;
|
||||
}
|
||||
this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_CPAP_MODE, (int) cpapmode));
|
||||
|
||||
switch (data[3]) {
|
||||
case 0: // 0 = None
|
||||
flexmode = FLEX_None;
|
||||
//CHECK_VALUES(cpapmode, PRS1_MODE_S, PRS1_MODE_ST);
|
||||
CHECK_VALUE(cpapmode, PRS1_MODE_ST);
|
||||
break;
|
||||
/*
|
||||
case 1: // 1 = Bi-Flex, only seen with "S - Bi-Flex"
|
||||
flexmode = FLEX_BiFlex;
|
||||
CHECK_VALUE(cpapmode, PRS1_MODE_S);
|
||||
break;
|
||||
*/
|
||||
case 2: // 2 = AVAPS: usually "PC - AVAPS", sometimes "S/T - AVAPS"
|
||||
flexmode = FLEX_AVAPS;
|
||||
//CHECK_VALUES(cpapmode, PRS1_MODE_ST, PRS1_MODE_PC);
|
||||
CHECK_VALUE(cpapmode, PRS1_MODE_ST);
|
||||
break;
|
||||
default:
|
||||
UNEXPECTED_VALUE(data[3], "known flex mode");
|
||||
break;
|
||||
}
|
||||
this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_FLEX_MODE, (int) flexmode));
|
||||
|
||||
int epap = data[4] + (data[5] << 1); // 0x82 = EPAP 13 cmH2O; 0x78 = EPAP 12 cmH2O; 0x50 = EPAP 8 cmH2O
|
||||
int min_ipap = data[6] + (data[7] << 1); // 0xA0 = IPAP 16 cmH2O; 0xBE = 19 cmH2O min IPAP (in AVAPS); 0x78 = IPAP 12 cmH2O
|
||||
int max_ipap = data[8] + (data[9] << 1); // 0xAA = ???; 0x12C = 30 cmH2O max IPAP (in AVAPS); 0x78 = ???
|
||||
this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_EPAP, epap));
|
||||
this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP_MIN, min_ipap));
|
||||
this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP_MAX, max_ipap));
|
||||
// TODO: calculte PS or min/max PS? Create IPAP event when not AVAPS?
|
||||
|
||||
//CHECK_VALUE(data[0xa], 1, 3); // 1 = Rise Time Setting 1, 2 = Rise Time Setting 2, 3 = Rise Time Setting 3
|
||||
CHECK_VALUES(data[0xb], 0, 1); // 1 = Rise Time Lock
|
||||
CHECK_VALUE(data[0xc], 0);
|
||||
CHECK_VALUE(data[0xd], 0);
|
||||
|
||||
CHECK_VALUES(data[0xe], 0x14, 0x3C); // 0x14 = ???; 0x3C = Tidal Volume 600
|
||||
if (flexmode == FLEX_AVAPS) {
|
||||
this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_TIDAL_VOLUME, data[0xe] * 10.0));
|
||||
}
|
||||
|
||||
//CHECK_VALUES(data[0xf], 0x0C, 0x0A); // 0xC = Breath Rate 12, 0xA = Breath Rate 10
|
||||
//CHECK_VALUE(data[0x10], 0x0A, 0x14); // 0xA = Timed Inspiration 1, 0x14 = Time Inspiration 2
|
||||
if (data[0x0f] == 0) UNEXPECTED_VALUE(data[0x0f], "nonzero"); // Is this backup mode off?
|
||||
this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_BACKUP_BREATH_MODE, PRS1Backup_Fixed));
|
||||
this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_BACKUP_BREATH_RATE, data[0xf]));
|
||||
this->AddEvent(new PRS1ScaledSettingEvent(PRS1_SETTING_BACKUP_TIMED_INSPIRATION, data[0x10], 0.1));
|
||||
|
||||
CHECK_VALUE(data[0x11], 0);
|
||||
|
||||
//CHECK_VALUE(data[0x12], 0x1E, 0x0F); // 0x1E = ramp time 30 minutes, 0x0F = ramp time 15 minutes
|
||||
//CHECK_VALUE(data[0x13], 0x3C, 0x5A, 0x28); // 0x3C = ramp pressure 6 cmH2O, 0x28 = ramp pressure 4 cmH2O, 0x5A = ramp pressure 9 cmH2O
|
||||
CHECK_VALUE(data[0x14], 0); // the ramp pressure is probably a 16-bit value like the ones above are
|
||||
int ramp_time = data[0x12];
|
||||
int ramp_pressure = data[0x13];
|
||||
this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_RAMP_TIME, ramp_time));
|
||||
this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_RAMP_PRESSURE, ramp_pressure));
|
||||
|
||||
this->ParseHumidifierSettingF0V4(data[0x15], data[0x16], true);
|
||||
|
||||
// Menu options?
|
||||
CHECK_VALUES(data[0x17], 0x10, 0x90); // 0x10 = resist 1, tubing 22; 0x90 = resist 1, resist lock, tubing 22
|
||||
this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_MASK_RESIST_LOCK, (data[0x17] & 0x80) != 0));
|
||||
CHECK_VALUE(data[0x18], 0x16);
|
||||
|
||||
// Alarms?
|
||||
CHECK_VALUE(data[0x19], 0);
|
||||
CHECK_VALUE(data[0x1a], 0);
|
||||
CHECK_VALUE(data[0x1b], 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// borrowed largely from ParseSummaryF5V012
|
||||
bool PRS1DataChunk::ParseSummaryF3V3(void)
|
||||
{
|
||||
if (this->family != 3 || (this->familyVersion > 3)) {
|
||||
qWarning() << "ParseSummaryF3V3 called with family" << this->family << "familyVersion" << this->familyVersion;
|
||||
return false;
|
||||
}
|
||||
const unsigned char * data = (unsigned char *)this->m_data.constData();
|
||||
this->ParseSettingsF3V3(data, 0);
|
||||
this->duration = 0;
|
||||
return true;
|
||||
int chunk_size = this->m_data.size();
|
||||
QVector<int> minimum_sizes = { 0x1b, 3, 5, 9 };
|
||||
// NOTE: These are fixed sizes, but are called minimum to more closely match the F0V6 parser.
|
||||
|
||||
bool ok = true;
|
||||
int pos = 0;
|
||||
int code, size;
|
||||
int tt = 0;
|
||||
do {
|
||||
code = data[pos++];
|
||||
// There is no hblock prior to F3V6.
|
||||
size = 0;
|
||||
if (code < minimum_sizes.length()) {
|
||||
// make sure the handlers below don't go past the end of the buffer
|
||||
size = minimum_sizes[code];
|
||||
} // 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], 0);
|
||||
/*
|
||||
CHECK_VALUE(data[pos] & 0xF0, 0); // TODO: what are these?
|
||||
if ((data[pos] & 0x0F) != 1) { // This is the most frequent value.
|
||||
//CHECK_VALUES(data[pos] & 0x0F, 3, 5); // TODO: what are these? 0 seems to be related to errors.
|
||||
}
|
||||
*/
|
||||
// F3V3 doesn't have a separate settings record like F3V6 does, the settings just follow the EquipmentOn data.
|
||||
ok = this->ParseSettingsF3V3(data, size);
|
||||
/*
|
||||
CHECK_VALUE(data[pos+0x11], 0);
|
||||
CHECK_VALUE(data[pos+0x12], 0);
|
||||
CHECK_VALUE(data[pos+0x13], 0);
|
||||
CHECK_VALUE(data[pos+0x14], 0);
|
||||
CHECK_VALUE(data[pos+0x15], 0);
|
||||
CHECK_VALUE(data[pos+0x16], 0);
|
||||
CHECK_VALUE(data[pos+0x17], 0);
|
||||
*/
|
||||
break;
|
||||
case 2: // Mask On
|
||||
tt += data[pos] | (data[pos+1] << 8);
|
||||
this->AddEvent(new PRS1ParsedSliceEvent(tt, MaskOn));
|
||||
CHECK_VALUE(data[pos+2], 0); // probably initial pressure
|
||||
this->ParseHumidifierSettingF0V4(data[pos+3], data[pos+4]);
|
||||
break;
|
||||
case 3: // Mask Off
|
||||
tt += data[pos] | (data[pos+1] << 8);
|
||||
this->AddEvent(new PRS1ParsedSliceEvent(tt, MaskOff));
|
||||
// F3V3 doesn't have a separate stats record like F3V6 does, the stats just follow the MaskOff data.
|
||||
CHECK_VALUE(data[pos+0x2], 0);
|
||||
CHECK_VALUE(data[pos+0x3], 0);
|
||||
CHECK_VALUE(data[pos+0x4], 0);
|
||||
CHECK_VALUE(data[pos+0x5], 0);
|
||||
CHECK_VALUE(data[pos+0x6], 0);
|
||||
CHECK_VALUES(data[pos+0x7], 0, 2);
|
||||
CHECK_VALUE(data[pos+0x8], 0);
|
||||
break;
|
||||
case 1: // Equipment Off
|
||||
tt += data[pos] | (data[pos+1] << 8);
|
||||
this->AddEvent(new PRS1ParsedSliceEvent(tt, EquipmentOff));
|
||||
CHECK_VALUE(data[pos+2], 0);
|
||||
break;
|
||||
/*
|
||||
case 5: // Clock adjustment? See ParseSummaryF0V4.
|
||||
CHECK_VALUE(pos, 1); // Always first
|
||||
CHECK_VALUE(chunk_size, 5); // and the only record in the session.
|
||||
if (false) {
|
||||
long value = data[pos] | data[pos+1]<<8 | data[pos+2]<<16 | data[pos+3]<<24;
|
||||
qDebug() << this->sessionid << "clock changing from" << ts(value * 1000L)
|
||||
<< "to" << ts(this->timestamp * 1000L)
|
||||
<< "delta:" << (this->timestamp - value);
|
||||
}
|
||||
break;
|
||||
case 6: // Cleared?
|
||||
// Appears in the very first session when that session number is > 1.
|
||||
// Presumably previous sessions were cleared out.
|
||||
// TODO: add an internal event for this.
|
||||
CHECK_VALUE(pos, 1); // Always first
|
||||
CHECK_VALUE(chunk_size, 1); // and the only record in the session.
|
||||
if (this->sessionid == 1) UNEXPECTED_VALUE(this->sessionid, ">1");
|
||||
break;
|
||||
case 7: // ???
|
||||
tt += data[pos] | (data[pos+1] << 8); // This adds to the total duration (otherwise it won't match report)
|
||||
break;
|
||||
case 8: // ???
|
||||
tt += data[pos] | (data[pos+1] << 8); // Since 7 and 8 seem to occur near each other, let's assume 8 also has a timestamp
|
||||
CHECK_VALUE(pos, 1);
|
||||
CHECK_VALUE(chunk_size, 3);
|
||||
CHECK_VALUE(data[pos], 0); // and alert us if the timestamp is nonzero
|
||||
CHECK_VALUE(data[pos+1], 0);
|
||||
break;
|
||||
case 9: // Humidifier setting change
|
||||
tt += data[pos] | (data[pos+1] << 8); // This adds to the total duration (otherwise it won't match report)
|
||||
this->ParseHumidifierSettingF0V4(data[pos+2], data[pos+3]);
|
||||
break;
|
||||
*/
|
||||
default:
|
||||
UNEXPECTED_VALUE(code, "known slice code");
|
||||
ok = false; // unlike F0V6, we don't know the size of unknown slices, so we can't recover
|
||||
break;
|
||||
}
|
||||
pos += size;
|
||||
} while (ok && pos < chunk_size);
|
||||
|
||||
if (ok && pos != chunk_size) {
|
||||
qWarning() << this->sessionid << (this->size() - pos) << "trailing bytes";
|
||||
}
|
||||
|
||||
this->duration = tt;
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
|
||||
@ -5008,7 +5207,7 @@ bool PRS1DataChunk::ParseSummaryF5V012(void)
|
||||
//CHECK_VALUES(data[pos] & 0x0F, 3, 5); // TODO: what are these? 0 seems to be related to errors.
|
||||
}
|
||||
*/
|
||||
// F5V012 doesn't have a separate settings record like F0V6 does, the settings just follow the EquipmentOn data.
|
||||
// F5V012 doesn't have a separate settings record like F5V3 does, the settings just follow the EquipmentOn data.
|
||||
ok = this->ParseSettingsF5V012(data, size);
|
||||
/*
|
||||
CHECK_VALUE(data[pos+0x11], 0);
|
||||
@ -5033,7 +5232,7 @@ bool PRS1DataChunk::ParseSummaryF5V012(void)
|
||||
case 3: // Mask Off
|
||||
tt += data[pos] | (data[pos+1] << 8);
|
||||
this->AddEvent(new PRS1ParsedSliceEvent(tt, MaskOff));
|
||||
// F0V4 doesn't have a separate stats record like F0V6 does, the stats just follow the MaskOff data.
|
||||
// F5V012 doesn't have a separate stats record like F5V3 does, the stats just follow the MaskOff data.
|
||||
/*
|
||||
//CHECK_VALUES(data[pos+2], 130); // probably ending pressure
|
||||
//CHECK_VALUE(data[pos+3], 0); // ending IPAP for bilevel? average?
|
||||
|
Loading…
Reference in New Issue
Block a user