From 43e0f512b13e08c3194384c106a02c8b8f56320f Mon Sep 17 00:00:00 2001 From: Mark Watkins Date: Mon, 6 Jun 2011 13:20:20 +1000 Subject: [PATCH] CMS50 Oximeter Loader and basic Graphs --- Projects/CodeBlocks/SleepyHead.cbp | 4 + Projects/CodeBlocks/SleepyHead.depend | 22 ++- Projects/CodeBlocks/SleepyHead.layout | 29 +++- src/SleepyHeadMain.cpp | 104 +++++++++--- src/SleepyHeadMain.h | 12 +- src/graphs/graph.cpp | 5 +- src/graphs/graph.h | 2 +- .../sleeplib/loader_plugins/cms50_loader.cpp | 149 ++++++++++++++++++ .../sleeplib/loader_plugins/cms50_loader.h | 4 + src/libs/sleeplib/machine.cpp | 2 +- src/libs/sleeplib/machine.h | 2 +- 11 files changed, 288 insertions(+), 47 deletions(-) diff --git a/Projects/CodeBlocks/SleepyHead.cbp b/Projects/CodeBlocks/SleepyHead.cbp index d67833c7..6394c700 100644 --- a/Projects/CodeBlocks/SleepyHead.cbp +++ b/Projects/CodeBlocks/SleepyHead.cbp @@ -73,6 +73,10 @@ + + + + diff --git a/Projects/CodeBlocks/SleepyHead.depend b/Projects/CodeBlocks/SleepyHead.depend index f915070c..cf95bd42 100644 --- a/Projects/CodeBlocks/SleepyHead.depend +++ b/Projects/CodeBlocks/SleepyHead.depend @@ -8249,7 +8249,7 @@ 1307245704 /home/mark/projects/git/sleepyhead/src/SleepyHeadApp.h -1307245704 /home/mark/projects/git/sleepyhead/src/SleepyHeadMain.h +1307291151 /home/mark/projects/git/sleepyhead/src/SleepyHeadMain.h "SleepyHeadApp.h" @@ -8257,7 +8257,7 @@ "sleeplib/machine.h" "graphs/graph.h" -1307272411 /home/mark/projects/git/sleepyhead/src/libs/sleeplib/machine.h +1307289735 /home/mark/projects/git/sleepyhead/src/libs/sleeplib/machine.h @@ -8291,7 +8291,7 @@ "tinyxml/tinyxml.h" -1307245704 /home/mark/projects/git/sleepyhead/src/graphs/graph.h +1307290670 /home/mark/projects/git/sleepyhead/src/graphs/graph.h @@ -8312,7 +8312,7 @@ "preferences.h" "tinyxml/tinyxml.h" -1307252474 source:/home/mark/projects/git/sleepyhead/src/SleepyHeadMain.cpp +1307292874 source:/home/mark/projects/git/sleepyhead/src/SleepyHeadMain.cpp "wx_pch.h" "version.h" @@ -8331,7 +8331,7 @@ "sleeplib/profiles.h" "sleeplib/machine_loader.h" -1307245704 source:/home/mark/projects/git/sleepyhead/src/graphs/graph.cpp +1307290767 source:/home/mark/projects/git/sleepyhead/src/graphs/graph.cpp @@ -8346,7 +8346,7 @@ -1307245704 source:/home/mark/projects/git/sleepyhead/src/libs/sleeplib/machine.cpp +1307289238 source:/home/mark/projects/git/sleepyhead/src/libs/sleeplib/machine.cpp @@ -8409,14 +8409,20 @@ "tinyxml.h" -1307274777 /home/mark/projects/git/sleepyhead/src/libs/sleeplib/loader_plugins/cms50_loader.h +1307287644 /home/mark/projects/git/sleepyhead/src/libs/sleeplib/loader_plugins/cms50_loader.h "sleeplib/machine_loader.h" -1307275420 source:/home/mark/projects/git/sleepyhead/src/libs/sleeplib/loader_plugins/cms50_loader.cpp +1307289931 source:/home/mark/projects/git/sleepyhead/src/libs/sleeplib/loader_plugins/cms50_loader.cpp + "cms50_loader.h" "sleeplib/machine.h" 1307275207 /home/mark/projects/git/sleepyhead/src/libs/sleeplib/loader_plugins/zeo_loader.h "sleeplib/machine_loader.h" +1307275410 source:/home/mark/projects/git/sleepyhead/src/libs/sleeplib/loader_plugins/zeo_loader.cpp + + "zeo_loader.h" + "sleeplib/machine.h" + diff --git a/Projects/CodeBlocks/SleepyHead.layout b/Projects/CodeBlocks/SleepyHead.layout index 1030f053..501ae35c 100644 --- a/Projects/CodeBlocks/SleepyHead.layout +++ b/Projects/CodeBlocks/SleepyHead.layout @@ -2,12 +2,33 @@ - + - - + + - + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/SleepyHeadMain.cpp b/src/SleepyHeadMain.cpp index 39298c50..5a16a247 100644 --- a/src/SleepyHeadMain.cpp +++ b/src/SleepyHeadMain.cpp @@ -541,9 +541,9 @@ Daily::Daily(wxWindow *win,Profile *p) this->Connect(wxEVT_COMMAND_TREE_SEL_CHANGED, wxTreeEventHandler( Daily::OnEventTreeSelection), NULL, this); Notebook->AddPage(HTMLInfo,wxT("Details"),false,NULL); Notebook->AddPage(EventTree,wxT("Events"),false,NULL); - AddData(tap_eap=new TAPData(CPAP_EAP)); - AddData(tap_iap=new TAPData(CPAP_IAP)); - AddData(tap=new TAPData(CPAP_Pressure)); + AddCPAPData(tap_eap=new TAPData(CPAP_EAP)); + AddCPAPData(tap_iap=new TAPData(CPAP_IAP)); + AddCPAPData(tap=new TAPData(CPAP_Pressure)); TAP=new gGraphWindow(ScrolledWindow,-1,wxT("Time@Pressure"),wxPoint(0,0), wxSize(600,60), wxNO_BORDER); TAP->SetMargins(20,15,5,50); @@ -559,7 +559,7 @@ Daily::Daily(wxWindow *win,Profile *p) G_AHI=new gGraphWindow(ScrolledWindow,-1,wxT("Event Breakdown"),wxPoint(0,0), wxSize(600,60), wxNO_BORDER); G_AHI->SetMargins(20,15,5,50); - AddData(g_ahi=new AHIData()); + AddCPAPData(g_ahi=new AHIData()); gCandleStick *l=new gCandleStick(g_ahi); l->AddName(wxT("H")); l->AddName(wxT("OA")); @@ -576,37 +576,53 @@ Daily::Daily(wxWindow *win,Profile *p) l->color.push_back(wxGREEN2); G_AHI->AddLayer(l); - AddData(leakdata=new PressureData(CPAP_Leak,0)); + AddCPAPData(leakdata=new PressureData(CPAP_Leak,0)); //leakdata->ForceMinY(0); //leakdata->ForceMaxY(120); + AddOXIData(pulse=new PressureData(OXI_Pulse,0,32768)); + pulse->ForceMinY(40); + pulse->ForceMaxY(120); + + PULSE=new gGraphWindow(ScrolledWindow,-1,wxT("Pulse"),wxPoint(0,0), wxSize(600,130), wxNO_BORDER); + PULSE->AddLayer(new gLineChart(pulse,wxRED,32768,false,false,true)); + PULSE->AddLayer(new gXAxis(wxBLACK)); + + AddOXIData(spo2=new PressureData(OXI_SPO2,0,32768)); + spo2->ForceMinY(60); + spo2->ForceMaxY(100); + SPO2=new gGraphWindow(ScrolledWindow,-1,wxT("SpO2"),wxPoint(0,0), wxSize(600,130), wxNO_BORDER); + SPO2->AddLayer(new gLineChart(spo2,wxBLUE,32768,false,false,true)); + SPO2->AddLayer(new gXAxis(wxBLACK)); + SPO2->LinkZoom(PULSE); + PULSE->LinkZoom(SPO2); LEAK=new gGraphWindow(ScrolledWindow,-1,wxT("Mask Leaks"),wxPoint(0,0), wxSize(600,130), wxNO_BORDER); LEAK->AddLayer(new gLineChart(leakdata,wxPURPLE,4096,false,false,true)); LEAK->AddLayer(new gXAxis(wxBLACK)); - AddData(pressure_iap=new PressureData(CPAP_IAP)); - AddData(pressure_eap=new PressureData(CPAP_EAP)); - AddData(prd=new PressureData(CPAP_Pressure)); + AddCPAPData(pressure_iap=new PressureData(CPAP_IAP)); + AddCPAPData(pressure_eap=new PressureData(CPAP_EAP)); + AddCPAPData(prd=new PressureData(CPAP_Pressure)); PRD=new gGraphWindow(ScrolledWindow,-1,wxT("Pressure"),wxPoint(0,0), wxSize(600,130), wxNO_BORDER); PRD->AddLayer(new gLineChart(prd,wxDARK_GREEN,4096,false,false,true)); PRD->AddLayer(new gLineChart(pressure_iap,wxBLUE,4096,false,true,true)); PRD->AddLayer(new gLineChart(pressure_eap,wxRED,4096,false,true,true)); PRD->AddLayer(new gXAxis(wxBLACK)); - AddData(frw=new FlowData()); + AddCPAPData(frw=new FlowData()); FRW=new gGraphWindow(ScrolledWindow,-1,wxT("Flow Rate"),wxPoint(0,0), wxSize(600,150), wxNO_BORDER); - AddData(flags[0]=new FlagData(CPAP_CSR,7,1,0)); - AddData(flags[1]=new FlagData(CPAP_ClearAirway,6)); - AddData(flags[2]=new FlagData(CPAP_Obstructive,5)); - AddData(flags[3]=new FlagData(CPAP_Hypopnea,4)); - AddData(flags[4]=new FlagData(CPAP_FlowLimit,3)); - AddData(flags[5]=new FlagData(CPAP_VSnore,2)); - AddData(flags[6]=new FlagData(CPAP_RERA,1)); - AddData(flags[7]=new FlagData(PRS1_PressurePulse,1)); - AddData(flags[8]=new FlagData(PRS1_VSnore2,1)); - AddData(flags[9]=new FlagData(PRS1_Unknown0E,1)); + AddCPAPData(flags[0]=new FlagData(CPAP_CSR,7,1,0)); + AddCPAPData(flags[1]=new FlagData(CPAP_ClearAirway,6)); + AddCPAPData(flags[2]=new FlagData(CPAP_Obstructive,5)); + AddCPAPData(flags[3]=new FlagData(CPAP_Hypopnea,4)); + AddCPAPData(flags[4]=new FlagData(CPAP_FlowLimit,3)); + AddCPAPData(flags[5]=new FlagData(CPAP_VSnore,2)); + AddCPAPData(flags[6]=new FlagData(CPAP_RERA,1)); + AddCPAPData(flags[7]=new FlagData(PRS1_PressurePulse,1)); + AddCPAPData(flags[8]=new FlagData(PRS1_VSnore2,1)); + AddCPAPData(flags[9]=new FlagData(PRS1_Unknown0E,1)); FRW->AddLayer(new gLineOverlayBar(flags[0],wxGREEN2,wxT("CSR"))); FRW->AddLayer(new gLineChart(frw,wxBLACK,200000,true)); @@ -646,6 +662,8 @@ Daily::Daily(wxWindow *win,Profile *p) fgSizer->Add(SF,1,wxEXPAND); fgSizer->Add(FRW,1,wxEXPAND); + fgSizer->Add(PULSE,1,wxEXPAND); + fgSizer->Add(SPO2,1,wxEXPAND); fgSizer->Add(PRD,1,wxEXPAND); fgSizer->Add(LEAK,1,wxEXPAND); fgSizer->Add(G_AHI,1,wxEXPAND); @@ -704,18 +722,22 @@ void Daily::RefreshData() date-=wxTimeSpan::Days(1); Day *d=NULL; + Day *oxi=NULL; if (profile->daylist.find(date)!=profile->daylist.end()) { vector::iterator di; for (di=profile->daylist[date].begin();di!=profile->daylist[date].end();di++) { - if ((*di)->machine_type()==MT_CPAP) { + if (!d && ((*di)->machine_type()==MT_CPAP)) { d=(*di); - break; + UpdateCPAPGraphs(d); + } + if ((*di)->machine_type()==MT_OXIMETER) { + oxi=(*di); + UpdateOXIGraphs(oxi); } } } - UpdateGraphs(d); if (d) { CPAPMode mode=(CPAPMode)d->summary_max(CPAP_Mode); if (mode!=MODE_BIPAP) { @@ -765,8 +787,6 @@ void Daily::RefreshData() EventTree->SortChildren(root); EventTree->Expand(root); - fgSizer->Layout(); - ScrolledWindow->FitInside(); PRTypes pr=(PRTypes)d->summary_max(CPAP_PressureReliefType); wxString epr=PressureReliefNames[pr]+wxString::Format(wxT(" x%i"),(int)d->summary_max(CPAP_PressureReliefSetting)); @@ -834,6 +854,25 @@ void Daily::RefreshData() } html=html+wxT("")+_("Avg Leak")+wxT("")+wxString::Format(wxT("%.2f"),d->summary_weighted_avg(CPAP_LeakAverage))+wxT("\n"); + html=html+wxT("  \n"); + + if (oxi) { + html=html+wxT("")+_("Oximeter Information")+wxT("\n"); + html=html+wxT("")+_("Pulse Avg")+wxT("")+wxString::Format(wxT("%.1fcmH2O"),d->summary_avg(OXI_PulseAverage))+wxT("\n"); + html=html+wxT("")+_("Pulse Min")+wxT("")+wxString::Format(wxT("%.1fcmH2O"),d->summary_min(OXI_PulseMin))+wxT("\n"); + html=html+wxT("")+_("Pulse Max")+wxT("")+wxString::Format(wxT("%.1fcmH2O"),d->summary_max(OXI_PulseMax))+wxT("\n"); + html=html+wxT("")+_("SpO2 Avg")+wxT("")+wxString::Format(wxT("%.1fcmH2O"),d->summary_avg(OXI_SPO2Average))+wxT("\n"); + html=html+wxT("")+_("SpO2 Min")+wxT("")+wxString::Format(wxT("%.1fcmH2O"),d->summary_min(OXI_SPO2Min))+wxT("\n"); + html=html+wxT("")+_("SpO2 Max")+wxT("")+wxString::Format(wxT("%.1fcmH2O"),d->summary_max(OXI_SPO2Max))+wxT("\n"); + PULSE->Show(true); + SPO2->Show(true); + } else { + PULSE->Show(false); + SPO2->Show(false); + } + fgSizer->Layout(); +// fgSizer->Layout(); + ScrolledWindow->FitInside(); html=html+wxT("  \n"); @@ -906,18 +945,31 @@ void Daily::OnCalendarDay( wxCalendarEvent& event ) } RefreshData(); } -void Daily::UpdateGraphs(Day *day) +void Daily::UpdateCPAPGraphs(Day *day) { //if (!day) return; if (day) { day->OpenEvents(); day->OpenWaveforms(); } - for (list::iterator g=Data.begin();g!=Data.end();g++) { + for (list::iterator g=CPAPData.begin();g!=CPAPData.end();g++) { (*g)->Update(day); } }; +void Daily::UpdateOXIGraphs(Day *day) +{ + //if (!day) return; + if (day) { + day->OpenEvents(); + day->OpenWaveforms(); + } + for (list::iterator g=OXIData.begin();g!=OXIData.end();g++) { + (*g)->Update(day); + } +}; + + void Daily::OnCalendarMonth( wxCalendarEvent& event ) { wxDateTime et=event.GetDate(); diff --git a/src/SleepyHeadMain.h b/src/SleepyHeadMain.h index aca65c95..10b81b08 100644 --- a/src/SleepyHeadMain.h +++ b/src/SleepyHeadMain.h @@ -66,16 +66,20 @@ protected: virtual void OnSelectSession( wxCommandEvent& event ); virtual void OnEventTreeSelection( wxTreeEvent& event ); - void AddData(gPointData *d) { Data.push_back(d); }; - void UpdateGraphs(Day *day); + void AddCPAPData(gPointData *d) { CPAPData.push_back(d); }; + void AddOXIData(gPointData *d) { OXIData.push_back(d); }; + void UpdateCPAPGraphs(Day *day); + void UpdateOXIGraphs(Day *day); bool foobar_datehack; gPointData *tap,*tap_eap,*tap_iap,*g_ahi,*frw,*prd,*leakdata,*pressure_iap,*pressure_eap; + gPointData *pulse,*spo2; gPointData *flags[10]; - gGraphWindow *PRD,*FRW,*G_AHI,*TAP,*LEAK,*SF,*TAP_EAP,*TAP_IAP; + gGraphWindow *PRD,*FRW,*G_AHI,*TAP,*LEAK,*SF,*TAP_EAP,*TAP_IAP,*PULSE,*SPO2; Profile *profile; - list Data; + list OXIData; + list CPAPData; wxHtmlWindow *HTMLInfo; wxTreeCtrl *EventTree; diff --git a/src/graphs/graph.cpp b/src/graphs/graph.cpp index 17b223cb..4477587e 100644 --- a/src/graphs/graph.cpp +++ b/src/graphs/graph.cpp @@ -1602,8 +1602,8 @@ void FlowData::Reload(Day *day) //graph->Refresh(false); } -PressureData::PressureData(MachineCode _code,int _field) -:gPointData(1024),code(_code),field(_field) +PressureData::PressureData(MachineCode _code,int _field,int _size) +:gPointData(_size),code(_code),field(_field) { } PressureData::~PressureData() @@ -1616,6 +1616,7 @@ void PressureData::Reload(Day *day) m_ready=false; return; } + min_x=day->first().GetMJD(); max_x=day->last().GetMJD(); assert(min_x +#include #include "cms50_loader.h" #include "sleeplib/machine.h" +extern wxProgressDialog *loader_progress; + CMS50Loader::CMS50Loader() { //ctor @@ -25,8 +28,154 @@ bool CMS50Loader::Open(wxString & path,Profile *profile) // Not sure whether to both supporting SpO2 files here, as the timestamps are utterly useless for overlay purposes. // May just ignore the crap and support my CMS50 logger + // Contains three files + // Data Folder + // SpO2 Review.ini + // SpO2.ini + + wxString tmp=path+wxFileName::GetPathSeparator()+wxT("Data"); + if ((wxFileExists(path+wxFileName::GetPathSeparator()+wxT("SpO2 Review.ini"))) + && (wxFileExists(path+wxFileName::GetPathSeparator()+wxT("SpO2.ini"))) + && (wxDirExists(tmp))) { + // Their software + + return OpenCMS50(tmp,profile); + } else { + // My Logger + } + return false; } +bool CMS50Loader::OpenCMS50(wxString & path, Profile *profile) +{ + wxDir dir; + if (!dir.Open(path)) return false; + list files; + + wxString filename,pathname; + + bool cont=dir.GetFirst(&filename); + + while (cont) { + if (filename.EndsWith(wxT(".spoR"))) { + pathname=path+wxFileName::GetPathSeparator()+filename; + files.push_back(pathname); + } + cont=dir.GetNext(&filename); + } + int size=files.size(); + if (size==0) return false; + + Machine *mach=CreateMachine(profile); + int cnt=0; + for (list::iterator n=files.begin();n!=files.end();n++,++cnt) { + if (loader_progress) loader_progress->Update((float(cnt)/float(size)*100.0)); + OpenSPORFile((*n),mach,profile); + } + mach->Save(); + return true; +} +bool CMS50Loader::OpenSPORFile(wxString path,Machine *mach,Profile *profile) +{ + wxFFile f; + unsigned char tmp[256]; + if (!f.Open(path,wxT("rb"))) return false; + + wxString str=path.AfterLast(wxChar('_')); + wxString id=str.BeforeFirst(wxChar('.')); + wxDateTime dt; + dt.ParseFormat(id,wxT("%Y%m%d%H%M")); + + SessionID sessid=dt.GetTicks(); // Import date becomes session id + + if (mach->SessionExists(sessid)) + return false; // Already imported + + // Find everything after the last _ + short data_starts; + short some_code; + int num_records; + short some_more_code; + int br; + + + + f.Read(tmp,2); + data_starts=tmp[0] | (tmp[1] << 8); + f.Read(tmp,2); + some_code=tmp[0] | (tmp[1] << 8); // == 512 + f.Read(tmp,2); + num_records=tmp[0] | (tmp[1] << 8); + num_records <<= 1; + + f.Read(tmp,2); + some_more_code=tmp[0] | (tmp[1] << 8); // == 512 + + f.Read(tmp,16); + + wxString datestr; + for (int i=0;i<16;i+=2) { + datestr=datestr+tmp[i]; + } + wxDateTime date; + date.ParseFormat(datestr,wxT("%m/%d/%y")); + + + f.Seek(data_starts,wxFromStart); + buffer=new char [num_records*2]; + br=f.Read(buffer,num_records); + if (br!=num_records) { + wxLogDebug(wxT("Short .spoR File: ")+path); + } + char last_pulse=buffer[0]; + char last_spo2=buffer[1]; + EventDataType data[2]; + wxDateTime last_pulse_time=date; + wxDateTime last_spo2_time=date; + data[0]=last_pulse; + data[1]=last_spo2; + + Session *sess=new Session(mach,sessid); + sess->set_first(date); + sess->AddEvent(new Event(date,OXI_Pulse,data,1)); + sess->AddEvent(new Event(date,OXI_SPO2,&data[1],1)); + //wxDateTime + wxDateTime tt=date; + for (int i=2;iAddEvent(new Event(tt,OXI_Pulse,data,1)); + } + if (last_spo2!=buffer[i+1]) { + data[1]=buffer[i+1]; + sess->AddEvent(new Event(tt,OXI_SPO2,&data[1],1)); + } + last_pulse=buffer[i]; + last_spo2=buffer[i+1]; + tt+=wxTimeSpan::Seconds(1); + } + sess->AddEvent(new Event(tt,OXI_Pulse,data,1)); + sess->AddEvent(new Event(tt,OXI_SPO2,&data[1],1)); + + sess->set_last(tt); + wxTimeSpan t=sess->last()-sess->first(); + + double hours=(t.GetSeconds().GetLo()/3600.0); + sess->set_hours(hours); + + sess->summary[OXI_PulseAverage]=sess->weighted_avg_event_field(OXI_Pulse,0); + sess->summary[OXI_PulseMin]=sess->min_event_field(OXI_Pulse,0); + sess->summary[OXI_PulseMax]=sess->max_event_field(OXI_Pulse,0); + sess->summary[OXI_SPO2Average]=sess->weighted_avg_event_field(OXI_SPO2,0); + sess->summary[OXI_SPO2Min]=sess->min_event_field(OXI_SPO2,0); + sess->summary[OXI_SPO2Max]=sess->max_event_field(OXI_SPO2,0); + + mach->AddSession(sess,profile); + sess->SetChanged(true); + delete buffer; + + return true; +} Machine *CMS50Loader::CreateMachine(Profile *profile) { if (!profile) { // shouldn't happen.. diff --git a/src/libs/sleeplib/loader_plugins/cms50_loader.h b/src/libs/sleeplib/loader_plugins/cms50_loader.h index e8abf149..084be58e 100644 --- a/src/libs/sleeplib/loader_plugins/cms50_loader.h +++ b/src/libs/sleeplib/loader_plugins/cms50_loader.h @@ -22,7 +22,11 @@ class CMS50Loader : public MachineLoader Machine *CreateMachine(Profile *profile); protected: + bool OpenCMS50(wxString & path, Profile *profile); + bool OpenSPORFile(wxString path, Machine * machine,Profile *profile); + private: + char *buffer; }; #endif // CMS50LOADER_H diff --git a/src/libs/sleeplib/machine.cpp b/src/libs/sleeplib/machine.cpp index d7592aff..deec3905 100644 --- a/src/libs/sleeplib/machine.cpp +++ b/src/libs/sleeplib/machine.cpp @@ -907,7 +907,7 @@ void Session::AddEvent(Event * e) { events[e->code()].push_back(e); if (e->time()>s_last) s_last=e->time(); - // wxLogMessage(e->time().Format(wxT("%Y-%m-%d %H:%M:%S"))+wxString::Format(wxT(" %04i %02i "),e->code(),e->fields())); + // wxLogMessage(e->time().Format(wxT("%Y-%m-%d %H:%M:%S"))+wxString::Format(wxT(" %04i %02i "),e->code(),e->fields())); } void Session::AddWaveform(Waveform *w) { diff --git a/src/libs/sleeplib/machine.h b/src/libs/sleeplib/machine.h index 98ced224..3f0a9e44 100644 --- a/src/libs/sleeplib/machine.h +++ b/src/libs/sleeplib/machine.h @@ -60,7 +60,7 @@ enum MachineCode//:wxInt16 PRS1_SystemLockStatus, PRS1_SystemResistanceStatus, PRS1_SystemResistanceSetting, PRS1_HoseDiameter, PRS1_AutoOff, PRS1_MaskAlert, PRS1_ShowAHI, // Oximeter Codes - OXI_Pulse=0x2000, OXI_SpO2, OXI_Plethy, OXI_Signal2, OXI_SignalGood, + OXI_Pulse=0x2000, OXI_SPO2, OXI_Plethy, OXI_Signal2, OXI_SignalGood, OXI_PulseAverage, OXI_PulseMin, OXI_PulseMax, OXI_SPO2Average, OXI_SPO2Min, OXI_SPO2Max, ZEO_SleepStage=0x2800, ZEO_Waveform,