mirror of
https://gitlab.com/pholy/OSCAR-code.git
synced 2025-04-22 05:30:44 +00:00
Merge branch 'master' into json-11
This commit is contained in:
commit
f69e22f876
@ -12,6 +12,8 @@ CONFIG -= debug_and_release
|
|||||||
|
|
||||||
QT += core widgets
|
QT += core widgets
|
||||||
|
|
||||||
|
DEFINES+=DUMPSTR
|
||||||
|
|
||||||
TARGET = anotDump
|
TARGET = anotDump
|
||||||
|
|
||||||
TEMPLATE = app
|
TEMPLATE = app
|
||||||
@ -27,6 +29,6 @@ SOURCES += \
|
|||||||
dumpSTR/edfparser.cpp \
|
dumpSTR/edfparser.cpp \
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
dumpSTR/common.h \
|
dumpSTR/SleepLib/common.h \
|
||||||
dumpSTR/edfparser.h \
|
dumpSTR/edfparser.h \
|
||||||
|
|
||||||
|
@ -81,9 +81,14 @@ int main(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
EDFInfo edf;
|
EDFInfo edf;
|
||||||
QByteArray * buffer = edf.Open(filename);
|
if ( ! edf.Open(filename) ) {
|
||||||
if ( ! edf.Parse(buffer) )
|
qDebug() << "Failed to open" << filename;
|
||||||
exit(-1);
|
exit(-1);
|
||||||
|
}
|
||||||
|
if ( ! edf.Parse() ) {
|
||||||
|
qDebug() << "Parsing failed on" << filename;
|
||||||
|
exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
QDate d2 = edf.edfHdr.startdate_orig.date();
|
QDate d2 = edf.edfHdr.startdate_orig.date();
|
||||||
if (d2.year() < 2000) {
|
if (d2.year() < 2000) {
|
||||||
@ -119,5 +124,6 @@ int main(int argc, char *argv[]) {
|
|||||||
qDebug() << "Offset: " << anno->offset << " Duration: " << anno->duration << " Text: " << anno->text;
|
qDebug() << "Offset: " << anno->offset << " Duration: " << anno->duration << " Text: " << anno->text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exit(0);
|
||||||
}
|
}
|
||||||
|
@ -193,4 +193,6 @@ int main(int argc, char *argv[]) {
|
|||||||
// delete &str;
|
// delete &str;
|
||||||
QThread::sleep(1);
|
QThread::sleep(1);
|
||||||
qDebug() << "Done";
|
qDebug() << "Done";
|
||||||
|
|
||||||
|
exit(0);
|
||||||
}
|
}
|
||||||
|
@ -480,6 +480,7 @@ void FlowParser::calc(bool calcResp, bool calcTv, bool calcTi, bool calcTe, bool
|
|||||||
quint32 *tv_tptr = nullptr;
|
quint32 *tv_tptr = nullptr;
|
||||||
EventStoreType *tv_dptr = nullptr;
|
EventStoreType *tv_dptr = nullptr;
|
||||||
int tv_count = 0;
|
int tv_count = 0;
|
||||||
|
double tvlast, tvlast2, tvlast3;
|
||||||
|
|
||||||
if (calcTv) {
|
if (calcTv) {
|
||||||
TV = m_session->AddEventList(CPAP_TidalVolume, EVL_Event);
|
TV = m_session->AddEventList(CPAP_TidalVolume, EVL_Event);
|
||||||
@ -598,8 +599,15 @@ void FlowParser::calc(bool calcResp, bool calcTv, bool calcTi, bool calcTe, bool
|
|||||||
//double x=sqrt(q)*2;
|
//double x=sqrt(q)*2;
|
||||||
//val2=x;
|
//val2=x;
|
||||||
|
|
||||||
if (tv < mintv) { mintv = tv; }
|
// Average TV over last three data points
|
||||||
|
if (tv_count == 0)
|
||||||
|
tvlast = tvlast2 = tvlast3 = tv;
|
||||||
|
tv = (tvlast + tvlast2 + tvlast3 + tv*2)/5;
|
||||||
|
tvlast3 = tvlast2;
|
||||||
|
tvlast2 = tvlast;
|
||||||
|
tvlast = tv;
|
||||||
|
|
||||||
|
if (tv < mintv) { mintv = tv; }
|
||||||
if (tv > maxtv) { maxtv = tv; }
|
if (tv > maxtv) { maxtv = tv; }
|
||||||
|
|
||||||
*tv_tptr++ = timeval;
|
*tv_tptr++ = timeval;
|
||||||
@ -889,8 +897,9 @@ void calcRespRate(Session *session, FlowParser *flowparser)
|
|||||||
bool calcTe = !session->eventlist.contains(CPAP_Te);
|
bool calcTe = !session->eventlist.contains(CPAP_Te);
|
||||||
bool calcMv = !session->eventlist.contains(CPAP_MinuteVent);
|
bool calcMv = !session->eventlist.contains(CPAP_MinuteVent);
|
||||||
|
|
||||||
|
|
||||||
int z = (calcResp ? 1 : 0) + (calcTv ? 1 : 0) + (calcMv ? 1 : 0);
|
int z = (calcResp ? 1 : 0) + (calcTv ? 1 : 0) + (calcMv ? 1 : 0);
|
||||||
|
// Force calculation for testing calculation vs CPAP data
|
||||||
|
// z = 1;
|
||||||
|
|
||||||
// If any of these three missing, remove all, and switch all on
|
// If any of these three missing, remove all, and switch all on
|
||||||
if (z > 0 && z < 3) {
|
if (z > 0 && z < 3) {
|
||||||
|
@ -415,7 +415,7 @@ bool removeDir(const QString &path)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void copyPath(QString src, QString dst)
|
void copyPath(QString src, QString dst, bool overwrite)
|
||||||
{
|
{
|
||||||
QDir dir(src);
|
QDir dir(src);
|
||||||
if (!dir.exists())
|
if (!dir.exists())
|
||||||
@ -425,7 +425,7 @@ void copyPath(QString src, QString dst)
|
|||||||
foreach (QString d, dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) {
|
foreach (QString d, dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) {
|
||||||
QString dst_path = dst + QDir::separator() + d;
|
QString dst_path = dst + QDir::separator() + d;
|
||||||
dir.mkpath(dst_path);
|
dir.mkpath(dst_path);
|
||||||
copyPath(src + QDir::separator() + d, dst_path);
|
copyPath(src + QDir::separator() + d, dst_path, overwrite);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Files
|
// Files
|
||||||
@ -433,6 +433,9 @@ void copyPath(QString src, QString dst)
|
|||||||
QString srcFile = src + QDir::separator() + f;
|
QString srcFile = src + QDir::separator() + f;
|
||||||
QString destFile = dst + QDir::separator() + f;
|
QString destFile = dst + QDir::separator() + f;
|
||||||
|
|
||||||
|
if (overwrite && QFile::exists(destFile)) {
|
||||||
|
QFile::remove(destFile);
|
||||||
|
}
|
||||||
if (!QFile::exists(destFile)) {
|
if (!QFile::exists(destFile)) {
|
||||||
if (!QFile::copy(srcFile, destFile)) {
|
if (!QFile::copy(srcFile, destFile)) {
|
||||||
qWarning() << "copyPath: could not copy" << srcFile << "to" << destFile;
|
qWarning() << "copyPath: could not copy" << srcFile << "to" << destFile;
|
||||||
|
@ -74,7 +74,7 @@ struct ValueCount {
|
|||||||
|
|
||||||
extern int idealThreads();
|
extern int idealThreads();
|
||||||
|
|
||||||
void copyPath(QString src, QString dst);
|
void copyPath(QString src, QString dst, bool overwrite=false);
|
||||||
|
|
||||||
|
|
||||||
// Primarily sort by value
|
// Primarily sort by value
|
||||||
@ -157,6 +157,7 @@ const QString STR_MACH_Journal = "Journal";
|
|||||||
const QString STR_MACH_Intellipap = "Intellipap";
|
const QString STR_MACH_Intellipap = "Intellipap";
|
||||||
const QString STR_MACH_Weinmann= "Weinmann";
|
const QString STR_MACH_Weinmann= "Weinmann";
|
||||||
const QString STR_MACH_FPIcon = "FPIcon";
|
const QString STR_MACH_FPIcon = "FPIcon";
|
||||||
|
const QString STR_MACH_SleepStyle = "SleepStyle";
|
||||||
const QString STR_MACH_MSeries = "MSeries";
|
const QString STR_MACH_MSeries = "MSeries";
|
||||||
const QString STR_MACH_CMS50 = "CMS50";
|
const QString STR_MACH_CMS50 = "CMS50";
|
||||||
const QString STR_MACH_ZEO = "Zeo";
|
const QString STR_MACH_ZEO = "Zeo";
|
||||||
|
@ -58,6 +58,12 @@ EDFInfo::~EDFInfo()
|
|||||||
// delete a;
|
// delete a;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set timezone to UTC
|
||||||
|
void EDFInfo::setTimeZoneUTC () {
|
||||||
|
TZ_offset = 0;
|
||||||
|
EDFInfo::localNoDST = QTimeZone(TZ_offset);
|
||||||
|
}
|
||||||
|
|
||||||
bool EDFInfo::Open(const QString & name)
|
bool EDFInfo::Open(const QString & name)
|
||||||
{
|
{
|
||||||
if (hdrPtr != nullptr) {
|
if (hdrPtr != nullptr) {
|
||||||
|
@ -142,6 +142,9 @@ class EDFInfo
|
|||||||
|
|
||||||
static QDateTime getStartDT(const QString str); //! \brief Returns the start time using noLocalDST
|
static QDateTime getStartDT(const QString str); //! \brief Returns the start time using noLocalDST
|
||||||
|
|
||||||
|
static void setTimeZoneUTC(); //! \brief Sets noLocalDST to UTC (for EDF files using UTC time)
|
||||||
|
|
||||||
|
|
||||||
// The data members follow
|
// The data members follow
|
||||||
|
|
||||||
static int TZ_offset;
|
static int TZ_offset;
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
* for more details. */
|
* for more details. */
|
||||||
|
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
|
#include <QCoreApplication>
|
||||||
|
|
||||||
#include "intellipap_loader.h"
|
#include "intellipap_loader.h"
|
||||||
|
|
||||||
@ -357,7 +358,7 @@ int IntellipapLoader::OpenDV5(const QString & path)
|
|||||||
sess->AddEventList(CPAP_Snore, EVL_Event);
|
sess->AddEventList(CPAP_Snore, EVL_Event);
|
||||||
|
|
||||||
sess->AddEventList(CPAP_Obstructive, EVL_Event);
|
sess->AddEventList(CPAP_Obstructive, EVL_Event);
|
||||||
sess->AddEventList(CPAP_VSnore, EVL_Event);
|
sess->AddEventList(INTP_SnoreFlag, EVL_Event);
|
||||||
sess->AddEventList(CPAP_Hypopnea, EVL_Event);
|
sess->AddEventList(CPAP_Hypopnea, EVL_Event);
|
||||||
sess->AddEventList(CPAP_NRI, EVL_Event);
|
sess->AddEventList(CPAP_NRI, EVL_Event);
|
||||||
sess->AddEventList(CPAP_LeakFlag, EVL_Event);
|
sess->AddEventList(CPAP_LeakFlag, EVL_Event);
|
||||||
@ -375,7 +376,7 @@ int IntellipapLoader::OpenDV5(const QString & path)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QDateTime d = QDateTime::fromTime_t(sid);
|
QDateTime d = QDateTime::fromSecsSinceEpoch(sid);
|
||||||
qDebug() << sid << "has double ups" << d;
|
qDebug() << sid << "has double ups" << d;
|
||||||
/*Session *sess=Sessions[sid];
|
/*Session *sess=Sessions[sid];
|
||||||
Sessions.erase(Sessions.find(sid));
|
Sessions.erase(Sessions.find(sid));
|
||||||
@ -483,7 +484,7 @@ int IntellipapLoader::OpenDV5(const QString & path)
|
|||||||
sess->eventlist[CPAP_Snore][0]->AddEvent(time, m_buffer[pos + 0x4]); //4/5??
|
sess->eventlist[CPAP_Snore][0]->AddEvent(time, m_buffer[pos + 0x4]); //4/5??
|
||||||
|
|
||||||
if (m_buffer[pos+0x4] > 0) {
|
if (m_buffer[pos+0x4] > 0) {
|
||||||
sess->eventlist[CPAP_VSnore][0]->AddEvent(time, m_buffer[pos + 0x5]);
|
sess->eventlist[INTP_SnoreFlag][0]->AddEvent(time, m_buffer[pos + 0x5]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 0x0f == Leak Event
|
// 0x0f == Leak Event
|
||||||
@ -720,7 +721,7 @@ PACK (struct DV6_S_REC{
|
|||||||
unsigned char checksum; //54
|
unsigned char checksum; //54
|
||||||
});
|
});
|
||||||
|
|
||||||
// DV6 SET.BIN - structure of the entire file
|
// DV6 SET.BIN - structure of the entire settings file
|
||||||
PACK (struct SET_BIN_REC {
|
PACK (struct SET_BIN_REC {
|
||||||
char unknown_00; // assuming file version
|
char unknown_00; // assuming file version
|
||||||
char serial[11]; // null terminated
|
char serial[11]; // null terminated
|
||||||
@ -868,13 +869,16 @@ struct DV6_SessionInfo {
|
|||||||
unsigned int begin;
|
unsigned int begin;
|
||||||
unsigned int end;
|
unsigned int end;
|
||||||
unsigned int written;
|
unsigned int written;
|
||||||
bool haveHighResData;
|
// bool haveHighResData;
|
||||||
|
unsigned int firstHighRes;
|
||||||
|
unsigned int lastHighRes;
|
||||||
CPAPMode mode = MODE_UNKNOWN;
|
CPAPMode mode = MODE_UNKNOWN;
|
||||||
};
|
};
|
||||||
|
|
||||||
QString card_path;
|
QString card_path;
|
||||||
QString backup_path;
|
QString backup_path;
|
||||||
QString history_path;
|
QString history_path;
|
||||||
|
QString rebuild_path;
|
||||||
|
|
||||||
MachineInfo info;
|
MachineInfo info;
|
||||||
Machine * mach = nullptr;
|
Machine * mach = nullptr;
|
||||||
@ -882,6 +886,8 @@ Machine * mach = nullptr;
|
|||||||
bool rebuild_from_backups = false;
|
bool rebuild_from_backups = false;
|
||||||
bool create_backups = false;
|
bool create_backups = false;
|
||||||
|
|
||||||
|
QStringList inputFilePaths;
|
||||||
|
|
||||||
QMap<SessionID, DV6_S_Data> DailySummaries;
|
QMap<SessionID, DV6_S_Data> DailySummaries;
|
||||||
QMap<SessionID, DV6_SessionInfo> SessionData;
|
QMap<SessionID, DV6_SessionInfo> SessionData;
|
||||||
SET_BIN_REC * settings;
|
SET_BIN_REC * settings;
|
||||||
@ -909,84 +915,119 @@ public:
|
|||||||
~RollingBackup () {
|
~RollingBackup () {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool open (const QString filetype, DV6_HEADER * newhdr); // Open the file
|
bool open (const QString filetype, DV6_HEADER * newhdr, QByteArray * startTime); // Open the file
|
||||||
bool close(); // close the file
|
bool close(); // close the file
|
||||||
bool save(QByteArray dataBA); // save the next record in the file
|
bool save(const QByteArray &dataBA); // save the next record in the file
|
||||||
|
|
||||||
private:
|
private:
|
||||||
//DV6_HEADER hdr; // file header
|
DV6_HEADER hdr; // file header
|
||||||
QString filetype;
|
QString filetype;
|
||||||
QFile hFile;
|
QFile histfile;
|
||||||
|
|
||||||
//int record_length; // Length of record block in incoming file
|
const qint64 maxHistFileSize = 10000000; // Maximum size of file before we create a new file, in MB (40 MB)
|
||||||
//const int maxHistFileSize = 20*10e6; // Maximum size of file before we create a new file
|
// (While 40e6 would be easier to understand, 40e6 is a double, not an int)
|
||||||
|
|
||||||
//int numWritten; // Number of records written
|
unsigned int lastTimeInFile; // Timestamp of last data record in history file
|
||||||
//quint32 lastTimestamp;
|
int numWritten; // Number of records written
|
||||||
//unsigned int wrap_record;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
bool RollingBackup::open (const QString filetype, DV6_HEADER * newhdr) {
|
QStringList getHistoryFileNames (const QString filetype, bool reversed = false) {
|
||||||
|
QStringList filters;
|
||||||
|
QDir hpath(history_path);
|
||||||
|
|
||||||
|
filters.append(filetype); // Assume one-letter file name like "S.BIN"
|
||||||
|
filters[0].insert(1, "_*"); // Add a wild card like "S_*.BIN"
|
||||||
|
hpath.setNameFilters(filters);
|
||||||
|
hpath.setFilter(QDir::Files);
|
||||||
|
hpath.setSorting(QDir::Name);
|
||||||
|
if (reversed) hpath.setSorting(QDir::Name | QDir::Reversed);
|
||||||
|
|
||||||
|
return hpath.entryList(); // Get list of files
|
||||||
|
}
|
||||||
|
|
||||||
|
QString getNewFileName (QString filetype, QByteArray * startTime, int offset=0) {
|
||||||
|
unsigned char startTimeChar[5];
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
startTimeChar[i] = startTime->at(offset+i);
|
||||||
|
unsigned int ts = convertTime(startTimeChar);
|
||||||
|
QString newfile = filetype.left(1) + "_" + QDateTime::fromSecsSinceEpoch(ts).toString("yyyyMMdd") + ".BIN";
|
||||||
|
qDebug() << "DV6 getNewFileName returns" << newfile;
|
||||||
|
return newfile;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RollingBackup::open (const QString filetype, DV6_HEADER * inputhdr, QByteArray * startTimeOfBackup) {
|
||||||
if (!create_backups)
|
if (!create_backups)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
#ifdef ROLLBACKUP
|
QDir hpath(history_path);
|
||||||
|
QString historypath = hpath.absolutePath() + "/";
|
||||||
|
int histfilesize = 0;
|
||||||
|
|
||||||
this->filetype = filetype;
|
this->filetype = filetype;
|
||||||
|
|
||||||
QDir hpath(history_path);
|
bool needNewFile = false;
|
||||||
QStringList filters;
|
memcpy (&hdr, inputhdr, sizeof(DV6_HEADER));
|
||||||
|
|
||||||
numWritten = 0;
|
numWritten = 0;
|
||||||
|
|
||||||
filters.append(filetype);
|
QStringList fileNames = getHistoryFileNames(filetype, true);
|
||||||
filters[0].insert(1, "_*");
|
|
||||||
hpath.setNameFilters(filters);
|
|
||||||
hpath.setFilter(QDir::Files);
|
|
||||||
hpath.setSorting(QDir::Name | QDir::Reversed);
|
|
||||||
|
|
||||||
QStringList fileNames = hpath.entryList(); // Get list of files
|
|
||||||
QFile histfile(fileNames.first());
|
|
||||||
|
|
||||||
// bool needNewFile = false;
|
|
||||||
|
|
||||||
// Handle first time a history file is being created
|
// Handle first time a history file is being created
|
||||||
if (fileNames.isEmpty()) {
|
if (fileNames.isEmpty()) {
|
||||||
memcpy (&hdr, newhdr, sizeof(DV6_HEADER));
|
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
hdr.recordStart[i] = 0;
|
hdr.recordStart[i] = 0;
|
||||||
hdr.lasttime[i] = 0;
|
hdr.lasttime[i] = 0;
|
||||||
}
|
}
|
||||||
record_length = hdr.recordLength;
|
lastTimeInFile = 0;
|
||||||
|
histfile.setFileName(historypath + getNewFileName (filetype, startTimeOfBackup));
|
||||||
|
needNewFile= true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We have an existing history record
|
// We have an existing history record
|
||||||
if (! fileNames.isEmpty()) {
|
if (! fileNames.isEmpty()) {
|
||||||
|
histfile.setFileName(historypath + fileNames.first()); // File names are in reverse order, so latest is first
|
||||||
|
|
||||||
|
// Open and read history file header and save the header
|
||||||
|
if (!histfile.open(QIODevice::ReadWrite)) {
|
||||||
|
qWarning() << "DV6 rb(open) could not open" << fileNames.first() << "for readwrite, error code" << histfile.error() << histfile.errorString();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
histfilesize = histfile.size();
|
||||||
|
QByteArray dataBA = histfile.read(sizeof(DV6_HEADER));
|
||||||
|
memcpy (&hdr, dataBA.data(), sizeof(DV6_HEADER));
|
||||||
|
lastTimeInFile = convertTime(hdr.lasttime);
|
||||||
|
|
||||||
// See if this file is large enough that we want to create a new file
|
// See if this file is large enough that we want to create a new file
|
||||||
|
// If it is large, we'll start a new file.
|
||||||
if (histfile.size() > maxHistFileSize) {
|
if (histfile.size() > maxHistFileSize) {
|
||||||
memcpy (&hdr, newhdr, sizeof(DV6_HEADER));
|
QString nextFile = historypath + getNewFileName (filetype, &dataBA, 51);
|
||||||
for (int i = 0; i < 4; i++)
|
QString hh = histfile.fileName();
|
||||||
hdr.recordStart[i] = 0;
|
|
||||||
|
|
||||||
if (!histfile.open(QIODevice::ReadOnly)) {
|
if (hh != nextFile) {
|
||||||
qWarning() << "DV6 RollingBackup could not open" << fileNames.first() << "for reading, error code" << histfile.error() << histfile.errorString();
|
lastTimeInFile = convertTime(hdr.lasttime);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
record_length = hdr.recordLength;
|
|
||||||
|
|
||||||
wrap_record = convertNum(hdr.recordStart);
|
|
||||||
if (!histfile.seek(sizeof(DV6_HEADER) + (wrap_record-1) * record_length)) {
|
|
||||||
qWarning() << "DV6 RollingBackup unable to make initial seek to record" << wrap_record
|
|
||||||
<< "in" + histfile.fileName() << histfile.error() << histfile.errorString();
|
|
||||||
histfile.close();
|
histfile.close();
|
||||||
return false;
|
// Update header saying we are starting at record 0
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
hdr.recordStart[i] = 0;
|
||||||
|
histfile.setFileName(nextFile);
|
||||||
|
needNewFile = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
Q_UNUSED(filetype)
|
if (needNewFile) {
|
||||||
Q_UNUSED(newhdr)
|
if (!histfile.open(QIODevice::ReadWrite)) {
|
||||||
#endif
|
qWarning() << "DV6 rb(open) could not create new file" << histfile.fileName() << "for readwrite, error code" << histfile.error() << histfile.errorString();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (histfile.write((char *)&hdr.unknown, sizeof(DV6_HEADER)) != sizeof(DV6_HEADER)) {
|
||||||
|
qWarning() << "DV6 rb(open) could not write header to new file" << histfile.fileName() << "for readwrite, error code" << histfile.error() << histfile.errorString();
|
||||||
|
histfile.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// qDebug() << "DV6 rb(open) history file size" << histfilesize;
|
||||||
|
histfile.seek(histfilesize);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -994,13 +1035,62 @@ bool RollingBackup::open (const QString filetype, DV6_HEADER * newhdr) {
|
|||||||
bool RollingBackup::close() {
|
bool RollingBackup::close() {
|
||||||
if (!create_backups)
|
if (!create_backups)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
qint32 size = histfile.size();
|
||||||
|
|
||||||
|
if (!histfile.seek(0)) {
|
||||||
|
qWarning() << "DV6 rb(close) unable to seek to file beginning" << histfile.error() << histfile.errorString();
|
||||||
|
histfile.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
quint32 sizehdr = sizeof(DV6_HEADER);
|
||||||
|
quint32 reclen = hdr.recordLength;
|
||||||
|
quint32 wrap_point = (size - sizehdr) / reclen;
|
||||||
|
|
||||||
|
hdr.recordStart[0] = wrap_point & 0xff;
|
||||||
|
hdr.recordStart[1] = (wrap_point >> 8) & 0xff;
|
||||||
|
hdr.recordStart[2] = (wrap_point >> 16) & 0xff;
|
||||||
|
hdr.recordStart[3] = (wrap_point >> 24) & 0xff;
|
||||||
|
|
||||||
|
if (histfile.write((char *)&hdr, sizeof(DV6_HEADER)) != sizeof(DV6_HEADER)) {
|
||||||
|
qWarning() << "DV6 rb(close) could not write header to file" << histfile.fileName() << "error code" << histfile.error() << histfile.errorString();
|
||||||
|
histfile.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
histfile.close();
|
||||||
|
qDebug() << "DV6 rb(close) wrote" << numWritten << "records.";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RollingBackup::save(QByteArray dataBA) {
|
bool RollingBackup::save(const QByteArray &dataBA) {
|
||||||
Q_UNUSED(dataBA)
|
|
||||||
if (!create_backups)
|
if (!create_backups)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
unsigned char * data = (unsigned char *)dataBA.data();
|
||||||
|
unsigned int thisTimeStamp = convertTime(data);
|
||||||
|
|
||||||
|
if (thisTimeStamp > lastTimeInFile) { // Is this data new to us?
|
||||||
|
// If so, save it to the history file.
|
||||||
|
if (histfile.write(dataBA) == -1) {
|
||||||
|
qWarning() << "DV6 rb(save) could not save record" << histfile.fileName() << "for readwrite, error code" << histfile.error() << histfile.errorString();
|
||||||
|
histfile.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
memcpy(&hdr.lasttime, data, 4);
|
||||||
|
// if (!histfile.seek(histfile.pos() + dataBA.length()))
|
||||||
|
// qWarning() << "DV6 rb(save) failed respositioning" << histfile.fileName() << "for readwrite, error code" << histfile.error() << histfile.errorString();
|
||||||
|
numWritten++;
|
||||||
|
}
|
||||||
|
/***
|
||||||
|
else {
|
||||||
|
qDebug() << "DV6 rb(save) skipping record" << numWritten << QDateTime::fromSecsSinceEpoch(thisTimeStamp).toString("MM/dd/yyyy hh:mm:ss")
|
||||||
|
<< "last in file" << QDateTime::fromSecsSinceEpoch(lastTimeInFile).toString("MM/dd/yyyy hh:mm:ss");
|
||||||
|
}
|
||||||
|
***/
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1018,7 +1108,7 @@ public:
|
|||||||
hdr = nullptr;
|
hdr = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool open (QString fn); // Open the file
|
bool open (QString fn, bool getNext = false); // Open the file
|
||||||
bool close(); // close the file
|
bool close(); // close the file
|
||||||
unsigned char * get(); // read the next record in the file
|
unsigned char * get(); // read the next record in the file
|
||||||
|
|
||||||
@ -1043,13 +1133,33 @@ private:
|
|||||||
unsigned char * data = nullptr; // record pointer
|
unsigned char * data = nullptr; // record pointer
|
||||||
};
|
};
|
||||||
|
|
||||||
bool RollingFile::open(QString filetype) {
|
bool RollingFile::open(QString filetype, bool getNext) {
|
||||||
|
|
||||||
filename = filetype;
|
filename = filetype;
|
||||||
file.setFileName(card_path + "/" +filetype);
|
|
||||||
|
if (rebuild_from_backups) {
|
||||||
|
// Building from backup
|
||||||
|
if (!getNext) { // Initialize on first call
|
||||||
|
inputFilePaths.clear();
|
||||||
|
QStringList histFileNames = getHistoryFileNames(filetype);
|
||||||
|
qDebug() << "DV6 rf(open) History file names" << histFileNames;
|
||||||
|
for (int i=0; i < histFileNames.size(); i++) {
|
||||||
|
file.setFileName(history_path + "/" + histFileNames.at(i));
|
||||||
|
inputFilePaths.append(file.fileName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (inputFilePaths.empty())
|
||||||
|
return false;
|
||||||
|
file.setFileName(inputFilePaths.at(0));
|
||||||
|
inputFilePaths.removeAt(0);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
file.setFileName(card_path + "/" +filetype);
|
||||||
|
inputFilePaths.clear();
|
||||||
|
}
|
||||||
|
|
||||||
if (!file.open(QIODevice::ReadOnly)) {
|
if (!file.open(QIODevice::ReadOnly)) {
|
||||||
qWarning() << "DV6 RollingFile could not open" << filename << "for reading, error code" << file.error() << file.errorString();
|
qWarning() << "DV6 rf(open) could not open" << filename << "for reading, error code" << file.error() << file.errorString();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1068,30 +1178,48 @@ bool RollingFile::open(QString filetype) {
|
|||||||
// Create buffer to hold each record as it is read
|
// Create buffer to hold each record as it is read
|
||||||
data = new unsigned char[record_length];
|
data = new unsigned char[record_length];
|
||||||
|
|
||||||
// Seek to first data record in file
|
// Seek to oldest data record in file, which is always at the wrap point
|
||||||
if (!file.seek(sizeof(DV6_HEADER) + wrap_record * record_length)) {
|
// wrap_record is the C offset where the next data record is to be written.
|
||||||
qWarning() << "DV6 RollingFile unable to make initial seek to record" << wrap_record << "in" + filename << file.error() << file.errorString();
|
// Since C offsets begin with zero, it is also the number of records in the file.
|
||||||
|
int seekpos = sizeof(DV6_HEADER) + wrap_record * record_length;
|
||||||
|
if (!file.seek(seekpos)) {
|
||||||
|
qWarning() << "DV6 rf(open) unable to make initial seek to record" << wrap_record << "in" + filename << file.error() << file.errorString();
|
||||||
file.close();
|
file.close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#ifdef ROLLBACKUP
|
qDebug() << "DV6 rf(open)" << filetype << "positioning to oldest record at pos" << seekpos << "after seek" << file.pos();
|
||||||
if (!rb.open(filetype, hdr)) {
|
|
||||||
qWarning() << "DV6 RollingBackup failed";
|
|
||||||
file.close();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
qDebug() << "DV6 RollingFile opening" << filename << "at wrap record" << wrap_record;
|
if (file.atEnd()) {
|
||||||
|
file.seek(sizeof(DV6_HEADER));
|
||||||
|
}
|
||||||
|
dataBA = file.read(4); // Read timestamp of newest data record
|
||||||
|
file.seek(seekpos); // Reset read position before what we just read so we start reading data records here
|
||||||
|
if (!rb.open(filetype, hdr, &dataBA)) {
|
||||||
|
qWarning() << "DV6 rf(open) failed";
|
||||||
|
file.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "DV6 rf(open)" << filename << "at wrap record" << wrap_record << "now at pos" << file.pos();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RollingFile::close() {
|
bool RollingFile::close() {
|
||||||
|
|
||||||
|
/*** Works for backing up but prevents chart appearing for the last day
|
||||||
|
// Flush any additional input that has not been backed up
|
||||||
|
if (create_backups) {
|
||||||
|
do {
|
||||||
|
DV6_U_REC * rec = (DV6_U_REC *) get();
|
||||||
|
if (rec == nullptr)
|
||||||
|
break;
|
||||||
|
} while (true);
|
||||||
|
}
|
||||||
|
***/
|
||||||
|
|
||||||
file.close();
|
file.close();
|
||||||
|
|
||||||
#ifdef ROLLBACKUP
|
|
||||||
rb.close();
|
rb.close();
|
||||||
#endif
|
|
||||||
|
|
||||||
if (data)
|
if (data)
|
||||||
delete [] data;
|
delete [] data;
|
||||||
@ -1105,44 +1233,54 @@ bool RollingFile::close() {
|
|||||||
|
|
||||||
unsigned char * RollingFile::get() {
|
unsigned char * RollingFile::get() {
|
||||||
|
|
||||||
|
// int readpos;
|
||||||
record_number++;
|
record_number++;
|
||||||
|
|
||||||
// If we have found the wrap record again, we are done
|
// If we have found the wrap record again, we are done
|
||||||
if (wrapping && (record_number == wrap_record))
|
if (wrapping && record_number == wrap_record) {
|
||||||
return nullptr;
|
// Unless we are rebuilding from backup and may have more files
|
||||||
|
if (rebuild_from_backups && !inputFilePaths.empty()) {
|
||||||
// Hare we reached end of file and need to wrap around to beginning?
|
qDebug() << "DV6 rf(get) closing" << file.fileName();
|
||||||
if (file.atEnd()) {
|
file.close();
|
||||||
if (wrapping) {
|
open(inputFilePaths.at(0), true);
|
||||||
qDebug() << "DV6 RollingFile wrap - second time through";
|
} else {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
qDebug() << "DV6 RollingFile wrapping to beginning of data in" << filename << "record number is" << record_number-1 << "records read" << number_read;
|
}
|
||||||
record_number = 0;
|
|
||||||
|
// Have we reached end of file and need to wrap around to beginning?
|
||||||
|
if (file.atEnd()) {
|
||||||
|
if (wrapping) {
|
||||||
|
qDebug() << "DV6 rf(get) wrap - second time through";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
qDebug() << "DV6 rf(get) wrapping to beginning of data in" << filename << "record number is" << record_number-1 << "records read" << number_read;
|
||||||
|
record_number = 1;
|
||||||
wrapping = true;
|
wrapping = true;
|
||||||
if (!file.seek(sizeof(DV6_HEADER))) {
|
if (!file.seek(sizeof(DV6_HEADER))) {
|
||||||
file.close();
|
file.close();
|
||||||
qWarning() << "DV6 RollingFile unable to seek to first data record in file";
|
qWarning() << "DV6 rf(get) unable to seek to first data record in file";
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
qDebug() << "DV6 rf(get) #" << record_number << "now at pos" << file.pos();
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray dataBA;
|
QByteArray dataBA;
|
||||||
|
// readpos = file.pos();
|
||||||
dataBA=file.read(record_length); // read next record
|
dataBA=file.read(record_length); // read next record
|
||||||
if (dataBA.size() != record_length) {
|
if (dataBA.size() != record_length) {
|
||||||
qWarning() << "DV6 RollingFile record" << record_number << "wrong length";
|
qWarning() << "DV6 rf(get) #" << record_number << "wrong length";
|
||||||
file.close();
|
file.close();
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
#ifdef ROLLBACKUP
|
|
||||||
if (!rb.save(dataBA)) {
|
if (!rb.save(dataBA)) {
|
||||||
qWarning() << "DV6 RollingBackup failed";
|
qWarning() << "DV6 rf(get) failed";
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
number_read++;
|
number_read++;
|
||||||
|
|
||||||
// qDebug() << "RollingFile read" << filename << "record number" << record_number << "of length" << record_length << "number read so far" << number_read;
|
// qDebug() << "DV6 rf(get)" << filename << "at start pos" << readpos << "end pos" << file.pos() << "record number" << record_number << "of length" << record_length << "number read so far" << number_read;
|
||||||
memcpy (data, (unsigned char *) dataBA.data(), record_length);
|
memcpy (data, (unsigned char *) dataBA.data(), record_length);
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
@ -1163,15 +1301,6 @@ QByteArray fileChecksum(const QString &fileName,
|
|||||||
return QByteArray();
|
return QByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
/***
|
|
||||||
// Return the OSCAR date that the last data was written.
|
|
||||||
// This will be considered to be the last day for which we have any data.
|
|
||||||
// Adjust to get the correct date for sessions starting after midnight.
|
|
||||||
QDate getLastDate () {
|
|
||||||
return QDate();
|
|
||||||
}
|
|
||||||
***/
|
|
||||||
|
|
||||||
// Return date used within OSCAR, assuming day ends at split time in preferences (usually noon)
|
// Return date used within OSCAR, assuming day ends at split time in preferences (usually noon)
|
||||||
QDate getNominalDate (QDateTime dt) {
|
QDate getNominalDate (QDateTime dt) {
|
||||||
QDate d = dt.date();
|
QDate d = dt.date();
|
||||||
@ -1213,15 +1342,17 @@ bool load6Sessions () {
|
|||||||
// big endian
|
// big endian
|
||||||
ts1 = convertTime(rec->begin); // session start time (this is also the session id)
|
ts1 = convertTime(rec->begin); // session start time (this is also the session id)
|
||||||
ts2 = convertTime(rec->end); // session end time
|
ts2 = convertTime(rec->end); // session end time
|
||||||
#ifdef DEBUG6
|
//#ifdef DEBUG6
|
||||||
qDebug() << "U.BIN Session" << QDateTime::fromTime_t(ts1).toString("MM/dd/yyyy hh:mm:ss") << ts1 << "to" << QDateTime::fromTime_t(ts2).toString("MM/dd/yyyy hh:mm:ss") << ts2;
|
qDebug() << "U.BIN Session" << QDateTime::fromSecsSinceEpoch(ts1).toString("MM/dd/yyyy hh:mm:ss") << ts1 << "to" << QDateTime::fromSecsSinceEpoch(ts2).toString("MM/dd/yyyy hh:mm:ss") << ts2;
|
||||||
#endif
|
//#endif
|
||||||
sinfo.sess = nullptr;
|
sinfo.sess = nullptr;
|
||||||
sinfo.dailyData = nullptr;
|
sinfo.dailyData = nullptr;
|
||||||
sinfo.begin = ts1;
|
sinfo.begin = ts1;
|
||||||
sinfo.end = ts2;
|
sinfo.end = ts2;
|
||||||
sinfo.written = 0;
|
sinfo.written = 0;
|
||||||
sinfo.haveHighResData = false;
|
// sinfo.haveHighResData = false;
|
||||||
|
sinfo.firstHighRes = 0;
|
||||||
|
sinfo.lastHighRes = 0;
|
||||||
|
|
||||||
SessionData[ts1] = sinfo;
|
SessionData[ts1] = sinfo;
|
||||||
} while (true);
|
} while (true);
|
||||||
@ -1241,6 +1372,8 @@ bool load6Settings (const QString & path) {
|
|||||||
QByteArray dataBA;
|
QByteArray dataBA;
|
||||||
|
|
||||||
QFile f(path+"/"+SET_BIN);
|
QFile f(path+"/"+SET_BIN);
|
||||||
|
if (rebuild_from_backups)
|
||||||
|
f.setFileName(rebuild_path+"/"+SET_BIN);
|
||||||
|
|
||||||
if (f.open(QIODevice::ReadOnly)) {
|
if (f.open(QIODevice::ReadOnly)) {
|
||||||
// Read and parse entire SET.BIN file
|
// Read and parse entire SET.BIN file
|
||||||
@ -1361,6 +1494,8 @@ bool load6VersionInfo(const QString & path) {
|
|||||||
QByteArray str;
|
QByteArray str;
|
||||||
|
|
||||||
QFile f(path+"/VER.BIN");
|
QFile f(path+"/VER.BIN");
|
||||||
|
if (rebuild_from_backups)
|
||||||
|
f.setFileName(rebuild_path+"/VER.BIN");
|
||||||
info.series = "DV6";
|
info.series = "DV6";
|
||||||
info.brand = "DeVilbiss";
|
info.brand = "DeVilbiss";
|
||||||
|
|
||||||
@ -1434,19 +1569,21 @@ int create6Sessions() {
|
|||||||
|
|
||||||
if (mach->SessionExists(sid)) {
|
if (mach->SessionExists(sid)) {
|
||||||
// skip already imported sessions..
|
// skip already imported sessions..
|
||||||
qDebug() << "Session already exists" << QDateTime::fromTime_t(sid).toString("MM/dd/yyyy hh:mm:ss");
|
qDebug() << "Session already exists" << QDateTime::fromSecsSinceEpoch(sid).toString("MM/dd/yyyy hh:mm:ss");
|
||||||
|
|
||||||
} else if (sinfo->sess == nullptr) {
|
} else if (sinfo->sess == nullptr) {
|
||||||
// process new sessions
|
// process new sessions
|
||||||
sess = new Session(mach, sid);
|
sess = new Session(mach, sid);
|
||||||
#ifdef DEBUG6
|
#ifdef DEBUG6
|
||||||
qDebug() << "Creating session" << QDateTime::fromTime_t(sinfo->begin).toString("MM/dd/yyyy hh:mm:ss") << "to" << QDateTime::fromTime_t(sinfo->end).toString("MM/dd/yyyy hh:mm:ss");
|
qDebug() << "Creating session" << QDateTime::fromSecsSinceEpoch(sinfo->begin).toString("MM/dd/yyyy hh:mm:ss") << "to" << QDateTime::fromSecsSinceEpoch(sinfo->end).toString("MM/dd/yyyy hh:mm:ss");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
sinfo->sess = sess;
|
sinfo->sess = sess;
|
||||||
sinfo->dailyData = nullptr;
|
sinfo->dailyData = nullptr;
|
||||||
sinfo->written = 0;
|
sinfo->written = 0;
|
||||||
sinfo->haveHighResData = false;
|
// sinfo->haveHighResData = false;
|
||||||
|
sinfo->firstHighRes = 0;
|
||||||
|
sinfo->lastHighRes = 0;
|
||||||
|
|
||||||
sess->really_set_first(quint64(sinfo->begin) * 1000L);
|
sess->really_set_first(quint64(sinfo->begin) * 1000L);
|
||||||
sess->really_set_last(quint64(sinfo->end) * 1000L);
|
sess->really_set_last(quint64(sinfo->end) * 1000L);
|
||||||
@ -1464,9 +1601,9 @@ int create6Sessions() {
|
|||||||
sess->AddEventList(CPAP_MinuteVent, EVL_Event);
|
sess->AddEventList(CPAP_MinuteVent, EVL_Event);
|
||||||
sess->AddEventList(CPAP_RespRate, EVL_Event);
|
sess->AddEventList(CPAP_RespRate, EVL_Event);
|
||||||
sess->AddEventList(CPAP_Snore, EVL_Event);
|
sess->AddEventList(CPAP_Snore, EVL_Event);
|
||||||
|
sess->AddEventList(INTP_SnoreFlag, EVL_Event);
|
||||||
|
|
||||||
sess->AddEventList(CPAP_Obstructive, EVL_Event);
|
sess->AddEventList(CPAP_Obstructive, EVL_Event);
|
||||||
// sess->AddEventList(CPAP_VSnore, EVL_Event);
|
|
||||||
sess->AddEventList(CPAP_Hypopnea, EVL_Event);
|
sess->AddEventList(CPAP_Hypopnea, EVL_Event);
|
||||||
sess->AddEventList(CPAP_NRI, EVL_Event);
|
sess->AddEventList(CPAP_NRI, EVL_Event);
|
||||||
// sess->AddEventList(CPAP_LeakFlag, EVL_Event);
|
// sess->AddEventList(CPAP_LeakFlag, EVL_Event);
|
||||||
@ -1483,7 +1620,7 @@ int create6Sessions() {
|
|||||||
//?? SessionEnd[z] = 0;
|
//?? SessionEnd[z] = 0;
|
||||||
//?? break;
|
//?? break;
|
||||||
//?? }
|
//?? }
|
||||||
qDebug() << sid << "has double ups" << QDateTime::fromTime_t(sid).toString("MM/dd/yyyy hh:mm:ss");
|
qDebug() << sid << "has double ups" << QDateTime::fromSecsSinceEpoch(sid).toString("MM/dd/yyyy hh:mm:ss");
|
||||||
|
|
||||||
/*Session *sess=Sessions[sid];
|
/*Session *sess=Sessions[sid];
|
||||||
Sessions.erase(Sessions.find(sid));
|
Sessions.erase(Sessions.find(sid));
|
||||||
@ -1560,9 +1697,9 @@ bool load6HighResData () {
|
|||||||
|
|
||||||
if (rec_ts1 < previousRecBegin) {
|
if (rec_ts1 < previousRecBegin) {
|
||||||
qWarning() << "R.BIN - Corruption/Out of sequence data found, skipping record" << rf.recnum() << ", prev"
|
qWarning() << "R.BIN - Corruption/Out of sequence data found, skipping record" << rf.recnum() << ", prev"
|
||||||
<< QDateTime::fromTime_t(previousRecBegin).toString("MM/dd/yyyy hh:mm:ss") << previousRecBegin
|
<< QDateTime::fromSecsSinceEpoch(previousRecBegin).toString("MM/dd/yyyy hh:mm:ss") << previousRecBegin
|
||||||
<< "this"
|
<< "this"
|
||||||
<< QDateTime::fromTime_t(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << rec_ts1;
|
<< QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << rec_ts1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1592,8 +1729,8 @@ bool load6HighResData () {
|
|||||||
// Skip over sessions until we find one that this record is in
|
// Skip over sessions until we find one that this record is in
|
||||||
while (rec_ts1 > sinfo->end) {
|
while (rec_ts1 > sinfo->end) {
|
||||||
#ifdef DEBUG6
|
#ifdef DEBUG6
|
||||||
qDebug() << "R.BIN - skipping session" << QDateTime::fromTime_t(sinfo->begin).toString("MM/dd/yyyy hh:mm:ss")
|
qDebug() << "R.BIN - skipping session" << QDateTime::fromSecsSinceEpoch(sinfo->begin).toString("MM/dd/yyyy hh:mm:ss")
|
||||||
<< "looking for" << QDateTime::fromTime_t(rec_ts1).toString("MM/dd/yyyy hh:mm:ss")
|
<< "looking for" << QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss")
|
||||||
<< "record" << rf.recnum();
|
<< "record" << rf.recnum();
|
||||||
#endif
|
#endif
|
||||||
if (inSession && sess) {
|
if (inSession && sess) {
|
||||||
@ -1657,10 +1794,13 @@ bool load6HighResData () {
|
|||||||
qint64 ti = qint64(rec_ts1) * 1000;
|
qint64 ti = qint64(rec_ts1) * 1000;
|
||||||
flow->AddWaveform(ti,R->breath,50,2000);
|
flow->AddWaveform(ti,R->breath,50,2000);
|
||||||
pressure->AddWaveform(ti, &R->pressure1, 2, 2000);
|
pressure->AddWaveform(ti, &R->pressure1, 2, 2000);
|
||||||
sinfo->haveHighResData = true;
|
if (sinfo->firstHighRes == 0 || sinfo->firstHighRes > rec_ts1) sinfo->firstHighRes = rec_ts1;
|
||||||
|
if (sinfo->lastHighRes == 0 || sinfo->lastHighRes < rec_ts1+2) sinfo->lastHighRes = rec_ts1+2;
|
||||||
|
// sinfo->haveHighResData = true;
|
||||||
if (sess->first() == 0)
|
if (sess->first() == 0)
|
||||||
qWarning() << "first = 0 - 1442";
|
qWarning() << "first = 0 - 1442";
|
||||||
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////
|
||||||
// Show Flow Limitation Events as a graph
|
// Show Flow Limitation Events as a graph
|
||||||
//////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////
|
||||||
@ -2050,18 +2190,20 @@ bool load6PerMinute () {
|
|||||||
rec_ts1 = convertTime(rec->timestamp);
|
rec_ts1 = convertTime(rec->timestamp);
|
||||||
|
|
||||||
if (rec_ts1 < previousRecBegin) {
|
if (rec_ts1 < previousRecBegin) {
|
||||||
|
#ifdef DEBUG6
|
||||||
qWarning() << "L.BIN - Corruption/Out of sequence data found, skipping record" << rf.recnum() << ", prev"
|
qWarning() << "L.BIN - Corruption/Out of sequence data found, skipping record" << rf.recnum() << ", prev"
|
||||||
<< QDateTime::fromTime_t(previousRecBegin).toString("MM/dd/yyyy hh:mm:ss") << previousRecBegin
|
<< QDateTime::fromSecsSinceEpoch(previousRecBegin).toString("MM/dd/yyyy hh:mm:ss") << previousRecBegin
|
||||||
<< "this"
|
<< "this"
|
||||||
<< QDateTime::fromTime_t(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << rec_ts1;
|
<< QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << rec_ts1;
|
||||||
|
#endif
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
/****
|
/****
|
||||||
// Look for a gap in DV6_L records. They should be at one minute intervals.
|
// Look for a gap in DV6_L records. They should be at one minute intervals.
|
||||||
// If there is a gap, we are probably in a new session
|
// If there is a gap, we are probably in a new session
|
||||||
if (inSession && ((rec_ts1 - previousRecBegin) > 60)) {
|
if (inSession && ((rec_ts1 - previousRecBegin) > 60)) {
|
||||||
qDebug() << "L.BIN record gap, current" << QDateTime::fromTime_t(rec_ts1).toString("MM/dd/yyyy hh:mm:ss")
|
qDebug() << "L.BIN record gap, current" << QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss")
|
||||||
<< "previous" << QDateTime::fromTime_t(previousRecBegin).toString("MM/dd/yyyy hh:mm:ss");
|
<< "previous" << QDateTime::fromSecsSinceEpoch(previousRecBegin).toString("MM/dd/yyyy hh:mm:ss");
|
||||||
sess->set_last(maxleak->last());
|
sess->set_last(maxleak->last());
|
||||||
sess = nullptr;
|
sess = nullptr;
|
||||||
leak = maxleak = MV = TV = RR = Pressure = nullptr;
|
leak = maxleak = MV = TV = RR = Pressure = nullptr;
|
||||||
@ -2071,7 +2213,7 @@ bool load6PerMinute () {
|
|||||||
// Skip over sessions until we find one that this record is in
|
// Skip over sessions until we find one that this record is in
|
||||||
while (rec_ts1 > sinfo->end) {
|
while (rec_ts1 > sinfo->end) {
|
||||||
#ifdef DEBUG6
|
#ifdef DEBUG6
|
||||||
qDebug() << "L.BIN - skipping session" << QDateTime::fromTime_t(sinfo->begin).toString("MM/dd/yyyy hh:mm:ss") << "looking for" << QDateTime::fromTime_t(rec_ts1).toString("MM/dd/yyyy hh:mm:ss");
|
qDebug() << "L.BIN - skipping session" << QDateTime::fromSecsSinceEpoch(sinfo->begin).toString("MM/dd/yyyy hh:mm:ss") << "looking for" << QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss");
|
||||||
#endif
|
#endif
|
||||||
if (inSession && sess) {
|
if (inSession && sess) {
|
||||||
// Close the open session and update the min and max
|
// Close the open session and update the min and max
|
||||||
@ -2095,9 +2237,9 @@ bool load6PerMinute () {
|
|||||||
|
|
||||||
if (rec_ts1 < previousRecBegin) {
|
if (rec_ts1 < previousRecBegin) {
|
||||||
qWarning() << "L.BIN - Corruption/Out of sequence data found, stopping import, prev"
|
qWarning() << "L.BIN - Corruption/Out of sequence data found, stopping import, prev"
|
||||||
<< QDateTime::fromTime_t(previousRecBegin).toString("MM/dd/yyyy hh:mm:ss")
|
<< QDateTime::fromSecsSinceEpoch(previousRecBegin).toString("MM/dd/yyyy hh:mm:ss")
|
||||||
<< "this"
|
<< "this"
|
||||||
<< QDateTime::fromTime_t(rec_ts1).toString("MM/dd/yyyy hh:mm:ss");
|
<< QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2114,10 +2256,9 @@ bool load6PerMinute () {
|
|||||||
if (sess->last()/1000 > sinfo->end)
|
if (sess->last()/1000 > sinfo->end)
|
||||||
sinfo->end = sess->last()/1000;
|
sinfo->end = sess->last()/1000;
|
||||||
|
|
||||||
if (!sinfo->haveHighResData) {
|
// if (!sinfo->haveHighResData) {
|
||||||
// Don't use this pressure if we already have higher resolution data
|
// Don't use this pressure if we already have higher resolution data
|
||||||
Pressure = sess->AddEventList(CPAP_Pressure, EVL_Event);
|
|
||||||
}
|
|
||||||
if (sinfo->mode == MODE_UNKNOWN) {
|
if (sinfo->mode == MODE_UNKNOWN) {
|
||||||
if (rec->pressureLimitLow != rec->pressureLimitHigh) {
|
if (rec->pressureLimitLow != rec->pressureLimitHigh) {
|
||||||
sess->settings[CPAP_PressureMin] = rec->pressureLimitLow / 10.0f;
|
sess->settings[CPAP_PressureMin] = rec->pressureLimitLow / 10.0f;
|
||||||
@ -2141,7 +2282,29 @@ bool load6PerMinute () {
|
|||||||
leak->AddEvent(ti, rec->avgLeak); //???
|
leak->AddEvent(ti, rec->avgLeak); //???
|
||||||
RR->AddEvent(ti, rec->breathRate);
|
RR->AddEvent(ti, rec->breathRate);
|
||||||
|
|
||||||
if (Pressure) Pressure->AddEvent(ti, rec->avgPressure / 10.0f); // average pressure
|
if ( sinfo->firstHighRes == 0 // No high res data
|
||||||
|
|| rec_ts1 < sinfo->firstHighRes // Before high res data begins
|
||||||
|
|| ((rec_ts1 > (sinfo->lastHighRes+2)) && (sinfo->lastHighRes > 0))) // or after high res data ends
|
||||||
|
{
|
||||||
|
if (!Pressure)
|
||||||
|
Pressure = sess->AddEventList(CPAP_Pressure, EVL_Event, 0.1f);
|
||||||
|
|
||||||
|
// if (sinfo->firstHighRes == 0) {
|
||||||
|
Pressure->AddEvent(ti, rec->avgPressure); // average pressure for next minute
|
||||||
|
Pressure->AddEvent(ti + 59998, rec->avgPressure); // end of pressure block
|
||||||
|
// } else {
|
||||||
|
// for (int i = 0; i < 60; i++) {
|
||||||
|
// Pressure->AddEvent(ti+i, rec->avgPressure); // average pressure for next minute
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
if (Pressure)
|
||||||
|
qDebug() << "Lowres pressure" << QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss")
|
||||||
|
<< "rec_ts1" << rec_ts1 << "firstHighRes" << sinfo->firstHighRes << "last" << sinfo->lastHighRes
|
||||||
|
<< "Pressure" << rec->avgPressure / 10.0f;
|
||||||
|
***/
|
||||||
|
|
||||||
unsigned tv = rec->tidalVolume6 + (rec->tidalVolume7 << 8);
|
unsigned tv = rec->tidalVolume6 + (rec->tidalVolume7 << 8);
|
||||||
MV->AddEvent(ti, rec->breathRate * tv / 1000.0 );
|
MV->AddEvent(ti, rec->breathRate * tv / 1000.0 );
|
||||||
@ -2177,13 +2340,14 @@ bool load6EventData () {
|
|||||||
|
|
||||||
EventList * OA = nullptr;
|
EventList * OA = nullptr;
|
||||||
EventList * CA = nullptr;
|
EventList * CA = nullptr;
|
||||||
EventList * H = nullptr;
|
EventList * H = nullptr;
|
||||||
EventList * RE = nullptr;
|
EventList * RE = nullptr;
|
||||||
EventList * PB = nullptr;
|
EventList * PB = nullptr;
|
||||||
EventList * LL = nullptr;
|
EventList * LL = nullptr;
|
||||||
EventList * EP = nullptr;
|
EventList * EP = nullptr;
|
||||||
EventList * SN = nullptr;
|
EventList * SN = nullptr;
|
||||||
EventList * FL = nullptr;
|
EventList * FL = nullptr;
|
||||||
|
// EventList * FLG = nullptr;
|
||||||
|
|
||||||
if (!rf.open("E.BIN")) {
|
if (!rf.open("E.BIN")) {
|
||||||
qWarning() << "DV6 Unable to open E.BIN";
|
qWarning() << "DV6 Unable to open E.BIN";
|
||||||
@ -2209,7 +2373,7 @@ bool load6EventData () {
|
|||||||
// Skip over sessions until we find one that this record is in
|
// Skip over sessions until we find one that this record is in
|
||||||
while (rec_ts1 > sinfo->end) {
|
while (rec_ts1 > sinfo->end) {
|
||||||
#ifdef DEBUG6
|
#ifdef DEBUG6
|
||||||
qDebug() << "E.BIN - skipping session" << QDateTime::fromTime_t(sinfo->begin).toString("MM/dd/yyyy hh:mm:ss") << "looking for" << QDateTime::fromTime_t(rec_ts1).toString("MM/dd/yyyy hh:mm:ss");
|
qDebug() << "E.BIN - skipping session" << QDateTime::fromSecsSinceEpoch(sinfo->begin).toString("MM/dd/yyyy hh:mm:ss") << "looking for" << QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss");
|
||||||
#endif
|
#endif
|
||||||
if (inSession) {
|
if (inSession) {
|
||||||
// Close the open session and update the min and max
|
// Close the open session and update the min and max
|
||||||
@ -2227,11 +2391,14 @@ bool load6EventData () {
|
|||||||
sess->set_last(LL->last());
|
sess->set_last(LL->last());
|
||||||
if (EP->last() > 0)
|
if (EP->last() > 0)
|
||||||
sess->set_last(EP->last());
|
sess->set_last(EP->last());
|
||||||
if (SN->last() > 0)
|
|
||||||
sess->set_last(SN->last());
|
|
||||||
if (FL->last() > 0)
|
if (FL->last() > 0)
|
||||||
sess->set_last(FL->last());
|
sess->set_last(FL->last());
|
||||||
|
if (SN->last() > 0)
|
||||||
|
sess->set_last(SN->last());
|
||||||
|
/***
|
||||||
|
if (FLG->last() > 0)
|
||||||
|
sess->set_last(FLG->last());
|
||||||
|
***/
|
||||||
sess = nullptr;
|
sess = nullptr;
|
||||||
H = CA = RE = OA = PB = LL = EP = SN = FL = nullptr;
|
H = CA = RE = OA = PB = LL = EP = SN = FL = nullptr;
|
||||||
inSession = false;
|
inSession = false;
|
||||||
@ -2245,16 +2412,18 @@ bool load6EventData () {
|
|||||||
|
|
||||||
// If we have data beyond last session, we are in trouble (for unknown reasons)
|
// If we have data beyond last session, we are in trouble (for unknown reasons)
|
||||||
if (sinfo == SessionData.end()) {
|
if (sinfo == SessionData.end()) {
|
||||||
qWarning() << "DV6 E.BIN import ran out of sessions to match flow data";
|
qWarning() << "DV6 E.BIN import ran out of sessions,"
|
||||||
|
<< "event data begins"
|
||||||
|
<< QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rec_ts1 < previousRecBegin) {
|
if (rec_ts1 < previousRecBegin) {
|
||||||
qWarning() << "E.BIN - Corruption/Out of sequence data found, stopping import, prev"
|
qWarning() << "DV6 E.BIN - Out of sequence data found, skipping, prev"
|
||||||
<< QDateTime::fromTime_t(previousRecBegin).toString("MM/dd/yyyy hh:mm:ss")
|
<< QDateTime::fromSecsSinceEpoch(previousRecBegin).toString("MM/dd/yyyy hh:mm:ss")
|
||||||
<< "this"
|
<< "this event"
|
||||||
<< QDateTime::fromTime_t(rec_ts1).toString("MM/dd/yyyy hh:mm:ss");
|
<< QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss");
|
||||||
break;
|
continue; // break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if record belongs in this session or a future session
|
// Check if record belongs in this session or a future session
|
||||||
@ -2268,10 +2437,12 @@ bool load6EventData () {
|
|||||||
PB = sess->AddEventList(CPAP_PB, EVL_Event);
|
PB = sess->AddEventList(CPAP_PB, EVL_Event);
|
||||||
LL = sess->AddEventList(CPAP_LargeLeak, EVL_Event);
|
LL = sess->AddEventList(CPAP_LargeLeak, EVL_Event);
|
||||||
EP = sess->AddEventList(CPAP_ExP, EVL_Event);
|
EP = sess->AddEventList(CPAP_ExP, EVL_Event);
|
||||||
// SN = sess->AddEventList(CPAP_VSnore, EVL_Event);
|
SN = sess->AddEventList(INTP_SnoreFlag, EVL_Event);
|
||||||
FL = sess->AddEventList(CPAP_FlowLimit, EVL_Event);
|
FL = sess->AddEventList(CPAP_FlowLimit, EVL_Event);
|
||||||
|
|
||||||
SN = sess->AddEventList(CPAP_Snore, EVL_Waveform, 1.0f, 0.0f, 0.0f, 0.0f, double(2000) / double(2));
|
// FLG = sess->AddEventList(CPAP_FLG, EVL_Waveform, 1.0f, 0.0f, 0.0f, 0.0f, double(2000) / double(2));
|
||||||
|
// SN = sess->AddEventList(CPAP_Snore, EVL_Waveform, 1.0f, 0.0f, 0.0f, 0.0f, double(2000) / double(2));
|
||||||
|
|
||||||
inSession = true;
|
inSession = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2281,11 +2452,12 @@ bool load6EventData () {
|
|||||||
// TODO: We don't know what is really going on here. Is it sloppiness on the part of the DV6 in recording time stamps?
|
// TODO: We don't know what is really going on here. Is it sloppiness on the part of the DV6 in recording time stamps?
|
||||||
qint64 ti = qint64(rec_ts1 - (duration/2)) * 1000L;
|
qint64 ti = qint64(rec_ts1 - (duration/2)) * 1000L;
|
||||||
if (duration < 0) {
|
if (duration < 0) {
|
||||||
qDebug() << "E.BIN at" << QDateTime::fromTime_t(rec_ts1).toString("MM/dd/yyyy hh:mm:ss")
|
qDebug() << "E.BIN at" << QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss")
|
||||||
<< "reports duration of" << duration
|
<< "reports duration of" << duration
|
||||||
<< "ending" << QDateTime::fromTime_t(rec_ts2).toString("MM/dd/yyyy hh:mm:ss");
|
<< "ending" << QDateTime::fromSecsSinceEpoch(rec_ts2).toString("MM/dd/yyyy hh:mm:ss");
|
||||||
}
|
}
|
||||||
int code = rec->event_type;
|
int code = rec->event_type;
|
||||||
|
/***
|
||||||
//////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////
|
||||||
// Show Snore Events as a graph
|
// Show Snore Events as a graph
|
||||||
//////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////
|
||||||
@ -2293,6 +2465,15 @@ bool load6EventData () {
|
|||||||
qint16 severity = rec->event_severity;
|
qint16 severity = rec->event_severity;
|
||||||
SN->AddWaveform(ti, &severity, 1, duration*1000);
|
SN->AddWaveform(ti, &severity, 1, duration*1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////
|
||||||
|
// Show Flow Limit Events as a graph
|
||||||
|
//////////////////////////////////////////////////////////////////
|
||||||
|
if (code == 10) {
|
||||||
|
qint16 severity = rec->event_severity;
|
||||||
|
FLG->AddWaveform(ti, &severity, 1, duration*1000);
|
||||||
|
}
|
||||||
|
***/
|
||||||
if (rec->event_severity >= 3)
|
if (rec->event_severity >= 3)
|
||||||
switch (code) {
|
switch (code) {
|
||||||
case 1:
|
case 1:
|
||||||
@ -2300,33 +2481,33 @@ bool load6EventData () {
|
|||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
OA->AddEvent(ti, duration);
|
OA->AddEvent(ti, duration);
|
||||||
// qDebug() << "E.BIN - OA" << QDateTime::fromTime_t(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << "duration" << duration << "r" << rf.recnum();
|
// qDebug() << "E.BIN - OA" << QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << "duration" << duration << "r" << rf.recnum();
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
H->AddEvent(ti, duration);
|
H->AddEvent(ti, duration);
|
||||||
break;
|
break;
|
||||||
case 5:
|
case 5:
|
||||||
RE->AddEvent(ti, duration);
|
RE->AddEvent(ti, duration);
|
||||||
// qDebug() << "E.BIN - RERA" << QDateTime::fromTime_t(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << "duration" << duration << "r" << rf.recnum();
|
// qDebug() << "E.BIN - RERA" << QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << "duration" << duration << "r" << rf.recnum();
|
||||||
break;
|
break;
|
||||||
case 8: // snore
|
case 8: // snore
|
||||||
SN->AddEvent(ti, duration);
|
SN->AddEvent(ti, duration);
|
||||||
// qDebug() << "E.BIN - Snore" << QDateTime::fromTime_t(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << "duration" << duration << "r" << rf.recnum();
|
// qDebug() << "E.BIN - Snore" << QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << "duration" << duration << "r" << rf.recnum();
|
||||||
break;
|
break;
|
||||||
case 9: // expiratory puff
|
case 9: // expiratory puff
|
||||||
EP->AddEvent(ti, duration);
|
EP->AddEvent(ti, duration);
|
||||||
// qDebug() << "E.BIN - exhale puff" << QDateTime::fromTime_t(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << "duration" << duration << "r" << rf.recnum();
|
// qDebug() << "E.BIN - exhale puff" << QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << "duration" << duration << "r" << rf.recnum();
|
||||||
break;
|
break;
|
||||||
case 10: // flow limitation
|
case 10: // flow limitation
|
||||||
FL->AddEvent(ti, duration);
|
FL->AddEvent(ti, duration);
|
||||||
// qDebug() << "E.BIN - flow limit" << QDateTime::fromTime_t(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << "duration" << duration << "r" << rf.recnum();
|
// qDebug() << "E.BIN - flow limit" << QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << "duration" << duration << "r" << rf.recnum();
|
||||||
break;
|
break;
|
||||||
case 11: // periodic breathing
|
case 11: // periodic breathing
|
||||||
PB->AddEvent(ti, duration);
|
PB->AddEvent(ti, duration);
|
||||||
break;
|
break;
|
||||||
case 12: // large leaks
|
case 12: // large leaks
|
||||||
LL->AddEvent(ti, duration);
|
LL->AddEvent(ti, duration);
|
||||||
// qDebug() << "E.BIN - large leak" << QDateTime::fromTime_t(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << "duration" << duration << "r" << rf.recnum();
|
// qDebug() << "E.BIN - large leak" << QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << "duration" << duration << "r" << rf.recnum();
|
||||||
break;
|
break;
|
||||||
case 13: // pressure change
|
case 13: // pressure change
|
||||||
break;
|
break;
|
||||||
@ -2359,7 +2540,7 @@ int addSessions() {
|
|||||||
}
|
}
|
||||||
#ifdef DEBUG6
|
#ifdef DEBUG6
|
||||||
else
|
else
|
||||||
qDebug() << "Added session" << sess->session() << QDateTime::fromTime_t(sess->session()).toString("MM/dd/yyyy hh:mm:ss");;
|
qDebug() << "Added session" << sess->session() << QDateTime::fromSecsSinceEpoch(sess->session()).toString("MM/dd/yyyy hh:mm:ss");;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Update indexes, process waveform and perform flagging
|
// Update indexes, process waveform and perform flagging
|
||||||
@ -2370,8 +2551,8 @@ int addSessions() {
|
|||||||
|
|
||||||
// Unload them from memory
|
// Unload them from memory
|
||||||
sess->TrashEvents();
|
sess->TrashEvents();
|
||||||
} else
|
} // else
|
||||||
qWarning() << "addSessions: session pointer is null";
|
// qWarning() << "addSessions: session pointer is null";
|
||||||
}
|
}
|
||||||
|
|
||||||
return SessionData.size();
|
return SessionData.size();
|
||||||
@ -2391,24 +2572,10 @@ bool backup6 (const QString & path) {
|
|||||||
QDir ipath(path);
|
QDir ipath(path);
|
||||||
QDir cpath(card_path);
|
QDir cpath(card_path);
|
||||||
QDir bpath(backup_path);
|
QDir bpath(backup_path);
|
||||||
|
QDir hpath(history_path);
|
||||||
if ( ! bpath.exists()) {
|
|
||||||
if ( ! bpath.mkpath(backup_path) ) {
|
|
||||||
qWarning() << "Could not create DV6 backup directory" << backup_path;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy input data to backup location
|
// Copy input data to backup location
|
||||||
copyPath(ipath.absolutePath(), bpath.absolutePath());
|
copyPath(ipath.absolutePath(), bpath.absolutePath(), true);
|
||||||
|
|
||||||
// Create history directory for dated backups
|
|
||||||
QDir hpath(history_path);
|
|
||||||
if ( ! hpath.exists())
|
|
||||||
if ( ! hpath.mkpath(history_path)) {
|
|
||||||
qWarning() << "Could not create DV6 archive directory" << history_path;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create archive of settings file if needed (SET.BIN)
|
// Create archive of settings file if needed (SET.BIN)
|
||||||
bool backup_settings = true;
|
bool backup_settings = true;
|
||||||
@ -2460,10 +2627,12 @@ bool init6Environment (const QString & path) {
|
|||||||
|
|
||||||
backup_path = mach->getBackupPath();
|
backup_path = mach->getBackupPath();
|
||||||
history_path = backup_path + "/HISTORY";
|
history_path = backup_path + "/HISTORY";
|
||||||
|
rebuild_path = backup_path + "/DV6";
|
||||||
|
|
||||||
// Compare QDirs rather than QStrings because separators may be different, especially on Windows.
|
// Compare QDirs rather than QStrings because separators may be different, especially on Windows.
|
||||||
QDir ipath(path);
|
QDir ipath(path);
|
||||||
QDir bpath(backup_path);
|
QDir bpath(backup_path);
|
||||||
|
QDir hpath(history_path);
|
||||||
|
|
||||||
if (ipath == bpath) {
|
if (ipath == bpath) {
|
||||||
// Don't create backups if importing from backup folder
|
// Don't create backups if importing from backup folder
|
||||||
@ -2472,6 +2641,20 @@ bool init6Environment (const QString & path) {
|
|||||||
} else {
|
} else {
|
||||||
rebuild_from_backups = false;
|
rebuild_from_backups = false;
|
||||||
create_backups = p_profile->session->backupCardData();
|
create_backups = p_profile->session->backupCardData();
|
||||||
|
|
||||||
|
if ( ! bpath.exists()) {
|
||||||
|
if ( ! bpath.mkpath(backup_path) ) {
|
||||||
|
qWarning() << "Could not create DV6 backup directory" << backup_path;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( ! hpath.exists()) {
|
||||||
|
if ( ! hpath.mkpath(history_path) ) {
|
||||||
|
qWarning() << "Could not create DV6 backup HISTORY directory" << history_path;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -2486,6 +2669,10 @@ int IntellipapLoader::OpenDV6(const QString & path)
|
|||||||
qDebug() << "DV6 loader started";
|
qDebug() << "DV6 loader started";
|
||||||
card_path = path + DV6_DIR;
|
card_path = path + DV6_DIR;
|
||||||
|
|
||||||
|
emit updateMessage(QObject::tr("Getting Ready..."));
|
||||||
|
emit setProgressValue(0);
|
||||||
|
QCoreApplication::processEvents();
|
||||||
|
|
||||||
// 1. Prime the machine database's info field with this machine
|
// 1. Prime the machine database's info field with this machine
|
||||||
info = newInfo();
|
info = newInfo();
|
||||||
|
|
||||||
@ -2505,10 +2692,16 @@ int IntellipapLoader::OpenDV6(const QString & path)
|
|||||||
if (!load6DailySummaries())
|
if (!load6DailySummaries())
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
|
emit updateMessage(QObject::tr("Backing up files..."));
|
||||||
|
QCoreApplication::processEvents();
|
||||||
|
|
||||||
// 6. Back up data files (must do after parsing VER.BIN, S.BIN, and creating Machine)
|
// 6. Back up data files (must do after parsing VER.BIN, S.BIN, and creating Machine)
|
||||||
if (!backup6(path))
|
if (!backup6(path))
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
|
emit updateMessage(QObject::tr("Reading data files..."));
|
||||||
|
QCoreApplication::processEvents();
|
||||||
|
|
||||||
// 7. U.BIN - Open and parse session list and create a list of session times
|
// 7. U.BIN - Open and parse session list and create a list of session times
|
||||||
// (S.BIN must already be loaded)
|
// (S.BIN must already be loaded)
|
||||||
if (!load6Sessions())
|
if (!load6Sessions())
|
||||||
@ -2530,6 +2723,9 @@ int IntellipapLoader::OpenDV6(const QString & path)
|
|||||||
if (!load6EventData())
|
if (!load6EventData())
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
|
emit updateMessage(QObject::tr("Finishing up..."));
|
||||||
|
QCoreApplication::processEvents();
|
||||||
|
|
||||||
// Finalize input
|
// Finalize input
|
||||||
return addSessions();
|
return addSessions();
|
||||||
}
|
}
|
||||||
@ -2580,6 +2776,14 @@ void IntellipapLoader::initChannels()
|
|||||||
QObject::tr("Intellipap pressure relief level."),
|
QObject::tr("Intellipap pressure relief level."),
|
||||||
QObject::tr("SmartFlex Level"),
|
QObject::tr("SmartFlex Level"),
|
||||||
"", DEFAULT, Qt::green));
|
"", DEFAULT, Qt::green));
|
||||||
|
|
||||||
|
channel.add(GRP_CPAP, new Channel(INTP_SnoreFlag = 0xe301, FLAG, MT_CPAP, SESSION,
|
||||||
|
"INTP_SnoreFlag",
|
||||||
|
QObject::tr("Snore"),
|
||||||
|
QObject::tr("Snoring event."),
|
||||||
|
QObject::tr("SN"),
|
||||||
|
STR_UNIT_EventsPerHour, DEFAULT, QColor("#e20004")));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool intellipap_initialized = false;
|
bool intellipap_initialized = false;
|
||||||
|
111
oscar/SleepLib/loader_plugins/sleepstyle_EDFinfo.cpp
Normal file
111
oscar/SleepLib/loader_plugins/sleepstyle_EDFinfo.cpp
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
/* SleepLib SleepStyle Loader Implementation
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 The OSCAR Team
|
||||||
|
* Copyright (c) 2011-2018 Mark Watkins <mark@jedimark.net>
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
* for more details. */
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QString>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QTextStream>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QStringList>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "SleepLib/session.h"
|
||||||
|
#include "SleepLib/calcs.h"
|
||||||
|
|
||||||
|
#include "SleepLib/loader_plugins/sleepstyle_EDFinfo.h"
|
||||||
|
|
||||||
|
|
||||||
|
SleepStyleEDFInfo::SleepStyleEDFInfo() : EDFInfo() {
|
||||||
|
setTimeZoneUTC(); // Ask EDF Parser to assume data is in UTC, not in local time
|
||||||
|
}
|
||||||
|
SleepStyleEDFInfo::~SleepStyleEDFInfo() { }
|
||||||
|
|
||||||
|
bool SleepStyleEDFInfo::Parse( ) // overrides and calls the super's Parse
|
||||||
|
{
|
||||||
|
if ( ! EDFInfo::Parse( ) ) {
|
||||||
|
qWarning() << "sleepStyle EDFInfo::Parse failed!";
|
||||||
|
// sleep(1);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now massage some stuff into OSCAR's layout
|
||||||
|
// Extract the serial number from header string
|
||||||
|
QStringList parts = edfHdr.recordingident.split(' ');
|
||||||
|
serialnumber = parts[6];
|
||||||
|
|
||||||
|
if (!edfHdr.startdate_orig.isValid()) {
|
||||||
|
qDebug() << "sleepStyle EDFInfo::Parse Invalid date time retreieved parsing EDF File" << filename;
|
||||||
|
// sleep(1);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
startdate = qint64(edfHdr.startdate_orig.toTime_t()) * 1000L;
|
||||||
|
//startdate-=timezoneOffset();
|
||||||
|
if (startdate == 0) {
|
||||||
|
qDebug() << "sleepStyle EDFInfo::Parse Invalid startdate = 0 in EDF File" << filename;
|
||||||
|
// sleep(1);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
dur_data_record = (edfHdr.duration_Seconds * 1000.0L);
|
||||||
|
|
||||||
|
enddate = startdate + dur_data_record * qint64(edfHdr.num_data_records);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extern QHash<ChannelID, QStringList> resmed_codes;
|
||||||
|
|
||||||
|
// Looks up foreign language Signal names that match this channelID
|
||||||
|
EDFSignal *SleepStyleEDFInfo::lookupSignal(ChannelID ch)
|
||||||
|
{
|
||||||
|
// Get list of all known foreign language names for this channel
|
||||||
|
auto channames = resmed_codes.find(ch);
|
||||||
|
if (channames == resmed_codes.end()) {
|
||||||
|
// no alternatives strings found for this channel
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is bad, because ResMed thinks it was a cool idea to use two channels with the same name.
|
||||||
|
|
||||||
|
// Scan through EDF's list of signals to see if any match
|
||||||
|
for (auto & name : channames.value()) {
|
||||||
|
EDFSignal *sig = lookupLabel(name);
|
||||||
|
if (sig)
|
||||||
|
return sig;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Failed
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDateTime SleepStyleEDFInfo::getStartDT( QString dateTimeStr )
|
||||||
|
{
|
||||||
|
// edfHdr.startdate_orig = QDateTime::fromString(QString::fromLatin1(hdrPtr->datetime, 16), "dd.MM.yyHH.mm.ss");
|
||||||
|
// QString dateTimeStr; // , dateStr, timeStr;
|
||||||
|
QDate qDate;
|
||||||
|
QTime qTime;
|
||||||
|
// dateTimeStr = QString::fromLatin1(hdrPtr->datetime, 16);
|
||||||
|
// dateStr = dateTimeStr.left(8);
|
||||||
|
// timeStr = dateTimeStr.right(8);
|
||||||
|
qDate = QDate::fromString(dateTimeStr.left(8), "dd.MM.yy");
|
||||||
|
qTime = QTime::fromString(dateTimeStr.right(8), "HH.mm.ss");
|
||||||
|
return QDateTime(qDate, qTime, Qt::UTC);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void dumpEDFduration( ssEDFduration dur )
|
||||||
|
{
|
||||||
|
qDebug() << "Fullpath" << dur.path << "Filename" << dur.filename << "Start" << dur.start << "End" << dur.end;
|
||||||
|
}
|
||||||
|
|
64
oscar/SleepLib/loader_plugins/sleepstyle_EDFinfo.h
Normal file
64
oscar/SleepLib/loader_plugins/sleepstyle_EDFinfo.h
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
/* SleepLib SleepStyle EDFinfo Header
|
||||||
|
*
|
||||||
|
* Copyright (C) 2011-2018 Mark Watkins <mark@jedimark.net>
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
* for more details. */
|
||||||
|
|
||||||
|
#ifndef SLEEPSTYLE_EDFINFO_H
|
||||||
|
#define SLEEPSTYLE_EDFINFO_H
|
||||||
|
|
||||||
|
#include <QVector>
|
||||||
|
#include "SleepLib/machine.h" // Base class: MachineLoader
|
||||||
|
#include "SleepLib/machine_loader.h"
|
||||||
|
#include "SleepLib/profiles.h"
|
||||||
|
#include "SleepLib/loader_plugins/edfparser.h"
|
||||||
|
|
||||||
|
//enum EDFType { EDF_UNKNOWN, EDF_BRP, EDF_PLD, EDF_SAD, EDF_EVE, EDF_CSL, EDF_AEV };
|
||||||
|
enum EDFType { EDF_UNKNOWN, EDF_RT };
|
||||||
|
|
||||||
|
// EDFType lookupEDFType(const QString & filename);
|
||||||
|
|
||||||
|
const QString SLEEPSTYLE_class_name = STR_MACH_ResMed;
|
||||||
|
|
||||||
|
//class STRFile; // forward
|
||||||
|
|
||||||
|
class SleepStyleEDFInfo : public EDFInfo
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SleepStyleEDFInfo();
|
||||||
|
~SleepStyleEDFInfo();
|
||||||
|
|
||||||
|
virtual bool Parse() override; // overrides and calls the super's Parse
|
||||||
|
|
||||||
|
virtual qint64 GetDurationMillis() { return dur_data_record; } // overrides the super
|
||||||
|
|
||||||
|
EDFSignal *lookupSignal(ChannelID ch);
|
||||||
|
|
||||||
|
QDateTime getStartDT( QString dateTimeStr );
|
||||||
|
|
||||||
|
//! \brief The following are computed from the edfHdr data
|
||||||
|
QString serialnumber;
|
||||||
|
qint64 dur_data_record;
|
||||||
|
qint64 startdate;
|
||||||
|
qint64 enddate;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ssEDFduration
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ssEDFduration() { start = end = 0; type = EDF_UNKNOWN; }
|
||||||
|
ssEDFduration(quint32 start, quint32 end, QString path) :
|
||||||
|
start(start), end(end), path(path) {}
|
||||||
|
|
||||||
|
quint32 start;
|
||||||
|
quint32 end;
|
||||||
|
QString path;
|
||||||
|
QString filename;
|
||||||
|
EDFType type;
|
||||||
|
};
|
||||||
|
|
||||||
|
void dumpEDFduration( ssEDFduration dur );
|
||||||
|
|
||||||
|
#endif // SLEEPSTYLE_EDFINFO_H
|
936
oscar/SleepLib/loader_plugins/sleepstyle_loader.cpp
Normal file
936
oscar/SleepLib/loader_plugins/sleepstyle_loader.cpp
Normal file
@ -0,0 +1,936 @@
|
|||||||
|
/* SleepLib Fisher & Paykel SleepStyle Loader Implementation
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 The Oscar Team
|
||||||
|
*
|
||||||
|
* Derived from icon_loader.cpp
|
||||||
|
* Copyright (c) 2011-2018 Mark Watkins <mark@jedimark.net>
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
* for more details. */
|
||||||
|
|
||||||
|
#include <QDir>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QDataStream>
|
||||||
|
#include <QTextStream>
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "sleepstyle_loader.h"
|
||||||
|
#include "sleepstyle_EDFinfo.h"
|
||||||
|
|
||||||
|
const QString FPHCARE = "FPHCARE";
|
||||||
|
|
||||||
|
SleepStyle::SleepStyle(Profile *profile, MachineID id)
|
||||||
|
: CPAP(profile, id)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
SleepStyle::~SleepStyle()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
SleepStyleLoader::SleepStyleLoader()
|
||||||
|
{
|
||||||
|
m_buffer = nullptr;
|
||||||
|
m_type = MT_CPAP;
|
||||||
|
}
|
||||||
|
|
||||||
|
SleepStyleLoader::~SleepStyleLoader()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getIconDir - returns the path to the ICON directory
|
||||||
|
*/
|
||||||
|
QString getIconDir (QString givenpath) {
|
||||||
|
|
||||||
|
QString path = givenpath;
|
||||||
|
|
||||||
|
path = path.replace("\\", "/");
|
||||||
|
|
||||||
|
if (path.endsWith("/")) {
|
||||||
|
path.chop(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path.endsWith("/" + FPHCARE)) {
|
||||||
|
path = path.section("/",0,-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
QDir dir(path);
|
||||||
|
|
||||||
|
if (!dir.exists()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is a backup directory, higher level directories have been
|
||||||
|
// omitted.
|
||||||
|
if (path.endsWith("/Backup/", Qt::CaseInsensitive))
|
||||||
|
return path;
|
||||||
|
|
||||||
|
// F&P Icon have a folder called FPHCARE in the root directory
|
||||||
|
if (!dir.exists(FPHCARE)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECKME: I can't access F&P ICON data right now
|
||||||
|
if (!dir.exists("FPHCARE/ICON")) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return dir.filePath("FPHCARE/ICON");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getSleepStyleMachines returns a list of all SleepStyle machine folders in the ICON directory
|
||||||
|
*/
|
||||||
|
QStringList getSleepStyleMachines (QString iconPath) {
|
||||||
|
QStringList ssMachines;
|
||||||
|
|
||||||
|
QDir iconDir (iconPath);
|
||||||
|
|
||||||
|
// SleepStyle are mixed alpha and numeric; ICON serial numbers (directory names) are all digits
|
||||||
|
iconDir.setFilter(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Files | QDir::Hidden | QDir::NoSymLinks);
|
||||||
|
iconDir.setSorting(QDir::Name);
|
||||||
|
|
||||||
|
QFileInfoList flist = iconDir.entryInfoList(); // List of Icon subdirectories
|
||||||
|
|
||||||
|
bool isIconFilename;
|
||||||
|
|
||||||
|
// Walk though directory list and save those that appear to be for SleepStyle machins.
|
||||||
|
for (int i = 0; i < flist.size(); i++) {
|
||||||
|
QFileInfo fi = flist.at(i);
|
||||||
|
QString filename = fi.fileName();
|
||||||
|
filename.toInt(&isIconFilename);
|
||||||
|
if (isIconFilename) // Ignore this directory if named as used for older F&P Icon machine
|
||||||
|
continue;
|
||||||
|
if (filename.length() < 8) // F&P machine names are 8 characters long, but we allow more just in case...
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// directory is serial number and must not be all digits (which would make it an ICON directory)
|
||||||
|
// and it must have *.FPH files within it to be a SleepStyle folder
|
||||||
|
|
||||||
|
QDir machineDir (iconPath + "/" + filename);
|
||||||
|
machineDir.setFilter(QDir::NoDotAndDotDot | QDir::Files | QDir::Hidden | QDir::NoSymLinks);
|
||||||
|
machineDir.setSorting(QDir::Name);
|
||||||
|
QStringList filters;
|
||||||
|
filters << "*.fph";
|
||||||
|
machineDir.setNameFilters(filters);
|
||||||
|
QFileInfoList flist = machineDir.entryInfoList();
|
||||||
|
if (flist.size() <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ssMachines.push_back(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ssMachines;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SleepStyleLoader::Detect(const QString & givenpath)
|
||||||
|
{
|
||||||
|
QString iconPath = getIconDir(givenpath);
|
||||||
|
if (iconPath.isEmpty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
QStringList machines = getSleepStyleMachines(iconPath);
|
||||||
|
if (machines.length() <= 0)
|
||||||
|
// Did not find any SleepStyle machine directories
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SleepStyleLoader::backupData (Machine * mach, const QString & path) {
|
||||||
|
|
||||||
|
QDir ipath(path);
|
||||||
|
QDir bpath(mach->getBackupPath());
|
||||||
|
|
||||||
|
// Compare QDirs rather than QStrings because separators may be different, especially on Windows.
|
||||||
|
|
||||||
|
if (ipath == bpath) {
|
||||||
|
// Don't create backups if importing from backup folder
|
||||||
|
rebuild_from_backups = true;
|
||||||
|
create_backups = false;
|
||||||
|
} else {
|
||||||
|
rebuild_from_backups = false;
|
||||||
|
create_backups = p_profile->session->backupCardData();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rebuild_from_backups || !create_backups)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Copy input data to backup location
|
||||||
|
copyPath(ipath.absolutePath(), bpath.absolutePath());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int SleepStyleLoader::Open(const QString & path)
|
||||||
|
{
|
||||||
|
QString iconPath = getIconDir(path);
|
||||||
|
if (iconPath.isEmpty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
QStringList serialNumbers = getSleepStyleMachines(iconPath);
|
||||||
|
if (serialNumbers.length() <= 0)
|
||||||
|
// Did not find any SleepStyle machine directories
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Machine *m;
|
||||||
|
|
||||||
|
int c = 0;
|
||||||
|
for (int i = 0; i < serialNumbers.size(); i++) {
|
||||||
|
MachineInfo info = newInfo();
|
||||||
|
info.serial = serialNumbers[i];
|
||||||
|
m = p_profile->CreateMachine(info);
|
||||||
|
|
||||||
|
setSerialPath(iconPath + "/" + info.serial);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (m) {
|
||||||
|
c+=OpenMachine(m, path, serialPath);
|
||||||
|
}
|
||||||
|
} catch (OneTypePerDay& e) {
|
||||||
|
Q_UNUSED(e)
|
||||||
|
p_profile->DelMachine(m);
|
||||||
|
MachList.erase(MachList.find(info.serial));
|
||||||
|
QMessageBox::warning(nullptr, tr("Import Error"),
|
||||||
|
tr("This Machine Record cannot be imported in this profile.")+"\n\n"+tr("The Day records overlap with already existing content."),
|
||||||
|
QMessageBox::Ok);
|
||||||
|
delete m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
int SleepStyleLoader::OpenMachine(Machine *mach, const QString & path, const QString & ssPath)
|
||||||
|
{
|
||||||
|
emit updateMessage(QObject::tr("Getting Ready..."));
|
||||||
|
emit setProgressValue(0);
|
||||||
|
QCoreApplication::processEvents();
|
||||||
|
|
||||||
|
QDir dir(ssPath);
|
||||||
|
|
||||||
|
if (!dir.exists() || (!dir.isReadable())) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
backupData(mach, path);
|
||||||
|
|
||||||
|
qDebug() << "Opening F&P SleepStyle" << ssPath;
|
||||||
|
|
||||||
|
dir.setFilter(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Files | QDir::Hidden | QDir::NoSymLinks);
|
||||||
|
dir.setSorting(QDir::Name);
|
||||||
|
QFileInfoList flist = dir.entryInfoList();
|
||||||
|
|
||||||
|
QString filename, fpath;
|
||||||
|
|
||||||
|
emit updateMessage(QObject::tr("Reading data files..."));
|
||||||
|
QCoreApplication::processEvents();
|
||||||
|
|
||||||
|
QStringList summary, det, his;
|
||||||
|
Sessions.clear();
|
||||||
|
|
||||||
|
for (int i = 0; i < flist.size(); i++) {
|
||||||
|
QFileInfo fi = flist.at(i);
|
||||||
|
filename = fi.fileName();
|
||||||
|
fpath = ssPath + "/" + filename;
|
||||||
|
|
||||||
|
if (filename.left(3).toUpper() == "SUM") {
|
||||||
|
summary.push_back(fpath);
|
||||||
|
OpenSummary(mach, fpath);
|
||||||
|
} else if (filename.left(3).toUpper() == "DET") {
|
||||||
|
det.push_back(fpath);
|
||||||
|
} else if (filename.left(3).toUpper() == "HIS") {
|
||||||
|
his.push_back(fpath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < det.size(); i++) {
|
||||||
|
OpenDetail(mach, det[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process REALTIME files
|
||||||
|
dir.cd("REALTIME");
|
||||||
|
QFileInfoList rtlist = dir.entryInfoList();
|
||||||
|
for (int i = 0; i < rtlist.size(); i++) {
|
||||||
|
QFileInfo fi = rtlist.at(i);
|
||||||
|
filename = fi.fileName();
|
||||||
|
fpath = ssPath + "/REALTIME/" + filename;
|
||||||
|
if (filename.left(3).toUpper() == "HRD"
|
||||||
|
&& filename.right(3).toUpper() == "EDF" ) {
|
||||||
|
OpenRealTime (mach, filename, fpath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LOG files were not processed by icon_loader
|
||||||
|
// So we don't need to do anything
|
||||||
|
|
||||||
|
SessionID sid;//,st;
|
||||||
|
float hours, mins;
|
||||||
|
|
||||||
|
// For diagnostics, print summary of last 20 session or one week
|
||||||
|
qDebug() << "SS Loader - last 20 Sessions:";
|
||||||
|
|
||||||
|
int cnt = 0;
|
||||||
|
QDateTime dt;
|
||||||
|
QString a = "";
|
||||||
|
|
||||||
|
if (Sessions.size() > 0) {
|
||||||
|
|
||||||
|
QMap<SessionID, Session *>::iterator it = Sessions.end();
|
||||||
|
it--;
|
||||||
|
|
||||||
|
dt = QDateTime::fromTime_t(qint64(it.value()->first()) / 1000L);
|
||||||
|
QDate date = dt.date().addDays(-7);
|
||||||
|
it++;
|
||||||
|
|
||||||
|
do {
|
||||||
|
it--;
|
||||||
|
Session *sess = it.value();
|
||||||
|
sid = sess->session();
|
||||||
|
hours = sess->hours();
|
||||||
|
mins = hours * 60;
|
||||||
|
dt = QDateTime::fromTime_t(sid);
|
||||||
|
|
||||||
|
qDebug() << cnt << ":" << dt << "session" << sid << "," << mins << "minutes" << a;
|
||||||
|
|
||||||
|
if (dt.date() < date) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
++cnt;
|
||||||
|
|
||||||
|
} while (it != Sessions.begin());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// qDebug() << "Unmatched Sessions";
|
||||||
|
// QList<FPWaveChunk> chunks;
|
||||||
|
// for (QMap<int,QDate>::iterator dit=FLWDate.begin();dit!=FLWDate.end();dit++) {
|
||||||
|
// int k=dit.key();
|
||||||
|
// //QDate date=dit.value();
|
||||||
|
//// QList<Session *> values = SessDate.values(date);
|
||||||
|
// for (int j=0;j<FLWTS[k].size();j++) {
|
||||||
|
|
||||||
|
// FPWaveChunk chunk(FLWTS[k].at(j),FLWDuration[k].at(j),k);
|
||||||
|
// chunk.flow=FLWMapFlow[k].at(j);
|
||||||
|
// chunk.leak=FLWMapLeak[k].at(j);
|
||||||
|
// chunk.pressure=FLWMapPres[k].at(j);
|
||||||
|
|
||||||
|
// chunks.push_back(chunk);
|
||||||
|
|
||||||
|
// zz=FLWTS[k].at(j)/1000;
|
||||||
|
// dur=double(FLWDuration[k].at(j))/60000.0;
|
||||||
|
// bool b,c=false;
|
||||||
|
// if (Sessions.contains(zz)) b=true; else b=false;
|
||||||
|
// if (b) {
|
||||||
|
// if (Sessions[zz]->channelDataExists(CPAP_FlowRate)) c=true;
|
||||||
|
// }
|
||||||
|
// qDebug() << k << "-" <<j << ":" << zz << qRound(dur) << "minutes" << (b ? "*" : "") << (c ? QDateTime::fromTime_t(zz).toString() : "");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// std::sort(chunks.begin(), chunks.end());
|
||||||
|
// bool b,c;
|
||||||
|
// for (int i=0;i<chunks.size();i++) {
|
||||||
|
// const FPWaveChunk & chunk=chunks.at(i);
|
||||||
|
// zz=chunk.st/1000;
|
||||||
|
// dur=double(chunk.duration)/60000.0;
|
||||||
|
// if (Sessions.contains(zz)) b=true; else b=false;
|
||||||
|
// if (b) {
|
||||||
|
// if (Sessions[zz]->channelDataExists(CPAP_FlowRate)) c=true;
|
||||||
|
// }
|
||||||
|
// qDebug() << chunk.file << ":" << i << zz << dur << "minutes" << (b ? "*" : "") << (c ? QDateTime::fromTime_t(zz).toString() : "");
|
||||||
|
// }
|
||||||
|
|
||||||
|
int c = Sessions.size();
|
||||||
|
qDebug() << "SS Loader found" << c << "sessions";
|
||||||
|
|
||||||
|
emit updateMessage(QObject::tr("Finishing up..."));
|
||||||
|
QCoreApplication::processEvents();
|
||||||
|
|
||||||
|
finishAddingSessions();
|
||||||
|
|
||||||
|
mach->Save();
|
||||||
|
|
||||||
|
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
// !\brief Convert F&P 32bit date format to 32bit UNIX Timestamp
|
||||||
|
quint32 ssconvertDate(quint32 timestamp)
|
||||||
|
{
|
||||||
|
quint16 day, month,hour=0, minute=0, second=0;
|
||||||
|
quint16 year;
|
||||||
|
|
||||||
|
|
||||||
|
day = timestamp & 0x1f;
|
||||||
|
month = (timestamp >> 5) & 0x0f;
|
||||||
|
year = 2000 + ((timestamp >> 9) & 0x3f);
|
||||||
|
quint32 ts2 = timestamp >> 15;
|
||||||
|
second = ts2 & 0x3f;
|
||||||
|
minute = (ts2 >> 6) & 0x3f;
|
||||||
|
hour = (ts2 >> 12);
|
||||||
|
|
||||||
|
QDateTime dt = QDateTime(QDate(year, month, day), QTime(hour, minute, second), Qt::UTC);
|
||||||
|
|
||||||
|
qDebug().noquote() << "SS timestamp" << timestamp << year << month << day << dt << hour << minute << second;
|
||||||
|
|
||||||
|
// Q NO!!! _ASSERT(dt.isValid());
|
||||||
|
// if ((year == 2013) && (month == 9) && (day == 18)) {
|
||||||
|
// // this is for testing.. set a breakpoint on here and
|
||||||
|
// int i=5;
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
// From Rudd's data set compared to times reported from his F&P software's report (just the time bits left over)
|
||||||
|
// 90514 = 00:06:18 WET 23:06:18 UTC 09:06:18 AEST
|
||||||
|
// 94360 = 01:02:24 WET
|
||||||
|
// 91596 = 00:23:12 WET
|
||||||
|
// 19790 = 23:23:50 WET
|
||||||
|
|
||||||
|
return dt.addSecs(-54).toTime_t(); // Huh? Why do this?
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionID is in seconds, not msec
|
||||||
|
SessionID SleepStyleLoader::findSession (SessionID sid) {
|
||||||
|
for(auto sessKey : Sessions.keys())
|
||||||
|
{
|
||||||
|
Session * sess = Sessions.value(sessKey);
|
||||||
|
if (sid >= (sess->realFirst() / 1000L) && sid <= (sess->realLast() / 1000L))
|
||||||
|
return sessKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SleepStyleLoader::OpenRealTime(Machine *mach, const QString & fname, const QString & filepath)
|
||||||
|
{
|
||||||
|
// Q_UNUSED(filepath)
|
||||||
|
Q_UNUSED(mach)
|
||||||
|
Q_UNUSED(fname)
|
||||||
|
|
||||||
|
SleepStyleEDFInfo edf;
|
||||||
|
|
||||||
|
// Open the EDF file and read contents into edf object
|
||||||
|
if (!edf.Open(filepath)) {
|
||||||
|
qWarning() << "SS Realtime failed to open" << filepath;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!edf.Parse()) {
|
||||||
|
qWarning() << "SS Realtime Parse failed to open" << filepath;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug().noquote() << "SS ORT timestamp" << edf.startdate / 1000L << QDateTime::fromSecsSinceEpoch(edf.startdate / 1000L).toString("MM/dd/yyyy hh:mm:ss");
|
||||||
|
SessionID sessKey = findSession(edf.startdate / 1000L);
|
||||||
|
if (sessKey == 0) {
|
||||||
|
qWarning() << "SS ORT session not found";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Session * sess = Sessions.value(sessKey);
|
||||||
|
|
||||||
|
if (sess == nullptr) {
|
||||||
|
qWarning() << "SS ORT session not found - nullptr";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
sess->updateFirst(edf.startdate);
|
||||||
|
|
||||||
|
qint64 duration = edf.GetNumDataRecords() * edf.GetDurationMillis();
|
||||||
|
sess->updateLast(edf.startdate + duration);
|
||||||
|
|
||||||
|
// Find the leak signal and data
|
||||||
|
long leakrecs = 0;
|
||||||
|
EDFSignal leakSignal;
|
||||||
|
for (auto & esleak : edf.edfsignals) {
|
||||||
|
leakrecs = esleak.sampleCnt * edf.GetNumDataRecords();
|
||||||
|
if (leakrecs < 0)
|
||||||
|
continue;
|
||||||
|
if (esleak.label == "Leak") {
|
||||||
|
leakSignal = esleak;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk through all signals, ignoring leaks
|
||||||
|
for (auto & es : edf.edfsignals) {
|
||||||
|
long recs = es.sampleCnt * edf.GetNumDataRecords();
|
||||||
|
if (recs < 0)
|
||||||
|
continue;
|
||||||
|
ChannelID code = 0;
|
||||||
|
|
||||||
|
if (es.label == "Flow") {
|
||||||
|
// Flow data appears to include total leaks, which are also reported in the edf file.
|
||||||
|
// We subtract the leak from the flow data to get flow data that is centered around zero.
|
||||||
|
// This is needed for other derived graphs (tidal volume, insp and exp times, etc.) to be reasonable
|
||||||
|
code = CPAP_FlowRate;
|
||||||
|
bool done = false;
|
||||||
|
if (leakrecs > 0) {
|
||||||
|
for (int ileak = 0; ileak < leakrecs && !done; ileak++) {
|
||||||
|
for (int iflow = 0; iflow < 25 && !done; iflow++) {
|
||||||
|
if (ileak*25 + iflow >= recs) {
|
||||||
|
done = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
es.dataArray[ileak*25 + iflow] -= leakSignal.dataArray[ileak] - 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (es.label == "Pressure") {
|
||||||
|
code = CPAP_MaskPressure;
|
||||||
|
|
||||||
|
} else
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (code) {
|
||||||
|
double rate = double(duration) / double(recs);
|
||||||
|
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);
|
||||||
|
|
||||||
|
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(code, min);
|
||||||
|
sess->updateMax(code, max);
|
||||||
|
sess->setPhysMin(code, es.physical_minimum);
|
||||||
|
sess->setPhysMax(code, es.physical_maximum);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Open Summary file, create list of sessions and session summary data
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
bool SleepStyleLoader::OpenSummary(Machine *mach, const QString & filename)
|
||||||
|
{
|
||||||
|
qDebug() << filename;
|
||||||
|
QByteArray header;
|
||||||
|
QFile file(filename);
|
||||||
|
|
||||||
|
if (!file.open(QFile::ReadOnly)) {
|
||||||
|
qDebug() << "SS SUM Couldn't open" << filename;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read header of summary file
|
||||||
|
header = file.read(0x200);
|
||||||
|
|
||||||
|
if (header.size() != 0x200) {
|
||||||
|
qDebug() << "SS SUM Short file" << filename;
|
||||||
|
file.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Header is terminated by ';' at 0x1ff
|
||||||
|
unsigned char hterm = 0x3b;
|
||||||
|
|
||||||
|
if (hterm != header[0x1ff]) {
|
||||||
|
qWarning() << "SS SUM Header missing ';' terminator" << filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTextStream htxt(&header);
|
||||||
|
QString h1, version, fname, serial, model, type, unknownident;
|
||||||
|
htxt >> h1;
|
||||||
|
htxt >> version;
|
||||||
|
htxt >> fname;
|
||||||
|
htxt >> serial;
|
||||||
|
htxt >> model; //TODO: Should become Series in machine info???
|
||||||
|
htxt >> type; // SPSAAN etc with 4th character being A (Auto) or C (CPAP)
|
||||||
|
htxt >> unknownident; // Constant, but has different value when version number is different.
|
||||||
|
|
||||||
|
qDebug() << "SS SUM header" << h1 << version << fname << serial << model << type << unknownident;
|
||||||
|
|
||||||
|
if (type.length() > 4)
|
||||||
|
type = (type.at(3) == 'C' ? "CPAP" : "Auto");
|
||||||
|
mach->setModel(model + " " + type);
|
||||||
|
|
||||||
|
// Read remainder of summary file
|
||||||
|
QByteArray data;
|
||||||
|
data = file.readAll();
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
QDataStream in(data);
|
||||||
|
in.setVersion(QDataStream::Qt_4_8);
|
||||||
|
in.setByteOrder(QDataStream::LittleEndian);
|
||||||
|
|
||||||
|
quint32 ts;
|
||||||
|
//QByteArray line;
|
||||||
|
unsigned char p1, p2, p3, j1, x1, x2;
|
||||||
|
|
||||||
|
unsigned char runTime, useTime, minPressSet, maxPressSet, minPressSeen, pct95PressSeen, maxPressSeen;
|
||||||
|
unsigned char senseAwakeLevel, humidityLevel, smartFlexLevel;
|
||||||
|
|
||||||
|
quint16 c1, c2, c3, c4;
|
||||||
|
// quint16 d1, d2, d3;
|
||||||
|
unsigned char d1, d2, d3, d4, d5, d6;
|
||||||
|
|
||||||
|
int usage; //,runtime;
|
||||||
|
|
||||||
|
QDate date;
|
||||||
|
|
||||||
|
int nblock = 0;
|
||||||
|
|
||||||
|
// Go through blocks of data until end marker is found
|
||||||
|
do {
|
||||||
|
nblock++;
|
||||||
|
|
||||||
|
in >> ts;
|
||||||
|
if (ts == 0xffffffff) {
|
||||||
|
qDebug() << "SS SUM 0xffffffff terminator found at block" << nblock;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ((ts & 0xffff) == 0xfafe) {
|
||||||
|
qDebug() << "SS SUM 0xfafa terminator found at block" << nblock;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ts = ssconvertDate(ts);
|
||||||
|
|
||||||
|
qDebug() << "\nSS SUM Session" << nblock << "with timestamp" << ts << QDateTime::fromSecsSinceEpoch(ts).toString("MM/dd/yyyy hh:mm:ss");
|
||||||
|
|
||||||
|
// the following two quite often match in value
|
||||||
|
in >> runTime; // 0x04
|
||||||
|
in >> useTime; // 0x05
|
||||||
|
usage = useTime * 360; // Convert to seconds (durations are in .1 hour intervals)
|
||||||
|
|
||||||
|
in >> minPressSeen; // 0x06
|
||||||
|
in >> pct95PressSeen; // 0x07
|
||||||
|
in >> maxPressSeen; // 0x08
|
||||||
|
|
||||||
|
in >> d1; // 0x09
|
||||||
|
in >> d2; // 0x0a
|
||||||
|
in >> d3; // 0x0b
|
||||||
|
in >> d4; // 0x0c
|
||||||
|
in >> d5; // 0x0d
|
||||||
|
in >> d6; // 0x0e
|
||||||
|
|
||||||
|
in >> c1; // 0x0f
|
||||||
|
in >> c2; // 0x11
|
||||||
|
in >> c3; // 0x13
|
||||||
|
in >> c4; // 0x15
|
||||||
|
|
||||||
|
in >> j1; // 0x17
|
||||||
|
|
||||||
|
in >> p1; // 0x18
|
||||||
|
in >> p2; // 0x19
|
||||||
|
in >> p3; // 0x1a
|
||||||
|
|
||||||
|
in >> x1; // 0x1b
|
||||||
|
in >> x2; // 0x1c
|
||||||
|
|
||||||
|
in >> minPressSet;
|
||||||
|
in >> maxPressSet;
|
||||||
|
in >> senseAwakeLevel;
|
||||||
|
in >> humidityLevel;
|
||||||
|
in >> smartFlexLevel;
|
||||||
|
|
||||||
|
// soak up unknown stuff to apparent end of data for the day
|
||||||
|
unsigned char s [6];
|
||||||
|
for (unsigned int i=0; i < sizeof(s); i++)
|
||||||
|
in >> s[i];
|
||||||
|
|
||||||
|
qDebug() << "SS SUM block" << nblock
|
||||||
|
<< "a:" <<"Pressure Min"<<minPressSeen<<"95%"<<pct95PressSeen<<"Max"<<maxPressSeen
|
||||||
|
<< "\nd:" <<d1<<d2<<d3<<d4<<d5<<d6
|
||||||
|
<< "\nj:" <<j1 << " c:" << c1 << c2 << c3 << c4
|
||||||
|
<< "\np:" <<p1<<p2<<p3
|
||||||
|
<< "\nx:" <<x1<<x2
|
||||||
|
<< "\ns:" <<"Min set" <<minPressSet<<"Max set"<<maxPressSet<<"SA"<<senseAwakeLevel<<"Humid"<<humidityLevel<<"SmartFlex"<<smartFlexLevel<<s[0]<<s[1]<<s[2]<<s[3]<<s[4]<<s[5];
|
||||||
|
|
||||||
|
if (runTime != useTime) {
|
||||||
|
qDebug() << "SS SUM run time" << runTime << "!= use time" << useTime << "-" << nblock << QDateTime::fromSecsSinceEpoch(ts).toString("MM/dd/yyyy hh:mm:ss");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mach->SessionExists(ts)) {
|
||||||
|
Session *sess = new Session(mach, ts);
|
||||||
|
sess->really_set_first(qint64(ts) * 1000L);
|
||||||
|
sess->really_set_last(qint64(ts + usage) * 1000L);
|
||||||
|
sess->SetChanged(true);
|
||||||
|
/****
|
||||||
|
// TODO: None of the apnea numbers have been confirmed
|
||||||
|
sess->setCount(CPAP_Obstructive, c3);
|
||||||
|
// sess->setCph(CPAP_Obstructive, c3 / (float(usage)/3600.00));
|
||||||
|
|
||||||
|
sess->setCount(CPAP_Hypopnea, c4);
|
||||||
|
// sess->setCph(CPAP_Hypopnea, c4 / (float(usage)/3600.00));
|
||||||
|
|
||||||
|
sess->setCount(CPAP_ClearAirway, c1);
|
||||||
|
// sess->setCph(CPAP_ClearAirway, c1 / (float(usage)/3600.00));
|
||||||
|
|
||||||
|
sess->setCount(CPAP_FlowLimit, c2);
|
||||||
|
// sess->setCph(CPAP_FlowLimit, c2 / (float(usage)/3600.00));
|
||||||
|
****/
|
||||||
|
SessDate.insert(date, sess);
|
||||||
|
|
||||||
|
if (minPressSet != maxPressSet) {
|
||||||
|
sess->settings[CPAP_Mode] = (int)MODE_APAP;
|
||||||
|
sess->settings[CPAP_PressureMin] = minPressSet / 10.0;
|
||||||
|
sess->settings[CPAP_PressureMax] = maxPressSet / 10.0;
|
||||||
|
} else {
|
||||||
|
sess->settings[CPAP_Mode] = (int)MODE_CPAP;
|
||||||
|
sess->settings[CPAP_Pressure] = minPressSet / 10.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
sess->settings[CPAP_HumidSetting] = humidityLevel;
|
||||||
|
sess->settings[SS_SenseAwakeLevel] = senseAwakeLevel / 10.0;
|
||||||
|
sess->settings[CPAP_PresReliefMode] = PR_SMARTFLEX;
|
||||||
|
sess->settings[SS_SmartFlexLevel] = smartFlexLevel / 1.0;
|
||||||
|
|
||||||
|
Sessions[ts] = sess;
|
||||||
|
|
||||||
|
addSession(sess);
|
||||||
|
}
|
||||||
|
} while (!in.atEnd());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Open Detail record contains list of sessions and pressure, leak, and event flags
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
bool SleepStyleLoader::OpenDetail(Machine *mach, const QString & filename)
|
||||||
|
{
|
||||||
|
Q_UNUSED(mach);
|
||||||
|
|
||||||
|
qDebug() << "SS DET Opening Detail" << filename;
|
||||||
|
QByteArray header;
|
||||||
|
QFile file(filename);
|
||||||
|
|
||||||
|
if (!file.open(QFile::ReadOnly)) {
|
||||||
|
qDebug() << "SS DET Couldn't open" << filename;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
header = file.read(0x200);
|
||||||
|
|
||||||
|
if (header.size() != 0x200) {
|
||||||
|
qDebug() << "SS DET short file" << filename;
|
||||||
|
file.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Header is terminated by ';' at 0x1ff
|
||||||
|
unsigned char hterm = 0x3b;
|
||||||
|
|
||||||
|
if (hterm != header[0x1ff]) {
|
||||||
|
file.close();
|
||||||
|
qWarning() << "SS DET Header missing ';' terminator" << filename;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTextStream htxt(&header);
|
||||||
|
QString h1, version, fname, serial, model, type, unknownident;
|
||||||
|
htxt >> h1;
|
||||||
|
htxt >> version;
|
||||||
|
htxt >> fname;
|
||||||
|
htxt >> serial;
|
||||||
|
htxt >> model; //TODO: Should become Series in machine info???
|
||||||
|
htxt >> type; // SPSAAN etc with 4th character being A (Auto) or C (CPAP)
|
||||||
|
htxt >> unknownident; // Constant, but has different value when version number is different.
|
||||||
|
|
||||||
|
qDebug() << "SS DET file header" << h1 << version << fname << serial << model << type << unknownident;
|
||||||
|
|
||||||
|
// Read session indices
|
||||||
|
QByteArray index = file.read(0x800);
|
||||||
|
if (index.size()!=0x800) {
|
||||||
|
// faulty file..
|
||||||
|
qWarning() << "SS DET file short index block";
|
||||||
|
file.close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
QDataStream in(index);
|
||||||
|
quint32 ts;
|
||||||
|
|
||||||
|
in.setVersion(QDataStream::Qt_4_6);
|
||||||
|
in.setByteOrder(QDataStream::LittleEndian);
|
||||||
|
|
||||||
|
QVector<quint32> times;
|
||||||
|
QVector<quint16> start;
|
||||||
|
QVector<quint8> records;
|
||||||
|
|
||||||
|
quint16 strt;
|
||||||
|
quint8 recs;
|
||||||
|
quint16 unknownIndex;
|
||||||
|
|
||||||
|
int totalrecs = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
// Read timestamp for session and check for end of data signal
|
||||||
|
in >> ts;
|
||||||
|
if (ts == 0xffffffff) break;
|
||||||
|
if ((ts & 0xffff) == 0xfafe) break;
|
||||||
|
|
||||||
|
ts = ssconvertDate(ts);
|
||||||
|
|
||||||
|
in >> strt;
|
||||||
|
in >> recs;
|
||||||
|
in >> unknownIndex;
|
||||||
|
totalrecs += recs; // Number of data records for this session
|
||||||
|
|
||||||
|
qDebug().noquote() << "SS DET block timestamp" << ts << QDateTime::fromSecsSinceEpoch(ts).toString("MM/dd/yyyy hh:mm:ss") << "start" << strt << "records" << recs << "unknown" << unknownIndex;
|
||||||
|
|
||||||
|
if (Sessions.contains(ts)) {
|
||||||
|
times.push_back(ts);
|
||||||
|
start.push_back(strt);
|
||||||
|
records.push_back(recs);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
qDebug() << "SS DET session not found" << ts;
|
||||||
|
} while (!in.atEnd());
|
||||||
|
|
||||||
|
QByteArray databytes = file.readAll();
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
in.setVersion(QDataStream::Qt_4_6);
|
||||||
|
in.setByteOrder(QDataStream::BigEndian);
|
||||||
|
|
||||||
|
// 7 (was 5) byte repeating patterns
|
||||||
|
|
||||||
|
quint8 *data = (quint8 *)databytes.data();
|
||||||
|
|
||||||
|
qint64 ti;
|
||||||
|
quint8 pressure, leak, a1, a2, a3, a4, a5, a6, a7;
|
||||||
|
// quint8 sa1, sa2; // The two sense awake bits per 2 minutes
|
||||||
|
SessionID sessid;
|
||||||
|
Session *sess;
|
||||||
|
int idx;
|
||||||
|
|
||||||
|
for (int r = 0; r < start.size(); r++) {
|
||||||
|
sessid = times[r];
|
||||||
|
sess = Sessions[sessid];
|
||||||
|
ti = qint64(sessid) * 1000L;
|
||||||
|
sess->really_set_first(ti);
|
||||||
|
|
||||||
|
EventList *LK = sess->AddEventList(CPAP_LeakTotal, EVL_Event, 1);
|
||||||
|
EventList *PR = sess->AddEventList(CPAP_Pressure, EVL_Event, 0.1F);
|
||||||
|
// EventList *OA = sess->AddEventList(CPAP_Obstructive, EVL_Event);
|
||||||
|
EventList *H = sess->AddEventList(CPAP_Hypopnea, EVL_Event);
|
||||||
|
EventList *FL = sess->AddEventList(CPAP_FlowLimit, EVL_Event);
|
||||||
|
EventList *SA = sess->AddEventList(CPAP_SensAwake, EVL_Event);
|
||||||
|
// EventList *CA = sess->AddEventList(CPAP_ClearAirway, EVL_Event);
|
||||||
|
EventList *UA = sess->AddEventList(CPAP_Apnea, EVL_Event);
|
||||||
|
// For testing to determine which bit is for which event type:
|
||||||
|
// EventList *UF1 = sess->AddEventList(CPAP_UserFlag1, EVL_Event);
|
||||||
|
// EventList *UF2 = sess->AddEventList(CPAP_UserFlag2, EVL_Event);
|
||||||
|
|
||||||
|
unsigned stidx = start[r];
|
||||||
|
int rec = records[r];
|
||||||
|
|
||||||
|
idx = stidx * 21; // Each record has three blocks of 7 bytes for 21 bytes total
|
||||||
|
|
||||||
|
quint8 bitmask;
|
||||||
|
for (int i = 0; i < rec; ++i) {
|
||||||
|
for (int j = 0; j < 3; ++j) {
|
||||||
|
pressure = data[idx];
|
||||||
|
PR->AddEvent(ti/*+120000*/, pressure);
|
||||||
|
|
||||||
|
leak = data[idx + 1];
|
||||||
|
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
|
||||||
|
a3 = data[idx + 4]; // [0..5] Flow Limitation, [6..7] Unknown
|
||||||
|
a4 = data[idx + 5]; // [0..5] UF1, [6..7] Unknown
|
||||||
|
a5 = data[idx + 6]; // [0..5] UF2, [6..7] Unknown
|
||||||
|
|
||||||
|
// Sure there isn't 6 SenseAwake bits?
|
||||||
|
a6 = (a3 >> 6) << 4 | ((a4 >> 6) << 2) | (a5 >> 6);
|
||||||
|
|
||||||
|
// this does the same thing as behaviour
|
||||||
|
// a6 = (a3 >> 7) << 3 | ((a3 >> 6) & 1);
|
||||||
|
a7 = (a1 >> 6) | (a2 >> 6); // Are these bits used?
|
||||||
|
|
||||||
|
bitmask = 1;
|
||||||
|
for (int k = 0; k < 6; k++) { // There are 6 flag sets per 2 minutes
|
||||||
|
if (a1 & bitmask) { UA->AddEvent(ti+60000, 0); }
|
||||||
|
if (a2 & bitmask) { UA->AddEvent(ti+60000, 0); } // may be CA?
|
||||||
|
if (a3 & bitmask) { H->AddEvent(ti+60000, 0); }
|
||||||
|
if (a4 & bitmask) { H->AddEvent(ti+60000, 0); } // may be OA?
|
||||||
|
if (a5 & bitmask) { FL->AddEvent(ti+60000, 0); }
|
||||||
|
if (a6 & bitmask) { SA->AddEvent(ti+60000, 0); }
|
||||||
|
|
||||||
|
bitmask <<= 1;
|
||||||
|
ti += 20000L; // Increment 20 seconds
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug print non-zero flags
|
||||||
|
if (a1 != 0 || a2 != 0 || a3 != 0 || a4 != 0 || a5 != 0 || a6 != 0 || a7 != 0) {
|
||||||
|
qDebug() << "SS DET events" << QDateTime::fromSecsSinceEpoch(ti/1000).toString("MM/dd/yyyy hh:mm:ss")
|
||||||
|
<< "pressure" << pressure
|
||||||
|
<< "leak" << leak
|
||||||
|
<< "flags" << a1 << a2 << a3 << a4 << a5 << a6 << "unknown" << a7;
|
||||||
|
}
|
||||||
|
|
||||||
|
idx += 7; //was 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update indexes, process waveform and perform flagging
|
||||||
|
sess->UpdateSummaries();
|
||||||
|
|
||||||
|
// sess->really_set_last(ti-360000L);
|
||||||
|
// sess->SetChanged(true);
|
||||||
|
// addSession(sess,profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SleepStyleLoader::initChannels()
|
||||||
|
{
|
||||||
|
using namespace schema;
|
||||||
|
Channel * chan = nullptr;
|
||||||
|
|
||||||
|
/****
|
||||||
|
channel.add(GRP_CPAP, chan = new Channel(INTP_SmartFlexMode = 0x1165, SETTING, MT_CPAP, SESSION,
|
||||||
|
"INTPSmartFlexMode", QObject::tr("SmartFlex Mode"),
|
||||||
|
QObject::tr("Pressure relief mode."),
|
||||||
|
QObject::tr("SmartFlex Mode"),
|
||||||
|
"", DEFAULT, Qt::green));
|
||||||
|
|
||||||
|
chan->addOption(0, STR_TR_Off);
|
||||||
|
****/
|
||||||
|
|
||||||
|
channel.add(GRP_CPAP, chan = new Channel(SS_SmartFlexLevel = 0xf304, SETTING, MT_CPAP, SESSION,
|
||||||
|
"SSSmartFlexLevel", QObject::tr("SmartFlex Level"),
|
||||||
|
QObject::tr("Exhalation pressure relief level."),
|
||||||
|
QObject::tr("SmartFlex"),
|
||||||
|
"", LOOKUP, Qt::green));
|
||||||
|
chan->addOption(0, STR_TR_Off);
|
||||||
|
|
||||||
|
channel.add(GRP_CPAP, new Channel(SS_SenseAwakeLevel = 0xf305, SETTING, MT_CPAP, SESSION,
|
||||||
|
"SS_SenseAwakeLevel",
|
||||||
|
QObject::tr("SenseAwake level"),
|
||||||
|
QObject::tr("SenseAwake level"),
|
||||||
|
QObject::tr("SenseAwake"),
|
||||||
|
STR_UNIT_CMH2O, LOOKUP, Qt::black));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool sleepstyle_initialized = false;
|
||||||
|
void SleepStyleLoader::Register()
|
||||||
|
{
|
||||||
|
if (sleepstyle_initialized) { return; }
|
||||||
|
|
||||||
|
qDebug() << "Registering F&P Sleepstyle Loader";
|
||||||
|
RegisterLoader(new SleepStyleLoader());
|
||||||
|
//InitModelMap();
|
||||||
|
sleepstyle_initialized = true;
|
||||||
|
}
|
129
oscar/SleepLib/loader_plugins/sleepstyle_loader.h
Normal file
129
oscar/SleepLib/loader_plugins/sleepstyle_loader.h
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
/* SleepLib Fisher & Paykel SleepStyle Loader Implementation
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 The Oscar Team (info@oscar-team.org)
|
||||||
|
* Copyright (C) 2011-2018 Mark Watkins <mark@jedimark.net>
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
* for more details. */
|
||||||
|
|
||||||
|
#ifndef SLEEPSTYLE_LOADER_H
|
||||||
|
#define SLEEPSTYLE_LOADER_H
|
||||||
|
|
||||||
|
#include <QMultiMap>
|
||||||
|
#include "SleepLib/machine.h"
|
||||||
|
#include "SleepLib/machine_loader.h"
|
||||||
|
#include "SleepLib/profiles.h"
|
||||||
|
|
||||||
|
|
||||||
|
//********************************************************************************************
|
||||||
|
/// IMPORTANT!!!
|
||||||
|
//********************************************************************************************
|
||||||
|
// Please INCREMENT the following value when making changes to this loaders implementation.
|
||||||
|
//
|
||||||
|
const int sleepstyle_data_version = 1;
|
||||||
|
//
|
||||||
|
//********************************************************************************************
|
||||||
|
|
||||||
|
/*! \class SleepStyle
|
||||||
|
\brief F&P SleepStyle customized machine object
|
||||||
|
*/
|
||||||
|
class SleepStyle: public CPAP
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SleepStyle(Profile *, MachineID id = 0);
|
||||||
|
virtual ~SleepStyle();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const int sleepstyle_load_buffer_size = 1024 * 1024;
|
||||||
|
|
||||||
|
extern ChannelID INTP_SmartFlexMode;
|
||||||
|
extern ChannelID SS_SmartFlexLevel;
|
||||||
|
extern ChannelID SS_SenseAwakeLevel;
|
||||||
|
|
||||||
|
const QString sleepstyle_class_name = STR_MACH_SleepStyle;
|
||||||
|
|
||||||
|
/*! \class SleepStyleLoader
|
||||||
|
\brief Loader for Fisher & Paykel SleepStyle data
|
||||||
|
This is only relatively recent addition and still needs more work
|
||||||
|
*/
|
||||||
|
|
||||||
|
class SleepStyleLoader : public CPAPLoader
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
SleepStyleLoader();
|
||||||
|
virtual ~SleepStyleLoader();
|
||||||
|
|
||||||
|
//! \brief Detect if the given path contains a valid Folder structure
|
||||||
|
virtual bool Detect(const QString & path);
|
||||||
|
|
||||||
|
//! \brief Scans path for F&P SleepStyle data signature, and Loads any new data
|
||||||
|
virtual int Open(const QString & path);
|
||||||
|
|
||||||
|
int OpenMachine(Machine *mach, const QString & path, const QString & ssPath);
|
||||||
|
|
||||||
|
bool OpenSummary(Machine *mach, const QString & path);
|
||||||
|
bool OpenDetail(Machine *mach, const QString & path);
|
||||||
|
// bool OpenFLW(Machine *mach, const QString & filename);
|
||||||
|
bool OpenRealTime(Machine *mach, const QString & fname, const QString & filename);
|
||||||
|
|
||||||
|
//! \brief Returns SleepLib database version of this F&P SleepStyle loader
|
||||||
|
virtual int Version() { return sleepstyle_data_version; }
|
||||||
|
|
||||||
|
//! \brief Returns the machine class name of this CPAP machine, "SleepStyle"
|
||||||
|
virtual const QString & loaderName() { return sleepstyle_class_name; }
|
||||||
|
|
||||||
|
// ! \brief Creates a machine object, indexed by serial number
|
||||||
|
//Machine *CreateMachine(QString serial);
|
||||||
|
|
||||||
|
QString getSerialPath () {return serialPath;}
|
||||||
|
void setSerialPath (QString sp) {serialPath = sp;}
|
||||||
|
bool backupData (Machine * mach, const QString & path);
|
||||||
|
|
||||||
|
SessionID findSession (SessionID sid);
|
||||||
|
|
||||||
|
void initChannels();
|
||||||
|
|
||||||
|
virtual MachineInfo newInfo() {
|
||||||
|
return MachineInfo(MT_CPAP, 0, sleepstyle_class_name, QObject::tr("Fisher & Paykel"), QString(), QString(), QString(), QObject::tr("SleepStyle"), QDateTime::currentDateTime(), sleepstyle_data_version);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//! \brief Registers this MachineLoader with the master list, so F&P Icon data can load
|
||||||
|
static void Register();
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Now for some CPAPLoader overrides
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
virtual QString presRelType() { return QObject::tr(""); } // might not need this one
|
||||||
|
|
||||||
|
virtual ChannelID presRelSet() { return NoChannel; }
|
||||||
|
virtual ChannelID presRelLevel() { return NoChannel; }
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// QDateTime readFPDateTime(quint8 *data);
|
||||||
|
|
||||||
|
QString last;
|
||||||
|
QHash<QString, Machine *> MachList;
|
||||||
|
QMap<SessionID, Session *> Sessions;
|
||||||
|
QMultiMap<QDate, Session *> SessDate;
|
||||||
|
//QMap<int,QList<EventList *> > FLWMapFlow;
|
||||||
|
//QMap<int,QList<EventList *> > FLWMapLeak;
|
||||||
|
//QMap<int,QList<EventList *> > FLWMapPres;
|
||||||
|
//QMap<int,QList<qint64> > FLWDuration;
|
||||||
|
//QMap<int,QList<qint64> > FLWTS;
|
||||||
|
//QMap<int,QDate> FLWDate;
|
||||||
|
|
||||||
|
QString serialPath; // fully qualified path to the input data, ...SDCard.../FPHCARE/ICON/serial
|
||||||
|
// QString serial; // Serial number
|
||||||
|
bool rebuild_from_backups = false;
|
||||||
|
bool create_backups = true;
|
||||||
|
|
||||||
|
unsigned char *m_buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SLEEPSTYLE_LOADER_H
|
@ -28,12 +28,14 @@ ChannelID CPAP_IPAP, CPAP_IPAPLo, CPAP_IPAPHi, CPAP_EPAP, CPAP_EPAPLo, CPAP_EPAP
|
|||||||
|
|
||||||
|
|
||||||
ChannelID RMS9_E01, RMS9_E02, RMS9_SetPressure, RMS9_MaskOnTime;
|
ChannelID RMS9_E01, RMS9_E02, RMS9_SetPressure, RMS9_MaskOnTime;
|
||||||
ChannelID INTELLIPAP_Unknown1, INTELLIPAP_Unknown2;
|
ChannelID INTELLIPAP_Unknown1, INTELLIPAP_Unknown2, INTP_SnoreFlag;
|
||||||
|
|
||||||
ChannelID CPAP_LargeLeak,
|
ChannelID CPAP_LargeLeak,
|
||||||
PRS1_BND, PRS1_FlexMode, PRS1_FlexLevel, PRS1_HumidStatus, PRS1_HumidLevel, PRS1_HumidTargetTime, PRS1_MaskResistLock,
|
PRS1_BND, PRS1_FlexMode, PRS1_FlexLevel, PRS1_HumidStatus, PRS1_HumidLevel, PRS1_HumidTargetTime, PRS1_MaskResistLock,
|
||||||
PRS1_MaskResistSet, PRS1_HoseDiam, PRS1_AutoOn, PRS1_AutoOff, PRS1_MaskAlert, PRS1_ShowAHI;
|
PRS1_MaskResistSet, PRS1_HoseDiam, PRS1_AutoOn, PRS1_AutoOff, PRS1_MaskAlert, PRS1_ShowAHI;
|
||||||
|
|
||||||
|
ChannelID SS_SenseAwakeLevel, SS_SmartFlexLevel;
|
||||||
|
|
||||||
ChannelID OXI_Pulse, OXI_SPO2, OXI_Perf, OXI_PulseChange, OXI_SPO2Drop, OXI_Plethy;
|
ChannelID OXI_Pulse, OXI_SPO2, OXI_Perf, OXI_PulseChange, OXI_SPO2Drop, OXI_Plethy;
|
||||||
|
|
||||||
ChannelID Journal_Notes, Journal_Weight, Journal_BMI, Journal_ZombieMeter, LastUpdated,
|
ChannelID Journal_Notes, Journal_Weight, Journal_BMI, Journal_ZombieMeter, LastUpdated,
|
||||||
|
@ -164,7 +164,9 @@ extern ChannelID CPAP_LargeLeak, PRS1_BND,
|
|||||||
CPAP_HumidSetting,
|
CPAP_HumidSetting,
|
||||||
PRS1_MaskResistSet, PRS1_HoseDiam, PRS1_AutoOn, PRS1_AutoOff, PRS1_MaskAlert, PRS1_ShowAHI;
|
PRS1_MaskResistSet, PRS1_HoseDiam, PRS1_AutoOn, PRS1_AutoOff, PRS1_MaskAlert, PRS1_ShowAHI;
|
||||||
|
|
||||||
extern ChannelID INTELLIPAP_Unknown1, INTELLIPAP_Unknown2;
|
extern ChannelID INTELLIPAP_Unknown1, INTELLIPAP_Unknown2, INTP_SnoreFlag;
|
||||||
|
|
||||||
|
extern ChannelID SS_SenseAwakeLevel, SS_SmartFlexLevel;
|
||||||
|
|
||||||
extern ChannelID OXI_Pulse, OXI_SPO2, OXI_Perf, OXI_PulseChange, OXI_SPO2Drop, OXI_Plethy;
|
extern ChannelID OXI_Pulse, OXI_SPO2, OXI_Perf, OXI_PulseChange, OXI_SPO2Drop, OXI_Plethy;
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Update the string below to set OSCAR's version and release status.
|
// 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.
|
// See https://semver.org/spec/v2.0.0.html for details on format.
|
||||||
|
|
||||||
#define VERSION "1.2.1-alpha.0"
|
#define VERSION "1.2.1-alpha.1"
|
||||||
|
@ -43,6 +43,7 @@
|
|||||||
#include "SleepLib/loader_plugins/resmed_loader.h"
|
#include "SleepLib/loader_plugins/resmed_loader.h"
|
||||||
#include "SleepLib/loader_plugins/intellipap_loader.h"
|
#include "SleepLib/loader_plugins/intellipap_loader.h"
|
||||||
#include "SleepLib/loader_plugins/icon_loader.h"
|
#include "SleepLib/loader_plugins/icon_loader.h"
|
||||||
|
#include "SleepLib/loader_plugins/sleepstyle_loader.h"
|
||||||
#include "SleepLib/loader_plugins/weinmann_loader.h"
|
#include "SleepLib/loader_plugins/weinmann_loader.h"
|
||||||
#include "SleepLib/loader_plugins/viatom_loader.h"
|
#include "SleepLib/loader_plugins/viatom_loader.h"
|
||||||
|
|
||||||
@ -666,6 +667,7 @@ int main(int argc, char *argv[]) {
|
|||||||
ResmedLoader::Register();
|
ResmedLoader::Register();
|
||||||
IntellipapLoader::Register();
|
IntellipapLoader::Register();
|
||||||
FPIconLoader::Register();
|
FPIconLoader::Register();
|
||||||
|
SleepStyleLoader::Register();
|
||||||
WeinmannLoader::Register();
|
WeinmannLoader::Register();
|
||||||
CMS50Loader::Register();
|
CMS50Loader::Register();
|
||||||
CMS50F37Loader::Register();
|
CMS50F37Loader::Register();
|
||||||
|
@ -1080,6 +1080,7 @@ QList<ImportPath> MainWindow::selectCPAPDataCards(const QString & prompt)
|
|||||||
w.setDirectory(folder);
|
w.setDirectory(folder);
|
||||||
w.setFileMode(QFileDialog::Directory);
|
w.setFileMode(QFileDialog::Directory);
|
||||||
w.setOption(QFileDialog::ShowDirsOnly, true);
|
w.setOption(QFileDialog::ShowDirsOnly, true);
|
||||||
|
w.setWindowTitle(tr("Find your CPAP data card"));
|
||||||
|
|
||||||
// This doesn't work on WinXP
|
// This doesn't work on WinXP
|
||||||
|
|
||||||
|
@ -295,6 +295,8 @@ SOURCES += \
|
|||||||
SleepLib/loader_plugins/cms50_loader.cpp \
|
SleepLib/loader_plugins/cms50_loader.cpp \
|
||||||
SleepLib/loader_plugins/dreem_loader.cpp \
|
SleepLib/loader_plugins/dreem_loader.cpp \
|
||||||
SleepLib/loader_plugins/icon_loader.cpp \
|
SleepLib/loader_plugins/icon_loader.cpp \
|
||||||
|
SleepLib/loader_plugins/sleepstyle_loader.cpp \
|
||||||
|
SleepLib/loader_plugins/sleepstyle_EDFinfo.cpp \
|
||||||
SleepLib/loader_plugins/intellipap_loader.cpp \
|
SleepLib/loader_plugins/intellipap_loader.cpp \
|
||||||
SleepLib/loader_plugins/mseries_loader.cpp \
|
SleepLib/loader_plugins/mseries_loader.cpp \
|
||||||
SleepLib/loader_plugins/prs1_loader.cpp \
|
SleepLib/loader_plugins/prs1_loader.cpp \
|
||||||
@ -380,6 +382,8 @@ HEADERS += \
|
|||||||
SleepLib/loader_plugins/cms50_loader.h \
|
SleepLib/loader_plugins/cms50_loader.h \
|
||||||
SleepLib/loader_plugins/dreem_loader.h \
|
SleepLib/loader_plugins/dreem_loader.h \
|
||||||
SleepLib/loader_plugins/icon_loader.h \
|
SleepLib/loader_plugins/icon_loader.h \
|
||||||
|
SleepLib/loader_plugins/sleepstyle_loader.h \
|
||||||
|
SleepLib/loader_plugins/sleepstyle_EDFinfo.h \
|
||||||
SleepLib/loader_plugins/intellipap_loader.h \
|
SleepLib/loader_plugins/intellipap_loader.h \
|
||||||
SleepLib/loader_plugins/mseries_loader.h \
|
SleepLib/loader_plugins/mseries_loader.h \
|
||||||
SleepLib/loader_plugins/prs1_loader.h \
|
SleepLib/loader_plugins/prs1_loader.h \
|
||||||
|
Loading…
Reference in New Issue
Block a user