mirror of
https://gitlab.com/pholy/OSCAR-code.git
synced 2025-04-09 20:50:43 +00:00
Merge branch 'master' into Legacy
This commit is contained in:
commit
a2232d4b61
File diff suppressed because it is too large
Load Diff
@ -28,7 +28,7 @@
|
||||
const int prs1_data_version = 15;
|
||||
//
|
||||
//********************************************************************************************
|
||||
|
||||
#if 0 // Apparently unused
|
||||
/*! \class PRS1
|
||||
\brief PRS1 customized machine object (via CPAP)
|
||||
*/
|
||||
@ -41,6 +41,7 @@ class PRS1: public CPAP
|
||||
|
||||
|
||||
const int max_load_buffer_size = 1024 * 1024;
|
||||
#endif
|
||||
const QString prs1_class_name = STR_MACH_PRS1;
|
||||
|
||||
/*! \struct PRS1Waveform
|
||||
@ -77,13 +78,13 @@ public:
|
||||
m_index = -1;
|
||||
}
|
||||
PRS1DataChunk(class QFile & f);
|
||||
~PRS1DataChunk() {
|
||||
}
|
||||
~PRS1DataChunk();
|
||||
inline int size() const { return m_data.size(); }
|
||||
|
||||
QByteArray m_header;
|
||||
QByteArray m_data;
|
||||
QByteArray m_headerblock;
|
||||
QList<class PRS1ParsedEvent*> m_parsedData;
|
||||
|
||||
QString m_path;
|
||||
qint64 m_filepos; // file offset
|
||||
@ -108,6 +109,9 @@ public:
|
||||
|
||||
// V3 normal/non-waveform fields
|
||||
QMap<unsigned char, short> hblock;
|
||||
|
||||
QMap<unsigned char, QByteArray> mainblock;
|
||||
QMap<unsigned char, QByteArray> hbdata;
|
||||
|
||||
// Trailing common fields
|
||||
quint8 storedChecksum; // header checksum stored in file, last byte of m_header
|
||||
@ -124,7 +128,58 @@ public:
|
||||
//! \brief Read the chunk's data from a PRS1 file and calculate its CRC, must be called after ReadHeader
|
||||
bool ReadData(class QFile & f);
|
||||
|
||||
//! \brief Parse a single data chunk from a .000 file containing compliance data for a brick
|
||||
bool ParseCompliance(void);
|
||||
|
||||
//! \brief Figures out which Summary Parser to call, based on machine family/version and calls it.
|
||||
bool ParseSummary();
|
||||
|
||||
//! \brief Parse a single data chunk from a .001 file containing summary data for a family 0 CPAP/APAP family version 2 or 3 machine
|
||||
bool ParseSummaryF0V23(void);
|
||||
|
||||
//! \brief Parse a single data chunk from a .001 file containing summary data for a family 0 CPAP/APAP family version 4 machine
|
||||
bool ParseSummaryF0V4(void);
|
||||
|
||||
//! \brief Parse a single data chunk from a .001 file containing summary data for a family 0 CPAP/APAP family version 6 machine
|
||||
bool ParseSummaryF0V6(void);
|
||||
|
||||
//! \brief Parse a single data chunk from a .001 file containing summary data for a family 3 ventilator (family version 6?) machine
|
||||
bool ParseSummaryF3(void);
|
||||
|
||||
//! \brief Parse a single data chunk from a .001 file containing summary data for a family 5 ASV family version 0-2 machine
|
||||
bool ParseSummaryF5V012(void);
|
||||
|
||||
//! \brief Parse a single data chunk from a .001 file containing summary data for a family 5 ASV family version 3 machine
|
||||
bool ParseSummaryF5V3(void);
|
||||
|
||||
//! \brief Parse a flex setting byte from a .000 or .001 containing compliance/summary data
|
||||
void ParseFlexSetting(quint8 flex, CPAPMode cpapmode);
|
||||
|
||||
//! \brief Parse an humidifier setting byte from a .000 or .001 containing compliance/summary data
|
||||
void ParseHumidifierSetting(int humid, bool supportsHeatedTubing=true);
|
||||
|
||||
//! \brief Figures out which Event Parser to call, based on machine family/version and calls it.
|
||||
bool ParseEvents(CPAPMode mode);
|
||||
|
||||
//! \brief Parse a single data chunk from a .002 file containing event data for a family 0 CPAP/APAP machine
|
||||
bool ParseEventsF0(CPAPMode mode);
|
||||
|
||||
//! \brief Parse a single data chunk from a .002 file containing event data for a family 3 ventilator family version 3 machine
|
||||
bool ParseEventsF3V3(void);
|
||||
|
||||
//! \brief Parse a single data chunk from a .002 file containing event data for a family 3 ventilator family version 6 machine
|
||||
bool ParseEventsF3V6(void);
|
||||
|
||||
//! \brief Parse a single data chunk from a .002 file containing event data for a family 5 ASV family version 0-2 machine
|
||||
bool ParseEventsF5V012(void);
|
||||
|
||||
//! \brief Parse a single data chunk from a .002 file containing event data for a family 5 ASV family version 3 machine
|
||||
bool ParseEventsF5V3(void);
|
||||
|
||||
protected:
|
||||
//! \brief Add a parsed event to the chunk
|
||||
void AddEvent(class PRS1ParsedEvent* event);
|
||||
|
||||
//! \brief Read and parse the non-waveform header data from a V2 PRS1 file
|
||||
bool ReadNormalHeaderV2(class QFile & f);
|
||||
|
||||
@ -138,6 +193,13 @@ protected:
|
||||
bool ExtractStoredCrc(int size);
|
||||
};
|
||||
|
||||
|
||||
#if UNITTEST_MODE
|
||||
QString _PRS1ParsedEventName(PRS1ParsedEvent* e);
|
||||
QMap<QString,QString> _PRS1ParsedEventContents(PRS1ParsedEvent* e);
|
||||
#endif
|
||||
|
||||
|
||||
class PRS1Loader;
|
||||
|
||||
/*! \class PRS1Import
|
||||
@ -167,9 +229,6 @@ public:
|
||||
QList<PRS1DataChunk *> waveforms;
|
||||
QList<PRS1DataChunk *> oximetry;
|
||||
|
||||
QMap<unsigned char, QByteArray> mainblock;
|
||||
QMap<unsigned char, QByteArray> hbdata;
|
||||
|
||||
|
||||
QString wavefile;
|
||||
QString oxifile;
|
||||
@ -177,8 +236,8 @@ public:
|
||||
//! \brief As it says on the tin.. Parses .001 files for bricks.
|
||||
bool ParseCompliance();
|
||||
|
||||
//! \brief Figures out which Summary Parser to call, based on machine family/version and calls it.
|
||||
bool ParseSummary();
|
||||
//! \brief Imports the .002 summary file.
|
||||
bool ImportSummary();
|
||||
|
||||
//! \brief Figures out which Event Parser to call, based on machine family/version and calls it.
|
||||
bool ParseEvents();
|
||||
@ -193,24 +252,6 @@ public:
|
||||
bool ParseOximetery();
|
||||
|
||||
|
||||
//! \brief Summary parser for 50 series Family 0 CPAP/APAP models
|
||||
bool ParseSummaryF0();
|
||||
//! \brief Summary parser for 60 series Family 0 CPAP/APAP models
|
||||
bool ParseSummaryF0V4();
|
||||
//! \brief Summary parser for 1060 series AVAPS models
|
||||
bool ParseSummaryF3();
|
||||
//! \brief Summary parser for 50 series Family 5-0 BiPAP/AutoSV models
|
||||
bool ParseSummaryF5V0();
|
||||
//! \brief Summary parser for 60 series Family 5-1 BiPAP/AutoSV models
|
||||
bool ParseSummaryF5V1();
|
||||
//! \brief Summary parser for 60 series Family 5-2 BiPAP/AutoSV models
|
||||
bool ParseSummaryF5V2();
|
||||
//! \brief Summary parser for 60 series Family 5-3 BiPAP/AutoSV models
|
||||
bool ParseSummaryF5V3();
|
||||
|
||||
//! \brief Summary parser for DreamStation series CPAP/APAP models
|
||||
bool ParseSummaryF0V6();
|
||||
|
||||
//! \brief Parse a single data chunk from a .002 file containing event data for a standard system one machine
|
||||
bool ParseF0Events();
|
||||
//! \brief Parse a single data chunk from a .002 file containing event data for a AVAPS 1060P machine
|
||||
@ -250,6 +291,9 @@ class PRS1Loader : public CPAPLoader
|
||||
//! \brief Examine path and return it back if it contains what looks to be a valid PRS1 SD card structure
|
||||
QString checkDir(const QString & path);
|
||||
|
||||
//! \brief Peek into PROP.TXT or properties.txt at given path, and return it as a normalized key/value hash
|
||||
bool PeekProperties(const QString & filename, QHash<QString,QString> & props);
|
||||
|
||||
//! \brief Peek into PROP.TXT or properties.txt at given path, and use it to fill MachineInfo structure
|
||||
bool PeekProperties(MachineInfo & info, const QString & path, Machine * mach = nullptr);
|
||||
|
||||
@ -337,4 +381,21 @@ class PRS1Loader : public CPAPLoader
|
||||
qint32 summary_duration;
|
||||
};
|
||||
|
||||
|
||||
//********************************************************************************************
|
||||
|
||||
class PRS1ModelInfo
|
||||
{
|
||||
protected:
|
||||
QHash<int, QHash<int, QStringList>> m_testedModels;
|
||||
|
||||
public:
|
||||
PRS1ModelInfo();
|
||||
bool IsSupported(const QHash<QString,QString> & properties) const;
|
||||
bool IsSupported(int family, int familyVersion) const;
|
||||
bool IsTested(const QHash<QString,QString> & properties) const;
|
||||
bool IsTested(const QString & modelNumber, int family, int familyVersion) const;
|
||||
};
|
||||
|
||||
|
||||
#endif // PRS1LOADER_H
|
||||
|
@ -1,5 +1,6 @@
|
||||
/* SleepLib Machine Class Implementation
|
||||
/* SleepLib Machine Class Implementation
|
||||
*
|
||||
* Copyright (c) 2019 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
|
||||
@ -45,6 +46,7 @@ Machine::Machine(Profile *_profile, MachineID id) : profile(_profile)
|
||||
day.clear();
|
||||
highest_sessionid = 0;
|
||||
m_unsupported = false;
|
||||
m_untested = false;
|
||||
|
||||
if (!id) {
|
||||
srand(time(nullptr));
|
||||
|
@ -1,5 +1,6 @@
|
||||
/* SleepLib Machine Class Header
|
||||
/* SleepLib Machine Class Header
|
||||
*
|
||||
* Copyright (c) 2019 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
|
||||
@ -184,9 +185,12 @@ class Machine
|
||||
QSemaphore *savelistSem;
|
||||
|
||||
bool m_unsupported;
|
||||
bool m_untested;
|
||||
|
||||
bool unsupported() { return m_unsupported; }
|
||||
void setUnsupported(bool b) { m_unsupported = b; }
|
||||
bool untested() { return m_untested; }
|
||||
void setUntested(bool b) { m_untested = b; }
|
||||
|
||||
void lockSaveMutex() { listMutex.lock(); }
|
||||
void unlockSaveMutex() { listMutex.unlock(); }
|
||||
|
@ -33,6 +33,47 @@ void PRS1Tests::cleanupTestCase(void)
|
||||
}
|
||||
|
||||
|
||||
// ====================================================================================================
|
||||
|
||||
extern PRS1ModelInfo s_PRS1ModelInfo;
|
||||
void PRS1Tests::testMachineSupport()
|
||||
{
|
||||
QHash<QString,QString> tested = {
|
||||
{ "ModelNumber", "550P" },
|
||||
{ "Family", "0" },
|
||||
{ "FamilyVersion", "3" },
|
||||
};
|
||||
QHash<QString,QString> supported = {
|
||||
{ "ModelNumber", "700X999" },
|
||||
{ "Family", "0" },
|
||||
{ "FamilyVersion", "6" },
|
||||
};
|
||||
QHash<QString,QString> unsupported = {
|
||||
{ "ModelNumber", "550P" },
|
||||
{ "Family", "0" },
|
||||
{ "FamilyVersion", "9" },
|
||||
};
|
||||
|
||||
Q_ASSERT(s_PRS1ModelInfo.IsSupported(5, 3));
|
||||
Q_ASSERT(!s_PRS1ModelInfo.IsSupported(5, 9));
|
||||
Q_ASSERT(!s_PRS1ModelInfo.IsSupported(9, 9));
|
||||
Q_ASSERT(s_PRS1ModelInfo.IsTested("550P", 0, 2));
|
||||
Q_ASSERT(s_PRS1ModelInfo.IsTested("550P", 0, 3));
|
||||
Q_ASSERT(s_PRS1ModelInfo.IsTested("760P", 0, 4));
|
||||
Q_ASSERT(s_PRS1ModelInfo.IsTested("700X110", 0, 6));
|
||||
Q_ASSERT(!s_PRS1ModelInfo.IsTested("700X999", 0, 6));
|
||||
|
||||
Q_ASSERT(s_PRS1ModelInfo.IsTested(tested));
|
||||
Q_ASSERT(!s_PRS1ModelInfo.IsTested(supported));
|
||||
Q_ASSERT(s_PRS1ModelInfo.IsSupported(tested));
|
||||
Q_ASSERT(s_PRS1ModelInfo.IsSupported(supported));
|
||||
Q_ASSERT(!s_PRS1ModelInfo.IsSupported(unsupported));
|
||||
}
|
||||
|
||||
|
||||
// ====================================================================================================
|
||||
|
||||
|
||||
void parseAndEmitSessionYaml(const QString & path)
|
||||
{
|
||||
qDebug() << path;
|
||||
@ -84,13 +125,16 @@ static QString ts(qint64 msecs)
|
||||
return QDateTime::fromMSecsSinceEpoch(msecs).toString(Qt::ISODate);
|
||||
}
|
||||
|
||||
static QString byteList(QByteArray data)
|
||||
static QString byteList(QByteArray data, int limit=-1)
|
||||
{
|
||||
int count = data.size();
|
||||
if (limit == -1 || limit > count) limit = count;
|
||||
QStringList l;
|
||||
for (int i = 0; i < data.size(); i++) {
|
||||
for (int i = 0; i < limit; i++) {
|
||||
l.push_back(QString( "%1" ).arg((int) data[i] & 0xFF, 2, 16, QChar('0') ).toUpper());
|
||||
}
|
||||
QString s = l.join("");
|
||||
if (limit < count) l.push_back("...");
|
||||
QString s = l.join(" ");
|
||||
return s;
|
||||
}
|
||||
|
||||
@ -139,7 +183,36 @@ void ChunkToYaml(QFile & file, PRS1DataChunk* chunk)
|
||||
}
|
||||
|
||||
// data
|
||||
out << " data: " << byteList(chunk->m_data) << endl;
|
||||
bool dump_data = true;
|
||||
if (chunk->m_parsedData.size() > 0) {
|
||||
dump_data = false;
|
||||
out << " events:" << endl;
|
||||
for (auto & e : chunk->m_parsedData) {
|
||||
QString name = _PRS1ParsedEventName(e);
|
||||
if (name == "raw" || name == "unknown") {
|
||||
dump_data = true;
|
||||
}
|
||||
QMap<QString,QString> contents = _PRS1ParsedEventContents(e);
|
||||
if (name == "setting" && contents.size() == 1) {
|
||||
out << " - set_" << contents.firstKey() << ": " << contents.first() << endl;
|
||||
}
|
||||
else {
|
||||
out << " - " << name << ":" << endl;
|
||||
|
||||
// Always emit start first if present
|
||||
if (contents.contains("start")) {
|
||||
out << " " << "start" << ": " << contents["start"] << endl;
|
||||
}
|
||||
for (auto & key : contents.keys()) {
|
||||
if (key == "start") continue;
|
||||
out << " " << key << ": " << contents[key] << endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dump_data) {
|
||||
out << " data: " << byteList(chunk->m_data, 100) << endl;
|
||||
}
|
||||
|
||||
// data CRC
|
||||
out << " crc: " << hex << chunk->storedCrc << endl;
|
||||
@ -211,8 +284,17 @@ void parseAndEmitChunkYaml(const QString & path)
|
||||
// Parse the chunks in the file.
|
||||
QList<PRS1DataChunk *> chunks = s_loader->ParseFile(inpath);
|
||||
for (int i=0; i < chunks.size(); i++) {
|
||||
// Emit the YAML.
|
||||
PRS1DataChunk * chunk = chunks.at(i);
|
||||
|
||||
// Parse the inner data.
|
||||
switch (chunk->ext) {
|
||||
case 0: chunk->ParseCompliance(); break;
|
||||
case 1: chunk->ParseSummary(); break;
|
||||
case 2: chunk->ParseEvents(MODE_UNKNOWN); break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
// Emit the YAML.
|
||||
ChunkToYaml(file, chunk);
|
||||
delete chunk;
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ class PRS1Tests : public QObject
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void testMachineSupport();
|
||||
void testChunksToYaml();
|
||||
void testSessionsToYaml();
|
||||
// void test2();
|
||||
|
@ -133,22 +133,26 @@ static QString eventChannel(ChannelID i)
|
||||
return s;
|
||||
}
|
||||
|
||||
static QString intList(EventStoreType* data, int count)
|
||||
static QString intList(EventStoreType* data, int count, int limit=-1)
|
||||
{
|
||||
if (limit == -1 || limit > count) limit = count;
|
||||
QStringList l;
|
||||
for (int i = 0; i < count; i++) {
|
||||
for (int i = 0; i < limit; i++) {
|
||||
l.push_back(QString::number(data[i]));
|
||||
}
|
||||
if (limit < count) l.push_back("...");
|
||||
QString s = "[ " + l.join(",") + " ]";
|
||||
return s;
|
||||
}
|
||||
|
||||
static QString intList(quint32* data, int count)
|
||||
static QString intList(quint32* data, int count, int limit=-1)
|
||||
{
|
||||
if (limit == -1 || limit > count) limit = count;
|
||||
QStringList l;
|
||||
for (int i = 0; i < count; i++) {
|
||||
for (int i = 0; i < limit; i++) {
|
||||
l.push_back(QString::number(data[i] / 1000));
|
||||
}
|
||||
if (limit < count) l.push_back("...");
|
||||
QString s = "[ " + l.join(",") + " ]";
|
||||
return s;
|
||||
}
|
||||
@ -174,7 +178,14 @@ void SessionToYaml(QString filepath, Session* session)
|
||||
QList<ChannelID> keys = session->settings.keys();
|
||||
std::sort(keys.begin(), keys.end());
|
||||
for (QList<ChannelID>::iterator key = keys.begin(); key != keys.end(); key++) {
|
||||
out << " " << settingChannel(*key) << ": " << session->settings[*key].toString() << endl;
|
||||
QVariant & value = session->settings[*key];
|
||||
QString s;
|
||||
if ((QMetaType::Type) value.type() == QMetaType::Float) {
|
||||
s = QString::number(value.toFloat()); // Print the shortest accurate representation rather than QVariant's full precision.
|
||||
} else {
|
||||
s = value.toString();
|
||||
}
|
||||
out << " " << settingChannel(*key) << ": " << s << endl;
|
||||
}
|
||||
|
||||
out << " events:" << endl;
|
||||
@ -209,15 +220,15 @@ void SessionToYaml(QString filepath, Session* session)
|
||||
out << " data:" << endl;
|
||||
out << " min: " << e.Min() << endl;
|
||||
out << " max: " << e.Max() << endl;
|
||||
out << " raw: " << intList((EventStoreType*) e.m_data.data(), e.count()) << endl;
|
||||
out << " raw: " << intList((EventStoreType*) e.m_data.data(), e.count(), 100) << endl;
|
||||
if (e.type() != EVL_Waveform) {
|
||||
out << " delta: " << intList((quint32*) e.m_time.data(), e.count()) << endl;
|
||||
out << " delta: " << intList((quint32*) e.m_time.data(), e.count(), 100) << endl;
|
||||
}
|
||||
if (e.hasSecondField()) {
|
||||
out << " data2:" << endl;
|
||||
out << " min: " << e.min2() << endl;
|
||||
out << " max: " << e.max2() << endl;
|
||||
out << " raw: " << intList((EventStoreType*) e.m_data2.data(), e.count()) << endl;
|
||||
out << " raw: " << intList((EventStoreType*) e.m_data2.data(), e.count(), 100) << endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user