/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * * SleepLib CMS50X Loader Implementation * * Copyright (c) 2011 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the Linux * distribution for more details. */ //******************************************************************************************** /// IMPORTANT!!! //******************************************************************************************** // Please INCREMENT the cms50_data_version in cms50_loader.h when making changes to this loader // that change loader behaviour or modify channels. //******************************************************************************************** #include #include #include #include #include #include #include #include using namespace std; #include "cms50_loader.h" #include "SleepLib/machine.h" #include "SleepLib/session.h" extern QProgressBar *qprogress; // Possibly need to replan this to include oximetry CMS50Loader::CMS50Loader() { } CMS50Loader::~CMS50Loader() { } int CMS50Loader::Open(QString & path,Profile *profile) { // CMS50 folder structure detection stuff here. // 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 if (!profile) { qWarning() << "Empty Profile in CMS50Loader::Open()"; return 0; } // This bit needs modifying for the SPO2 folder detection. QDir dir(path); QString tmp=path+"/Data"; // The directory path containing the .spor/.spo2 files if ((dir.exists("SpO2 Review.ini") || dir.exists("SpO2.ini")) && dir.exists("Data")) { // SPO2Review/etc software return OpenCMS50(tmp,profile); } return 0; } int CMS50Loader::OpenCMS50(QString & path, Profile *profile) { QString filename,pathname; QList files; QDir dir(path); if (!dir.exists()) return 0; if(qprogress) qprogress->setValue(0); dir.setFilter(QDir::Files | QDir::Hidden | QDir::NoSymLinks); dir.setSorting(QDir::Name); QFileInfoList flist=dir.entryInfoList(); QString fn; for (int i=0;iPulse(); } int size=files.size(); if (size==0) return 0; Machine *mach=CreateMachine(profile); int cnt=0; for (QList::iterator n=files.begin();n!=files.end();n++,++cnt) { if (qprogress) qprogress->setValue((float(cnt)/float(size)*50.0)); QApplication::processEvents(); OpenSPORFile((*n),mach,profile); } mach->Save(); if (qprogress) qprogress->setValue(100); return 1; } bool CMS50Loader::OpenSPORFile(QString path,Machine *mach,Profile *profile) { if (!mach || !profile) return false; QFile f(path); unsigned char tmp[256]; qint16 data_starts; qint16 some_code; qint16 some_more_code; int seconds=0,num_records; int br; if (!f.open(QIODevice::ReadOnly)) return false; // Find everything after the last _ QString str=path.section("/",-1); str=str.section("_",-1); str=str.section(".",0,0); QDateTime dt; if (str.length()==14) { dt=QDateTime::fromString(str,"yyyyMMddHHmmss"); } else if (str.length()==12) { dt=QDateTime::fromString(str,"yyyyMMddHHmm"); } else { qDebug() << "CMS50::Spo[r2] Dodgy date field"; return false; } if (!dt.isValid()) return false; SessionID sessid=dt.toTime_t(); // Import date becomes session id if (mach->SessionExists(sessid)) return false; // Already imported br=f.read((char *)tmp,2); if (br!=2) return false; data_starts=tmp[0] | (tmp[1] << 8); br=f.read((char *)tmp,2); if (br!=2) return false; some_code=tmp[0] | (tmp[1] << 8); // 512 or 256 observed Q_UNUSED(some_code); br=f.read((char *)tmp,2); if (br!=2) return false; seconds=tmp[0] | (tmp[1] << 8); if (!seconds) { num_records=(f.size()-data_starts); seconds=num_records/2; } else { num_records=seconds << 1; } if (seconds<60) { // Don't bother importing short sessions return false; } br=f.read((char *)tmp,2); if (br!=2) return false; some_more_code=tmp[0] | (tmp[1] << 8); // == 0 Q_UNUSED(some_more_code); br=f.read((char *)tmp,34); // Read widechar date record if (br!=34) return false; for (int i=0;i<17;i++) { // Convert to 8bit tmp[i]=tmp[i << 1]; } tmp[17]=0; QString datestr=(char *)tmp; QDateTime date; qint64 starttime; if (datestr.isEmpty()) { // Has Internal date record, so use it date=QDateTime::fromString(datestr,"MM/dd/yy HH:mm:ss"); QDate d2=date.date(); if (d2.year()<2000) { // Nice to see CMS50 is Y2K friendly.. d2.setDate(d2.year()+100,d2.month(),d2.day()); date.setDate(d2); } if (!date.isValid()) { qDebug() << "Invalid date time retreieved in CMS50::OpenSPO[R2]File"; return false; } starttime=qint64(date.toTime_t())*1000L; } else if (dt.isValid()) { // Else take the filenames date date=dt; starttime=qint64(dt.toTime_t())*1000L; } else { // Has nothing, so add it up to current time qDebug() << "CMS50: Couldn't get any start date indication"; date=QDateTime::currentDateTime(); date=date.addSecs(-seconds); starttime=qint64(date.toTime_t())*1000L; } f.seek(data_starts); buffer=new char [num_records]; br=f.read(buffer,num_records); if (br!=num_records) { qDebug() << "Short .spo[R2] File: " << path; delete [] buffer; return false; } //QDateTime last_pulse_time=date; //QDateTime last_spo2_time=date; EventDataType last_pulse=buffer[0]; EventDataType last_spo2=buffer[1]; EventDataType cp=0,cs=0; Session *sess=new Session(mach,sessid); sess->updateFirst(starttime); EventList *oxip=sess->AddEventList(OXI_Pulse,EVL_Event); EventList *oxis=sess->AddEventList(OXI_SPO2,EVL_Event); oxip->AddEvent(starttime,last_pulse); oxis->AddEvent(starttime,last_spo2); EventDataType PMin=0,PMax=0,SMin=0,SMax=0,PAvg=0,SAvg=0; int PCnt=0,SCnt=0; qint64 tt=starttime; //fixme: Need two lasttime values here.. qint64 lasttime=starttime; bool first_p=true,first_s=true; for (int i=2;iAddEvent(tt,cp); if (tt>lasttime) lasttime=tt; if (cp>0) { if (first_p) { PMin=cp; first_p=false; } else { if (PMin>cp) PMin=cp; } PAvg+=cp; PCnt++; } } if (last_spo2!=cs) { oxis->AddEvent(tt,cs); if (tt>lasttime) lasttime=tt; if (cs>0) { if (first_s) { SMin=cs; first_s=false; } else { if (SMin>cs) SMin=cs; } SAvg+=cs; SCnt++; } } last_pulse=cp; last_spo2=cs; if (PMaxAddEvent(tt,cp); if (cs) oxis->AddEvent(tt,cs); sess->updateLast(tt); EventDataType pa=0,sa=0; if (PCnt>0) pa=PAvg/double(PCnt); if (SCnt>0) sa=SAvg/double(SCnt); sess->setMin(OXI_Pulse,PMin); sess->setMax(OXI_Pulse,PMax); sess->setAvg(OXI_Pulse,pa); sess->setMin(OXI_SPO2,SMin); sess->setMax(OXI_SPO2,SMax); sess->setAvg(OXI_SPO2,sa); mach->AddSession(sess,profile); sess->SetChanged(true); delete [] buffer; return true; } Machine *CMS50Loader::CreateMachine(Profile *profile) { if (!profile) return NULL; // NOTE: This only allows for one CMS50 machine per profile.. // Upgrading their oximeter will use this same record.. QList ml=profile->GetMachines(MT_OXIMETER); for (QList::iterator i=ml.begin(); i!=ml.end(); i++) { if ((*i)->GetClass()==cms50_class_name) { return (*i); break; } } qDebug() << "Create CMS50 Machine Record"; Machine *m=new Oximeter(profile,0); m->SetClass(cms50_class_name); m->properties[STR_PROP_Brand]="Contec"; m->properties[STR_PROP_Model]="CMS50X"; m->properties[STR_PROP_DataVersion]=QString::number(cms50_data_version); profile->AddMachine(m); QString path="{"+STR_GEN_DataFolder+"}/"+m->GetClass()+"_"+m->hexid()+"/"; m->properties[STR_PROP_Path]=path; return m; } static bool cms50_initialized=false; void CMS50Loader::Register() { if (cms50_initialized) return; qDebug() << "Registering CMS50Loader"; RegisterLoader(new CMS50Loader()); //InitModelMap(); cms50_initialized=true; }