From 5b959efc37d2004e40f4a264c92805555bb32531 Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Wed, 22 May 2019 11:00:45 -0400 Subject: [PATCH 01/31] Split parsing from importing for F5V3 events. This doesn't look much prettier yet, since it requires double the switch statements, but the expectation is that once all event parsers are split out from import, the import routines will be identical among all machines, and can then be consolidated. Regardless, it's important to drive a wedge between file parsing and the internal database structure. --- oscar/SleepLib/loader_plugins/prs1_loader.cpp | 403 +++++++++++++++--- oscar/SleepLib/loader_plugins/prs1_loader.h | 10 +- 2 files changed, 340 insertions(+), 73 deletions(-) diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.cpp b/oscar/SleepLib/loader_plugins/prs1_loader.cpp index 7db293db..7778e546 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.cpp +++ b/oscar/SleepLib/loader_plugins/prs1_loader.cpp @@ -954,11 +954,195 @@ void PRS1Loader::ScanFiles(const QStringList & paths, int sessionid_base, Machin } } +enum PRS1ParsedEventType +{ + EV_PRS1_TB, + EV_PRS1_OA, + EV_PRS1_CA, + EV_PRS1_FL, + EV_PRS1_PB, + EV_PRS1_LL, + EV_PRS1_HY, + EV_PRS1_IPAP, + EV_PRS1_TOTLEAK, + EV_PRS1_IPAPLOW, + EV_PRS1_IPAPHIGH, + EV_PRS1_RR, + EV_PRS1_PTB, + EV_PRS1_MV, + EV_PRS1_TV, + EV_PRS1_SNORE, + EV_PRS1_EPAP, +}; + +enum PRS1ParsedEventUnit +{ + PRS1_UNIT_NONE, + PRS1_UNIT_CMH2O, + PRS1_UNIT_ML, +}; + +class PRS1ParsedEvent +{ +public: + PRS1ParsedEventType m_type; + int m_start; // seconds relative to chunk timestamp at which this event began + int m_duration; + int m_value; + float m_offset; + float m_gain; + PRS1ParsedEventUnit m_unit; + +protected: + PRS1ParsedEvent(PRS1ParsedEventType type, int start) + : m_type(type), m_start(start), m_duration(0), m_value(0), m_offset(0.0), m_gain(1.0), m_unit(PRS1_UNIT_NONE) + { + } + ~PRS1ParsedEvent() + { + } +}; + +class PRS1ParsedDurationEvent : public PRS1ParsedEvent +{ +protected: + PRS1ParsedDurationEvent(PRS1ParsedEventType type, int start, int duration) : PRS1ParsedEvent(type, start) + { + m_duration = duration; + } +}; + +class PRS1ParsedValueEvent : public PRS1ParsedEvent +{ +protected: + PRS1ParsedValueEvent(PRS1ParsedEventType type, int start, int value) : PRS1ParsedEvent(type, start) { m_value = value; } +}; + +class PRS1PressureEvent : public PRS1ParsedValueEvent +{ +public: + PRS1PressureEvent(PRS1ParsedEventType type, int start, int value) + : PRS1ParsedValueEvent(type, start, value) + { + m_gain = 0.1; + m_unit = PRS1_UNIT_CMH2O; + } +}; + +class PRS1TimedBreathEvent : public PRS1ParsedDurationEvent +{ +public: + PRS1TimedBreathEvent(int start, int duration) : PRS1ParsedDurationEvent(EV_PRS1_TB, start, duration) {} +}; + +class PRS1ObstructiveApneaEvent : public PRS1ParsedDurationEvent +{ +public: + PRS1ObstructiveApneaEvent(int start, int duration) : PRS1ParsedDurationEvent(EV_PRS1_OA, start, duration) {} +}; + +class PRS1ClearAirwayEvent : public PRS1ParsedDurationEvent +{ +public: + PRS1ClearAirwayEvent(int start, int duration) : PRS1ParsedDurationEvent(EV_PRS1_CA, start, duration) {} +}; + +class PRS1FlowLimitationEvent : public PRS1ParsedDurationEvent +{ +public: + PRS1FlowLimitationEvent(int start, int duration) : PRS1ParsedDurationEvent(EV_PRS1_FL, start, duration) {} +}; + +class PRS1PeriodicBreathingEvent : public PRS1ParsedDurationEvent +{ +public: + PRS1PeriodicBreathingEvent(int start, int duration) : PRS1ParsedDurationEvent(EV_PRS1_PB, start, duration) {} +}; + +class PRS1LargeLeakEvent : public PRS1ParsedDurationEvent +{ +public: + PRS1LargeLeakEvent(int start, int duration) : PRS1ParsedDurationEvent(EV_PRS1_LL, start, duration) {} +}; + +class PRS1HypopneaEvent : public PRS1ParsedDurationEvent +{ +public: + PRS1HypopneaEvent(int start, int duration) : PRS1ParsedDurationEvent(EV_PRS1_HY, start, duration) {} +}; + +class PRS1IPAPEvent : public PRS1PressureEvent +{ +public: + PRS1IPAPEvent(int start, int value) : PRS1PressureEvent(EV_PRS1_IPAP, start, value) {} +}; + +class PRS1TotalLeakEvent : public PRS1ParsedValueEvent +{ +public: + PRS1TotalLeakEvent(int start, int value) : PRS1ParsedValueEvent(EV_PRS1_TOTLEAK, start, value) {} +}; + +class PRS1IPAPHighEvent : public PRS1PressureEvent +{ +public: + PRS1IPAPHighEvent(int start, int value) : PRS1PressureEvent(EV_PRS1_IPAPHIGH, start, value) {} +}; + +class PRS1IPAPLowEvent : public PRS1PressureEvent +{ +public: + PRS1IPAPLowEvent(int start, int value) : PRS1PressureEvent(EV_PRS1_IPAPLOW, start, value) {} +}; + +class PRS1RespiratoryRateEvent : public PRS1ParsedValueEvent +{ +public: + PRS1RespiratoryRateEvent(int start, int value) : PRS1ParsedValueEvent(EV_PRS1_RR, start, value) {} +}; + +class PRS1PatientTriggeredBreathsEvent : public PRS1ParsedValueEvent +{ +public: + PRS1PatientTriggeredBreathsEvent(int start, int value) : PRS1ParsedValueEvent(EV_PRS1_PTB, start, value) {} +}; + +class PRS1MinuteVentilationEvent : public PRS1ParsedValueEvent +{ +public: + PRS1MinuteVentilationEvent(int start, int value) : PRS1ParsedValueEvent(EV_PRS1_MV, start, value) {} +}; + +class PRS1TidalVolumeEvent : public PRS1ParsedValueEvent +{ +public: + PRS1TidalVolumeEvent(int start, int value) + : PRS1ParsedValueEvent(EV_PRS1_TV, start, value) + { + m_gain = 10.0; + m_unit = PRS1_UNIT_ML; + } +}; + +class PRS1SnoreEvent : public PRS1ParsedValueEvent +{ +public: + PRS1SnoreEvent(int start, int value) : PRS1ParsedValueEvent(EV_PRS1_SNORE, start, value) {} +}; + +class PRS1EPAPEvent : public PRS1PressureEvent +{ +public: + PRS1EPAPEvent(int start, int value) : PRS1PressureEvent(EV_PRS1_EPAP, start, value) {} +}; + +void PRS1DataChunk::AddEvent(PRS1ParsedEvent* const event) +{ + m_parsedData.push_back(event); +} + bool PRS1Import::ParseF5EventsFV3() { - EventDataType data0, data1, data2, data3, data4, data5; - Q_UNUSED(data3) - EventDataType currentPressure=0, leak; //, p; bool calcLeaks = p_profile->cpap->calculateUnintentionalLeaks(); @@ -972,16 +1156,7 @@ bool PRS1Import::ParseF5EventsFV3() //qint64 start=timestamp; qint64 t = qint64(event->timestamp) * 1000L; session->updateFirst(t); - qint64 tt = 0; - int pos = 0; - //int cnt = 0; - short delta;//,duration; - //bool badcode = false; - unsigned char lastcode3 = 0, lastcode2 = 0, lastcode = 0, code = 0; - int lastpos = 0, startpos = 0, lastpos2 = 0, lastpos3 = 0; - int size = event->m_data.size(); - unsigned char * buffer = (unsigned char *)event->m_data.data(); EventList *OA = session->AddEventList(CPAP_Obstructive, EVL_Event); EventList *HY = session->AddEventList(CPAP_Hypopnea, EVL_Event); @@ -1007,6 +1182,118 @@ bool PRS1Import::ParseF5EventsFV3() EventList *FL = session->AddEventList(CPAP_FlowLimit, EVL_Event); EventList *VS = session->AddEventList(CPAP_VSnore, EVL_Event); + bool ok; + ok = event->ParseEventsF5V3(); + if (!ok) { + return false; + } + + for (int i=0; i < event->m_parsedData.count(); i++) { + PRS1ParsedEvent* e = event->m_parsedData.at(i); + t = qint64(event->timestamp + e->m_start) * 1000L; + + switch (e->m_type) { + case EV_PRS1_TB: + TB->AddEvent(t, e->m_duration); + break; + case EV_PRS1_OA: + OA->AddEvent(t, e->m_duration); + break; + case EV_PRS1_CA: + CA->AddEvent(t, e->m_duration); + break; + case EV_PRS1_FL: + FL->AddEvent(t, e->m_duration); + break; + case EV_PRS1_PB: + PB->AddEvent(t, e->m_duration); + break; + case EV_PRS1_LL: + LL->AddEvent(t, e->m_duration); + break; + case EV_PRS1_HY: + HY->AddEvent(t, e->m_duration); + break; + case EV_PRS1_IPAP: + IPAP->AddEvent(t, e->m_value); + currentPressure = e->m_value; + break; + case EV_PRS1_TOTLEAK: + TOTLEAK->AddEvent(t, e->m_value); + leak = e->m_value; + if (calcLeaks) { // Much Quicker doing this here than the recalc method. + leak -= (((currentPressure/10.0f) - 4.0) * ppm + lpm4); + if (leak < 0) leak = 0; + LEAK->AddEvent(t, leak); + } + break; + case EV_PRS1_IPAPLOW: + IPAPLo->AddEvent(t, e->m_value); + break; + case EV_PRS1_IPAPHIGH: + IPAPHi->AddEvent(t, e->m_value); + break; + case EV_PRS1_RR: + RR->AddEvent(t, e->m_value); + break; + case EV_PRS1_PTB: + PTB->AddEvent(t, e->m_value); + break; + case EV_PRS1_MV: + MV->AddEvent(t, e->m_value); + break; + case EV_PRS1_TV: + TV->AddEvent(t, e->m_value); + break; + case EV_PRS1_SNORE: + SNORE->AddEvent(t, e->m_value); + if (e->m_value > 0) { + VS->AddEvent(t, 0); //data2); // VSnore + } + break; + case EV_PRS1_EPAP: + EPAP->AddEvent(t, e->m_value); + EventDataType ps = currentPressure - e->m_value; + PS->AddEvent(t, ps); // Pressure Support + break; + default: + qWarning() << "Unknown PRS1 event type" << (int) e->m_type; + break; + } + } + + session->updateLast(t); + session->m_cnt.clear(); + session->m_cph.clear(); + + session->m_valuesummary[CPAP_Pressure].clear(); + session->m_valuesummary.erase(session->m_valuesummary.find(CPAP_Pressure)); + + return true; +} + + +bool PRS1DataChunk::ParseEventsF5V3() +{ + if (this->family != 5 || this->familyVersion != 3) { + qWarning() << "ParseEventsF5V3 called with family" << this->family << "familyVersion" << this->familyVersion; + //break; // don't break to avoid changing behavior (for now) + } + + EventDataType data0, data1, data2, data3, data4, data5; + Q_UNUSED(data3) + + int t = 0; + int pos = 0; + //int cnt = 0; + short delta;//,duration; + //bool badcode = false; + unsigned char lastcode3 = 0, lastcode2 = 0, lastcode = 0, code = 0; + int lastpos = 0, startpos = 0, lastpos2 = 0, lastpos3 = 0; + + int size = this->m_data.size(); + unsigned char * buffer = (unsigned char *)this->m_data.data(); + while (pos < size) { lastcode3 = lastcode2; lastcode2 = lastcode; @@ -1018,7 +1305,7 @@ bool PRS1Import::ParseF5EventsFV3() code = buffer[pos++]; if (code >= 0x12) { - qDebug() << "Illegal PRS1 code " << hex << int(code) << " appeared at " << hex << startpos << "in" << event->sessionid;; + qDebug() << "Illegal PRS1 code " << hex << int(code) << " appeared at " << hex << startpos << "in" << this->sessionid;; qDebug() << "1: (" << int(lastcode) << hex << lastpos << ")"; qDebug() << "2: (" << int(lastcode2) << hex << lastpos2 << ")"; qDebug() << "3: (" << int(lastcode3) << hex << lastpos3 << ")"; @@ -1027,90 +1314,67 @@ bool PRS1Import::ParseF5EventsFV3() delta = buffer[pos]; //delta=buffer[pos+1] << 8 | buffer[pos]; pos += 2; - t += qint64(delta) * 1000L; - tt=t; + t += delta; switch(code) { case 0x01: // Leak ??? data0 = buffer[pos++]; - - tt -= qint64(data0) * 1000L; // Subtract Time Offset + //tt -= qint64(data0) * 1000L; // Subtract Time Offset break; case 0x02: // Meh??? Timed Breath?? data0 = buffer[pos++]; - tt -= qint64(data0) * 1000L; // Subtract Time Offset - TB->AddEvent(tt, data0); - - + this->AddEvent(new PRS1TimedBreathEvent(t - data0, data0)); break; case 0x03: // Graph Data - IPAP->AddEvent(t, currentPressure = data0 = buffer[pos++]); // 00=IAP + data0 = buffer[pos++]; + this->AddEvent(new PRS1IPAPEvent(t, data0)); // 00=IAP data4 = buffer[pos++]; - IPAPLo->AddEvent(t, data4); // 01=IAP Low + this->AddEvent(new PRS1IPAPLowEvent(t, data4)); // 01=IAP Low data5 = buffer[pos++]; - IPAPHi->AddEvent(t, data5); // 02=IAP High - - TOTLEAK->AddEvent(t, leak=buffer[pos++]); // 03=LEAK - if (calcLeaks) { // Much Quicker doing this here than the recalc method. - leak -= (((currentPressure/10.0f) - 4.0) * ppm + lpm4); - if (leak < 0) leak = 0; - - LEAK->AddEvent(t, leak); - } + this->AddEvent(new PRS1IPAPHighEvent(t, data5)); // 02=IAP High + this->AddEvent(new PRS1TotalLeakEvent(t, buffer[pos++])); // 03=LEAK - RR->AddEvent(t, buffer[pos++]); // 04=Breaths Per Minute - PTB->AddEvent(t, buffer[pos++]); // 05=Patient Triggered Breaths - MV->AddEvent(t, buffer[pos++]); // 06=Minute Ventilation + this->AddEvent(new PRS1RespiratoryRateEvent(t, buffer[pos++])); // 04=Breaths Per Minute + this->AddEvent(new PRS1PatientTriggeredBreathsEvent(t, buffer[pos++])); // 05=Patient Triggered Breaths + this->AddEvent(new PRS1MinuteVentilationEvent(t, buffer[pos++])); // 06=Minute Ventilation //tmp=buffer[pos++] * 10.0; - TV->AddEvent(t, buffer[pos++]); // 07=Tidal Volume - SNORE->AddEvent(t, data2 = buffer[pos++]); // 08=Snore - - if (data2 > 0) { - VS->AddEvent(t, 0); //data2); // VSnore - } - - EPAP->AddEvent(t, data1 = buffer[pos++]); // 09=EPAP - data2 = data0 - data1; - PS->AddEvent(t, data2); // Pressure Support + this->AddEvent(new PRS1TidalVolumeEvent(t, buffer[pos++])); // 07=Tidal Volume + this->AddEvent(new PRS1SnoreEvent(t, buffer[pos++])); // 08=Snore + this->AddEvent(new PRS1EPAPEvent(t, buffer[pos++])); // 09=EPAP data0 = buffer[pos++]; break; case 0x05: data0 = buffer[pos++]; - tt -= qint64(data0) * 1000L; // Subtract Time Offset - OA->AddEvent(tt, data0); + this->AddEvent(new PRS1ObstructiveApneaEvent(t - data0, data0)); // PS->AddEvent(tt, data0); break; case 0x06: // Clear Airway data0 = buffer[pos++]; - tt -= qint64(data0) * 1000L; // Subtract Time Offset - - CA->AddEvent(tt, data0); + this->AddEvent(new PRS1ClearAirwayEvent(t - data0, data0)); // PTB->AddEvent(tt, data0); break; case 0x07: data0 = buffer[pos++]; data1 = buffer[pos++]; - tt -= qint64(data0) * 1000L; // Subtract Time Offset + //tt -= qint64(data0) * 1000L; // Subtract Time Offset break; case 0x08: // Flow Limitation data0 = buffer[pos++]; - tt -= qint64(data0) * 1000L; // Subtract Time Offset - - FL->AddEvent(tt, data0); + this->AddEvent(new PRS1FlowLimitationEvent(t - data0, data0)); break; case 0x09: data0 = buffer[pos++]; data1 = buffer[pos++]; data2 = buffer[pos++]; data3 = buffer[pos++]; - tt -= qint64(data0) * 1000L; // Subtract Time Offset + //tt -= qint64(data0) * 1000L; // Subtract Time Offset // TB->AddEvent(tt, data0); @@ -1120,8 +1384,7 @@ bool PRS1Import::ParseF5EventsFV3() data0 *= 2; pos += 2; data1 = buffer[pos++]; - tt = t - qint64(data1) * 1000L; - PB->AddEvent(tt, data0); + this->AddEvent(new PRS1PeriodicBreathingEvent(t - data1, data0)); break; case 0x0b: // Large Leak @@ -1129,38 +1392,28 @@ bool PRS1Import::ParseF5EventsFV3() data0 *= 2; pos += 2; data1 = buffer[pos++]; - tt = t - qint64(data1) * 1000L; - LL->AddEvent(tt, data0); + this->AddEvent(new PRS1LargeLeakEvent(t - data1, data0)); break; case 0x0d: // flag ?? data0 = buffer[pos++]; - tt -= qint64(data0) * 1000L; // Subtract Time Offset - HY->AddEvent(tt, data0); + this->AddEvent(new PRS1HypopneaEvent(t - data0, data0)); break; case 0x0e: data0 = buffer[pos++]; - tt -= qint64(data0) * 1000L; // Subtract Time Offset - HY->AddEvent(tt, data0); + this->AddEvent(new PRS1HypopneaEvent(t - data0, data0)); break; default: - qDebug() << "Unknown code:" << hex << code << "in" << event->sessionid << "at" << pos; + qDebug() << "Unknown code:" << hex << code << "in" << this->sessionid << "at" << pos; } } - session->updateLast(t); - session->m_cnt.clear(); - session->m_cph.clear(); - - session->m_valuesummary[CPAP_Pressure].clear(); - session->m_valuesummary.erase(session->m_valuesummary.find(CPAP_Pressure)); - return true; } @@ -3544,6 +3797,14 @@ PRS1DataChunk::PRS1DataChunk(QFile & f) m_path = QFileInfo(f).canonicalFilePath(); } +PRS1DataChunk::~PRS1DataChunk() +{ + for (int i=0; i < m_parsedData.count(); i++) { + PRS1ParsedEvent* e = m_parsedData.at(i); + delete e; + } +} + PRS1DataChunk* PRS1DataChunk::ParseNext(QFile & f) { diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.h b/oscar/SleepLib/loader_plugins/prs1_loader.h index 17772769..3043f05f 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.h +++ b/oscar/SleepLib/loader_plugins/prs1_loader.h @@ -77,13 +77,13 @@ public: m_index = -1; } PRS1DataChunk(class QFile & f); - ~PRS1DataChunk() { - } + ~PRS1DataChunk(); inline int size() const { return m_data.size(); } QByteArray m_header; QByteArray m_data; QByteArray m_headerblock; + QList m_parsedData; QString m_path; qint64 m_filepos; // file offset @@ -124,7 +124,13 @@ public: //! \brief Read the chunk's data from a PRS1 file and calculate its CRC, must be called after ReadHeader bool ReadData(class QFile & f); + //! \brief Parse a single data chunk from a .002 file containing event data for a family 5 ASV family version 3 machine + bool ParseEventsF5V3(); + protected: + //! \brief Add a parsed event to the chunk + void AddEvent(class PRS1ParsedEvent* event); + //! \brief Read and parse the non-waveform header data from a V2 PRS1 file bool ReadNormalHeaderV2(class QFile & f); From 355a85a1fc5102ea2b1801cd88a1a9a9178fd21f Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Wed, 22 May 2019 19:59:10 -0400 Subject: [PATCH 02/31] Tweak Session YAML output to print sane floats instead of full precision. --- oscar/tests/sessiontests.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/oscar/tests/sessiontests.cpp b/oscar/tests/sessiontests.cpp index f5491d85..421e3a29 100644 --- a/oscar/tests/sessiontests.cpp +++ b/oscar/tests/sessiontests.cpp @@ -174,7 +174,14 @@ void SessionToYaml(QString filepath, Session* session) QList keys = session->settings.keys(); std::sort(keys.begin(), keys.end()); for (QList::iterator key = keys.begin(); key != keys.end(); key++) { - out << " " << settingChannel(*key) << ": " << session->settings[*key].toString() << endl; + QVariant & value = session->settings[*key]; + QString s; + if ((QMetaType::Type) value.type() == QMetaType::Float) { + s = QString::number(value.toFloat()); // Print the shortest accurate representation rather than QVariant's full precision. + } else { + s = value.toString(); + } + out << " " << settingChannel(*key) << ": " << s << endl; } out << " events:" << endl; From 795e9414bea5a4d4070624d7a534708adb8ac4b0 Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Wed, 22 May 2019 20:11:48 -0400 Subject: [PATCH 03/31] Split parsing from importing for F5V3 summaries. This looks even uglier than the F5V3 event split for now, since portions of the fileVersion 3 parsing are still stuck in PRS1Import, and can't be reasonably moved until all the parsers are split into PRS1DataChunk. Also, PRS1ParsedSettingEvent may be the wrong abstraction. That's a first attempt, so that it can inherit PRS1ParsedEvent's notion of gain and unit, and because m_parsedData is currently a list of PRS1ParsedEvent pointers. But it has no notion of time (start=0) and requires yet another enum to specify which setting it represents. This should be revisited once all the parsers have been split and the summary parsing can be examined in more detail. --- oscar/SleepLib/loader_plugins/prs1_loader.cpp | 134 ++++++++++++++---- oscar/SleepLib/loader_plugins/prs1_loader.h | 11 +- 2 files changed, 114 insertions(+), 31 deletions(-) diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.cpp b/oscar/SleepLib/loader_plugins/prs1_loader.cpp index 7778e546..00b8b481 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.cpp +++ b/oscar/SleepLib/loader_plugins/prs1_loader.cpp @@ -973,6 +973,7 @@ enum PRS1ParsedEventType EV_PRS1_TV, EV_PRS1_SNORE, EV_PRS1_EPAP, + EV_PRS1_SETTING, }; enum PRS1ParsedEventUnit @@ -982,6 +983,16 @@ enum PRS1ParsedEventUnit PRS1_UNIT_ML, }; +enum PRS1ParsedSettingType +{ + PRS1_SETTING_EPAP_MIN, + PRS1_SETTING_EPAP_MAX, + PRS1_SETTING_IPAP_MIN, + PRS1_SETTING_IPAP_MAX, + PRS1_SETTING_PS_MIN, + PRS1_SETTING_PS_MAX, +}; + class PRS1ParsedEvent { public: @@ -993,6 +1004,8 @@ public: float m_gain; PRS1ParsedEventUnit m_unit; + inline float value(void) { return (m_value * m_gain) + m_offset; } + protected: PRS1ParsedEvent(PRS1ParsedEventType type, int start) : m_type(type), m_start(start), m_duration(0), m_value(0), m_offset(0.0), m_gain(1.0), m_unit(PRS1_UNIT_NONE) @@ -1021,11 +1034,33 @@ protected: class PRS1PressureEvent : public PRS1ParsedValueEvent { public: + static constexpr float GAIN = 0.1; + static const PRS1ParsedEventUnit UNIT = PRS1_UNIT_CMH2O; + PRS1PressureEvent(PRS1ParsedEventType type, int start, int value) : PRS1ParsedValueEvent(type, start, value) { - m_gain = 0.1; - m_unit = PRS1_UNIT_CMH2O; + m_gain = GAIN; + m_unit = UNIT; + } +}; + +class PRS1ParsedSettingEvent : public PRS1ParsedValueEvent +{ +public: + PRS1ParsedSettingType m_setting; + + PRS1ParsedSettingEvent(PRS1ParsedSettingType setting, int value) : PRS1ParsedValueEvent(EV_PRS1_SETTING, 0, value), m_setting(setting) {} +}; + +class PRS1PressureSettingEvent : public PRS1ParsedSettingEvent +{ +public: + PRS1PressureSettingEvent(PRS1ParsedSettingType setting, int value) + : PRS1ParsedSettingEvent(setting, value) + { + m_gain = PRS1PressureEvent::GAIN; + m_unit = PRS1PressureEvent::UNIT; } }; @@ -1143,7 +1178,7 @@ void PRS1DataChunk::AddEvent(PRS1ParsedEvent* const event) bool PRS1Import::ParseF5EventsFV3() { - EventDataType currentPressure=0, leak; //, p; + EventDataType currentPressure=0, leak, ps=0; bool calcLeaks = p_profile->cpap->calculateUnintentionalLeaks(); EventDataType lpm4 = p_profile->cpap->custom4cmH2OLeaks(); @@ -1253,7 +1288,7 @@ bool PRS1Import::ParseF5EventsFV3() break; case EV_PRS1_EPAP: EPAP->AddEvent(t, e->m_value); - EventDataType ps = currentPressure - e->m_value; + ps = currentPressure - e->m_value; PS->AddEvent(t, ps); // Pressure Support break; default: @@ -1273,7 +1308,7 @@ bool PRS1Import::ParseF5EventsFV3() } -bool PRS1DataChunk::ParseEventsF5V3() +bool PRS1DataChunk::ParseEventsF5V3(void) { if (this->family != 5 || this->familyVersion != 3) { qWarning() << "ParseEventsF5V3 called with family" << this->family << "familyVersion" << this->familyVersion; @@ -2815,25 +2850,25 @@ bool PRS1Import::ParseSummaryF3() QMap::iterator it; - if ((it=mainblock.find(0x0a)) != mainblock.end()) { + if ((it=summary->mainblock.find(0x0a)) != summary->mainblock.end()) { mode = MODE_CPAP; session->settings[CPAP_Pressure] = EventDataType(it.value()[0]/10.0f); - } else if ((it=mainblock.find(0x0d)) != mainblock.end()) { + } else if ((it=summary->mainblock.find(0x0d)) != summary->mainblock.end()) { mode = MODE_APAP; session->settings[CPAP_PressureMin] = EventDataType(it.value()[0]/10.0f); session->settings[CPAP_PressureMax] = EventDataType(it.value()[1]/10.0f); - } else if ((it=mainblock.find(0x0e)) != mainblock.end()) { + } else if ((it=summary->mainblock.find(0x0e)) != summary->mainblock.end()) { mode = MODE_BILEVEL_FIXED; session->settings[CPAP_EPAP] = ipap = EventDataType(it.value()[0] / 10.0f); session->settings[CPAP_IPAP] = epap = EventDataType(it.value()[1] / 10.0f); session->settings[CPAP_PS] = ipap - epap; - } else if ((it=mainblock.find(0x0f)) != mainblock.end()) { + } else if ((it=summary->mainblock.find(0x0f)) != summary->mainblock.end()) { mode = MODE_BILEVEL_AUTO_VARIABLE_PS; session->settings[CPAP_EPAPLo] = EventDataType(it.value()[0]/10.0f); session->settings[CPAP_IPAPHi] = EventDataType(it.value()[1]/10.0f); session->settings[CPAP_PSMin] = EventDataType(it.value()[2]/10.0f); session->settings[CPAP_PSMax] = EventDataType(it.value()[3]/10.0f); - } else if ((it=mainblock.find(0x10)) != mainblock.end()) { + } else if ((it=summary->mainblock.find(0x10)) != summary->mainblock.end()) { mode = MODE_APAP; // Disgusting APAP "IQ" trial session->settings[CPAP_PressureMin] = EventDataType(it.value()[0]/10.0f); session->settings[CPAP_PressureMax] = EventDataType(it.value()[1]/10.0f); @@ -2841,7 +2876,7 @@ bool PRS1Import::ParseSummaryF3() session->settings[CPAP_Mode] = (int)mode; - if ((it=hbdata.find(5)) != hbdata.end()) { + if ((it=summary->hbdata.find(5)) != summary->hbdata.end()) { summary_duration = (it.value()[1] << 8 ) + it.value()[0]; } @@ -3047,6 +3082,58 @@ bool PRS1Import::ParseSummaryF5V2() } bool PRS1Import::ParseSummaryF5V3() +{ + bool ok; + ok = summary->ParseSummaryF5V3(); + + CPAPMode cpapmode = MODE_ASV_VARIABLE_EPAP; + + session->settings[CPAP_Mode] = (int)cpapmode; + EventDataType epapHi, epapLo, minps, maxps; + + for (int i=0; i < summary->m_parsedData.count(); i++) { + PRS1ParsedEvent* e = summary->m_parsedData.at(i); + if (e->m_type != EV_PRS1_SETTING) { + qWarning() << "Summary had non-setting event:" << (int) e->m_type; + continue; + } + PRS1ParsedSettingEvent* s = (PRS1ParsedSettingEvent*) e; + switch (s->m_setting) { + case PRS1_SETTING_EPAP_MIN: + epapLo = e->value(); + session->settings[CPAP_EPAPLo] = epapLo; + break; + case PRS1_SETTING_EPAP_MAX: + epapHi = e->value(); + session->settings[CPAP_EPAPHi] = epapHi; + break; + case PRS1_SETTING_PS_MIN: + minps = e->value(); + session->settings[CPAP_PSMin] = minps; + break; + case PRS1_SETTING_PS_MAX: + maxps = e->value(); + session->settings[CPAP_PSMax] = maxps; + break; + default: + qWarning() << "Unknown PRS1 setting type" << (int) s->m_setting; + break; + } + } + + session->settings[CPAP_IPAPLo] = epapLo + minps; + session->settings[CPAP_IPAPHi] = qMin(25.0f, epapHi + maxps); + + if (!ok) { + return false; + } + summary_duration = summary->duration; + + return true; +} + + +bool PRS1DataChunk::ParseSummaryF5V3(void) { unsigned char * pressureBlock = (unsigned char *)mainblock[0x0a].data(); @@ -3057,25 +3144,17 @@ bool PRS1Import::ParseSummaryF5V3() EventDataType minps = pressureBlock[3] ; EventDataType maxps = pressureBlock[4]+epapLo; - - - CPAPMode cpapmode = MODE_ASV_VARIABLE_EPAP; - - session->settings[CPAP_Mode] = (int)cpapmode; - - session->settings[CPAP_EPAPLo] = epapLo/10.0f; - session->settings[CPAP_EPAPHi] = epapHi/10.0f; - session->settings[CPAP_IPAPLo] = (epapLo + minps)/10.0f; - session->settings[CPAP_IPAPHi] = qMin(25.0f, (epapHi+maxps)/10.0f); - session->settings[CPAP_PSMin] = minps/10.0f; - session->settings[CPAP_PSMax] = maxps/10.0f; + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_EPAP_MAX, epapHi)); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_EPAP_MIN, epapLo)); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS_MIN, minps)); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS_MAX, maxps)); if (hbdata[4].size() < 2) { - qDebug() << "summary missing duration section:" << session->session(); + qDebug() << "summary missing duration section:" << this->sessionid; return false; } unsigned char * durBlock = (unsigned char *)hbdata[4].data(); - summary_duration = durBlock[0] | durBlock[1] << 8; + this->duration = durBlock[0] | durBlock[1] << 8; return true; } @@ -3251,6 +3330,7 @@ bool PRS1Import::ParseSummary() session->set_first(qint64(summary->timestamp) * 1000L); + // TODO: The below is probably wrong. It should move to PRS1DataChunk when it gets fixed. /* Example data block 000000c6@0000: 00 [10] 01 [00 01 02 01 01 00 02 01 00 04 01 40 07 000000c6@0010: 01 60 1e 03 02 0c 14 2c 01 14 2d 01 40 2e 01 02 @@ -3279,14 +3359,14 @@ bool PRS1Import::ParseSummary() if (val != 1) { // store the data block for later reference - hbdata[val] = QByteArray((const char *)(&data[pos]), bsize); + summary->hbdata[val] = QByteArray((const char *)(&data[pos]), bsize); } else { // Parse the nested data structure which contains settings int p2 = 0; do { val = data[pos + p2++]; len = data[pos + p2++]; - mainblock[val] = QByteArray((const char *)(&data[pos+p2]), len); + summary->mainblock[val] = QByteArray((const char *)(&data[pos+p2]), len); p2 += len; } while ((p2 < bsize) && ((pos+p2) < size)); } diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.h b/oscar/SleepLib/loader_plugins/prs1_loader.h index 3043f05f..6c3f0486 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.h +++ b/oscar/SleepLib/loader_plugins/prs1_loader.h @@ -108,6 +108,9 @@ public: // V3 normal/non-waveform fields QMap hblock; + + QMap mainblock; + QMap hbdata; // Trailing common fields quint8 storedChecksum; // header checksum stored in file, last byte of m_header @@ -124,8 +127,11 @@ public: //! \brief Read the chunk's data from a PRS1 file and calculate its CRC, must be called after ReadHeader bool ReadData(class QFile & f); + //! \brief Parse a single data chunk from a .001 file containing summary data for a family 5 ASV family version 3 machine + bool ParseSummaryF5V3(void); + //! \brief Parse a single data chunk from a .002 file containing event data for a family 5 ASV family version 3 machine - bool ParseEventsF5V3(); + bool ParseEventsF5V3(void); protected: //! \brief Add a parsed event to the chunk @@ -173,9 +179,6 @@ public: QList waveforms; QList oximetry; - QMap mainblock; - QMap hbdata; - QString wavefile; QString oxifile; From a5b4851583b02327dec29ef8b6c074e600e00f6c Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Thu, 23 May 2019 12:17:21 -0400 Subject: [PATCH 04/31] Remove unused code from PRS1 event parsers. Also temporarily disable the time-consuming chunk unit test, since the current work is on conversion from chunk to sessions. --- oscar/SleepLib/loader_plugins/prs1_loader.cpp | 41 +------------------ oscar/tests/prs1tests.cpp | 2 +- 2 files changed, 2 insertions(+), 41 deletions(-) diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.cpp b/oscar/SleepLib/loader_plugins/prs1_loader.cpp index 00b8b481..e1a3979e 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.cpp +++ b/oscar/SleepLib/loader_plugins/prs1_loader.cpp @@ -1308,6 +1308,7 @@ bool PRS1Import::ParseF5EventsFV3() } +// 900X series bool PRS1DataChunk::ParseEventsF5V3(void) { if (this->family != 5 || this->familyVersion != 3) { @@ -1682,14 +1683,6 @@ bool PRS1Import::ParseF5Events() pos += 1; //tt-=delta; tt -= qint64(data1) * 1000L; - - if (!PB) { - if (!(PB = session->AddEventList(cpapcode, EVL_Event))) { - qDebug() << "!PB addeventlist exit"; - return false; - } - } - PB->AddEvent(tt, data0); break; @@ -1701,14 +1694,6 @@ bool PRS1Import::ParseF5Events() pos += 2; data1 = buffer[pos++]; tt = t - qint64(data1) * 1000L; - - if (!PB) { - if (!(PB = session->AddEventList(cpapcode, EVL_Event))) { - qDebug() << "!PB addeventlist exit"; - return false; - } - } - PB->AddEvent(tt, data0); } else { @@ -1758,13 +1743,6 @@ bool PRS1Import::ParseF5Events() SNORE->AddEvent(t, data2 = buffer[pos++]); // 08=Snore if (data2 > 0) { - if (!VS) { - if (!(VS = session->AddEventList(CPAP_VSnore, EVL_Event))) { - qDebug() << "!VS eventlist exit"; - return false; - } - } - VS->AddEvent(t, 0); //data2); // VSnore } @@ -1792,13 +1770,6 @@ bool PRS1Import::ParseF5Events() SNORE->AddEvent(t, data2 = buffer[pos+8]); //?? if (data2 > 0) { - if (!VS) { - if (!(VS = session->AddEventList(CPAP_VSnore, EVL_Event))) { - qDebug() << "!VS eventlist exit"; - return false; - } - } - VS->AddEvent(t, 0); //data2); // VSnore } data2 = data1 - data0; @@ -1869,16 +1840,6 @@ bool PRS1Import::ParseF5Events() session->m_cnt.clear(); session->m_cph.clear(); -// EventDataType minEpap = session->Min(CPAP_EPAP); -// EventDataType minIpapLo = session->Min(CPAP_IPAPLo); -// EventDataType maxIpapHi = session->Max(CPAP_IPAPHi); - -// session->settings[CPAP_IPAPLo] = minIpapLo; -// session->settings[CPAP_IPAPHi] = maxIpapHi; - -// session->settings[CPAP_PSMax] = maxIpapHi - minEpap; -// session->settings[CPAP_PSMin] = minIpapLo - minEpap; - session->m_valuesummary[CPAP_Pressure].clear(); session->m_valuesummary.erase(session->m_valuesummary.find(CPAP_Pressure)); diff --git a/oscar/tests/prs1tests.cpp b/oscar/tests/prs1tests.cpp index 16ffb412..4ac60375 100644 --- a/oscar/tests/prs1tests.cpp +++ b/oscar/tests/prs1tests.cpp @@ -224,7 +224,7 @@ void parseAndEmitChunkYaml(const QString & path) void PRS1Tests::testChunksToYaml() { - iterateTestCards(TESTDATA_PATH "prs1/input/", parseAndEmitChunkYaml); + //iterateTestCards(TESTDATA_PATH "prs1/input/", parseAndEmitChunkYaml); } From a4612a4f2464d822f33506ed5b4e4e0d60aaabae Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Fri, 24 May 2019 17:08:51 -0400 Subject: [PATCH 05/31] Split parsing from importing for F5V0, F5V1, F5V2 events. --- oscar/SleepLib/loader_plugins/prs1_loader.cpp | 293 +++++++++++------- oscar/SleepLib/loader_plugins/prs1_loader.h | 3 + 2 files changed, 186 insertions(+), 110 deletions(-) diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.cpp b/oscar/SleepLib/loader_plugins/prs1_loader.cpp index e1a3979e..8d46ff6d 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.cpp +++ b/oscar/SleepLib/loader_plugins/prs1_loader.cpp @@ -956,6 +956,7 @@ void PRS1Loader::ScanFiles(const QStringList & paths, int sessionid_base, Machin enum PRS1ParsedEventType { + EV_PRS1_UNKNOWN = 0, // these have their value graphed EV_PRS1_TB, EV_PRS1_OA, EV_PRS1_CA, @@ -965,6 +966,7 @@ enum PRS1ParsedEventType EV_PRS1_HY, EV_PRS1_IPAP, EV_PRS1_TOTLEAK, + EV_PRS1_LEAK, // F5V2: Is this real or actually TOTLEAK? EV_PRS1_IPAPLOW, EV_PRS1_IPAPHIGH, EV_PRS1_RR, @@ -1031,6 +1033,13 @@ protected: PRS1ParsedValueEvent(PRS1ParsedEventType type, int start, int value) : PRS1ParsedEvent(type, start) { m_value = value; } }; +class PRS1UnknownValueEvent : public PRS1ParsedValueEvent +{ +public: + int m_code; + PRS1UnknownValueEvent(int code, int start, int value, float gain=1.0) : PRS1ParsedValueEvent(EV_PRS1_UNKNOWN, start, value), m_code(code) { m_gain = gain; } +}; + class PRS1PressureEvent : public PRS1ParsedValueEvent { public: @@ -1118,6 +1127,12 @@ public: PRS1TotalLeakEvent(int start, int value) : PRS1ParsedValueEvent(EV_PRS1_TOTLEAK, start, value) {} }; +class PRS1LeakEvent : public PRS1ParsedValueEvent // TODO: F5V2 - is this real or actually TotalLeak? +{ +public: + PRS1LeakEvent(int start, int value) : PRS1ParsedValueEvent(EV_PRS1_LEAK, start, value) {} +}; + class PRS1IPAPHighEvent : public PRS1PressureEvent { public: @@ -1496,9 +1511,7 @@ bool PRS1Import::ParseF5Events() //EventList * PRESSURE=nullptr; //EventList * PP=nullptr; - EventDataType data0, data1, data2, data4, data5; - - EventDataType currentPressure=0, leak; //, p; + EventDataType currentPressure=0, leak, ps=0; //, p; bool calcLeaks = p_profile->cpap->calculateUnintentionalLeaks(); EventDataType lpm4 = p_profile->cpap->custom4cmH2OLeaks(); @@ -1511,7 +1524,120 @@ bool PRS1Import::ParseF5Events() //qint64 start=timestamp; qint64 t = qint64(event->timestamp) * 1000L; session->updateFirst(t); - qint64 tt; + + bool ok; + ok = event->ParseEventsF5V012(); + + for (int i=0; i < event->m_parsedData.count(); i++) { + PRS1ParsedEvent* e = event->m_parsedData.at(i); + t = qint64(event->timestamp + e->m_start) * 1000L; + + switch (e->m_type) { + case EV_PRS1_TB: + TB->AddEvent(t, e->m_duration); + break; + case EV_PRS1_OA: + OA->AddEvent(t, e->m_duration); + break; + case EV_PRS1_CA: + CA->AddEvent(t, e->m_duration); + break; + case EV_PRS1_FL: + FL->AddEvent(t, e->m_duration); + break; + case EV_PRS1_PB: + PB->AddEvent(t, e->m_duration); + break; + case EV_PRS1_LL: + LL->AddEvent(t, e->m_duration); + break; + case EV_PRS1_HY: + HY->AddEvent(t, e->m_duration); + break; + case EV_PRS1_IPAP: + IPAP->AddEvent(t, e->m_value); + currentPressure = e->m_value; + break; + case EV_PRS1_IPAPLOW: + IPAPLo->AddEvent(t, e->m_value); + break; + case EV_PRS1_IPAPHIGH: + IPAPHi->AddEvent(t, e->m_value); + break; + case EV_PRS1_TOTLEAK: + TOTLEAK->AddEvent(t, e->m_value); + leak = e->m_value; + if (calcLeaks) { // Much Quicker doing this here than the recalc method. + leak -= (((currentPressure/10.0f) - 4.0) * ppm + lpm4); + if (leak < 0) leak = 0; + LEAK->AddEvent(t, leak); + } + break; + case EV_PRS1_LEAK: // F5V2, is this really leak rather than totleak? + LEAK->AddEvent(t, e->m_value); + break; + case EV_PRS1_RR: + RR->AddEvent(t, e->m_value); + break; + case EV_PRS1_PTB: + PTB->AddEvent(t, e->m_value); + break; + case EV_PRS1_MV: + MV->AddEvent(t, e->m_value); + break; + case EV_PRS1_TV: + TV->AddEvent(t, e->m_value); + break; + case EV_PRS1_SNORE: + SNORE->AddEvent(t, e->m_value); + if (e->m_value > 0) { + VS->AddEvent(t, 0); //data2); // VSnore + } + break; + case EV_PRS1_EPAP: + EPAP->AddEvent(t, e->m_value); + ps = currentPressure - e->m_value; + PS->AddEvent(t, ps); // Pressure Support + break; + case EV_PRS1_UNKNOWN: + { + int code = ((PRS1UnknownValueEvent*) e)->m_code; + Q_ASSERT(code < ncodes); + if (!Code[code]) { + ChannelID cpapcode = Codes[(int)code]; + if (!(Code[code] = session->AddEventList(cpapcode, EVL_Event, e->m_gain))) { return false; } + } + Code[code]->AddEvent(t, e->m_value); + break; + } + default: + qWarning() << "Unknown PRS1 event type" << (int) e->m_type; + break; + } + } + + if (!ok) { + return false; + } + + t = qint64(event->timestamp + event->duration) * 1000L; + session->updateLast(t); + session->m_cnt.clear(); + session->m_cph.clear(); + + session->m_valuesummary[CPAP_Pressure].clear(); + session->m_valuesummary.erase(session->m_valuesummary.find(CPAP_Pressure)); + + return true; +} + + +// 950P is F5V0, 960P and 961P are F5V1, 960T is F5V2 +bool PRS1DataChunk::ParseEventsF5V012(void) +{ + EventDataType data0, data1, data2, data4, data5; + + int t = 0; int pos = 0; int cnt = 0; short delta;//,duration; @@ -1519,8 +1645,8 @@ bool PRS1Import::ParseF5Events() unsigned char lastcode3 = 0, lastcode2 = 0, lastcode = 0, code = 0; int lastpos = 0, startpos = 0, lastpos2 = 0, lastpos3 = 0; - int size = event->m_data.size(); - unsigned char * buffer = (unsigned char *)event->m_data.data(); + int size = this->m_data.size(); + unsigned char * buffer = (unsigned char *)this->m_data.data(); while (pos < size) { lastcode3 = lastcode2; @@ -1532,8 +1658,8 @@ bool PRS1Import::ParseF5Events() startpos = pos; code = buffer[pos++]; - if (code >= ncodes) { - qDebug() << "Illegal PRS1 code " << hex << int(code) << " appeared at " << hex << startpos << "in" << event->sessionid;; + if (code >= 0x13) { + qDebug() << "Illegal PRS1 code " << hex << int(code) << " appeared at " << hex << startpos << "in" << this->sessionid;; qDebug() << "1: (" << int(lastcode) << hex << lastpos << ")"; qDebug() << "2: (" << int(lastcode2) << hex << lastpos2 << ")"; qDebug() << "3: (" << int(lastcode3) << hex << lastpos3 << ")"; @@ -1541,17 +1667,16 @@ bool PRS1Import::ParseF5Events() } if (code == 0) { + // No delta } else if (code != 0x12) { delta = buffer[pos]; //duration=buffer[pos+1]; //delta=buffer[pos+1] << 8 | buffer[pos]; pos += 2; - t += qint64(delta) * 1000L; + t += delta; } - ChannelID cpapcode = Codes[(int)code]; //EventDataType PS; - tt = t; cnt++; int fc = 0; @@ -1574,11 +1699,7 @@ bool PRS1Import::ParseF5Events() break; case 0x01: // Unknown - if (!Code[1]) { - if (!(Code[1] = session->AddEventList(cpapcode, EVL_Event, 0.1F))) { return false; } - } - - Code[1]->AddEvent(t, 0); + this->AddEvent(new PRS1UnknownValueEvent(code, t, 0, 0.1F)); break; case 0x02: // Pressure ??? @@ -1602,57 +1723,47 @@ bool PRS1Import::ParseF5Events() case 0x04: // Timed Breath data0 = buffer[pos++]; - TB->AddEvent(t, data0); + this->AddEvent(new PRS1TimedBreathEvent(t, data0)); break; case 0x05: //code=CPAP_Obstructive; data0 = buffer[pos++]; - tt -= qint64(data0) * 1000L; // Subtract Time Offset - OA->AddEvent(tt, data0); + this->AddEvent(new PRS1ObstructiveApneaEvent(t - data0, data0)); break; case 0x06: //code=CPAP_ClearAirway; data0 = buffer[pos++]; - tt -= qint64(data0) * 1000L; // Subtract Time Offset - - CA->AddEvent(tt, data0); + this->AddEvent(new PRS1ClearAirwayEvent(t - data0, data0)); break; case 0x07: //code=CPAP_Hypopnea; data0 = buffer[pos++]; - tt -= qint64(data0) * 1000L; // Subtract Time Offset - HY->AddEvent(tt, data0); + this->AddEvent(new PRS1HypopneaEvent(t - data0, data0)); break; case 0x08: // ??? data0 = buffer[pos++]; - tt -= qint64(data0) * 1000L; // Subtract Time Offset //qDebug() << "Code 8 found at " << hex << pos - 1 << " " << tt; - if (event->familyVersion>=2) { - HY->AddEvent(tt, data0); + if (this->familyVersion>=2) { + this->AddEvent(new PRS1HypopneaEvent(t - data0, data0)); } else { - if (!Code[10]) { - if (!(Code[10] = session->AddEventList(cpapcode, EVL_Event))) { return false; } - } - + this->AddEvent(new PRS1UnknownValueEvent(code, t - data0, data0)); //???? //data1=buffer[pos++]; // ??? - Code[10]->AddEvent(tt, data0); //pos++; } break; case 0x09: // ASV Codes - if (event->familyVersion<2) { + if (this->familyVersion<2) { //code=CPAP_FlowLimit; data0 = buffer[pos++]; - tt -= qint64(data0) * 1000L; // Subtract Time Offset - FL->AddEvent(tt, data0); + this->AddEvent(new PRS1FlowLimitationEvent(t - data0, data0)); } else { data0 = buffer[pos++]; data1 = buffer[pos++]; @@ -1662,15 +1773,10 @@ bool PRS1Import::ParseF5Events() case 0x0a: data0 = buffer[pos++]; - tt -= qint64(data0) * 1000L; // Subtract Time Offset - if (event->familyVersion>=2) { - FL->AddEvent(tt, data0); + if (this->familyVersion>=2) { + this->AddEvent(new PRS1FlowLimitationEvent(t - data0, data0)); } else { - if (!Code[7]) { - if (!(Code[7] = session->AddEventList(cpapcode, EVL_Event))) { return false; } - } - - Code[7]->AddEvent(tt, data0); + this->AddEvent(new PRS1UnknownValueEvent(code, t - data0, data0)); } break; @@ -1682,74 +1788,52 @@ bool PRS1Import::ParseF5Events() data1 = ((unsigned char *)buffer)[pos]; //|buffer[pos+1] << 8 pos += 1; //tt-=delta; - tt -= qint64(data1) * 1000L; - PB->AddEvent(tt, data0); + this->AddEvent(new PRS1PeriodicBreathingEvent(t - data1, data0)); break; case 0x0c: - if (event->familyVersion>=2) { + if (this->familyVersion>=2) { data0 = (buffer[pos + 1] << 8 | buffer[pos]); data0 *= 2; pos += 2; data1 = buffer[pos++]; - tt = t - qint64(data1) * 1000L; - PB->AddEvent(tt, data0); + this->AddEvent(new PRS1PeriodicBreathingEvent(t - data1, data0)); } else { data0 = buffer[pos++]; - tt -= qint64(data0) * 1000L; // Subtract Time Offset - qDebug() << "Code 12 found at " << hex << pos - 1 << " " << tt; + qDebug() << "Code 12 found at " << hex << pos - 1 << " " << t - data0; - - if (!Code[8]) { - if (!(Code[8] = session->AddEventList(cpapcode, EVL_Event))) { return false; } - } - - Code[8]->AddEvent(tt, data0); + this->AddEvent(new PRS1UnknownValueEvent(code, t - data0, data0)); pos += 2; } break; case 0x0d: // All the other ASV graph stuff. - if (event->familyVersion>=2) { + if (this->familyVersion>=2) { data0 = (buffer[pos + 1] << 8 | buffer[pos]); data0 *= 2; pos += 2; data1 = buffer[pos++]; - tt = t - qint64(data1) * 1000L; + //tt = t - qint64(data1) * 1000L; } else { - IPAP->AddEvent(t, currentPressure = data0 = buffer[pos++]); // 00=IAP + this->AddEvent(new PRS1IPAPEvent(t, buffer[pos++])); // 00=IAP data4 = buffer[pos++]; - IPAPLo->AddEvent(t, data4); // 01=IAP Low + this->AddEvent(new PRS1IPAPLowEvent(t, data4)); // 01=IAP Low data5 = buffer[pos++]; - IPAPHi->AddEvent(t, data5); // 02=IAP High + this->AddEvent(new PRS1IPAPHighEvent(t, data5)); // 02=IAP High - TOTLEAK->AddEvent(t, leak=buffer[pos++]); // 03=LEAK - if (calcLeaks) { // Much Quicker doing this here than the recalc method. - leak -= (((currentPressure/10.0f) - 4.0) * ppm + lpm4); - if (leak < 0) leak = 0; + this->AddEvent(new PRS1TotalLeakEvent(t, buffer[pos++])); // 03=LEAK - LEAK->AddEvent(t, leak); - } - - - RR->AddEvent(t, buffer[pos++]); // 04=Breaths Per Minute - PTB->AddEvent(t, buffer[pos++]); // 05=Patient Triggered Breaths - MV->AddEvent(t, buffer[pos++]); // 06=Minute Ventilation + this->AddEvent(new PRS1RespiratoryRateEvent(t, buffer[pos++])); // 04=Breaths Per Minute + this->AddEvent(new PRS1PatientTriggeredBreathsEvent(t, buffer[pos++])); // 05=Patient Triggered Breaths + this->AddEvent(new PRS1MinuteVentilationEvent(t, buffer[pos++])); // 06=Minute Ventilation //tmp=buffer[pos++] * 10.0; - TV->AddEvent(t, buffer[pos++]); // 07=Tidal Volume - SNORE->AddEvent(t, data2 = buffer[pos++]); // 08=Snore - - if (data2 > 0) { - VS->AddEvent(t, 0); //data2); // VSnore - } - - EPAP->AddEvent(t, data1 = buffer[pos++]); // 09=EPAP - data2 = data0 - data1; - PS->AddEvent(t, data2); // Pressure Support - if (event->familyVersion >= 1) { + this->AddEvent(new PRS1TidalVolumeEvent(t, buffer[pos++])); // 07=Tidal Volume + this->AddEvent(new PRS1SnoreEvent(t, buffer[pos++])); // 08=Snore + this->AddEvent(new PRS1EPAPEvent(t, data1 = buffer[pos++])); // 09=EPAP + if (this->familyVersion >= 1) { data0 = buffer[pos++]; } } @@ -1757,23 +1841,17 @@ bool PRS1Import::ParseF5Events() case 0x0e: // Unknown // Family 5.2 has this code - if (event->familyVersion>=2) { - EPAP->AddEvent(t, data0=buffer[pos+9]); // 9 - IPAP->AddEvent(t, data1=buffer[pos+0]); // 0 - IPAPLo->AddEvent(t, buffer[pos+1]); // 1 - IPAPHi->AddEvent(t, buffer[pos+2]); // 2 - LEAK->AddEvent(t, buffer[pos+3]); // 3 - TV->AddEvent(t, buffer[pos+7]); // 7 - RR->AddEvent(t, buffer[pos+4]); // 4 - PTB->AddEvent(t, buffer[pos+5]); // 5 - MV->AddEvent(t, buffer[pos+6]); //6 - SNORE->AddEvent(t, data2 = buffer[pos+8]); //?? - - if (data2 > 0) { - VS->AddEvent(t, 0); //data2); // VSnore - } - data2 = data1 - data0; - PS->AddEvent(t, data2); // Pressure Support + if (this->familyVersion>=2) { + this->AddEvent(new PRS1IPAPEvent(t, data1=buffer[pos+0])); // 0 + this->AddEvent(new PRS1IPAPLowEvent(t, buffer[pos+1])); // 1 + this->AddEvent(new PRS1IPAPHighEvent(t, buffer[pos+2])); // 2 + this->AddEvent(new PRS1LeakEvent(t, buffer[pos+3])); // 3 // F5V2, is this really leak rather than totleak? + this->AddEvent(new PRS1TidalVolumeEvent(t, buffer[pos+7])); // 7 + this->AddEvent(new PRS1RespiratoryRateEvent(t, buffer[pos+4])); // 4 + this->AddEvent(new PRS1PatientTriggeredBreathsEvent(t, buffer[pos+5])); // 5 + this->AddEvent(new PRS1MinuteVentilationEvent(t, buffer[pos+6])); //6 + this->AddEvent(new PRS1SnoreEvent(t, buffer[pos+8])); //?? + this->AddEvent(new PRS1EPAPEvent(t, buffer[pos+9])); // 9 pos+=11; } else { qDebug() << "0x0E Observed in ASV data!!????"; @@ -1789,7 +1867,7 @@ bool PRS1Import::ParseF5Events() pos += 2; data1 = buffer[pos]; //|buffer[pos+1] << 8 pos += 1; - tt -= qint64(data1) * 1000L; + //tt -= qint64(data1) * 1000L; //session->AddEvent(new Event(tt,cpapcode, 0, data, 2)); break; @@ -1798,8 +1876,7 @@ bool PRS1Import::ParseF5Events() pos += 2; data1 = buffer[pos++]; - tt = t - qint64(data1) * 1000L; - LL->AddEvent(tt, data0); + this->AddEvent(new PRS1LargeLeakEvent(t - data1, data0)); // qDebug() << "0x10 Observed in ASV data!!????"; // data0 = buffer[pos++]; // << 8) | buffer[pos]; @@ -1835,13 +1912,7 @@ bool PRS1Import::ParseF5Events() break; } } - - session->updateLast(t); - session->m_cnt.clear(); - session->m_cph.clear(); - - session->m_valuesummary[CPAP_Pressure].clear(); - session->m_valuesummary.erase(session->m_valuesummary.find(CPAP_Pressure)); + this->duration = t; // The last event might start before t, so record the last delta timestamp. return true; @@ -2010,6 +2081,8 @@ bool PRS1Import::ParseF3EventsV3() } return true; } + + bool PRS1Import::ParseF3Events() { qint64 t = qint64(event->timestamp) * 1000L, tt; diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.h b/oscar/SleepLib/loader_plugins/prs1_loader.h index 6c3f0486..97dc9ae1 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.h +++ b/oscar/SleepLib/loader_plugins/prs1_loader.h @@ -130,6 +130,9 @@ public: //! \brief Parse a single data chunk from a .001 file containing summary data for a family 5 ASV family version 3 machine bool ParseSummaryF5V3(void); + //! \brief Parse a single data chunk from a .002 file containing event data for a family 5 ASV family version 0-2 machine + bool ParseEventsF5V012(void); + //! \brief Parse a single data chunk from a .002 file containing event data for a family 5 ASV family version 3 machine bool ParseEventsF5V3(void); From 5a88887cc3f3eacd9e581844a4a0151692fe3d4d Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Fri, 24 May 2019 19:41:42 -0400 Subject: [PATCH 06/31] Split parsing from importing for F3V6 events. --- oscar/SleepLib/loader_plugins/prs1_loader.cpp | 239 +++++++++++++----- oscar/SleepLib/loader_plugins/prs1_loader.h | 3 + 2 files changed, 177 insertions(+), 65 deletions(-) diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.cpp b/oscar/SleepLib/loader_plugins/prs1_loader.cpp index 8d46ff6d..33c19f11 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.cpp +++ b/oscar/SleepLib/loader_plugins/prs1_loader.cpp @@ -956,6 +956,7 @@ void PRS1Loader::ScanFiles(const QStringList & paths, int sessionid_base, Machin enum PRS1ParsedEventType { + EV_PRS1_RAW = -1, // these only get logged EV_PRS1_UNKNOWN = 0, // these have their value graphed EV_PRS1_TB, EV_PRS1_OA, @@ -975,6 +976,10 @@ enum PRS1ParsedEventType EV_PRS1_TV, EV_PRS1_SNORE, EV_PRS1_EPAP, + EV_PRS1_RERA, + EV_PRS1_NRI, + EV_PRS1_TEST1, + EV_PRS1_TEST2, EV_PRS1_SETTING, }; @@ -1040,6 +1045,22 @@ public: PRS1UnknownValueEvent(int code, int start, int value, float gain=1.0) : PRS1ParsedValueEvent(EV_PRS1_UNKNOWN, start, value), m_code(code) { m_gain = gain; } }; +class PRS1UnknownDataEvent : public PRS1ParsedEvent +{ +public: + int m_pos; + unsigned char m_code; + QByteArray m_data; + PRS1UnknownDataEvent(const QByteArray & data, int pos, int len=18) + : PRS1ParsedEvent(EV_PRS1_RAW, 0) + { + m_pos = pos; + m_data = data.mid(pos, len); + Q_ASSERT(m_data.size() >= 3); + m_code = m_data.at(0); + } +}; + class PRS1PressureEvent : public PRS1ParsedValueEvent { public: @@ -1186,6 +1207,30 @@ public: PRS1EPAPEvent(int start, int value) : PRS1PressureEvent(EV_PRS1_EPAP, start, value) {} }; +class PRS1RERAEvent : public PRS1ParsedValueEvent +{ +public: + PRS1RERAEvent(int start, int value) : PRS1ParsedValueEvent(EV_PRS1_RERA, start, value) {} +}; + +class PRS1NonRespondingEvent : public PRS1ParsedValueEvent // TODO: is this a single event or an index/hour? +{ +public: + PRS1NonRespondingEvent(int start, int value) : PRS1ParsedValueEvent(EV_PRS1_NRI, start, value) {} +}; + +class PRS1Test1Event : public PRS1ParsedValueEvent +{ +public: + PRS1Test1Event(int start, int value) : PRS1ParsedValueEvent(EV_PRS1_TEST1, start, value) {} +}; + +class PRS1Test2Event : public PRS1ParsedValueEvent +{ +public: + PRS1Test2Event(int start, int value) : PRS1ParsedValueEvent(EV_PRS1_TEST2, start, value) {} +}; + void PRS1DataChunk::AddEvent(PRS1ParsedEvent* const event) { m_parsedData.push_back(event); @@ -1922,7 +1967,7 @@ bool PRS1Import::ParseF3EventsV3() { // AVAPS machine... it's delta packed, unlike the older ones?? (double check that! :/) - EventList *PP = session->AddEventList(PRS1_TimedBreath, EVL_Event); + EventList *TB = session->AddEventList(PRS1_TimedBreath, EVL_Event); EventList *OA = session->AddEventList(CPAP_Obstructive, EVL_Event); EventList *HY = session->AddEventList(CPAP_Hypopnea, EVL_Event); EventList *ZZ = session->AddEventList(CPAP_NRI, EVL_Event); @@ -1944,12 +1989,115 @@ bool PRS1Import::ParseF3EventsV3() EventList *IPAP = session->AddEventList(CPAP_IPAP, EVL_Event, 0.1f); EventList *FLOW = session->AddEventList(CPAP_Test2, EVL_Event); - qint64 t = qint64(event->timestamp) * 1000L; //, tt; + qint64 t; + bool ok; + ok = event->ParseEventsF3V6(); + + for (int i=0; i < event->m_parsedData.count(); i++) { + PRS1ParsedEvent* e = event->m_parsedData.at(i); + t = qint64(event->timestamp + e->m_start) * 1000L; + + switch (e->m_type) { + case EV_PRS1_TB: + TB->AddEvent(t, e->m_duration); + break; + case EV_PRS1_OA: + OA->AddEvent(t, e->m_duration); + break; + case EV_PRS1_CA: + CA->AddEvent(t, e->m_duration); + break; + case EV_PRS1_PB: + PB->AddEvent(t, e->m_duration); + break; + case EV_PRS1_LL: + LL->AddEvent(t, e->m_duration); + break; + case EV_PRS1_HY: + HY->AddEvent(t, e->m_duration); + break; + case EV_PRS1_EPAP: + EPAP->AddEvent(t, e->m_value); + break; + case EV_PRS1_IPAP: + IPAP->AddEvent(t, e->m_value); + break; + case EV_PRS1_LEAK: // F3V6, is this really leak rather than totleak? + LEAK->AddEvent(t, e->m_value); + break; + case EV_PRS1_RR: + RR->AddEvent(t, e->m_value); + break; + case EV_PRS1_PTB: + PTB->AddEvent(t, e->m_value); + break; + case EV_PRS1_MV: + MV->AddEvent(t, e->m_value); + break; + case EV_PRS1_TV: + TV->AddEvent(t, e->m_value); + break; + case EV_PRS1_RERA: + RE->AddEvent(t, e->m_value); + break; + case EV_PRS1_NRI: + ZZ->AddEvent(t, e->m_value); + break; + case EV_PRS1_TEST1: + TMV->AddEvent(t, e->m_value); + break; + case EV_PRS1_TEST2: + FLOW->AddEvent(t, e->m_value); + break; + case EV_PRS1_RAW: + { + PRS1UnknownDataEvent* unk = (PRS1UnknownDataEvent*) e; + int code = unk->m_code; + char* data = unk->m_data.data(); + QString dump; + if (!loader->unknownCodes.contains(code)) { + loader->unknownCodes.insert(code, QStringList()); + } + QStringList & str = loader->unknownCodes[code]; + dump = QString("%1@0x%5: [%2] [%3 %4]") + .arg(event->sessionid, 8, 16, QChar('0')) + .arg(data[0], 2, 16, QChar('0')) + .arg(data[1], 2, 16, QChar('0')) + .arg(data[2], 2, 16, QChar('0')) + .arg(unk->m_pos, 5, 16, QChar('0')); + for (int i=3; im_data.size(); i++) { + dump += QString(" %1").arg(data[i], 2, 16, QChar('0')); + } + str.append(dump.trimmed()); + break; + } + default: + qWarning() << "Unknown PRS1 event type" << (int) e->m_type; + break; + } + } + if (!ok) { + return false; + } + + return true; +} + + +// 1030X, 11030X series +bool PRS1DataChunk::ParseEventsF3V6(void) +{ + if (this->family != 3 || this->familyVersion != 6) { + qWarning() << "ParseEventsF3V6 called with family" << this->family << "familyVersion" << this->familyVersion; + //break; // don't break to avoid changing behavior (for now) + } + + int t = 0; int pos = 0; - int datasize = event->m_data.size(); + int datasize = this->m_data.size(); - unsigned char * data = (unsigned char *)event->m_data.data(); + unsigned char * data = (unsigned char *)this->m_data.data(); unsigned char code; unsigned short delta; bool failed = false; @@ -1958,29 +2106,13 @@ bool PRS1Import::ParseF3EventsV3() QString dump; do { + int startpos = pos; code = data[pos++]; delta = (data[pos+1] < 8) | data[pos]; pos += 2; #ifdef DEBUG_EVENTS if (code == 0x00) { - if (!loader->unknownCodes.contains(code)) { - loader->unknownCodes.insert(code, QStringList()); - } - QStringList & str = loader->unknownCodes[code]; - - dump = QString("%1@0x%5: [%2] [%3 %4]") - .arg(session->session(), 8, 16, QChar('0')) - .arg(data[pos-3], 2, 16, QChar('0')) - .arg(data[pos-2], 2, 16, QChar('0')) - .arg(data[pos-1], 2, 16, QChar('0')) - .arg(pos-3, 5, 16, QChar('0')); - - for (int i=0; i<15; i++) { - if ((pos+i) > datasize) break; - dump += QString(" %1").arg(data[pos+i], 2, 16, QChar('0')); - } - str.append(dump.trimmed()); - + this->AddEvent(new PRS1UnknownDataEvent(this->m_data, startpos)); } #endif unsigned short epap; @@ -1988,95 +2120,72 @@ bool PRS1Import::ParseF3EventsV3() switch(code) { case 0x01: // Who knows val = data[pos++]; - PP->AddEvent(t, val); + this->AddEvent(new PRS1TimedBreathEvent(t, val)); break; case 0x02: - LEAK->AddEvent(t, data[pos+3]); - PTB->AddEvent(t, data[pos+5]); - MV->AddEvent(t, data[pos+6]); - TV->AddEvent(t, data[pos+7]); + this->AddEvent(new PRS1LeakEvent(t, data[pos+3])); + this->AddEvent(new PRS1PatientTriggeredBreathsEvent(t, data[pos+5])); + this->AddEvent(new PRS1MinuteVentilationEvent(t, data[pos+6])); + this->AddEvent(new PRS1TidalVolumeEvent(t, data[pos+7])); - EPAP->AddEvent(t, epap=data[pos+0]); - IPAP->AddEvent(t, data[pos+1]); - FLOW->AddEvent(t, data[pos+4]); - TMV->AddEvent(t, data[pos+8]); - RR->AddEvent(t, data[pos+9]); + this->AddEvent(new PRS1EPAPEvent(t, epap=data[pos+0])); + this->AddEvent(new PRS1IPAPEvent(t, data[pos+1])); + this->AddEvent(new PRS1Test2Event(t, data[pos+4])); // Flow??? + this->AddEvent(new PRS1Test1Event(t, data[pos+8])); // TMV??? + this->AddEvent(new PRS1RespiratoryRateEvent(t, data[pos+9])); pos += 12; break; case 0x04: // ??? val = data[pos++]; - PP->AddEvent(t, val); + this->AddEvent(new PRS1TimedBreathEvent(t, val)); break; case 0x05: // ??? val = data[pos++]; - CA->AddEvent(t, val); + this->AddEvent(new PRS1ClearAirwayEvent(t, val)); break; case 0x06: // Obstructive Apnea val = data[pos++]; val2 = data[pos++]; - OA->AddEvent(t + (qint64(val2)*1000L), val); + this->AddEvent(new PRS1ObstructiveApneaEvent(t + val2, val)); // ??? shouldn't this be t - val2? break; case 0x07: // PB val = data[pos+1] << 8 | data[pos]; pos += 2; val2 = data[pos++]; - PB->AddEvent(t - (qint64(val2)*1000L), val); + this->AddEvent(new PRS1PeriodicBreathingEvent(t - val2, val)); break; case 0x08: // RERA val = data[pos++]; - RE->AddEvent(t, val); + this->AddEvent(new PRS1RERAEvent(t, val)); break; case 0x09: // ??? val = data[pos+1] << 8 | data[pos]; pos += 2; val2 = data[pos++]; - LL->AddEvent(t - (qint64(val)*1000L), val2); + this->AddEvent(new PRS1LargeLeakEvent(t - val, val2)); break; case 0x0a: // ??? val = data[pos++]; - ZZ->AddEvent(t, val); + this->AddEvent(new PRS1NonRespondingEvent(t, val)); break; case 0x0b: // Hypopnea val = data[pos++]; - if (session->session() == 239) { - if (HY->count() == 0) { - qDebug() << t << delta << val << "hypopnea"; - } - } - HY->AddEvent(t, val); + this->AddEvent(new PRS1HypopneaEvent(t, val)); break; default: - if (!loader->unknownCodes.contains(code)) { - loader->unknownCodes.insert(code, QStringList()); - } - QStringList & str = loader->unknownCodes[code]; - - dump = QString("%1@0x%5: [%2] [%3 %4]") - .arg(session->session(), 8, 16, QChar('0')) - .arg(data[pos-3], 2, 16, QChar('0')) - .arg(data[pos-2], 2, 16, QChar('0')) - .arg(data[pos-1], 2, 16, QChar('0')) - .arg(pos-3, 5, 16, QChar('0')); - - for (int i=0; i<15; i++) { - if ((pos+i) > datasize) break; - dump += QString(" %1").arg(data[pos+i], 2, 16, QChar('0')); - } - str.append(dump.trimmed()); - + this->AddEvent(new PRS1UnknownDataEvent(this->m_data, startpos)); failed = true; break; }; - t += qint64(delta) * 1000L; + t += delta; } while ((pos < datasize) && !failed); if (failed) { - // Clean up this shit... return false; } return true; diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.h b/oscar/SleepLib/loader_plugins/prs1_loader.h index 97dc9ae1..b56a3325 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.h +++ b/oscar/SleepLib/loader_plugins/prs1_loader.h @@ -129,6 +129,9 @@ public: //! \brief Parse a single data chunk from a .001 file containing summary data for a family 5 ASV family version 3 machine bool ParseSummaryF5V3(void); + + //! \brief Parse a single data chunk from a .002 file containing event data for a family 3 ventilator family version 6 machine + bool ParseEventsF3V6(void); //! \brief Parse a single data chunk from a .002 file containing event data for a family 5 ASV family version 0-2 machine bool ParseEventsF5V012(void); From abac19bbf1020114ad473dd70cd2db9010441731 Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Fri, 24 May 2019 20:09:53 -0400 Subject: [PATCH 07/31] Split parsing from importing for F3V3 events. --- oscar/SleepLib/loader_plugins/prs1_loader.cpp | 109 +++++++++++++++--- oscar/SleepLib/loader_plugins/prs1_loader.h | 3 + 2 files changed, 94 insertions(+), 18 deletions(-) diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.cpp b/oscar/SleepLib/loader_plugins/prs1_loader.cpp index 33c19f11..9c5d384b 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.cpp +++ b/oscar/SleepLib/loader_plugins/prs1_loader.cpp @@ -978,6 +978,7 @@ enum PRS1ParsedEventType EV_PRS1_EPAP, EV_PRS1_RERA, EV_PRS1_NRI, + EV_PRS1_FLOWRATE, EV_PRS1_TEST1, EV_PRS1_TEST2, EV_PRS1_SETTING, @@ -1219,6 +1220,12 @@ public: PRS1NonRespondingEvent(int start, int value) : PRS1ParsedValueEvent(EV_PRS1_NRI, start, value) {} }; +class PRS1FlowRateEvent : public PRS1ParsedValueEvent // TODO: is this a single event or an index/hour? +{ +public: + PRS1FlowRateEvent(int start, int value) : PRS1ParsedValueEvent(EV_PRS1_FLOWRATE, start, value) {} +}; + class PRS1Test1Event : public PRS1ParsedValueEvent { public: @@ -2194,14 +2201,14 @@ bool PRS1DataChunk::ParseEventsF3V6(void) bool PRS1Import::ParseF3Events() { - qint64 t = qint64(event->timestamp) * 1000L, tt; + qint64 t = qint64(event->timestamp) * 1000L; session->updateFirst(t); EventList *OA = session->AddEventList(CPAP_Obstructive, EVL_Event); EventList *HY = session->AddEventList(CPAP_Hypopnea, EVL_Event); EventList *CA = session->AddEventList(CPAP_ClearAirway, EVL_Event); - EventList *LEAK = session->AddEventList(CPAP_LeakTotal, EVL_Event); - EventList *ULK = session->AddEventList(CPAP_Leak, EVL_Event); + EventList *TOTLEAK = session->AddEventList(CPAP_LeakTotal, EVL_Event); + EventList *LEAK = session->AddEventList(CPAP_Leak, EVL_Event); EventList *MV = session->AddEventList(CPAP_MinuteVent, EVL_Event); //EventList *TMV = session->AddEventList(CPAP_TgMV, EVL_Event); EventList *TV = session->AddEventList(CPAP_TidalVolume, EVL_Event,10.0f); @@ -2211,8 +2218,71 @@ bool PRS1Import::ParseF3Events() EventList *IPAP = session->AddEventList(CPAP_IPAP, EVL_Event,0.1f); EventList *FLOW = session->AddEventList(CPAP_FlowRate, EVL_Event); - int size = event->m_data.size()/0x10; - unsigned char * h = (unsigned char *)event->m_data.data(); + bool ok; + ok = event->ParseEventsF3V3(); + + for (int i=0; i < event->m_parsedData.count(); i++) { + PRS1ParsedEvent* e = event->m_parsedData.at(i); + t = qint64(event->timestamp + e->m_start) * 1000L; + + switch (e->m_type) { + case EV_PRS1_OA: + OA->AddEvent(t, e->m_duration); + break; + case EV_PRS1_CA: + CA->AddEvent(t, e->m_duration); + break; + case EV_PRS1_HY: + HY->AddEvent(t, e->m_duration); + break; + case EV_PRS1_EPAP: + EPAP->AddEvent(t, e->m_value); + break; + case EV_PRS1_IPAP: + IPAP->AddEvent(t, e->m_value); + break; + case EV_PRS1_TOTLEAK: + TOTLEAK->AddEvent(t, e->m_value); + break; + case EV_PRS1_LEAK: + LEAK->AddEvent(t, e->m_value); + break; + case EV_PRS1_RR: + RR->AddEvent(t, e->m_value); + break; + case EV_PRS1_PTB: + PTB->AddEvent(t, e->m_value); + break; + case EV_PRS1_MV: + MV->AddEvent(t, e->m_value); + break; + case EV_PRS1_TV: + TV->AddEvent(t, e->m_value); + break; + case EV_PRS1_FLOWRATE: + FLOW->AddEvent(t, e->m_value); + break; + default: + qWarning() << "Unknown PRS1 event type" << (int) e->m_type; + break; + } + } + + return true; +} + + +// 1160P series +bool PRS1DataChunk::ParseEventsF3V3(void) +{ + if (this->family != 3 || this->familyVersion != 3) { + qWarning() << "ParseEventsF3V3 called with family" << this->family << "familyVersion" << this->familyVersion; + //break; // don't break to avoid changing behavior (for now) + } + + int t = 0, tt; + int size = this->m_data.size()/0x10; + unsigned char * h = (unsigned char *)this->m_data.data(); int hy, oa, ca; qint64 div = 0; @@ -2223,31 +2293,33 @@ bool PRS1Import::ParseF3Events() // interleave for each channel = 1 // also warn on any remainder of data size % record size (but don't fail) - const qint64 block_duration = 120000; + const qint64 block_duration = 120; for (int x=0; x < size; x++) { - IPAP->AddEvent(t, h[0] | (h[1] << 8)); - EPAP->AddEvent(t, h[2] | (h[3] << 8)); - LEAK->AddEvent(t, h[4]); - TV->AddEvent(t, h[5]); - FLOW->AddEvent(t, h[6]); - PTB->AddEvent(t, h[7]); - RR->AddEvent(t, h[8]); + this->AddEvent(new PRS1IPAPEvent(t, h[0] | (h[1] << 8))); + this->AddEvent(new PRS1EPAPEvent(t, h[2] | (h[3] << 8))); + this->AddEvent(new PRS1TotalLeakEvent(t, h[4])); + this->AddEvent(new PRS1TidalVolumeEvent(t, h[5])); + this->AddEvent(new PRS1FlowRateEvent(t, h[6])); + this->AddEvent(new PRS1PatientTriggeredBreathsEvent(t, h[7])); + this->AddEvent(new PRS1RespiratoryRateEvent(t, h[8])); //TMV->AddEvent(t, h[9]); // not sure what this is.. encore doesn't graph it. - MV->AddEvent(t, h[11]); - ULK->AddEvent(t, h[15]); + // h[10]? + this->AddEvent(new PRS1MinuteVentilationEvent(t, h[11])); + this->AddEvent(new PRS1LeakEvent(t, h[15])); hy = h[12]; // count of hypopnea events ca = h[13]; // count of clear airway events oa = h[14]; // count of obstructive events // divide each event evenly over the 2 minute block + // TODO: revisit whether this is the right approach and should be done here? should the durations be hy or div? if (hy > 0) { div = block_duration / hy; tt = t; for (int i=0; i < hy; ++i) { - HY->AddEvent(t, hy); + this->AddEvent(new PRS1HypopneaEvent(t, hy)); tt += div; } } @@ -2257,7 +2329,7 @@ bool PRS1Import::ParseF3Events() tt = t; for (int i=0; i < ca; ++i) { - CA->AddEvent(tt, ca); + this->AddEvent(new PRS1ClearAirwayEvent(tt, ca)); tt += div; } } @@ -2266,7 +2338,7 @@ bool PRS1Import::ParseF3Events() tt = t; for (int i=0; i < oa; ++i) { - OA->AddEvent(t, oa); + this->AddEvent(new PRS1ObstructiveApneaEvent(t, oa)); tt += div; } } @@ -2277,6 +2349,7 @@ bool PRS1Import::ParseF3Events() return true; } + extern EventDataType CatmullRomSpline(EventDataType p0, EventDataType p1, EventDataType p2, EventDataType p3, EventDataType t = 0.5); void SmoothEventList(Session * session, EventList * ev, ChannelID code) diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.h b/oscar/SleepLib/loader_plugins/prs1_loader.h index b56a3325..13db0cb0 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.h +++ b/oscar/SleepLib/loader_plugins/prs1_loader.h @@ -130,6 +130,9 @@ public: //! \brief Parse a single data chunk from a .001 file containing summary data for a family 5 ASV family version 3 machine bool ParseSummaryF5V3(void); + //! \brief Parse a single data chunk from a .002 file containing event data for a family 3 ventilator family version 3 machine + bool ParseEventsF3V3(void); + //! \brief Parse a single data chunk from a .002 file containing event data for a family 3 ventilator family version 6 machine bool ParseEventsF3V6(void); From 0d8e37c94c87671dbf3e2b4e0bb3664c37ac98f9 Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Sat, 25 May 2019 17:00:44 -0400 Subject: [PATCH 08/31] Split parsing from importing for F0 events. --- oscar/SleepLib/loader_plugins/prs1_loader.cpp | 400 ++++++++++-------- oscar/SleepLib/loader_plugins/prs1_loader.h | 3 + 2 files changed, 234 insertions(+), 169 deletions(-) diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.cpp b/oscar/SleepLib/loader_plugins/prs1_loader.cpp index 9c5d384b..9dc34af5 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.cpp +++ b/oscar/SleepLib/loader_plugins/prs1_loader.cpp @@ -965,17 +965,21 @@ enum PRS1ParsedEventType EV_PRS1_PB, EV_PRS1_LL, EV_PRS1_HY, - EV_PRS1_IPAP, EV_PRS1_TOTLEAK, EV_PRS1_LEAK, // F5V2: Is this real or actually TOTLEAK? + EV_PRS1_PRESSURE, // TODO: maybe fold PRESSURE and IPAP into one + EV_PRS1_IPAP, EV_PRS1_IPAPLOW, EV_PRS1_IPAPHIGH, + EV_PRS1_EPAP, + EV_PRS1_FLEX, EV_PRS1_RR, EV_PRS1_PTB, EV_PRS1_MV, EV_PRS1_TV, EV_PRS1_SNORE, - EV_PRS1_EPAP, + EV_PRS1_VS, // F0: Is this different from SNORE? + EV_PRS1_PP, EV_PRS1_RERA, EV_PRS1_NRI, EV_PRS1_FLOWRATE, @@ -1137,12 +1141,6 @@ public: PRS1HypopneaEvent(int start, int duration) : PRS1ParsedDurationEvent(EV_PRS1_HY, start, duration) {} }; -class PRS1IPAPEvent : public PRS1PressureEvent -{ -public: - PRS1IPAPEvent(int start, int value) : PRS1PressureEvent(EV_PRS1_IPAP, start, value) {} -}; - class PRS1TotalLeakEvent : public PRS1ParsedValueEvent { public: @@ -1155,6 +1153,18 @@ public: PRS1LeakEvent(int start, int value) : PRS1ParsedValueEvent(EV_PRS1_LEAK, start, value) {} }; +class PRS1CPAPEvent : public PRS1PressureEvent +{ +public: + PRS1CPAPEvent(int start, int value) : PRS1PressureEvent(EV_PRS1_PRESSURE, start, value) {} +}; + +class PRS1IPAPEvent : public PRS1PressureEvent +{ +public: + PRS1IPAPEvent(int start, int value) : PRS1PressureEvent(EV_PRS1_IPAP, start, value) {} +}; + class PRS1IPAPHighEvent : public PRS1PressureEvent { public: @@ -1167,6 +1177,18 @@ public: PRS1IPAPLowEvent(int start, int value) : PRS1PressureEvent(EV_PRS1_IPAPLOW, start, value) {} }; +class PRS1EPAPEvent : public PRS1PressureEvent +{ +public: + PRS1EPAPEvent(int start, int value) : PRS1PressureEvent(EV_PRS1_EPAP, start, value) {} +}; + +class PRS1PressureReliefEvent : public PRS1PressureEvent // value is pressure applied during pressure relief, similar to EPAP +{ +public: + PRS1PressureReliefEvent(int start, int value) : PRS1PressureEvent(EV_PRS1_FLEX, start, value) {} +}; + class PRS1RespiratoryRateEvent : public PRS1ParsedValueEvent { public: @@ -1202,13 +1224,19 @@ public: PRS1SnoreEvent(int start, int value) : PRS1ParsedValueEvent(EV_PRS1_SNORE, start, value) {} }; -class PRS1EPAPEvent : public PRS1PressureEvent +class PRS1VibratorySnoreEvent : public PRS1ParsedValueEvent { public: - PRS1EPAPEvent(int start, int value) : PRS1PressureEvent(EV_PRS1_EPAP, start, value) {} + PRS1VibratorySnoreEvent(int start, int value) : PRS1ParsedValueEvent(EV_PRS1_VS, start, value) {} }; -class PRS1RERAEvent : public PRS1ParsedValueEvent +class PRS1PressurePulseEvent : public PRS1ParsedValueEvent +{ +public: + PRS1PressurePulseEvent(int start, int value) : PRS1ParsedValueEvent(EV_PRS1_PP, start, value) {} +}; + +class PRS1RERAEvent : public PRS1ParsedValueEvent // TODO: should this really be a duration event? { public: PRS1RERAEvent(int start, int value) : PRS1ParsedValueEvent(EV_PRS1_RERA, start, value) {} @@ -1226,7 +1254,7 @@ public: PRS1FlowRateEvent(int start, int value) : PRS1ParsedValueEvent(EV_PRS1_FLOWRATE, start, value) {} }; -class PRS1Test1Event : public PRS1ParsedValueEvent +class PRS1Test1Event : public PRS1ParsedValueEvent // TODO: replace test1/2 event with unknownvalue events and appropriate mapping { public: PRS1Test1Event(int start, int value) : PRS1ParsedValueEvent(EV_PRS1_TEST1, start, value) {} @@ -2415,17 +2443,20 @@ void SmoothEventList(Session * session, EventList * ev, ChannelID code) } + +// 750P is F0V2; 550P is F0V2/F0V3; 450P is F0V3; 460P, 560P[BT], 660P, 760P are F0V4 +// 200X, 400X, 400G, 500X, 502G, 600X, 700X are F0V6 bool PRS1Import::ParseF0Events() { - unsigned char code=0; + ChannelID Codes[] = { + PRS1_00, PRS1_01, 0, 0, 0, 0, 0, 0, 0, 0, 0, + PRS1_0B, 0, 0, PRS1_0E + }; + + int ncodes = sizeof(Codes) / sizeof(ChannelID); EventList *Code[0x20] = {0}; - EventDataType data0, data1, data2; - Q_UNUSED(data2) - int cnt = 0; - short delta; - int pos; - qint64 t = qint64(event->timestamp) * 1000L, tt; + qint64 t = qint64(event->timestamp) * 1000L; session->updateFirst(t); @@ -2444,7 +2475,7 @@ bool PRS1Import::ParseF0Events() EventList *VS2 = session->AddEventList(CPAP_VSnore2, EVL_Event); //EventList *T1 = session->AddEventList(CPAP_Test1, EVL_Event, 0.1); - Code[17] = session->AddEventList(PRS1_0E, EVL_Event); + Code[0x0e] = session->AddEventList(PRS1_0E, EVL_Event); EventList * LL = session->AddEventList(CPAP_LargeLeak, EVL_Event); EventList *PRESSURE = nullptr; @@ -2452,14 +2483,6 @@ bool PRS1Import::ParseF0Events() EventList *IPAP = nullptr; EventList *PS = nullptr; - unsigned char lastcode3 = 0, lastcode2 = 0, lastcode = 0; - int lastpos = 0, startpos = 0, lastpos2 = 0, lastpos3 = 0; - - int size = event->m_data.size(); - - bool FV3 = (event->fileVersion == 3); - unsigned char * buffer = (unsigned char *)event->m_data.data(); - EventDataType currentPressure=0, leak; //, p; bool calcLeaks = p_profile->cpap->calculateUnintentionalLeaks(); @@ -2471,6 +2494,145 @@ bool PRS1Import::ParseF0Events() CPAPMode mode = (CPAPMode) session->settings[CPAP_Mode].toInt(); + bool ok; + ok = event->ParseEventsF0(mode); + + for (int i=0; i < event->m_parsedData.count(); i++) { + PRS1ParsedEvent* e = event->m_parsedData.at(i); + t = qint64(event->timestamp + e->m_start) * 1000L; + + switch (e->m_type) { + case EV_PRS1_PRESSURE: + if (!PRESSURE) { + if (!(PRESSURE = session->AddEventList(CPAP_Pressure, EVL_Event, e->m_gain))) { return false; } + } + PRESSURE->AddEvent(t, e->m_value); + currentPressure = e->m_value; + break; + case EV_PRS1_IPAP: + if(!IPAP) { + if (!(IPAP = session->AddEventList(CPAP_IPAP, EVL_Event, e->m_gain))) { return false; } + } + IPAP->AddEvent(t, e->m_value); + currentPressure = e->m_value; + break; + case EV_PRS1_EPAP: + if (!EPAP) { + if (!(EPAP = session->AddEventList(CPAP_EPAP, EVL_Event, e->m_gain))) { return false; } + } + if(!PS) { + if (!(PS = session->AddEventList(CPAP_PS, EVL_Event, e->m_gain))) { return false; } + } + EPAP->AddEvent(t, e->m_value); + PS->AddEvent(t, currentPressure - e->m_value); + break; + case EV_PRS1_FLEX: + if (!EPAP) { + if (!(EPAP = session->AddEventList(CPAP_EPAP, EVL_Event, e->m_gain))) { return false; } + } + EPAP->AddEvent(t, e->m_value); + break; + case EV_PRS1_OA: + OA->AddEvent(t, e->m_duration); + break; + case EV_PRS1_CA: + CA->AddEvent(t, e->m_duration); + break; + case EV_PRS1_FL: + FL->AddEvent(t, e->m_duration); + break; + case EV_PRS1_PB: + PB->AddEvent(t, e->m_duration); + break; + case EV_PRS1_LL: + LL->AddEvent(t, e->m_duration); + break; + case EV_PRS1_HY: + HY->AddEvent(t, e->m_duration); + break; + case EV_PRS1_TOTLEAK: + TOTLEAK->AddEvent(t, e->m_value); + leak = e->m_value; + if (calcLeaks) { // Much Quicker doing this here than the recalc method. + leak -= (((currentPressure/10.0f) - 4.0) * ppm + lpm4); + if (leak < 0) leak = 0; + LEAK->AddEvent(t, leak); + } + break; + case EV_PRS1_SNORE: + SNORE->AddEvent(t, e->m_value); + if (e->m_value > 0) { + VS2->AddEvent(t, e->m_value); + } + break; + case EV_PRS1_VS: // F0: Is this really distinct from SNORE and VS2? + VS->AddEvent(t, 0); + break; + case EV_PRS1_RERA: + RE->AddEvent(t, e->m_value); + break; + case EV_PRS1_PP: + PP->AddEvent(t, e->m_value); + break; + case EV_PRS1_UNKNOWN: + { + int code = ((PRS1UnknownValueEvent*) e)->m_code; + Q_ASSERT(code < ncodes); + if (!Code[code]) { + ChannelID cpapcode = Codes[(int)code]; + Q_ASSERT(cpapcode); // any unknown codes returned by chunk parser should be given a channel above + if (!(Code[code] = session->AddEventList(cpapcode, EVL_Event, e->m_gain))) { return false; } + } + Code[code]->AddEvent(t, e->m_value); + break; + } + default: + qWarning() << "Unknown PRS1 event type" << (int) e->m_type; + break; + } + } + + if (!ok) { + return false; + } + + t = qint64(event->timestamp + event->duration) * 1000L; + +// SmoothEventList(session, PRESSURE, CPAP_Pressure); +// SmoothEventList(session, IPAP, CPAP_IPAP); +// SmoothEventList(session, EPAP, CPAP_EPAP); + + session->updateLast(t); + session->m_cnt.clear(); + session->m_cph.clear(); + session->m_lastchan.clear(); + session->m_firstchan.clear(); + session->m_valuesummary[CPAP_Pressure].clear(); + session->m_valuesummary.erase(session->m_valuesummary.find(CPAP_Pressure)); + + return true; +} + + +bool PRS1DataChunk::ParseEventsF0(CPAPMode mode) +{ + unsigned char code=0; + + EventDataType data0, data1, data2; + Q_UNUSED(data2) + int cnt = 0; + short delta; + int pos; + int t = 0; + + unsigned char lastcode3 = 0, lastcode2 = 0, lastcode = 0; + int lastpos = 0, startpos = 0, lastpos2 = 0, lastpos3 = 0; + + int size = this->m_data.size(); + + bool FV3 = (this->fileVersion == 3); + unsigned char * buffer = (unsigned char *)this->m_data.data(); + for (pos = 0; pos < size;) { lastcode3 = lastcode2; lastcode2 = lastcode; @@ -2482,7 +2644,7 @@ bool PRS1Import::ParseF0Events() code = buffer[pos++]; if (code > 0x15) { - qDebug() << "Illegal PRS1 code " << hex << int(code) << " appeared at " << hex << startpos << "in" << event->sessionid; + qDebug() << "Illegal PRS1 code " << hex << int(code) << " appeared at " << hex << startpos << "in" << this->sessionid; qDebug() << "1: (" << hex << int(lastcode) << hex << lastpos << ")"; qDebug() << "2: (" << hex << int(lastcode2) << hex << lastpos2 << ")"; qDebug() << "3: (" << hex << int(lastcode3) << hex << lastpos3 << ")"; @@ -2493,8 +2655,7 @@ bool PRS1Import::ParseF0Events() delta = buffer[pos + 1] << 8 | buffer[pos]; pos += 2; - t += qint64(delta) * 1000L; - tt = t; + t += delta; } cnt++; @@ -2502,128 +2663,70 @@ bool PRS1Import::ParseF0Events() switch (code) { case 0x00: // Unknown 00 - - if (!Code[0]) { - if (!(Code[0] = session->AddEventList(PRS1_00, EVL_Event))) { return false; } - } - - Code[0]->AddEvent(t, buffer[pos++]); - - if (((event->family == 0) && (event->familyVersion >= 4)) || (event->fileVersion == 3)){ + this->AddEvent(new PRS1UnknownValueEvent(code, t, buffer[pos++])); + if (((this->family == 0) && (this->familyVersion >= 4)) || (this->fileVersion == 3)){ pos++; } - break; case 0x01: // Unknown - if ((event->family == 0) && (event->familyVersion >= 4)) { - if (!PRESSURE) { - PRESSURE = session->AddEventList(CPAP_Pressure, EVL_Event, 0.1F); - - if (!PRESSURE) { return false; } - } - - PRESSURE->AddEvent(t, currentPressure = buffer[pos++]); + if ((this->family == 0) && (this->familyVersion >= 4)) { + this->AddEvent(new PRS1CPAPEvent(t, buffer[pos++])); } else { - if (!Code[1]) { - if (!(Code[1] = session->AddEventList(PRS1_01, EVL_Event))) { return false; } - } - - Code[1]->AddEvent(t, 0); + this->AddEvent(new PRS1UnknownValueEvent(code, t, 0)); } - break; case 0x02: // Pressure - if ((event->family == 0) && (event->familyVersion >= 4)) { // BiPAP Pressure - if (!EPAP) { - if (!(EPAP = session->AddEventList(CPAP_EPAP, EVL_Event, 0.1F))) { return false; } - } - if(!IPAP) { - if (!(IPAP = session->AddEventList(CPAP_IPAP, EVL_Event, 0.1F))) { return false; } - } - if(!PS) { - if (!(PS = session->AddEventList(CPAP_PS, EVL_Event, 0.1F))) { return false; } - } - - EPAP->AddEvent(t, data0 = buffer[pos++]); - IPAP->AddEvent(t, data1 = currentPressure = buffer[pos++]); - PS->AddEvent(t, data1 - data0); + if ((this->family == 0) && (this->familyVersion >= 4)) { // BiPAP Pressure + data0 = buffer[pos++]; + data1 = buffer[pos++]; + this->AddEvent(new PRS1IPAPEvent(t, data1)); + this->AddEvent(new PRS1EPAPEvent(t, data0)); // EPAP needs to be added second to calculate PS } else { - if (!PRESSURE) { - PRESSURE = session->AddEventList(CPAP_Pressure, EVL_Event, 0.1F); - - if (!PRESSURE) { return false; } - } - - PRESSURE->AddEvent(t, currentPressure = buffer[pos++]); + this->AddEvent(new PRS1CPAPEvent(t, buffer[pos++])); } - break; case 0x03: // BIPAP Pressure if (FV3) { - if (!PRESSURE) { - PRESSURE = session->AddEventList(CPAP_Pressure, EVL_Event, 0.1F); - - if (!PRESSURE) { return false; } - } - PRESSURE->AddEvent(t, currentPressure = buffer[pos++]); - + this->AddEvent(new PRS1CPAPEvent(t, buffer[pos++])); } else { - if (!EPAP) { - if (!(EPAP = session->AddEventList(CPAP_EPAP, EVL_Event, 0.1F))) { return false; } - } - if(!IPAP) { - if (!(IPAP = session->AddEventList(CPAP_IPAP, EVL_Event, 0.1F))) { return false; } - } - if(!PS) { - if (!(PS = session->AddEventList(CPAP_PS, EVL_Event, 0.1F))) { return false; } - } - - EPAP->AddEvent(t, data0 = buffer[pos++]); - IPAP->AddEvent(t, data1 = currentPressure = buffer[pos++]); - PS->AddEvent(t, data1 - data0); + data0 = buffer[pos++]; + data1 = buffer[pos++]; + this->AddEvent(new PRS1IPAPEvent(t, data1)); + this->AddEvent(new PRS1EPAPEvent(t, data0)); // EPAP needs to be added second to calculate PS } break; case 0x04: // Pressure Pulse data0 = buffer[pos++]; - - PP->AddEvent(t, data0); + this->AddEvent(new PRS1PressurePulseEvent(t, data0)); break; case 0x05: // RERA data0 = buffer[pos++]; - tt = t - (qint64(data0) * 1000L); - - RE->AddEvent(tt, data0); + this->AddEvent(new PRS1RERAEvent(t - data0, data0)); break; case 0x06: // Obstructive Apoanea data0 = buffer[pos++]; - tt = t - (qint64(data0) * 1000L); - OA->AddEvent(tt, data0); + this->AddEvent(new PRS1ObstructiveApneaEvent(t - data0, data0)); break; case 0x07: // Clear Airway data0 = buffer[pos++]; - tt = t - (qint64(data0) * 1000L); - - CA->AddEvent(tt, data0); + this->AddEvent(new PRS1ClearAirwayEvent(t - data0, data0)); break; case 0x0a: // Hypopnea data0 = buffer[pos++]; - tt = t - (qint64(data0) * 1000L); - HY->AddEvent(tt, data0); + this->AddEvent(new PRS1HypopneaEvent(t - data0, data0)); break; case 0x0c: // Flow Limitation data0 = buffer[pos++]; - tt = t - (qint64(data0) * 1000L); - - FL->AddEvent(tt, data0); + this->AddEvent(new PRS1FlowLimitationEvent(t - data0, data0)); break; case 0x0b: // Breathing not Detected flag???? but it doesn't line up @@ -2631,7 +2734,7 @@ bool PRS1Import::ParseF0Events() data1 = buffer[pos+1]; pos += 2; - if (event->familyVersion >= 4) { + if (this->familyVersion >= 4) { // might not doublerize on older machines? // data0 *= 2; } @@ -2639,90 +2742,63 @@ bool PRS1Import::ParseF0Events() //tt = t - qint64((data0+data1)*2) * 1000L; - if (!Code[12]) { - Code[12] = session->AddEventList(PRS1_0B, EVL_Event); - } - - // FIXME - Code[12]->AddEvent(t, data0); + this->AddEvent(new PRS1UnknownValueEvent(code, t, data0)); // FIXME break; case 0x0d: // Vibratory Snore - VS->AddEvent(t, 0); + this->AddEvent(new PRS1VibratorySnoreEvent(t, 0)); break; case 0x0e: // Unknown data0 = buffer[pos + 1] << 8 | buffer[pos]; - if (event->familyVersion >= 4) { + if (this->familyVersion >= 4) { // might not doublerize on older machines? data0 *= 2; } pos += 2; data1 = buffer[pos++]; - - tt = t - qint64(data1) * 1000L; - Code[17]->AddEvent(tt, data0); - + this->AddEvent(new PRS1UnknownValueEvent(code, t - data1, data0)); break; case 0x0f: // Cheyne Stokes Respiration data0 = (buffer[pos + 1] << 8 | buffer[pos]); - if (event->familyVersion >= 4) { + if (this->familyVersion >= 4) { // might not doublerize on older machines data0 *= 2; } pos += 2; data1 = buffer[pos++]; - tt = t - qint64(data1) * 1000L; - PB->AddEvent(tt, data0); + this->AddEvent(new PRS1PeriodicBreathingEvent(t - data1, data0)); break; case 0x10: // Large Leak data0 = buffer[pos + 1] << 8 | buffer[pos]; - if (event->familyVersion >= 4) { + if (this->familyVersion >= 4) { // might not doublerize on older machines data0 *= 2; } pos += 2; data1 = buffer[pos++]; - - tt = t - qint64(data1) * 1000L; - LL->AddEvent(tt, data0); + this->AddEvent(new PRS1LargeLeakEvent(t - data1, data0)); break; case 0x11: // Leak Rate & Snore Graphs data0 = buffer[pos++]; data1 = buffer[pos++]; + this->AddEvent(new PRS1TotalLeakEvent(t, data0)); + this->AddEvent(new PRS1SnoreEvent(t, data1)); - TOTLEAK->AddEvent(t, data0); - SNORE->AddEvent(t, data1); - - if (calcLeaks) { // Much Quicker doing this here than the recalc method. - leak = data0-(((currentPressure/10.0f) - 4.0) * ppm + lpm4); - if (leak < 0) leak = 0; - - LEAK->AddEvent(t, leak); - } - - if (data1 > 0) { - VS2->AddEvent(t, data1); - } - - if ((event->family == 0) && (event->familyVersion >= 4)) { + if ((this->family == 0) && (this->familyVersion >= 4)) { // EPAP / Flex Pressure data0 = buffer[pos++]; // Perhaps this check is not necessary, as it will theoretically add extra resolution to pressure chart // for bipap models and above??? if (mode <= MODE_BILEVEL_FIXED) { - if (!EPAP) { - if (!(EPAP = session->AddEventList(CPAP_EPAP, EVL_Event, 0.1F))) { return false; } - } - EPAP->AddEvent(t, data0); + this->AddEvent(new PRS1PressureReliefEvent(t, data0)); } } - break; case 0x12: // Summary @@ -2737,36 +2813,22 @@ bool PRS1Import::ParseF0Events() case 0x14: // DreamStation Hypopnea data0 = buffer[pos++]; - tt = t - (qint64(data0) * 1000L); - HY->AddEvent(tt, data0); + this->AddEvent(new PRS1HypopneaEvent(t - data0, data0)); break; case 0x15: // DreamStation Hypopnea data0 = buffer[pos++]; - tt = t - (qint64(data0) * 1000L); - HY->AddEvent(tt, data0); + this->AddEvent(new PRS1HypopneaEvent(t - data0, data0)); break; default: // ERROR!!! - qWarning() << "Some new fandangled PRS1 code detected in" << event->sessionid << hex + qWarning() << "Some new fandangled PRS1 code detected in" << this->sessionid << hex << int(code) << " at " << pos - 1; return false; } } - -// SmoothEventList(session, PRESSURE, CPAP_Pressure); -// SmoothEventList(session, IPAP, CPAP_IPAP); -// SmoothEventList(session, EPAP, CPAP_EPAP); - - - session->updateLast(t); - session->m_cnt.clear(); - session->m_cph.clear(); - session->m_lastchan.clear(); - session->m_firstchan.clear(); - session->m_valuesummary[CPAP_Pressure].clear(); - session->m_valuesummary.erase(session->m_valuesummary.find(CPAP_Pressure)); + this->duration = t; return true; } diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.h b/oscar/SleepLib/loader_plugins/prs1_loader.h index 13db0cb0..50ec55ee 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.h +++ b/oscar/SleepLib/loader_plugins/prs1_loader.h @@ -130,6 +130,9 @@ public: //! \brief Parse a single data chunk from a .001 file containing summary data for a family 5 ASV family version 3 machine bool ParseSummaryF5V3(void); + //! \brief Parse a single data chunk from a .002 file containing event data for a family 0 CPAP/APAP machine + bool ParseEventsF0(CPAPMode mode); + //! \brief Parse a single data chunk from a .002 file containing event data for a family 3 ventilator family version 3 machine bool ParseEventsF3V3(void); From 1cd7ac7c14a298911f8af8ea296b9183eee8b73d Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Sat, 25 May 2019 18:01:09 -0400 Subject: [PATCH 09/31] Merge identical F5V0 and F5V1 and remove unused F5V2 summary parsers. --- oscar/SleepLib/loader_plugins/prs1_loader.cpp | 98 +------------------ oscar/SleepLib/loader_plugins/prs1_loader.h | 8 +- 2 files changed, 6 insertions(+), 100 deletions(-) diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.cpp b/oscar/SleepLib/loader_plugins/prs1_loader.cpp index 9dc34af5..95e5e48a 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.cpp +++ b/oscar/SleepLib/loader_plugins/prs1_loader.cpp @@ -3189,7 +3189,7 @@ bool PRS1Import::ParseSummaryF3() return true; } -bool PRS1Import::ParseSummaryF5V0() +bool PRS1Import::ParseSummaryF5V012() { const unsigned char * data = (unsigned char *)summary->m_data.constData(); @@ -3269,96 +3269,6 @@ bool PRS1Import::ParseSummaryF5V0() return true; } -bool PRS1Import::ParseSummaryF5V1() -{ - const unsigned char * data = (unsigned char *)summary->m_data.constData(); - - CPAPMode cpapmode = MODE_UNKNOWN; - - int imin_epap = data[0x3]; - int imax_epap = data[0x4]; - int imin_ps = data[0x5]; - int imax_ps = data[0x6]; - int imax_pressure = data[0x2]; - - cpapmode = MODE_ASV_VARIABLE_EPAP; - - session->settings[CPAP_Mode] = (int)cpapmode; - - if (cpapmode == MODE_CPAP) { - session->settings[CPAP_Pressure] = imin_epap/10.0f; - - } else if (cpapmode == MODE_BILEVEL_FIXED) { - session->settings[CPAP_EPAP] = imin_epap/10.0f; - session->settings[CPAP_IPAP] = imax_epap/10.0f; - - } else if (cpapmode == MODE_ASV_VARIABLE_EPAP) { - //int imax_ipap = imax_epap + imax_ps; - int imin_ipap = imin_epap + imin_ps; - - session->settings[CPAP_EPAPLo] = imin_epap / 10.0f; - session->settings[CPAP_EPAPHi] = imax_epap / 10.0f; - session->settings[CPAP_IPAPLo] = imin_ipap / 10.0f; - session->settings[CPAP_IPAPHi] = imax_pressure / 10.0f; - session->settings[CPAP_PSMin] = imin_ps / 10.0f; - session->settings[CPAP_PSMax] = imax_ps / 10.0f; - } - - quint8 flex = data[0x0c]; - - int flexlevel = flex & 0x03; - FlexMode flexmode = FLEX_Unknown; - - flex &= 0xf8; - bool split = false; - - if (flex & 0x40) { // This bit defines the Flex setting for the CPAP component of the Split night - split = true; - } - if (flex & 0x80) { // CFlex bit - if (flex & 0x10) { - flexmode = FLEX_RiseTime; - } else if (flex & 8) { // Plus bit - if (split || (cpapmode == MODE_CPAP)) { - flexmode = FLEX_CFlexPlus; - } else if (cpapmode == MODE_APAP) { - flexmode = FLEX_AFlex; - } - } else { - // CFlex bits refer to Rise Time on BiLevel machines - flexmode = (cpapmode >= MODE_BILEVEL_FIXED) ? FLEX_BiFlex : FLEX_CFlex; - } - } else flexmode = FLEX_None; - - session->settings[PRS1_FlexMode] = (int)flexmode; - session->settings[PRS1_FlexLevel] = (int)flexlevel; - - - int ramp_time = data[0x0a]; - EventDataType ramp_pressure = EventDataType(data[0x0b]) / 10.0; - - session->settings[CPAP_RampTime] = (int)ramp_time; - session->settings[CPAP_RampPressure] = ramp_pressure; - - session->settings[PRS1_HumidStatus] = (bool)(data[0x0d] & 0x80); // Humidifier Connected - session->settings[PRS1_HeatedTubing] = (bool)(data[0x0d] & 0x10); // Heated Hose?? - session->settings[PRS1_HumidLevel] = (int)(data[0x0d] & 7); // Humidifier Value - - summary_duration = data[0x18] | data[0x19] << 8; - - return true; -} - -bool PRS1Import::ParseSummaryF5V2() -{ - const unsigned char * data = (unsigned char *)summary->m_data.constData(); - - //CPAPMode cpapmode = MODE_UNKNOWN; - summary_duration = data[0x18] | data[0x19] << 8; - - return true; -} - bool PRS1Import::ParseSummaryF5V3() { bool ok; @@ -3682,11 +3592,11 @@ bool PRS1Import::ParseSummary() break; case 5: if (summary->familyVersion == 1) { - return ParseSummaryF5V1(); + return ParseSummaryF5V012(); } else if (summary->familyVersion == 0) { - return ParseSummaryF5V0(); + return ParseSummaryF5V012(); } else if (summary->familyVersion == 2) { - return ParseSummaryF5V1(); + return ParseSummaryF5V012(); } else if (summary->familyVersion == 3) { return ParseSummaryF5V3(); } diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.h b/oscar/SleepLib/loader_plugins/prs1_loader.h index 50ec55ee..fd26d8af 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.h +++ b/oscar/SleepLib/loader_plugins/prs1_loader.h @@ -220,12 +220,8 @@ public: bool ParseSummaryF0V4(); //! \brief Summary parser for 1060 series AVAPS models bool ParseSummaryF3(); - //! \brief Summary parser for 50 series Family 5-0 BiPAP/AutoSV models - bool ParseSummaryF5V0(); - //! \brief Summary parser for 60 series Family 5-1 BiPAP/AutoSV models - bool ParseSummaryF5V1(); - //! \brief Summary parser for 60 series Family 5-2 BiPAP/AutoSV models - bool ParseSummaryF5V2(); + //! \brief Summary parser for 50 series Family 5-0 through 60 series Family 5-2 BiPAP/AutoSV models + bool ParseSummaryF5V012(); //! \brief Summary parser for 60 series Family 5-3 BiPAP/AutoSV models bool ParseSummaryF5V3(); From 6b1b7e225ca993f5b330ddc5690b950864123aaa Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Sat, 25 May 2019 19:21:46 -0400 Subject: [PATCH 10/31] Split parsing from importing for remaining F5 summaries. --- oscar/SleepLib/loader_plugins/prs1_loader.cpp | 135 +++++++++++++----- oscar/SleepLib/loader_plugins/prs1_loader.h | 5 +- 2 files changed, 104 insertions(+), 36 deletions(-) diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.cpp b/oscar/SleepLib/loader_plugins/prs1_loader.cpp index 95e5e48a..4cbfeee2 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.cpp +++ b/oscar/SleepLib/loader_plugins/prs1_loader.cpp @@ -997,12 +997,20 @@ enum PRS1ParsedEventUnit enum PRS1ParsedSettingType { + PRS1_SETTING_CPAP_MODE, PRS1_SETTING_EPAP_MIN, PRS1_SETTING_EPAP_MAX, PRS1_SETTING_IPAP_MIN, PRS1_SETTING_IPAP_MAX, PRS1_SETTING_PS_MIN, PRS1_SETTING_PS_MAX, + PRS1_SETTING_FLEX_MODE, + PRS1_SETTING_FLEX_LEVEL, + PRS1_SETTING_RAMP_TIME, + PRS1_SETTING_RAMP_PRESSURE, + PRS1_SETTING_HUMID_STATUS, + PRS1_SETTING_HUMID_LEVEL, + PRS1_SETTING_HEATED_TUBING, }; class PRS1ParsedEvent @@ -3191,7 +3199,76 @@ bool PRS1Import::ParseSummaryF3() bool PRS1Import::ParseSummaryF5V012() { - const unsigned char * data = (unsigned char *)summary->m_data.constData(); + bool ok; + ok = summary->ParseSummaryF5V012(); + + for (int i=0; i < summary->m_parsedData.count(); i++) { + PRS1ParsedEvent* e = summary->m_parsedData.at(i); + if (e->m_type != EV_PRS1_SETTING) { + qWarning() << "Summary had non-setting event:" << (int) e->m_type; + continue; + } + PRS1ParsedSettingEvent* s = (PRS1ParsedSettingEvent*) e; + switch (s->m_setting) { + case PRS1_SETTING_CPAP_MODE: + session->settings[CPAP_Mode] = e->m_value; + break; + case PRS1_SETTING_EPAP_MIN: + session->settings[CPAP_EPAPLo] = e->value(); + break; + case PRS1_SETTING_EPAP_MAX: + session->settings[CPAP_EPAPHi] = e->value(); + break; + case PRS1_SETTING_IPAP_MIN: + session->settings[CPAP_IPAPLo] = e->value(); + break; + case PRS1_SETTING_IPAP_MAX: + session->settings[CPAP_IPAPHi] = e->value(); + break; + case PRS1_SETTING_PS_MIN: + session->settings[CPAP_PSMin] = e->value(); + break; + case PRS1_SETTING_PS_MAX: + session->settings[CPAP_PSMax] = e->value(); + break; + case PRS1_SETTING_FLEX_MODE: + session->settings[PRS1_FlexMode] = e->m_value; + break; + case PRS1_SETTING_FLEX_LEVEL: + session->settings[PRS1_FlexLevel] = e->m_value; + break; + case PRS1_SETTING_RAMP_TIME: + session->settings[CPAP_RampTime] = e->m_value; + break; + case PRS1_SETTING_RAMP_PRESSURE: + session->settings[CPAP_RampPressure] = e->value(); + break; + case PRS1_SETTING_HUMID_STATUS: + session->settings[PRS1_HumidStatus] = (bool) e->m_value; + break; + case PRS1_SETTING_HEATED_TUBING: + session->settings[PRS1_HeatedTubing] = (bool) e->m_value; + break; + case PRS1_SETTING_HUMID_LEVEL: + session->settings[PRS1_HumidLevel] = e->m_value; + break; + default: + qWarning() << "Unknown PRS1 setting type" << (int) s->m_setting; + break; + } + } + + if (!ok) { + return false; + } + summary_duration = summary->duration; + + return true; +} + +bool PRS1DataChunk::ParseSummaryF5V012(void) +{ + const unsigned char * data = (unsigned char *)this->m_data.constData(); CPAPMode cpapmode = MODE_UNKNOWN; @@ -3202,28 +3279,15 @@ bool PRS1Import::ParseSummaryF5V012() int imax_pressure = data[0x2]; cpapmode = MODE_ASV_VARIABLE_EPAP; + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_CPAP_MODE, (int) cpapmode)); - session->settings[CPAP_Mode] = (int)cpapmode; - - if (cpapmode == MODE_CPAP) { - session->settings[CPAP_Pressure] = imin_epap/10.0f; - - } else if (cpapmode == MODE_BILEVEL_FIXED) { - session->settings[CPAP_EPAP] = imin_epap/10.0f; - session->settings[CPAP_IPAP] = imax_epap/10.0f; - - } else if (cpapmode == MODE_ASV_VARIABLE_EPAP) { - //int imax_ipap = imax_epap + imax_ps; - int imin_ipap = imin_epap + imin_ps; - - session->settings[CPAP_EPAPLo] = imin_epap / 10.0f; - session->settings[CPAP_EPAPHi] = imax_epap / 10.0f; - session->settings[CPAP_IPAPLo] = imin_ipap / 10.0f; - session->settings[CPAP_IPAPHi] = imax_pressure / 10.0f; - session->settings[CPAP_PSMin] = imin_ps / 10.0f; - session->settings[CPAP_PSMax] = imax_ps / 10.0f; - } - + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_EPAP_MIN, imin_epap)); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_EPAP_MAX, imax_epap)); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP_MIN, imin_epap + imin_ps)); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP_MAX, imax_pressure)); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS_MIN, imin_ps)); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS_MAX, imax_ps)); + quint8 flex = data[0x0c]; int flexlevel = flex & 0x03; @@ -3250,21 +3314,20 @@ bool PRS1Import::ParseSummaryF5V012() } } else flexmode = FLEX_None; - session->settings[PRS1_FlexMode] = (int)flexmode; - session->settings[PRS1_FlexLevel] = (int)flexlevel; - + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_FLEX_MODE, (int) flexmode)); + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_FLEX_LEVEL, flexlevel)); int ramp_time = data[0x0a]; - EventDataType ramp_pressure = EventDataType(data[0x0b]) / 10.0; + int ramp_pressure = data[0x0b]; - session->settings[CPAP_RampTime] = (int)ramp_time; - session->settings[CPAP_RampPressure] = ramp_pressure; + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_RAMP_TIME, ramp_time)); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_RAMP_PRESSURE, ramp_pressure)); - session->settings[PRS1_HumidStatus] = (bool)(data[0x0d] & 0x80); // Humidifier Connected - session->settings[PRS1_HeatedTubing] = (bool)(data[0x0d] & 0x10); // Heated Hose?? - session->settings[PRS1_HumidLevel] = (int)(data[0x0d] & 7); // Humidifier Value + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_STATUS, (data[0x0d] & 0x80) != 0)); // Humidifier Connected + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HEATED_TUBING, (data[0x0d] & 0x10) != 0)); // Heated Hose?? + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_LEVEL, (data[0x0d] & 7))); // Humidifier Value - summary_duration = data[0x18] | data[0x19] << 8; + this->duration = data[0x18] | data[0x19] << 8; return true; } @@ -3274,9 +3337,6 @@ bool PRS1Import::ParseSummaryF5V3() bool ok; ok = summary->ParseSummaryF5V3(); - CPAPMode cpapmode = MODE_ASV_VARIABLE_EPAP; - - session->settings[CPAP_Mode] = (int)cpapmode; EventDataType epapHi, epapLo, minps, maxps; for (int i=0; i < summary->m_parsedData.count(); i++) { @@ -3287,6 +3347,9 @@ bool PRS1Import::ParseSummaryF5V3() } PRS1ParsedSettingEvent* s = (PRS1ParsedSettingEvent*) e; switch (s->m_setting) { + case PRS1_SETTING_CPAP_MODE: + session->settings[CPAP_Mode] = e->m_value; + break; case PRS1_SETTING_EPAP_MIN: epapLo = e->value(); session->settings[CPAP_EPAPLo] = epapLo; @@ -3323,6 +3386,8 @@ bool PRS1Import::ParseSummaryF5V3() bool PRS1DataChunk::ParseSummaryF5V3(void) { + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_CPAP_MODE, (int) MODE_ASV_VARIABLE_EPAP)); + unsigned char * pressureBlock = (unsigned char *)mainblock[0x0a].data(); EventDataType epapHi = pressureBlock[0]; diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.h b/oscar/SleepLib/loader_plugins/prs1_loader.h index fd26d8af..1fd3d247 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.h +++ b/oscar/SleepLib/loader_plugins/prs1_loader.h @@ -127,6 +127,9 @@ public: //! \brief Read the chunk's data from a PRS1 file and calculate its CRC, must be called after ReadHeader bool ReadData(class QFile & f); + //! \brief Parse a single data chunk from a .001 file containing summary data for a family 5 ASV family version 0-2 machine + bool ParseSummaryF5V012(void); + //! \brief Parse a single data chunk from a .001 file containing summary data for a family 5 ASV family version 3 machine bool ParseSummaryF5V3(void); @@ -220,7 +223,7 @@ public: bool ParseSummaryF0V4(); //! \brief Summary parser for 1060 series AVAPS models bool ParseSummaryF3(); - //! \brief Summary parser for 50 series Family 5-0 through 60 series Family 5-2 BiPAP/AutoSV models + //! \brief Summary parser for 50 series Family 5-0 through 5-2 BiPAP/AutoSV models bool ParseSummaryF5V012(); //! \brief Summary parser for 60 series Family 5-3 BiPAP/AutoSV models bool ParseSummaryF5V3(); From b6c35c7610f7a87a7355e3527fda7e85e1cf19fe Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Sat, 25 May 2019 19:43:35 -0400 Subject: [PATCH 11/31] Split parsing from importing for F3 (probably V6) summaries. This looks discouragingly redundant at first glance, but eventually the import stage will probably be unified across all families, leaving the parsers the only difference. That'll happen after all summary importers have been split. --- oscar/SleepLib/loader_plugins/prs1_loader.cpp | 147 ++++++++++++------ oscar/SleepLib/loader_plugins/prs1_loader.h | 3 + 2 files changed, 102 insertions(+), 48 deletions(-) diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.cpp b/oscar/SleepLib/loader_plugins/prs1_loader.cpp index 4cbfeee2..07f05eaf 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.cpp +++ b/oscar/SleepLib/loader_plugins/prs1_loader.cpp @@ -998,10 +998,16 @@ enum PRS1ParsedEventUnit enum PRS1ParsedSettingType { PRS1_SETTING_CPAP_MODE, + PRS1_SETTING_PRESSURE, + PRS1_SETTING_PRESSURE_MIN, + PRS1_SETTING_PRESSURE_MAX, + PRS1_SETTING_EPAP, PRS1_SETTING_EPAP_MIN, PRS1_SETTING_EPAP_MAX, + PRS1_SETTING_IPAP, PRS1_SETTING_IPAP_MIN, PRS1_SETTING_IPAP_MAX, + PRS1_SETTING_PS, PRS1_SETTING_PS_MIN, PRS1_SETTING_PS_MAX, PRS1_SETTING_FLEX_MODE, @@ -3130,73 +3136,118 @@ bool PRS1Import::ParseSummaryF0V4() bool PRS1Import::ParseSummaryF3() +{ + bool ok; + ok = summary->ParseSummaryF3(); + + for (int i=0; i < summary->m_parsedData.count(); i++) { + PRS1ParsedEvent* e = summary->m_parsedData.at(i); + if (e->m_type != EV_PRS1_SETTING) { + qWarning() << "Summary had non-setting event:" << (int) e->m_type; + continue; + } + PRS1ParsedSettingEvent* s = (PRS1ParsedSettingEvent*) e; + switch (s->m_setting) { + case PRS1_SETTING_CPAP_MODE: + session->settings[CPAP_Mode] = e->m_value; + break; + case PRS1_SETTING_PRESSURE: + session->settings[CPAP_Pressure] = e->value(); + break; + case PRS1_SETTING_PRESSURE_MIN: + session->settings[CPAP_PressureMin] = e->value(); + break; + case PRS1_SETTING_PRESSURE_MAX: + session->settings[CPAP_PressureMax] = e->value(); + break; + case PRS1_SETTING_EPAP: + session->settings[CPAP_EPAP] = e->value(); + break; + case PRS1_SETTING_IPAP: + session->settings[CPAP_IPAP] = e->value(); + break; + case PRS1_SETTING_PS: + session->settings[CPAP_PS] = e->value(); + break; + case PRS1_SETTING_EPAP_MIN: + session->settings[CPAP_EPAPLo] = e->value(); + break; + case PRS1_SETTING_EPAP_MAX: + session->settings[CPAP_EPAPHi] = e->value(); + break; + case PRS1_SETTING_IPAP_MIN: + session->settings[CPAP_IPAPLo] = e->value(); + break; + case PRS1_SETTING_IPAP_MAX: + session->settings[CPAP_IPAPHi] = e->value(); + break; + case PRS1_SETTING_PS_MIN: + session->settings[CPAP_PSMin] = e->value(); + break; + case PRS1_SETTING_PS_MAX: + session->settings[CPAP_PSMax] = e->value(); + break; + default: + qWarning() << "Unknown PRS1 setting type" << (int) s->m_setting; + break; + } + } + + if (!ok) { + return false; + } + summary_duration = summary->duration; + + return true; +} + + +// TODO: This is probably only F3V6, as it uses mainblock, only present in fileVersion 3. +bool PRS1DataChunk::ParseSummaryF3(void) { CPAPMode mode = MODE_UNKNOWN; EventDataType epap, ipap; QMap::iterator it; - if ((it=summary->mainblock.find(0x0a)) != summary->mainblock.end()) { + if ((it=this->mainblock.find(0x0a)) != this->mainblock.end()) { mode = MODE_CPAP; - session->settings[CPAP_Pressure] = EventDataType(it.value()[0]/10.0f); - } else if ((it=summary->mainblock.find(0x0d)) != summary->mainblock.end()) { + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PRESSURE, it.value()[0])); + } else if ((it=this->mainblock.find(0x0d)) != this->mainblock.end()) { mode = MODE_APAP; - session->settings[CPAP_PressureMin] = EventDataType(it.value()[0]/10.0f); - session->settings[CPAP_PressureMax] = EventDataType(it.value()[1]/10.0f); - } else if ((it=summary->mainblock.find(0x0e)) != summary->mainblock.end()) { + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PRESSURE_MIN, it.value()[0])); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PRESSURE_MAX, it.value()[1])); + } else if ((it=this->mainblock.find(0x0e)) != this->mainblock.end()) { mode = MODE_BILEVEL_FIXED; - session->settings[CPAP_EPAP] = ipap = EventDataType(it.value()[0] / 10.0f); - session->settings[CPAP_IPAP] = epap = EventDataType(it.value()[1] / 10.0f); - session->settings[CPAP_PS] = ipap - epap; - } else if ((it=summary->mainblock.find(0x0f)) != summary->mainblock.end()) { + ipap = it.value()[0]; + epap = it.value()[1]; + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP, ipap)); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_EPAP, epap)); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS, ipap - epap)); + } else if ((it=this->mainblock.find(0x0f)) != this->mainblock.end()) { mode = MODE_BILEVEL_AUTO_VARIABLE_PS; - session->settings[CPAP_EPAPLo] = EventDataType(it.value()[0]/10.0f); - session->settings[CPAP_IPAPHi] = EventDataType(it.value()[1]/10.0f); - session->settings[CPAP_PSMin] = EventDataType(it.value()[2]/10.0f); - session->settings[CPAP_PSMax] = EventDataType(it.value()[3]/10.0f); - } else if ((it=summary->mainblock.find(0x10)) != summary->mainblock.end()) { + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_EPAP_MIN, it.value()[0])); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP_MAX, it.value()[1])); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS_MIN, it.value()[2])); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS_MAX, it.value()[3])); + } else if ((it=this->mainblock.find(0x10)) != this->mainblock.end()) { mode = MODE_APAP; // Disgusting APAP "IQ" trial - session->settings[CPAP_PressureMin] = EventDataType(it.value()[0]/10.0f); - session->settings[CPAP_PressureMax] = EventDataType(it.value()[1]/10.0f); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PRESSURE_MIN, it.value()[0])); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PRESSURE_MAX, it.value()[1])); } - session->settings[CPAP_Mode] = (int)mode; + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_CPAP_MODE, (int) mode)); - if ((it=summary->hbdata.find(5)) != summary->hbdata.end()) { - summary_duration = (it.value()[1] << 8 ) + it.value()[0]; + if ((it=this->hbdata.find(5)) != this->hbdata.end()) { + this->duration = (it.value()[1] << 8 ) + it.value()[0]; + } else { + qWarning() << "missing summary duration"; } -/* QDateTime date = QDateTime::fromMSecsSinceEpoch(session->first()); - if (date.date() == QDate(2018,5,1)) { - - qDebug() << "Dumping session" << (int)session->session() << "summary file"; - QString hexstr = QString("%1@0000: ").arg(session->session(),8,16,QChar('0')); - const unsigned char * data = (const unsigned char *)summary->m_data.constData(); - int size = summary->m_data.size(); - for (int i=0; isession(),8,16,QChar('0')).arg(i+1, 4, 16, QChar('0')); - } - } - qDebug() << "Dumping mainblock"; - for (auto it=mainblock.cbegin(), end=mainblock.cend(); it!=end; ++it) { - qDebug() << it.key() << it.value().toHex(); - } - qDebug() << "Dumping hbdata"; - for (auto it=hbdata.cbegin(), end=hbdata.cend(); it!=end; ++it) { - qDebug() << it.key() << it.value().toHex(); - } - - qDebug() << "In date"; - } */ - - return true; } + bool PRS1Import::ParseSummaryF5V012() { bool ok; diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.h b/oscar/SleepLib/loader_plugins/prs1_loader.h index 1fd3d247..281a40bf 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.h +++ b/oscar/SleepLib/loader_plugins/prs1_loader.h @@ -127,6 +127,9 @@ public: //! \brief Read the chunk's data from a PRS1 file and calculate its CRC, must be called after ReadHeader bool ReadData(class QFile & f); + //! \brief Parse a single data chunk from a .001 file containing summary data for a family 3 ventilator (family version 6?) machine + bool ParseSummaryF3(void); + //! \brief Parse a single data chunk from a .001 file containing summary data for a family 5 ASV family version 0-2 machine bool ParseSummaryF5V012(void); From e49c947b6b309633aa539892af0c3e8afb69ed3e Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Sat, 25 May 2019 21:08:53 -0400 Subject: [PATCH 12/31] Split parsing from importing for F0V6 summaries. --- oscar/SleepLib/loader_plugins/prs1_loader.cpp | 110 ++++++++++++++---- oscar/SleepLib/loader_plugins/prs1_loader.h | 3 + 2 files changed, 91 insertions(+), 22 deletions(-) diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.cpp b/oscar/SleepLib/loader_plugins/prs1_loader.cpp index 07f05eaf..6e853ecc 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.cpp +++ b/oscar/SleepLib/loader_plugins/prs1_loader.cpp @@ -3465,12 +3465,79 @@ bool PRS1DataChunk::ParseSummaryF5V3(void) bool PRS1Import::ParseSummaryF0V6() +{ + bool ok; + ok = summary->ParseSummaryF0V6(); + + for (int i=0; i < summary->m_parsedData.count(); i++) { + PRS1ParsedEvent* e = summary->m_parsedData.at(i); + if (e->m_type != EV_PRS1_SETTING) { + qWarning() << "Summary had non-setting event:" << (int) e->m_type; + continue; + } + PRS1ParsedSettingEvent* s = (PRS1ParsedSettingEvent*) e; + switch (s->m_setting) { + case PRS1_SETTING_CPAP_MODE: + session->settings[CPAP_Mode] = e->m_value; + break; + case PRS1_SETTING_PRESSURE: + session->settings[CPAP_Pressure] = e->value(); + break; + case PRS1_SETTING_PRESSURE_MIN: + session->settings[CPAP_PressureMin] = e->value(); + break; + case PRS1_SETTING_PRESSURE_MAX: + session->settings[CPAP_PressureMax] = e->value(); + break; + case PRS1_SETTING_EPAP: + session->settings[CPAP_EPAP] = e->value(); + break; + case PRS1_SETTING_IPAP: + session->settings[CPAP_IPAP] = e->value(); + break; + case PRS1_SETTING_PS: + session->settings[CPAP_PS] = e->value(); + break; + case PRS1_SETTING_EPAP_MIN: + session->settings[CPAP_EPAPLo] = e->value(); + break; + case PRS1_SETTING_EPAP_MAX: + session->settings[CPAP_EPAPHi] = e->value(); + break; + case PRS1_SETTING_IPAP_MIN: + session->settings[CPAP_IPAPLo] = e->value(); + break; + case PRS1_SETTING_IPAP_MAX: + session->settings[CPAP_IPAPHi] = e->value(); + break; + case PRS1_SETTING_PS_MIN: + session->settings[CPAP_PSMin] = e->value(); + break; + case PRS1_SETTING_PS_MAX: + session->settings[CPAP_PSMax] = e->value(); + break; + default: + qWarning() << "Unknown PRS1 setting type" << (int) s->m_setting; + break; + } + } + + if (!ok) { + return false; + } + summary_duration = summary->duration; + + return true; +} + + +bool PRS1DataChunk::ParseSummaryF0V6() { // DreamStation machines... // APAP models.. - const unsigned char * data = (unsigned char *)summary->m_data.constData(); + const unsigned char * data = (unsigned char *)this->m_data.constData(); CPAPMode cpapmode = MODE_UNKNOWN; @@ -3489,11 +3556,11 @@ bool PRS1Import::ParseSummaryF0V6() // first, verify that this dataSize is where we expect // each var pair in headerblock should be (indexByte, valueByte) - if ((int)summary->m_headerblock[(1 * 2)] != 0x01) { + if ((int)this->m_headerblock[(1 * 2)] != 0x01) { return false; //nope, not here - qDebug() << "PRS1Loader::ParseSummaryF0V6=" << "Bad datablock length"; + qDebug() << "PRS1DataChunk::ParseSummaryF0V6=" << "Bad datablock length"; } - int dataBlockSize = summary->m_headerblock[(1 * 2) + 1]; + int dataBlockSize = this->m_headerblock[(1 * 2) + 1]; //int zero = 0; const unsigned char *dataPtr; @@ -3506,25 +3573,25 @@ bool PRS1Import::ParseSummaryF0V6() break; case 10: // 0x0a cpapmode = MODE_CPAP; - if (dataPtr[1] != 1) qDebug() << "PRS1Loader::ParseSummaryF0V6=" << "Bad CPAP value"; + if (dataPtr[1] != 1) qDebug() << "PRS1DataChunk::ParseSummaryF0V6=" << "Bad CPAP value"; imin_epap = dataPtr[2]; break; case 13: // 0x0d cpapmode = MODE_APAP; - if (dataPtr[1] != 2) qDebug() << "PRS1Loader::ParseSummaryF0V6=" << "Bad APAP value"; + if (dataPtr[1] != 2) qDebug() << "PRS1DataChunk::ParseSummaryF0V6=" << "Bad APAP value"; min_pressure = dataPtr[2]; max_pressure = dataPtr[3]; break; case 14: // 0x0e // <--- this is a total guess.. might be 3 and have a pressure support value cpapmode = MODE_BILEVEL_FIXED; - if (dataPtr[1] != 2) qDebug() << "PRS1Loader::ParseSummaryF0V6=" << "Bad APAP value"; + if (dataPtr[1] != 2) qDebug() << "PRS1DataChunk::ParseSummaryF0V6=" << "Bad APAP value"; min_pressure = dataPtr[2]; max_pressure = dataPtr[3]; imin_ps = max_pressure - min_pressure; break; case 15: // 0x0f cpapmode = MODE_BILEVEL_AUTO_VARIABLE_PS; //might be C_CHECK? - if (dataPtr[1] != 4) qDebug() << "PRS1Loader::ParseSummaryF0V6=" << "Bad APAP value"; + if (dataPtr[1] != 4) qDebug() << "PRS1DataChunk::ParseSummaryF0V6=" << "Bad APAP value"; min_pressure = dataPtr[2]; max_pressure = dataPtr[3]; imin_ps = dataPtr[4]; @@ -3532,7 +3599,7 @@ bool PRS1Import::ParseSummaryF0V6() break; case 0x10: // Auto Trial mode cpapmode = MODE_APAP; - if (dataPtr[1] != 3) qDebug() << "PRS1Loader::ParseSummaryF0V6=" << "Bad APAP value"; + if (dataPtr[1] != 3) qDebug() << "PRS1DataChunk::ParseSummaryF0V6=" << "Bad APAP value"; min_pressure = dataPtr[3]; max_pressure = dataPtr[4]; break; @@ -3597,25 +3664,24 @@ bool PRS1Import::ParseSummaryF0V6() }*/ // need to populate summary-> - summary_duration = duration; - session->settings[CPAP_Mode] = (int)cpapmode; + this->duration = duration; + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_CPAP_MODE, (int) cpapmode)); if (cpapmode == MODE_CPAP) { - session->settings[CPAP_Pressure] = imin_epap/10.0f; + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PRESSURE, imin_epap)); } else if (cpapmode == MODE_APAP) { - session->settings[CPAP_PressureMin] = min_pressure/10.0f; - session->settings[CPAP_PressureMax] = max_pressure/10.0f; + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PRESSURE_MIN, min_pressure)); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PRESSURE_MAX, max_pressure)); } else if (cpapmode == MODE_BILEVEL_FIXED) { // Guessing here.. haven't seen BIPAP data. - session->settings[CPAP_EPAP] = min_pressure/10.0f; - session->settings[CPAP_IPAP] = max_pressure/10.0f; - session->settings[CPAP_PS] = imin_ps/10.0f; + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_EPAP, min_pressure)); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP, max_pressure)); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS, imin_ps)); } else if (cpapmode == MODE_BILEVEL_AUTO_VARIABLE_PS) { - session->settings[CPAP_EPAPLo] = min_pressure/10.0f; - session->settings[CPAP_IPAPHi] = max_pressure/10.0f; - session->settings[CPAP_PSMin] = imin_ps/10.0f; - session->settings[CPAP_PSMax] = imax_ps/10.0f; - + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_EPAP_MIN, min_pressure)); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP_MAX, max_pressure)); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS_MIN, imin_ps)); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS_MAX, imax_ps)); } return true; diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.h b/oscar/SleepLib/loader_plugins/prs1_loader.h index 281a40bf..33bef49d 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.h +++ b/oscar/SleepLib/loader_plugins/prs1_loader.h @@ -127,6 +127,9 @@ public: //! \brief Read the chunk's data from a PRS1 file and calculate its CRC, must be called after ReadHeader bool ReadData(class QFile & f); + //! \brief Parse a single data chunk from a .001 file containing summary data for a family 0 CPAP/APAP family version 6 machine + bool ParseSummaryF0V6(void); + //! \brief Parse a single data chunk from a .001 file containing summary data for a family 3 ventilator (family version 6?) machine bool ParseSummaryF3(void); From 7a153a65167d61de421eb21e0e8661d8a6504a9c Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Sat, 25 May 2019 21:25:56 -0400 Subject: [PATCH 13/31] Split parsing from importing for F0V4 summaries. --- oscar/SleepLib/loader_plugins/prs1_loader.cpp | 145 ++++++++++++++---- oscar/SleepLib/loader_plugins/prs1_loader.h | 3 + 2 files changed, 120 insertions(+), 28 deletions(-) diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.cpp b/oscar/SleepLib/loader_plugins/prs1_loader.cpp index 6e853ecc..bb57c034 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.cpp +++ b/oscar/SleepLib/loader_plugins/prs1_loader.cpp @@ -3044,9 +3044,98 @@ bool PRS1Import::ParseSummaryF0() return true; } + bool PRS1Import::ParseSummaryF0V4() { - const unsigned char * data = (unsigned char *)summary->m_data.constData(); + bool ok; + ok = summary->ParseSummaryF0V4(); + + for (int i=0; i < summary->m_parsedData.count(); i++) { + PRS1ParsedEvent* e = summary->m_parsedData.at(i); + if (e->m_type != EV_PRS1_SETTING) { + qWarning() << "Summary had non-setting event:" << (int) e->m_type; + continue; + } + PRS1ParsedSettingEvent* s = (PRS1ParsedSettingEvent*) e; + switch (s->m_setting) { + case PRS1_SETTING_CPAP_MODE: + session->settings[CPAP_Mode] = e->m_value; + break; + case PRS1_SETTING_PRESSURE: + session->settings[CPAP_Pressure] = e->value(); + break; + case PRS1_SETTING_PRESSURE_MIN: + session->settings[CPAP_PressureMin] = e->value(); + break; + case PRS1_SETTING_PRESSURE_MAX: + session->settings[CPAP_PressureMax] = e->value(); + break; + case PRS1_SETTING_EPAP: + session->settings[CPAP_EPAP] = e->value(); + break; + case PRS1_SETTING_IPAP: + session->settings[CPAP_IPAP] = e->value(); + break; + case PRS1_SETTING_PS: + session->settings[CPAP_PS] = e->value(); + break; + case PRS1_SETTING_EPAP_MIN: + session->settings[CPAP_EPAPLo] = e->value(); + break; + case PRS1_SETTING_EPAP_MAX: + session->settings[CPAP_EPAPHi] = e->value(); + break; + case PRS1_SETTING_IPAP_MIN: + session->settings[CPAP_IPAPLo] = e->value(); + break; + case PRS1_SETTING_IPAP_MAX: + session->settings[CPAP_IPAPHi] = e->value(); + break; + case PRS1_SETTING_PS_MIN: + session->settings[CPAP_PSMin] = e->value(); + break; + case PRS1_SETTING_PS_MAX: + session->settings[CPAP_PSMax] = e->value(); + break; + case PRS1_SETTING_FLEX_MODE: + session->settings[PRS1_FlexMode] = e->m_value; + break; + case PRS1_SETTING_FLEX_LEVEL: + session->settings[PRS1_FlexLevel] = e->m_value; + break; + case PRS1_SETTING_RAMP_TIME: + session->settings[CPAP_RampTime] = e->m_value; + break; + case PRS1_SETTING_RAMP_PRESSURE: + session->settings[CPAP_RampPressure] = e->value(); + break; + case PRS1_SETTING_HUMID_STATUS: + session->settings[PRS1_HumidStatus] = (bool) e->m_value; + break; + case PRS1_SETTING_HEATED_TUBING: + session->settings[PRS1_HeatedTubing] = (bool) e->m_value; + break; + case PRS1_SETTING_HUMID_LEVEL: + session->settings[PRS1_HumidLevel] = e->m_value; + break; + default: + qWarning() << "Unknown PRS1 setting type" << (int) s->m_setting; + break; + } + } + + if (!ok) { + return false; + } + summary_duration = summary->duration; + + return true; +} + + +bool PRS1DataChunk::ParseSummaryF0V4(void) +{ + const unsigned char * data = (unsigned char *)this->m_data.constData(); CPAPMode cpapmode = MODE_UNKNOWN; @@ -3064,30 +3153,29 @@ bool PRS1Import::ParseSummaryF0V4() cpapmode = MODE_BILEVEL_AUTO_VARIABLE_PS; } - - EventDataType min_pressure = EventDataType(data[0x03]) / 10.0; - EventDataType max_pressure = EventDataType(data[0x04]) / 10.0; - EventDataType min_ps = EventDataType(data[0x05]) / 10.0; // pressure support - EventDataType max_ps = EventDataType(data[0x06]) / 10.0; // pressure support + int min_pressure = data[0x03]; + int max_pressure = data[0x04]; + int min_ps = data[0x05]; // pressure support + int max_ps = data[0x06]; // pressure support if (cpapmode == MODE_CPAP) { - session->settings[CPAP_Pressure] = min_pressure; + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PRESSURE, min_pressure)); } else if (cpapmode == MODE_APAP) { - session->settings[CPAP_PressureMin] = min_pressure; - session->settings[CPAP_PressureMax] = max_pressure; + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PRESSURE_MIN, min_pressure)); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PRESSURE_MAX, max_pressure)); } else if (cpapmode == MODE_BILEVEL_FIXED) { - session->settings[CPAP_EPAP] = min_pressure; - session->settings[CPAP_IPAP] = max_pressure; - session->settings[CPAP_PS] = max_pressure - min_pressure; + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_EPAP, min_pressure)); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP, max_pressure)); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS, max_pressure - min_pressure)); } else if (cpapmode == MODE_BILEVEL_AUTO_VARIABLE_PS) { - session->settings[CPAP_EPAPLo] = min_pressure; - session->settings[CPAP_EPAPHi] = max_pressure; - session->settings[CPAP_IPAPLo] = min_pressure + min_ps; - session->settings[CPAP_IPAPHi] = max_pressure; - session->settings[CPAP_PSMin] = min_ps; - session->settings[CPAP_PSMax] = max_ps; + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_EPAP_MIN, min_pressure)); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_EPAP_MAX, max_pressure)); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP_MIN, min_pressure + min_ps)); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP_MAX, max_pressure)); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS_MIN, min_ps)); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS_MAX, max_ps)); } - session->settings[CPAP_Mode] = (int)cpapmode; + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_CPAP_MODE, (int) cpapmode)); quint8 flex = data[0x0a]; @@ -3116,20 +3204,21 @@ bool PRS1Import::ParseSummaryF0V4() } else flexmode = FLEX_None; int ramp_time = data[0x08]; - EventDataType ramp_pressure = EventDataType(data[0x09]) / 10.0; + int ramp_pressure = data[0x09]; - session->settings[CPAP_RampTime] = (int)ramp_time; - session->settings[CPAP_RampPressure] = ramp_pressure; + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_RAMP_TIME, ramp_time)); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_RAMP_PRESSURE, ramp_pressure)); - session->settings[PRS1_FlexMode] = (int)flexmode; - session->settings[PRS1_FlexLevel] = (int)flexlevel; + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_FLEX_MODE, (int) flexmode)); + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_FLEX_LEVEL, flexlevel)); - session->settings[PRS1_HumidStatus] = (bool)(data[0x0b] & 0x80); // Humidifier Connected - session->settings[PRS1_HeatedTubing] = (bool)(data[0x0b] & 0x10); // Heated Hose?? - session->settings[PRS1_HumidLevel] = (int)(data[0x0b] & 7); // Humidifier Value + int humid = data[0x0b]; + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_STATUS, (humid & 0x80) != 0)); // Humidifier Connected + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HEATED_TUBING, (humid & 0x10) != 0)); // Heated Hose?? + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_LEVEL, (humid & 7))); // Humidifier Value - summary_duration = data[0x14] | data[0x15] << 8; + this->duration = data[0x14] | data[0x15] << 8; return true; } diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.h b/oscar/SleepLib/loader_plugins/prs1_loader.h index 33bef49d..0869a884 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.h +++ b/oscar/SleepLib/loader_plugins/prs1_loader.h @@ -127,6 +127,9 @@ public: //! \brief Read the chunk's data from a PRS1 file and calculate its CRC, must be called after ReadHeader bool ReadData(class QFile & f); + //! \brief Parse a single data chunk from a .001 file containing summary data for a family 0 CPAP/APAP family version 4 machine + bool ParseSummaryF0V4(void); + //! \brief Parse a single data chunk from a .001 file containing summary data for a family 0 CPAP/APAP family version 6 machine bool ParseSummaryF0V6(void); From eb758602216ceecf425c7a3181bd0547d2450ba7 Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Sat, 25 May 2019 21:57:29 -0400 Subject: [PATCH 14/31] Split parsing from importing for F0V2 and F0V3 summaries. --- oscar/SleepLib/loader_plugins/prs1_loader.cpp | 190 ++++++++++++++---- oscar/SleepLib/loader_plugins/prs1_loader.h | 5 +- 2 files changed, 159 insertions(+), 36 deletions(-) diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.cpp b/oscar/SleepLib/loader_plugins/prs1_loader.cpp index bb57c034..57fcd708 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.cpp +++ b/oscar/SleepLib/loader_plugins/prs1_loader.cpp @@ -1017,6 +1017,14 @@ enum PRS1ParsedSettingType PRS1_SETTING_HUMID_STATUS, PRS1_SETTING_HUMID_LEVEL, PRS1_SETTING_HEATED_TUBING, + PRS1_SETTING_SYSTEMONE_RESIST_LOCK, + PRS1_SETTING_SYSTEMONE_RESIST_SETTING, + PRS1_SETTING_SYSTEMONE_RESIST_STATUS, + PRS1_SETTING_HOSE_DIAMETER, + PRS1_SETTING_AUTO_ON, + PRS1_SETTING_AUTO_OFF, + PRS1_SETTING_MASK_ALERT, + PRS1_SETTING_SHOW_AHI, }; class PRS1ParsedEvent @@ -2937,9 +2945,119 @@ bool PRS1Import::ParseCompliance() return true; } -bool PRS1Import::ParseSummaryF0() + +bool PRS1Import::ParseSummaryF0V23() { - const unsigned char * data = (unsigned char *)summary->m_data.constData(); + bool ok; + ok = summary->ParseSummaryF0V23(); + + for (int i=0; i < summary->m_parsedData.count(); i++) { + PRS1ParsedEvent* e = summary->m_parsedData.at(i); + if (e->m_type != EV_PRS1_SETTING) { + qWarning() << "Summary had non-setting event:" << (int) e->m_type; + continue; + } + PRS1ParsedSettingEvent* s = (PRS1ParsedSettingEvent*) e; + switch (s->m_setting) { + case PRS1_SETTING_CPAP_MODE: + session->settings[CPAP_Mode] = e->m_value; + break; + case PRS1_SETTING_PRESSURE: + session->settings[CPAP_Pressure] = e->value(); + break; + case PRS1_SETTING_PRESSURE_MIN: + session->settings[CPAP_PressureMin] = e->value(); + break; + case PRS1_SETTING_PRESSURE_MAX: + session->settings[CPAP_PressureMax] = e->value(); + break; + case PRS1_SETTING_EPAP: + session->settings[CPAP_EPAP] = e->value(); + break; + case PRS1_SETTING_IPAP: + session->settings[CPAP_IPAP] = e->value(); + break; + case PRS1_SETTING_PS: + session->settings[CPAP_PS] = e->value(); + break; + case PRS1_SETTING_EPAP_MIN: + session->settings[CPAP_EPAPLo] = e->value(); + break; + case PRS1_SETTING_EPAP_MAX: + session->settings[CPAP_EPAPHi] = e->value(); + break; + case PRS1_SETTING_IPAP_MIN: + session->settings[CPAP_IPAPLo] = e->value(); + break; + case PRS1_SETTING_IPAP_MAX: + session->settings[CPAP_IPAPHi] = e->value(); + break; + case PRS1_SETTING_PS_MIN: + session->settings[CPAP_PSMin] = e->value(); + break; + case PRS1_SETTING_PS_MAX: + session->settings[CPAP_PSMax] = e->value(); + break; + case PRS1_SETTING_FLEX_MODE: + session->settings[PRS1_FlexMode] = e->m_value; + break; + case PRS1_SETTING_FLEX_LEVEL: + session->settings[PRS1_FlexLevel] = e->m_value; + break; + case PRS1_SETTING_RAMP_TIME: + session->settings[CPAP_RampTime] = e->m_value; + break; + case PRS1_SETTING_RAMP_PRESSURE: + session->settings[CPAP_RampPressure] = e->value(); + break; + case PRS1_SETTING_HUMID_STATUS: + session->settings[PRS1_HumidStatus] = (bool) e->m_value; + break; + case PRS1_SETTING_HUMID_LEVEL: + session->settings[PRS1_HumidLevel] = e->m_value; + break; + case PRS1_SETTING_SYSTEMONE_RESIST_LOCK: + session->settings[PRS1_SysLock] = (bool) e->m_value; + break; + case PRS1_SETTING_SYSTEMONE_RESIST_SETTING: + session->settings[PRS1_SysOneResistSet] = e->m_value; + break; + case PRS1_SETTING_SYSTEMONE_RESIST_STATUS: + session->settings[PRS1_SysOneResistStat] = (bool) e->m_value; + break; + case PRS1_SETTING_HOSE_DIAMETER: + session->settings[PRS1_HoseDiam] = e->m_value == 15 ? QObject::tr("15mm") : QObject::tr("22mm"); + break; + case PRS1_SETTING_AUTO_ON: + session->settings[PRS1_AutoOn] = (bool) e->m_value; + break; + case PRS1_SETTING_AUTO_OFF: + session->settings[PRS1_AutoOff] = (bool) e->m_value; + break; + case PRS1_SETTING_MASK_ALERT: + session->settings[PRS1_MaskAlert] = (bool) e->m_value; + break; + case PRS1_SETTING_SHOW_AHI: + session->settings[PRS1_ShowAHI] = (bool) e->m_value; + break; + default: + qWarning() << "Unknown PRS1 setting type" << (int) s->m_setting; + break; + } + } + + if (!ok) { + return false; + } + summary_duration = summary->duration; + + return true; +} + + +bool PRS1DataChunk::ParseSummaryF0V23() +{ + const unsigned char * data = (unsigned char *)this->m_data.constData(); CPAPMode cpapmode = MODE_UNKNOWN; @@ -2957,50 +3075,52 @@ bool PRS1Import::ParseSummaryF0() cpapmode = MODE_BILEVEL_AUTO_VARIABLE_PS; } - EventDataType min_pressure = EventDataType(data[0x03]) / 10.0; - EventDataType max_pressure = EventDataType(data[0x04]) / 10.0; - EventDataType ps = EventDataType(data[0x05]) / 10.0; // pressure support + int min_pressure = data[0x03]; + int max_pressure = data[0x04]; + int ps = data[0x05]; // pressure support if (cpapmode == MODE_CPAP) { - session->settings[CPAP_Pressure] = min_pressure; + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PRESSURE, min_pressure)); } else if (cpapmode == MODE_APAP) { - session->settings[CPAP_PressureMin] = min_pressure; - session->settings[CPAP_PressureMax] = max_pressure; + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PRESSURE_MIN, min_pressure)); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PRESSURE_MAX, max_pressure)); } else if (cpapmode == MODE_BILEVEL_FIXED) { - session->settings[CPAP_EPAP] = min_pressure; - session->settings[CPAP_IPAP] = max_pressure; - session->settings[CPAP_PS] = ps; + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_EPAP, min_pressure)); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP, max_pressure)); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS, ps)); } else if (cpapmode == MODE_BILEVEL_AUTO_VARIABLE_PS) { - session->settings[CPAP_EPAPLo] = min_pressure; - session->settings[CPAP_EPAPHi] = max_pressure - 2.0; - session->settings[CPAP_IPAPLo] = min_pressure + 2.0; - session->settings[CPAP_IPAPHi] = max_pressure; - session->settings[CPAP_PSMin] = 2.0f; - session->settings[CPAP_PSMax] = ps; + int min_ps = 2; + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_EPAP_MIN, min_pressure)); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_EPAP_MAX, max_pressure - min_ps)); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP_MIN, min_pressure + min_ps)); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP_MAX, max_pressure)); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS_MIN, min_ps)); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS_MAX, ps)); } - session->settings[CPAP_Mode] = (int)cpapmode; + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_CPAP_MODE, (int) cpapmode)); int ramp_time = data[0x06]; - EventDataType ramp_pressure = EventDataType(data[0x07]) / 10.0; + int ramp_pressure = data[0x07]; - session->settings[CPAP_RampTime] = (int)ramp_time; - session->settings[CPAP_RampPressure] = ramp_pressure; + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_RAMP_TIME, ramp_time)); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_RAMP_PRESSURE, ramp_pressure)); // Tubing lock has no setting byte // Menu Options - session->settings[PRS1_SysLock] = (bool) (data[0x0a] & 0x80); // System One Resistance Lock Setting - session->settings[PRS1_SysOneResistSet] = (int)data[0x0a] & 7; // SYstem One Resistance setting value - session->settings[PRS1_SysOneResistStat] = (bool) (data[0x0a] & 0x40); // System One Resistance Status bit - session->settings[PRS1_HoseDiam] = (data[0x0a] & 0x08) ? QObject::tr("15mm") : QObject::tr("22mm"); - session->settings[PRS1_AutoOn] = (bool) (data[0x0b] & 0x40); - session->settings[PRS1_AutoOff] = (bool) (data[0x0c] & 0x10); - session->settings[PRS1_MaskAlert] = (bool) (data[0x0c] & 0x08); - session->settings[PRS1_ShowAHI] = (bool) (data[0x0c] & 0x04); - session->settings[PRS1_HumidStatus] = (bool)(data[0x09] & 0x80); // Humidifier Connected - session->settings[PRS1_HumidLevel] = (int)(data[0x09] & 7); // Humidifier Value + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_SYSTEMONE_RESIST_LOCK, (data[0x0a] & 0x80) != 0)); // System One Resistance Lock Setting + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_SYSTEMONE_RESIST_SETTING, data[0x0a] & 7)); // SYstem One Resistance setting value + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_SYSTEMONE_RESIST_STATUS, (data[0x0a] & 0x40) != 0)); // System One Resistance Status bit + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HOSE_DIAMETER, (data[0x0a] & 0x08) ? 15 : 22)); + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_AUTO_ON, (data[0x0b] & 0x40) != 0)); + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_AUTO_OFF, (data[0x0c] & 0x10) != 0)); + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_MASK_ALERT, (data[0x0c] & 0x08) != 0)); + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_SHOW_AHI, (data[0x0c] & 0x04) != 0)); + int humid = data[0x09]; + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_STATUS, (humid & 0x80) != 0)); // Humidifier Connected + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_LEVEL, (humid & 7))); // Humidifier Value // session-> @@ -3036,10 +3156,10 @@ bool PRS1Import::ParseSummaryF0() } } else flexmode = FLEX_None; - session->settings[PRS1_FlexMode] = (int)flexmode; - session->settings[PRS1_FlexLevel] = (int)flexlevel; + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_FLEX_MODE, (int) flexmode)); + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_FLEX_LEVEL, flexlevel)); - summary_duration = data[0x14] | data[0x15] << 8; + this->duration = data[0x14] | data[0x15] << 8; return true; } @@ -3856,7 +3976,7 @@ bool PRS1Import::ParseSummary() } else if (summary->familyVersion == 4) { return ParseSummaryF0V4(); } else { - return ParseSummaryF0(); + return ParseSummaryF0V23(); } case 3: return ParseSummaryF3(); diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.h b/oscar/SleepLib/loader_plugins/prs1_loader.h index 0869a884..212c496d 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.h +++ b/oscar/SleepLib/loader_plugins/prs1_loader.h @@ -127,6 +127,9 @@ public: //! \brief Read the chunk's data from a PRS1 file and calculate its CRC, must be called after ReadHeader bool ReadData(class QFile & f); + //! \brief Parse a single data chunk from a .001 file containing summary data for a family 0 CPAP/APAP family version 2 or 3 machine + bool ParseSummaryF0V23(void); + //! \brief Parse a single data chunk from a .001 file containing summary data for a family 0 CPAP/APAP family version 4 machine bool ParseSummaryF0V4(void); @@ -227,7 +230,7 @@ public: //! \brief Summary parser for 50 series Family 0 CPAP/APAP models - bool ParseSummaryF0(); + bool ParseSummaryF0V23(); //! \brief Summary parser for 60 series Family 0 CPAP/APAP models bool ParseSummaryF0V4(); //! \brief Summary parser for 1060 series AVAPS models From 53f1a881a333496df851a518feada04f6623f8c4 Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Sun, 26 May 2019 14:17:58 -0400 Subject: [PATCH 15/31] Split parsing from importing for compliance (brick) data. This isn't fully tested yet, since I don't currently have fileVersion 2 samples. It has been tested with 200X devices and doesn't change behavior there, but the slice parsing immediately bails, so it's not really being exercised. It seems very weird that "slices", whatever they are, would only show up on bricks, the least capable devices. This needs more investigation. --- oscar/SleepLib/loader_plugins/prs1_loader.cpp | 114 ++++++++++++++---- oscar/SleepLib/loader_plugins/prs1_loader.h | 3 + 2 files changed, 94 insertions(+), 23 deletions(-) diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.cpp b/oscar/SleepLib/loader_plugins/prs1_loader.cpp index 57fcd708..02e33ce5 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.cpp +++ b/oscar/SleepLib/loader_plugins/prs1_loader.cpp @@ -986,6 +986,7 @@ enum PRS1ParsedEventType EV_PRS1_TEST1, EV_PRS1_TEST2, EV_PRS1_SETTING, + EV_PRS1_SLICE, }; enum PRS1ParsedEventUnit @@ -1121,6 +1122,13 @@ public: } }; +class PRS1ParsedSliceEvent : public PRS1ParsedDurationEvent +{ +public: + SliceStatus m_status; + PRS1ParsedSliceEvent(int start, int duration, SliceStatus status) : PRS1ParsedDurationEvent(EV_PRS1_SLICE, start, duration), m_status(status) {} +}; + class PRS1TimedBreathEvent : public PRS1ParsedDurationEvent { public: @@ -2858,25 +2866,87 @@ bool PRS1DataChunk::ParseEventsF0(CPAPMode mode) bool PRS1Import::ParseCompliance() { - const unsigned char * data = (unsigned char *)compliance->m_data.constData(); + bool ok; + ok = compliance->ParseCompliance(); + qint64 start = qint64(compliance->timestamp) * 1000L; + + for (int i=0; i < compliance->m_parsedData.count(); i++) { + PRS1ParsedEvent* e = compliance->m_parsedData.at(i); + if (e->m_type == EV_PRS1_SLICE) { + PRS1ParsedSliceEvent* s = (PRS1ParsedSliceEvent*) e; + qint64 tt = start + qint64(s->m_start) * 1000L; + qint64 duration = qint64(s->m_duration) * 1000L; + session->m_slices.append(SessionSlice(tt, tt + duration, s->m_status)); + qDebug() << compliance->sessionid << "Added Slice" << tt << (tt+duration) << s->m_status; + continue; + } else if (e->m_type != EV_PRS1_SETTING) { + qWarning() << "Compliance had non-setting event:" << (int) e->m_type; + continue; + } + PRS1ParsedSettingEvent* s = (PRS1ParsedSettingEvent*) e; + switch (s->m_setting) { + case PRS1_SETTING_CPAP_MODE: + session->settings[CPAP_Mode] = e->m_value; + break; + case PRS1_SETTING_PRESSURE: + session->settings[CPAP_Pressure] = e->value(); + break; + case PRS1_SETTING_FLEX_MODE: + session->settings[PRS1_FlexMode] = e->m_value; + break; + case PRS1_SETTING_FLEX_LEVEL: + session->settings[PRS1_FlexLevel] = e->m_value; + break; + case PRS1_SETTING_RAMP_TIME: + session->settings[CPAP_RampTime] = e->m_value; + break; + case PRS1_SETTING_RAMP_PRESSURE: + session->settings[CPAP_RampPressure] = e->value(); + break; + case PRS1_SETTING_HUMID_STATUS: + session->settings[PRS1_HumidStatus] = (bool) e->m_value; + break; + case PRS1_SETTING_HUMID_LEVEL: + session->settings[PRS1_HumidLevel] = e->m_value; + break; + default: + qWarning() << "Unknown PRS1 setting type" << (int) s->m_setting; + break; + } + } + + if (!ok) { + return false; + } + session->setSummaryOnly(true); + session->set_first(start); + session->set_last(qint64(compliance->timestamp + compliance->duration) * 1000L); + + return true; +} + + +bool PRS1DataChunk::ParseCompliance(void) +{ + const unsigned char * data = (unsigned char *)this->m_data.constData(); if (data[0x00] > 0) { return false; } - session->settings[CPAP_Mode] = (int)MODE_CPAP; + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_CPAP_MODE, (int) MODE_CPAP)); - EventDataType min_pressure = EventDataType(data[0x03]) / 10.0; + int min_pressure = data[0x03]; // EventDataType max_pressure = EventDataType(data[0x04]) / 10.0; - session->settings[CPAP_Pressure] = min_pressure; + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PRESSURE, min_pressure)); int ramp_time = data[0x06]; - EventDataType ramp_pressure = EventDataType(data[0x07]) / 10.0; + int ramp_pressure = data[0x07]; - session->settings[CPAP_RampTime] = (int)ramp_time; - session->settings[CPAP_RampPressure] = ramp_pressure; + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_RAMP_TIME, ramp_time)); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_RAMP_PRESSURE, ramp_pressure)); quint8 flex = data[0x09]; @@ -2899,28 +2969,28 @@ bool PRS1Import::ParseCompliance() } } else flexmode = FLEX_None; - session->settings[PRS1_FlexMode] = (int)flexmode; - session->settings[PRS1_FlexLevel] = (int)flexlevel; - session->setSummaryOnly(true); - //session->settings[CPAP_SummaryOnly] = true; + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_FLEX_MODE, (int) flexmode)); + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_FLEX_LEVEL, flexlevel)); - session->settings[PRS1_HumidStatus] = (bool)(data[0x0A] & 0x80); // Humidifier Connected - session->settings[PRS1_HumidLevel] = (int)(data[0x0A] & 7); // Humidifier Value + int humid = data[0x0A]; + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_STATUS, (humid & 0x80) != 0)); // Humidifier Connected + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_LEVEL, (humid & 7))); // Humidifier Value + // TODO: What are slices, and why would only bricks have them? That seems very weird. + // need to parse a repeating structure here containing lengths of mask on/off.. // 0x03 = mask on // 0x01 = mask off - qint64 start = qint64(compliance->timestamp) * 1000L; - qint64 tt = start; + int start = 0; + int tt = start; - int len = compliance->size()-3; + int len = this->size()-3; int pos = 0x11; do { quint8 c = data[pos++]; - quint64 duration = data[pos] | data[pos+1] << 8; + int duration = data[pos] | data[pos+1] << 8; pos+=2; - duration *= 1000L; SliceStatus status; if (c == 0x03) { status = EquipmentOn; @@ -2929,17 +2999,15 @@ bool PRS1Import::ParseCompliance() } else if (c == 0x01) { status = EquipmentOff; } else { - qDebug() << compliance->sessionid << "Wasn't expecting" << c; + qDebug() << this->sessionid << "Wasn't expecting" << c; break; } - session->m_slices.append(SessionSlice(tt, tt + duration, status)); - qDebug() << compliance->sessionid << "Added Slice" << tt << (tt+duration) << status; + this->AddEvent(new PRS1ParsedSliceEvent(tt, duration, status)); tt += duration; } while (pos < len); - session->set_first(start); - session->set_last(tt); + this->duration = tt; // Bleh!! There is probably 10 different formats for these useless piece of junk machines return true; diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.h b/oscar/SleepLib/loader_plugins/prs1_loader.h index 212c496d..441ca7af 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.h +++ b/oscar/SleepLib/loader_plugins/prs1_loader.h @@ -127,6 +127,9 @@ public: //! \brief Read the chunk's data from a PRS1 file and calculate its CRC, must be called after ReadHeader bool ReadData(class QFile & f); + //! \brief Parse a single data chunk from a .000 file containing compliance data for a brick + bool ParseCompliance(void); + //! \brief Parse a single data chunk from a .001 file containing summary data for a family 0 CPAP/APAP family version 2 or 3 machine bool ParseSummaryF0V23(void); From 923fb7bf8660f81d693fe174c7363e3aa55fc2ac Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Sun, 26 May 2019 17:36:12 -0400 Subject: [PATCH 16/31] Merge redundant code into single PRS1DataChunk::ParseFlexSetting function. --- oscar/SleepLib/loader_plugins/prs1_loader.cpp | 125 +++++------------- oscar/SleepLib/loader_plugins/prs1_loader.h | 3 + 2 files changed, 33 insertions(+), 95 deletions(-) diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.cpp b/oscar/SleepLib/loader_plugins/prs1_loader.cpp index 02e33ce5..27ae1239 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.cpp +++ b/oscar/SleepLib/loader_plugins/prs1_loader.cpp @@ -2950,33 +2950,14 @@ bool PRS1DataChunk::ParseCompliance(void) quint8 flex = data[0x09]; - int flexlevel = flex & 0x03; - - - FlexMode flexmode = FLEX_Unknown; - - flex &= 0xf8; - //bool split = false; - - if (flex & 0x40) { // This bit defines the Flex setting for the CPAP component of the Split night - // split = true; - } - if (flex & 0x80) { // CFlex bit - if (flex & 8) { // Plus bit - flexmode = FLEX_CFlexPlus; - } else { - flexmode = FLEX_CFlex; - } - } else flexmode = FLEX_None; - - this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_FLEX_MODE, (int) flexmode)); - this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_FLEX_LEVEL, flexlevel)); + this->ParseFlexSetting(flex, MODE_CPAP); int humid = data[0x0A]; this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_STATUS, (humid & 0x80) != 0)); // Humidifier Connected this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_LEVEL, (humid & 7))); // Humidifier Value // TODO: What are slices, and why would only bricks have them? That seems very weird. + // TODO: The below seems not to work on 200X models. // need to parse a repeating structure here containing lengths of mask on/off.. // 0x03 = mask on @@ -3193,39 +3174,7 @@ bool PRS1DataChunk::ParseSummaryF0V23() // session-> quint8 flex = data[0x08]; - - int flexlevel = flex & 0x03; - FlexMode flexmode = FLEX_Unknown; - - // 88 CFlex+ / AFlex (depending on CPAP mode) - // 80 CFlex - // 00 NoFlex - // c0 Split CFlex then None - // c8 Split CFlex+ then None - - flex &= 0xf8; - bool split = false; - - if (flex & 0x40) { // This bit defines the Flex setting for the CPAP component of the Split night - split = true; - } - if (flex & 0x80) { // CFlex bit - if (flex & 0x10) { - flexmode = FLEX_RiseTime; - } else if (flex & 8) { // Plus bit - if (split || (cpapmode == MODE_CPAP)) { - flexmode = FLEX_CFlexPlus; - } else if (cpapmode == MODE_APAP) { - flexmode = FLEX_AFlex; - } - } else { - // CFlex bits refer to Rise Time on BiLevel machines - flexmode = (cpapmode >= MODE_BILEVEL_FIXED) ? FLEX_BiFlex : FLEX_CFlex; - } - } else flexmode = FLEX_None; - - this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_FLEX_MODE, (int) flexmode)); - this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_FLEX_LEVEL, flexlevel)); + this->ParseFlexSetting(flex, cpapmode); this->duration = data[0x14] | data[0x15] << 8; @@ -3366,30 +3315,7 @@ bool PRS1DataChunk::ParseSummaryF0V4(void) this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_CPAP_MODE, (int) cpapmode)); quint8 flex = data[0x0a]; - - int flexlevel = flex & 0x03; - FlexMode flexmode = FLEX_Unknown; - - flex &= 0xf8; - bool split = false; - - if (flex & 0x40) { // This bit defines the Flex setting for the CPAP component of the Split night - split = true; - } - if (flex & 0x80) { // CFlex bit - if (flex & 0x10) { - flexmode = FLEX_RiseTime; - } else if (flex & 8) { // Plus bit - if (split || (cpapmode == MODE_CPAP)) { - flexmode = FLEX_CFlexPlus; - } else if (cpapmode == MODE_APAP) { - flexmode = FLEX_AFlex; - } - } else { - // CFlex bits refer to Rise Time on BiLevel machines - flexmode = (cpapmode >= MODE_BILEVEL_FIXED) ? FLEX_BiFlex : FLEX_CFlex; - } - } else flexmode = FLEX_None; + this->ParseFlexSetting(flex, cpapmode); int ramp_time = data[0x08]; int ramp_pressure = data[0x09]; @@ -3397,9 +3323,6 @@ bool PRS1DataChunk::ParseSummaryF0V4(void) this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_RAMP_TIME, ramp_time)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_RAMP_PRESSURE, ramp_pressure)); - this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_FLEX_MODE, (int) flexmode)); - this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_FLEX_LEVEL, flexlevel)); - int humid = data[0x0b]; this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_STATUS, (humid & 0x80) != 0)); // Humidifier Connected this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HEATED_TUBING, (humid & 0x10) != 0)); // Heated Hose?? @@ -3617,10 +3540,35 @@ bool PRS1DataChunk::ParseSummaryF5V012(void) this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS_MAX, imax_ps)); quint8 flex = data[0x0c]; + this->ParseFlexSetting(flex, cpapmode); + int ramp_time = data[0x0a]; + int ramp_pressure = data[0x0b]; + + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_RAMP_TIME, ramp_time)); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_RAMP_PRESSURE, ramp_pressure)); + + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_STATUS, (data[0x0d] & 0x80) != 0)); // Humidifier Connected + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HEATED_TUBING, (data[0x0d] & 0x10) != 0)); // Heated Hose?? + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_LEVEL, (data[0x0d] & 7))); // Humidifier Value + + this->duration = data[0x18] | data[0x19] << 8; + + return true; +} + + +void PRS1DataChunk::ParseFlexSetting(quint8 flex, CPAPMode cpapmode) +{ int flexlevel = flex & 0x03; FlexMode flexmode = FLEX_Unknown; + // 88 CFlex+ / AFlex (depending on CPAP mode) + // 80 CFlex + // 00 NoFlex + // c0 Split CFlex then None + // c8 Split CFlex+ then None + flex &= 0xf8; bool split = false; @@ -3644,22 +3592,9 @@ bool PRS1DataChunk::ParseSummaryF5V012(void) this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_FLEX_MODE, (int) flexmode)); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_FLEX_LEVEL, flexlevel)); - - int ramp_time = data[0x0a]; - int ramp_pressure = data[0x0b]; - - this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_RAMP_TIME, ramp_time)); - this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_RAMP_PRESSURE, ramp_pressure)); - - this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_STATUS, (data[0x0d] & 0x80) != 0)); // Humidifier Connected - this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HEATED_TUBING, (data[0x0d] & 0x10) != 0)); // Heated Hose?? - this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_LEVEL, (data[0x0d] & 7))); // Humidifier Value - - this->duration = data[0x18] | data[0x19] << 8; - - return true; } + bool PRS1Import::ParseSummaryF5V3() { bool ok; diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.h b/oscar/SleepLib/loader_plugins/prs1_loader.h index 441ca7af..27db6841 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.h +++ b/oscar/SleepLib/loader_plugins/prs1_loader.h @@ -148,6 +148,9 @@ public: //! \brief Parse a single data chunk from a .001 file containing summary data for a family 5 ASV family version 3 machine bool ParseSummaryF5V3(void); + //! \brief Parse a flex setting byte from a .000 or .001 containing compliance/summary data + void ParseFlexSetting(quint8 flex, CPAPMode cpapmode); + //! \brief Parse a single data chunk from a .002 file containing event data for a family 0 CPAP/APAP machine bool ParseEventsF0(CPAPMode mode); From d13b417d5b7e24c8b082602ea1feba795f66eba8 Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Sun, 26 May 2019 17:43:10 -0400 Subject: [PATCH 17/31] Fix regression from eb758602 that was missed by the initial testing. --- oscar/SleepLib/loader_plugins/prs1_loader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.cpp b/oscar/SleepLib/loader_plugins/prs1_loader.cpp index 27ae1239..a50899af 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.cpp +++ b/oscar/SleepLib/loader_plugins/prs1_loader.cpp @@ -3138,7 +3138,7 @@ bool PRS1DataChunk::ParseSummaryF0V23() this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP, max_pressure)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS, ps)); } else if (cpapmode == MODE_BILEVEL_AUTO_VARIABLE_PS) { - int min_ps = 2; + int min_ps = 20; // 2.0 cmH2O this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_EPAP_MIN, min_pressure)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_EPAP_MAX, max_pressure - min_ps)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP_MIN, min_pressure + min_ps)); From 44558e4c065470e428530e54c09d8fe16434623c Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Sun, 26 May 2019 18:46:26 -0400 Subject: [PATCH 18/31] Merge redundant code into single PRS1DataChunk::ParseHumidifierSetting function. --- oscar/SleepLib/loader_plugins/prs1_loader.cpp | 26 +++++++++++-------- oscar/SleepLib/loader_plugins/prs1_loader.h | 3 +++ 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.cpp b/oscar/SleepLib/loader_plugins/prs1_loader.cpp index a50899af..387ab0ac 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.cpp +++ b/oscar/SleepLib/loader_plugins/prs1_loader.cpp @@ -2953,8 +2953,7 @@ bool PRS1DataChunk::ParseCompliance(void) this->ParseFlexSetting(flex, MODE_CPAP); int humid = data[0x0A]; - this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_STATUS, (humid & 0x80) != 0)); // Humidifier Connected - this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_LEVEL, (humid & 7))); // Humidifier Value + this->ParseHumidifierSetting(humid, false); // TODO: What are slices, and why would only bricks have them? That seems very weird. // TODO: The below seems not to work on 200X models. @@ -3168,8 +3167,7 @@ bool PRS1DataChunk::ParseSummaryF0V23() this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_MASK_ALERT, (data[0x0c] & 0x08) != 0)); this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_SHOW_AHI, (data[0x0c] & 0x04) != 0)); int humid = data[0x09]; - this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_STATUS, (humid & 0x80) != 0)); // Humidifier Connected - this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_LEVEL, (humid & 7))); // Humidifier Value + this->ParseHumidifierSetting(humid, false); // session-> @@ -3324,10 +3322,7 @@ bool PRS1DataChunk::ParseSummaryF0V4(void) this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_RAMP_PRESSURE, ramp_pressure)); int humid = data[0x0b]; - this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_STATUS, (humid & 0x80) != 0)); // Humidifier Connected - this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HEATED_TUBING, (humid & 0x10) != 0)); // Heated Hose?? - this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_LEVEL, (humid & 7))); // Humidifier Value - + this->ParseHumidifierSetting(humid); this->duration = data[0x14] | data[0x15] << 8; @@ -3548,9 +3543,8 @@ bool PRS1DataChunk::ParseSummaryF5V012(void) this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_RAMP_TIME, ramp_time)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_RAMP_PRESSURE, ramp_pressure)); - this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_STATUS, (data[0x0d] & 0x80) != 0)); // Humidifier Connected - this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HEATED_TUBING, (data[0x0d] & 0x10) != 0)); // Heated Hose?? - this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_LEVEL, (data[0x0d] & 7))); // Humidifier Value + int humid = data[0x0d]; + this->ParseHumidifierSetting(humid); this->duration = data[0x18] | data[0x19] << 8; @@ -3595,6 +3589,16 @@ void PRS1DataChunk::ParseFlexSetting(quint8 flex, CPAPMode cpapmode) } +void PRS1DataChunk::ParseHumidifierSetting(int humid, bool supportsHeatedTubing) +{ + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_STATUS, (humid & 0x80) != 0)); // Humidifier Connected + if (supportsHeatedTubing) { + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HEATED_TUBING, (humid & 0x10) != 0)); // Heated Hose?? + } + this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_HUMID_LEVEL, (humid & 7))); // Humidifier Value +} + + bool PRS1Import::ParseSummaryF5V3() { bool ok; diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.h b/oscar/SleepLib/loader_plugins/prs1_loader.h index 27db6841..33ed63f0 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.h +++ b/oscar/SleepLib/loader_plugins/prs1_loader.h @@ -151,6 +151,9 @@ public: //! \brief Parse a flex setting byte from a .000 or .001 containing compliance/summary data void ParseFlexSetting(quint8 flex, CPAPMode cpapmode); + //! \brief Parse an humidifier setting byte from a .000 or .001 containing compliance/summary data + void ParseHumidifierSetting(int humid, bool supportsHeatedTubing=true); + //! \brief Parse a single data chunk from a .002 file containing event data for a family 0 CPAP/APAP machine bool ParseEventsF0(CPAPMode mode); From 739ba7d5d5684c56b3e59b6fb4c78a7cae328075 Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Sun, 26 May 2019 19:02:08 -0400 Subject: [PATCH 19/31] Clean up ParseSummaryF5V3 to align more closely with all other summary parsers. --- oscar/SleepLib/loader_plugins/prs1_loader.cpp | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.cpp b/oscar/SleepLib/loader_plugins/prs1_loader.cpp index 387ab0ac..121604f0 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.cpp +++ b/oscar/SleepLib/loader_plugins/prs1_loader.cpp @@ -3604,8 +3604,6 @@ bool PRS1Import::ParseSummaryF5V3() bool ok; ok = summary->ParseSummaryF5V3(); - EventDataType epapHi, epapLo, minps, maxps; - for (int i=0; i < summary->m_parsedData.count(); i++) { PRS1ParsedEvent* e = summary->m_parsedData.at(i); if (e->m_type != EV_PRS1_SETTING) { @@ -3618,20 +3616,22 @@ bool PRS1Import::ParseSummaryF5V3() session->settings[CPAP_Mode] = e->m_value; break; case PRS1_SETTING_EPAP_MIN: - epapLo = e->value(); - session->settings[CPAP_EPAPLo] = epapLo; + session->settings[CPAP_EPAPLo] = e->value(); break; case PRS1_SETTING_EPAP_MAX: - epapHi = e->value(); - session->settings[CPAP_EPAPHi] = epapHi; + session->settings[CPAP_EPAPHi] = e->value(); + break; + case PRS1_SETTING_IPAP_MIN: + session->settings[CPAP_IPAPLo] = e->value(); + break; + case PRS1_SETTING_IPAP_MAX: + session->settings[CPAP_IPAPHi] = e->value(); break; case PRS1_SETTING_PS_MIN: - minps = e->value(); - session->settings[CPAP_PSMin] = minps; + session->settings[CPAP_PSMin] = e->value(); break; case PRS1_SETTING_PS_MAX: - maxps = e->value(); - session->settings[CPAP_PSMax] = maxps; + session->settings[CPAP_PSMax] = e->value(); break; default: qWarning() << "Unknown PRS1 setting type" << (int) s->m_setting; @@ -3639,9 +3639,6 @@ bool PRS1Import::ParseSummaryF5V3() } } - session->settings[CPAP_IPAPLo] = epapLo + minps; - session->settings[CPAP_IPAPHi] = qMin(25.0f, epapHi + maxps); - if (!ok) { return false; } @@ -3657,17 +3654,20 @@ bool PRS1DataChunk::ParseSummaryF5V3(void) unsigned char * pressureBlock = (unsigned char *)mainblock[0x0a].data(); - EventDataType epapHi = pressureBlock[0]; - EventDataType epapRange = pressureBlock[2]; - EventDataType epapLo = epapHi - epapRange; + int epapHi = pressureBlock[0]; + int epapRange = pressureBlock[2]; + int epapLo = epapHi - epapRange; - EventDataType minps = pressureBlock[3] ; - EventDataType maxps = pressureBlock[4]+epapLo; + int minps = pressureBlock[3] ; + int maxps = pressureBlock[4]+epapLo; this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_EPAP_MAX, epapHi)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_EPAP_MIN, epapLo)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS_MIN, minps)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS_MAX, maxps)); + + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP_MIN, epapLo + minps)); + this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP_MAX, qMin(250, epapHi + maxps))); // 25.0 cmH2O max if (hbdata[4].size() < 2) { qDebug() << "summary missing duration section:" << this->sessionid; From e3a4edaca2979e8d2538838e3bafbb0a7a687af9 Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Mon, 27 May 2019 10:05:16 -0400 Subject: [PATCH 20/31] Add PRS1ModelInfo to manage the set of supported and tested machines. Also move an extra unsupported check out of PRSImport::ImportSummary into CreateMachineFromProperties, where it should intervene before reaching PRS1Import. Leave a warning debug message in its place. --- oscar/SleepLib/loader_plugins/prs1_loader.cpp | 205 +++++++++++++++--- oscar/SleepLib/loader_plugins/prs1_loader.h | 23 +- oscar/tests/prs1tests.cpp | 41 ++++ oscar/tests/prs1tests.h | 1 + 4 files changed, 237 insertions(+), 33 deletions(-) diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.cpp b/oscar/SleepLib/loader_plugins/prs1_loader.cpp index 121604f0..db7d2eb5 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.cpp +++ b/oscar/SleepLib/loader_plugins/prs1_loader.cpp @@ -196,6 +196,7 @@ enum FlexMode { FLEX_None, FLEX_CFlex, FLEX_CFlexPlus, FLEX_AFlex, FLEX_RiseTime ChannelID PRS1_TimedBreath = 0, PRS1_HeatedTubing = 0; +#if 0 // Apparently unused PRS1::PRS1(Profile *profile, MachineID id): CPAP(profile, id) { } @@ -203,18 +204,105 @@ PRS1::~PRS1() { } - - -#if 0 // TODO: Remove: unused, superseded by PRS1Waveform -/*! \struct WaveHeaderList - \brief Used in PRS1 Waveform Parsing */ -struct WaveHeaderList { - quint16 interleave; - quint8 sample_format; - WaveHeaderList(quint16 i, quint8 f) { interleave = i; sample_format = f; } -}; #endif +struct PRS1TestedModel +{ + QString model; + int family; + int familyVersion; +}; + +static const PRS1TestedModel s_PRS1TestedModels[] = { + { "450P", 0, 3 }, + { "550P", 0, 2 }, + { "550P", 0, 3 }, + { "750P", 0, 2 }, + + { "460P", 0, 4 }, + { "560P", 0, 4 }, + { "560PBT", 0, 4 }, + { "660P", 0, 4 }, + { "760P", 0, 4 }, + + { "200X110", 0, 6 }, + { "400G110", 0, 6 }, + { "400X110", 0, 6 }, + { "400X150", 0, 6 }, + { "500X110", 0, 6 }, + { "500X150", 0, 6 }, + { "502G150", 0, 6 }, + { "600X110", 0, 6 }, + { "700X110", 0, 6 }, + + { "950P", 5, 0 }, + { "960P", 5, 1 }, + { "961P", 5, 1 }, + { "960T", 5, 2 }, + { "900X110", 5, 3 }, + { "900X120", 5, 3 }, + + { "1160P", 3, 3 }, + { "1030X110", 3, 6 }, + { "1130X110", 3, 6 }, + + { "", 0, 0 }, +}; +PRS1ModelInfo s_PRS1ModelInfo; + +PRS1ModelInfo::PRS1ModelInfo() +{ + for (int i = 0; !s_PRS1TestedModels[i].model.isEmpty(); i++) { + const PRS1TestedModel & model = s_PRS1TestedModels[i]; + m_testedModels[model.family][model.familyVersion].append(model.model); + } +} + +bool PRS1ModelInfo::IsSupported(int family, int familyVersion) const +{ + if (m_testedModels.value(family).contains(familyVersion)) { + return true; + } + return false; +} + +bool PRS1ModelInfo::IsTested(const QString & model, int family, int familyVersion) const +{ + if (m_testedModels.value(family).value(familyVersion).contains(model)) { + return true; + } + return false; +}; + +bool PRS1ModelInfo::IsSupported(const QHash & props) const +{ + bool ok; + int family = props["Family"].toInt(&ok, 10); + if (ok) { + int familyVersion = props["FamilyVersion"].toInt(&ok, 10); + if (ok) { + ok = IsSupported(family, familyVersion); + } + } + return ok; +} + +bool PRS1ModelInfo::IsTested(const QHash & props) const +{ + bool ok; + int family = props["Family"].toInt(&ok, 10); + if (ok) { + int familyVersion = props["FamilyVersion"].toInt(&ok, 10); + if (ok) { + ok = IsTested(props["ModelNumber"], family, familyVersion); + } + } + return ok; +}; + +// TODO: add brick list, IsBrick() test +// TODO: add model name, Name() function + PRS1Loader::PRS1Loader() { @@ -232,7 +320,6 @@ PRS1Loader::PRS1Loader() m_pixmaps["DreamStation"] = QPixmap(DREAMSTATION_ICON); #endif - //genCRCTable(); // find what I did with this.. m_type = MT_CPAP; } @@ -390,45 +477,93 @@ void parseModel(MachineInfo & info, const QString & modelnum) } } -bool PRS1Loader::PeekProperties(MachineInfo & info, const QString & filename, Machine * mach) +bool PRS1Loader::PeekProperties(const QString & filename, QHash & props) { + const static QMap s_longFieldNames = { + // CF? + { "SN", "SerialNumber" }, + { "MN", "ModelNumber" }, + { "PT", "ProductType" }, + { "DF", "DataFormat" }, + { "DFV", "DataFormatVersion" }, + { "F", "Family" }, + { "FV", "FamilyVersion" }, + { "SV", "SoftwareVersion" }, + { "FD", "FirstDate" }, + { "LD", "LastDate" }, + // SID? + // SK? + { "BK", "BasicKey" }, + { "DK", "DetailsKey" }, + { "EK", "ErrorKey" }, + { "FN", "PatientFolderNum" }, // most recent Pn directory + { "PFN", "PatientFileNum" }, // number of files in the most recent Pn directory + { "EFN", "EquipFileNum" }, // number of .004 files in the E directory + { "DFN", "DFileNum" }, // number of .003 files in the D directory + { "VC", "ValidCheck" }, + }; + QFile f(filename); if (!f.open(QFile::ReadOnly)) { return false; } QTextStream in(&f); - QString modelnum; - int ptype=0; - int dfv=0; - bool ok; do { QString line = in.readLine(); QStringList pair = line.split("="); + if (s_longFieldNames.contains(pair[0])) { + pair[0] = s_longFieldNames[pair[0]]; + } + if (pair[0] == "Family") { + if (pair[1] == "xPAP") { + pair[1] = "0"; + } + } + props[pair[0]] = pair[1]; + } while (!in.atEnd()); + + return true; +} + +bool PRS1Loader::PeekProperties(MachineInfo & info, const QString & filename, Machine * mach) +{ + QHash props; + if (!PeekProperties(filename, props)) { + return false; + } + QString modelnum; + int ptype=0; + int dfv=0; + bool ok; + for (auto & key : props.keys()) { bool skip = false; - if (pair[0].contains("ModelNumber", Qt::CaseInsensitive) || pair[0].contains("MN", Qt::CaseInsensitive)) { - modelnum = pair[1]; + if (key == "ModelNumber") { + modelnum = props[key]; skip = true; } - if (pair[0].contains("SerialNumber", Qt::CaseInsensitive) || pair[0].contains("SN", Qt::CaseInsensitive)) { - info.serial = pair[1]; + if (key == "SerialNumber") { + info.serial = props[key]; skip = true; } - if (pair[0].contains("ProductType", Qt::CaseInsensitive) || pair[0].contains("PT", Qt::CaseInsensitive)) { - ptype = pair[1].toInt(&ok, 16); + if (key == "ProductType") { + ptype = props[key].toInt(&ok, 16); + if (!ok) qWarning() << "ProductType" << props[key]; skip = true; } - if (pair[0].contains("DataFormatVersion", Qt::CaseInsensitive) || pair[0].contains("DFV", Qt::CaseInsensitive)) { - dfv = pair[1].toInt(&ok, 10); + if (key == "DataFormatVersion") { + dfv = props[key].toInt(&ok, 10); + if (!ok) qWarning() << "DataFormatVersion" << props[key]; skip = true; } if (!mach || skip) continue; - mach->properties[pair[0]] = pair[1]; - - } while (!in.atEnd()); + mach->properties[key] = props[key]; + }; + // TODO: replace the below logic with PRS1ModelInfo table-driven logic + if (!modelnum.isEmpty()) { parseModel(info, modelnum); } @@ -763,6 +898,16 @@ Machine* PRS1Loader::CreateMachineFromProperties(QString propertyfile) // This time supply the machine object so it can populate machine properties.. PeekProperties(m->info, propertyfile, m); + + // TODO: Replace much of the above logic with PRS1ModelInfo logic. + QHash props; + PeekProperties(propertyfile, props); + if (!s_PRS1ModelInfo.IsSupported(props)) { + if (!m->unsupported()) { + unsupported(m); + } + } + return m; } @@ -4002,11 +4147,7 @@ bool PRS1Import::ParseSummary() ; } - this->loader->saveMutex.lock(); - if (!mach->unsupported()) { - this->loader->unsupported(mach); - } - this->loader->saveMutex.unlock(); + qWarning() << "unexpected family" << summary->family << "familyVersion" << summary->familyVersion; return false; ////////////////////////////////////////////////////////////////////////////////////////// diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.h b/oscar/SleepLib/loader_plugins/prs1_loader.h index 33ed63f0..6563e6f1 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.h +++ b/oscar/SleepLib/loader_plugins/prs1_loader.h @@ -28,7 +28,7 @@ const int prs1_data_version = 15; // //******************************************************************************************** - +#if 0 // Apparently unused /*! \class PRS1 \brief PRS1 customized machine object (via CPAP) */ @@ -41,6 +41,7 @@ class PRS1: public CPAP const int max_load_buffer_size = 1024 * 1024; +#endif const QString prs1_class_name = STR_MACH_PRS1; /*! \struct PRS1Waveform @@ -291,6 +292,9 @@ class PRS1Loader : public CPAPLoader //! \brief Examine path and return it back if it contains what looks to be a valid PRS1 SD card structure QString checkDir(const QString & path); + //! \brief Peek into PROP.TXT or properties.txt at given path, and return it as a normalized key/value hash + bool PeekProperties(const QString & filename, QHash & props); + //! \brief Peek into PROP.TXT or properties.txt at given path, and use it to fill MachineInfo structure bool PeekProperties(MachineInfo & info, const QString & path, Machine * mach = nullptr); @@ -378,4 +382,21 @@ class PRS1Loader : public CPAPLoader qint32 summary_duration; }; + +//******************************************************************************************** + +class PRS1ModelInfo +{ +protected: + QHash> m_testedModels; + +public: + PRS1ModelInfo(); + bool IsSupported(const QHash & properties) const; + bool IsSupported(int family, int familyVersion) const; + bool IsTested(const QHash & properties) const; + bool IsTested(const QString & modelNumber, int family, int familyVersion) const; +}; + + #endif // PRS1LOADER_H diff --git a/oscar/tests/prs1tests.cpp b/oscar/tests/prs1tests.cpp index 4ac60375..49d4e633 100644 --- a/oscar/tests/prs1tests.cpp +++ b/oscar/tests/prs1tests.cpp @@ -33,6 +33,47 @@ void PRS1Tests::cleanupTestCase(void) } +// ==================================================================================================== + +extern PRS1ModelInfo s_PRS1ModelInfo; +void PRS1Tests::testMachineSupport() +{ + QHash tested = { + { "ModelNumber", "550P" }, + { "Family", "0" }, + { "FamilyVersion", "3" }, + }; + QHash supported = { + { "ModelNumber", "700X999" }, + { "Family", "0" }, + { "FamilyVersion", "6" }, + }; + QHash unsupported = { + { "ModelNumber", "550P" }, + { "Family", "0" }, + { "FamilyVersion", "9" }, + }; + + Q_ASSERT(s_PRS1ModelInfo.IsSupported(5, 3)); + Q_ASSERT(!s_PRS1ModelInfo.IsSupported(5, 9)); + Q_ASSERT(!s_PRS1ModelInfo.IsSupported(9, 9)); + Q_ASSERT(s_PRS1ModelInfo.IsTested("550P", 0, 2)); + Q_ASSERT(s_PRS1ModelInfo.IsTested("550P", 0, 3)); + Q_ASSERT(s_PRS1ModelInfo.IsTested("760P", 0, 4)); + Q_ASSERT(s_PRS1ModelInfo.IsTested("700X110", 0, 6)); + Q_ASSERT(!s_PRS1ModelInfo.IsTested("700X999", 0, 6)); + + Q_ASSERT(s_PRS1ModelInfo.IsTested(tested)); + Q_ASSERT(!s_PRS1ModelInfo.IsTested(supported)); + Q_ASSERT(s_PRS1ModelInfo.IsSupported(tested)); + Q_ASSERT(s_PRS1ModelInfo.IsSupported(supported)); + Q_ASSERT(!s_PRS1ModelInfo.IsSupported(unsupported)); +} + + +// ==================================================================================================== + + void parseAndEmitSessionYaml(const QString & path) { qDebug() << path; diff --git a/oscar/tests/prs1tests.h b/oscar/tests/prs1tests.h index 5c8f2992..c4a36989 100644 --- a/oscar/tests/prs1tests.h +++ b/oscar/tests/prs1tests.h @@ -18,6 +18,7 @@ class PRS1Tests : public QObject private slots: void initTestCase(); + void testMachineSupport(); void testChunksToYaml(); void testSessionsToYaml(); // void test2(); From 4d1b947e22e5891f6a94a1558e92a44001200ebc Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Mon, 27 May 2019 10:25:57 -0400 Subject: [PATCH 21/31] Split PRS1Import::ParseSummary into ImportSummar/ParseSummary. --- oscar/SleepLib/loader_plugins/prs1_loader.cpp | 35 +++++++++++-------- oscar/SleepLib/loader_plugins/prs1_loader.h | 3 ++ 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.cpp b/oscar/SleepLib/loader_plugins/prs1_loader.cpp index db7d2eb5..791ec2e6 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.cpp +++ b/oscar/SleepLib/loader_plugins/prs1_loader.cpp @@ -4048,7 +4048,8 @@ bool PRS1DataChunk::ParseSummaryF0V6() return true; } -bool PRS1Import::ParseSummary() + +bool PRS1Import::ImportSummary() { if (!summary) return false; @@ -4060,7 +4061,23 @@ bool PRS1Import::ParseSummary() session->set_first(qint64(summary->timestamp) * 1000L); + session->setPhysMax(CPAP_LeakTotal, 120); + session->setPhysMin(CPAP_LeakTotal, 0); + session->setPhysMax(CPAP_Pressure, 25); + session->setPhysMin(CPAP_Pressure, 4); + session->setPhysMax(CPAP_IPAP, 25); + session->setPhysMin(CPAP_IPAP, 4); + session->setPhysMax(CPAP_EPAP, 25); + session->setPhysMin(CPAP_EPAP, 4); + session->setPhysMax(CPAP_PS, 25); + session->setPhysMin(CPAP_PS, 0); + + return this->ParseSummary(); +} + +bool PRS1Import::ParseSummary() +{ // TODO: The below is probably wrong. It should move to PRS1DataChunk when it gets fixed. /* Example data block 000000c6@0000: 00 [10] 01 [00 01 02 01 01 00 02 01 00 04 01 40 07 @@ -4108,19 +4125,6 @@ bool PRS1Import::ParseSummary() // Family 3 = BIPAP AVAPS // Family 5 = BIPAP AutoSV - - - session->setPhysMax(CPAP_LeakTotal, 120); - session->setPhysMin(CPAP_LeakTotal, 0); - session->setPhysMax(CPAP_Pressure, 25); - session->setPhysMin(CPAP_Pressure, 4); - session->setPhysMax(CPAP_IPAP, 25); - session->setPhysMin(CPAP_IPAP, 4); - session->setPhysMax(CPAP_EPAP, 25); - session->setPhysMin(CPAP_EPAP, 4); - session->setPhysMax(CPAP_PS, 25); - session->setPhysMin(CPAP_PS, 0); - switch (summary->family) { case 0: if (summary->familyVersion == 6) { @@ -4183,6 +4187,7 @@ bool PRS1Import::ParseSummary() ////////////////////////////////////////////////////////////////////////////////////////// } + bool PRS1Import::ParseEvents() { bool res = false; @@ -4461,7 +4466,7 @@ bool PRS1Import::ParseSession(void) bool save = false; session = new Session(mach, sessionid); - if ((compliance && ParseCompliance()) || (summary && ParseSummary())) { + if ((compliance && ParseCompliance()) || (summary && ImportSummary())) { if (event && !ParseEvents()) { } diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.h b/oscar/SleepLib/loader_plugins/prs1_loader.h index 6563e6f1..511b7958 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.h +++ b/oscar/SleepLib/loader_plugins/prs1_loader.h @@ -223,6 +223,9 @@ public: //! \brief As it says on the tin.. Parses .001 files for bricks. bool ParseCompliance(); + //! \brief Imports the .002 summary file. + bool ImportSummary(); + //! \brief Figures out which Summary Parser to call, based on machine family/version and calls it. bool ParseSummary(); From 44e4b2547125990fe3ede154b4e85fbeda8012a2 Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Mon, 27 May 2019 10:28:14 -0400 Subject: [PATCH 22/31] Move PRS1Import::ParseSummaryF0V23 in preparation for merge; no changes. --- oscar/SleepLib/loader_plugins/prs1_loader.cpp | 218 +++++++++--------- 1 file changed, 109 insertions(+), 109 deletions(-) diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.cpp b/oscar/SleepLib/loader_plugins/prs1_loader.cpp index 791ec2e6..1214644b 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.cpp +++ b/oscar/SleepLib/loader_plugins/prs1_loader.cpp @@ -3139,115 +3139,6 @@ bool PRS1DataChunk::ParseCompliance(void) } -bool PRS1Import::ParseSummaryF0V23() -{ - bool ok; - ok = summary->ParseSummaryF0V23(); - - for (int i=0; i < summary->m_parsedData.count(); i++) { - PRS1ParsedEvent* e = summary->m_parsedData.at(i); - if (e->m_type != EV_PRS1_SETTING) { - qWarning() << "Summary had non-setting event:" << (int) e->m_type; - continue; - } - PRS1ParsedSettingEvent* s = (PRS1ParsedSettingEvent*) e; - switch (s->m_setting) { - case PRS1_SETTING_CPAP_MODE: - session->settings[CPAP_Mode] = e->m_value; - break; - case PRS1_SETTING_PRESSURE: - session->settings[CPAP_Pressure] = e->value(); - break; - case PRS1_SETTING_PRESSURE_MIN: - session->settings[CPAP_PressureMin] = e->value(); - break; - case PRS1_SETTING_PRESSURE_MAX: - session->settings[CPAP_PressureMax] = e->value(); - break; - case PRS1_SETTING_EPAP: - session->settings[CPAP_EPAP] = e->value(); - break; - case PRS1_SETTING_IPAP: - session->settings[CPAP_IPAP] = e->value(); - break; - case PRS1_SETTING_PS: - session->settings[CPAP_PS] = e->value(); - break; - case PRS1_SETTING_EPAP_MIN: - session->settings[CPAP_EPAPLo] = e->value(); - break; - case PRS1_SETTING_EPAP_MAX: - session->settings[CPAP_EPAPHi] = e->value(); - break; - case PRS1_SETTING_IPAP_MIN: - session->settings[CPAP_IPAPLo] = e->value(); - break; - case PRS1_SETTING_IPAP_MAX: - session->settings[CPAP_IPAPHi] = e->value(); - break; - case PRS1_SETTING_PS_MIN: - session->settings[CPAP_PSMin] = e->value(); - break; - case PRS1_SETTING_PS_MAX: - session->settings[CPAP_PSMax] = e->value(); - break; - case PRS1_SETTING_FLEX_MODE: - session->settings[PRS1_FlexMode] = e->m_value; - break; - case PRS1_SETTING_FLEX_LEVEL: - session->settings[PRS1_FlexLevel] = e->m_value; - break; - case PRS1_SETTING_RAMP_TIME: - session->settings[CPAP_RampTime] = e->m_value; - break; - case PRS1_SETTING_RAMP_PRESSURE: - session->settings[CPAP_RampPressure] = e->value(); - break; - case PRS1_SETTING_HUMID_STATUS: - session->settings[PRS1_HumidStatus] = (bool) e->m_value; - break; - case PRS1_SETTING_HUMID_LEVEL: - session->settings[PRS1_HumidLevel] = e->m_value; - break; - case PRS1_SETTING_SYSTEMONE_RESIST_LOCK: - session->settings[PRS1_SysLock] = (bool) e->m_value; - break; - case PRS1_SETTING_SYSTEMONE_RESIST_SETTING: - session->settings[PRS1_SysOneResistSet] = e->m_value; - break; - case PRS1_SETTING_SYSTEMONE_RESIST_STATUS: - session->settings[PRS1_SysOneResistStat] = (bool) e->m_value; - break; - case PRS1_SETTING_HOSE_DIAMETER: - session->settings[PRS1_HoseDiam] = e->m_value == 15 ? QObject::tr("15mm") : QObject::tr("22mm"); - break; - case PRS1_SETTING_AUTO_ON: - session->settings[PRS1_AutoOn] = (bool) e->m_value; - break; - case PRS1_SETTING_AUTO_OFF: - session->settings[PRS1_AutoOff] = (bool) e->m_value; - break; - case PRS1_SETTING_MASK_ALERT: - session->settings[PRS1_MaskAlert] = (bool) e->m_value; - break; - case PRS1_SETTING_SHOW_AHI: - session->settings[PRS1_ShowAHI] = (bool) e->m_value; - break; - default: - qWarning() << "Unknown PRS1 setting type" << (int) s->m_setting; - break; - } - } - - if (!ok) { - return false; - } - summary_duration = summary->duration; - - return true; -} - - bool PRS1DataChunk::ParseSummaryF0V23() { const unsigned char * data = (unsigned char *)this->m_data.constData(); @@ -4076,6 +3967,115 @@ bool PRS1Import::ImportSummary() } +bool PRS1Import::ParseSummaryF0V23() +{ + bool ok; + ok = summary->ParseSummaryF0V23(); + + for (int i=0; i < summary->m_parsedData.count(); i++) { + PRS1ParsedEvent* e = summary->m_parsedData.at(i); + if (e->m_type != EV_PRS1_SETTING) { + qWarning() << "Summary had non-setting event:" << (int) e->m_type; + continue; + } + PRS1ParsedSettingEvent* s = (PRS1ParsedSettingEvent*) e; + switch (s->m_setting) { + case PRS1_SETTING_CPAP_MODE: + session->settings[CPAP_Mode] = e->m_value; + break; + case PRS1_SETTING_PRESSURE: + session->settings[CPAP_Pressure] = e->value(); + break; + case PRS1_SETTING_PRESSURE_MIN: + session->settings[CPAP_PressureMin] = e->value(); + break; + case PRS1_SETTING_PRESSURE_MAX: + session->settings[CPAP_PressureMax] = e->value(); + break; + case PRS1_SETTING_EPAP: + session->settings[CPAP_EPAP] = e->value(); + break; + case PRS1_SETTING_IPAP: + session->settings[CPAP_IPAP] = e->value(); + break; + case PRS1_SETTING_PS: + session->settings[CPAP_PS] = e->value(); + break; + case PRS1_SETTING_EPAP_MIN: + session->settings[CPAP_EPAPLo] = e->value(); + break; + case PRS1_SETTING_EPAP_MAX: + session->settings[CPAP_EPAPHi] = e->value(); + break; + case PRS1_SETTING_IPAP_MIN: + session->settings[CPAP_IPAPLo] = e->value(); + break; + case PRS1_SETTING_IPAP_MAX: + session->settings[CPAP_IPAPHi] = e->value(); + break; + case PRS1_SETTING_PS_MIN: + session->settings[CPAP_PSMin] = e->value(); + break; + case PRS1_SETTING_PS_MAX: + session->settings[CPAP_PSMax] = e->value(); + break; + case PRS1_SETTING_FLEX_MODE: + session->settings[PRS1_FlexMode] = e->m_value; + break; + case PRS1_SETTING_FLEX_LEVEL: + session->settings[PRS1_FlexLevel] = e->m_value; + break; + case PRS1_SETTING_RAMP_TIME: + session->settings[CPAP_RampTime] = e->m_value; + break; + case PRS1_SETTING_RAMP_PRESSURE: + session->settings[CPAP_RampPressure] = e->value(); + break; + case PRS1_SETTING_HUMID_STATUS: + session->settings[PRS1_HumidStatus] = (bool) e->m_value; + break; + case PRS1_SETTING_HUMID_LEVEL: + session->settings[PRS1_HumidLevel] = e->m_value; + break; + case PRS1_SETTING_SYSTEMONE_RESIST_LOCK: + session->settings[PRS1_SysLock] = (bool) e->m_value; + break; + case PRS1_SETTING_SYSTEMONE_RESIST_SETTING: + session->settings[PRS1_SysOneResistSet] = e->m_value; + break; + case PRS1_SETTING_SYSTEMONE_RESIST_STATUS: + session->settings[PRS1_SysOneResistStat] = (bool) e->m_value; + break; + case PRS1_SETTING_HOSE_DIAMETER: + session->settings[PRS1_HoseDiam] = e->m_value == 15 ? QObject::tr("15mm") : QObject::tr("22mm"); + break; + case PRS1_SETTING_AUTO_ON: + session->settings[PRS1_AutoOn] = (bool) e->m_value; + break; + case PRS1_SETTING_AUTO_OFF: + session->settings[PRS1_AutoOff] = (bool) e->m_value; + break; + case PRS1_SETTING_MASK_ALERT: + session->settings[PRS1_MaskAlert] = (bool) e->m_value; + break; + case PRS1_SETTING_SHOW_AHI: + session->settings[PRS1_ShowAHI] = (bool) e->m_value; + break; + default: + qWarning() << "Unknown PRS1 setting type" << (int) s->m_setting; + break; + } + } + + if (!ok) { + return false; + } + summary_duration = summary->duration; + + return true; +} + + bool PRS1Import::ParseSummary() { // TODO: The below is probably wrong. It should move to PRS1DataChunk when it gets fixed. From e47c4934ef43469e64d6d82550ab7355d339d42d Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Mon, 27 May 2019 11:05:34 -0400 Subject: [PATCH 23/31] Merge PRS1Import::ParseSummaryF* functionality into PRS1Import::ImportSummary. Only PRS1Import::ParseSummaryF0V23 has disappeared, being merged into ImportSummary. The only change it needed to subsume the functionality of all other variants was the addition of one extra setting. The rest of the now-unused variants will be removed next. --- oscar/SleepLib/loader_plugins/prs1_loader.cpp | 27 +++++++++---------- oscar/SleepLib/loader_plugins/prs1_loader.h | 2 -- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.cpp b/oscar/SleepLib/loader_plugins/prs1_loader.cpp index 1214644b..0a8e421b 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.cpp +++ b/oscar/SleepLib/loader_plugins/prs1_loader.cpp @@ -3963,14 +3963,8 @@ bool PRS1Import::ImportSummary() session->setPhysMax(CPAP_PS, 25); session->setPhysMin(CPAP_PS, 0); - return this->ParseSummary(); -} - - -bool PRS1Import::ParseSummaryF0V23() -{ bool ok; - ok = summary->ParseSummaryF0V23(); + ok = this->ParseSummary(); for (int i=0; i < summary->m_parsedData.count(); i++) { PRS1ParsedEvent* e = summary->m_parsedData.at(i); @@ -4034,6 +4028,9 @@ bool PRS1Import::ParseSummaryF0V23() case PRS1_SETTING_HUMID_STATUS: session->settings[PRS1_HumidStatus] = (bool) e->m_value; break; + case PRS1_SETTING_HEATED_TUBING: + session->settings[PRS1_HeatedTubing] = (bool) e->m_value; + break; case PRS1_SETTING_HUMID_LEVEL: session->settings[PRS1_HumidLevel] = e->m_value; break; @@ -4128,24 +4125,24 @@ bool PRS1Import::ParseSummary() switch (summary->family) { case 0: if (summary->familyVersion == 6) { - return ParseSummaryF0V6(); + return summary->ParseSummaryF0V6(); } else if (summary->familyVersion == 4) { - return ParseSummaryF0V4(); + return summary->ParseSummaryF0V4(); } else { - return ParseSummaryF0V23(); + return summary->ParseSummaryF0V23(); } case 3: - return ParseSummaryF3(); + return summary->ParseSummaryF3(); break; case 5: if (summary->familyVersion == 1) { - return ParseSummaryF5V012(); + return summary->ParseSummaryF5V012(); } else if (summary->familyVersion == 0) { - return ParseSummaryF5V012(); + return summary->ParseSummaryF5V012(); } else if (summary->familyVersion == 2) { - return ParseSummaryF5V012(); + return summary->ParseSummaryF5V012(); } else if (summary->familyVersion == 3) { - return ParseSummaryF5V3(); + return summary->ParseSummaryF5V3(); } default: ; diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.h b/oscar/SleepLib/loader_plugins/prs1_loader.h index 511b7958..4de1a08a 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.h +++ b/oscar/SleepLib/loader_plugins/prs1_loader.h @@ -242,8 +242,6 @@ public: bool ParseOximetery(); - //! \brief Summary parser for 50 series Family 0 CPAP/APAP models - bool ParseSummaryF0V23(); //! \brief Summary parser for 60 series Family 0 CPAP/APAP models bool ParseSummaryF0V4(); //! \brief Summary parser for 1060 series AVAPS models From 685527b993f090bf6a04a470b1e376079a613bcb Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Mon, 27 May 2019 11:14:55 -0400 Subject: [PATCH 24/31] Remove unused ParseSummaryF* functions, move ParseSummary from PRS1Import to PRS1DataChunk. --- oscar/SleepLib/loader_plugins/prs1_loader.cpp | 394 ++---------------- oscar/SleepLib/loader_plugins/prs1_loader.h | 18 +- 2 files changed, 30 insertions(+), 382 deletions(-) diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.cpp b/oscar/SleepLib/loader_plugins/prs1_loader.cpp index 0a8e421b..819f6aa8 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.cpp +++ b/oscar/SleepLib/loader_plugins/prs1_loader.cpp @@ -3216,94 +3216,6 @@ bool PRS1DataChunk::ParseSummaryF0V23() } -bool PRS1Import::ParseSummaryF0V4() -{ - bool ok; - ok = summary->ParseSummaryF0V4(); - - for (int i=0; i < summary->m_parsedData.count(); i++) { - PRS1ParsedEvent* e = summary->m_parsedData.at(i); - if (e->m_type != EV_PRS1_SETTING) { - qWarning() << "Summary had non-setting event:" << (int) e->m_type; - continue; - } - PRS1ParsedSettingEvent* s = (PRS1ParsedSettingEvent*) e; - switch (s->m_setting) { - case PRS1_SETTING_CPAP_MODE: - session->settings[CPAP_Mode] = e->m_value; - break; - case PRS1_SETTING_PRESSURE: - session->settings[CPAP_Pressure] = e->value(); - break; - case PRS1_SETTING_PRESSURE_MIN: - session->settings[CPAP_PressureMin] = e->value(); - break; - case PRS1_SETTING_PRESSURE_MAX: - session->settings[CPAP_PressureMax] = e->value(); - break; - case PRS1_SETTING_EPAP: - session->settings[CPAP_EPAP] = e->value(); - break; - case PRS1_SETTING_IPAP: - session->settings[CPAP_IPAP] = e->value(); - break; - case PRS1_SETTING_PS: - session->settings[CPAP_PS] = e->value(); - break; - case PRS1_SETTING_EPAP_MIN: - session->settings[CPAP_EPAPLo] = e->value(); - break; - case PRS1_SETTING_EPAP_MAX: - session->settings[CPAP_EPAPHi] = e->value(); - break; - case PRS1_SETTING_IPAP_MIN: - session->settings[CPAP_IPAPLo] = e->value(); - break; - case PRS1_SETTING_IPAP_MAX: - session->settings[CPAP_IPAPHi] = e->value(); - break; - case PRS1_SETTING_PS_MIN: - session->settings[CPAP_PSMin] = e->value(); - break; - case PRS1_SETTING_PS_MAX: - session->settings[CPAP_PSMax] = e->value(); - break; - case PRS1_SETTING_FLEX_MODE: - session->settings[PRS1_FlexMode] = e->m_value; - break; - case PRS1_SETTING_FLEX_LEVEL: - session->settings[PRS1_FlexLevel] = e->m_value; - break; - case PRS1_SETTING_RAMP_TIME: - session->settings[CPAP_RampTime] = e->m_value; - break; - case PRS1_SETTING_RAMP_PRESSURE: - session->settings[CPAP_RampPressure] = e->value(); - break; - case PRS1_SETTING_HUMID_STATUS: - session->settings[PRS1_HumidStatus] = (bool) e->m_value; - break; - case PRS1_SETTING_HEATED_TUBING: - session->settings[PRS1_HeatedTubing] = (bool) e->m_value; - break; - case PRS1_SETTING_HUMID_LEVEL: - session->settings[PRS1_HumidLevel] = e->m_value; - break; - default: - qWarning() << "Unknown PRS1 setting type" << (int) s->m_setting; - break; - } - } - - if (!ok) { - return false; - } - summary_duration = summary->duration; - - return true; -} - - bool PRS1DataChunk::ParseSummaryF0V4(void) { const unsigned char * data = (unsigned char *)this->m_data.constData(); @@ -3366,73 +3278,6 @@ bool PRS1DataChunk::ParseSummaryF0V4(void) } -bool PRS1Import::ParseSummaryF3() -{ - bool ok; - ok = summary->ParseSummaryF3(); - - for (int i=0; i < summary->m_parsedData.count(); i++) { - PRS1ParsedEvent* e = summary->m_parsedData.at(i); - if (e->m_type != EV_PRS1_SETTING) { - qWarning() << "Summary had non-setting event:" << (int) e->m_type; - continue; - } - PRS1ParsedSettingEvent* s = (PRS1ParsedSettingEvent*) e; - switch (s->m_setting) { - case PRS1_SETTING_CPAP_MODE: - session->settings[CPAP_Mode] = e->m_value; - break; - case PRS1_SETTING_PRESSURE: - session->settings[CPAP_Pressure] = e->value(); - break; - case PRS1_SETTING_PRESSURE_MIN: - session->settings[CPAP_PressureMin] = e->value(); - break; - case PRS1_SETTING_PRESSURE_MAX: - session->settings[CPAP_PressureMax] = e->value(); - break; - case PRS1_SETTING_EPAP: - session->settings[CPAP_EPAP] = e->value(); - break; - case PRS1_SETTING_IPAP: - session->settings[CPAP_IPAP] = e->value(); - break; - case PRS1_SETTING_PS: - session->settings[CPAP_PS] = e->value(); - break; - case PRS1_SETTING_EPAP_MIN: - session->settings[CPAP_EPAPLo] = e->value(); - break; - case PRS1_SETTING_EPAP_MAX: - session->settings[CPAP_EPAPHi] = e->value(); - break; - case PRS1_SETTING_IPAP_MIN: - session->settings[CPAP_IPAPLo] = e->value(); - break; - case PRS1_SETTING_IPAP_MAX: - session->settings[CPAP_IPAPHi] = e->value(); - break; - case PRS1_SETTING_PS_MIN: - session->settings[CPAP_PSMin] = e->value(); - break; - case PRS1_SETTING_PS_MAX: - session->settings[CPAP_PSMax] = e->value(); - break; - default: - qWarning() << "Unknown PRS1 setting type" << (int) s->m_setting; - break; - } - } - - if (!ok) { - return false; - } - summary_duration = summary->duration; - - return true; -} - - // TODO: This is probably only F3V6, as it uses mainblock, only present in fileVersion 3. bool PRS1DataChunk::ParseSummaryF3(void) { @@ -3479,75 +3324,6 @@ bool PRS1DataChunk::ParseSummaryF3(void) } -bool PRS1Import::ParseSummaryF5V012() -{ - bool ok; - ok = summary->ParseSummaryF5V012(); - - for (int i=0; i < summary->m_parsedData.count(); i++) { - PRS1ParsedEvent* e = summary->m_parsedData.at(i); - if (e->m_type != EV_PRS1_SETTING) { - qWarning() << "Summary had non-setting event:" << (int) e->m_type; - continue; - } - PRS1ParsedSettingEvent* s = (PRS1ParsedSettingEvent*) e; - switch (s->m_setting) { - case PRS1_SETTING_CPAP_MODE: - session->settings[CPAP_Mode] = e->m_value; - break; - case PRS1_SETTING_EPAP_MIN: - session->settings[CPAP_EPAPLo] = e->value(); - break; - case PRS1_SETTING_EPAP_MAX: - session->settings[CPAP_EPAPHi] = e->value(); - break; - case PRS1_SETTING_IPAP_MIN: - session->settings[CPAP_IPAPLo] = e->value(); - break; - case PRS1_SETTING_IPAP_MAX: - session->settings[CPAP_IPAPHi] = e->value(); - break; - case PRS1_SETTING_PS_MIN: - session->settings[CPAP_PSMin] = e->value(); - break; - case PRS1_SETTING_PS_MAX: - session->settings[CPAP_PSMax] = e->value(); - break; - case PRS1_SETTING_FLEX_MODE: - session->settings[PRS1_FlexMode] = e->m_value; - break; - case PRS1_SETTING_FLEX_LEVEL: - session->settings[PRS1_FlexLevel] = e->m_value; - break; - case PRS1_SETTING_RAMP_TIME: - session->settings[CPAP_RampTime] = e->m_value; - break; - case PRS1_SETTING_RAMP_PRESSURE: - session->settings[CPAP_RampPressure] = e->value(); - break; - case PRS1_SETTING_HUMID_STATUS: - session->settings[PRS1_HumidStatus] = (bool) e->m_value; - break; - case PRS1_SETTING_HEATED_TUBING: - session->settings[PRS1_HeatedTubing] = (bool) e->m_value; - break; - case PRS1_SETTING_HUMID_LEVEL: - session->settings[PRS1_HumidLevel] = e->m_value; - break; - default: - qWarning() << "Unknown PRS1 setting type" << (int) s->m_setting; - break; - } - } - - if (!ok) { - return false; - } - summary_duration = summary->duration; - - return true; -} - bool PRS1DataChunk::ParseSummaryF5V012(void) { const unsigned char * data = (unsigned char *)this->m_data.constData(); @@ -3635,55 +3411,6 @@ void PRS1DataChunk::ParseHumidifierSetting(int humid, bool supportsHeatedTubing) } -bool PRS1Import::ParseSummaryF5V3() -{ - bool ok; - ok = summary->ParseSummaryF5V3(); - - for (int i=0; i < summary->m_parsedData.count(); i++) { - PRS1ParsedEvent* e = summary->m_parsedData.at(i); - if (e->m_type != EV_PRS1_SETTING) { - qWarning() << "Summary had non-setting event:" << (int) e->m_type; - continue; - } - PRS1ParsedSettingEvent* s = (PRS1ParsedSettingEvent*) e; - switch (s->m_setting) { - case PRS1_SETTING_CPAP_MODE: - session->settings[CPAP_Mode] = e->m_value; - break; - case PRS1_SETTING_EPAP_MIN: - session->settings[CPAP_EPAPLo] = e->value(); - break; - case PRS1_SETTING_EPAP_MAX: - session->settings[CPAP_EPAPHi] = e->value(); - break; - case PRS1_SETTING_IPAP_MIN: - session->settings[CPAP_IPAPLo] = e->value(); - break; - case PRS1_SETTING_IPAP_MAX: - session->settings[CPAP_IPAPHi] = e->value(); - break; - case PRS1_SETTING_PS_MIN: - session->settings[CPAP_PSMin] = e->value(); - break; - case PRS1_SETTING_PS_MAX: - session->settings[CPAP_PSMax] = e->value(); - break; - default: - qWarning() << "Unknown PRS1 setting type" << (int) s->m_setting; - break; - } - } - - if (!ok) { - return false; - } - summary_duration = summary->duration; - - return true; -} - - bool PRS1DataChunk::ParseSummaryF5V3(void) { this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_CPAP_MODE, (int) MODE_ASV_VARIABLE_EPAP)); @@ -3716,73 +3443,6 @@ bool PRS1DataChunk::ParseSummaryF5V3(void) } -bool PRS1Import::ParseSummaryF0V6() -{ - bool ok; - ok = summary->ParseSummaryF0V6(); - - for (int i=0; i < summary->m_parsedData.count(); i++) { - PRS1ParsedEvent* e = summary->m_parsedData.at(i); - if (e->m_type != EV_PRS1_SETTING) { - qWarning() << "Summary had non-setting event:" << (int) e->m_type; - continue; - } - PRS1ParsedSettingEvent* s = (PRS1ParsedSettingEvent*) e; - switch (s->m_setting) { - case PRS1_SETTING_CPAP_MODE: - session->settings[CPAP_Mode] = e->m_value; - break; - case PRS1_SETTING_PRESSURE: - session->settings[CPAP_Pressure] = e->value(); - break; - case PRS1_SETTING_PRESSURE_MIN: - session->settings[CPAP_PressureMin] = e->value(); - break; - case PRS1_SETTING_PRESSURE_MAX: - session->settings[CPAP_PressureMax] = e->value(); - break; - case PRS1_SETTING_EPAP: - session->settings[CPAP_EPAP] = e->value(); - break; - case PRS1_SETTING_IPAP: - session->settings[CPAP_IPAP] = e->value(); - break; - case PRS1_SETTING_PS: - session->settings[CPAP_PS] = e->value(); - break; - case PRS1_SETTING_EPAP_MIN: - session->settings[CPAP_EPAPLo] = e->value(); - break; - case PRS1_SETTING_EPAP_MAX: - session->settings[CPAP_EPAPHi] = e->value(); - break; - case PRS1_SETTING_IPAP_MIN: - session->settings[CPAP_IPAPLo] = e->value(); - break; - case PRS1_SETTING_IPAP_MAX: - session->settings[CPAP_IPAPHi] = e->value(); - break; - case PRS1_SETTING_PS_MIN: - session->settings[CPAP_PSMin] = e->value(); - break; - case PRS1_SETTING_PS_MAX: - session->settings[CPAP_PSMax] = e->value(); - break; - default: - qWarning() << "Unknown PRS1 setting type" << (int) s->m_setting; - break; - } - } - - if (!ok) { - return false; - } - summary_duration = summary->duration; - - return true; -} - - bool PRS1DataChunk::ParseSummaryF0V6() { // DreamStation machines... @@ -3964,7 +3624,7 @@ bool PRS1Import::ImportSummary() session->setPhysMin(CPAP_PS, 0); bool ok; - ok = this->ParseSummary(); + ok = summary->ParseSummary(); for (int i=0; i < summary->m_parsedData.count(); i++) { PRS1ParsedEvent* e = summary->m_parsedData.at(i); @@ -4073,9 +3733,9 @@ bool PRS1Import::ImportSummary() } -bool PRS1Import::ParseSummary() +bool PRS1DataChunk::ParseSummary() { - // TODO: The below is probably wrong. It should move to PRS1DataChunk when it gets fixed. + // TODO: The below mainblock creation is probably wrong. It should move to to its own function when it gets fixed. /* Example data block 000000c6@0000: 00 [10] 01 [00 01 02 01 01 00 02 01 00 04 01 40 07 000000c6@0010: 01 60 1e 03 02 0c 14 2c 01 14 2d 01 40 2e 01 02 @@ -4085,33 +3745,33 @@ bool PRS1Import::ParseSummary() 000000c6@0050: 08 [61 60] 0a [00 00 00 00 03 00 00 00 02 00 02 00 000000c6@0060: 05 00 2b 11 00 10 2b 5c 07 12 00 00] 03 [00 00 01 000000c6@0070: 1a 00 38 04] */ - if (summary->fileVersion == 3) { + if (this->fileVersion == 3) { // Parse summary structures into bytearray map according to size given in header block - const unsigned char * data = (unsigned char *)summary->m_data.constData(); - int size = summary->m_data.size(); + const unsigned char * data = (unsigned char *)this->m_data.constData(); + int size = this->m_data.size(); int pos = 0; int bsize; short val, len; do { val = data[pos++]; - auto it = summary->hblock.find(val); - if (it == summary->hblock.end()) { - qDebug() << "Block parse error in ParseSummary" << session->session(); + auto it = this->hblock.find(val); + if (it == this->hblock.end()) { + qDebug() << "Block parse error in ParseSummary" << this->sessionid; break; } bsize = it.value(); if (val != 1) { // store the data block for later reference - summary->hbdata[val] = QByteArray((const char *)(&data[pos]), bsize); + this->hbdata[val] = QByteArray((const char *)(&data[pos]), bsize); } else { // Parse the nested data structure which contains settings int p2 = 0; do { val = data[pos + p2++]; len = data[pos + p2++]; - summary->mainblock[val] = QByteArray((const char *)(&data[pos+p2]), len); + this->mainblock[val] = QByteArray((const char *)(&data[pos+p2]), len); p2 += len; } while ((p2 < bsize) && ((pos+p2) < size)); } @@ -4122,33 +3782,33 @@ bool PRS1Import::ParseSummary() // Family 3 = BIPAP AVAPS // Family 5 = BIPAP AutoSV - switch (summary->family) { + switch (this->family) { case 0: - if (summary->familyVersion == 6) { - return summary->ParseSummaryF0V6(); - } else if (summary->familyVersion == 4) { - return summary->ParseSummaryF0V4(); + if (this->familyVersion == 6) { + return this->ParseSummaryF0V6(); + } else if (this->familyVersion == 4) { + return this->ParseSummaryF0V4(); } else { - return summary->ParseSummaryF0V23(); + return this->ParseSummaryF0V23(); } case 3: - return summary->ParseSummaryF3(); + return this->ParseSummaryF3(); break; case 5: - if (summary->familyVersion == 1) { - return summary->ParseSummaryF5V012(); - } else if (summary->familyVersion == 0) { - return summary->ParseSummaryF5V012(); - } else if (summary->familyVersion == 2) { - return summary->ParseSummaryF5V012(); - } else if (summary->familyVersion == 3) { - return summary->ParseSummaryF5V3(); + if (this->familyVersion == 1) { + return this->ParseSummaryF5V012(); + } else if (this->familyVersion == 0) { + return this->ParseSummaryF5V012(); + } else if (this->familyVersion == 2) { + return this->ParseSummaryF5V012(); + } else if (this->familyVersion == 3) { + return this->ParseSummaryF5V3(); } default: ; } - qWarning() << "unexpected family" << summary->family << "familyVersion" << summary->familyVersion; + qWarning() << "unexpected family" << this->family << "familyVersion" << this->familyVersion; return false; ////////////////////////////////////////////////////////////////////////////////////////// diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.h b/oscar/SleepLib/loader_plugins/prs1_loader.h index 4de1a08a..1c8ce865 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.h +++ b/oscar/SleepLib/loader_plugins/prs1_loader.h @@ -131,6 +131,9 @@ public: //! \brief Parse a single data chunk from a .000 file containing compliance data for a brick bool ParseCompliance(void); + //! \brief Figures out which Summary Parser to call, based on machine family/version and calls it. + bool ParseSummary(); + //! \brief Parse a single data chunk from a .001 file containing summary data for a family 0 CPAP/APAP family version 2 or 3 machine bool ParseSummaryF0V23(void); @@ -226,9 +229,6 @@ public: //! \brief Imports the .002 summary file. bool ImportSummary(); - //! \brief Figures out which Summary Parser to call, based on machine family/version and calls it. - bool ParseSummary(); - //! \brief Figures out which Event Parser to call, based on machine family/version and calls it. bool ParseEvents(); @@ -242,18 +242,6 @@ public: bool ParseOximetery(); - //! \brief Summary parser for 60 series Family 0 CPAP/APAP models - bool ParseSummaryF0V4(); - //! \brief Summary parser for 1060 series AVAPS models - bool ParseSummaryF3(); - //! \brief Summary parser for 50 series Family 5-0 through 5-2 BiPAP/AutoSV models - bool ParseSummaryF5V012(); - //! \brief Summary parser for 60 series Family 5-3 BiPAP/AutoSV models - bool ParseSummaryF5V3(); - - //! \brief Summary parser for DreamStation series CPAP/APAP models - bool ParseSummaryF0V6(); - //! \brief Parse a single data chunk from a .002 file containing event data for a standard system one machine bool ParseF0Events(); //! \brief Parse a single data chunk from a .002 file containing event data for a AVAPS 1060P machine From 3b4a5b4dd6b60b081a0b5ecc8c1dd005e6dbe4e3 Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Mon, 27 May 2019 12:38:55 -0400 Subject: [PATCH 25/31] Clean up PRS1Import::ParseF*Events functions in preparation for merging, no functional changes. --- oscar/SleepLib/loader_plugins/prs1_loader.cpp | 120 ++++++++++-------- 1 file changed, 67 insertions(+), 53 deletions(-) diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.cpp b/oscar/SleepLib/loader_plugins/prs1_loader.cpp index 819f6aa8..47d99c93 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.cpp +++ b/oscar/SleepLib/loader_plugins/prs1_loader.cpp @@ -1111,7 +1111,7 @@ enum PRS1ParsedEventType EV_PRS1_LL, EV_PRS1_HY, EV_PRS1_TOTLEAK, - EV_PRS1_LEAK, // F5V2: Is this real or actually TOTLEAK? + EV_PRS1_LEAK, // unintentional leak EV_PRS1_PRESSURE, // TODO: maybe fold PRESSURE and IPAP into one EV_PRS1_IPAP, EV_PRS1_IPAPLOW, @@ -1322,7 +1322,7 @@ public: PRS1TotalLeakEvent(int start, int value) : PRS1ParsedValueEvent(EV_PRS1_TOTLEAK, start, value) {} }; -class PRS1LeakEvent : public PRS1ParsedValueEvent // TODO: F5V2 - is this real or actually TotalLeak? +class PRS1LeakEvent : public PRS1ParsedValueEvent // TODO: do machines really report unintentional leak? { public: PRS1LeakEvent(int start, int value) : PRS1ParsedValueEvent(EV_PRS1_LEAK, start, value) {} @@ -1448,20 +1448,7 @@ void PRS1DataChunk::AddEvent(PRS1ParsedEvent* const event) bool PRS1Import::ParseF5EventsFV3() { - EventDataType currentPressure=0, leak, ps=0; - - bool calcLeaks = p_profile->cpap->calculateUnintentionalLeaks(); - EventDataType lpm4 = p_profile->cpap->custom4cmH2OLeaks(); - EventDataType lpm20 = p_profile->cpap->custom20cmH2OLeaks(); - - EventDataType lpm = lpm20 - lpm4; - EventDataType ppm = lpm / 16.0; - - - //qint64 start=timestamp; - qint64 t = qint64(event->timestamp) * 1000L; - session->updateFirst(t); - + // Required channels EventList *OA = session->AddEventList(CPAP_Obstructive, EVL_Event); EventList *HY = session->AddEventList(CPAP_Hypopnea, EVL_Event); @@ -1487,6 +1474,21 @@ bool PRS1Import::ParseF5EventsFV3() EventList *FL = session->AddEventList(CPAP_FlowLimit, EVL_Event); EventList *VS = session->AddEventList(CPAP_VSnore, EVL_Event); + // Unintentional leak calculation, see zMaskProfile:calcLeak in calcs.cpp for explanation + EventDataType currentPressure=0, leak, ps=0; + + bool calcLeaks = p_profile->cpap->calculateUnintentionalLeaks(); + EventDataType lpm4 = p_profile->cpap->custom4cmH2OLeaks(); + EventDataType lpm20 = p_profile->cpap->custom20cmH2OLeaks(); + + EventDataType lpm = lpm20 - lpm4; + EventDataType ppm = lpm / 16.0; + + + //qint64 start=timestamp; + qint64 t = qint64(event->timestamp) * 1000L; + session->updateFirst(t); + bool ok; ok = event->ParseEventsF5V3(); if (!ok) { @@ -1726,17 +1728,7 @@ bool PRS1DataChunk::ParseEventsF5V3(void) bool PRS1Import::ParseF5Events() { - ChannelID Codes[] = { - PRS1_00, PRS1_01, CPAP_Pressure, CPAP_EPAP, CPAP_PressurePulse, CPAP_Obstructive, - CPAP_ClearAirway, CPAP_Hypopnea, PRS1_08, CPAP_FlowLimit, PRS1_0A, CPAP_PB, - PRS1_0C, CPAP_VSnore, PRS1_0E, PRS1_0F, - CPAP_LargeLeak, // Large leak apparently - CPAP_LeakTotal, PRS1_12 - }; - - int ncodes = sizeof(Codes) / sizeof(ChannelID); - EventList *Code[0x20] = {nullptr}; - + // Required channels EventList *OA = session->AddEventList(CPAP_Obstructive, EVL_Event); EventList *HY = session->AddEventList(CPAP_Hypopnea, EVL_Event); @@ -1763,9 +1755,22 @@ bool PRS1Import::ParseF5Events() EventList *VS = session->AddEventList(CPAP_VSnore, EVL_Event); // EventList *VS2 = session->AddEventList(CPAP_VSnore2, EVL_Event); + // On-demand channels + ChannelID Codes[] = { + PRS1_00, PRS1_01, CPAP_Pressure, CPAP_EPAP, CPAP_PressurePulse, CPAP_Obstructive, + CPAP_ClearAirway, CPAP_Hypopnea, PRS1_08, CPAP_FlowLimit, PRS1_0A, CPAP_PB, + PRS1_0C, CPAP_VSnore, PRS1_0E, PRS1_0F, + CPAP_LargeLeak, // Large leak apparently + CPAP_LeakTotal, PRS1_12 + }; + + int ncodes = sizeof(Codes) / sizeof(ChannelID); + EventList *Code[0x20] = {nullptr}; + //EventList * PRESSURE=nullptr; //EventList * PP=nullptr; + // Unintentional leak calculation, see zMaskProfile:calcLeak in calcs.cpp for explanation EventDataType currentPressure=0, leak, ps=0; //, p; bool calcLeaks = p_profile->cpap->calculateUnintentionalLeaks(); @@ -1828,7 +1833,7 @@ bool PRS1Import::ParseF5Events() LEAK->AddEvent(t, leak); } break; - case EV_PRS1_LEAK: // F5V2, is this really leak rather than totleak? + case EV_PRS1_LEAK: LEAK->AddEvent(t, e->m_value); break; case EV_PRS1_RR: @@ -2100,7 +2105,7 @@ bool PRS1DataChunk::ParseEventsF5V012(void) this->AddEvent(new PRS1IPAPEvent(t, data1=buffer[pos+0])); // 0 this->AddEvent(new PRS1IPAPLowEvent(t, buffer[pos+1])); // 1 this->AddEvent(new PRS1IPAPHighEvent(t, buffer[pos+2])); // 2 - this->AddEvent(new PRS1LeakEvent(t, buffer[pos+3])); // 3 // F5V2, is this really leak rather than totleak? + this->AddEvent(new PRS1LeakEvent(t, buffer[pos+3])); // 3 // F5V2, is this really unintentional leak rather than total leak? this->AddEvent(new PRS1TidalVolumeEvent(t, buffer[pos+7])); // 7 this->AddEvent(new PRS1RespiratoryRateEvent(t, buffer[pos+4])); // 4 this->AddEvent(new PRS1PatientTriggeredBreathsEvent(t, buffer[pos+5])); // 5 @@ -2175,8 +2180,7 @@ bool PRS1DataChunk::ParseEventsF5V012(void) bool PRS1Import::ParseF3EventsV3() { - // AVAPS machine... it's delta packed, unlike the older ones?? (double check that! :/) - + // Required channels EventList *TB = session->AddEventList(PRS1_TimedBreath, EVL_Event); EventList *OA = session->AddEventList(CPAP_Obstructive, EVL_Event); EventList *HY = session->AddEventList(CPAP_Hypopnea, EVL_Event); @@ -2200,6 +2204,8 @@ bool PRS1Import::ParseF3EventsV3() EventList *FLOW = session->AddEventList(CPAP_Test2, EVL_Event); qint64 t; + // missing session->updateFirst(t)? + bool ok; ok = event->ParseEventsF3V6(); @@ -2232,7 +2238,7 @@ bool PRS1Import::ParseF3EventsV3() case EV_PRS1_IPAP: IPAP->AddEvent(t, e->m_value); break; - case EV_PRS1_LEAK: // F3V6, is this really leak rather than totleak? + case EV_PRS1_LEAK: LEAK->AddEvent(t, e->m_value); break; case EV_PRS1_RR: @@ -2298,6 +2304,8 @@ bool PRS1Import::ParseF3EventsV3() // 1030X, 11030X series bool PRS1DataChunk::ParseEventsF3V6(void) { + // AVAPS machine... it's delta packed, unlike the older ones?? (double check that! :/) + if (this->family != 3 || this->familyVersion != 6) { qWarning() << "ParseEventsF3V6 called with family" << this->family << "familyVersion" << this->familyVersion; //break; // don't break to avoid changing behavior (for now) @@ -2333,7 +2341,7 @@ bool PRS1DataChunk::ParseEventsF3V6(void) this->AddEvent(new PRS1TimedBreathEvent(t, val)); break; case 0x02: - this->AddEvent(new PRS1LeakEvent(t, data[pos+3])); + this->AddEvent(new PRS1LeakEvent(t, data[pos+3])); // TODO: F3V6, is this really unintentional leak rather than total leak? this->AddEvent(new PRS1PatientTriggeredBreathsEvent(t, data[pos+5])); this->AddEvent(new PRS1MinuteVentilationEvent(t, data[pos+6])); this->AddEvent(new PRS1TidalVolumeEvent(t, data[pos+7])); @@ -2404,9 +2412,7 @@ bool PRS1DataChunk::ParseEventsF3V6(void) bool PRS1Import::ParseF3Events() { - qint64 t = qint64(event->timestamp) * 1000L; - - session->updateFirst(t); + // Required channels EventList *OA = session->AddEventList(CPAP_Obstructive, EVL_Event); EventList *HY = session->AddEventList(CPAP_Hypopnea, EVL_Event); EventList *CA = session->AddEventList(CPAP_ClearAirway, EVL_Event); @@ -2421,6 +2427,9 @@ bool PRS1Import::ParseF3Events() EventList *IPAP = session->AddEventList(CPAP_IPAP, EVL_Event,0.1f); EventList *FLOW = session->AddEventList(CPAP_FlowRate, EVL_Event); + qint64 t = qint64(event->timestamp) * 1000L; + session->updateFirst(t); + bool ok; ok = event->ParseEventsF3V3(); @@ -2471,6 +2480,10 @@ bool PRS1Import::ParseF3Events() } } + if (!ok) { + return false; + } + return true; } @@ -2509,7 +2522,7 @@ bool PRS1DataChunk::ParseEventsF3V3(void) //TMV->AddEvent(t, h[9]); // not sure what this is.. encore doesn't graph it. // h[10]? this->AddEvent(new PRS1MinuteVentilationEvent(t, h[11])); - this->AddEvent(new PRS1LeakEvent(t, h[15])); + this->AddEvent(new PRS1LeakEvent(t, h[15])); // TODO: F3V3, is this really unintentional leak rather than total leak? hy = h[12]; // count of hypopnea events ca = h[13]; // count of clear airway events @@ -2553,6 +2566,8 @@ bool PRS1DataChunk::ParseEventsF3V3(void) } +#if 0 +// Currently unused, apparently an abandoned effort to massage F0 pressure/IPAP/EPAP data. extern EventDataType CatmullRomSpline(EventDataType p0, EventDataType p1, EventDataType p2, EventDataType p3, EventDataType t = 0.5); void SmoothEventList(Session * session, EventList * ev, ChannelID code) @@ -2617,24 +2632,14 @@ void SmoothEventList(Session * session, EventList * ev, ChannelID code) } } +#endif // 750P is F0V2; 550P is F0V2/F0V3; 450P is F0V3; 460P, 560P[BT], 660P, 760P are F0V4 // 200X, 400X, 400G, 500X, 502G, 600X, 700X are F0V6 bool PRS1Import::ParseF0Events() { - ChannelID Codes[] = { - PRS1_00, PRS1_01, 0, 0, 0, 0, 0, 0, 0, 0, 0, - PRS1_0B, 0, 0, PRS1_0E - }; - - int ncodes = sizeof(Codes) / sizeof(ChannelID); - EventList *Code[0x20] = {0}; - - qint64 t = qint64(event->timestamp) * 1000L; - - session->updateFirst(t); - + // Required channels EventList *OA = session->AddEventList(CPAP_Obstructive, EVL_Event); EventList *HY = session->AddEventList(CPAP_Hypopnea, EVL_Event); EventList *PB = session->AddEventList(CPAP_PB, EVL_Event); @@ -2650,6 +2655,15 @@ bool PRS1Import::ParseF0Events() EventList *VS2 = session->AddEventList(CPAP_VSnore2, EVL_Event); //EventList *T1 = session->AddEventList(CPAP_Test1, EVL_Event, 0.1); + // On-demand channels + ChannelID Codes[] = { + PRS1_00, PRS1_01, 0, 0, 0, 0, 0, 0, 0, 0, 0, + PRS1_0B, 0, 0, PRS1_0E + }; + + int ncodes = sizeof(Codes) / sizeof(ChannelID); + EventList *Code[0x20] = {0}; + Code[0x0e] = session->AddEventList(PRS1_0E, EVL_Event); EventList * LL = session->AddEventList(CPAP_LargeLeak, EVL_Event); @@ -2658,6 +2672,7 @@ bool PRS1Import::ParseF0Events() EventList *IPAP = nullptr; EventList *PS = nullptr; + // Unintentional leak calculation, see zMaskProfile:calcLeak in calcs.cpp for explanation EventDataType currentPressure=0, leak; //, p; bool calcLeaks = p_profile->cpap->calculateUnintentionalLeaks(); @@ -2669,6 +2684,9 @@ bool PRS1Import::ParseF0Events() CPAPMode mode = (CPAPMode) session->settings[CPAP_Mode].toInt(); + qint64 t = qint64(event->timestamp) * 1000L; + session->updateFirst(t); + bool ok; ok = event->ParseEventsF0(mode); @@ -2773,10 +2791,6 @@ bool PRS1Import::ParseF0Events() t = qint64(event->timestamp + event->duration) * 1000L; -// SmoothEventList(session, PRESSURE, CPAP_Pressure); -// SmoothEventList(session, IPAP, CPAP_IPAP); -// SmoothEventList(session, EPAP, CPAP_EPAP); - session->updateLast(t); session->m_cnt.clear(); session->m_cph.clear(); From 7c949fc8430bf0ec143d3b53ada800570da756cb Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Mon, 27 May 2019 21:02:28 -0400 Subject: [PATCH 26/31] Convert PRS1ParsedEvent enums to static constants, update switch statements. --- oscar/SleepLib/loader_plugins/prs1_loader.cpp | 380 +++++++----------- 1 file changed, 155 insertions(+), 225 deletions(-) diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.cpp b/oscar/SleepLib/loader_plugins/prs1_loader.cpp index 47d99c93..b9d31bab 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.cpp +++ b/oscar/SleepLib/loader_plugins/prs1_loader.cpp @@ -1099,6 +1099,12 @@ void PRS1Loader::ScanFiles(const QStringList & paths, int sessionid_base, Machin } } + +//******************************************************************************************** +// Internal PRS1 parsed data types +//******************************************************************************************** + +// For new events, add an enum here and then a class below with an PRS1_*_EVENT macro enum PRS1ParsedEventType { EV_PRS1_RAW = -1, // these only get logged @@ -1186,9 +1192,13 @@ public: inline float value(void) { return (m_value * m_gain) + m_offset; } + static const PRS1ParsedEventType TYPE = EV_PRS1_UNKNOWN; + static constexpr float GAIN = 1.0; + static const PRS1ParsedEventUnit UNIT = PRS1_UNIT_NONE; + protected: PRS1ParsedEvent(PRS1ParsedEventType type, int start) - : m_type(type), m_start(start), m_duration(0), m_value(0), m_offset(0.0), m_gain(1.0), m_unit(PRS1_UNIT_NONE) + : m_type(type), m_start(start), m_duration(0), m_value(0), m_offset(0.0), m_gain(GAIN), m_unit(UNIT) { } ~PRS1ParsedEvent() @@ -1215,17 +1225,20 @@ class PRS1UnknownValueEvent : public PRS1ParsedValueEvent { public: int m_code; - PRS1UnknownValueEvent(int code, int start, int value, float gain=1.0) : PRS1ParsedValueEvent(EV_PRS1_UNKNOWN, start, value), m_code(code) { m_gain = gain; } + PRS1UnknownValueEvent(int code, int start, int value, float gain=1.0) : PRS1ParsedValueEvent(TYPE, start, value), m_code(code) { m_gain = gain; } }; class PRS1UnknownDataEvent : public PRS1ParsedEvent { public: + static const PRS1ParsedEventType TYPE = EV_PRS1_RAW; + int m_pos; unsigned char m_code; QByteArray m_data; + PRS1UnknownDataEvent(const QByteArray & data, int pos, int len=18) - : PRS1ParsedEvent(EV_PRS1_RAW, 0) + : PRS1ParsedEvent(TYPE, 0) { m_pos = pos; m_data = data.mid(pos, len); @@ -1251,195 +1264,112 @@ public: class PRS1ParsedSettingEvent : public PRS1ParsedValueEvent { public: + static const PRS1ParsedEventType TYPE = EV_PRS1_SETTING; PRS1ParsedSettingType m_setting; - PRS1ParsedSettingEvent(PRS1ParsedSettingType setting, int value) : PRS1ParsedValueEvent(EV_PRS1_SETTING, 0, value), m_setting(setting) {} + PRS1ParsedSettingEvent(PRS1ParsedSettingType setting, int value) : PRS1ParsedValueEvent(TYPE, 0, value), m_setting(setting) {} }; class PRS1PressureSettingEvent : public PRS1ParsedSettingEvent { public: + static constexpr float GAIN = PRS1PressureEvent::GAIN; + static const PRS1ParsedEventUnit UNIT = PRS1PressureEvent::UNIT; + PRS1PressureSettingEvent(PRS1ParsedSettingType setting, int value) : PRS1ParsedSettingEvent(setting, value) { - m_gain = PRS1PressureEvent::GAIN; - m_unit = PRS1PressureEvent::UNIT; + m_gain = GAIN; + m_unit = UNIT; } }; class PRS1ParsedSliceEvent : public PRS1ParsedDurationEvent { public: + static const PRS1ParsedEventType TYPE = EV_PRS1_SLICE; SliceStatus m_status; - PRS1ParsedSliceEvent(int start, int duration, SliceStatus status) : PRS1ParsedDurationEvent(EV_PRS1_SLICE, start, duration), m_status(status) {} + + PRS1ParsedSliceEvent(int start, int duration, SliceStatus status) : PRS1ParsedDurationEvent(TYPE, start, duration), m_status(status) {} }; +#define _PRS1_EVENT(T, E, P, ARG) \ +class T : public P \ +{ \ +public: \ + static const PRS1ParsedEventType TYPE = E; \ + T(int start, int ARG) : P(TYPE, start, ARG) {} \ +} +#define PRS1_DURATION_EVENT(T, E) _PRS1_EVENT(T, E, PRS1ParsedDurationEvent, duration) +#define PRS1_VALUE_EVENT(T, E) _PRS1_EVENT(T, E, PRS1ParsedValueEvent, value) +#define PRS1_PRESSURE_EVENT(T, E) _PRS1_EVENT(T, E, PRS1PressureEvent, value) -class PRS1TimedBreathEvent : public PRS1ParsedDurationEvent -{ -public: - PRS1TimedBreathEvent(int start, int duration) : PRS1ParsedDurationEvent(EV_PRS1_TB, start, duration) {} -}; +PRS1_DURATION_EVENT(PRS1TimedBreathEvent, EV_PRS1_TB); -class PRS1ObstructiveApneaEvent : public PRS1ParsedDurationEvent -{ -public: - PRS1ObstructiveApneaEvent(int start, int duration) : PRS1ParsedDurationEvent(EV_PRS1_OA, start, duration) {} -}; +PRS1_DURATION_EVENT(PRS1ObstructiveApneaEvent, EV_PRS1_OA); -class PRS1ClearAirwayEvent : public PRS1ParsedDurationEvent -{ -public: - PRS1ClearAirwayEvent(int start, int duration) : PRS1ParsedDurationEvent(EV_PRS1_CA, start, duration) {} -}; +PRS1_DURATION_EVENT(PRS1ClearAirwayEvent, EV_PRS1_CA); -class PRS1FlowLimitationEvent : public PRS1ParsedDurationEvent -{ -public: - PRS1FlowLimitationEvent(int start, int duration) : PRS1ParsedDurationEvent(EV_PRS1_FL, start, duration) {} -}; +PRS1_DURATION_EVENT(PRS1FlowLimitationEvent, EV_PRS1_FL); -class PRS1PeriodicBreathingEvent : public PRS1ParsedDurationEvent -{ -public: - PRS1PeriodicBreathingEvent(int start, int duration) : PRS1ParsedDurationEvent(EV_PRS1_PB, start, duration) {} -}; +PRS1_DURATION_EVENT(PRS1PeriodicBreathingEvent, EV_PRS1_PB); -class PRS1LargeLeakEvent : public PRS1ParsedDurationEvent -{ -public: - PRS1LargeLeakEvent(int start, int duration) : PRS1ParsedDurationEvent(EV_PRS1_LL, start, duration) {} -}; +PRS1_DURATION_EVENT(PRS1LargeLeakEvent, EV_PRS1_LL); -class PRS1HypopneaEvent : public PRS1ParsedDurationEvent -{ -public: - PRS1HypopneaEvent(int start, int duration) : PRS1ParsedDurationEvent(EV_PRS1_HY, start, duration) {} -}; +PRS1_DURATION_EVENT(PRS1HypopneaEvent, EV_PRS1_HY); -class PRS1TotalLeakEvent : public PRS1ParsedValueEvent -{ -public: - PRS1TotalLeakEvent(int start, int value) : PRS1ParsedValueEvent(EV_PRS1_TOTLEAK, start, value) {} -}; +PRS1_VALUE_EVENT(PRS1TotalLeakEvent, EV_PRS1_TOTLEAK); -class PRS1LeakEvent : public PRS1ParsedValueEvent // TODO: do machines really report unintentional leak? -{ -public: - PRS1LeakEvent(int start, int value) : PRS1ParsedValueEvent(EV_PRS1_LEAK, start, value) {} -}; +PRS1_VALUE_EVENT(PRS1LeakEvent, EV_PRS1_LEAK); // TODO: do machines really report unintentional leak? -class PRS1CPAPEvent : public PRS1PressureEvent -{ -public: - PRS1CPAPEvent(int start, int value) : PRS1PressureEvent(EV_PRS1_PRESSURE, start, value) {} -}; +PRS1_PRESSURE_EVENT(PRS1CPAPEvent, EV_PRS1_PRESSURE); -class PRS1IPAPEvent : public PRS1PressureEvent -{ -public: - PRS1IPAPEvent(int start, int value) : PRS1PressureEvent(EV_PRS1_IPAP, start, value) {} -}; +PRS1_PRESSURE_EVENT(PRS1IPAPEvent, EV_PRS1_IPAP); -class PRS1IPAPHighEvent : public PRS1PressureEvent -{ -public: - PRS1IPAPHighEvent(int start, int value) : PRS1PressureEvent(EV_PRS1_IPAPHIGH, start, value) {} -}; +PRS1_PRESSURE_EVENT(PRS1IPAPHighEvent, EV_PRS1_IPAPHIGH); -class PRS1IPAPLowEvent : public PRS1PressureEvent -{ -public: - PRS1IPAPLowEvent(int start, int value) : PRS1PressureEvent(EV_PRS1_IPAPLOW, start, value) {} -}; +PRS1_PRESSURE_EVENT(PRS1IPAPLowEvent, EV_PRS1_IPAPLOW); -class PRS1EPAPEvent : public PRS1PressureEvent -{ -public: - PRS1EPAPEvent(int start, int value) : PRS1PressureEvent(EV_PRS1_EPAP, start, value) {} -}; +PRS1_PRESSURE_EVENT(PRS1EPAPEvent, EV_PRS1_EPAP); -class PRS1PressureReliefEvent : public PRS1PressureEvent // value is pressure applied during pressure relief, similar to EPAP -{ -public: - PRS1PressureReliefEvent(int start, int value) : PRS1PressureEvent(EV_PRS1_FLEX, start, value) {} -}; +PRS1_PRESSURE_EVENT(PRS1PressureReliefEvent, EV_PRS1_FLEX); // value is pressure applied during pressure relief, similar to EPAP -class PRS1RespiratoryRateEvent : public PRS1ParsedValueEvent -{ -public: - PRS1RespiratoryRateEvent(int start, int value) : PRS1ParsedValueEvent(EV_PRS1_RR, start, value) {} -}; +PRS1_VALUE_EVENT(PRS1RespiratoryRateEvent, EV_PRS1_RR); -class PRS1PatientTriggeredBreathsEvent : public PRS1ParsedValueEvent -{ -public: - PRS1PatientTriggeredBreathsEvent(int start, int value) : PRS1ParsedValueEvent(EV_PRS1_PTB, start, value) {} -}; +PRS1_VALUE_EVENT(PRS1PatientTriggeredBreathsEvent, EV_PRS1_PTB); -class PRS1MinuteVentilationEvent : public PRS1ParsedValueEvent -{ -public: - PRS1MinuteVentilationEvent(int start, int value) : PRS1ParsedValueEvent(EV_PRS1_MV, start, value) {} -}; +PRS1_VALUE_EVENT(PRS1MinuteVentilationEvent, EV_PRS1_MV); class PRS1TidalVolumeEvent : public PRS1ParsedValueEvent { public: + static const PRS1ParsedEventType TYPE = EV_PRS1_TV; + + static constexpr float GAIN = 10.0; + static const PRS1ParsedEventUnit UNIT = PRS1_UNIT_ML; + PRS1TidalVolumeEvent(int start, int value) - : PRS1ParsedValueEvent(EV_PRS1_TV, start, value) + : PRS1ParsedValueEvent(TYPE, start, value) { - m_gain = 10.0; - m_unit = PRS1_UNIT_ML; + m_gain = GAIN; + m_unit = UNIT; } }; -class PRS1SnoreEvent : public PRS1ParsedValueEvent -{ -public: - PRS1SnoreEvent(int start, int value) : PRS1ParsedValueEvent(EV_PRS1_SNORE, start, value) {} -}; +PRS1_VALUE_EVENT(PRS1SnoreEvent, EV_PRS1_SNORE); -class PRS1VibratorySnoreEvent : public PRS1ParsedValueEvent -{ -public: - PRS1VibratorySnoreEvent(int start, int value) : PRS1ParsedValueEvent(EV_PRS1_VS, start, value) {} -}; +PRS1_VALUE_EVENT(PRS1VibratorySnoreEvent, EV_PRS1_VS); -class PRS1PressurePulseEvent : public PRS1ParsedValueEvent -{ -public: - PRS1PressurePulseEvent(int start, int value) : PRS1ParsedValueEvent(EV_PRS1_PP, start, value) {} -}; +PRS1_VALUE_EVENT(PRS1PressurePulseEvent, EV_PRS1_PP); -class PRS1RERAEvent : public PRS1ParsedValueEvent // TODO: should this really be a duration event? -{ -public: - PRS1RERAEvent(int start, int value) : PRS1ParsedValueEvent(EV_PRS1_RERA, start, value) {} -}; +PRS1_VALUE_EVENT(PRS1RERAEvent, EV_PRS1_RERA); // TODO: should this really be a duration event? -class PRS1NonRespondingEvent : public PRS1ParsedValueEvent // TODO: is this a single event or an index/hour? -{ -public: - PRS1NonRespondingEvent(int start, int value) : PRS1ParsedValueEvent(EV_PRS1_NRI, start, value) {} -}; +PRS1_VALUE_EVENT(PRS1NonRespondingEvent, EV_PRS1_NRI); // TODO: is this a single event or an index/hour? -class PRS1FlowRateEvent : public PRS1ParsedValueEvent // TODO: is this a single event or an index/hour? -{ -public: - PRS1FlowRateEvent(int start, int value) : PRS1ParsedValueEvent(EV_PRS1_FLOWRATE, start, value) {} -}; +PRS1_VALUE_EVENT(PRS1FlowRateEvent, EV_PRS1_FLOWRATE); // TODO: is this a single event or an index/hour? -class PRS1Test1Event : public PRS1ParsedValueEvent // TODO: replace test1/2 event with unknownvalue events and appropriate mapping -{ -public: - PRS1Test1Event(int start, int value) : PRS1ParsedValueEvent(EV_PRS1_TEST1, start, value) {} -}; +PRS1_VALUE_EVENT(PRS1Test1Event, EV_PRS1_TEST1); // TODO: replace test1/2 event with unknownvalue events and appropriate mapping -class PRS1Test2Event : public PRS1ParsedValueEvent -{ -public: - PRS1Test2Event(int start, int value) : PRS1ParsedValueEvent(EV_PRS1_TEST2, start, value) {} -}; +PRS1_VALUE_EVENT(PRS1Test2Event, EV_PRS1_TEST2); void PRS1DataChunk::AddEvent(PRS1ParsedEvent* const event) { @@ -1500,32 +1430,32 @@ bool PRS1Import::ParseF5EventsFV3() t = qint64(event->timestamp + e->m_start) * 1000L; switch (e->m_type) { - case EV_PRS1_TB: + case PRS1TimedBreathEvent::TYPE: TB->AddEvent(t, e->m_duration); break; - case EV_PRS1_OA: + case PRS1ObstructiveApneaEvent::TYPE: OA->AddEvent(t, e->m_duration); break; - case EV_PRS1_CA: + case PRS1ClearAirwayEvent::TYPE: CA->AddEvent(t, e->m_duration); break; - case EV_PRS1_FL: + case PRS1FlowLimitationEvent::TYPE: FL->AddEvent(t, e->m_duration); break; - case EV_PRS1_PB: + case PRS1PeriodicBreathingEvent::TYPE: PB->AddEvent(t, e->m_duration); break; - case EV_PRS1_LL: + case PRS1LargeLeakEvent::TYPE: LL->AddEvent(t, e->m_duration); break; - case EV_PRS1_HY: + case PRS1HypopneaEvent::TYPE: HY->AddEvent(t, e->m_duration); break; - case EV_PRS1_IPAP: + case PRS1IPAPEvent::TYPE: IPAP->AddEvent(t, e->m_value); currentPressure = e->m_value; break; - case EV_PRS1_TOTLEAK: + case PRS1TotalLeakEvent::TYPE: TOTLEAK->AddEvent(t, e->m_value); leak = e->m_value; if (calcLeaks) { // Much Quicker doing this here than the recalc method. @@ -1534,31 +1464,31 @@ bool PRS1Import::ParseF5EventsFV3() LEAK->AddEvent(t, leak); } break; - case EV_PRS1_IPAPLOW: + case PRS1IPAPLowEvent::TYPE: IPAPLo->AddEvent(t, e->m_value); break; - case EV_PRS1_IPAPHIGH: + case PRS1IPAPHighEvent::TYPE: IPAPHi->AddEvent(t, e->m_value); break; - case EV_PRS1_RR: + case PRS1RespiratoryRateEvent::TYPE: RR->AddEvent(t, e->m_value); break; - case EV_PRS1_PTB: + case PRS1PatientTriggeredBreathsEvent::TYPE: PTB->AddEvent(t, e->m_value); break; - case EV_PRS1_MV: + case PRS1MinuteVentilationEvent::TYPE: MV->AddEvent(t, e->m_value); break; - case EV_PRS1_TV: + case PRS1TidalVolumeEvent::TYPE: TV->AddEvent(t, e->m_value); break; - case EV_PRS1_SNORE: + case PRS1SnoreEvent::TYPE: SNORE->AddEvent(t, e->m_value); if (e->m_value > 0) { VS->AddEvent(t, 0); //data2); // VSnore } break; - case EV_PRS1_EPAP: + case PRS1EPAPEvent::TYPE: EPAP->AddEvent(t, e->m_value); ps = currentPressure - e->m_value; PS->AddEvent(t, ps); // Pressure Support @@ -1793,38 +1723,38 @@ bool PRS1Import::ParseF5Events() t = qint64(event->timestamp + e->m_start) * 1000L; switch (e->m_type) { - case EV_PRS1_TB: + case PRS1TimedBreathEvent::TYPE: TB->AddEvent(t, e->m_duration); break; - case EV_PRS1_OA: + case PRS1ObstructiveApneaEvent::TYPE: OA->AddEvent(t, e->m_duration); break; - case EV_PRS1_CA: + case PRS1ClearAirwayEvent::TYPE: CA->AddEvent(t, e->m_duration); break; - case EV_PRS1_FL: + case PRS1FlowLimitationEvent::TYPE: FL->AddEvent(t, e->m_duration); break; - case EV_PRS1_PB: + case PRS1PeriodicBreathingEvent::TYPE: PB->AddEvent(t, e->m_duration); break; - case EV_PRS1_LL: + case PRS1LargeLeakEvent::TYPE: LL->AddEvent(t, e->m_duration); break; - case EV_PRS1_HY: + case PRS1HypopneaEvent::TYPE: HY->AddEvent(t, e->m_duration); break; - case EV_PRS1_IPAP: + case PRS1IPAPEvent::TYPE: IPAP->AddEvent(t, e->m_value); currentPressure = e->m_value; break; - case EV_PRS1_IPAPLOW: + case PRS1IPAPLowEvent::TYPE: IPAPLo->AddEvent(t, e->m_value); break; - case EV_PRS1_IPAPHIGH: + case PRS1IPAPHighEvent::TYPE: IPAPHi->AddEvent(t, e->m_value); break; - case EV_PRS1_TOTLEAK: + case PRS1TotalLeakEvent::TYPE: TOTLEAK->AddEvent(t, e->m_value); leak = e->m_value; if (calcLeaks) { // Much Quicker doing this here than the recalc method. @@ -1833,33 +1763,33 @@ bool PRS1Import::ParseF5Events() LEAK->AddEvent(t, leak); } break; - case EV_PRS1_LEAK: + case PRS1LeakEvent::TYPE: LEAK->AddEvent(t, e->m_value); break; - case EV_PRS1_RR: + case PRS1RespiratoryRateEvent::TYPE: RR->AddEvent(t, e->m_value); break; - case EV_PRS1_PTB: + case PRS1PatientTriggeredBreathsEvent::TYPE: PTB->AddEvent(t, e->m_value); break; - case EV_PRS1_MV: + case PRS1MinuteVentilationEvent::TYPE: MV->AddEvent(t, e->m_value); break; - case EV_PRS1_TV: + case PRS1TidalVolumeEvent::TYPE: TV->AddEvent(t, e->m_value); break; - case EV_PRS1_SNORE: + case PRS1SnoreEvent::TYPE: SNORE->AddEvent(t, e->m_value); if (e->m_value > 0) { VS->AddEvent(t, 0); //data2); // VSnore } break; - case EV_PRS1_EPAP: + case PRS1EPAPEvent::TYPE: EPAP->AddEvent(t, e->m_value); ps = currentPressure - e->m_value; PS->AddEvent(t, ps); // Pressure Support break; - case EV_PRS1_UNKNOWN: + case PRS1UnknownValueEvent::TYPE: { int code = ((PRS1UnknownValueEvent*) e)->m_code; Q_ASSERT(code < ncodes); @@ -2214,58 +2144,58 @@ bool PRS1Import::ParseF3EventsV3() t = qint64(event->timestamp + e->m_start) * 1000L; switch (e->m_type) { - case EV_PRS1_TB: + case PRS1TimedBreathEvent::TYPE: TB->AddEvent(t, e->m_duration); break; - case EV_PRS1_OA: + case PRS1ObstructiveApneaEvent::TYPE: OA->AddEvent(t, e->m_duration); break; - case EV_PRS1_CA: + case PRS1ClearAirwayEvent::TYPE: CA->AddEvent(t, e->m_duration); break; - case EV_PRS1_PB: + case PRS1PeriodicBreathingEvent::TYPE: PB->AddEvent(t, e->m_duration); break; - case EV_PRS1_LL: + case PRS1LargeLeakEvent::TYPE: LL->AddEvent(t, e->m_duration); break; - case EV_PRS1_HY: + case PRS1HypopneaEvent::TYPE: HY->AddEvent(t, e->m_duration); break; - case EV_PRS1_EPAP: + case PRS1EPAPEvent::TYPE: EPAP->AddEvent(t, e->m_value); break; - case EV_PRS1_IPAP: + case PRS1IPAPEvent::TYPE: IPAP->AddEvent(t, e->m_value); break; - case EV_PRS1_LEAK: + case PRS1LeakEvent::TYPE: LEAK->AddEvent(t, e->m_value); break; - case EV_PRS1_RR: + case PRS1RespiratoryRateEvent::TYPE: RR->AddEvent(t, e->m_value); break; - case EV_PRS1_PTB: + case PRS1PatientTriggeredBreathsEvent::TYPE: PTB->AddEvent(t, e->m_value); break; - case EV_PRS1_MV: + case PRS1MinuteVentilationEvent::TYPE: MV->AddEvent(t, e->m_value); break; - case EV_PRS1_TV: + case PRS1TidalVolumeEvent::TYPE: TV->AddEvent(t, e->m_value); break; - case EV_PRS1_RERA: + case PRS1RERAEvent::TYPE: RE->AddEvent(t, e->m_value); break; - case EV_PRS1_NRI: + case PRS1NonRespondingEvent::TYPE: ZZ->AddEvent(t, e->m_value); break; - case EV_PRS1_TEST1: + case PRS1Test1Event::TYPE: TMV->AddEvent(t, e->m_value); break; - case EV_PRS1_TEST2: + case PRS1Test2Event::TYPE: FLOW->AddEvent(t, e->m_value); break; - case EV_PRS1_RAW: + case PRS1UnknownDataEvent::TYPE: { PRS1UnknownDataEvent* unk = (PRS1UnknownDataEvent*) e; int code = unk->m_code; @@ -2438,40 +2368,40 @@ bool PRS1Import::ParseF3Events() t = qint64(event->timestamp + e->m_start) * 1000L; switch (e->m_type) { - case EV_PRS1_OA: + case PRS1ObstructiveApneaEvent::TYPE: OA->AddEvent(t, e->m_duration); break; - case EV_PRS1_CA: + case PRS1ClearAirwayEvent::TYPE: CA->AddEvent(t, e->m_duration); break; - case EV_PRS1_HY: + case PRS1HypopneaEvent::TYPE: HY->AddEvent(t, e->m_duration); break; - case EV_PRS1_EPAP: + case PRS1EPAPEvent::TYPE: EPAP->AddEvent(t, e->m_value); break; - case EV_PRS1_IPAP: + case PRS1IPAPEvent::TYPE: IPAP->AddEvent(t, e->m_value); break; - case EV_PRS1_TOTLEAK: + case PRS1TotalLeakEvent::TYPE: TOTLEAK->AddEvent(t, e->m_value); break; - case EV_PRS1_LEAK: + case PRS1LeakEvent::TYPE: LEAK->AddEvent(t, e->m_value); break; - case EV_PRS1_RR: + case PRS1RespiratoryRateEvent::TYPE: RR->AddEvent(t, e->m_value); break; - case EV_PRS1_PTB: + case PRS1PatientTriggeredBreathsEvent::TYPE: PTB->AddEvent(t, e->m_value); break; - case EV_PRS1_MV: + case PRS1MinuteVentilationEvent::TYPE: MV->AddEvent(t, e->m_value); break; - case EV_PRS1_TV: + case PRS1TidalVolumeEvent::TYPE: TV->AddEvent(t, e->m_value); break; - case EV_PRS1_FLOWRATE: + case PRS1FlowRateEvent::TYPE: FLOW->AddEvent(t, e->m_value); break; default: @@ -2695,21 +2625,21 @@ bool PRS1Import::ParseF0Events() t = qint64(event->timestamp + e->m_start) * 1000L; switch (e->m_type) { - case EV_PRS1_PRESSURE: + case PRS1CPAPEvent::TYPE: if (!PRESSURE) { if (!(PRESSURE = session->AddEventList(CPAP_Pressure, EVL_Event, e->m_gain))) { return false; } } PRESSURE->AddEvent(t, e->m_value); currentPressure = e->m_value; break; - case EV_PRS1_IPAP: + case PRS1IPAPEvent::TYPE: if(!IPAP) { if (!(IPAP = session->AddEventList(CPAP_IPAP, EVL_Event, e->m_gain))) { return false; } } IPAP->AddEvent(t, e->m_value); currentPressure = e->m_value; break; - case EV_PRS1_EPAP: + case PRS1EPAPEvent::TYPE: if (!EPAP) { if (!(EPAP = session->AddEventList(CPAP_EPAP, EVL_Event, e->m_gain))) { return false; } } @@ -2719,31 +2649,31 @@ bool PRS1Import::ParseF0Events() EPAP->AddEvent(t, e->m_value); PS->AddEvent(t, currentPressure - e->m_value); break; - case EV_PRS1_FLEX: + case PRS1PressureReliefEvent::TYPE: if (!EPAP) { if (!(EPAP = session->AddEventList(CPAP_EPAP, EVL_Event, e->m_gain))) { return false; } } EPAP->AddEvent(t, e->m_value); break; - case EV_PRS1_OA: + case PRS1ObstructiveApneaEvent::TYPE: OA->AddEvent(t, e->m_duration); break; - case EV_PRS1_CA: + case PRS1ClearAirwayEvent::TYPE: CA->AddEvent(t, e->m_duration); break; - case EV_PRS1_FL: + case PRS1FlowLimitationEvent::TYPE: FL->AddEvent(t, e->m_duration); break; - case EV_PRS1_PB: + case PRS1PeriodicBreathingEvent::TYPE: PB->AddEvent(t, e->m_duration); break; - case EV_PRS1_LL: + case PRS1LargeLeakEvent::TYPE: LL->AddEvent(t, e->m_duration); break; - case EV_PRS1_HY: + case PRS1HypopneaEvent::TYPE: HY->AddEvent(t, e->m_duration); break; - case EV_PRS1_TOTLEAK: + case PRS1TotalLeakEvent::TYPE: TOTLEAK->AddEvent(t, e->m_value); leak = e->m_value; if (calcLeaks) { // Much Quicker doing this here than the recalc method. @@ -2752,22 +2682,22 @@ bool PRS1Import::ParseF0Events() LEAK->AddEvent(t, leak); } break; - case EV_PRS1_SNORE: + case PRS1SnoreEvent::TYPE: SNORE->AddEvent(t, e->m_value); if (e->m_value > 0) { VS2->AddEvent(t, e->m_value); } break; - case EV_PRS1_VS: // F0: Is this really distinct from SNORE and VS2? + case PRS1VibratorySnoreEvent::TYPE: // F0: Is this really distinct from SNORE and VS2? VS->AddEvent(t, 0); break; - case EV_PRS1_RERA: + case PRS1RERAEvent::TYPE: RE->AddEvent(t, e->m_value); break; - case EV_PRS1_PP: + case PRS1PressurePulseEvent::TYPE: PP->AddEvent(t, e->m_value); break; - case EV_PRS1_UNKNOWN: + case PRS1UnknownValueEvent::TYPE: { int code = ((PRS1UnknownValueEvent*) e)->m_code; Q_ASSERT(code < ncodes); @@ -3031,14 +2961,14 @@ bool PRS1Import::ParseCompliance() for (int i=0; i < compliance->m_parsedData.count(); i++) { PRS1ParsedEvent* e = compliance->m_parsedData.at(i); - if (e->m_type == EV_PRS1_SLICE) { + if (e->m_type == PRS1ParsedSliceEvent::TYPE) { PRS1ParsedSliceEvent* s = (PRS1ParsedSliceEvent*) e; qint64 tt = start + qint64(s->m_start) * 1000L; qint64 duration = qint64(s->m_duration) * 1000L; session->m_slices.append(SessionSlice(tt, tt + duration, s->m_status)); qDebug() << compliance->sessionid << "Added Slice" << tt << (tt+duration) << s->m_status; continue; - } else if (e->m_type != EV_PRS1_SETTING) { + } else if (e->m_type != PRS1ParsedSettingEvent::TYPE) { qWarning() << "Compliance had non-setting event:" << (int) e->m_type; continue; } @@ -3642,7 +3572,7 @@ bool PRS1Import::ImportSummary() for (int i=0; i < summary->m_parsedData.count(); i++) { PRS1ParsedEvent* e = summary->m_parsedData.at(i); - if (e->m_type != EV_PRS1_SETTING) { + if (e->m_type != PRS1ParsedSettingEvent::TYPE) { qWarning() << "Summary had non-setting event:" << (int) e->m_type; continue; } From 1892ceda85a87c212f326a623e672fc34cf6eb72 Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Tue, 28 May 2019 18:25:08 -0400 Subject: [PATCH 27/31] Clean PRS1ParsedEvents and reorder PRS1Import::Parse*Events for more consistency, no functional changes. --- oscar/SleepLib/loader_plugins/prs1_loader.cpp | 318 ++++++++---------- 1 file changed, 145 insertions(+), 173 deletions(-) diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.cpp b/oscar/SleepLib/loader_plugins/prs1_loader.cpp index b9d31bab..4dc4157d 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.cpp +++ b/oscar/SleepLib/loader_plugins/prs1_loader.cpp @@ -1209,10 +1209,7 @@ protected: class PRS1ParsedDurationEvent : public PRS1ParsedEvent { protected: - PRS1ParsedDurationEvent(PRS1ParsedEventType type, int start, int duration) : PRS1ParsedEvent(type, start) - { - m_duration = duration; - } + PRS1ParsedDurationEvent(PRS1ParsedEventType type, int start, int duration) : PRS1ParsedEvent(type, start) { m_duration = duration; } }; class PRS1ParsedValueEvent : public PRS1ParsedEvent @@ -1261,6 +1258,22 @@ public: } }; +class PRS1TidalVolumeEvent : public PRS1ParsedValueEvent +{ +public: + static const PRS1ParsedEventType TYPE = EV_PRS1_TV; + + static constexpr float GAIN = 10.0; + static const PRS1ParsedEventUnit UNIT = PRS1_UNIT_ML; + + PRS1TidalVolumeEvent(int start, int value) + : PRS1ParsedValueEvent(TYPE, start, value) + { + m_gain = GAIN; + m_unit = UNIT; + } +}; + class PRS1ParsedSettingEvent : public PRS1ParsedValueEvent { public: @@ -1291,7 +1304,9 @@ public: SliceStatus m_status; PRS1ParsedSliceEvent(int start, int duration, SliceStatus status) : PRS1ParsedDurationEvent(TYPE, start, duration), m_status(status) {} -}; +}; + + #define _PRS1_EVENT(T, E, P, ARG) \ class T : public P \ { \ @@ -1304,73 +1319,38 @@ public: \ #define PRS1_PRESSURE_EVENT(T, E) _PRS1_EVENT(T, E, PRS1PressureEvent, value) PRS1_DURATION_EVENT(PRS1TimedBreathEvent, EV_PRS1_TB); - PRS1_DURATION_EVENT(PRS1ObstructiveApneaEvent, EV_PRS1_OA); - PRS1_DURATION_EVENT(PRS1ClearAirwayEvent, EV_PRS1_CA); - PRS1_DURATION_EVENT(PRS1FlowLimitationEvent, EV_PRS1_FL); - PRS1_DURATION_EVENT(PRS1PeriodicBreathingEvent, EV_PRS1_PB); - PRS1_DURATION_EVENT(PRS1LargeLeakEvent, EV_PRS1_LL); - PRS1_DURATION_EVENT(PRS1HypopneaEvent, EV_PRS1_HY); PRS1_VALUE_EVENT(PRS1TotalLeakEvent, EV_PRS1_TOTLEAK); - PRS1_VALUE_EVENT(PRS1LeakEvent, EV_PRS1_LEAK); // TODO: do machines really report unintentional leak? PRS1_PRESSURE_EVENT(PRS1CPAPEvent, EV_PRS1_PRESSURE); - PRS1_PRESSURE_EVENT(PRS1IPAPEvent, EV_PRS1_IPAP); - PRS1_PRESSURE_EVENT(PRS1IPAPHighEvent, EV_PRS1_IPAPHIGH); - PRS1_PRESSURE_EVENT(PRS1IPAPLowEvent, EV_PRS1_IPAPLOW); - PRS1_PRESSURE_EVENT(PRS1EPAPEvent, EV_PRS1_EPAP); - PRS1_PRESSURE_EVENT(PRS1PressureReliefEvent, EV_PRS1_FLEX); // value is pressure applied during pressure relief, similar to EPAP PRS1_VALUE_EVENT(PRS1RespiratoryRateEvent, EV_PRS1_RR); - PRS1_VALUE_EVENT(PRS1PatientTriggeredBreathsEvent, EV_PRS1_PTB); - PRS1_VALUE_EVENT(PRS1MinuteVentilationEvent, EV_PRS1_MV); - -class PRS1TidalVolumeEvent : public PRS1ParsedValueEvent -{ -public: - static const PRS1ParsedEventType TYPE = EV_PRS1_TV; - - static constexpr float GAIN = 10.0; - static const PRS1ParsedEventUnit UNIT = PRS1_UNIT_ML; - - PRS1TidalVolumeEvent(int start, int value) - : PRS1ParsedValueEvent(TYPE, start, value) - { - m_gain = GAIN; - m_unit = UNIT; - } -}; - PRS1_VALUE_EVENT(PRS1SnoreEvent, EV_PRS1_SNORE); - PRS1_VALUE_EVENT(PRS1VibratorySnoreEvent, EV_PRS1_VS); - PRS1_VALUE_EVENT(PRS1PressurePulseEvent, EV_PRS1_PP); - PRS1_VALUE_EVENT(PRS1RERAEvent, EV_PRS1_RERA); // TODO: should this really be a duration event? - PRS1_VALUE_EVENT(PRS1NonRespondingEvent, EV_PRS1_NRI); // TODO: is this a single event or an index/hour? - PRS1_VALUE_EVENT(PRS1FlowRateEvent, EV_PRS1_FLOWRATE); // TODO: is this a single event or an index/hour? - PRS1_VALUE_EVENT(PRS1Test1Event, EV_PRS1_TEST1); // TODO: replace test1/2 event with unknownvalue events and appropriate mapping - PRS1_VALUE_EVENT(PRS1Test2Event, EV_PRS1_TEST2); +//******************************************************************************************** + + void PRS1DataChunk::AddEvent(PRS1ParsedEvent* const event) { m_parsedData.push_back(event); @@ -1381,31 +1361,29 @@ bool PRS1Import::ParseF5EventsFV3() // Required channels EventList *OA = session->AddEventList(CPAP_Obstructive, EVL_Event); EventList *HY = session->AddEventList(CPAP_Hypopnea, EVL_Event); + EventList *CA = session->AddEventList(CPAP_ClearAirway, EVL_Event); - EventList *PB = session->AddEventList(CPAP_PB, EVL_Event); + EventList *LL = session->AddEventList(CPAP_LargeLeak, EVL_Event); EventList *TOTLEAK = session->AddEventList(CPAP_LeakTotal, EVL_Event); EventList *LEAK = session->AddEventList(CPAP_Leak, EVL_Event); - EventList *LL = session->AddEventList(CPAP_LargeLeak, EVL_Event); - EventList *SNORE = session->AddEventList(CPAP_Snore, EVL_Event); + EventList *RR = session->AddEventList(CPAP_RespRate, EVL_Event); + EventList *TV = session->AddEventList(CPAP_TidalVolume, EVL_Event, 10.0F); + EventList *MV = session->AddEventList(CPAP_MinuteVent, EVL_Event); + EventList *PB = session->AddEventList(CPAP_PB, EVL_Event); + EventList *PTB = session->AddEventList(CPAP_PTB, EVL_Event); + EventList *TB = session->AddEventList(PRS1_TimedBreath, EVL_Event); EventList *IPAP = session->AddEventList(CPAP_IPAP, EVL_Event, 0.1F); EventList *EPAP = session->AddEventList(CPAP_EPAP, EVL_Event, 0.1F); EventList *PS = session->AddEventList(CPAP_PS, EVL_Event, 0.1F); EventList *IPAPLo = session->AddEventList(CPAP_IPAPLo, EVL_Event, 0.1F); EventList *IPAPHi = session->AddEventList(CPAP_IPAPHi, EVL_Event, 0.1F); - EventList *RR = session->AddEventList(CPAP_RespRate, EVL_Event); - EventList *PTB = session->AddEventList(CPAP_PTB, EVL_Event); - EventList *TB = session->AddEventList(PRS1_TimedBreath, EVL_Event); - - EventList *MV = session->AddEventList(CPAP_MinuteVent, EVL_Event); - EventList *TV = session->AddEventList(CPAP_TidalVolume, EVL_Event, 10.0F); - - - EventList *CA = session->AddEventList(CPAP_ClearAirway, EVL_Event); EventList *FL = session->AddEventList(CPAP_FlowLimit, EVL_Event); + EventList *SNORE = session->AddEventList(CPAP_Snore, EVL_Event); EventList *VS = session->AddEventList(CPAP_VSnore, EVL_Event); + // Unintentional leak calculation, see zMaskProfile:calcLeak in calcs.cpp for explanation - EventDataType currentPressure=0, leak, ps=0; + EventDataType currentPressure=0, leak; bool calcLeaks = p_profile->cpap->calculateUnintentionalLeaks(); EventDataType lpm4 = p_profile->cpap->custom4cmH2OLeaks(); @@ -1415,7 +1393,6 @@ bool PRS1Import::ParseF5EventsFV3() EventDataType ppm = lpm / 16.0; - //qint64 start=timestamp; qint64 t = qint64(event->timestamp) * 1000L; session->updateFirst(t); @@ -1430,6 +1407,20 @@ bool PRS1Import::ParseF5EventsFV3() t = qint64(event->timestamp + e->m_start) * 1000L; switch (e->m_type) { + case PRS1IPAPEvent::TYPE: + IPAP->AddEvent(t, e->m_value); + currentPressure = e->m_value; + break; + case PRS1IPAPLowEvent::TYPE: + IPAPLo->AddEvent(t, e->m_value); + break; + case PRS1IPAPHighEvent::TYPE: + IPAPHi->AddEvent(t, e->m_value); + break; + case PRS1EPAPEvent::TYPE: + EPAP->AddEvent(t, e->m_value); + PS->AddEvent(t, currentPressure - e->m_value); // Pressure Support + break; case PRS1TimedBreathEvent::TYPE: TB->AddEvent(t, e->m_duration); break; @@ -1439,6 +1430,9 @@ bool PRS1Import::ParseF5EventsFV3() case PRS1ClearAirwayEvent::TYPE: CA->AddEvent(t, e->m_duration); break; + case PRS1HypopneaEvent::TYPE: + HY->AddEvent(t, e->m_duration); + break; case PRS1FlowLimitationEvent::TYPE: FL->AddEvent(t, e->m_duration); break; @@ -1448,13 +1442,6 @@ bool PRS1Import::ParseF5EventsFV3() case PRS1LargeLeakEvent::TYPE: LL->AddEvent(t, e->m_duration); break; - case PRS1HypopneaEvent::TYPE: - HY->AddEvent(t, e->m_duration); - break; - case PRS1IPAPEvent::TYPE: - IPAP->AddEvent(t, e->m_value); - currentPressure = e->m_value; - break; case PRS1TotalLeakEvent::TYPE: TOTLEAK->AddEvent(t, e->m_value); leak = e->m_value; @@ -1464,11 +1451,11 @@ bool PRS1Import::ParseF5EventsFV3() LEAK->AddEvent(t, leak); } break; - case PRS1IPAPLowEvent::TYPE: - IPAPLo->AddEvent(t, e->m_value); - break; - case PRS1IPAPHighEvent::TYPE: - IPAPHi->AddEvent(t, e->m_value); + case PRS1SnoreEvent::TYPE: + SNORE->AddEvent(t, e->m_value); + if (e->m_value > 0) { + VS->AddEvent(t, 0); //data2); // VSnore + } break; case PRS1RespiratoryRateEvent::TYPE: RR->AddEvent(t, e->m_value); @@ -1482,23 +1469,13 @@ bool PRS1Import::ParseF5EventsFV3() case PRS1TidalVolumeEvent::TYPE: TV->AddEvent(t, e->m_value); break; - case PRS1SnoreEvent::TYPE: - SNORE->AddEvent(t, e->m_value); - if (e->m_value > 0) { - VS->AddEvent(t, 0); //data2); // VSnore - } - break; - case PRS1EPAPEvent::TYPE: - EPAP->AddEvent(t, e->m_value); - ps = currentPressure - e->m_value; - PS->AddEvent(t, ps); // Pressure Support - break; default: qWarning() << "Unknown PRS1 event type" << (int) e->m_type; break; } } + //t = qint64(event->timestamp + event->duration) * 1000L; session->updateLast(t); session->m_cnt.clear(); session->m_cph.clear(); @@ -1661,29 +1638,25 @@ bool PRS1Import::ParseF5Events() // Required channels EventList *OA = session->AddEventList(CPAP_Obstructive, EVL_Event); EventList *HY = session->AddEventList(CPAP_Hypopnea, EVL_Event); + EventList *CA = session->AddEventList(CPAP_ClearAirway, EVL_Event); - EventList *PB = session->AddEventList(CPAP_PB, EVL_Event); + EventList *LL = session->AddEventList(CPAP_LargeLeak, EVL_Event); EventList *TOTLEAK = session->AddEventList(CPAP_LeakTotal, EVL_Event); EventList *LEAK = session->AddEventList(CPAP_Leak, EVL_Event); - EventList *LL = session->AddEventList(CPAP_LargeLeak, EVL_Event); - EventList *SNORE = session->AddEventList(CPAP_Snore, EVL_Event); + EventList *RR = session->AddEventList(CPAP_RespRate, EVL_Event); + EventList *TV = session->AddEventList(CPAP_TidalVolume, EVL_Event, 10.0F); + EventList *MV = session->AddEventList(CPAP_MinuteVent, EVL_Event); + EventList *PB = session->AddEventList(CPAP_PB, EVL_Event); + EventList *PTB = session->AddEventList(CPAP_PTB, EVL_Event); + EventList *TB = session->AddEventList(PRS1_TimedBreath, EVL_Event); EventList *IPAP = session->AddEventList(CPAP_IPAP, EVL_Event, 0.1F); EventList *EPAP = session->AddEventList(CPAP_EPAP, EVL_Event, 0.1F); EventList *PS = session->AddEventList(CPAP_PS, EVL_Event, 0.1F); EventList *IPAPLo = session->AddEventList(CPAP_IPAPLo, EVL_Event, 0.1F); EventList *IPAPHi = session->AddEventList(CPAP_IPAPHi, EVL_Event, 0.1F); - EventList *RR = session->AddEventList(CPAP_RespRate, EVL_Event); - EventList *PTB = session->AddEventList(CPAP_PTB, EVL_Event); - EventList *TB = session->AddEventList(PRS1_TimedBreath, EVL_Event); - - EventList *MV = session->AddEventList(CPAP_MinuteVent, EVL_Event); - EventList *TV = session->AddEventList(CPAP_TidalVolume, EVL_Event, 10.0F); - - - EventList *CA = session->AddEventList(CPAP_ClearAirway, EVL_Event); EventList *FL = session->AddEventList(CPAP_FlowLimit, EVL_Event); + EventList *SNORE = session->AddEventList(CPAP_Snore, EVL_Event); EventList *VS = session->AddEventList(CPAP_VSnore, EVL_Event); - // EventList *VS2 = session->AddEventList(CPAP_VSnore2, EVL_Event); // On-demand channels ChannelID Codes[] = { @@ -1700,8 +1673,9 @@ bool PRS1Import::ParseF5Events() //EventList * PRESSURE=nullptr; //EventList * PP=nullptr; + // Unintentional leak calculation, see zMaskProfile:calcLeak in calcs.cpp for explanation - EventDataType currentPressure=0, leak, ps=0; //, p; + EventDataType currentPressure=0, leak; bool calcLeaks = p_profile->cpap->calculateUnintentionalLeaks(); EventDataType lpm4 = p_profile->cpap->custom4cmH2OLeaks(); @@ -1711,7 +1685,6 @@ bool PRS1Import::ParseF5Events() EventDataType ppm = lpm / 16.0; - //qint64 start=timestamp; qint64 t = qint64(event->timestamp) * 1000L; session->updateFirst(t); @@ -1723,27 +1696,6 @@ bool PRS1Import::ParseF5Events() t = qint64(event->timestamp + e->m_start) * 1000L; switch (e->m_type) { - case PRS1TimedBreathEvent::TYPE: - TB->AddEvent(t, e->m_duration); - break; - case PRS1ObstructiveApneaEvent::TYPE: - OA->AddEvent(t, e->m_duration); - break; - case PRS1ClearAirwayEvent::TYPE: - CA->AddEvent(t, e->m_duration); - break; - case PRS1FlowLimitationEvent::TYPE: - FL->AddEvent(t, e->m_duration); - break; - case PRS1PeriodicBreathingEvent::TYPE: - PB->AddEvent(t, e->m_duration); - break; - case PRS1LargeLeakEvent::TYPE: - LL->AddEvent(t, e->m_duration); - break; - case PRS1HypopneaEvent::TYPE: - HY->AddEvent(t, e->m_duration); - break; case PRS1IPAPEvent::TYPE: IPAP->AddEvent(t, e->m_value); currentPressure = e->m_value; @@ -1754,6 +1706,31 @@ bool PRS1Import::ParseF5Events() case PRS1IPAPHighEvent::TYPE: IPAPHi->AddEvent(t, e->m_value); break; + case PRS1EPAPEvent::TYPE: + EPAP->AddEvent(t, e->m_value); + PS->AddEvent(t, currentPressure - e->m_value); // Pressure Support + break; + case PRS1TimedBreathEvent::TYPE: + TB->AddEvent(t, e->m_duration); + break; + case PRS1ObstructiveApneaEvent::TYPE: + OA->AddEvent(t, e->m_duration); + break; + case PRS1ClearAirwayEvent::TYPE: + CA->AddEvent(t, e->m_duration); + break; + case PRS1HypopneaEvent::TYPE: + HY->AddEvent(t, e->m_duration); + break; + case PRS1FlowLimitationEvent::TYPE: + FL->AddEvent(t, e->m_duration); + break; + case PRS1PeriodicBreathingEvent::TYPE: + PB->AddEvent(t, e->m_duration); + break; + case PRS1LargeLeakEvent::TYPE: + LL->AddEvent(t, e->m_duration); + break; case PRS1TotalLeakEvent::TYPE: TOTLEAK->AddEvent(t, e->m_value); leak = e->m_value; @@ -1766,6 +1743,12 @@ bool PRS1Import::ParseF5Events() case PRS1LeakEvent::TYPE: LEAK->AddEvent(t, e->m_value); break; + case PRS1SnoreEvent::TYPE: + SNORE->AddEvent(t, e->m_value); + if (e->m_value > 0) { + VS->AddEvent(t, 0); //data2); // VSnore + } + break; case PRS1RespiratoryRateEvent::TYPE: RR->AddEvent(t, e->m_value); break; @@ -1778,17 +1761,6 @@ bool PRS1Import::ParseF5Events() case PRS1TidalVolumeEvent::TYPE: TV->AddEvent(t, e->m_value); break; - case PRS1SnoreEvent::TYPE: - SNORE->AddEvent(t, e->m_value); - if (e->m_value > 0) { - VS->AddEvent(t, 0); //data2); // VSnore - } - break; - case PRS1EPAPEvent::TYPE: - EPAP->AddEvent(t, e->m_value); - ps = currentPressure - e->m_value; - PS->AddEvent(t, ps); // Pressure Support - break; case PRS1UnknownValueEvent::TYPE: { int code = ((PRS1UnknownValueEvent*) e)->m_code; @@ -2111,28 +2083,26 @@ bool PRS1DataChunk::ParseEventsF5V012(void) bool PRS1Import::ParseF3EventsV3() { // Required channels - EventList *TB = session->AddEventList(PRS1_TimedBreath, EVL_Event); EventList *OA = session->AddEventList(CPAP_Obstructive, EVL_Event); EventList *HY = session->AddEventList(CPAP_Hypopnea, EVL_Event); - EventList *ZZ = session->AddEventList(CPAP_NRI, EVL_Event); - //EventList *Z2 = session->AddEventList(CPAP_ExP, EVL_Event); EventList *CA = session->AddEventList(CPAP_ClearAirway, EVL_Event); - EventList *PB = session->AddEventList(CPAP_PB, EVL_Event); + EventList *LL = session->AddEventList(CPAP_LargeLeak, EVL_Event); - EventList *RE = session->AddEventList(CPAP_RERA, EVL_Event); EventList *LEAK = session->AddEventList(CPAP_Leak, EVL_Event); -// EventList *ULK = session->AddEventList(CPAP_Leak, EVL_Event); - - EventList *PTB = session->AddEventList(CPAP_PTB, EVL_Event); EventList *RR = session->AddEventList(CPAP_RespRate, EVL_Event); - EventList *TV = session->AddEventList(CPAP_TidalVolume, EVL_Event, 10.0f); - + EventList *TV = session->AddEventList(CPAP_TidalVolume, EVL_Event, 10.0F); EventList *MV = session->AddEventList(CPAP_MinuteVent, EVL_Event); + EventList *PB = session->AddEventList(CPAP_PB, EVL_Event); + EventList *PTB = session->AddEventList(CPAP_PTB, EVL_Event); + EventList *TB = session->AddEventList(PRS1_TimedBreath, EVL_Event); + EventList *IPAP = session->AddEventList(CPAP_IPAP, EVL_Event, 0.1F); + EventList *EPAP = session->AddEventList(CPAP_EPAP, EVL_Event, 0.1F); + EventList *RE = session->AddEventList(CPAP_RERA, EVL_Event); + EventList *ZZ = session->AddEventList(CPAP_NRI, EVL_Event); EventList *TMV = session->AddEventList(CPAP_Test1, EVL_Event); - EventList *EPAP = session->AddEventList(CPAP_EPAP, EVL_Event, 0.1f); - EventList *IPAP = session->AddEventList(CPAP_IPAP, EVL_Event, 0.1f); EventList *FLOW = session->AddEventList(CPAP_Test2, EVL_Event); + qint64 t; // missing session->updateFirst(t)? @@ -2144,6 +2114,12 @@ bool PRS1Import::ParseF3EventsV3() t = qint64(event->timestamp + e->m_start) * 1000L; switch (e->m_type) { + case PRS1IPAPEvent::TYPE: + IPAP->AddEvent(t, e->m_value); + break; + case PRS1EPAPEvent::TYPE: + EPAP->AddEvent(t, e->m_value); + break; case PRS1TimedBreathEvent::TYPE: TB->AddEvent(t, e->m_duration); break; @@ -2153,21 +2129,15 @@ bool PRS1Import::ParseF3EventsV3() case PRS1ClearAirwayEvent::TYPE: CA->AddEvent(t, e->m_duration); break; + case PRS1HypopneaEvent::TYPE: + HY->AddEvent(t, e->m_duration); + break; case PRS1PeriodicBreathingEvent::TYPE: PB->AddEvent(t, e->m_duration); break; case PRS1LargeLeakEvent::TYPE: LL->AddEvent(t, e->m_duration); break; - case PRS1HypopneaEvent::TYPE: - HY->AddEvent(t, e->m_duration); - break; - case PRS1EPAPEvent::TYPE: - EPAP->AddEvent(t, e->m_value); - break; - case PRS1IPAPEvent::TYPE: - IPAP->AddEvent(t, e->m_value); - break; case PRS1LeakEvent::TYPE: LEAK->AddEvent(t, e->m_value); break; @@ -2346,17 +2316,18 @@ bool PRS1Import::ParseF3Events() EventList *OA = session->AddEventList(CPAP_Obstructive, EVL_Event); EventList *HY = session->AddEventList(CPAP_Hypopnea, EVL_Event); EventList *CA = session->AddEventList(CPAP_ClearAirway, EVL_Event); + EventList *TOTLEAK = session->AddEventList(CPAP_LeakTotal, EVL_Event); EventList *LEAK = session->AddEventList(CPAP_Leak, EVL_Event); - EventList *MV = session->AddEventList(CPAP_MinuteVent, EVL_Event); - //EventList *TMV = session->AddEventList(CPAP_TgMV, EVL_Event); - EventList *TV = session->AddEventList(CPAP_TidalVolume, EVL_Event,10.0f); EventList *RR = session->AddEventList(CPAP_RespRate, EVL_Event); + EventList *TV = session->AddEventList(CPAP_TidalVolume, EVL_Event, 10.0F); + EventList *MV = session->AddEventList(CPAP_MinuteVent, EVL_Event); EventList *PTB = session->AddEventList(CPAP_PTB, EVL_Event); - EventList *EPAP = session->AddEventList(CPAP_EPAP, EVL_Event,0.1f); - EventList *IPAP = session->AddEventList(CPAP_IPAP, EVL_Event,0.1f); + EventList *IPAP = session->AddEventList(CPAP_IPAP, EVL_Event, 0.1F); + EventList *EPAP = session->AddEventList(CPAP_EPAP, EVL_Event, 0.1F); EventList *FLOW = session->AddEventList(CPAP_FlowRate, EVL_Event); + qint64 t = qint64(event->timestamp) * 1000L; session->updateFirst(t); @@ -2368,6 +2339,12 @@ bool PRS1Import::ParseF3Events() t = qint64(event->timestamp + e->m_start) * 1000L; switch (e->m_type) { + case PRS1IPAPEvent::TYPE: + IPAP->AddEvent(t, e->m_value); + break; + case PRS1EPAPEvent::TYPE: + EPAP->AddEvent(t, e->m_value); + break; case PRS1ObstructiveApneaEvent::TYPE: OA->AddEvent(t, e->m_duration); break; @@ -2377,12 +2354,6 @@ bool PRS1Import::ParseF3Events() case PRS1HypopneaEvent::TYPE: HY->AddEvent(t, e->m_duration); break; - case PRS1EPAPEvent::TYPE: - EPAP->AddEvent(t, e->m_value); - break; - case PRS1IPAPEvent::TYPE: - IPAP->AddEvent(t, e->m_value); - break; case PRS1TotalLeakEvent::TYPE: TOTLEAK->AddEvent(t, e->m_value); break; @@ -2409,7 +2380,7 @@ bool PRS1Import::ParseF3Events() break; } } - + if (!ok) { return false; } @@ -2572,18 +2543,18 @@ bool PRS1Import::ParseF0Events() // Required channels EventList *OA = session->AddEventList(CPAP_Obstructive, EVL_Event); EventList *HY = session->AddEventList(CPAP_Hypopnea, EVL_Event); - EventList *PB = session->AddEventList(CPAP_PB, EVL_Event); + EventList *CA = session->AddEventList(CPAP_ClearAirway, EVL_Event); + EventList *TOTLEAK = session->AddEventList(CPAP_LeakTotal, EVL_Event); EventList *LEAK = session->AddEventList(CPAP_Leak, EVL_Event); - EventList *SNORE = session->AddEventList(CPAP_Snore, EVL_Event); - - EventList *PP = session->AddEventList(CPAP_PressurePulse, EVL_Event); - EventList *RE = session->AddEventList(CPAP_RERA, EVL_Event); - EventList *CA = session->AddEventList(CPAP_ClearAirway, EVL_Event); + EventList *PB = session->AddEventList(CPAP_PB, EVL_Event); EventList *FL = session->AddEventList(CPAP_FlowLimit, EVL_Event); + EventList *SNORE = session->AddEventList(CPAP_Snore, EVL_Event); EventList *VS = session->AddEventList(CPAP_VSnore, EVL_Event); EventList *VS2 = session->AddEventList(CPAP_VSnore2, EVL_Event); - //EventList *T1 = session->AddEventList(CPAP_Test1, EVL_Event, 0.1); + EventList *PP = session->AddEventList(CPAP_PressurePulse, EVL_Event); + EventList *RE = session->AddEventList(CPAP_RERA, EVL_Event); + // On-demand channels ChannelID Codes[] = { @@ -2602,8 +2573,9 @@ bool PRS1Import::ParseF0Events() EventList *IPAP = nullptr; EventList *PS = nullptr; + // Unintentional leak calculation, see zMaskProfile:calcLeak in calcs.cpp for explanation - EventDataType currentPressure=0, leak; //, p; + EventDataType currentPressure=0, leak; bool calcLeaks = p_profile->cpap->calculateUnintentionalLeaks(); EventDataType lpm4 = p_profile->cpap->custom4cmH2OLeaks(); @@ -2647,7 +2619,7 @@ bool PRS1Import::ParseF0Events() if (!(PS = session->AddEventList(CPAP_PS, EVL_Event, e->m_gain))) { return false; } } EPAP->AddEvent(t, e->m_value); - PS->AddEvent(t, currentPressure - e->m_value); + PS->AddEvent(t, currentPressure - e->m_value); // Pressure Support break; case PRS1PressureReliefEvent::TYPE: if (!EPAP) { @@ -2661,6 +2633,9 @@ bool PRS1Import::ParseF0Events() case PRS1ClearAirwayEvent::TYPE: CA->AddEvent(t, e->m_duration); break; + case PRS1HypopneaEvent::TYPE: + HY->AddEvent(t, e->m_duration); + break; case PRS1FlowLimitationEvent::TYPE: FL->AddEvent(t, e->m_duration); break; @@ -2670,9 +2645,6 @@ bool PRS1Import::ParseF0Events() case PRS1LargeLeakEvent::TYPE: LL->AddEvent(t, e->m_duration); break; - case PRS1HypopneaEvent::TYPE: - HY->AddEvent(t, e->m_duration); - break; case PRS1TotalLeakEvent::TYPE: TOTLEAK->AddEvent(t, e->m_value); leak = e->m_value; @@ -2720,10 +2692,10 @@ bool PRS1Import::ParseF0Events() } t = qint64(event->timestamp + event->duration) * 1000L; - session->updateLast(t); session->m_cnt.clear(); session->m_cph.clear(); + session->m_lastchan.clear(); session->m_firstchan.clear(); session->m_valuesummary[CPAP_Pressure].clear(); From d0150d18ef188c8dd740794d1c2b4789737374b6 Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Tue, 28 May 2019 21:09:17 -0400 Subject: [PATCH 28/31] Limit raw data in PRS1/Session YAML to 100 bytes per entry. This changes the reference output, compared to prior versions, but it runs much faster and doesn't affect user-observable behavior. --- oscar/SleepLib/loader_plugins/prs1_loader.cpp | 6 ++++-- oscar/tests/prs1tests.cpp | 9 ++++++--- oscar/tests/sessiontests.cpp | 18 +++++++++++------- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.cpp b/oscar/SleepLib/loader_plugins/prs1_loader.cpp index 4dc4157d..c84ac8a9 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.cpp +++ b/oscar/SleepLib/loader_plugins/prs1_loader.cpp @@ -1273,6 +1273,7 @@ public: m_unit = UNIT; } }; +const PRS1ParsedEventType PRS1TidalVolumeEvent::TYPE; class PRS1ParsedSettingEvent : public PRS1ParsedValueEvent { @@ -1313,7 +1314,8 @@ class T : public P \ public: \ static const PRS1ParsedEventType TYPE = E; \ T(int start, int ARG) : P(TYPE, start, ARG) {} \ -} +}; \ +const PRS1ParsedEventType T::TYPE #define PRS1_DURATION_EVENT(T, E) _PRS1_EVENT(T, E, PRS1ParsedDurationEvent, duration) #define PRS1_VALUE_EVENT(T, E) _PRS1_EVENT(T, E, PRS1ParsedValueEvent, value) #define PRS1_PRESSURE_EVENT(T, E) _PRS1_EVENT(T, E, PRS1PressureEvent, value) @@ -1345,7 +1347,7 @@ PRS1_VALUE_EVENT(PRS1PressurePulseEvent, EV_PRS1_PP); PRS1_VALUE_EVENT(PRS1RERAEvent, EV_PRS1_RERA); // TODO: should this really be a duration event? PRS1_VALUE_EVENT(PRS1NonRespondingEvent, EV_PRS1_NRI); // TODO: is this a single event or an index/hour? PRS1_VALUE_EVENT(PRS1FlowRateEvent, EV_PRS1_FLOWRATE); // TODO: is this a single event or an index/hour? -PRS1_VALUE_EVENT(PRS1Test1Event, EV_PRS1_TEST1); // TODO: replace test1/2 event with unknownvalue events and appropriate mapping +PRS1_VALUE_EVENT(PRS1Test1Event, EV_PRS1_TEST1); PRS1_VALUE_EVENT(PRS1Test2Event, EV_PRS1_TEST2); //******************************************************************************************** diff --git a/oscar/tests/prs1tests.cpp b/oscar/tests/prs1tests.cpp index 49d4e633..f4ca6b0c 100644 --- a/oscar/tests/prs1tests.cpp +++ b/oscar/tests/prs1tests.cpp @@ -125,12 +125,15 @@ static QString ts(qint64 msecs) return QDateTime::fromMSecsSinceEpoch(msecs).toString(Qt::ISODate); } -static QString byteList(QByteArray data) +static QString byteList(QByteArray data, int limit=-1) { + int count = data.size(); + if (limit == -1 || limit > count) limit = count; QStringList l; - for (int i = 0; i < data.size(); i++) { + for (int i = 0; i < limit; i++) { l.push_back(QString( "%1" ).arg((int) data[i] & 0xFF, 2, 16, QChar('0') ).toUpper()); } + if (limit < count) l.push_back("..."); QString s = l.join(""); return s; } @@ -180,7 +183,7 @@ void ChunkToYaml(QFile & file, PRS1DataChunk* chunk) } // data - out << " data: " << byteList(chunk->m_data) << endl; + out << " data: " << byteList(chunk->m_data, 100) << endl; // data CRC out << " crc: " << hex << chunk->storedCrc << endl; diff --git a/oscar/tests/sessiontests.cpp b/oscar/tests/sessiontests.cpp index 421e3a29..1701f25d 100644 --- a/oscar/tests/sessiontests.cpp +++ b/oscar/tests/sessiontests.cpp @@ -133,22 +133,26 @@ static QString eventChannel(ChannelID i) return s; } -static QString intList(EventStoreType* data, int count) +static QString intList(EventStoreType* data, int count, int limit=-1) { + if (limit == -1 || limit > count) limit = count; QStringList l; - for (int i = 0; i < count; i++) { + for (int i = 0; i < limit; i++) { l.push_back(QString::number(data[i])); } + if (limit < count) l.push_back("..."); QString s = "[ " + l.join(",") + " ]"; return s; } -static QString intList(quint32* data, int count) +static QString intList(quint32* data, int count, int limit=-1) { + if (limit == -1 || limit > count) limit = count; QStringList l; - for (int i = 0; i < count; i++) { + for (int i = 0; i < limit; i++) { l.push_back(QString::number(data[i] / 1000)); } + if (limit < count) l.push_back("..."); QString s = "[ " + l.join(",") + " ]"; return s; } @@ -216,15 +220,15 @@ void SessionToYaml(QString filepath, Session* session) out << " data:" << endl; out << " min: " << e.Min() << endl; out << " max: " << e.Max() << endl; - out << " raw: " << intList((EventStoreType*) e.m_data.data(), e.count()) << endl; + out << " raw: " << intList((EventStoreType*) e.m_data.data(), e.count(), 100) << endl; if (e.type() != EVL_Waveform) { - out << " delta: " << intList((quint32*) e.m_time.data(), e.count()) << endl; + out << " delta: " << intList((quint32*) e.m_time.data(), e.count(), 100) << endl; } if (e.hasSecondField()) { out << " data2:" << endl; out << " min: " << e.min2() << endl; out << " max: " << e.max2() << endl; - out << " raw: " << intList((EventStoreType*) e.m_data2.data(), e.count()) << endl; + out << " raw: " << intList((EventStoreType*) e.m_data2.data(), e.count(), 100) << endl; } } } From ea008273ab43689f9633c615a049d15c499252f4 Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Wed, 29 May 2019 11:20:20 -0400 Subject: [PATCH 29/31] Add PRS1DataChunk::ParseEvents for testing and future unification. --- oscar/SleepLib/loader_plugins/prs1_loader.cpp | 46 ++++++++++++++++--- oscar/SleepLib/loader_plugins/prs1_loader.h | 3 ++ 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.cpp b/oscar/SleepLib/loader_plugins/prs1_loader.cpp index c84ac8a9..2c803428 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.cpp +++ b/oscar/SleepLib/loader_plugins/prs1_loader.cpp @@ -1145,6 +1145,7 @@ enum PRS1ParsedEventUnit PRS1_UNIT_NONE, PRS1_UNIT_CMH2O, PRS1_UNIT_ML, + PRS1_UNIT_S, }; enum PRS1ParsedSettingType @@ -1207,10 +1208,13 @@ protected: }; class PRS1ParsedDurationEvent : public PRS1ParsedEvent -{ +{ protected: + static const PRS1ParsedEventUnit UNIT = PRS1_UNIT_S; + PRS1ParsedDurationEvent(PRS1ParsedEventType type, int start, int duration) : PRS1ParsedEvent(type, start) { m_duration = duration; } }; +const PRS1ParsedEventUnit PRS1ParsedDurationEvent::UNIT; class PRS1ParsedValueEvent : public PRS1ParsedEvent { @@ -1399,7 +1403,7 @@ bool PRS1Import::ParseF5EventsFV3() session->updateFirst(t); bool ok; - ok = event->ParseEventsF5V3(); + ok = event->ParseEvents(MODE_UNKNOWN); if (!ok) { return false; } @@ -1691,7 +1695,7 @@ bool PRS1Import::ParseF5Events() session->updateFirst(t); bool ok; - ok = event->ParseEventsF5V012(); + ok = event->ParseEvents(MODE_UNKNOWN); for (int i=0; i < event->m_parsedData.count(); i++) { PRS1ParsedEvent* e = event->m_parsedData.at(i); @@ -2109,7 +2113,7 @@ bool PRS1Import::ParseF3EventsV3() // missing session->updateFirst(t)? bool ok; - ok = event->ParseEventsF3V6(); + ok = event->ParseEvents(MODE_UNKNOWN); for (int i=0; i < event->m_parsedData.count(); i++) { PRS1ParsedEvent* e = event->m_parsedData.at(i); @@ -2334,7 +2338,7 @@ bool PRS1Import::ParseF3Events() session->updateFirst(t); bool ok; - ok = event->ParseEventsF3V3(); + ok = event->ParseEvents(MODE_UNKNOWN); for (int i=0; i < event->m_parsedData.count(); i++) { PRS1ParsedEvent* e = event->m_parsedData.at(i); @@ -2592,7 +2596,7 @@ bool PRS1Import::ParseF0Events() session->updateFirst(t); bool ok; - ok = event->ParseEventsF0(mode); + ok = event->ParseEvents(mode); for (int i=0; i < event->m_parsedData.count(); i++) { PRS1ParsedEvent* e = event->m_parsedData.at(i); @@ -3763,6 +3767,36 @@ bool PRS1DataChunk::ParseSummary() } +// TODO: Eventually PRS1Import::ImportEvents will call this directly, once the PRS1Import::ParseF*Events have been merged. +bool PRS1DataChunk::ParseEvents(CPAPMode mode) +{ + bool ok = false; + switch (this->family) { + case 0: + ok = this->ParseEventsF0(mode); + break; + case 3: + if (this->familyVersion == 6) { + ok = this->ParseEventsF3V6(); + } else if (this->familyVersion == 3) { + ok = this->ParseEventsF3V3(); + } + break; + case 5: + if (this->familyVersion == 3) { + ok = this->ParseEventsF5V3(); + } else if (this->familyVersion < 3) { + ok = this->ParseEventsF5V012(); + } + break; + default: + qDebug() << "Unknown PRS1 family" << this->family << "familyVersion" << this->familyVersion; + } + return ok; +} + + +// TODO: Eventually this will be renamed PRS1Import::ImportEvents, once PRS1Import::ParseF*Events have been merged and incorporated. bool PRS1Import::ParseEvents() { bool res = false; diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.h b/oscar/SleepLib/loader_plugins/prs1_loader.h index 1c8ce865..bd0a97e8 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.h +++ b/oscar/SleepLib/loader_plugins/prs1_loader.h @@ -158,6 +158,9 @@ public: //! \brief Parse an humidifier setting byte from a .000 or .001 containing compliance/summary data void ParseHumidifierSetting(int humid, bool supportsHeatedTubing=true); + //! \brief Figures out which Event Parser to call, based on machine family/version and calls it. + bool ParseEvents(CPAPMode mode); + //! \brief Parse a single data chunk from a .002 file containing event data for a family 0 CPAP/APAP machine bool ParseEventsF0(CPAPMode mode); From 135ac25c1e21f943a05922fea971eac5886c070a Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Wed, 29 May 2019 12:11:53 -0400 Subject: [PATCH 30/31] Add YAML output for PRS1DataChunks. Also move a data check from PRS1Import::ImportSummary to PRS1DataChunk::ParseSummary to prevent crashing in regression tests. --- oscar/SleepLib/loader_plugins/prs1_loader.cpp | 220 +++++++++++++++++- oscar/SleepLib/loader_plugins/prs1_loader.h | 7 + oscar/tests/prs1tests.cpp | 46 +++- 3 files changed, 261 insertions(+), 12 deletions(-) diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.cpp b/oscar/SleepLib/loader_plugins/prs1_loader.cpp index 2c803428..ea5300ed 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.cpp +++ b/oscar/SleepLib/loader_plugins/prs1_loader.cpp @@ -1180,6 +1180,15 @@ enum PRS1ParsedSettingType PRS1_SETTING_SHOW_AHI, }; + +#if UNITTEST_MODE +static QString timeStr(int t); +static QString byteList(QByteArray data, int limit=-1); +static QString hex(int i); +static QString parsedSettingTypeName(PRS1ParsedSettingType t); +#endif + + class PRS1ParsedEvent { public: @@ -1196,19 +1205,31 @@ public: static const PRS1ParsedEventType TYPE = EV_PRS1_UNKNOWN; static constexpr float GAIN = 1.0; static const PRS1ParsedEventUnit UNIT = PRS1_UNIT_NONE; + + virtual QMap contents(void) = 0; protected: PRS1ParsedEvent(PRS1ParsedEventType type, int start) : m_type(type), m_start(start), m_duration(0), m_value(0), m_offset(0.0), m_gain(GAIN), m_unit(UNIT) { } - ~PRS1ParsedEvent() + virtual ~PRS1ParsedEvent() { } }; + class PRS1ParsedDurationEvent : public PRS1ParsedEvent { +public: + virtual QMap contents(void) + { + QMap out; + out["start"] = timeStr(m_start); + out["duration"] = timeStr(m_duration); + return out; + } + protected: static const PRS1ParsedEventUnit UNIT = PRS1_UNIT_S; @@ -1216,22 +1237,51 @@ protected: }; const PRS1ParsedEventUnit PRS1ParsedDurationEvent::UNIT; + class PRS1ParsedValueEvent : public PRS1ParsedEvent { +public: + virtual QMap contents(void) + { + QMap out; + out["start"] = timeStr(m_start); + out["value"] = QString::number(value()); + return out; + } + protected: PRS1ParsedValueEvent(PRS1ParsedEventType type, int start, int value) : PRS1ParsedEvent(type, start) { m_value = value; } }; + class PRS1UnknownValueEvent : public PRS1ParsedValueEvent { public: + virtual QMap contents(void) + { + QMap out; + out["start"] = timeStr(m_start); + out["code"] = hex(m_code); + out["value"] = QString::number(value()); + return out; + } + int m_code; PRS1UnknownValueEvent(int code, int start, int value, float gain=1.0) : PRS1ParsedValueEvent(TYPE, start, value), m_code(code) { m_gain = gain; } }; + class PRS1UnknownDataEvent : public PRS1ParsedEvent { public: + virtual QMap contents(void) + { + QMap out; + out["pos"] = QString::number(m_pos); + out["data"] = byteList(m_data); + return out; + } + static const PRS1ParsedEventType TYPE = EV_PRS1_RAW; int m_pos; @@ -1282,6 +1332,13 @@ const PRS1ParsedEventType PRS1TidalVolumeEvent::TYPE; class PRS1ParsedSettingEvent : public PRS1ParsedValueEvent { public: + virtual QMap contents(void) + { + QMap out; + out[parsedSettingTypeName(m_setting)] = QString::number(value()); + return out; + } + static const PRS1ParsedEventType TYPE = EV_PRS1_SETTING; PRS1ParsedSettingType m_setting; @@ -1305,6 +1362,22 @@ public: class PRS1ParsedSliceEvent : public PRS1ParsedDurationEvent { public: + virtual QMap contents(void) + { + QMap out; + out["start"] = timeStr(m_start); + out["duration"] = timeStr(m_duration); + QString s; + switch (m_status) { + case EquipmentOn: s = "EquipmentOn"; break; + case EquipmentOff: s = "EquipmentOff"; break; + case EquipmentLeaking: s = "EquipmentLeaking"; break; + case UnknownStatus: s = "Unknown"; break; + } + out["status"] = s; + return out; + } + static const PRS1ParsedEventType TYPE = EV_PRS1_SLICE; SliceStatus m_status; @@ -1354,6 +1427,131 @@ PRS1_VALUE_EVENT(PRS1FlowRateEvent, EV_PRS1_FLOWRATE); // TODO: is this a singl PRS1_VALUE_EVENT(PRS1Test1Event, EV_PRS1_TEST1); PRS1_VALUE_EVENT(PRS1Test2Event, EV_PRS1_TEST2); + +//******************************************************************************************** + +#if UNITTEST_MODE +static QString hex(int i) +{ + return QString("0x") + QString::number(i, 16).toUpper(); +} + +#define ENUMSTRING(ENUM) case ENUM: s = #ENUM; break +static QString parsedEventTypeName(PRS1ParsedEventType t) +{ + QString s; + switch (t) { + ENUMSTRING(EV_PRS1_RAW); + ENUMSTRING(EV_PRS1_UNKNOWN); + ENUMSTRING(EV_PRS1_TB); + ENUMSTRING(EV_PRS1_OA); + ENUMSTRING(EV_PRS1_CA); + ENUMSTRING(EV_PRS1_FL); + ENUMSTRING(EV_PRS1_PB); + ENUMSTRING(EV_PRS1_LL); + ENUMSTRING(EV_PRS1_HY); + ENUMSTRING(EV_PRS1_TOTLEAK); + ENUMSTRING(EV_PRS1_LEAK); + ENUMSTRING(EV_PRS1_PRESSURE); + ENUMSTRING(EV_PRS1_IPAP); + ENUMSTRING(EV_PRS1_IPAPLOW); + ENUMSTRING(EV_PRS1_IPAPHIGH); + ENUMSTRING(EV_PRS1_EPAP); + ENUMSTRING(EV_PRS1_FLEX); + ENUMSTRING(EV_PRS1_RR); + ENUMSTRING(EV_PRS1_PTB); + ENUMSTRING(EV_PRS1_MV); + ENUMSTRING(EV_PRS1_TV); + ENUMSTRING(EV_PRS1_SNORE); + ENUMSTRING(EV_PRS1_VS); + ENUMSTRING(EV_PRS1_PP); + ENUMSTRING(EV_PRS1_RERA); + ENUMSTRING(EV_PRS1_NRI); + ENUMSTRING(EV_PRS1_FLOWRATE); + ENUMSTRING(EV_PRS1_TEST1); + ENUMSTRING(EV_PRS1_TEST2); + ENUMSTRING(EV_PRS1_SETTING); + ENUMSTRING(EV_PRS1_SLICE); + default: + s = hex(t); + qDebug() << "Unknown PRS1ParsedEventType type:" << qPrintable(s); + return s; + } + return s.mid(8).toLower(); // lop off initial EV_PRS1_ +} + +static QString parsedSettingTypeName(PRS1ParsedSettingType t) +{ + QString s; + switch (t) { + ENUMSTRING(PRS1_SETTING_CPAP_MODE); + ENUMSTRING(PRS1_SETTING_PRESSURE); + ENUMSTRING(PRS1_SETTING_PRESSURE_MIN); + ENUMSTRING(PRS1_SETTING_PRESSURE_MAX); + ENUMSTRING(PRS1_SETTING_EPAP); + ENUMSTRING(PRS1_SETTING_EPAP_MIN); + ENUMSTRING(PRS1_SETTING_EPAP_MAX); + ENUMSTRING(PRS1_SETTING_IPAP); + ENUMSTRING(PRS1_SETTING_IPAP_MIN); + ENUMSTRING(PRS1_SETTING_IPAP_MAX); + ENUMSTRING(PRS1_SETTING_PS); + ENUMSTRING(PRS1_SETTING_PS_MIN); + ENUMSTRING(PRS1_SETTING_PS_MAX); + ENUMSTRING(PRS1_SETTING_FLEX_MODE); + ENUMSTRING(PRS1_SETTING_FLEX_LEVEL); + ENUMSTRING(PRS1_SETTING_RAMP_TIME); + ENUMSTRING(PRS1_SETTING_RAMP_PRESSURE); + ENUMSTRING(PRS1_SETTING_HUMID_STATUS); + ENUMSTRING(PRS1_SETTING_HUMID_LEVEL); + ENUMSTRING(PRS1_SETTING_HEATED_TUBING); + ENUMSTRING(PRS1_SETTING_SYSTEMONE_RESIST_LOCK); + ENUMSTRING(PRS1_SETTING_SYSTEMONE_RESIST_SETTING); + ENUMSTRING(PRS1_SETTING_SYSTEMONE_RESIST_STATUS); + ENUMSTRING(PRS1_SETTING_HOSE_DIAMETER); + ENUMSTRING(PRS1_SETTING_AUTO_ON); + ENUMSTRING(PRS1_SETTING_AUTO_OFF); + ENUMSTRING(PRS1_SETTING_MASK_ALERT); + ENUMSTRING(PRS1_SETTING_SHOW_AHI); + default: + s = hex(t); + qDebug() << "Unknown PRS1ParsedSettingType type:" << qPrintable(s); + return s; + } + return s.mid(13).toLower(); // lop off initial PRS1_SETTING_ +} + +static QString timeStr(int t) +{ + int h = t / 3600; + int m = (t - (h * 3600)) / 60; + int s = t % 60; + return QString("%1:%2:%3").arg(h, 2, 10, QChar('0')).arg(m, 2, 10, QChar('0')).arg(s, 2, 10, QChar('0')); +} + +static QString byteList(QByteArray data, int limit) +{ + int count = data.size(); + if (limit == -1 || limit > count) limit = count; + QStringList l; + for (int i = 0; i < limit; i++) { + l.push_back(QString( "%1" ).arg((int) data[i] & 0xFF, 2, 16, QChar('0') ).toUpper()); + } + if (limit < count) l.push_back("..."); + QString s = l.join(" "); + return s; +} + +QString _PRS1ParsedEventName(PRS1ParsedEvent* e) +{ + return parsedEventTypeName(e->m_type); +} + +QMap _PRS1ParsedEventContents(PRS1ParsedEvent* e) +{ + return e->contents(); +} +#endif + //******************************************************************************************** @@ -1530,6 +1728,7 @@ bool PRS1DataChunk::ParseEventsF5V3(void) qDebug() << "1: (" << int(lastcode) << hex << lastpos << ")"; qDebug() << "2: (" << int(lastcode2) << hex << lastpos2 << ")"; qDebug() << "3: (" << int(lastcode3) << hex << lastpos3 << ")"; + this->AddEvent(new PRS1UnknownDataEvent(m_data, startpos)); return false; } delta = buffer[pos]; @@ -1628,7 +1827,8 @@ bool PRS1DataChunk::ParseEventsF5V3(void) break; default: - qDebug() << "Unknown code:" << hex << code << "in" << this->sessionid << "at" << pos; + qDebug() << "Unknown code:" << hex << code << "in" << this->sessionid << "at" << startpos; + this->AddEvent(new PRS1UnknownDataEvent(m_data, startpos)); } @@ -1831,6 +2031,7 @@ bool PRS1DataChunk::ParseEventsF5V012(void) qDebug() << "1: (" << int(lastcode) << hex << lastpos << ")"; qDebug() << "2: (" << int(lastcode2) << hex << lastpos2 << ")"; qDebug() << "3: (" << int(lastcode3) << hex << lastpos3 << ")"; + this->AddEvent(new PRS1UnknownDataEvent(m_data, startpos)); return false; } @@ -2072,6 +2273,7 @@ bool PRS1DataChunk::ParseEventsF5V012(void) default: // ERROR!!! qWarning() << "Some new fandangled PRS1 code detected " << hex << int(code) << " at " << pos - 1; + this->AddEvent(new PRS1UnknownDataEvent(m_data, startpos)); badcode = true; break; } @@ -2745,6 +2947,7 @@ bool PRS1DataChunk::ParseEventsF0(CPAPMode mode) qDebug() << "1: (" << hex << int(lastcode) << hex << lastpos << ")"; qDebug() << "2: (" << hex << int(lastcode2) << hex << lastpos2 << ")"; qDebug() << "3: (" << hex << int(lastcode3) << hex << lastpos3 << ")"; + this->AddEvent(new PRS1UnknownDataEvent(m_data, startpos)); return false; } @@ -2922,6 +3125,7 @@ bool PRS1DataChunk::ParseEventsF0(CPAPMode mode) // ERROR!!! qWarning() << "Some new fandangled PRS1 code detected in" << this->sessionid << hex << int(code) << " at " << pos - 1; + this->AddEvent(new PRS1UnknownDataEvent(m_data, startpos)); return false; } } @@ -3526,12 +3730,6 @@ bool PRS1Import::ImportSummary() { if (!summary) return false; - // All machines have a first byte zero for clean summary - if (summary->m_data.constData()[0] != 0) { - qDebug() << "Non zero hblock[0] indicator"; - return false; - } - session->set_first(qint64(summary->timestamp) * 1000L); session->setPhysMax(CPAP_LeakTotal, 120); @@ -3657,6 +3855,12 @@ bool PRS1Import::ImportSummary() bool PRS1DataChunk::ParseSummary() { + // All machines have a first byte zero for clean summary + if (this->m_data.constData()[0] != 0) { + qDebug() << "Non zero hblock[0] indicator"; + return false; + } + // TODO: The below mainblock creation is probably wrong. It should move to to its own function when it gets fixed. /* Example data block 000000c6@0000: 00 [10] 01 [00 01 02 01 01 00 02 01 00 04 01 40 07 diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.h b/oscar/SleepLib/loader_plugins/prs1_loader.h index bd0a97e8..04f24860 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.h +++ b/oscar/SleepLib/loader_plugins/prs1_loader.h @@ -193,6 +193,13 @@ protected: bool ExtractStoredCrc(int size); }; + +#if UNITTEST_MODE +QString _PRS1ParsedEventName(PRS1ParsedEvent* e); +QMap _PRS1ParsedEventContents(PRS1ParsedEvent* e); +#endif + + class PRS1Loader; /*! \class PRS1Import diff --git a/oscar/tests/prs1tests.cpp b/oscar/tests/prs1tests.cpp index f4ca6b0c..15eb3872 100644 --- a/oscar/tests/prs1tests.cpp +++ b/oscar/tests/prs1tests.cpp @@ -134,7 +134,7 @@ static QString byteList(QByteArray data, int limit=-1) l.push_back(QString( "%1" ).arg((int) data[i] & 0xFF, 2, 16, QChar('0') ).toUpper()); } if (limit < count) l.push_back("..."); - QString s = l.join(""); + QString s = l.join(" "); return s; } @@ -183,7 +183,36 @@ void ChunkToYaml(QFile & file, PRS1DataChunk* chunk) } // data - out << " data: " << byteList(chunk->m_data, 100) << endl; + bool dump_data = true; + if (chunk->m_parsedData.size() > 0) { + dump_data = false; + out << " events:" << endl; + for (auto & e : chunk->m_parsedData) { + QString name = _PRS1ParsedEventName(e); + if (name == "raw" || name == "unknown") { + dump_data = true; + } + QMap contents = _PRS1ParsedEventContents(e); + if (name == "setting" && contents.size() == 1) { + out << " - set_" << contents.firstKey() << ": " << contents.first() << endl; + } + else { + out << " - " << name << ":" << endl; + + // Always emit start first if present + if (contents.contains("start")) { + out << " " << "start" << ": " << contents["start"] << endl; + } + for (auto & key : contents.keys()) { + if (key == "start") continue; + out << " " << key << ": " << contents[key] << endl; + } + } + } + } + if (dump_data) { + out << " data: " << byteList(chunk->m_data, 100) << endl; + } // data CRC out << " crc: " << hex << chunk->storedCrc << endl; @@ -255,8 +284,17 @@ void parseAndEmitChunkYaml(const QString & path) // Parse the chunks in the file. QList chunks = s_loader->ParseFile(inpath); for (int i=0; i < chunks.size(); i++) { - // Emit the YAML. PRS1DataChunk * chunk = chunks.at(i); + + // Parse the inner data. + switch (chunk->ext) { + case 0: chunk->ParseCompliance(); break; + case 1: chunk->ParseSummary(); break; + case 2: chunk->ParseEvents(MODE_UNKNOWN); break; + default: break; + } + + // Emit the YAML. ChunkToYaml(file, chunk); delete chunk; } @@ -268,7 +306,7 @@ void parseAndEmitChunkYaml(const QString & path) void PRS1Tests::testChunksToYaml() { - //iterateTestCards(TESTDATA_PATH "prs1/input/", parseAndEmitChunkYaml); + iterateTestCards(TESTDATA_PATH "prs1/input/", parseAndEmitChunkYaml); } From f62d99bb31ebb97ea33d359f0a3b4eb2e44b3cfa Mon Sep 17 00:00:00 2001 From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com> Date: Thu, 30 May 2019 16:32:57 -0400 Subject: [PATCH 31/31] Add machines to tested PRS1 list, alert user if their machine is untested. This introduces a very slight change UI behavior, where the user will receive an alert if their machine has a supported family/version but is a model number for which we don't have any test data. Also, the user alert for an unsupported machine has been updated to align with the actual underlying parsing limitations. It's possible this will create some new warnings, but it doesn't seem like any such machines would have worked anyway, in which case an explanatory message is an improvement. The "untested" alert will happen on import once per launch of OSCAR. Ideally this would only happen the first time an untested machine is imported, but that will require figuring out how to serialize an additional value in the user's machine profile. --- oscar/SleepLib/loader_plugins/prs1_loader.cpp | 33 ++++++++++++++----- oscar/SleepLib/machine.cpp | 4 ++- oscar/SleepLib/machine.h | 6 +++- 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/oscar/SleepLib/loader_plugins/prs1_loader.cpp b/oscar/SleepLib/loader_plugins/prs1_loader.cpp index ea5300ed..b76cd507 100644 --- a/oscar/SleepLib/loader_plugins/prs1_loader.cpp +++ b/oscar/SleepLib/loader_plugins/prs1_loader.cpp @@ -214,14 +214,19 @@ struct PRS1TestedModel }; static const PRS1TestedModel s_PRS1TestedModels[] = { + { "251P", 0, 2 }, { "450P", 0, 3 }, + { "451P", 0, 3 }, { "550P", 0, 2 }, { "550P", 0, 3 }, + { "551P", 0, 2 }, { "750P", 0, 2 }, { "460P", 0, 4 }, + { "461P", 0, 4 }, { "560P", 0, 4 }, { "560PBT", 0, 4 }, + { "561P", 0, 4 }, { "660P", 0, 4 }, { "760P", 0, 4 }, @@ -242,6 +247,7 @@ static const PRS1TestedModel s_PRS1TestedModels[] = { { "900X110", 5, 3 }, { "900X120", 5, 3 }, + { "1061T", 3, 3 }, { "1160P", 3, 3 }, { "1030X110", 3, 6 }, { "1130X110", 3, 6 }, @@ -835,6 +841,9 @@ int PRS1Loader::FindSessionDirsAndProperties(const QString & path, QStringList & Machine* PRS1Loader::CreateMachineFromProperties(QString propertyfile) { + QHash props; + PeekProperties(propertyfile, props); + MachineInfo info = newInfo(); // Have a peek first to get the model number. PeekProperties(info, propertyfile); @@ -870,13 +879,13 @@ Machine* PRS1Loader::CreateMachineFromProperties(QString propertyfile) } // A bit of protection against future annoyances.. - if (((series != 5) && (series != 6) && (series != 0) && (series != 3))) { // || (type >= 10)) { + if (!s_PRS1ModelInfo.IsSupported(props) || ((series != 5) && (series != 6) && (series != 0) && (series != 3))) { // || (type >= 10)) { qDebug() << model << type << series << info.modelnumber << "unsupported"; #ifndef UNITTEST_MODE QMessageBox::information(QApplication::activeWindow(), QObject::tr("Machine Unsupported"), QObject::tr("Sorry, your Philips Respironics CPAP machine (Model %1) is not supported yet.").arg(info.modelnumber) +"\n\n"+ - QObject::tr("The developers needs a .zip copy of this machines' SD card and matching Encore .pdf reports to make it work with OSCAR.") + QObject::tr("The developers needs a .zip copy of this machine's SD card and matching Encore .pdf reports to make it work with OSCAR.") ,QMessageBox::Ok); #endif @@ -898,10 +907,21 @@ Machine* PRS1Loader::CreateMachineFromProperties(QString propertyfile) // This time supply the machine object so it can populate machine properties.. PeekProperties(m->info, propertyfile, m); + + if (!m->untested() && !s_PRS1ModelInfo.IsTested(props)) { + m->setUntested(true); + qDebug() << info.modelnumber << "untested"; +#ifndef UNITTEST_MODE + QMessageBox::information(QApplication::activeWindow(), + QObject::tr("Machine Untested"), + QObject::tr("Your Philips Respironics CPAP machine (Model %1) has not been tested yet.").arg(info.modelnumber) +"\n\n"+ + QObject::tr("It seems similar enough to other machines that it might work, but the developers would like a .zip copy of this machine's SD card and matching Encore .pdf reports to make sure it works with OSCAR.") + ,QMessageBox::Ok); + +#endif + } // TODO: Replace much of the above logic with PRS1ModelInfo logic. - QHash props; - PeekProperties(propertyfile, props); if (!s_PRS1ModelInfo.IsSupported(props)) { if (!m->unsupported()) { unsupported(m); @@ -1181,12 +1201,10 @@ enum PRS1ParsedSettingType }; -#if UNITTEST_MODE static QString timeStr(int t); static QString byteList(QByteArray data, int limit=-1); static QString hex(int i); static QString parsedSettingTypeName(PRS1ParsedSettingType t); -#endif class PRS1ParsedEvent @@ -1213,6 +1231,7 @@ protected: : m_type(type), m_start(start), m_duration(0), m_value(0), m_offset(0.0), m_gain(GAIN), m_unit(UNIT) { } +public: virtual ~PRS1ParsedEvent() { } @@ -1430,7 +1449,6 @@ PRS1_VALUE_EVENT(PRS1Test2Event, EV_PRS1_TEST2); //******************************************************************************************** -#if UNITTEST_MODE static QString hex(int i) { return QString("0x") + QString::number(i, 16).toUpper(); @@ -1550,7 +1568,6 @@ QMap _PRS1ParsedEventContents(PRS1ParsedEvent* e) { return e->contents(); } -#endif //******************************************************************************************** diff --git a/oscar/SleepLib/machine.cpp b/oscar/SleepLib/machine.cpp index bdc8480a..8f9ec846 100644 --- a/oscar/SleepLib/machine.cpp +++ b/oscar/SleepLib/machine.cpp @@ -1,5 +1,6 @@ -/* SleepLib Machine Class Implementation +/* SleepLib Machine Class Implementation * + * Copyright (c) 2019 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public @@ -45,6 +46,7 @@ Machine::Machine(Profile *_profile, MachineID id) : profile(_profile) day.clear(); highest_sessionid = 0; m_unsupported = false; + m_untested = false; if (!id) { srand(time(nullptr)); diff --git a/oscar/SleepLib/machine.h b/oscar/SleepLib/machine.h index 94e6ae2e..dcb2a6f5 100644 --- a/oscar/SleepLib/machine.h +++ b/oscar/SleepLib/machine.h @@ -1,5 +1,6 @@ -/* SleepLib Machine Class Header +/* SleepLib Machine Class Header * + * Copyright (c) 2019 The OSCAR Team * Copyright (C) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public @@ -184,9 +185,12 @@ class Machine QSemaphore *savelistSem; bool m_unsupported; + bool m_untested; bool unsupported() { return m_unsupported; } void setUnsupported(bool b) { m_unsupported = b; } + bool untested() { return m_untested; } + void setUntested(bool b) { m_untested = b; } void lockSaveMutex() { listMutex.lock(); } void unlockSaveMutex() { listMutex.unlock(); }