/* SleepLib PRS1 Loader Implementation Author: Mark Watkins License: GPL */ #include #include #include #include #include #include #include #include #include #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 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::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 ml=profile->GetMachines(MT_CPAP); bool found=false; QVector::iterator i; for (i=ml.begin(); i!=ml.end(); i++) { if (((*i)->GetClass()=="PRS1") && ((*i)->properties["Serial"]==serial)) { PRS1List[serial]=*i; //static_cast(*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,pseries="P-Series"; qDebug() << "PRS1Loader::Open path=" << newpath; if (path.endsWith("/"+pseries)) { newpath=path; } else { newpath=path+"/"+pseries; } QDir dir(newpath); if ((!dir.exists() || !dir.isReadable())) return 0; dir.setFilter(QDir::NoDotAndDotDot | QDir::Dirs | QDir::Files | QDir::Hidden | QDir::NoSymLinks); dir.setSorting(QDir::Name); QFileInfoList flist=dir.entryInfoList(); QList SerialNumbers; QList::iterator sn; for (int i=0;iDelMachine(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 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::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 paths; for (int i=0;iPulse(); } SessionID session; long ext; typedef QVector StringList; QMap sessfiles; int size=paths.size(); int cnt=0; bool ok; for (QList::iterator p=paths.begin(); p!=paths.end(); p++) { dir.setPath(*p); if (!dir.exists() || !dir.isReadable()) continue; flist=dir.entryInfoList(); for (int i=0;isetValue((float(cnt)/float(size)*33.0)); QApplication::processEvents(); } } size=sessfiles.size(); if (size==0) return 0; cnt=0; for (QMap::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]; } } if ((sess->last() - sess->first()) == 0) { //const double ignore_thresh=300.0/3600.0;// Ignore useless sessions under 5 minute //if (sess->hours()<=ignore_thresh) { qDebug() << "Ignoring empty session" << session;// << "which is only" << (sess->hours()*60.0) << "minute(s) long"; delete sess; continue; } 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->max(CPAP_IPAP)); sess->set90p(CPAP_Pressure,sess->p90(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; iset_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; while (pos=ncodes) { qDebug() << "Illegal PRS1 code " << hex << int(code) << " appeared at " << hex << pos+16; return false; } delta=0; if (code!=0x12) { delta=buffer[pos+1] << 8 | buffer[pos]; pos+=2; t+=qint64(delta)*1000L; tt=t; } cnt++; 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]); session->machine()->registerChannel(PRS1_Unknown00); } 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]); session->machine()->registerChannel(PRS1_Unknown01); } 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]); session->machine()->registerChannel(CPAP_Pressure); } 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]); session->machine()->registerChannel(CPAP_EPAP); session->machine()->registerChannel(CPAP_IPAP); session->machine()->registerChannel(CPAP_PressureSupport); } 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]); session->machine()->registerChannel(CPAP_PressurePulse); } Code[6]->AddEvent(t,buffer[pos++]); //qDebug() << hex << data[0]; break; case 0x05: // RERA data[0]=buffer[pos++]; tt=t-(qint64(data[0])*1000L); if (!Code[7]) { Code[7]=new EventList(CPAP_RERA,EVL_Event); session->eventlist[CPAP_RERA].push_back(Code[7]); session->machine()->registerChannel(CPAP_RERA); } Code[7]->AddEvent(tt,data[0]); break; case 0x06: // Obstructive Apoanea data[0]=buffer[pos++]; tt=t-(qint64(data[0])*1000L); if (!Code[8]) { Code[8]=new EventList(CPAP_Obstructive,EVL_Event); session->eventlist[CPAP_Obstructive].push_back(Code[8]); session->machine()->registerChannel(CPAP_Obstructive); } Code[8]->AddEvent(tt,data[0]); break; case 0x07: // Clear Airway data[0]=buffer[pos++]; tt=t-(qint64(data[0])*1000L); if (!Code[9]) { Code[9]=new EventList(CPAP_ClearAirway,EVL_Event); session->eventlist[CPAP_ClearAirway].push_back(Code[9]); session->machine()->registerChannel(CPAP_ClearAirway); } Code[9]->AddEvent(tt,data[0]); break; case 0x0a: // Hypopnea data[0]=buffer[pos++]; tt=t-(qint64(data[0])*1000L); if (!Code[10]) { Code[10]=new EventList(CPAP_Hypopnea,EVL_Event); session->eventlist[CPAP_Hypopnea].push_back(Code[10]); session->machine()->registerChannel(CPAP_Hypopnea); } Code[10]->AddEvent(tt,data[0]); break; case 0x0c: // Flow Limitation data[0]=buffer[pos++]; tt=t-(qint64(data[0])*1000L); if (!Code[11]) { Code[11]=new EventList(CPAP_FlowLimit,EVL_Event); session->eventlist[CPAP_FlowLimit].push_back(Code[11]); session->machine()->registerChannel(CPAP_FlowLimit); } 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]); session->machine()->registerChannel(PRS1_Unknown0B); } // 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]); session->machine()->registerChannel(CPAP_VSnore); } 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]); session->machine()->registerChannel(CPAP_Leak); session->machine()->registerChannel(CPAP_Snore); } 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]); session->machine()->registerChannel(PRS1_VSnore2); } 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]); session->machine()->registerChannel(PRS1_Unknown0E); } 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]); session->machine()->registerChannel(PRS1_Unknown10); } 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-qint64(data[1])*1000L; if (!Code[23]) { Code[23]=new EventList(CPAP_CSR,EVL_Event); session->eventlist[CPAP_CSR].push_back(Code[23]); session->machine()->registerChannel(CPAP_CSR); } 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]); session->machine()->registerChannel(PRS1_Unknown12); } 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=ncodes) { qDebug() << "Illegal PRS1 code " << hex << int(code) << " appeared at " << hex << pos+16; return false; } if (code==0) { } else if (code!=0x12) { delta=buffer[pos]; //duration=buffer[pos+1]; //delta=buffer[pos+1] << 8 | buffer[pos]; pos+=2; t+=qint64(delta)*1000L; } 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]); session->machine()->registerChannel(cpapcode); } 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]); session->machine()->registerChannel(cpapcode); } 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]); session->machine()->registerChannel(cpapcode); } Code[3]->AddEvent(t,data[0]); break; case 0x05: data[0]=buffer[pos++]; tt-=qint64(data[0])*1000L; // Subtract Time Offset if (!Code[4]) { Code[4]=new EventList(cpapcode,EVL_Event); session->eventlist[cpapcode].push_back(Code[4]); session->machine()->registerChannel(cpapcode); } Code[4]->AddEvent(tt,data[0]); break; case 0x06: data[0]=buffer[pos++]; tt-=qint64(data[0])*1000L; // Subtract Time Offset if (!Code[5]) { Code[5]=new EventList(cpapcode,EVL_Event); session->eventlist[cpapcode].push_back(Code[5]); session->machine()->registerChannel(cpapcode); } Code[5]->AddEvent(tt,data[0]); break; case 0x07: data[0]=buffer[pos++]; tt-=qint64(data[0])*1000L; // Subtract Time Offset if (!Code[6]) { Code[6]=new EventList(cpapcode,EVL_Event); session->eventlist[cpapcode].push_back(Code[6]); session->machine()->registerChannel(cpapcode); } Code[6]->AddEvent(tt,data[0]); break; case 0x08: // ASV Codes data[0]=buffer[pos++]; tt-=qint64(data[0])*1000L; // Subtract Time Offset if (!Code[10]) { Code[10]=new EventList(cpapcode,EVL_Event); session->eventlist[cpapcode].push_back(Code[10]); session->machine()->registerChannel(cpapcode); } Code[10]->AddEvent(tt,data[0]); break; case 0x09: // ASV Codes data[0]=buffer[pos++]; tt-=qint64(data[0])*1000L; // Subtract Time Offset if (!Code[11]) { Code[11]=new EventList(cpapcode,EVL_Event); session->eventlist[cpapcode].push_back(Code[11]); session->machine()->registerChannel(cpapcode); } Code[11]->AddEvent(tt,data[0]); break; case 0x0a: data[0]=buffer[pos++]; tt-=qint64(data[0])*1000L; // Subtract Time Offset if (!Code[7]) { Code[7]=new EventList(cpapcode,EVL_Event); session->eventlist[cpapcode].push_back(Code[7]); session->machine()->registerChannel(cpapcode); } Code[7]->AddEvent(tt,data[0]); break; case 0x0c: data[0]=buffer[pos++]; tt-=qint64(data[0])*1000L; // Subtract Time Offset if (!Code[8]) { Code[8]=new EventList(cpapcode,EVL_Event); session->eventlist[cpapcode].push_back(Code[8]); session->machine()->registerChannel(cpapcode); } 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-=qint64(data[1])*1000L; if (!Code[9]) { Code[9]=new EventList(cpapcode,EVL_Event,2.0); session->eventlist[cpapcode].push_back(Code[9]); session->machine()->registerChannel(cpapcode); } 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]); session->machine()->registerChannel(CPAP_IPAP); Code[13]=new EventList(CPAP_IPAP_Low,EVL_Event,0.1); session->eventlist[CPAP_IPAP_Low].push_back(Code[13]); session->machine()->registerChannel(CPAP_IPAP_Low); Code[14]=new EventList(CPAP_IPAP_High,EVL_Event,0.1); session->eventlist[CPAP_IPAP_High].push_back(Code[14]); session->machine()->registerChannel(CPAP_IPAP_High); Code[15]=new EventList(CPAP_Leak,EVL_Event); session->eventlist[CPAP_Leak].push_back(Code[15]); session->machine()->registerChannel(CPAP_Leak); Code[16]=new EventList(CPAP_RespiratoryRate,EVL_Event); session->eventlist[CPAP_RespiratoryRate].push_back(Code[16]); session->machine()->registerChannel(CPAP_RespiratoryRate); Code[17]=new EventList(CPAP_PatientTriggeredBreaths,EVL_Event); session->eventlist[CPAP_PatientTriggeredBreaths].push_back(Code[17]); session->machine()->registerChannel(CPAP_PatientTriggeredBreaths); Code[18]=new EventList(CPAP_MinuteVentilation,EVL_Event); session->eventlist[CPAP_MinuteVentilation].push_back(Code[18]); session->machine()->registerChannel(CPAP_MinuteVentilation); Code[19]=new EventList(CPAP_TidalVolume,EVL_Event,10.0); session->eventlist[CPAP_TidalVolume].push_back(Code[19]); session->machine()->registerChannel(CPAP_TidalVolume); Code[20]=new EventList(CPAP_Snore,EVL_Event); session->eventlist[CPAP_Snore].push_back(Code[20]); session->machine()->registerChannel(CPAP_Snore); Code[22]=new EventList(CPAP_EPAP,EVL_Event,0.1); session->eventlist[CPAP_EPAP].push_back(Code[22]); session->machine()->registerChannel(CPAP_EPAP); Code[23]=new EventList(CPAP_PressureSupport,EVL_Event,0.1); session->eventlist[CPAP_PressureSupport].push_back(Code[23]); session->machine()->registerChannel(CPAP_PressureSupport); } 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]); session->machine()->registerChannel(CPAP_VSnore); } 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-=qint64(data[1])*1000L; //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; } } 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; imax_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 (brupdateFirst(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 whl; // add the in reverse... for (int i=0;i3) { 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;i0 && wlength[0]>0) { qDebug() << "Timestamp resync" << block << diff << corrupt << duration << timestamp-lasttimestamp << filename; for (int i=0;imachine()->registerChannel(wc[i]); 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;imachine()->registerChannel(wc[i]); 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) { } 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; }