OSCAR-code/SleepLib/loader_plugins/prs1_loader.cpp
Mark Watkins a310caa928 Mega MachineCode enum replacement.
Importer will be temporarily slow due to creating craploads of new indexes. Lots of bugs fixed.. Probably lots of new bugs too!

Replaced slow map containers with fast QHash containers.
Plus plenty of other mind numbing stuff.
2011-08-01 06:24:43 +10:00

1334 lines
46 KiB
C++

/*
SleepLib PRS1 Loader Implementation
Author: Mark Watkins <jedimark64@users.sourceforge.net>
License: GPL
*/
#include <QApplication>
#include <QString>
#include <QDateTime>
#include <QDir>
#include <QFile>
#include <QMessageBox>
#include <QProgressBar>
#include <QDebug>
#include <cmath>
#include "prs1_loader.h"
#include "SleepLib/session.h"
const int PRS1_MAGIC_NUMBER=2;
const int PRS1_SUMMARY_FILE=1;
const int PRS1_EVENT_FILE=2;
const int PRS1_WAVEFORM_FILE=5;
//********************************************************************************************
/// IMPORTANT!!!
//********************************************************************************************
// Please INCREMENT the prs1_data_version in prs1_loader.h when making changes to this loader
// that change loader behaviour or modify channels.
//********************************************************************************************
extern QProgressBar *qprogress;
QHash<int,QString> ModelMap;
PRS1::PRS1(Profile *p,MachineID id):CPAP(p,id)
{
m_class=prs1_class_name;
properties["Brand"]="Philips Respironics";
properties["Model"]="System One";
//SleepFlags= { CPAP_RERA, PRS1_VSnore2, CPAP_FlowLimit, CPAP_Hypopnea, CPAP_Obstructive, CPAP_ClearAirway, CPAP_CSR };
}
PRS1::~PRS1()
{
}
PRS1Loader::PRS1Loader()
{
m_buffer=NULL;
}
PRS1Loader::~PRS1Loader()
{
for (QHash<QString,Machine *>::iterator i=PRS1List.begin(); i!=PRS1List.end(); i++) {
delete i.value();
}
}
Machine *PRS1Loader::CreateMachine(QString serial,Profile *profile)
{
if (!profile)
return NULL;
qDebug() << "Create Machine " << serial;
QVector<Machine *> ml=profile->GetMachines(MT_CPAP);
bool found=false;
QVector<Machine *>::iterator i;
for (i=ml.begin(); i!=ml.end(); i++) {
if (((*i)->GetClass()=="PRS1") && ((*i)->properties["Serial"]==serial)) {
PRS1List[serial]=*i; //static_cast<CPAP *>(*i);
found=true;
break;
}
}
if (found) return *i;
//assert(PRS1List.find(serial)==PRS1List.end())
Machine *m=new PRS1(profile,0);
PRS1List[serial]=m;
profile->AddMachine(m);
m->properties["Serial"]=serial;
return m;
}
bool isdigit(QChar c)
{
if ((c>='0') && (c<='9')) return true;
return false;
}
int PRS1Loader::Open(QString & path,Profile *profile)
{
QString newpath;
QString pseries="P-Series";
if (path.endsWith("/"+pseries)) {
newpath=path;
} else {
newpath=path+"/"+pseries;
}
QDir dir(newpath);
if ((!dir.exists() || !dir.isReadable()))
return 0;
//qDebug() << "PRS1Loader::Open newpath=" << newpath;
dir.setFilter(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Files | QDir::Hidden | QDir::NoSymLinks);
dir.setSorting(QDir::Name);
QFileInfoList flist=dir.entryInfoList();
QList<QString> SerialNumbers;
QList<QString>::iterator sn;
for (int i=0;i<flist.size();i++) {
QFileInfo fi=flist.at(i);
QString filename=fi.fileName();
if ((filename[0]=='P') && (isdigit(filename[1])) && (isdigit(filename[2]))) {
SerialNumbers.push_back(filename);
} else if (filename.toLower()=="last.txt") { // last.txt points to the current serial number
QString file=fi.canonicalFilePath();
QFile f(file);
if (!fi.isReadable()) {
qDebug() << "PRS1Loader: last.txt exists but I couldn't read it!";
continue;
}
if (!f.open(QIODevice::ReadOnly)) {
qDebug() << "PRS1Loader: last.txt exists but I couldn't open it!";
continue;
}
last=f.readLine(64);
last=last.trimmed();
f.close();
}
}
if (SerialNumbers.empty()) return 0;
m_buffer=new unsigned char [max_load_buffer_size]; //allocate once and reuse.
Machine *m;
for (sn=SerialNumbers.begin(); sn!=SerialNumbers.end(); sn++) {
QString s=*sn;
m=CreateMachine(s,profile);
try {
if (m) OpenMachine(m,newpath+"/"+(*sn),profile);
} catch(OneTypePerDay e) {
profile->DelMachine(m);
PRS1List.erase(PRS1List.find(s));
QMessageBox::warning(NULL,"Import Error","This Machine Record cannot be imported in this profile.\nThe Day records overlap with already existing content.",QMessageBox::Ok);
delete m;
}
}
delete [] m_buffer;
return PRS1List.size();
}
bool PRS1Loader::ParseProperties(Machine *m,QString filename)
{
QFile f(filename);
if (!f.open(QIODevice::ReadOnly))
return false;
QString line;
QHash<QString,QString> prop;
QString s=f.readLine();
QChar sep='=';
QString key,value;
while (!f.atEnd()) {
key=s.section(sep,0,0); //BeforeFirst(sep);
if (key==s) continue;
value=s.section(sep,1).trimmed(); //AfterFirst(sep).Strip();
if (value==s) continue;
prop[key]=value;
s=f.readLine();
}
bool ok;
QString pt=prop["ProductType"];
int i=pt.toInt(&ok,0);
if (ok) {
if (ModelMap.find(i)!=ModelMap.end()) {
m->properties["SubModel"]=ModelMap[i];
}
}
if (prop["SerialNumber"]!=m->properties["Serial"]) {
qDebug() << "Serial Number in PRS1 properties.txt doesn't match directory structure";
} else prop.erase(prop.find("SerialNumber")); // already got it stored.
for (QHash<QString,QString>::iterator i=prop.begin(); i!=prop.end(); i++) {
m->properties[i.key()]=i.value();
}
f.close();
return true;
}
int PRS1Loader::OpenMachine(Machine *m,QString path,Profile *profile)
{
qDebug() << "Opening PRS1 " << path;
QDir dir(path);
if (!dir.exists() || (!dir.isReadable()))
return false;
dir.setFilter(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Files | QDir::Hidden | QDir::NoSymLinks);
dir.setSorting(QDir::Name);
QFileInfoList flist=dir.entryInfoList();
QString filename;
if (qprogress) qprogress->setValue(0);
QList<QString> paths;
for (int i=0;i<flist.size();i++) {
QFileInfo fi=flist.at(i);
filename=fi.fileName();
if ((filename[0].toLower()=='p') && (isdigit(filename[1]))) {
paths.push_back(fi.canonicalFilePath());
} else if (filename.toLower()=="properties.txt") {
ParseProperties(m,fi.canonicalFilePath());
} else if (filename.toLower()=="e") {
// don't really give a crap about .004 files yet.
}
//if (qprogress) qprogress->Pulse();
}
SessionID session;
long ext;
typedef QVector<QString> StringList;
QHash<SessionID,StringList> sessfiles;
int size=paths.size();
int cnt=0;
bool ok;
for (QList<QString>::iterator p=paths.begin(); p!=paths.end(); p++) {
dir.setPath(*p);
if (!dir.exists() || !dir.isReadable()) continue;
flist=dir.entryInfoList();
for (int i=0;i<flist.size();i++) {
QFileInfo fi=flist.at(i);
QString ext_s=fi.fileName().section(".",-1);
QString session_s=fi.fileName().section(".",0,-2);
ext=ext_s.toLong(&ok);
if (!ok) continue;
session=session_s.toLong(&ok);
if (!ok) continue;
if (sessfiles[session].capacity()==0) sessfiles[session].resize(3);
if (ext==1) {
sessfiles[session][0]=fi.canonicalFilePath();
} else if (ext==2) {
sessfiles[session][1]=fi.canonicalFilePath();
} else if (ext==5) {
sessfiles[session][2]=fi.canonicalFilePath();
}
cnt++;
if (qprogress) qprogress->setValue((float(cnt)/float(size)*33.0));
QApplication::processEvents();
}
}
size=sessfiles.size();
if (size==0)
return 0;
cnt=0;
for (QHash<SessionID,StringList>::iterator s=sessfiles.begin(); s!=sessfiles.end(); s++) {
session=s.key();
cnt++;
if (qprogress) qprogress->setValue(33.0+(float(cnt)/float(size)*33.0));
QApplication::processEvents();
if (m->SessionExists(session)) continue;
if (s.value()[0].isEmpty()) continue;
Session *sess=new Session(m,session);
if (!OpenSummary(sess,s.value()[0])) {
//qWarning() << "PRS1Loader: Dodgy summary file " << s.value()[0];
delete sess;
continue;
}
//sess->SetSessionID(sess->start().GetTicks());
if (!s.value()[1].isEmpty()) {
if (!OpenEvents(sess,s.value()[1])) {
qWarning() << "PRS1Loader: Couldn't open event file " << s.value()[1];
}
}
if (!s.value()[2].isEmpty()) {
if (!OpenWaveforms(sess,s.value()[2])) {
qWarning() << "PRS1Loader: Couldn't open event file " << s.value()[2];
}
}
const double ignore_thresh=300.0/3600.0;// Ignore useless sessions under 5 minute
if (sess->hours()<=ignore_thresh) {
qDebug() << "Ignoring short session" << session << "which is only" << (sess->hours()*60.0) << "minute(s) long";
delete sess;
continue;
}
ChannelID e[]={
CPAP_Obstructive, CPAP_Hypopnea, CPAP_ClearAirway, CPAP_RERA, CPAP_FlowLimit, CPAP_VSnore,
CPAP_CSR, PRS1_VSnore2
};
for (unsigned i=0;i<sizeof(e)/sizeof(ChannelID);i++) {
sess->count(e[i]);
sess->max(e[i]);
sess->min(e[i]);
sess->avg(e[i]);
sess->p90(e[i]);
sess->cph(e[i]);
sess->sph(e[i]);
}
sess->setCph(CPAP_AHI,sess->cph(CPAP_Obstructive)+sess->cph(CPAP_Hypopnea)+sess->cph(CPAP_ClearAirway));
sess->setSph(CPAP_AHI,sess->sph(CPAP_Obstructive)+sess->sph(CPAP_Hypopnea)+sess->sph(CPAP_ClearAirway));
ChannelID a[]={
CPAP_FlowRate, CPAP_MaskPressure, CPAP_Leak, CPAP_Snore, CPAP_EPAP,
CPAP_IPAP, CPAP_TidalVolume, CPAP_RespiratoryRate,
CPAP_PatientTriggeredBreaths,CPAP_MinuteVentilation, CPAP_IPAP_Low,
CPAP_IPAP_High, CPAP_FlowLimitGraph, CPAP_PressureSupport, CPAP_PressurePulse
};
for (unsigned i=0;i<sizeof(a)/sizeof(ChannelID);i++) {
if (sess->eventlist.contains(a[i])) {
sess->min(a[i]);
sess->max(a[i]);
sess->avg(a[i]);
sess->wavg(a[i]);
sess->p90(a[i]);
sess->cph(a[i]);
}
}
if (sess->count(CPAP_IPAP)>0) {
//sess->summaryCPAP_Mode]!=MODE_ASV)
sess->settings[CPAP_Mode]=MODE_BIPAP;
if (sess->settings[PRS1_PressureReliefType].toInt()!=PR_NONE) {
sess->settings[PRS1_PressureReliefType]=PR_BIFLEX;
}
sess->setAvg(CPAP_Pressure,(sess->avg(CPAP_EPAP)+sess->avg(CPAP_IPAP))/2.0);
sess->setWavg(CPAP_Pressure,(sess->wavg(CPAP_EPAP)+sess->wavg(CPAP_IPAP))/2.0);
sess->setMin(CPAP_Pressure,sess->min(CPAP_EPAP));
sess->setMax(CPAP_Pressure,sess->min(CPAP_IPAP));
sess->set90p(CPAP_Pressure,sess->min(CPAP_IPAP));
sess->p90(CPAP_EPAP);
sess->p90(CPAP_IPAP);
} else {
sess->avg(CPAP_Pressure);
sess->wavg(CPAP_Pressure);
sess->p90(CPAP_Pressure);
sess->min(CPAP_Pressure);
sess->max(CPAP_Pressure);
sess->cph(CPAP_Pressure);
if (!sess->settings.contains(PRS1_PressureMin)) {
sess->settings[CPAP_BrokenSummary]=true;
//sess->set_last(sess->first());
if (sess->min(CPAP_Pressure)==sess->max(CPAP_Pressure)) {
sess->settings[CPAP_Mode]=MODE_CPAP; // no ramp
} else {
sess->settings[CPAP_Mode]=MODE_UNKNOWN;
}
sess->settings[PRS1_PressureReliefType]=PR_UNKNOWN;
}
}
if (sess->settings[CPAP_Mode]==MODE_CPAP) {
sess->settings[PRS1_PressureMax]=sess->settings[PRS1_PressureMin];
}
//Printf(sess->start().Format()+wxT(" avgsummary=%.3f avgmine=%.3f\n"),sess->summary[CPAP_PressureAverage].GetDouble(),sess->weighted_avg_event_field(CPAP_Pressure,0));
sess->SetChanged(true);
m->AddSession(sess,profile);
}
QString s;
s.sprintf("%i",prs1_data_version);
m->properties["DataVersion"]=s;
m->Save(); // Save any new sessions to disk in our format */
if (qprogress) qprogress->setValue(100);
//qDebug() << "OpenMachine Done";
return true;
}
bool PRS1Loader::OpenSummary(Session *session,QString filename)
{
int size,seconds,br,htype,version,sequence;
qint64 timestamp;
unsigned char header[24];
unsigned char ext,sum;
//qDebug() << "Opening PRS1 Summary " << filename;
QFile f(filename);
if (!f.open(QIODevice::ReadOnly))
return false;
if (!f.exists())
return false;
int hl=16;
br=f.read((char *)header,hl);
if (header[0]!=PRS1_MAGIC_NUMBER)
return false;
sequence=size=timestamp=seconds=ext=0;
sequence=(header[10] << 24) | (header[9] << 16) | (header[8] << 8) | header[7];
timestamp=(header[14] << 24) | (header[13] << 16) | (header[12] << 8) | header[11];
size=(header[2] << 8) | header[1];
ext=header[6];
htype=header[3]; // 00 = normal // 01=waveform // could be a bool?
version=header[4];
sequence=sequence;
version=version; // don't need it here?
htype=htype; // shut the warning up.. this is useless.
if (ext!=PRS1_SUMMARY_FILE)
return false;
size-=(hl+2);
// Calculate header checksum and compare to verify header
sum=0;
for (int i=0; i<hl-1; i++) sum+=header[i];
if (sum!=header[hl-1])
return false;
if (size<=19) {
// qDebug() << "Ignoring short session file " << filename;
return false;
}
qint64 date=timestamp*1000;
//memset(m_buffer,0,size);
unsigned char * buffer=m_buffer;
br=f.read((char *)buffer,size);
if (br<size) {
return false;
}
if (size<0x30)
return true;
session->set_first(date);
double max;
session->settings[PRS1_PressureMin]=(EventDataType)buffer[0x03]/10.0;
session->settings[PRS1_PressureMax]=max=(EventDataType)buffer[0x04]/10.0;
int offset=0;
if (buffer[0x05]!=0) { // This is a time value for ASV stuff
// non zero adds extra fields..
offset=4;
}
session->settings[PRS1_RampTime]=(int)buffer[offset+0x06]; // Minutes. Convert to seconds/hours here?
session->settings[PRS1_RampPressure]=(EventDataType)buffer[offset+0x07]/10.0;
if (max>0) { // Ignoring bipap until I see some more data.
session->settings[CPAP_Mode]=(int)MODE_APAP;
} else session->settings[CPAP_Mode]=(int)MODE_CPAP;
// This is incorrect..
if (buffer[offset+0x08] & 0x80) { // Flex Setting
if (buffer[offset+0x08] & 0x08) {
if (max>0) session->settings[PRS1_PressureReliefType]=(int)PR_AFLEX;
else session->settings[PRS1_PressureReliefType]=(int)PR_CFLEXPLUS;
} else session->settings[PRS1_PressureReliefType]=(int)PR_CFLEX;
} else session->settings[PRS1_PressureReliefType]=(int)PR_NONE;
session->settings[PRS1_PressureReliefSetting]=(int)buffer[offset+0x08] & 3;
session->settings[PRS1_HumidifierSetting]=(int)buffer[offset+0x09]&0x0f;
session->settings[PRS1_HumidifierStatus]=(buffer[offset+0x09]&0x80)==0x80;
session->settings[PRS1_SystemLockStatus]=(buffer[offset+0x0a]&0x80)==0x80;
session->settings[PRS1_SystemOneResistanceStatus]=(buffer[offset+0x0a]&0x40)==0x40;
session->settings[PRS1_SystemOneResistanceSetting]=(int)buffer[offset+0x0a]&7;
session->settings[PRS1_HoseDiameter]=(int)((buffer[offset+0x0a]&0x08)?15:22);
session->settings[PRS1_AutoOff]=(buffer[offset+0x0c]&0x10)==0x10;
session->settings[PRS1_MaskAlert]=(buffer[offset+0x0c]&0x08)==0x08;
session->settings[PRS1_ShowAHI]=(buffer[offset+0x0c]&0x04)==0x04;
unsigned duration=buffer[offset+0x14] | (buffer[0x15] << 8);
//session->settings[CPAP_Duration]=(int)duration;
//qDebug() << "ID: " << session->session() << " " << duration;
//float hours=float(duration)/3600.0;
//session->set_hours(hours);
if (!duration)
return false;
session->set_last(date+qint64(duration)*1000L);
session->settings[PRS1_PressureMinAchieved]=buffer[offset+0x16]/10.0;
session->settings[PRS1_PressureMaxAchieved]=buffer[offset+0x17]/10.0;
session->settings[PRS1_PressureAvg]=buffer[offset+0x18]/10.0;
session->settings[PRS1_Pressure90]=buffer[offset+0x19]/10.0;
if (max==0) {
session->settings[PRS1_PressureAvg]=session->settings[PRS1_PressureMin];
}
// Not using these because sometimes this summary is broken.
/*/if (size==0x4d) {
session->settings[CPAP_Obstructive]=(int)buffer[offset+0x1C] | (buffer[offset+0x1D] << 8);
session->settings[CPAP_ClearAirway]=(int)buffer[offset+0x20] | (buffer[offset+0x21] << 8);
session->settings[CPAP_Hypopnea]=(int)buffer[offset+0x2A] | (buffer[offset+0x2B] << 8);
session->settings[CPAP_RERA]=(int)buffer[offset+0x2E] | (buffer[offset+0x2F] << 8);
session->settings[CPAP_FlowLimit]=(int)buffer[offset+0x30] | (buffer[offset+0x31] << 8);
}*/
return true;
}
// v2 event parser.
bool PRS1Loader::Parse002(Session *session,unsigned char *buffer,int size,qint64 timestamp)
{
ChannelID Codes[]={
PRS1_Unknown00, PRS1_Unknown01, CPAP_Pressure, CPAP_EPAP, CPAP_PressurePulse, CPAP_RERA, CPAP_Obstructive, CPAP_ClearAirway,
PRS1_Unknown08, PRS1_Unknown09, CPAP_Hypopnea, PRS1_Unknown0B, CPAP_FlowLimit, CPAP_VSnore, PRS1_Unknown0E, CPAP_CSR, PRS1_Unknown10,
CPAP_Leak, PRS1_Unknown12
};
int ncodes=sizeof(Codes)/sizeof(ChannelID);
EventList * Code[0x20]={NULL};
EventDataType data[10];
session->updateFirst(timestamp);
//qint64 start=timestamp;
qint64 t=timestamp;
qint64 tt;
int pos=0;
int cnt=0;
short delta;//,duration;
while (pos<size) {
unsigned char code=buffer[pos++];
if (code>=ncodes) {
qDebug() << "Illegal PRS1 code " << hex << int(code) << " appeared at " << hex << pos+16;
return false;
}
//if (code==0xe) {
// pos+=2;
//} else
delta=0;
if (code!=0x12) {
//delta=buffer[pos];
//duration=buffer[pos+1];
delta=buffer[pos+1] << 8 | buffer[pos];
pos+=2;
t+=delta*1000;
tt=t;//+(delta*1000);
//QDateTime d=QDateTime::fromMSecsSinceEpoch(t);
//qDebug()<< d.toString("yyyy-MM-dd HH:mm:ss") << ": " << hex << pos+15 << " " << hex << int(code) << int(delta);
}
cnt++;
//int fc=0;
switch (code) {
case 0x00: // Unknown 00
if (!Code[0]) {
Code[0]=new EventList(PRS1_Unknown00,EVL_Event);
session->eventlist[PRS1_Unknown00].push_back(Code[0]);
}
Code[0]->AddEvent(t,buffer[pos++]);
break;
case 0x01: // Unknown
if (!Code[1]) {
Code[1]=new EventList(PRS1_Unknown01,EVL_Event);
session->eventlist[PRS1_Unknown01].push_back(Code[1]);
}
Code[1]->AddEvent(t,0);
break;
case 0x02: // Pressure
if (!Code[2]) {
Code[2]=new EventList(CPAP_Pressure,EVL_Event,0.1);
session->eventlist[CPAP_Pressure].push_back(Code[2]);
}
Code[2]->AddEvent(t,buffer[pos++]);
break;
case 0x03: // BIPAP Pressure
if (!Code[3]) {
Code[3]=new EventList(CPAP_EPAP,EVL_Event,0.1);
session->eventlist[CPAP_EPAP].push_back(Code[3]);
Code[4]=new EventList(CPAP_IPAP,EVL_Event,0.1);
session->eventlist[CPAP_IPAP].push_back(Code[4]);
Code[5]=new EventList(CPAP_PressureSupport,EVL_Event,0.1);
session->eventlist[CPAP_PressureSupport].push_back(Code[5]);
}
Code[3]->AddEvent(t,data[0]=buffer[pos++]);
Code[4]->AddEvent(t,data[1]=buffer[pos++]);
Code[5]->AddEvent(t,data[1]-data[0]);
break;
case 0x04: // Pressure Pulse
if (!Code[6]) {
Code[6]=new EventList(CPAP_PressurePulse,EVL_Event);
session->eventlist[CPAP_PressurePulse].push_back(Code[6]);
}
Code[6]->AddEvent(t,buffer[pos++]);
//qDebug() << hex << data[0];
break;
case 0x05: // RERA
data[0]=buffer[pos++];
tt=t-(data[0]*1000);
if (!Code[7]) {
Code[7]=new EventList(CPAP_RERA,EVL_Event);
session->eventlist[CPAP_RERA].push_back(Code[7]);
}
Code[7]->AddEvent(tt,data[0]);
break;
case 0x06: // Obstructive Apoanea
data[0]=buffer[pos++];
tt=t-(data[0]*1000);
if (!Code[8]) {
Code[8]=new EventList(CPAP_Obstructive,EVL_Event);
session->eventlist[CPAP_Obstructive].push_back(Code[8]);
}
Code[8]->AddEvent(tt,data[0]);
break;
case 0x07: // Clear Airway
data[0]=buffer[pos++];
tt=t-(data[0]*1000);
if (!Code[9]) {
Code[9]=new EventList(CPAP_ClearAirway,EVL_Event);
session->eventlist[CPAP_ClearAirway].push_back(Code[9]);
}
Code[9]->AddEvent(tt,data[0]);
break;
case 0x0a: // Hypopnea
data[0]=buffer[pos++];
tt=t-(data[0]*1000);
if (!Code[10]) {
Code[10]=new EventList(CPAP_Hypopnea,EVL_Event);
session->eventlist[CPAP_Hypopnea].push_back(Code[10]);
}
Code[10]->AddEvent(tt,data[0]);
break;
case 0x0c: // Flow Limitation
data[0]=buffer[pos++];
tt=t-(data[0]*1000);
if (!Code[11]) {
Code[11]=new EventList(CPAP_FlowLimit,EVL_Event);
session->eventlist[CPAP_FlowLimit].push_back(Code[11]);
}
Code[11]->AddEvent(tt,data[0]);
break;
case 0x0b: // Hypopnea related code
data[0]=buffer[pos++];
data[1]=buffer[pos++];
if (!Code[12]) {
Code[12]=new EventList(PRS1_Unknown0B,EVL_Event);
session->eventlist[PRS1_Unknown0B].push_back(Code[12]);
}
// FIXME
Code[12]->AddEvent(t,data[0]);
break;
case 0x0d: // Vibratory Snore
if (!Code[13]) {
Code[13]=new EventList(CPAP_VSnore,EVL_Event);
session->eventlist[CPAP_VSnore].push_back(Code[13]);
}
Code[13]->AddEvent(t,0);
break;
case 0x11: // Leak Rate & Snore Graphs
data[0]=buffer[pos++];
data[1]=buffer[pos++];
if (!Code[14]) {
Code[14]=new EventList(CPAP_Leak,EVL_Event);
session->eventlist[CPAP_Leak].push_back(Code[14]);
Code[15]=new EventList(CPAP_Snore,EVL_Event);
session->eventlist[CPAP_Snore].push_back(Code[15]);
}
Code[14]->AddEvent(t,data[0]);
Code[15]->AddEvent(t,data[1]);
if (data[1]>0) {
if (!Code[16]) {
Code[16]=new EventList(PRS1_VSnore2,EVL_Event);
session->eventlist[PRS1_VSnore2].push_back(Code[16]);
}
Code[16]->AddEvent(t,data[1]);
}
break;
case 0x0e: // Unknown
data[0]=((char *)buffer)[pos++];
data[1]=buffer[pos++]; //(buffer[pos+1] << 8) | buffer[pos];
//data[0]/=10.0;
//pos+=2;
data[2]=buffer[pos++];
if (!Code[17]) {
Code[17]=new EventList(PRS1_Unknown0E,EVL_Event);
session->eventlist[PRS1_Unknown0E].push_back(Code[17]);
}
Code[17]->AddEvent(t,data[0]);
//qDebug() << hex << data[0] << data[1] << data[2];
//session->AddEvent(new Event(t,cpapcode, 0, data, 3));
//tt-=data[1]*1000;
//session->AddEvent(new Event(t,CPAP_CSR, data, 2));
break;
case 0x10: // Unknown
data[0]=buffer[pos++]; // << 8) | buffer[pos];
data[1]=buffer[pos++];
data[2]=buffer[pos++];
if (!Code[20]) {
Code[20]=new EventList(PRS1_Unknown10,EVL_Event);
session->eventlist[PRS1_Unknown10].push_back(Code[20]);
}
Code[20]->AddEvent(t,data[0]);
break;
case 0x0f: // Cheyne Stokes Respiration
data[0]=buffer[pos+1]<<8 | buffer[pos];
pos+=2;
data[1]=buffer[pos++];
tt=t-data[1]*1000;
if (!Code[23]) {
Code[23]=new EventList(CPAP_CSR,EVL_Event);
session->eventlist[CPAP_CSR].push_back(Code[23]);
}
Code[23]->AddEvent(tt,data[0]);
break;
case 0x12: // Summary
data[0]=buffer[pos++];
data[1]=buffer[pos++];
data[2]=buffer[pos+1]<<8 | buffer[pos];
pos+=2;
if (!Code[24]) {
Code[24]=new EventList(PRS1_Unknown12,EVL_Event);
session->eventlist[PRS1_Unknown12].push_back(Code[24]);
}
Code[24]->AddEvent(t,data[0]);
break;
default:
// ERROR!!!
qWarning() << "Some new fandangled PRS1 code detected " << hex << int(code) << " at " << pos+15;
return false;
}
}
session->updateLast(t);
return true;
}
bool PRS1Loader::Parse002ASV(Session *session,unsigned char *buffer,int size,qint64 timestamp)
{
ChannelID Codes[]={
PRS1_Unknown00, PRS1_Unknown01, CPAP_Pressure, CPAP_EPAP, CPAP_PressurePulse, CPAP_Obstructive,
CPAP_ClearAirway, CPAP_Hypopnea, PRS1_Unknown08, CPAP_FlowLimit, PRS1_Unknown0A, CPAP_CSR,
PRS1_Unknown0C, CPAP_VSnore, PRS1_Unknown0E, PRS1_Unknown0F, PRS1_Unknown10,
CPAP_Leak, PRS1_Unknown12
};
int ncodes=sizeof(Codes)/sizeof(ChannelID);
EventList * Code[0x20]={NULL};
//for (int i=0;i<0x20;i++) Code[i]=NULL;
session->updateFirst(timestamp);
EventDataType data[10];
//qint64 start=timestamp;
qint64 t=timestamp;
qint64 tt;
int pos=0;
int cnt=0;
short delta;//,duration;
QDateTime d;
while (pos<size) {
unsigned char code=buffer[pos++];
if (code>=ncodes) {
qDebug() << "Illegal PRS1 code " << hex << int(code) << " appeared at " << hex << pos+16;
return false;
}
//QDateTime d=QDateTime::fromMSecsSinceEpoch(t);
//qDebug()<< d.toString("yyyy-MM-dd HH:mm:ss") << ": " << hex << pos+15 << " " << hex << int(code) ;
if (code==0) {
} else
if (code!=0x12) {
delta=buffer[pos];
//duration=buffer[pos+1];
//delta=buffer[pos+1] << 8 | buffer[pos];
pos+=2;
t+=delta*1000;
}
ChannelID cpapcode=Codes[(int)code];
//EventDataType PS;
tt=t;
cnt++;
int fc=0;
switch (code) {
case 0x00: // Unknown (ASV Pressure value)
// offset?
data[0]=buffer[pos++];
fc++;
if (!buffer[pos-1]) { // WTH???
data[1]=buffer[pos++];
fc++;
}
if (!buffer[pos-1]) {
data[2]=buffer[pos++];
fc++;
}
break;
case 0x01: // Unknown
if (!Code[1]) {
Code[1]=new EventList(cpapcode,EVL_Event,0.1);
session->eventlist[cpapcode].push_back(Code[1]);
}
Code[1]->AddEvent(t,0);
break;
case 0x02: // Pressure
data[0]=buffer[pos++];
if (!Code[2]) {
Code[2]=new EventList(cpapcode,EVL_Event,0.1);
session->eventlist[cpapcode].push_back(Code[2]);
}
Code[2]->AddEvent(t,data[0]);
break;
case 0x04: // Pressure Pulse
data[0]=buffer[pos++];
if (!Code[3]) {
Code[3]=new EventList(cpapcode,EVL_Event);
session->eventlist[cpapcode].push_back(Code[3]);
}
Code[3]->AddEvent(t,data[0]);
break;
case 0x05:
data[0]=buffer[pos++];
tt-=data[0]*1000; // Subtract Time Offset
if (!Code[4]) {
Code[4]=new EventList(cpapcode,EVL_Event);
session->eventlist[cpapcode].push_back(Code[4]);
}
Code[4]->AddEvent(tt,data[0]);
break;
case 0x06:
data[0]=buffer[pos++];
tt-=data[0]*1000; // Subtract Time Offset
if (!Code[5]) {
Code[5]=new EventList(cpapcode,EVL_Event);
session->eventlist[cpapcode].push_back(Code[5]);
}
Code[5]->AddEvent(tt,data[0]);
break;
case 0x07:
data[0]=buffer[pos++];
tt-=data[0]*1000; // Subtract Time Offset
if (!Code[6]) {
Code[6]=new EventList(cpapcode,EVL_Event);
session->eventlist[cpapcode].push_back(Code[6]);
}
Code[6]->AddEvent(tt,data[0]);
break;
case 0x08: // ASV Codes
data[0]=buffer[pos++];
tt-=data[0]*1000; // Subtract Time Offset
if (!Code[10]) {
Code[10]=new EventList(cpapcode,EVL_Event);
session->eventlist[cpapcode].push_back(Code[10]);
}
Code[10]->AddEvent(tt,data[0]);
break;
case 0x09: // ASV Codes
data[0]=buffer[pos++];
tt-=data[0]*1000; // Subtract Time Offset
if (!Code[11]) {
Code[11]=new EventList(cpapcode,EVL_Event);
session->eventlist[cpapcode].push_back(Code[11]);
}
Code[11]->AddEvent(tt,data[0]);
break;
case 0x0a:
data[0]=buffer[pos++];
tt-=data[0]*1000; // Subtract Time Offset
if (!Code[7]) {
Code[7]=new EventList(cpapcode,EVL_Event);
session->eventlist[cpapcode].push_back(Code[7]);
}
Code[7]->AddEvent(tt,data[0]);
break;
case 0x0c:
data[0]=buffer[pos++];
tt-=data[0]*1000; // Subtract Time Offset
if (!Code[8]) {
Code[8]=new EventList(cpapcode,EVL_Event);
session->eventlist[cpapcode].push_back(Code[8]);
}
Code[8]->AddEvent(tt,data[0]);
break;
case 0x0b: // Cheyne Stokes
data[0]=((unsigned char *)buffer)[pos+1]<<8 | ((unsigned char *)buffer)[pos];
//data[0]*=2;
pos+=2;
data[1]=((unsigned char *)buffer)[pos]; //|buffer[pos+1] << 8
pos+=1;
//tt-=delta;
tt-=data[1]*1000;
if (!Code[9]) {
Code[9]=new EventList(cpapcode,EVL_Event,2.0);
session->eventlist[cpapcode].push_back(Code[9]);
}
Code[9]->AddEvent(tt,data[0]);
//session->AddEvent(new Event(tt,cpapcode, data[0], data, 2));
break;
case 0x0d: // All the other ASV graph stuff.
if (!Code[12]) {
Code[12]=new EventList(CPAP_IPAP,EVL_Event,0.1);
session->eventlist[CPAP_IPAP].push_back(Code[12]);
Code[13]=new EventList(CPAP_IPAP_Low,EVL_Event,0.1);
session->eventlist[CPAP_IPAP_Low].push_back(Code[13]);
Code[14]=new EventList(CPAP_IPAP_High,EVL_Event,0.1);
session->eventlist[CPAP_IPAP_High].push_back(Code[14]);
Code[15]=new EventList(CPAP_Leak,EVL_Event);
session->eventlist[CPAP_Leak].push_back(Code[15]);
Code[16]=new EventList(CPAP_RespiratoryRate,EVL_Event);
session->eventlist[CPAP_RespiratoryRate].push_back(Code[16]);
Code[17]=new EventList(CPAP_PatientTriggeredBreaths,EVL_Event);
session->eventlist[CPAP_PatientTriggeredBreaths].push_back(Code[17]);
Code[18]=new EventList(CPAP_MinuteVentilation,EVL_Event);
session->eventlist[CPAP_MinuteVentilation].push_back(Code[18]);
Code[19]=new EventList(CPAP_TidalVolume,EVL_Event,10.0);
session->eventlist[CPAP_TidalVolume].push_back(Code[19]);
Code[20]=new EventList(CPAP_Snore,EVL_Event);
session->eventlist[CPAP_Snore].push_back(Code[20]);
Code[22]=new EventList(CPAP_EPAP,EVL_Event,0.1);
session->eventlist[CPAP_EPAP].push_back(Code[22]);
Code[23]=new EventList(CPAP_PressureSupport,EVL_Event,0.1);
session->eventlist[CPAP_PressureSupport].push_back(Code[23]);
}
Code[12]->AddEvent(t,data[0]=buffer[pos++]); // IAP
Code[13]->AddEvent(t,buffer[pos++]); // IAP Low
Code[14]->AddEvent(t,buffer[pos++]); // IAP High
Code[15]->AddEvent(t,buffer[pos++]); // LEAK
Code[16]->AddEvent(t,buffer[pos++]); // Breaths Per Minute
Code[17]->AddEvent(t,buffer[pos++]); // Patient Triggered Breaths
Code[18]->AddEvent(t,buffer[pos++]); // Minute Ventilation
Code[19]->AddEvent(t,buffer[pos++]); // Tidal Volume
Code[20]->AddEvent(t,data[2]=buffer[pos++]); // Snore
if (data[2]>0) {
if (!Code[21]) {
Code[21]=new EventList(CPAP_VSnore,EVL_Event);
session->eventlist[CPAP_VSnore].push_back(Code[21]);
}
Code[21]->AddEvent(t,0); //data[2]); // VSnore
}
Code[22]->AddEvent(t,data[1]=buffer[pos++]); // EPAP
Code[23]->AddEvent(t,data[0]-data[1]); // Pressure Support
break;
case 0x03: // BIPAP Pressure
qDebug() << "0x03 Observed in ASV data!!????";
data[0]=buffer[pos++];
data[1]=buffer[pos++];
/*data[0]/=10.0;
data[1]/=10.0;
session->AddEvent(new Event(t,CPAP_EAP, 0, data, 1));
session->AddEvent(new Event(t,CPAP_IAP, 0, &data[1], 1)); */
break;
case 0x11: // Not Leak Rate
qDebug() << "0x11 Observed in ASV data!!????";
//if (!Code[24]) {
// Code[24]=new EventList(cpapcode,EVL_Event);
//}
//Code[24]->AddEvent(t,buffer[pos++]);
break;
case 0x0e: // Unknown
qDebug() << "0x0E Observed in ASV data!!????";
data[0]=buffer[pos++]; // << 8) | buffer[pos];
//session->AddEvent(new Event(t,cpapcode, 0, data, 1));
break;
case 0x10: // Unknown
qDebug() << "0x10 Observed in ASV data!!????";
data[0]=buffer[pos++]; // << 8) | buffer[pos];
data[1]=buffer[pos++];
data[2]=buffer[pos++];
//session->AddEvent(new Event(t,cpapcode, 0, data, 3));
break;
case 0x0f:
qDebug() << "0x0f Observed in ASV data!!????";
data[0]=buffer[pos+1]<<8 | buffer[pos];
pos+=2;
data[1]=buffer[pos]; //|buffer[pos+1] << 8
pos+=1;
tt-=data[1]*1000;
//session->AddEvent(new Event(tt,cpapcode, 0, data, 2));
break;
case 0x12: // Summary
qDebug() << "0x12 Observed in ASV data!!????";
data[0]=buffer[pos++];
data[1]=buffer[pos++];
data[2]=buffer[pos+1]<<8 | buffer[pos];
pos+=2;
//session->AddEvent(new Event(t,cpapcode, 0, data,3));
break;
default:
// ERROR!!!
qWarning() << "Some new fandangled PRS1 code detected " << hex << int(code) << " at " << pos+15;
return false;
}
}
/*EventList *a;
for (int i=0;i<24;i++){
a=Code[i];
if (a) {
int v=ceil(a->max()/5);
a->setMax(v*5);
v=floor(a->min()/5);
a->setMin(v*5);
}
}*/
session->updateLast(t);
return true;
}
bool PRS1Loader::OpenEvents(Session *session,QString filename)
{
int size,sequence,seconds,br,version;
qint64 timestamp;
unsigned char header[24]; // use m_buffer?
unsigned char ext,htype;
QFile f(filename);
if (!f.open(QIODevice::ReadOnly))
return false;
int hl=16;
br=f.read((char *)header,hl);
if (header[0]!=PRS1_MAGIC_NUMBER)
return false;
sequence=size=timestamp=seconds=ext=0;
sequence=(header[10] << 24) | (header[9] << 16) | (header[8] << 8) | header[7];
timestamp=(header[14] << 24) | (header[13] << 16) | (header[12] << 8) | header[11];
size=(header[2] << 8) | header[1];
ext=header[6];
htype=header[3]; // 00 = normal // 01=waveform // could be a bool?
version=header[4];// | header[4];
htype=htype;
sequence=sequence;
if (ext!=PRS1_EVENT_FILE) // 2 == Event file
return false;
//size|=(header[3]<<16) | (header[4]<<24); // the jury is still out on the 32bitness of one. doesn't matter here anyway.
size-=(hl+2);
unsigned char sum=0;
for (int i=0; i<hl-1; i++) sum+=header[i];
if (sum!=header[hl-1]) return false;
unsigned char *buffer=(unsigned char *)m_buffer;
br=f.read((char *)buffer,size);
if (br<size) {
return false;
}
if (version==0) {
if (!Parse002(session,buffer,size,timestamp*1000L)) {
qDebug() << "Couldn't Parse PRS1 Event File " << filename;
return false;
}
} else if (version==5) {
if (!Parse002ASV(session,buffer,size,timestamp*1000L)) {
qDebug() << "Couldn't Parse PRS1 (ASV) Event File " << filename;
return false;
}
}
return true;
}
struct WaveHeaderList {
quint16 interleave;
quint8 sample_format;
WaveHeaderList(quint16 i,quint8 f){ interleave=i; sample_format=f; }
};
bool PRS1Loader::OpenWaveforms(Session *session,QString filename)
{
//int sequence,seconds,br,htype,version,numsignals;
QFile file(filename);
if (!file.open(QIODevice::ReadOnly)) {
qWarning() << "Couldn't open waveform file" << filename;
return false;
}
int pos,br,size=file.size();
if (size>max_load_buffer_size) {
qWarning() << "Waveform too big, increase max_load_buffer_size in PRS1Loader sourcecode" << filename;
return false;
}
br=file.read((char *)m_buffer,size);
if (br<size) {
qWarning() << "Couldn't read waveform data.. Disk error?" << filename;
return false;
}
// Look at the initial header and assume this header size for the lot.
if ((m_buffer[0]!=2) || (m_buffer[6]!=0x05)) {
qWarning() << "Not correct waveform format" << filename;
return false;
}
//quint8 version=m_buffer[4];
quint32 start=m_buffer[0xb] | m_buffer[0xc] << 8 | m_buffer[0xd] << 16 | m_buffer[0x0e] << 24;
session->updateFirst(qint64(start)*1000L);
quint16 num_signals=m_buffer[0x12] | m_buffer[0x13] << 8;
if (num_signals>2) {
qWarning() << "More than 2 Waveforms in " << filename;
return false;
}
pos=0x14+(num_signals-1)*3;
vector<WaveHeaderList> whl;
// add the in reverse...
for (int i=0;i<num_signals;i++) {
quint16 interleave=m_buffer[pos] | m_buffer[pos+1] << 8;
quint8 sample_format=m_buffer[pos+2];
whl.push_back(WaveHeaderList(interleave,sample_format));
pos-=3;
}
int hl=15+5+(num_signals*3);
quint16 duration,length;
quint32 timestamp,lasttimestamp;
pos=0;
int block=0;
lasttimestamp=start;
duration=0;
int corrupt=0;
char waveform[num_signals][500000];
int wlength[num_signals];
qint64 wdur[num_signals];
//EventList *evl[num_signals];
for (int i=0;i<num_signals;i++) {
wlength[i]=0;
wdur[i]=0;
//evl[i]=NULL;
//waveform[i].resize(500000);
}
//SampleFormat *wf[num_signals];
//EventList *FlowData=EventList(CPAP_FlowRate, EVL_Waveform,1,0,-128,128);
//EventList *MaskData=EventList(CPAP_MaskPressure, EVL_Waveform,1,0,-128,128);
ChannelID wc[2]={CPAP_FlowRate,CPAP_MaskPressure};
do {
timestamp=m_buffer[pos+0xb] | m_buffer[pos+0xc] << 8 | m_buffer[pos+0xd] << 16 | m_buffer[pos+0x0e] << 24;
register unsigned char sum8=0;
for (int i=0;i<hl;i++) sum8+=m_buffer[pos+i];
if (m_buffer[pos+hl]!=sum8) {
if (block==0) {
qDebug() << "Faulty Header Checksum, aborting" << filename;
return false;
}
if (++corrupt>3) {
qDebug() << "3 Faulty Waveform Headers in a row.. Aborting" << filename;
return false;
}
qDebug() << "Faulty Header Checksum, skipping block" << block;
pos+=length;
continue;
} else {
int diff=timestamp-(lasttimestamp+duration);
if (block==1) {
if (diff>0)
start-=diff;
}
length=m_buffer[pos+0x1] | m_buffer[pos+0x2] << 8; // block length in bytes
duration=m_buffer[pos+0xf] | m_buffer[pos+0x10] << 8; // block duration in seconds
if (diff<0) {
qDebug() << "Padding waveform to keep sync" << block;
//diff=abs(diff);
for (int i=0;i<num_signals;i++) {
for (int j=0;j<diff;j++) {
for (int k=0;k<whl[i].interleave;k++)
waveform[i][wlength[i]++]=0;
}
wdur[i]+=diff;
}
} else
if (diff>0 && wlength[0]>0) {
qDebug() << "Timestamp resync" << block << diff << corrupt << duration << timestamp-lasttimestamp << filename;
for (int i=0;i<num_signals;i++) {
double rate=(double(wdur[i])*1000.0)/double(wlength[i]);
double gain;
if (i==1) gain=0.1; else gain=1;
EventList *a=new EventList(wc[i],EVL_Waveform,gain,0,0,0,rate);
if (whl[i].sample_format)
a->AddWaveform(qint64(start)*1000L,(unsigned char *)waveform[i],wlength[i],qint64(wdur[i])*1000L);
else {
a->AddWaveform(qint64(start)*1000L,(char *)waveform[i],wlength[i],qint64(wdur[i])*1000L);
}
if (wc[i]==CPAP_FlowRate) {
a->setMax(120);
a->setMin(-120);
} else if (wc[i]==CPAP_MaskPressure) {
/* int v=ceil(a->max()/5);
a->setMax(v*5);
v=floor(a->min()/5);
a->setMin(v*5); */
}
session->eventlist[wc[i]].push_back(a);
session->updateLast(start+(qint64(wdur[i])*1000L));
wlength[i]=0;
wdur[i]=0;
}
start=timestamp;
corrupt=0;
}
}
pos+=hl+1;
//qDebug() << (duration*num_signals*whl[0].interleave) << duration;
if (num_signals==1) { // no interleave.. this is much quicker.
int bs=duration*whl[0].interleave;
memcpy((char *)&(waveform[0])[wlength[0]],(char *)&m_buffer[pos],bs);
wlength[0]+=bs;
pos+=bs;
} else {
for (int i=0;i<duration;i++) {
for (int s=0;s<num_signals;s++) {
memcpy((char *)&(waveform[s])[wlength[s]],(char *)&m_buffer[pos],whl[s].interleave);
wlength[s]+=whl[s].interleave;
pos+=whl[s].interleave;
}
}
}
for (int i=0;i<num_signals;i++) wdur[i]+=duration;
pos+=2;
block++;
lasttimestamp=timestamp;
} while (pos<size);
for (int i=0;i<num_signals;i++) {
double rate=(double(wdur[i])*1000.0)/double(wlength[i]);
double gain;
if (i==1) gain=0.1; else gain=1;
EventList *a=new EventList(wc[i],EVL_Waveform,gain,0,0,0,rate);
if (whl[i].sample_format)
a->AddWaveform(qint64(start)*1000L,(unsigned char *)waveform[i],wlength[i],qint64(wdur[i])*1000L);
else {
a->AddWaveform(qint64(start)*1000L,(char *)waveform[i],wlength[i],qint64(wdur[i])*1000L);
}
if (wc[i]==CPAP_FlowRate) {
a->setMax(120);
a->setMin(-120);
} else if (wc[i]==CPAP_MaskPressure) {
/*int v=ceil(a->max()/5);
a->setMax(v*5);
v=floor(a->min()/5);
a->setMin(v*5); */
}
session->eventlist[wc[i]].push_back(a);
session->updateLast(start+qint64(wdur[i])*1000L);
}
return true;
}
void InitModelMap()
{
ModelMap[34]="RemStar Pro with C-Flex+";
ModelMap[35]="RemStar Auto with A-Flex";
ModelMap[37]="RemStar BiPAP Auto with Bi-Flex";
ModelMap[0x41]="BiPAP autoSV Advanced";
};
bool initialized=false;
void PRS1Loader::Register()
{
if (initialized) return;
qDebug() << "Registering PRS1Loader";
RegisterLoader(new PRS1Loader());
InitModelMap();
initialized=true;
}