diff --git a/Htmldocs/release_notes.html b/Htmldocs/release_notes.html index 4b196123..5b48e3e3 100644 --- a/Htmldocs/release_notes.html +++ b/Htmldocs/release_notes.html @@ -18,8 +18,10 @@
  • [new] Additional Philips Respironics devices tested and fully supported:
  • +
  • [fix] Added support for pressure pulse, CA, and VS on BiPAP autoSV Advanced 30 (System One 60 Series) (960T).
  • Changes and fixes in OSCAR v1.3.5-alpha.2 diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.cpp b/oscar/SleepLib/loader_plugins/prs1_loader.cpp index 67196786..84c914b3 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.cpp +++ b/oscar/SleepLib/loader_plugins/prs1_loader.cpp @@ -141,6 +141,7 @@ static const PRS1TestedModel s_PRS1TestedModels[] = { { "960P", 5, 1, "BiPAP autoSV Advanced (System One 60 Series)" }, { "961P", 5, 1, "BiPAP autoSV Advanced (System One 60 Series)" }, { "960T", 5, 2, "BiPAP autoSV Advanced 30 (System One 60 Series)" }, // omits "(System One 60 Series)" on official reports + { "961TCA", 5, 2, "BiPAP autoSV Advanced 30 (System One 60 Series)" }, { "900X110", 5, 3, "DreamStation BiPAP autoSV" }, { "900X120", 5, 3, "DreamStation BiPAP autoSV" }, { "900X150", 5, 3, "DreamStation BiPAP autoSV" }, diff --git a/oscar/SleepLib/loader_plugins/prs1_parser.cpp b/oscar/SleepLib/loader_plugins/prs1_parser.cpp index a9f2c3a5..47a1bebb 100644 --- a/oscar/SleepLib/loader_plugins/prs1_parser.cpp +++ b/oscar/SleepLib/loader_plugins/prs1_parser.cpp @@ -713,12 +713,11 @@ void PRS1DataChunk::ParseHumidifierSetting60Series(unsigned char humid1, unsigne } else if (this->familyVersion == 2) { // F5V2 if (tubepresent) { - CHECK_VALUES(tubetemp, 0, 3); + // all tube temperatures seen if (tubetemp) { - CHECK_VALUE(tubehumidlevel, 1); + CHECK_VALUES(tubehumidlevel, 1, 3); } } - CHECK_VALUE(humidsystemone, false); CHECK_VALUE(humidclassic, false); } } diff --git a/oscar/SleepLib/loader_plugins/prs1_parser_asv.cpp b/oscar/SleepLib/loader_plugins/prs1_parser_asv.cpp index cb1e8c5a..76d1f54b 100644 --- a/oscar/SleepLib/loader_plugins/prs1_parser_asv.cpp +++ b/oscar/SleepLib/loader_plugins/prs1_parser_asv.cpp @@ -10,11 +10,6 @@ #include "prs1_parser.h" #include "prs1_loader.h" -static QString hex(int i) -{ - return QString("0x") + QString::number(i, 16).toUpper(); -} - //******************************************************************************************** // MARK: - // MARK: 50 and 60 Series @@ -664,7 +659,7 @@ bool PRS1DataChunk::ParseEventsF5V1(void) elapsed = data[pos]; this->AddEvent(new PRS1FlowLimitationEvent(t - elapsed, 0)); break; - case 0x0a: // Vibratory Snore, note this is 0x9 in F5V3 + case 0x0a: // Vibratory Snore, note this is 0xb in F5V2 and 0x9 in F5V3 // VS events are instantaneous flags with no duration, drawn on the official waveform. // The current thinking is that these are the snores that cause a change in auto-titrating // pressure. The snoring statistic above seems to be a total count. It's unclear whether @@ -672,7 +667,7 @@ bool PRS1DataChunk::ParseEventsF5V1(void) // no data bytes this->AddEvent(new PRS1VibratorySnoreEvent(t, 0)); break; - case 0x0b: // Periodic Breathing, note this is 0xa in F5V3 + case 0x0b: // Periodic Breathing, note this is 0xc in F5V2 and 0xa in F5V3 // PB events are reported some time after they conclude, and they do have a reported duration. duration = 2 * (data[pos] | (data[pos+1] << 8)); // confirmed to double in F5V0 elapsed = data[pos+2]; @@ -721,12 +716,13 @@ bool PRS1DataChunk::ParseEventsF5V1(void) const QVector ParsedEventsF5V2 = { PRS1EPAPSetEvent::TYPE, + PRS1PressurePulseEvent::TYPE, PRS1TimedBreathEvent::TYPE, PRS1ObstructiveApneaEvent::TYPE, - //PRS1ClearAirwayEvent::TYPE, // not yet seen + PRS1ClearAirwayEvent::TYPE, PRS1HypopneaEvent::TYPE, PRS1FlowLimitationEvent::TYPE, - //PRS1VibratorySnoreEvent::TYPE, // not yet seen + PRS1VibratorySnoreEvent::TYPE, PRS1PeriodicBreathingEvent::TYPE, //PRS1LargeLeakEvent::TYPE, // not yet seen PRS1IPAPAverageEvent::TYPE, @@ -751,7 +747,7 @@ bool PRS1DataChunk::ParseEventsF5V2(void) } const unsigned char * data = (unsigned char *)this->m_data.constData(); int chunk_size = this->m_data.size(); - static const QMap event_sizes = { {0,4}, {1,2}, {3,4}, {8,3}, {9,4}, {0xa,3}, {0xb,5}, {0xc,5}, {0xd,5}, {0xe,0xd}, {0xf,5}, {0x10,5}, {0x11,2}, {0x12,6} }; + static const QMap event_sizes = { {0,4}, {1,2}, {8,3}, {9,4}, {0xa,3}, {0xb,2}, {0xc,5}, {0xd,5}, {0xe,0xd}, {0xf,5}, {0x10,5}, {0x11,2}, {0x12,6} }; if (chunk_size < 1) { // This does occasionally happen in F0V6. @@ -780,51 +776,20 @@ bool PRS1DataChunk::ParseEventsF5V2(void) } startpos = pos; if (code != 0 && code != 0x12) { // These two codes have no timestamp TODO: verify this applies to F5V012 - t += data[pos] /*| (data[pos+1] << 8)*/; // TODO: Is this really only 1 byte? - if (data[pos+1] != 0) qWarning() << this->sessionid << "nonzero time? byte" << hex(startpos); - CHECK_VALUE(data[pos+1], 0); + t += data[pos] | (data[pos+1] << 8); pos += 2; } switch (code) { -/* - case 0x00: // Unknown (ASV Pressure value) - DUMP_EVENT(); - // offset? - data0 = data[pos++]; - - if (!data[pos - 1]) { // WTH??? - data1 = data[pos++]; - } - - if (!data[pos - 1]) { - //data2 = data[pos++]; - pos++; - } - - break; - - case 0x01: // Unknown - DUMP_EVENT(); - this->AddEvent(new PRS1UnknownValueEvent(code, t, 0, 0.1F)); - break; -*/ + //case 0x00: // never seen on F5V2 + //case 0x01: // never seen on F5V2 case 0x02: // Pressure adjustment this->AddEvent(new PRS1EPAPSetEvent(t, data[pos++], GAIN)); break; -/* - case 0x03: // BIPAP Pressure - DUMP_EVENT(); - qDebug() << "0x03 Observed in ASV data!!????"; - - data0 = data[pos++]; - data1 = data[pos++]; - // data0/=10.0; - // data1/=10.0; - // session->AddEvent(new Event(t,CPAP_EAP, 0, data, 1)); - // session->AddEvent(new Event(t,CPAP_IAP, 0, &data1, 1)); - break; -*/ + case 0x03: // Pressure Pulse + duration = data[pos]; // TODO: is this a duration? + this->AddEvent(new PRS1PressurePulseEvent(t, duration)); + break; case 0x04: // Timed Breath // TB events have a duration in 0.1s, based on the review of pressure waveforms. // TODO: Ideally the starting time here would be adjusted here, but PRS1ParsedEvents @@ -839,15 +804,13 @@ bool PRS1DataChunk::ParseEventsF5V2(void) elapsed = data[pos]; this->AddEvent(new PRS1ObstructiveApneaEvent(t - elapsed, 0)); break; -/* - case 0x06: - DUMP_EVENT(); - //code=CPAP_ClearAirway; - data0 = data[pos++]; - this->AddEvent(new PRS1ClearAirwayEvent(t - data0, data0)); - break; -*/ - + case 0x06: // Clear Airway Apnea + // CA events are instantaneous flags with no duration: reviewing waveforms + // shows that the time elapsed between the flag and reporting often includes + // non-apnea breathing. + elapsed = data[pos]; + this->AddEvent(new PRS1ClearAirwayEvent(t - elapsed, 0)); + break; case 0x07: // Hypopnea // NOTE: No additional (unknown) first byte as in F5V3 0x07, but see below. // This seems closer to F5V3 0x0d or 0x0e. @@ -862,21 +825,7 @@ bool PRS1DataChunk::ParseEventsF5V2(void) elapsed = data[pos]; // based on sample waveform, the hypopnea is over after this this->AddEvent(new PRS1HypopneaEvent(t - elapsed, 0)); break; -/* - case 0x09: // ASV Codes - DUMP_EVENT(); - / * - if (this->familyVersion<2) { - //code=CPAP_FlowLimit; - data0 = data[pos++]; - - this->AddEvent(new PRS1FlowLimitationEvent(t - data0, data0)); - } else { - * / - data0 = data[pos++]; - data1 = data[pos++]; - break; -*/ + //case 0x09: // never seen on F5V2 case 0x0a: // Flow Limitation, note this is 0x9 in F5V1 and 0x8 in F5V3 // TODO: We should revisit whether this is elapsed or duration once (if) // we start calculating flow limitations ourselves. Flow limitations aren't @@ -884,35 +833,21 @@ bool PRS1DataChunk::ParseEventsF5V2(void) elapsed = data[pos]; this->AddEvent(new PRS1FlowLimitationEvent(t - elapsed, 0)); break; -/* - case 0x0b: // Cheyne Stokes - DUMP_EVENT(); - data0 = ((unsigned char *)data)[pos + 1] << 8 | ((unsigned char *)data)[pos]; - //data0*=2; - pos += 2; - data1 = ((unsigned char *)data)[pos]; //|data[pos+1] << 8 - pos += 1; - //tt-=delta; - this->AddEvent(new PRS1PeriodicBreathingEvent(t - data1, data0)); - break; -*/ + case 0x0b: // Vibratory Snore, note this is 0xa in F5V1 and 0x9 in F5V3 + // VS events are instantaneous flags with no duration, drawn on the official waveform. + // The current thinking is that these are the snores that cause a change in auto-titrating + // pressure. The snoring statistic above seems to be a total count. It's unclear whether + // the trigger for pressure change is severity or count or something else. + // no data bytes + this->AddEvent(new PRS1VibratorySnoreEvent(t, 0)); + break; case 0x0c: // Periodic Breathing, note this is 0xb in F5V1 and 0xa in F5V3 // PB events are reported some time after they conclude, and they do have a reported duration. duration = 2 * (data[pos] | (data[pos+1] << 8)); // confirmed to double in F5V0 elapsed = data[pos+2]; this->AddEvent(new PRS1PeriodicBreathingEvent(t - elapsed - duration, duration)); break; -/* - case 0x0d: - DUMP_EVENT(); - - data0 = (data[pos + 1] << 8 | data[pos]); - data0 *= 2; - pos += 2; - data1 = data[pos++]; - //tt = t - qint64(data1) * 1000L; - break; -*/ + //case 0x0d: // never seen on F5V2 case 0x0e: // Statistics, note this was 0x0d in F5V0 and F5V1 // These appear every 2 minutes, so presumably summarize the preceding period. this->AddEvent(new PRS1IPAPAverageEvent(t, data[pos+0], GAIN)); // 00=IPAP @@ -928,53 +863,6 @@ bool PRS1DataChunk::ParseEventsF5V2(void) this->AddEvent(new PRS1LeakEvent(t, data[pos+0xa])); // 0A=Leak (average?) new to F5V1 (originally found in F5V3) this->AddEvent(new PRS1IntervalBoundaryEvent(t)); break; -/* - case 0x0f: - DUMP_EVENT(); - qDebug() << "0x0f Observed in ASV data!!????"; - - data0 = data[pos + 1] << 8 | data[pos]; - pos += 2; - data1 = data[pos]; //|data[pos+1] << 8 - pos += 1; - //tt -= qint64(data1) * 1000L; - //session->AddEvent(new Event(tt,cpapcode, 0, data, 2)); - break; - - case 0x10: // Unknown - DUMP_EVENT(); - data0 = data[pos + 1] << 8 | data[pos]; - pos += 2; - data1 = data[pos++]; - - this->AddEvent(new PRS1LargeLeakEvent(t - data1, data0)); - -// qDebug() << "0x10 Observed in ASV data!!????"; -// data0 = data[pos++]; // << 8) | data[pos]; -// data1 = data[pos++]; -// data2 = data[pos++]; - //session->AddEvent(new Event(t,cpapcode, 0, data, 3)); - break; - case 0x11: // Not Leak Rate - DUMP_EVENT(); - qDebug() << "0x11 Observed in ASV data!!????"; - //if (!Code[24]) { - // Code[24]=new EventList(cpapcode,EVL_Event); - //} - //Code[24]->AddEvent(t,data[pos++]); - break; - - - case 0x12: // Summary - DUMP_EVENT(); - qDebug() << "0x12 Observed in ASV data!!????"; - data0 = data[pos++]; - data1 = data[pos++]; - //data2 = data[pos + 1] << 8 | data[pos]; - pos += 2; - //session->AddEvent(new Event(t,cpapcode, 0, data,3)); - break; -*/ default: DUMP_EVENT(); UNEXPECTED_VALUE(code, "known event code");