Merge branch 'master' into leave-new-charts-enabled

This commit is contained in:
sawinglogz 2019-12-27 23:08:04 -06:00
commit f1bd9c585b
6 changed files with 114 additions and 16 deletions

View File

@ -204,7 +204,7 @@ static QString ts(qint64 msecs)
// for more than 2 values, just write the test manually and use UNEXPECTED_VALUE if it fails
enum FlexMode { FLEX_None, FLEX_CFlex, FLEX_CFlexPlus, FLEX_AFlex, FLEX_RiseTime, FLEX_BiFlex, FLEX_AVAPS, FLEX_Unknown };
enum FlexMode { FLEX_None, FLEX_CFlex, FLEX_CFlexPlus, FLEX_AFlex, FLEX_RiseTime, FLEX_BiFlex, FLEX_AVAPS, FLEX_PFlex, FLEX_Unknown };
enum BackupBreathMode { PRS1Backup_Off, PRS1Backup_Auto, PRS1Backup_Fixed };
@ -243,6 +243,7 @@ static const PRS1TestedModel s_PRS1TestedModels[] = {
{ "400X150", 0, 6, "DreamStation CPAP Pro" },
{ "500X110", 0, 6, "DreamStation Auto CPAP" },
{ "500X150", 0, 6, "DreamStation Auto CPAP" },
{ "501X120", 0, 6, "DreamStation Auto CPAP with P-Flex" },
{ "500G110", 0, 6, "DreamStation Go Auto" },
{ "502G150", 0, 6, "DreamStation Go Auto" },
{ "600X110", 0, 6, "DreamStation BiPAP Pro" },
@ -955,7 +956,6 @@ void PRS1Loader::ScanFiles(const QStringList & paths, int sessionid_base, Machin
// All samples exhibiting this behavior are DreamStations.
task->m_wavefiles.append(fi.canonicalFilePath());
} else if (ext == 6) {
qWarning() << fi.canonicalFilePath() << "oximetry is untested"; // TODO: mark as untested/unexpected
if (!task->oxifile.isEmpty()) {
qDebug() << sid << "already has oximetry file" << relativePath(task->oxifile)
<< "skipping" << relativePath(fi.canonicalFilePath());
@ -1025,6 +1025,10 @@ void PRS1Loader::ScanFiles(const QStringList & paths, int sessionid_base, Machin
//qDebug() << chunkComparison(chunk, task->summary);
} else {
// Warn about any non-identical duplicate session IDs.
//
// This seems to happen with F5V1 slice 8, which is the only slice in a session,
// and which doesn't update the session ID, so the following slice 7 session
// (which can be hours later) has the same session ID. Neither affects import.
qWarning() << chunkComparison(chunk, task->summary);
}
delete chunk;
@ -6007,7 +6011,11 @@ bool PRS1DataChunk::ParseSettingsF0V6(const unsigned char* data, int size)
CHECK_VALUES(data[pos], 1, 2); // 1 when EZ-Start is enabled? 2 when Auto-Trial? 3 when Auto-Trial is off or Opti-Start isn't off?
}
if (len == 2) { // 400G, 500G has extra byte
CHECK_VALUES(data[pos+1], 0, 0x20); // Maybe related to Opti-Start?
if (data[pos+1]) {
// 0x20 seen with Opti-Start enabled
// 0x30 seen with both Opti-Start and EZ-Start enabled on 500X110
CHECK_VALUES(data[pos+1], 0x20, 0x30);
}
}
break;
case 0x0a: // CPAP pressure setting
@ -6075,6 +6083,12 @@ bool PRS1DataChunk::ParseSettingsF0V6(const unsigned char* data, int size)
CHECK_VALUE(data[pos], 0x80); // EZ-Start enabled
this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_EZ_START, data[pos] != 0));
break;
case 0x42: // EZ-Start for Auto-CPAP?
// Seen on 500X110 before 0x2b when EZ-Start is enabled on Auto-CPAP
CHECK_VALUE(len, 1);
CHECK_VALUE(data[pos], 0x80); // EZ-Start enabled
this->AddEvent(new PRS1ParsedSettingEvent(PRS1_SETTING_EZ_START, data[pos] != 0));
break;
case 0x2b: // Ramp Type
CHECK_VALUE(len, 1);
CHECK_VALUES(data[pos], 0, 0x80); // 0 == "Linear", 0x80 = "SmartRamp"
@ -6113,6 +6127,9 @@ bool PRS1DataChunk::ParseSettingsF0V6(const unsigned char* data, int size)
break;
}
break;
case 0xB0: // P-Flex
flexmode = FLEX_PFlex; // TOOD: There's a level present in the settings, does it have any effect?
break;
default:
UNEXPECTED_VALUE(data[pos], "known flex mode");
break;
@ -6368,6 +6385,11 @@ bool PRS1DataChunk::ParseSummaryF0V6(void)
// TODO: The daily details will show when it changed, so maybe there's an event that indicates a pressure change.
//CHECK_VALUES(data[pos], 90, 60); // maybe CPAP-Check pressure, also matches EZ-Start Pressure
break;
case 0x0c:
// EZ-Start pressure for Auto-CPAP, seen on 500X110 following 4, before 8
// Appears to reflect the current session's EZ-Start pressure, though reported afterwards
//CHECK_VALUE(data[pos], 70, 80);
break;
default:
UNEXPECTED_VALUE(code, "known slice code");
break;
@ -7060,13 +7082,27 @@ QList<PRS1DataChunk *> PRS1Import::CoalesceWaveformChunks(QList<PRS1DataChunk *>
QString session_s = fi.fileName().section(".", 0, -2);
qint32 sid = session_s.toInt(&numeric, m_sessionid_base);
if (!numeric || sid != chunk->sessionid) {
qWarning() << chunk->m_path << chunk->sessionid << "session ID mismatch";
qWarning() << chunk->m_path << "@" << chunk->m_filepos << "session ID mismatch:" << chunk->sessionid;
}
if (lastchunk != nullptr) {
// Waveform files shouldn't contain multiple sessions
// A handful of 960P waveform files have been observed to have multiple sessions.
//
// This breaks the current approach of deferring waveform parsing until the (multithreaded)
// import, since each session is in a separate import task and could be in a separate
// thread, or already imported by the time it is discovered that this file contains
// more than one session.
//
// For now, we just dump the chunks that don't belong to the session currently
// being imported in this thread, since this happens so rarely.
//
// TODO: Rework the import process to handle waveform data after compliance/summary/
// events (since we're no longer inferring session information from it) and add it to the
// newly imported sessions.
if (lastchunk->sessionid != chunk->sessionid) {
qWarning() << "lastchunk->sessionid != chunk->sessionid in PRS1Loader::CoalesceWaveformChunks()";
qWarning() << chunk->m_path << "@" << chunk->m_filepos
<< "session ID" << lastchunk->sessionid << "->" << chunk->sessionid
<< ", skipping" << allchunks.size() - i << "remaining chunks";
// Free any remaining chunks
for (int j=i; j < allchunks.size(); ++j) {
chunk = allchunks.at(j);
@ -7112,8 +7148,20 @@ QList<PRS1DataChunk *> PRS1Import::CoalesceWaveformChunks(QList<PRS1DataChunk *>
}
for (int n=0; n < num; n++) {
int interleave = chunk->waveformInfo.at(n).interleave;
if (interleave != 5) {
qDebug() << chunk->m_path << "interleave?" << interleave;
switch (chunk->ext) {
case 5: // flow data, 5 samples per second
if (interleave != 5) {
qDebug() << chunk->m_path << "interleave?" << interleave;
}
break;
case 6: // oximetry, 1 sample per second
if (interleave != 1) {
qDebug() << chunk->m_path << "interleave?" << interleave;
}
break;
default:
qWarning() << chunk->m_path << "unknown waveform?" << chunk->ext;
break;
}
}
@ -7132,6 +7180,7 @@ bool PRS1Import::ParseOximetry()
for (int i=0; i < size; ++i) {
PRS1DataChunk * oxi = oximetry.at(i);
int num = oxi->waveformInfo.size();
CHECK_VALUE(num, 2);
int size = oxi->m_data.size();
if (size == 0) {
@ -7565,8 +7614,15 @@ PRS1DataChunk* PRS1DataChunk::ParseNext(QFile & f)
// Make sure the calculated CRC over the entire chunk (header and data) matches the stored CRC.
if (chunk->calcCrc != chunk->storedCrc) {
// corrupt data block.. bleh..
// Correupt data block, warn about it.
qWarning() << chunk->m_path << "@" << chunk->m_filepos << "block CRC calc" << hex << chunk->calcCrc << "!= stored" << hex << chunk->storedCrc;
// TODO: When this happens, it's usually because the chunk was truncated and another chunk header
// exists within the blockSize bytes. In theory it should be possible to rewing and resync by
// looking for another chunk header with the same fileVersion, htype, family, familyVersion, and
// ext (blockSize and other fields could vary).
//
// But this is quite rare, so for now we bail on the rest of the file.
break;
}
@ -7880,6 +7936,7 @@ void PRS1Loader::initChannels()
chan->addOption(FLEX_CFlex, QObject::tr("C-Flex"));
chan->addOption(FLEX_CFlexPlus, QObject::tr("C-Flex+"));
chan->addOption(FLEX_AFlex, QObject::tr("A-Flex"));
chan->addOption(FLEX_PFlex, QObject::tr("P-Flex"));
chan->addOption(FLEX_RiseTime, QObject::tr("Rise Time"));
chan->addOption(FLEX_BiFlex, QObject::tr("Bi-Flex"));
chan->addOption(FLEX_AVAPS, QObject::tr("AVAPS"));

View File

@ -1 +1 @@
const int build_number = 4;
const int build_number = 1;

View File

@ -7,16 +7,48 @@ Which was written and copyright 2011-2018 &copy; Mark Watkins
</p>
<p>
<b>Changes and fixes in OSCAR <u>**AFTER**</u> v1.1.0-testing-4</b>
<b>Changes and fixes in OSCAR v1.1.0-beta-1</b>
<ul>
<li>Portions of OSCAR are &copy; 2019 by The OSCAR Team</li>
<li>[new] Default and View/reset graphs use a different order for AVS and AVAPS CPAP modes</li>
<li>[new] Extensive overhaul of the Philips Respironics System One importer, resolving all known issues.</li>
<li>[new] The following Philips Respironics machines are now tested and fully supported:
REMstar Plus (System One) (251P),
REMstar Pro (System One) (450P, 451P),
REMstar Auto (System One) (550P, 551P),
BiPAP Auto (System One) (750P),
BiPAP AutoSV Advanced System One (950P, 951P),
REMstar Pro (System One 60 Series) (460P, 461P),
REMstar Auto (System One 60 Series) (560P, 561P, 562P, 560PBT),
BiPAP Pro (System One 60 Series) (660P),
BiPAP Auto (System One 60 Series) (760P),
BiPAP autoSV Advanced (System One 60 Series) (960P, 961P),
BiPAP autoSV Advanced 30 (System One 60 Series) (960T),
BiPAP S/T 30 (System One 60 Series) (1061T),
BiPAP AVAPS 30 (System One 60 Series) (1160P),
DreamStation CPAP (200X110),
DreamStation CPAP Pro (400X110, 400X150),
DreamStation Go (400G110),
DreamStation Auto CPAP (500X110, 500X150),
DreamStation Go Auto (500G110, 502G150),
DreamStation BiPAP Pro (600X110),
DreamStation Auto BiPAP (700X110),
DreamStation BiPAP autoSV (900X110, 900X120),
DreamStation BiPAP S/T 30 (1030X110),
DreamStation BiPAP S/T 30 with AAM (1030X150),
DreamStation BiPAP AVAPS 30 (1130X110),
DreamStation BiPAP AVAPS 30 AE (1131X150)</li>
<li>[new] Update translation files and add new languages</li>
<li>[new] Allow user to reset graph order on Daily page to Standard or Advanced order (often useful for AVS and AVAPS CPAP modes)</li>
<li>[new] Add preference setting to include serial number on machine settings list</li>
<li>[fix] Place date, time, and Oscar version information in report footers</li>
<li>[fix] Update identification of ResMed S9 machines on Welcome page</li>
<li>[fix] Correct formatting of event number in Daily Events tab</li>
<li>[fix] Correct timezone offset for somnopose imports</li>
<li>[fix] Show a progress bar when setting Overview range to a large number of days</li>
<li>[fix] Make session bars on Daily page clearer by using a better color</li>
<li>[fix] Improve list of machines on Statistics page</li>
<li>[fix] Report Pressure when IPAP data is missing</li>
<li>[fix] Implement Refresh button on Profile page</li>
</ul>
</p>

View File

@ -556,13 +556,24 @@ Statistics::Statistics(QObject *parent) :
rows.push_back(StatisticsRow("Pressure", SC_MIN, MT_CPAP));
rows.push_back(StatisticsRow("Pressure", SC_MAX, MT_CPAP));
rows.push_back(StatisticsRow("Pressure", SC_90P, MT_CPAP));
rows.push_back(StatisticsRow("PressureSet", SC_WAVG, MT_CPAP));
rows.push_back(StatisticsRow("PressureSet", SC_MIN, MT_CPAP));
rows.push_back(StatisticsRow("PressureSet", SC_MAX, MT_CPAP));
rows.push_back(StatisticsRow("PressureSet", SC_90P, MT_CPAP));
rows.push_back(StatisticsRow("EPAP", SC_WAVG, MT_CPAP));
rows.push_back(StatisticsRow("EPAP", SC_MIN, MT_CPAP));
rows.push_back(StatisticsRow("EPAP", SC_MAX, MT_CPAP));
rows.push_back(StatisticsRow("EPAPSet", SC_WAVG, MT_CPAP));
rows.push_back(StatisticsRow("EPAPSet", SC_MIN, MT_CPAP));
rows.push_back(StatisticsRow("EPAPSet", SC_MAX, MT_CPAP));
rows.push_back(StatisticsRow("IPAP", SC_WAVG, MT_CPAP));
rows.push_back(StatisticsRow("IPAP", SC_90P, MT_CPAP));
rows.push_back(StatisticsRow("IPAP", SC_MIN, MT_CPAP));
rows.push_back(StatisticsRow("IPAP", SC_MAX, MT_CPAP));
rows.push_back(StatisticsRow("IPAPSet", SC_WAVG, MT_CPAP));
rows.push_back(StatisticsRow("IPAPSet", SC_90P, MT_CPAP));
rows.push_back(StatisticsRow("IPAPSet", SC_MIN, MT_CPAP));
rows.push_back(StatisticsRow("IPAPSet", SC_MAX, MT_CPAP));
rows.push_back(StatisticsRow("", SC_HEADING, MT_OXIMETER)); // Just adds some space
rows.push_back(StatisticsRow(tr("Oximeter Statistics"), SC_HEADING, MT_OXIMETER));

View File

@ -325,9 +325,7 @@ void parseAndEmitChunkYaml(const QString & path)
case 1: ok = chunk->ParseSummary(); break;
case 2: ok = chunk->ParseEvents(); break;
case 5: break; // skip flow/pressure waveforms
case 6: // skip oximetry data (but log it)
qWarning() << relative << "oximetry is untested"; // never encountered
break;
case 6: break; // skip oximetry data
default:
qWarning() << relative << "unexpected file type";
break;

View File

@ -15,7 +15,7 @@
const int major_version = 1; // incompatible API changes
const int minor_version = 1; // new features that don't break things
const int revision_number = 0; // bugfixes, revisions
const QString ReleaseStatus = "testing"; // testing/nightly/unstable, beta/untamed, rc/almost, r/stable
const QString ReleaseStatus = "beta"; // testing/nightly/unstable, beta/untamed, rc/almost, r/stable
const QString VersionString = QString("%1.%2.%3-%4-%5").arg(major_version).arg(minor_version).arg(revision_number).arg(ReleaseStatus).arg(build_number);
const QString ShortVersionString = QString("%1.%2.%3").arg(major_version).arg(minor_version).arg(revision_number);