/* SleepLib PRS1 Loader Implementation Author: Mark Watkins License: GPL */ #include #include #include #include #include #include #include #include #include #include #include "prs1_loader.h" extern wxProgressDialog *loader_progress; map ModelMap= { {34,wxT("RemStar Pro with C-Flex+")}, {35,wxT("RemStar Auto with A-Flex")}, {37,wxT("RemStar BIPAP Auto with Bi-Flex")} }; PRS1::PRS1(Profile *p,MachineID id):CPAP(p,id) { m_class=wxT("PRS1"); properties[wxT("Brand")]=wxT("Philips Respironics"); properties[wxT("Model")]=wxT("System One"); SleepFlags= { CPAP_RERA, PRS1_VSnore2, CPAP_FlowLimit, CPAP_Hypopnea, CPAP_Obstructive, CPAP_ClearAirway, CPAP_CSR }; } PRS1::~PRS1() { } PRS1Loader::PRS1Loader() { m_buffer=new unsigned char [max_load_buffer_size]; //allocate once and reuse. } PRS1Loader::~PRS1Loader() { for (map::iterator i=PRS1List.begin(); i!=PRS1List.end(); i++) { delete i->second; } delete [] m_buffer; } Machine *PRS1Loader::CreateMachine(wxString serial,Profile *profile) { wxLogMessage(wxT("Create Machine ")+serial); if (!profile) { wxLogMessage(wxT("No Profile!")); return NULL; } vector ml=profile->GetMachines(MT_CPAP); bool found=false; for (vector::iterator i=ml.begin(); i!=ml.end(); i++) { if (((*i)->GetClass()==wxT("PRS1")) && ((*i)->properties[wxT("Serial")]==serial)) { PRS1List[serial]=*i; //static_cast(*i); found=true; break; } } if (found) return PRS1List[serial]; //assert(PRS1List.find(serial)==PRS1List.end()) //wxPuts(wxT("Creating CPAP Machine ")+serial); Machine *m=new PRS1(profile,0); PRS1List[serial]=m; profile->AddMachine(m); m->properties[wxT("Serial")]=serial; return m; } bool PRS1Loader::Open(wxString & path,Profile *profile) { wxString newpath; wxString sep=wxFileName::GetPathSeparator(); wxString pseries=wxT("P-Series"); if (path.Right(pseries.Len()+sep.Len())==sep+pseries) { newpath=path; } else { newpath=path+sep+pseries; } wxDir dir; dir.Open(newpath); if (!dir.IsOpened()) return 0; list SerialNumbers; list::iterator sn; wxString filename; bool cont=dir.GetFirst(&filename); int c=0; while (cont) { if ((filename[0]=='P') && (isdigit(filename[1])) && (isdigit(filename[2]))) { SerialNumbers.push_back(filename); } else if (filename.Lower()==wxT("last.txt")) { // last.txt points to the current serial number wxTextFile f(newpath+sep+filename); f.Open(); last=f.GetFirstLine(); last.Strip(); f.Close(); } cont=dir.GetNext(&filename); } if (SerialNumbers.empty()) return 0; Machine *m; for (sn=SerialNumbers.begin(); sn!=SerialNumbers.end(); sn++) { wxString s=*sn; m=CreateMachine(s,profile); try { if (m) OpenMachine(m,newpath+wxFileName::GetPathSeparator()+(*sn),profile); } catch(OneTypePerDay e) { profile->DelMachine(m); PRS1List.erase(s); wxMessageBox(_("This Machine Record cannot be imported in this profile.\nThe Day records overlap with already existing content."),_("Import Error"),wxOK|wxCENTER); delete m; } } return PRS1List.size(); // return c; } bool PRS1Loader::ParseProperties(Machine *m,wxString filename) { wxTextFile f(filename); f.Open(); if (!f.IsOpened()) return false; wxString line; map prop; wxString s=f.GetFirstLine(); wxChar sep=wxChar('='); wxString key,value; while (!f.Eof()) { key=s.BeforeFirst(sep); if (key==s) continue; value=s.AfterFirst(sep).Strip(); if (value==s) continue; prop[key]=value; s=f.GetNextLine(); } if (prop[wxT("ProductType")].IsNumber()) { long i; prop[wxT("ProductType")].ToLong(&i); if (ModelMap.find(i)!=ModelMap.end()) { m->properties[wxT("SubModel")]=ModelMap[i]; } } if (prop[wxT("SerialNumber")]!=m->properties[wxT("Serial")]) { wxLogWarning(wxT("Serial Number in PRS1 properties.txt doesn't match directory structure")); } else prop.erase(wxT("SerialNumber")); // already got it stored. for (map::iterator i=prop.begin(); i!=prop.end(); i++) { m->properties[i->first]=i->second; } f.Close(); return true; } int PRS1Loader::OpenMachine(Machine *m,wxString path,Profile *profile) { wxLogDebug(wxT("Opening PRS1 ")+path); //wxPuts(wxT("opening "+path)); wxDir dir; dir.Open(path); if (!dir.IsOpened()) return false; wxString pathname,filename; bool cont=dir.GetFirst(&filename); list paths; if(loader_progress) { loader_progress->Update(0); } while (cont) { pathname=path+wxFileName::GetPathSeparator()+filename; if ((filename[0]==wxChar('p')) && (isdigit(filename[1]))) { paths.push_back(pathname); } else if (filename.Lower()==wxT("properties.txt")) { ParseProperties(m,pathname); } else if (filename.Lower()==wxT("e")) { // don't really give a crap about .004 files yet. } if (loader_progress) loader_progress->Pulse(); cont=dir.GetNext(&filename); } SessionID session; long ext; typedef vector StringList; map sessfiles; int size=paths.size(); int cnt=0; for (list::iterator p=paths.begin(); p!=paths.end(); p++) { dir.Open(*p); if (!dir.IsOpened()) continue;; bool cont=dir.GetFirst(&filename); while (cont) { wxString ext_s=filename.AfterLast(wxChar('.')); wxString session_s=filename.BeforeLast(wxChar('.')); if (!ext_s.IsNumber()) continue; if (!session_s.IsNumber()) continue; session_s.ToLong(&session); ext_s.ToLong(&ext); if (sessfiles[session].capacity()==0) sessfiles[session].resize(3); wxString fullname=*p+wxFileName::GetPathSeparator()+filename; if (ext==1) { sessfiles[session][0]=fullname; } else if (ext==2) { sessfiles[session][1]=fullname; } else if (ext==5) { sessfiles[session][2]=fullname; } cnt++; if (loader_progress) loader_progress->Pulse(); //Update((float(cnt)/float(size)*25)); //if (loader_progress) loader_progress->Update((float(cnt)/float(size)*25.0)); cont=dir.GetNext(&filename); } } if (sessfiles.size()==0) return 0; size=sessfiles.size(); cnt=0; for (map::iterator s=sessfiles.begin(); s!=sessfiles.end(); s++) { session=s->first; cnt++; if (loader_progress) loader_progress->Update(25.0+(float(cnt)/float(size)*25.0)); if (m->SessionExists(session)) continue; if (!s->second[0]) continue; Session *sess=new Session(m,session); if (!OpenSummary(sess,s->second[0])) { wxLogWarning(wxT("PRS1Loader: Could'nt open summary file ")+s->second[0]); delete sess; continue; } //wxLogMessage(sess->first().Format(wxT("%Y-%m-%d %H:%M:%S"))+wxT(" ")+sess->last().Format(wxT("%Y-%m-%d %H:%M:%S"))); //sess->SetSessionID(sess->start().GetTicks()); if (!s->second[1].IsEmpty()) { if (!OpenEvents(sess,s->second[1])) { wxLogWarning(wxT("PRS1Loader: Couldn't open event file ")+s->second[1]); } } if (!s->second[2].IsEmpty()) { if (!OpenWaveforms(sess,s->second[2])) { wxLogWarning(wxT("PRS1Loader: Couldn't open event file ")+s->second[2]); } } const double ignore_thresh=300.0/3600.0;// Ignore useless sessions under 5 minute if (sess->hours()<=ignore_thresh) { delete sess; continue; } m->AddSession(sess,profile); //if (sess->summary.find(CPAP_Obstructive)==sess->summary.end()) { sess->summary[CPAP_Obstructive]=(long)sess->count_events(CPAP_Obstructive); sess->summary[CPAP_Hypopnea]=(long)sess->count_events(CPAP_Hypopnea); sess->summary[CPAP_ClearAirway]=(long)sess->count_events(CPAP_ClearAirway); sess->summary[CPAP_RERA]=(long)sess->count_events(CPAP_RERA); sess->summary[CPAP_FlowLimit]=(long)sess->count_events(CPAP_FlowLimit); //} sess->summary[CPAP_CSR]=sess->sum_event_field(CPAP_CSR,0); sess->summary[CPAP_VSnore]=(long)sess->count_events(CPAP_VSnore); sess->summary[PRS1_VSnore2]=sess->sum_event_field(PRS1_VSnore2,0); if (sess->count_events(CPAP_IAP)>0) { sess->summary[CPAP_Mode]=(long)MODE_BIPAP; if (sess->summary[CPAP_PressureReliefType].GetInteger()!=(long)PR_NONE) { sess->summary[CPAP_PressureReliefType]=(long)PR_BIFLEX; } sess->summary[CPAP_PressureMedian]=(sess->avg_event_field(CPAP_EAP,0)+sess->avg_event_field(CPAP_IAP,0))/2.0; sess->summary[CPAP_PressureAverage]=(sess->weighted_avg_event_field(CPAP_IAP,0)+sess->weighted_avg_event_field(CPAP_EAP,0))/2.0; sess->summary[CPAP_PressureMinAchieved]=sess->min_event_field(CPAP_IAP,0); sess->summary[CPAP_PressureMaxAchieved]=sess->max_event_field(CPAP_EAP,0); sess->summary[BIPAP_IAPAverage]=sess->weighted_avg_event_field(CPAP_IAP,0); sess->summary[BIPAP_IAPMin]=sess->min_event_field(CPAP_IAP,0); sess->summary[BIPAP_IAPMax]=sess->max_event_field(CPAP_IAP,0); sess->summary[BIPAP_EAPAverage]=sess->weighted_avg_event_field(CPAP_EAP,0); sess->summary[BIPAP_EAPMin]=sess->min_event_field(CPAP_EAP,0); sess->summary[BIPAP_EAPMax]=sess->max_event_field(CPAP_EAP,0); } else { sess->summary[CPAP_PressureMedian]=sess->avg_event_field(CPAP_Pressure,0); sess->summary[CPAP_PressureAverage]=sess->weighted_avg_event_field(CPAP_Pressure,0); sess->summary[CPAP_PressureMinAchieved]=sess->min_event_field(CPAP_Pressure,0); sess->summary[CPAP_PressureMaxAchieved]=sess->max_event_field(CPAP_Pressure,0); if (sess->summary.find(CPAP_PressureMin)==sess->summary.end()) { sess->summary[CPAP_BrokenSummary]=true; sess->set_last(sess->first()); if (sess->summary[CPAP_PressureMinAchieved]==sess->summary[CPAP_PressureMaxAchieved]) { sess->summary[CPAP_Mode]=(long)MODE_CPAP; } else { sess->summary[CPAP_Mode]=(long)MODE_UNKNOWN; } sess->summary[CPAP_PressureReliefType]=(long)PR_UNKNOWN; } } if (sess->summary[CPAP_Mode]==(long)MODE_CPAP) { sess->summary[CPAP_PressureMax]=sess->summary[CPAP_PressureMin]; } sess->summary[CPAP_LeakMinimum]=sess->min_event_field(CPAP_Leak,0); sess->summary[CPAP_LeakMaximum]=sess->max_event_field(CPAP_Leak,0); // should be merged.. sess->summary[CPAP_LeakMedian]=sess->avg_event_field(CPAP_Leak,0); sess->summary[CPAP_LeakAverage]=sess->weighted_avg_event_field(CPAP_Leak,0); //wxPrintf(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->Save(); // Save any new sessions to disk in our format if (loader_progress) loader_progress->Update(100); return true; } bool PRS1Loader::OpenSummary(Session *session,wxString filename) { int size,sequence,seconds,br; time_t timestamp; unsigned char header[24]; unsigned char ext,sum; //wxLogMessage(wxT("Opening PRS1 Summary ")+filename); wxFFile f(filename,wxT("rb")); if (!f.IsOpened()) return false; int hl=16; br=f.Read(header,hl); if (header[0]!=header[5]) 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]; if (ext!=1) 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); sum=0; for (int i=0; isession(),duration)); float hours=float(duration)/3600.0; session->set_hours(hours); session->set_last(session->first()+wxTimeSpan::Seconds(duration)); session->summary[CPAP_PressureMinAchieved]=buffer[0x16]/10.0; session->summary[CPAP_PressureMaxAchieved]=buffer[0x17]/10.0; session->summary[CPAP_PressurePercentValue]=buffer[0x18]/10.0; session->summary[CPAP_PressurePercentName]=90.0; session->summary[CPAP_PressureAverage]=buffer[0x19]/10.0; if (max==0) { session->summary[CPAP_PressureAverage]=session->summary[CPAP_PressureMin]; } if (size==0x4d) { session->summary[CPAP_Obstructive]=(long)buffer[0x1C] | (buffer[0x1D] << 8); session->summary[CPAP_ClearAirway]=(long)buffer[0x20] | (buffer[0x21] << 8); session->summary[CPAP_Hypopnea]=(long)buffer[0x2A] | (buffer[0x2B] << 8); session->summary[CPAP_RERA]=(long)buffer[0x2E] | (buffer[0x2F] << 8); session->summary[CPAP_FlowLimit]=(long)buffer[0x30] | (buffer[0x31] << 8); } return true; } bool PRS1Loader::Parse002(Session *session,unsigned char *buffer,int size,time_t timestamp) { vector Codes= { PRS1_Unknown00, PRS1_Unknown01, CPAP_Pressure, CPAP_EAP, PRS1_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 }; wxDateTime start; start.Set(timestamp); wxDateTime t=start; //t.Set(timestamp); int pos=0; int cnt=0; while (posAddEvent(new Event(t,cpapcode, {data0})); break; case 0x04: // Pressure Pulse data0=buffer[pos++]; session->AddEvent(new Event(t,cpapcode, {data0})); break; case 0x05: // RERA case 0x06: // Obstructive Apoanea case 0x07: // Clear Airway case 0x0a: // Hypopnea case 0x0c: // Flow Limitation tt-=wxTimeSpan::Seconds((buffer[pos++])); // Subtract Time Offset session->AddEvent(new Event(tt,cpapcode, {})); break; case 0x0d: // Vibratory Snore session->AddEvent(new Event(t,cpapcode, {})); break; case 0x03: // BIPAP Pressure case 0x0b: // Unknown case 0x11: // Leak Rate data0=buffer[pos++]; data1=buffer[pos++]; if (code==0x11) { session->AddEvent(new Event(t,cpapcode, {data0})); if (data1>0) { session->AddEvent(new Event(tt,PRS1_VSnore2, {data1})); } } else if (code==0x03) { data0/=10.0; data1/=10.0; session->AddEvent(new Event(t,CPAP_EAP, {data1})); session->AddEvent(new Event(t,CPAP_IAP, {data0})); } else { session->AddEvent(new Event(t,cpapcode, {data0,data1})); } break; case 0x0e: // Unknown case 0x10: // Unknown data0=buffer[pos++]; data1=buffer[pos++]; data2=buffer[pos++]; session->AddEvent(new Event(t,cpapcode, {data0,data1,data2})); break; case 0x0f: // Cheyne Stokes Respiration data0=buffer[pos+1]<<8 | buffer[pos]; pos+=2; data1=buffer[pos++]; tt-=wxTimeSpan::Seconds(data1); session->AddEvent(new Event(tt,cpapcode, {data0,data1})); break; case 0x12: // Summary data0=buffer[pos++]; data1=buffer[pos++]; data2=buffer[pos+1]<<8 | buffer[pos]; pos+=2; session->AddEvent(new Event(t,cpapcode, {data0,data1,data2})); break; default: // ERROR!!! throw exception(); // UnknownCode(); break; } } return true; } bool PRS1Loader::OpenEvents(Session *session,wxString filename) { int size,sequence,seconds,br; time_t timestamp; unsigned char header[24]; // use m_buffer? unsigned char ext; //wxLogMessage(wxT("Opening PRS1 Events ")+filename); wxFFile f(filename,wxT("rb")); int hl=16; br=f.Read(header,hl); if (header[0]!=header[5]) 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]; if (ext!=2) 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=max_load_buffer_size) { wxLogError(wxT("max_load_buffer_size is too small in PRS1 Loader")); if (cnt==0) return false; break; } br=f.Read((char *)&buffer[samples],size); if (brc) min=c; if (maxAddWaveform(w); //wxLogMessage(wxT("Done PRS1 Waveforms ")+filename); return true; } bool initialized=false; void PRS1Loader::Register() { if (initialized) return; wxLogVerbose(wxT("Registering PRS1Loader")); RegisterLoader(new PRS1Loader()); initialized=true; }