From 054b633ebe240f33f4ef18938c09db17c6b7bd07 Mon Sep 17 00:00:00 2001
From: Phil Olynyk
Date: Tue, 7 Sep 2021 08:51:00 -0400
Subject: [PATCH 01/19] Fix issues with beta.2
---
.../SleepLib/loader_plugins/resmed_loader.cpp | 68 +++++++++++++++----
1 file changed, 55 insertions(+), 13 deletions(-)
diff --git a/oscar/SleepLib/loader_plugins/resmed_loader.cpp b/oscar/SleepLib/loader_plugins/resmed_loader.cpp
index 96160e0f..53f19c88 100644
--- a/oscar/SleepLib/loader_plugins/resmed_loader.cpp
+++ b/oscar/SleepLib/loader_plugins/resmed_loader.cpp
@@ -200,8 +200,8 @@ void ResmedLoader::initChannels()
channel.add(GRP_CPAP, chan = new Channel(RMAS1x_Comfort = 0xe20E, SETTING, MT_CPAP, SESSION,
"RMAS1x_Comfort", QObject::tr("Response"), QObject::tr("Response"), QObject::tr("Response"), "", LOOKUP, Qt::black));
- chan->addOption(0, QObject::tr("Soft")); // This must be verified
- chan->addOption(1, QObject::tr("Standard"));
+ chan->addOption(0, QObject::tr("Standard")); // This must be verified
+ chan->addOption(1, QObject::tr("Soft"));
channel.add(GRP_CPAP, chan = new Channel(RMAS11_SmartStop = 0xe20F, SETTING, MT_CPAP, SESSION,
"RMAS11_SmartStop", QObject::tr("SmartStop"), QObject::tr("Machine auto stops by breathing"), QObject::tr("Smart Stop"), "", LOOKUP, Qt::black));
@@ -212,8 +212,8 @@ void ResmedLoader::initChannels()
channel.add(GRP_CPAP, chan = new Channel(RMAS11_PtView= 0xe210, SETTING, MT_CPAP, SESSION,
"RMAS11_PTView", QObject::tr("Patient View"), QObject::tr("Patient View"), QObject::tr("Patient View"), "", LOOKUP, Qt::black));
- chan->addOption(0, QObject::tr("Simple"));
- chan->addOption(1, QObject::tr("Advanced"));
+ chan->addOption(0, QObject::tr("Advanced"));
+ chan->addOption(1, QObject::tr("Simple"));
// Setup ResMeds signal name translation map
setupResMedTranslationMap();
@@ -1355,8 +1355,10 @@ bool ResmedLoader::ProcessSTRfiles(Machine *mach, QMap & STRmap,
if ((sig = str.lookupSignal(CPAP_Mode))) {
int mod = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset;
R.rms9_mode = mod;
+ if ( AS_eleven && (mod == 2) )
+ R.rms9_mode = 11; //make it look like A4Her
- if (mod == 11) {
+ if ((mod == 11) && ( ! AS_eleven)) {
mode = MODE_APAP; // For her is a special apap
} else if (mod == 9) {
mode = MODE_AVAPS;
@@ -1369,6 +1371,11 @@ bool ResmedLoader::ProcessSTRfiles(Machine *mach, QMap & STRmap,
} else if (mod >= 3) { // mod 3 == vpap s fixed pressure (EPAP, IPAP, No PS)
// 4,5 are S/T types...
mode = MODE_BILEVEL_FIXED;
+ } else if ((mod == 2)) {
+ if ( AS_eleven )
+ mode = MODE_APAP;
+ else
+ mode = MODE_UNKNOWN;
} else if (mod == 1) {
mode = MODE_APAP; // mod 1 == apap
// not sure what mode 2 is ?? split ?
@@ -1381,12 +1388,15 @@ bool ResmedLoader::ProcessSTRfiles(Machine *mach, QMap & STRmap,
if ((mod == 0) && (sig = str.lookupLabel("S.C.StartPress"))) {
R.ramp_pressure = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset;
}
- // Settings.Adaptive Starting Pressure? // mode 11 = APAP for her?
+ // Settings.Adaptive Starting Pressure?
if ( (mod == 1) && ((sig = str.lookupLabel("S.AS.StartPress")) || (sig = str.lookupLabel("S.A.StartPress"))) ) {
R.ramp_pressure = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset;
}
- if ( (mod == 11) && (sig = str.lookupLabel("S.AFH.StartPress"))) {
- R.ramp_pressure = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset;
+ // mode 11 = APAP for her?
+ if ( ((mod == 11) && ( ! AS_eleven)) || ((mod == 2) && AS_eleven) ) {
+ if ( nullptr != (sig = str.lookupLabel("S.AFH.StartPress"))) {
+ R.ramp_pressure = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset;
+ }
}
if ((R.mode == MODE_BILEVEL_FIXED) && (sig = str.lookupLabel("S.BL.StartPress"))) {
// Bilevel Starting Pressure
@@ -1487,7 +1497,7 @@ bool ResmedLoader::ProcessSTRfiles(Machine *mach, QMap & STRmap,
bool haveipap = false;
Q_UNUSED( haveipap );
-// if (R.mode == MODE_BILEVEL_FIXED) {
+
if ((sig = str.lookupSignal(CPAP_IPAP))) {
R.ipap = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset;
haveipap = true;
@@ -1495,10 +1505,36 @@ bool ResmedLoader::ProcessSTRfiles(Machine *mach, QMap & STRmap,
if ((sig = str.lookupSignal(CPAP_EPAP))) {
R.epap = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset;
}
+ if (R.mode == MODE_AVAPS) {
+ if ((sig = str.lookupLabel("S.i.StartPress"))) {
+ R.ramp_pressure = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset;
+ }
+ if ((sig = str.lookupLabel("S.i.EPAP"))) {
+ R.min_epap = R.max_epap = R.epap = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset;
+ }
+ if ((sig = str.lookupLabel("S.i.MinPS"))) {
+ R.min_ps = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset;
+ }
+ if ((sig = str.lookupLabel("S.i.MinEPAP"))) {
+ R.min_epap = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset;
+ }
+ if ((sig = str.lookupLabel("S.i.MaxEPAP"))) {
+ R.max_epap = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset;
+ }
+ if ((sig = str.lookupLabel("S.i.MaxPS"))) {
+ R.max_ps = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset;
+ }
+ if ( R.epap >= 0 ) {
+ R.max_ipap = R.epap + R.max_ps;
+ R.min_ipap = R.epap + R.min_ps;
+ } else {
+ R.max_ipap = R.max_epap + R.max_ps;
+ R.min_ipap = R.min_epap + R.min_ps;
+ }
+ }
if (R.mode == MODE_ASV) {
if ((sig = str.lookupLabel("S.AV.StartPress"))) {
- EventDataType sp = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset;
- R.ramp_pressure = sp;
+ R.ramp_pressure = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset;
}
if ((sig = str.lookupLabel("S.AV.EPAP"))) {
R.min_epap = R.max_epap = R.epap = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset;
@@ -1675,6 +1711,8 @@ bool ResmedLoader::ProcessSTRfiles(Machine *mach, QMap & STRmap,
R.s_RampEnable = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset;
if ( AS_eleven )
R.s_RampEnable--;
+ if ( R.s_RampEnable == 2 )
+ R.s_RampTime = -1;
}
if ((sig = str.lookupLabel("S.EPR.ClinEnable"))) {
R.s_EPR_ClinEnable = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset;
@@ -1701,8 +1739,12 @@ bool ResmedLoader::ProcessSTRfiles(Machine *mach, QMap & STRmap,
if ((sig = str.lookupLabel("S.Mask"))) {
R.s_Mask = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset;
- if ( AS_eleven )
- R.s_Mask--;
+ if ( AS_eleven ) {
+ if ( R.s_Mask < 2 || R.s_Mask > 4 )
+ R.s_Mask = 4; // unknown mask type
+ else
+ R.s_Mask -= 2; // why be consistent?
+ }
}
if ((sig = str.lookupLabel("S.PtAccess"))) {
if ( AS_eleven ) {
From 43a3b155b78012896efe2cfed0fe2a3c5e45e1dc Mon Sep 17 00:00:00 2001
From: Phil Olynyk
Date: Sat, 11 Sep 2021 21:33:01 -0400
Subject: [PATCH 02/19] Ignore new fields fro 28509 (Lumis 150)
---
oscar/SleepLib/loader_plugins/resmed_loader.cpp | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/oscar/SleepLib/loader_plugins/resmed_loader.cpp b/oscar/SleepLib/loader_plugins/resmed_loader.cpp
index 53f19c88..326021db 100644
--- a/oscar/SleepLib/loader_plugins/resmed_loader.cpp
+++ b/oscar/SleepLib/loader_plugins/resmed_loader.cpp
@@ -2881,6 +2881,8 @@ bool ResmedLoader::LoadEVE(Session *sess, const QString & path)
CA = sess->AddEventList(CPAP_ClearAirway, EVL_Event);
if (sess->checkInside(tt))
CA->AddEvent(tt, anno->duration);
+ } else if (anno->text == "SpO2 Desaturation") { // Used in 28509
+ continue; // ignored for now
} else {
if (anno->text != "Recording starts") {
qDebug() << "Unobserved ResMed annotation field: " << anno->text;
@@ -3240,6 +3242,12 @@ bool ResmedLoader::LoadPLD(Session *sess, const QString & path)
// a = ToTimeDelta(sess,edf,es, code,samples,duration,0,0, square);
} else if (es.label == "Va") { // Signal used in 36039... What to do with it???
a = nullptr; // We'll skip it for now
+ } else if (es.label == "AlvMinVent.2s") { // Signal used in 28509... What to do with it???
+ a = nullptr; // We'll skip it for now
+ } else if (es.label == "CLRatio.2s") { // Signal used in 28509... What to do with it???
+ a = nullptr; // We'll skip it for now
+ } else if (es.label == "TRRatio.2s") { // Signal used in 28509... What to do with it???
+ a = nullptr; // We'll skip it for now
} else if (es.label == "") { // What the hell resmed??
// these empty lables should be changed in resmed_EDFInfo to something unique
if (emptycnt == 0) {
From 150bc3a3bc438b0e4f867b97642728a2349ed627 Mon Sep 17 00:00:00 2001
From: Phil Olynyk
Date: Sun, 12 Sep 2021 13:45:24 -0400
Subject: [PATCH 03/19] Test for Ident.json file first, complain if both .json
and .tgt exisit
---
.../SleepLib/loader_plugins/resmed_loader.cpp | 37 ++++++++++---------
1 file changed, 20 insertions(+), 17 deletions(-)
diff --git a/oscar/SleepLib/loader_plugins/resmed_loader.cpp b/oscar/SleepLib/loader_plugins/resmed_loader.cpp
index 326021db..a34e2a66 100644
--- a/oscar/SleepLib/loader_plugins/resmed_loader.cpp
+++ b/oscar/SleepLib/loader_plugins/resmed_loader.cpp
@@ -263,26 +263,14 @@ MachineInfo ResmedLoader::PeekInfo(const QString & path)
QFile f(path+"/"+RMS9_STR_idfile+"tgt");
- // Abort if this file is dodgy..
- if (f.exists() ) {
- if ( !f.open(QIODevice::ReadOnly)) {
- return MachineInfo();
- }
- MachineInfo info = newInfo();
-
- // Parse # entries into idmap.
- while (!f.atEnd()) {
- QString line = f.readLine().trimmed();
- QHash hash = parseIdentLine( line, & info );
- }
-
- return info;
- }
- QFile j(path+"/"+RMS9_STR_idfile+"json");
- if (j.exists() ) {
+ QFile j(path+"/"+RMS9_STR_idfile+"json"); // Check for AS11 file first, just in case
+ if (j.exists() ) { // somebody is reusing an SD card w/o re-formatting
if ( !j.open(QIODevice::ReadOnly)) {
return MachineInfo();
}
+ if ( f.exists() ) {
+ qDebug() << "Old Ident.tgt file is ignored";
+ }
QByteArray identData = j.readAll();
j.close();
QJsonDocument identDoc(QJsonDocument::fromJson(identData));
@@ -301,6 +289,21 @@ MachineInfo ResmedLoader::PeekInfo(const QString & path)
}
}
+ // Abort if this file is dodgy..
+ if (f.exists() ) {
+ if ( !f.open(QIODevice::ReadOnly)) {
+ return MachineInfo();
+ }
+ MachineInfo info = newInfo();
+
+ // Parse # entries into idmap.
+ while (!f.atEnd()) {
+ QString line = f.readLine().trimmed();
+ QHash hash = parseIdentLine( line, & info );
+ }
+
+ return info;
+ }
// neither filename exists, return empty info
return MachineInfo();
}
From 0037eca57b0a619e18a3ce384e6919463e1e91e4 Mon Sep 17 00:00:00 2001
From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com>
Date: Wed, 15 Sep 2021 14:54:08 -0400
Subject: [PATCH 04/19] Allow zipping to continue when errors are encountered.
Add the debug log to SD card zips if there were unexpected errors.
Also display the progress dialog while scanning SD cards for zipping.
---
Htmldocs/release_notes.html | 1 +
oscar/mainwindow.cpp | 22 +++++++++++++++++++++-
oscar/zip.cpp | 16 +++++++++++-----
3 files changed, 33 insertions(+), 6 deletions(-)
diff --git a/Htmldocs/release_notes.html b/Htmldocs/release_notes.html
index 141b5594..5c876379 100644
--- a/Htmldocs/release_notes.html
+++ b/Htmldocs/release_notes.html
@@ -56,6 +56,7 @@
[fix] Ignore old sessions should not impact existing data.
[fix] Fix ocasional misordering of indexes on the Daily page.
[fix] Add Unclassified Apneas to the Statistics page.
+ [fix] Resolve empty CPAP data card zips on macOS Big Sur.
Changes and fixes in OSCAR v1.2.0
diff --git a/oscar/mainwindow.cpp b/oscar/mainwindow.cpp
index b0e08b24..252f1328 100644
--- a/oscar/mainwindow.cpp
+++ b/oscar/mainwindow.cpp
@@ -2712,8 +2712,28 @@ void MainWindow::on_actionCreate_Card_zip_triggered()
bool ok = z.Open(filename);
if (ok) {
ProgressDialog * prog = new ProgressDialog(this);
+
+ // Very full cards can sometimes take nearly a minute to scan,
+ // so display the progress dialog immediately.
+ prog->setMessage(tr("Calculating size..."));
+ prog->setWindowModality(Qt::ApplicationModal);
+ prog->open();
+
+ // Build the list of files.
+ FileQueue files;
+ bool ok = files.AddDirectory(cardPath, prefix);
+
+ // If there were any unexpected errors scanning the media, add the debug log to the zip.
+ if (!ok) {
+ qWarning() << "Unexpected errors when scanning SD card, adding debug log to zip.";
+ QString debugLog = logger->logFileName();
+ files.AddFile(debugLog, prefix + "-debug.txt");
+ }
+
prog->setMessage(tr("Creating zip..."));
- ok = z.AddDirectory(cardPath, prefix, prog);
+
+ // Create the zip.
+ ok = z.AddFiles(files, prog);
z.Close();
} else {
qWarning() << "Unable to open" << filename;
diff --git a/oscar/zip.cpp b/oscar/zip.cpp
index def2b80a..80f7409a 100644
--- a/oscar/zip.cpp
+++ b/oscar/zip.cpp
@@ -168,6 +168,14 @@ bool FileQueue::AddDirectory(const QString & path, const QString & prefix)
QDir dir(path);
if (!dir.exists() || !dir.isReadable()) {
qWarning() << dir.canonicalPath() << "can't read directory";
+#if defined(Q_OS_MACOS)
+ // If this is a directory known to be protected by macOS "Full Disk Access" permissions,
+ // skip it but don't consider it an error.
+ static const QSet s_macProtectedDirs = { ".fseventsd", ".Spotlight-V100", ".Trashes" };
+ if (s_macProtectedDirs.contains(dir.dirName())) {
+ return true;
+ }
+#endif
return false;
}
QString base = prefix;
@@ -190,14 +198,12 @@ bool FileQueue::AddDirectory(const QString & path, const QString & prefix)
qWarning() << "skipping symlink" << canonicalPath << fi.symLinkTarget();
} else if (fi.isDir()) {
// Descend and recurse
- ok = AddDirectory(canonicalPath, relative_path);
+ ok &= AddDirectory(canonicalPath, relative_path);
} else {
// Add the file to the zip
- ok = AddFile(canonicalPath, relative_path);
- }
- if (!ok) {
- break;
+ ok &= AddFile(canonicalPath, relative_path);
}
+ // Don't stop in our tracks when we hit an error.
}
return ok;
From 5355713255131139cf1eff7b37924302fe00888b Mon Sep 17 00:00:00 2001
From: Guy Scharf
Date: Sat, 18 Sep 2021 21:02:51 -0700
Subject: [PATCH 05/19] Check for Updates no longer shows unwanted early
releases Change option and messages for showing early releases. Update
Release Notes to correct an error re: zombie reports and include a section
for beta 2+ fixes.
---
Htmldocs/release_notes.html | 12 ++++++++++--
oscar/Graphs/gGraph.cpp | 1 +
oscar/checkupdates.cpp | 22 ++++++++++++++++------
oscar/checkupdates.h | 1 +
oscar/newprofile.cpp | 2 ++
oscar/preferencesdialog.ui | 4 ++--
6 files changed, 32 insertions(+), 10 deletions(-)
diff --git a/Htmldocs/release_notes.html b/Htmldocs/release_notes.html
index 5c876379..c6ffe462 100644
--- a/Htmldocs/release_notes.html
+++ b/Htmldocs/release_notes.html
@@ -11,7 +11,15 @@
For other languages, go to:
http://www.apneaboard.com/wiki/index.php/OSCAR_Release_Notes
- Changes and fixes in OSCAR v1.3.0 Beta 1
+ Changes and fixes in OSCAR v1.3.0 Beta ***
+
Portions of OSCAR are © 2019-2021 by
+ The OSCAR Team
+
+ - [fix] Check for Updates no longer shows test versions for release users.
+ - [fix] Available Rx pressures shown correctly in Profile dialog.
+
+
+ Changes and fixes in OSCAR v1.3.0 Beta 2/b>
Portions of OSCAR are © 2019-2021 by
The OSCAR Team
@@ -32,7 +40,7 @@
- [new] Improve Somnopose import options.
- [new] Purge Current Selected Day allows purge of each machine type separately
- [new] Multi-file import for non-CPAP loaders (Somnopose, Viatom, Zeo, Dreem)
- - [new] Weight, BMI and Zombie history appear in statistics
+ - [new] Weight, BMI and Zombie history appear in Overview page
- [new] Add Turkish signal names to RedMed loader.
- [new] Add Traditional Chinese to languages available.
- [fix] Correct calculation of average leak rate on Welcome page.
diff --git a/oscar/Graphs/gGraph.cpp b/oscar/Graphs/gGraph.cpp
index 9c82f9b8..4396ecba 100644
--- a/oscar/Graphs/gGraph.cpp
+++ b/oscar/Graphs/gGraph.cpp
@@ -1300,6 +1300,7 @@ EventDataType gGraph::MinY()
}
return rmin_y = val;
+// return rmin_y = val * 0.9;
}
EventDataType gGraph::MaxY()
{
diff --git a/oscar/checkupdates.cpp b/oscar/checkupdates.cpp
index 8b3e1b16..af3c2270 100644
--- a/oscar/checkupdates.cpp
+++ b/oscar/checkupdates.cpp
@@ -162,25 +162,30 @@ void CheckUpdates::compareVersions () {
VersionInfo testVersion = getVersionInfo ("test", platformStr());
msg = "";
+ if (!showTestVersion)
+ testVersion.version = "";
if (testVersion.version.length() == 0 && releaseVersion.version.length() == 0) {
- if (showIfCurrent)
- msg = QObject::tr("You are running the latest release of OSCAR");
+ if (showIfCurrent) {
+ QString txt = getVersion().IsReleaseVersion()?QObject::tr("release"):QObject::tr("test version");
+ QString txt2 = QObject::tr("You are running the latest %1 of OSCAR").arg(txt);
+ msg = txt2 + "" + QObject::tr("You are running OSCAR %1").arg(getVersion()) + "
";
+ }
} else {
msg = QObject::tr("A more recent version of OSCAR is available");
- msg += "" + QObject::tr("You are running version %1").arg(getVersion()) + "
";
+ msg += "" + QObject::tr("You are running OSCAR %1").arg(getVersion()) + "
";
if (releaseVersion.version.length() > 0) {
msg += "" + QObject::tr("OSCAR %1 is available here.").arg(releaseVersion.version).arg(releaseVersion.urlInstaller) + "
";
}
- if (testVersion.version.length() > 0) {
+ if (showTestVersion && (testVersion.version.length() > 0)) {
msg += "" + QObject::tr("Information about more recent test version %1 is available at %2").arg(testVersion.version).arg(testVersion.urlInstaller) + "
";
}
}
if (msg.length() > 0) {
// Add elapsed time in test versions only
- if (elapsedTime > 0 && !getVersion().IsReleaseVersion())
- msg += "" + QString(QObject::tr("(Reading %1 took %2 seconds)")).arg("versions.xml").arg(elapsedTime) + "
";
+// if (elapsedTime > 0 && !getVersion().IsReleaseVersion())
+// msg += "" + QString(QObject::tr("(Reading %1 took %2 seconds)")).arg("versions.xml").arg(elapsedTime) + "
";
msgIsReady = true;
}
@@ -212,9 +217,12 @@ void CheckUpdates::showMessage()
void CheckUpdates::checkForUpdates(bool showWhenCurrent)
{
showIfCurrent = showWhenCurrent;
+ showTestVersion = false;
// If running a test version of OSCAR, try reading versions.xml from OSCAR_Data directory
+ // and force display of any new test version
if (!getVersion().IsReleaseVersion()) {
+ showTestVersion = true;
versionXML = readLocalVersions();
if (versionXML.length() > 0) {
compareVersions();
@@ -223,6 +231,8 @@ void CheckUpdates::checkForUpdates(bool showWhenCurrent)
showMessage();
return;
}
+ } else {
+ showTestVersion = AppSetting->allowEarlyUpdates();
}
readTimer.start();
diff --git a/oscar/checkupdates.h b/oscar/checkupdates.h
index 2a980cd8..ec4e1918 100644
--- a/oscar/checkupdates.h
+++ b/oscar/checkupdates.h
@@ -50,6 +50,7 @@ class CheckUpdates : public QMainWindow
QString msg; // Message to show to user
bool msgIsReady = false; // Message is ready to be displayed
bool showIfCurrent = false; // show a message if running current release
+ bool showTestVersion = false; // Show message if test version is available
QProgressDialog * checkingBox;// Looking for updates message
QNetworkReply *reply;
diff --git a/oscar/newprofile.cpp b/oscar/newprofile.cpp
index da306902..a8333b8a 100644
--- a/oscar/newprofile.cpp
+++ b/oscar/newprofile.cpp
@@ -363,6 +363,8 @@ void NewProfile::edit(const QString name)
ui->untreatedAHIEdit->setValue(profile->cpap->untreatedAHI());
ui->cpapModeCombo->setCurrentIndex((int)profile->cpap->mode());
+ on_cpapModeCombo_activated(profile->cpap->mode());
+
ui->doctorNameEdit->setText(profile->doctor->name());
ui->doctorPracticeEdit->setText(profile->doctor->practiceName());
ui->doctorPhoneEdit->setText(profile->doctor->phone());
diff --git a/oscar/preferencesdialog.ui b/oscar/preferencesdialog.ui
index 53c9403b..112f4646 100644
--- a/oscar/preferencesdialog.ui
+++ b/oscar/preferencesdialog.ui
@@ -10,7 +10,7 @@
0
0
942
- 651
+ 655
@@ -2210,7 +2210,7 @@ Mainly affects the importer.
If you are interested in helping test new features and bugfixes early, click here.
- I want to try experimental and test builds. (Advanced users only please.)
+ I want to be notified of test versions. (Advanced users only please.)
From bdce7314956275e59c28893e0843d030e62f3cf1 Mon Sep 17 00:00:00 2001
From: Guy Scharf
Date: Mon, 20 Sep 2021 08:03:34 -0700
Subject: [PATCH 06/19] Update Overview page when zombie or weight values are
changed in Daily Notes tab.
---
oscar/daily.cpp | 2 ++
oscar/mainwindow.cpp | 5 +++++
oscar/mainwindow.h | 2 ++
3 files changed, 9 insertions(+)
diff --git a/oscar/daily.cpp b/oscar/daily.cpp
index 2531adac..e1c5ab2d 100644
--- a/oscar/daily.cpp
+++ b/oscar/daily.cpp
@@ -2474,6 +2474,7 @@ void Daily::on_ZombieMeter_valueChanged(int action)
}
journal->settings[Journal_ZombieMeter]=ui->ZombieMeter->value();
journal->SetChanged(true);
+ mainwin->updateOverview();
}
void Daily::on_bookmarkTable_itemChanged(QTableWidgetItem *item)
@@ -2552,6 +2553,7 @@ void Daily::on_weightSpinBox_editingFinished()
if (g) g->setDay(nullptr);
}
journal->SetChanged(true);
+ mainwin->updateOverview();
}
void Daily::on_ouncesSpinBox_valueChanged(int arg1)
diff --git a/oscar/mainwindow.cpp b/oscar/mainwindow.cpp
index 252f1328..f461b4f3 100644
--- a/oscar/mainwindow.cpp
+++ b/oscar/mainwindow.cpp
@@ -746,6 +746,11 @@ int MainWindow::importCPAP(ImportPath import, const QString &message)
return c;
}
+void MainWindow::updateOverview()
+{
+ if (overview)
+ overview->ReloadGraphs();
+}
void MainWindow::finishCPAPImport()
{
if (daily)
diff --git a/oscar/mainwindow.h b/oscar/mainwindow.h
index e50d21c1..33d3ab5b 100644
--- a/oscar/mainwindow.h
+++ b/oscar/mainwindow.h
@@ -137,6 +137,8 @@ class MainWindow : public QMainWindow
//! \brief Returns the Overview Tab object
Overview *getOverview() { return overview; }
+ void updateOverview();
+
/*! \fn void RestartApplication(bool force_login=false);
\brief Closes down OSCAR and restarts it
\param bool force_login
From 30b93c564e328e861168b39cab51d1aa699302a5 Mon Sep 17 00:00:00 2001
From: Guy Scharf
Date: Mon, 20 Sep 2021 08:21:06 -0700
Subject: [PATCH 07/19] Update Release Notes
---
Htmldocs/release_notes.html | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/Htmldocs/release_notes.html b/Htmldocs/release_notes.html
index c6ffe462..cb1c8549 100644
--- a/Htmldocs/release_notes.html
+++ b/Htmldocs/release_notes.html
@@ -15,8 +15,15 @@
Portions of OSCAR are © 2019-2021 by
The OSCAR Team
- - [fix] Check for Updates no longer shows test versions for release users.
- - [fix] Available Rx pressures shown correctly in Profile dialog.
+ - [fix] Check for Updates no longer shows test versions to release users.
+ - [fix] Rx pressures shown correctly in Profile dialog.
+ - [fix] Allow zipping to continue when errors occur (MacOS).
+ - [fix] Always prompt for SD card location when zipping SD card.
+ - [fix] Add script to identify platform in mkDistDeb.sh (Linux building).
+ - ---------- Fixes to previous fixes (do not include in final release notes) ---------
+ - [ffx] Weight, BMI, and Zombie are on Overview, not Statistics page.
+ - [ffx] Overview page updated correctly when Weight or Zombie values are changed.
+ - [ffx] Various corrections to language files.
Changes and fixes in OSCAR v1.3.0 Beta 2/b>
From 79c5809b59d9744a7717b6d93b7e04f168f0ba66 Mon Sep 17 00:00:00 2001
From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com>
Date: Wed, 15 Sep 2021 16:06:21 -0400
Subject: [PATCH 08/19] Add support for Viatom/Wellue files that have a SleepU_
etc. prefix.
---
oscar/SleepLib/loader_plugins/viatom_loader.cpp | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/oscar/SleepLib/loader_plugins/viatom_loader.cpp b/oscar/SleepLib/loader_plugins/viatom_loader.cpp
index dbee9e85..ff545263 100644
--- a/oscar/SleepLib/loader_plugins/viatom_loader.cpp
+++ b/oscar/SleepLib/loader_plugins/viatom_loader.cpp
@@ -208,7 +208,8 @@ void ViatomLoader::EndEventList(ChannelID channel, qint64 /*t*/)
QStringList ViatomLoader::getNameFilter()
{
- return QStringList("20[0-5][0-9][01][0-9][0-3][0-9][012][0-9][0-5][0-9][0-5][0-9]");
+ // Sometimes the files have a SleepU_ or O2Ring_ prefix.
+ return QStringList("*20[0-5][0-9][01][0-9][0-3][0-9][012][0-9][0-5][0-9][0-5][0-9]");
}
static bool viatom_initialized = false;
From 3d855066b25cef25947f383883e5e39e4d49d4bf Mon Sep 17 00:00:00 2001
From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com>
Date: Wed, 15 Sep 2021 17:02:58 -0400
Subject: [PATCH 09/19] Reinstate unexpected data warnings suppressed by 31bd10
until we find test data.
Also remove unnecessary comments now that the functional changes have been
reviewed and tested.
---
.../SleepLib/loader_plugins/viatom_loader.cpp | 38 +++++++++----------
oscar/SleepLib/loader_plugins/viatom_loader.h | 1 -
2 files changed, 19 insertions(+), 20 deletions(-)
diff --git a/oscar/SleepLib/loader_plugins/viatom_loader.cpp b/oscar/SleepLib/loader_plugins/viatom_loader.cpp
index ff545263..b0ef7645 100644
--- a/oscar/SleepLib/loader_plugins/viatom_loader.cpp
+++ b/oscar/SleepLib/loader_plugins/viatom_loader.cpp
@@ -2,7 +2,6 @@
*
* Copyright (c) 2019-2020 The OSCAR Team
* (Initial importer written by dave madden )
- * Modified 02/21/2021 to allow for CheckMe device data files by Crimson Nape
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of the source code
@@ -280,14 +279,7 @@ bool ViatomFile::ParseHeader()
int min = header[7];
int sec = header[8];
-
- /* CN - Changed the if statement to a switch to accomdate additional Viatom/Wellue signatures in the future
- if (sig != 0x0003) {
- qDebug() << m_file.fileName() << "invalid signature for Viatom data file" << sig;
- return false;
- }
- CN */
- switch (sig){
+ switch (sig) {
case 0x0003:
case 0x0005:
break;
@@ -361,8 +353,10 @@ bool ViatomFile::ParseHeader()
CHECK_VALUE(filesize, m_file.size());
}
CHECK_VALUES(m_resolution, 2000, 4000);
-// CHECK_VALUE(datasize % RECORD_SIZE, 0); CN - Commented out these 2 lines because CheckMe can record odd number of entries
-// CHECK_VALUE(m_duration % m_record_count, 0);
+ if (true) { // TODO: We need CheckMe sample data where this doesn't hold true.
+ CHECK_VALUE(datasize % RECORD_SIZE, 0);
+ CHECK_VALUE(m_duration % m_record_count, 0);
+ }
//qDebug().noquote() << m_file.fileName() << ts(m_timestamp) << dur(m_duration * 1000L) << ":" << m_record_count << "records @" << m_resolution << "ms";
@@ -374,22 +368,25 @@ QList ViatomFile::ReadData()
QByteArray data = m_file.readAll();
QDataStream in(data);
in.setByteOrder(QDataStream::LittleEndian);
- int iCheckMeAdj; // Allows for an odd number in the CheckMe duration/# of records return
+
QList records;
+
// Read all Pulse, SPO2 and Motion data
do {
ViatomFile::Record rec;
in >> rec.spo2 >> rec.hr >> rec.oximetry_invalid >> rec.motion >> rec.vibration;
- CHECK_VALUES(rec.oximetry_invalid, 0, 0xFF); //If it doesn't have one of these 2 values, it's bad
- if (rec.vibration == 0x40) rec.vibration = 0x80; //0x040 (64) or 0x80 (128) when vibration is triggered
- CHECK_VALUES(rec.vibration, 0, 0x80); // 0x80 (128) when vibration is triggered
+ CHECK_VALUES(rec.oximetry_invalid, 0, 0xFF);
+ if (rec.vibration) {
+ CHECK_VALUES(rec.vibration, 0x40, 0x80); // 0x40 or 0x80 when vibration is triggered
+ }
+ // Invalid readings indicate any interruption in the measurements, whether
+ // transitory (e.g. due to movement) or when the device is removed at the end of a session.
if (rec.oximetry_invalid == 0xFF) {
CHECK_VALUE(rec.spo2, 0xFF);
- CHECK_VALUE(rec.hr, 0xFF); // if all 3 have 0xFF, then end of data
+ CHECK_VALUE(rec.hr, 0xFF);
}
records.append(rec);
- } while (records.size() < m_record_count); // CN Changed to allow for an incomlpete record values
-// CN } while (!in.atEnd());
+ } while (records.size() < m_record_count);
// It turns out 2s files are actually just double-reported samples!
if (m_resolution == 2000) {
@@ -416,11 +413,14 @@ QList ViatomFile::ReadData()
records = dedup;
}
}
+ /* TODO: Test against CheckMe sample data
+ int iCheckMeAdj; // Allows for an odd number in the CheckMe duration/# of records return
iCheckMeAdj = duration() / records.size();
if(iCheckMeAdj == 3) iCheckMeAdj = 4; // CN - Sanity check for CheckMe devices since their files do not always terminate on an even number.
CHECK_VALUE(iCheckMeAdj, 4); // Crimson Nape - Changed to accomadate the CheckMe data files.
- // CHECK_VALUE(duration() / records.size(), 4); // We've only seen 4s true resolution so far.
+ */
+ CHECK_VALUE(duration() / records.size(), 4); // We've only seen 4s true resolution so far.
return records;
}
diff --git a/oscar/SleepLib/loader_plugins/viatom_loader.h b/oscar/SleepLib/loader_plugins/viatom_loader.h
index 948f539d..48b9b4ae 100644
--- a/oscar/SleepLib/loader_plugins/viatom_loader.h
+++ b/oscar/SleepLib/loader_plugins/viatom_loader.h
@@ -2,7 +2,6 @@
*
* Copyright (c) 2019-2020 The OSCAR Team
* (Initial importer written by dave madden )
- * Modified 02/21/2021 to allow for CheckMe device data files by Crimson Nape
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of the source code
From cf1b12e029f0f12c84de3d60724787108862fba6 Mon Sep 17 00:00:00 2001
From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com>
Date: Fri, 17 Sep 2021 09:06:37 -0400
Subject: [PATCH 10/19] Prefer a Viatom filename's timestamp to the header
timestamp when valid.
No naturally occurring discrepancies have been observed in the wild,
but for some reason the timestamps generated by Viatom/Wellue devices
seem to be a bit off, even when their clocks have been synchronized
to atomic time.
This patch provides a fairly easy way for a user to adjust Viatom
timestamps to match their CPAP's.
---
oscar/SleepLib/loader_plugins/viatom_loader.cpp | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/oscar/SleepLib/loader_plugins/viatom_loader.cpp b/oscar/SleepLib/loader_plugins/viatom_loader.cpp
index b0ef7645..3a2a2336 100644
--- a/oscar/SleepLib/loader_plugins/viatom_loader.cpp
+++ b/oscar/SleepLib/loader_plugins/viatom_loader.cpp
@@ -304,6 +304,20 @@ bool ViatomFile::ParseHeader()
// starting timestamp). Technically these should probably be square charts, but
// the code currently forces them to be non-square.
QDateTime data_timestamp = QDateTime(QDate(year, month, day), QTime(hour, min, sec));
+
+ QString date_string = QFileInfo(m_file).fileName().section("_", -1); // Strip any SleepU_ etc. prefix.
+ QDateTime filename_timestamp = QDateTime::fromString(date_string, "yyyyMMddHHmmss");
+ if (filename_timestamp.isValid()) {
+ if (filename_timestamp != data_timestamp) {
+ // TODO: Once there's a better/easier way to adjust session times within OSCAR, we can remove the below.
+ qDebug() << m_file.fileName() << "Using filename timestamp" << filename_timestamp.toString("yyyy-MM-dd HH:mm:ss")
+ << "instead of header timestamp" << data_timestamp.toString("yyyy-MM-dd HH:mm:ss");
+ data_timestamp = filename_timestamp;
+ }
+ } else {
+ qWarning() << m_file.fileName() << "invalid timestamp in Viatom filename";
+ }
+
m_timestamp = data_timestamp.toMSecsSinceEpoch();
m_sessionid = m_timestamp / 1000L;
From a9faa2eddcff92b1ba1cf2fe821514523b3b2776 Mon Sep 17 00:00:00 2001
From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com>
Date: Mon, 20 Sep 2021 13:37:03 -0400
Subject: [PATCH 11/19] Add support for unreadable SpO2 samples on
Viatom/Wellue oximeters.
These occur when SpO2 drops below 61% but pulse rate is still valid.
---
Htmldocs/release_notes.html | 1 +
.../SleepLib/loader_plugins/viatom_loader.cpp | 64 ++++++++++---------
2 files changed, 34 insertions(+), 31 deletions(-)
diff --git a/Htmldocs/release_notes.html b/Htmldocs/release_notes.html
index cb1c8549..29c63e06 100644
--- a/Htmldocs/release_notes.html
+++ b/Htmldocs/release_notes.html
@@ -72,6 +72,7 @@
[fix] Fix ocasional misordering of indexes on the Daily page.
[fix] Add Unclassified Apneas to the Statistics page.
[fix] Resolve empty CPAP data card zips on macOS Big Sur.
+ [fix] Add support for unreadably low SpO2 samples on Viatom/Wellue oximeters.
Changes and fixes in OSCAR v1.2.0
diff --git a/oscar/SleepLib/loader_plugins/viatom_loader.cpp b/oscar/SleepLib/loader_plugins/viatom_loader.cpp
index 3a2a2336..62567260 100644
--- a/oscar/SleepLib/loader_plugins/viatom_loader.cpp
+++ b/oscar/SleepLib/loader_plugins/viatom_loader.cpp
@@ -22,6 +22,16 @@
#include "viatom_loader.h"
#include "SleepLib/machine.h"
+// TODO: Merge this with PRS1 macros and generalize for all loaders.
+#define SESSIONID m_session->session()
+#define UNEXPECTED_VALUE(SRC, VALS) { \
+ QString message = QString("%1:%2: %3 = %4 != %5").arg(__func__).arg(__LINE__).arg(#SRC).arg(SRC).arg(VALS); \
+ qWarning() << SESSIONID << message; \
+ s_unexpectedMessages += message; \
+ }
+#define CHECK_VALUE(SRC, VAL) if ((SRC) != (VAL)) UNEXPECTED_VALUE(SRC, VAL)
+#define CHECK_VALUES(SRC, VAL1, VAL2) if ((SRC) != (VAL1) && (SRC) != (VAL2)) UNEXPECTED_VALUE(SRC, #VAL1 " or " #VAL2)
+// for more than 2 values, just write the test manually and use UNEXPECTED_VALUE if it fails
static QSet s_unexpectedMessages;
bool
@@ -157,8 +167,27 @@ Session* ViatomLoader::ParseFile(const QString & filename, bool *existing)
EndEventList(OXI_Pulse, time_ms);
EndEventList(OXI_SPO2, time_ms);
} else {
+ // Viatom advertises a range of 30 - 250 bpm.
+ if (rec.hr < 30 || rec.hr > 250) {
+ UNEXPECTED_VALUE(rec.hr, "30-250");
+ }
AddEvent(OXI_Pulse, time_ms, rec.hr);
- AddEvent(OXI_SPO2, time_ms, rec.spo2);
+
+ if (rec.spo2 == 0xFF) {
+ // When the readings fall below 61%, Viatom devices record 0xFF for SpO2.
+ // The official software discards these readings.
+ // TODO: Consider whether to import these as 60% since they reflect hypoxia.
+ EndEventList(OXI_SPO2, time_ms);
+ //qDebug() << "<61% at" << QDateTime::fromMSecsSinceEpoch(time_ms);
+ } else {
+ // Viatom advertises (and graphs) a range of 70% - 99%, but apparently records down to 61%.
+ // The official software graphs 61%-70% as 70%.
+ // TODO: Consider whether we should import 61%-70% as 70% to match the official reports.
+ if (rec.spo2 < 61 || rec.spo2 > 99) {
+ UNEXPECTED_VALUE(rec.spo2, "61-99%");
+ }
+ AddEvent(OXI_SPO2, time_ms, rec.spo2);
+ }
}
AddEvent(POS_Movement, time_ms, rec.motion);
time_ms += m_step;
@@ -227,35 +256,8 @@ ViatomLoader::Register()
// ===============================================================================================
-/*
-static QString ts(qint64 msecs)
-{
- // TODO: make this UTC so that tests don't vary by where they're run
- return QDateTime::fromMSecsSinceEpoch(msecs).toString(Qt::ISODate);
-}
-
-static QString dur(qint64 msecs)
-{
- qint64 s = msecs / 1000L;
- int h = s / 3600; s -= h * 3600;
- int m = s / 60; s -= m * 60;
- return QString("%1:%2:%3")
- .arg(h, 2, 10, QChar('0'))
- .arg(m, 2, 10, QChar('0'))
- .arg(s, 2, 10, QChar('0'));
-}
-*/
-
-// TODO: Merge this with PRS1 macros and generalize for all loaders.
-#define UNEXPECTED_VALUE(SRC, VALS) { \
- QString message = QString("%1:%2: %3 = %4 != %5").arg(__func__).arg(__LINE__).arg(#SRC).arg(SRC).arg(VALS); \
- qWarning() << this->m_sessionid << message; \
- s_unexpectedMessages += message; \
- }
-#define CHECK_VALUE(SRC, VAL) if ((SRC) != (VAL)) UNEXPECTED_VALUE(SRC, VAL)
-#define CHECK_VALUES(SRC, VAL1, VAL2) if ((SRC) != (VAL1) && (SRC) != (VAL2)) UNEXPECTED_VALUE(SRC, #VAL1 " or " #VAL2)
-// for more than 2 values, just write the test manually and use UNEXPECTED_VALUE if it fails
-
+#undef SESSIONID
+#define SESSIONID this->m_sessionid
ViatomFile::ViatomFile(QFile & file) : m_file(file)
{
@@ -341,7 +343,7 @@ bool ViatomFile::ParseHeader()
CHECK_VALUE(header[27], 0);
CHECK_VALUE(header[28], 0);
CHECK_VALUE(header[29], 0);
- CHECK_VALUE(header[30], 0);
+ //CHECK_VALUE(header[30], 0); // average pulse rate (when nonzero)
CHECK_VALUE(header[31], 0);
CHECK_VALUE(header[32], 0);
CHECK_VALUE(header[33], 0);
From 9134093af66de9bc5e03c99c9dad74022a74ef99 Mon Sep 17 00:00:00 2001
From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com>
Date: Mon, 20 Sep 2021 13:43:26 -0400
Subject: [PATCH 12/19] Update Viatom warnings based on additional test
samples.
---
oscar/SleepLib/loader_plugins/viatom_loader.cpp | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/oscar/SleepLib/loader_plugins/viatom_loader.cpp b/oscar/SleepLib/loader_plugins/viatom_loader.cpp
index 62567260..b2c768b7 100644
--- a/oscar/SleepLib/loader_plugins/viatom_loader.cpp
+++ b/oscar/SleepLib/loader_plugins/viatom_loader.cpp
@@ -290,6 +290,7 @@ bool ViatomFile::ParseHeader()
return false;
break;
}
+ CHECK_VALUE(sig, 3); // We have only a single sample of 5, without a corresponding PDF. We need more samples.
if ((year < 2015 || year > 2059) || (month < 1 || month > 12) || (day < 1 || day > 31) ||
(hour > 23) || (min > 59) || (sec > 59)) {
@@ -339,7 +340,7 @@ bool ViatomFile::ParseHeader()
//int time_under_90pct = header[22] | (header[23] << 8); // in seconds
//int events_under_90pct = header[24]; // number of distinct events
//float o2_score = header[25] * 0.1;
- CHECK_VALUE(header[26], 0);
+ CHECK_VALUES(header[26], 0, 4); // 4 has been seen only once
CHECK_VALUE(header[27], 0);
CHECK_VALUE(header[28], 0);
CHECK_VALUE(header[29], 0);
From d95c77ee40853265645d81ea7d5cdc6fc87d0671 Mon Sep 17 00:00:00 2001
From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com>
Date: Mon, 20 Sep 2021 14:08:49 -0400
Subject: [PATCH 13/19] Add support for yet another Viatom/Wellue filename
variation.
Apparently the Android app is now exporting files with timestamps of the form
"YYYY-MM-DD hh:mm:ss". It turns out that ":" is not a valid character on
macOS, so Mac users using version 2.72 of the Android app will need to rename
their files to end with "YYYYMMDDhhmmss" in order to select and import them.
Windows and Linux won't.
Fortunately the intersection of Android users and Mac users is relatively small.
And this may be reverted in a future version of the Android app.
Also clean up some competing release notes edits.
---
Htmldocs/release_notes.html | 6 +++---
oscar/SleepLib/loader_plugins/viatom_loader.cpp | 12 ++++++++++--
2 files changed, 13 insertions(+), 5 deletions(-)
diff --git a/Htmldocs/release_notes.html b/Htmldocs/release_notes.html
index 29c63e06..bebc84cd 100644
--- a/Htmldocs/release_notes.html
+++ b/Htmldocs/release_notes.html
@@ -15,9 +15,11 @@
Portions of OSCAR are © 2019-2021 by
The OSCAR Team
+ - [new] Add support for additional Viatom/Wellue filename conventions.
+ - [new] Add support for unreadably low SpO2 samples on Viatom/Wellue oximeters.
- [fix] Check for Updates no longer shows test versions to release users.
- [fix] Rx pressures shown correctly in Profile dialog.
- - [fix] Allow zipping to continue when errors occur (MacOS).
+ - [fix] Resolve empty CPAP data card zips on macOS Big Sur.
- [fix] Always prompt for SD card location when zipping SD card.
- [fix] Add script to identify platform in mkDistDeb.sh (Linux building).
- ---------- Fixes to previous fixes (do not include in final release notes) ---------
@@ -71,8 +73,6 @@
- [fix] Ignore old sessions should not impact existing data.
- [fix] Fix ocasional misordering of indexes on the Daily page.
- [fix] Add Unclassified Apneas to the Statistics page.
- - [fix] Resolve empty CPAP data card zips on macOS Big Sur.
- - [fix] Add support for unreadably low SpO2 samples on Viatom/Wellue oximeters.
Changes and fixes in OSCAR v1.2.0
diff --git a/oscar/SleepLib/loader_plugins/viatom_loader.cpp b/oscar/SleepLib/loader_plugins/viatom_loader.cpp
index b2c768b7..d4c02d0b 100644
--- a/oscar/SleepLib/loader_plugins/viatom_loader.cpp
+++ b/oscar/SleepLib/loader_plugins/viatom_loader.cpp
@@ -237,7 +237,11 @@ void ViatomLoader::EndEventList(ChannelID channel, qint64 /*t*/)
QStringList ViatomLoader::getNameFilter()
{
// Sometimes the files have a SleepU_ or O2Ring_ prefix.
- return QStringList("*20[0-5][0-9][01][0-9][0-3][0-9][012][0-9][0-5][0-9][0-5][0-9]");
+ // Sometimes they have punctuation in the timestamp.
+ // Note that ":" is not allowed on macOS, so Mac users will need to rename their files in order to select and import them.
+ return QStringList({"*20[0-5][0-9][01][0-9][0-3][0-9][012][0-9][0-5][0-9][0-5][0-9]",
+ "*20[0-5][0-9]-[01][0-9]-[0-3][0-9] [012][0-9]:[0-5][0-9]:[0-5][0-9]"
+ });
}
static bool viatom_initialized = false;
@@ -309,7 +313,11 @@ bool ViatomFile::ParseHeader()
QDateTime data_timestamp = QDateTime(QDate(year, month, day), QTime(hour, min, sec));
QString date_string = QFileInfo(m_file).fileName().section("_", -1); // Strip any SleepU_ etc. prefix.
- QDateTime filename_timestamp = QDateTime::fromString(date_string, "yyyyMMddHHmmss");
+ QString format_string = "yyyyMMddHHmmss";
+ if (date_string.contains(":")) {
+ format_string = "yyyy-MM-dd HH:mm:ss";
+ }
+ QDateTime filename_timestamp = QDateTime::fromString(date_string, format_string);
if (filename_timestamp.isValid()) {
if (filename_timestamp != data_timestamp) {
// TODO: Once there's a better/easier way to adjust session times within OSCAR, we can remove the below.
From 96f3b6c65830eb9bf5341bac3c3670c735e1512e Mon Sep 17 00:00:00 2001
From: Phil Olynyk
Date: Wed, 22 Sep 2021 13:52:02 -0400
Subject: [PATCH 14/19] Clean up AVAPS pressure settings
---
oscar/SleepLib/day.cpp | 36 +++++++++++++++---
.../SleepLib/loader_plugins/resmed_loader.cpp | 37 +++++++++++--------
2 files changed, 51 insertions(+), 22 deletions(-)
diff --git a/oscar/SleepLib/day.cpp b/oscar/SleepLib/day.cpp
index 740f7899..ab26cd48 100644
--- a/oscar/SleepLib/day.cpp
+++ b/oscar/SleepLib/day.cpp
@@ -848,11 +848,13 @@ ChannelID Day::getPressureChannelID() {
for (auto & preferredID : preferredIDs) {
// If preferred channel has data, return it
if (channelHasData(preferredID)) {
- //qDebug() << QString("Found pressure channel 0x%1").arg(preferredID, 4, 16, QChar('0'));
+// if ( cpapmode == MODE_AVAPS )
+// qDebug() << QString("Found pressure channel 0x%1").arg(preferredID, 4, 16, QChar('0'));
return preferredID;
}
}
+ qDebug() << "No pressure channel for " << getCPAPModeStr();
return NoChannel;
}
@@ -1582,13 +1584,21 @@ QString Day::getPressureSettings()
QString units = schema::channel[CPAP_Pressure].units();
if (mode == MODE_CPAP) {
- return QObject::tr("Fixed %1 (%2)").arg(validPressure(settings_min(CPAP_Pressure))).arg(units);
+ return QObject::tr("Fixed %1 (%2)").arg(validPressure(settings_min(CPAP_Pressure))).
+ arg(units);
} else if (mode == MODE_APAP) {
- return QObject::tr("Min %1 Max %2 (%3)").arg(validPressure(settings_min(CPAP_PressureMin))).arg(validPressure(settings_max(CPAP_PressureMax))).arg(units);
+ return QObject::tr("Min %1 Max %2 (%3)").arg(validPressure(settings_min(CPAP_PressureMin))).
+ arg(validPressure(settings_max(CPAP_PressureMax))).
+ arg(units);
} else if (mode == MODE_BILEVEL_FIXED ) {
- return QObject::tr("EPAP %1 IPAP %2 (%3)").arg(validPressure(settings_min(CPAP_EPAP))).arg(validPressure(settings_max(CPAP_IPAP))).arg(units);
+ return QObject::tr("EPAP %1 IPAP %2 (%3)").arg(validPressure(settings_min(CPAP_EPAP))).
+ arg(validPressure(settings_max(CPAP_IPAP))).
+ arg(units);
} else if (mode == MODE_BILEVEL_AUTO_FIXED_PS) {
- return QObject::tr("PS %1 over %2-%3 (%4)").arg(validPressure(settings_max(CPAP_PS))).arg(validPressure(settings_min(CPAP_EPAPLo))).arg(validPressure(settings_max(CPAP_IPAPHi))).arg(units);
+ return QObject::tr("PS %1 over %2-%3 (%4)").arg(validPressure(settings_max(CPAP_PS))).
+ arg(validPressure(settings_min(CPAP_EPAPLo))).
+ arg(validPressure(settings_max(CPAP_IPAPHi))).
+ arg(units);
} else if (mode == MODE_BILEVEL_AUTO_VARIABLE_PS) {
return QObject::tr("Min EPAP %1 Max IPAP %2 PS %3-%4 (%5)").arg(validPressure(settings_min(CPAP_EPAPLo))).
arg(validPressure(settings_max(CPAP_IPAPHi))).
@@ -1606,11 +1616,25 @@ QString Day::getPressureSettings()
arg(validPressure(settings_min(CPAP_PSMax))).
arg(units);
} else if (mode == MODE_AVAPS) {
- return QObject::tr("EPAP %1 IPAP %2-%3 (%4)").
+// qDebug() << "AVAPS: EPAP" << settings_min(CPAP_EPAP) << "IPAP min" << settings_max(CPAP_IPAPLo) <<
+// "IPAP max" << settings_max(CPAP_IPAPHi);
+ QString retStr;
+ if (settings_min(CPAP_EPAPLo) == settings_max(CPAP_EPAPHi))
+ retStr = QObject::tr("EPAP %1 IPAP %2-%3 (%4)").
arg(validPressure(settings_min(CPAP_EPAP))).
arg(validPressure(settings_max(CPAP_IPAPLo))).
arg(validPressure(settings_max(CPAP_IPAPHi))).
arg(units);
+ else
+ retStr = QObject::tr("EPAP %1-%2 IPAP %3-%4 (%5)").
+ arg(validPressure(settings_min(CPAP_EPAPLo))).
+ arg(validPressure(settings_min(CPAP_EPAPHi))).
+ arg(validPressure(settings_max(CPAP_IPAPLo))).
+ arg(validPressure(settings_max(CPAP_IPAPHi))).
+ arg(units);
+
+// qDebug() << "AVAPS mode:" << retStr;
+ return retStr;
}
return STR_TR_Unknown;
diff --git a/oscar/SleepLib/loader_plugins/resmed_loader.cpp b/oscar/SleepLib/loader_plugins/resmed_loader.cpp
index a34e2a66..34c81e47 100644
--- a/oscar/SleepLib/loader_plugins/resmed_loader.cpp
+++ b/oscar/SleepLib/loader_plugins/resmed_loader.cpp
@@ -1527,13 +1527,16 @@ bool ResmedLoader::ProcessSTRfiles(Machine *mach, QMap & STRmap,
if ((sig = str.lookupLabel("S.i.MaxPS"))) {
R.max_ps = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset;
}
- if ( R.epap >= 0 ) {
+ if ( (R.epap >= 0) && (R.max_epap == R.epap) ) {
R.max_ipap = R.epap + R.max_ps;
R.min_ipap = R.epap + R.min_ps;
} else {
R.max_ipap = R.max_epap + R.max_ps;
R.min_ipap = R.min_epap + R.min_ps;
}
+// qDebug() << "AVAPS mode; Ramp" << R.ramp_pressure << "Min EPAP" << R.min_epap <<
+// "Max EPAP" << R.max_epap << "Min PS" << R.min_ps << "Max PS" << R.max_ps <<
+// "Min IPAP" << R.min_ipap << "Max_IPAP" << R.max_ipap;
}
if (R.mode == MODE_ASV) {
if ((sig = str.lookupLabel("S.AV.StartPress"))) {
@@ -2280,20 +2283,21 @@ void StoreSettings(Session * sess, STRRecord & R)
if (R.min_ipap >= 0) sess->settings[CPAP_IPAPLo] = R.min_ipap;
if (R.min_ps >= 0) sess->settings[CPAP_PSMin] = R.min_ps;
if (R.max_ps >= 0) sess->settings[CPAP_PSMax] = R.max_ps;
+ } else {
+// qDebug() << "Setting session pressures for random mode";
+ if (R.set_pressure >= 0) sess->settings[CPAP_Pressure] = R.set_pressure;
+ if (R.min_pressure >= 0) sess->settings[CPAP_PressureMin] = R.min_pressure;
+ if (R.max_pressure >= 0) sess->settings[CPAP_PressureMax] = R.max_pressure;
+ if (R.max_epap >= 0) sess->settings[CPAP_EPAPHi] = R.max_epap;
+ if (R.min_epap >= 0) sess->settings[CPAP_EPAPLo] = R.min_epap;
+ if (R.max_ipap >= 0) sess->settings[CPAP_IPAPHi] = R.max_ipap;
+ if (R.min_ipap >= 0) sess->settings[CPAP_IPAPLo] = R.min_ipap;
+ if (R.min_ps >= 0) sess->settings[CPAP_PSMin] = R.min_ps;
+ if (R.max_ps >= 0) sess->settings[CPAP_PSMax] = R.max_ps;
+ if (R.ps >= 0) sess->settings[CPAP_PS] = R.ps;
+ if (R.epap >= 0) sess->settings[CPAP_EPAP] = R.epap;
+ if (R.ipap >= 0) sess->settings[CPAP_IPAP] = R.ipap;
}
- } else {
- if (R.set_pressure >= 0) sess->settings[CPAP_Pressure] = R.set_pressure;
- if (R.min_pressure >= 0) sess->settings[CPAP_PressureMin] = R.min_pressure;
- if (R.max_pressure >= 0) sess->settings[CPAP_PressureMax] = R.max_pressure;
- if (R.max_epap >= 0) sess->settings[CPAP_EPAPHi] = R.max_epap;
- if (R.min_epap >= 0) sess->settings[CPAP_EPAPLo] = R.min_epap;
- if (R.max_ipap >= 0) sess->settings[CPAP_IPAPHi] = R.max_ipap;
- if (R.min_ipap >= 0) sess->settings[CPAP_IPAPLo] = R.min_ipap;
- if (R.min_ps >= 0) sess->settings[CPAP_PSMin] = R.min_ps;
- if (R.max_ps >= 0) sess->settings[CPAP_PSMax] = R.max_ps;
- if (R.ps >= 0) sess->settings[CPAP_PS] = R.ps;
- if (R.epap >= 0) sess->settings[CPAP_EPAP] = R.epap;
- if (R.ipap >= 0) sess->settings[CPAP_IPAP] = R.ipap;
}
if (R.epr >= 0) {
@@ -3095,7 +3099,7 @@ bool ResmedLoader::LoadPLD(Session *sess, const QString & path)
time.start();
#endif
QString filename = path.section(-2, -1);
- qDebug() << "LoadPLD opening" << filename.section("/", -2, -1);
+// qDebug() << "LoadPLD opening" << filename.section("/", -2, -1);
ResMedEDFInfo edf;
if ( ! edf.Open(path) ) {
qDebug() << "LoadPLD failed to open" << filename.section("/", -2, -1);
@@ -3461,7 +3465,8 @@ EventList * buildEventList( EventStoreType est, EventDataType t_min, EventDataTy
el->AddEvent(tt, est);
} else {
- qDebug() << "Value:" << tmp << "Out of range: t_min:" << t_min << "t_max:" << t_max << "EL count:" << el->count();
+ if ( tmp > 0 )
+ qDebug() << "Value:" << tmp << "Out of range: t_min:" << t_min << "t_max:" << t_max << "EL count:" << el->count();
// Out of bounds value, start a new eventlist
// But first drop a closing value that repeats the last one
el->AddEvent(tt, el->raw(el->count() - 1));
From 3bae7f21021f9e515b6feb0cb98a7ac928a3d980 Mon Sep 17 00:00:00 2001
From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com>
Date: Wed, 22 Sep 2021 16:22:59 -0400
Subject: [PATCH 15/19] Fix clang compilation regression in 054b633e.
---
oscar/SleepLib/loader_plugins/resmed_loader.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/oscar/SleepLib/loader_plugins/resmed_loader.cpp b/oscar/SleepLib/loader_plugins/resmed_loader.cpp
index 34c81e47..838c1f95 100644
--- a/oscar/SleepLib/loader_plugins/resmed_loader.cpp
+++ b/oscar/SleepLib/loader_plugins/resmed_loader.cpp
@@ -1374,7 +1374,7 @@ bool ResmedLoader::ProcessSTRfiles(Machine *mach, QMap & STRmap,
} else if (mod >= 3) { // mod 3 == vpap s fixed pressure (EPAP, IPAP, No PS)
// 4,5 are S/T types...
mode = MODE_BILEVEL_FIXED;
- } else if ((mod == 2)) {
+ } else if (mod == 2) {
if ( AS_eleven )
mode = MODE_APAP;
else
From cc81c75ea06d15ffe5ad4cf0b9a6ed787f265ea0 Mon Sep 17 00:00:00 2001
From: sawinglogz <3787776-sawinglogz@users.noreply.gitlab.com>
Date: Sun, 26 Sep 2021 12:25:49 -0400
Subject: [PATCH 16/19] Update Viatom warnings based on further
experimentation.
Thanks for Ratchick for figuring out the mystery header value!
---
oscar/SleepLib/loader_plugins/viatom_loader.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/oscar/SleepLib/loader_plugins/viatom_loader.cpp b/oscar/SleepLib/loader_plugins/viatom_loader.cpp
index d4c02d0b..c2388e4a 100644
--- a/oscar/SleepLib/loader_plugins/viatom_loader.cpp
+++ b/oscar/SleepLib/loader_plugins/viatom_loader.cpp
@@ -348,7 +348,7 @@ bool ViatomFile::ParseHeader()
//int time_under_90pct = header[22] | (header[23] << 8); // in seconds
//int events_under_90pct = header[24]; // number of distinct events
//float o2_score = header[25] * 0.1;
- CHECK_VALUES(header[26], 0, 4); // 4 has been seen only once
+ //CHECK_VALUES(header[26], 0, 4); // number of steps taken (when nonzero, only reported by some models)
CHECK_VALUE(header[27], 0);
CHECK_VALUE(header[28], 0);
CHECK_VALUE(header[29], 0);
From 9f4cd79b3d32c8cd467dbae7a00b93c231e50234 Mon Sep 17 00:00:00 2001
From: Guy Scharf
Date: Sun, 26 Sep 2021 10:42:20 -0700
Subject: [PATCH 17/19] SleepStyle loader now shows high resolution leak rate
Loader calculates leak rate from mask pressure and does usual linear
interpolation for leak rate for the pressure Calculation of CPAP_Leak is
now done in the loader rather than in calcs.cpp
---
.../loader_plugins/sleepstyle_loader.cpp | 83 ++++++++++++++++++-
.../loader_plugins/sleepstyle_loader.h | 2 +
2 files changed, 82 insertions(+), 3 deletions(-)
diff --git a/oscar/SleepLib/loader_plugins/sleepstyle_loader.cpp b/oscar/SleepLib/loader_plugins/sleepstyle_loader.cpp
index f18b1879..394a86be 100644
--- a/oscar/SleepLib/loader_plugins/sleepstyle_loader.cpp
+++ b/oscar/SleepLib/loader_plugins/sleepstyle_loader.cpp
@@ -225,6 +225,10 @@ int SleepStyleLoader::OpenMachine(Machine *mach, const QString & path, const QSt
backupData(mach, path);
+ calc_leaks = p_profile->cpap->calculateUnintentionalLeaks();
+ lpm4 = p_profile->cpap->custom4cmH2OLeaks();
+ lpm20 = p_profile->cpap->custom20cmH2OLeaks();
+
qDebug() << "Opening F&P SleepStyle" << ssPath;
dir.setFilter(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Files | QDir::Hidden | QDir::NoSymLinks);
@@ -451,23 +455,32 @@ bool SleepStyleLoader::OpenRealTime(Machine *mach, const QString & fname, const
sess->really_set_first(edf.startdate);
qint64 duration = edf.GetNumDataRecords() * edf.GetDurationMillis();
+ qDebug() << "SS EDF millis" << edf.GetDurationMillis() << "num recs" << edf.GetNumDataRecords();
sess->updateLast(edf.startdate + duration);
// Find the leak signal and data
long leakrecs = 0;
EDFSignal leakSignal;
+ EDFSignal maskSignal;
+ long maskRecs;
for (auto & esleak : edf.edfsignals) {
leakrecs = esleak.sampleCnt * edf.GetNumDataRecords();
if (leakrecs < 0)
continue;
if (esleak.label == "Leak") {
leakSignal = esleak;
+ break;
}
}
// Walk through all signals, ignoring leaks
for (auto & es : edf.edfsignals) {
long recs = es.sampleCnt * edf.GetNumDataRecords();
+#ifdef DEBUGSS
+ qDebug() << "SS EDF" << es.label << "count" << es.sampleCnt << "gain" << es.gain << "offset" << es.offset
+ << "dim" << es.physical_dimension << "phys min" << es.physical_minimum << "max" << es.physical_maximum
+ << "dig min" << es.digital_minimum << "max" << es.digital_maximum;
+#endif
if (recs < 0)
continue;
ChannelID code = 0;
@@ -492,6 +505,59 @@ bool SleepStyleLoader::OpenRealTime(Machine *mach, const QString & fname, const
} else if (es.label == "Pressure") {
code = CPAP_MaskPressure;
+ maskRecs = es.sampleCnt * edf.GetNumDataRecords();
+ maskSignal = es;
+ float lpm = lpm20 - lpm4;
+ float ppm = lpm / 16.0;
+ if (maskRecs != leakrecs) {
+ qWarning() << "SS ORT maskRecs" << maskRecs << "!= leakrecs" << leakrecs;
+ } else {
+ qint16 * leakarray = new qint16 [maskRecs];
+
+ for (int i = 0; i < maskRecs; i++) {
+
+ // Extract IPAP from mask pressure, which is a combination of IPAP and EPAP values
+ // get maximum mask pressure over next several data points to make best guess at IPAP
+ float mp = es.dataArray[i];
+ for (int j = 1; j < 9; j++)
+ if (i < maskRecs-j)
+ mp = fmaxf(mp, es.dataArray[i+j]);
+
+ float press = mp * es.gain - 4.0; // Convert pressure to cmH2O and get difference from low end of adjustment curve
+
+ // Calculate expected (intentional) leak in l/m
+ float expLeak = press * ppm + lpm4;
+ qint16 unintLeak = leakSignal.dataArray[i] - (qint16)(expLeak / es.gain);
+ if (unintLeak < 0)
+ unintLeak = 0;
+
+ leakarray[i] = unintLeak;
+ }
+
+ ChannelID leakcode = CPAP_Leak;
+ double rate = double(duration) / double(recs);
+ EventList *a = sess->AddEventList(leakcode, EVL_Waveform, es.gain, es.offset, 0, 0, rate);
+ a->setDimension(es.physical_dimension);
+ a->AddWaveform(edf.startdate, leakarray, recs, duration);
+ EventDataType min = a->Min();
+ EventDataType max = a->Max();
+ /***
+ // Cap to physical dimensions, because there can be ram glitches/whatever that throw really big outliers.
+ if (min < es.physical_minimum)
+ min = es.physical_minimum;
+ if (max > es.physical_maximum)
+ max = es.physical_maximum;
+ ***/
+ sess->updateMin(leakcode, min);
+ sess->updateMax(leakcode, max);
+ sess->setPhysMin(leakcode, es.physical_minimum);
+ sess->setPhysMax(leakcode, es.physical_maximum);
+
+ delete [] leakarray;
+ }
+
+ } else if (es.label == "Leak") {
+ code = CPAP_LeakTotal;
} else
continue;
@@ -501,7 +567,9 @@ bool SleepStyleLoader::OpenRealTime(Machine *mach, const QString & fname, const
EventList *a = sess->AddEventList(code, EVL_Waveform, es.gain, es.offset, 0, 0, rate);
a->setDimension(es.physical_dimension);
a->AddWaveform(edf.startdate, es.dataArray, recs, duration);
-
+#ifdef DEBUGSS
+ qDebug() << "SS EDF recs" << recs << "duration" << duration << "rate" << rate;
+#endif
EventDataType min = a->Min();
EventDataType max = a->Max();
@@ -826,6 +894,7 @@ bool SleepStyleLoader::OpenDetail(Machine *mach, const QString & filename)
qint64 ti;
quint8 pressure, leak, a1, a2, a3, a4, a5, a6;
+ Q_UNUSED(leak)
// quint8 sa1, sa2; // The two sense awake bits per 2 minutes
SessionID sessid;
Session *sess;
@@ -836,8 +905,9 @@ bool SleepStyleLoader::OpenDetail(Machine *mach, const QString & filename)
sess = Sessions[sessid];
ti = qint64(sessid) * 1000L;
sess->really_set_first(ti);
+ long PRSessCount = 0;
- EventList *LK = sess->AddEventList(CPAP_LeakTotal, EVL_Event, 1);
+//fastleak EventList *LK = sess->AddEventList(CPAP_LeakTotal, EVL_Event, 1);
EventList *PR = sess->AddEventList(CPAP_Pressure, EVL_Event, 0.1F);
EventList *A = sess->AddEventList(CPAP_AllApnea, EVL_Event);
EventList *H = sess->AddEventList(CPAP_Hypopnea, EVL_Event);
@@ -859,10 +929,14 @@ bool SleepStyleLoader::OpenDetail(Machine *mach, const QString & filename)
for (int j = 0; j < 3; ++j) {
pressure = data[idx];
PR->AddEvent(ti+120000, pressure);
+ PRSessCount++;
+#ifdef DEBUGSS
leak = data[idx + 1];
+#endif
+/* fastleak
LK->AddEvent(ti+120000, leak);
-
+*/
// Comments below from MW. Appear not to be accurate
a1 = data[idx + 2]; // [0..5] Obstructive flag, [6..7] Unknown
a2 = data[idx + 3]; // [0..5] Hypopnea, [6..7] Unknown
@@ -904,6 +978,9 @@ bool SleepStyleLoader::OpenDetail(Machine *mach, const QString & filename)
}
}
+#ifdef DEBUGSS
+ qDebug() << "SS DET pressure events" << PR->count() << "prSessVount" << PRSessCount << "beginning" << QDateTime::fromSecsSinceEpoch(ti/1000).toString("MM/dd/yyyy hh:mm:ss");
+#endif
// Update indexes, process waveform and perform flagging
sess->setSummaryOnly(false);
sess->UpdateSummaries();
diff --git a/oscar/SleepLib/loader_plugins/sleepstyle_loader.h b/oscar/SleepLib/loader_plugins/sleepstyle_loader.h
index 58e1daa0..7d0cc327 100644
--- a/oscar/SleepLib/loader_plugins/sleepstyle_loader.h
+++ b/oscar/SleepLib/loader_plugins/sleepstyle_loader.h
@@ -112,6 +112,8 @@ class SleepStyleLoader : public CPAPLoader
// QString serial; // Serial number
bool rebuild_from_backups = false;
bool create_backups = true;
+ bool calc_leaks = true;
+ float lpm4, lpm20; // Leak per minute at 4 and 20 cmH20
unsigned char *m_buffer;
};
From 03238360c28759ea329f459c18c55ea104561f20 Mon Sep 17 00:00:00 2001
From: Guy Scharf
Date: Sun, 26 Sep 2021 10:44:44 -0700
Subject: [PATCH 18/19] Update VERSION to 1.3.0-beta.3. Update Release Notes.
---
Htmldocs/release_notes.html | 5 ++++-
oscar/VERSION | 2 +-
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/Htmldocs/release_notes.html b/Htmldocs/release_notes.html
index bebc84cd..6ff6cc83 100644
--- a/Htmldocs/release_notes.html
+++ b/Htmldocs/release_notes.html
@@ -11,12 +11,13 @@
For other languages, go to:
http://www.apneaboard.com/wiki/index.php/OSCAR_Release_Notes
- Changes and fixes in OSCAR v1.3.0 Beta ***
+ Changes and fixes in OSCAR v1.3.0 Beta 3
Portions of OSCAR are © 2019-2021 by
The OSCAR Team
- [new] Add support for additional Viatom/Wellue filename conventions.
- [new] Add support for unreadably low SpO2 samples on Viatom/Wellue oximeters.
+ - [fix] Fix AVAPS pressure settings.
- [fix] Check for Updates no longer shows test versions to release users.
- [fix] Rx pressures shown correctly in Profile dialog.
- [fix] Resolve empty CPAP data card zips on macOS Big Sur.
@@ -26,6 +27,8 @@
- [ffx] Weight, BMI, and Zombie are on Overview, not Statistics page.
- [ffx] Overview page updated correctly when Weight or Zombie values are changed.
- [ffx] Various corrections to language files.
+ - [ffx] SleepStyle loader now has higher resolution for leak rate (1 per second instead of 1 per 2 minutes).
+ - [ffx] Correct problems with settings display for AirSense 11.
Changes and fixes in OSCAR v1.3.0 Beta 2/b>
diff --git a/oscar/VERSION b/oscar/VERSION
index 8c9f212c..5a3bfe0b 100644
--- a/oscar/VERSION
+++ b/oscar/VERSION
@@ -1,4 +1,4 @@
// Update the string below to set OSCAR's version and release status.
// See https://semver.org/spec/v2.0.0.html for details on format.
-#define VERSION "1.3.0-beta.2"
+#define VERSION "1.3.0-beta.3"
From 7bffd782037207a9232fd4be025b7ac9948d082f Mon Sep 17 00:00:00 2001
From: Guy Scharf
Date: Tue, 5 Oct 2021 20:18:19 -0700
Subject: [PATCH 19/19] Code cleanup in SleepStyle loader Minor improvement
to mask pressure averaging used in unintentional leak calculations. Fix Y2K
calculation in unused code in sleepstle_EDFInfo.cpp Update Release Notes
---
Htmldocs/release_notes.html | 9 +++++++++
.../loader_plugins/sleepstyle_EDFinfo.cpp | 3 +++
.../SleepLib/loader_plugins/sleepstyle_loader.cpp | 15 ++++++++++-----
3 files changed, 22 insertions(+), 5 deletions(-)
diff --git a/Htmldocs/release_notes.html b/Htmldocs/release_notes.html
index 6ff6cc83..0d52aeeb 100644
--- a/Htmldocs/release_notes.html
+++ b/Htmldocs/release_notes.html
@@ -11,6 +11,15 @@
For other languages, go to:
http://www.apneaboard.com/wiki/index.php/OSCAR_Release_Notes
+ Changes and fixes in OSCAR v1.3.0 xxx.x
+
Portions of OSCAR are © 2019-2021 by
+ The OSCAR Team
+
+ - ---------- Fixes to previous fixes (do not include in final release notes) ---------
+ - [ffx] Improvement to pressure averaging in SleepStyle unintentional leak calculation.
+ - [ffx] Fix Y2K issue in unused SleepStyle EDF handling code.
+
+
Changes and fixes in OSCAR v1.3.0 Beta 3
Portions of OSCAR are © 2019-2021 by
The OSCAR Team
diff --git a/oscar/SleepLib/loader_plugins/sleepstyle_EDFinfo.cpp b/oscar/SleepLib/loader_plugins/sleepstyle_EDFinfo.cpp
index 39721ed4..92b8b4c5 100644
--- a/oscar/SleepLib/loader_plugins/sleepstyle_EDFinfo.cpp
+++ b/oscar/SleepLib/loader_plugins/sleepstyle_EDFinfo.cpp
@@ -99,6 +99,9 @@ QDateTime SleepStyleEDFInfo::getStartDT( QString dateTimeStr )
// dateStr = dateTimeStr.left(8);
// timeStr = dateTimeStr.right(8);
qDate = QDate::fromString(dateTimeStr.left(8), "dd.MM.yy");
+ if (qDate.year() < 2000) {
+ qDate = qDate.addYears(100);
+ }
qTime = QTime::fromString(dateTimeStr.right(8), "HH.mm.ss");
return QDateTime(qDate, qTime, Qt::UTC);
}
diff --git a/oscar/SleepLib/loader_plugins/sleepstyle_loader.cpp b/oscar/SleepLib/loader_plugins/sleepstyle_loader.cpp
index 394a86be..01be2fe5 100644
--- a/oscar/SleepLib/loader_plugins/sleepstyle_loader.cpp
+++ b/oscar/SleepLib/loader_plugins/sleepstyle_loader.cpp
@@ -504,7 +504,7 @@ bool SleepStyleLoader::OpenRealTime(Machine *mach, const QString & fname, const
}
} else if (es.label == "Pressure") {
- code = CPAP_MaskPressure;
+ // First compute CPAP_Leak data
maskRecs = es.sampleCnt * edf.GetNumDataRecords();
maskSignal = es;
float lpm = lpm20 - lpm4;
@@ -517,11 +517,13 @@ bool SleepStyleLoader::OpenRealTime(Machine *mach, const QString & fname, const
for (int i = 0; i < maskRecs; i++) {
// Extract IPAP from mask pressure, which is a combination of IPAP and EPAP values
- // get maximum mask pressure over next several data points to make best guess at IPAP
+ // get maximum mask pressure over several adjacent data points to make best guess at IPAP
float mp = es.dataArray[i];
- for (int j = 1; j < 9; j++)
- if (i < maskRecs-j)
- mp = fmaxf(mp, es.dataArray[i+j]);
+ int jrange = 3; // Number on each side of center
+ int jstart = std::max(0, i-jrange);
+ int jend = (i+jrange)>maskRecs ? maskRecs : i+jrange;
+ for (int j = jstart; j < jend; j++)
+ mp = fmaxf(mp, es.dataArray[j]);
float press = mp * es.gain - 4.0; // Convert pressure to cmH2O and get difference from low end of adjustment curve
@@ -556,6 +558,9 @@ bool SleepStyleLoader::OpenRealTime(Machine *mach, const QString & fname, const
delete [] leakarray;
}
+ // Now do normal processing for Mask Pressure
+ code = CPAP_MaskPressure;
+
} else if (es.label == "Leak") {
code = CPAP_LeakTotal;