/* -*- 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 Fisher & Paykel Icon 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. */ #include #include #include #include #include #include #include "icon_loader.h" extern QProgressBar *qprogress; FPIcon::FPIcon(Profile *p,MachineID id) :CPAP(p,id) { m_class=fpicon_class_name; } FPIcon::~FPIcon() { } FPIconLoader::FPIconLoader() { m_buffer=NULL; } FPIconLoader::~FPIconLoader() { } int FPIconLoader::Open(QString & path,Profile *profile) { QString newpath; path=path.replace("\\","/"); if (path.endsWith("/")) path.chop(1); QString dirtag="FPHCARE"; if (path.endsWith("/"+dirtag)) { newpath=path; } else { newpath=path+"/"+dirtag; } newpath+="/ICON/"; QString filename; 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(); QStringList SerialNumbers; bool ok; for (int i=0;iDelMachine(m); MachList.erase(MachList.find(sn)); 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; } } return MachList.size(); } struct FPWaveChunk { FPWaveChunk(){ st=0; duration=0; flow=NULL; pressure=NULL; leak=NULL; file=0; } FPWaveChunk(qint64 start, qint64 dur,int f) { st=start; duration=dur; file=f, flow=NULL; leak=NULL; pressure=NULL; } FPWaveChunk(const FPWaveChunk & copy) { st=copy.st; duration=copy.duration; flow=copy.flow; leak=copy.leak; pressure=copy.pressure; file=copy.file; } qint64 st; qint64 duration; int file; EventList * flow; EventList * leak; EventList * pressure; }; bool operator<(const FPWaveChunk & a, const FPWaveChunk & b) { return (a.st < b.st); } int FPIconLoader::OpenMachine(Machine *mach, QString & path, Profile * profile) { qDebug() << "Opening FPIcon " << 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,fpath; if (qprogress) qprogress->setValue(0); QStringList summary, log, flw, det; for (int i=0;i::iterator it=Sessions.end(); it--; dt=QDateTime::fromTime_t(qint64(it.value()->first())/1000L); QDate date=dt.date().addDays(-7); it++; do { it--; Session *sess=it.value(); sid=sess->session(); hours=sess->hours(); mins=hours*60; dt=QDateTime::fromTime_t(sid); if (sess->channelDataExists(CPAP_FlowRate)) a="(flow)"; else a=""; qDebug() << cnt << ":" << dt << "session" << sid << "," << mins << "minutes" << a; if (dt.date() chunks; // for (QMap::iterator dit=FLWDate.begin();dit!=FLWDate.end();dit++) { // int k=dit.key(); // //QDate date=dit.value(); //// QList values = SessDate.values(date); // for (int j=0;jchannelDataExists(CPAP_FlowRate)) c=true; // } // qDebug() << k << "-" <channelDataExists(CPAP_FlowRate)) c=true; // } // qDebug() << chunk.file << ":" << i << zz << dur << "minutes" << (b ? "*" : "") << (c ? QDateTime::fromTime_t(zz).toString() : ""); // } mach->Save(); return true; } QDateTime FPIconLoader::readFPDateTime(quint8 * data) { quint32 ts=(data[3] << 24) | (data[2] << 16) | ((data[1] << 8) | data[0]);// ^ 0xc00000; // 0x20a41b18 quint8 day=ts & 0x1f; // 0X18 24 ts >>= 5; // 10520D8 quint8 month=ts & 0x0f; // 0X08 8 ts >>= 4; // 10520D quint8 year=ts & 0x3f; // 0X0D 13 ts >>= 6; // 4148 quint8 second=ts & 0x3f;// 0X08 8 ts >>= 6; // 20A quint8 minute=ts & 0x3f;// 0A 10 ts >>= 6; // 10 quint8 hour=ts & 0x1f; // 10 16 QDate date=QDate(2000+year,month,day); QTime time=QTime(hour,minute,second); QDateTime datetime=QDateTime(date,time); return datetime; } /* *in >> a1; in >> a2; t1=a2 << 8 | a1; if (t1==0xfafe) break; day=t1 & 0x1f; month=(t1 >> 5) & 0x0f; year=2000+((t1 >> 9) & 0x3f); in >> a1; in >> a2; ts=((a2 << 8) | a1) << 1; ts|=(t1 >> 15) & 1; second=(ts & 0x3f); minute=(ts >> 6) & 0x3f; hour=(ts >> 12) & 0x1f; */ // FLW Header Structure // 0x0000-0x01fe // newline (0x0d) seperated list of machine information strings. // magic? 0201 // version 1.5.0 // serial number 12 digits // Machine Series "ICON" // Machine Model "Auto" // Remainder of header is 0 filled... // 0x01ff 8 bit additive sum checksum byte of previous header bytes // 0x0200-0x0203 32bit timestamp in bool FPIconLoader::OpenFLW(Machine * mach,QString filename, Profile * profile) { Q_UNUSED(mach); Q_UNUSED(profile); QByteArray data; quint16 t1; quint32 ts; double ti; EventList * flow=NULL, * pressure=NULL, *leak=NULL; QDateTime datetime; unsigned char * buf, *endbuf; qDebug() << filename; QByteArray header; QFile file(filename); if (!file.open(QFile::ReadOnly)) { qDebug() << "Couldn't open" << filename; return false; } header=file.read(0x200); if (header.size()!=0x200) { qDebug() << "Short file" << filename; return false; } unsigned char hsum=0xff; for (int i=0;i<0x1ff;i++) { hsum+=header[i]; } if (hsum!=header[0x1ff]) { qDebug() << "Header checksum mismatch" << filename; } QTextStream htxt(&header); QString h1,version,fname,serial,model,type; htxt >> h1; htxt >> version; htxt >> fname; htxt >> serial; htxt >> model; htxt >> type; if (mach->properties[STR_PROP_Model].isEmpty()) { mach->properties[STR_PROP_Model]=model+" "+type; } fname.chop(4); QString num=fname.right(4); int filenum=num.toInt(); data=file.readAll(); buf=(unsigned char *)data.data(); endbuf=buf+data.size(); t1=buf[1] << 8 | buf[0]; if (t1==0xfafe) // End of file marker.. { qDebug() << "FaFE observed in" << filename; return false; } datetime=readFPDateTime(buf); buf+=4; QDate date; QTime time; if (!datetime.isValid()) { qDebug() << "DateTime invalid in OpenFLW:" << filename; return false; } else { date=datetime.date(); time=datetime.time(); ts=datetime.toTime_t(); } ti=qint64(ts)*1000L; qint64 st=ti; EventStoreType pbuf[256]; QMap::iterator sit=Sessions.find(ts); Session *sess; bool newsess=false; if (sit!=Sessions.end()) { sess=sit.value(); qDebug() << filenum << ":" << date << sess->session() << ":" << sess->hours()*60.0; } else { qint64 k=-1; Session * s1=NULL; sess=NULL; for (sit=Sessions.begin();sit!=Sessions.end();sit++) { s1=sit.value(); qint64 z=qAbs(s1->first()-ti); if (z<3600000) { if ((k<0) || (k>z)) { k=z; sess=s1; } } } if (sess) { sess->set_first(ti); sess->setFirst(CPAP_FlowRate,ti); sess->setFirst(CPAP_MaskPressure,ti); } else { sess=new Session(mach,ts); sess->set_first(ti); sess->setFirst(CPAP_FlowRate,ti); sess->setFirst(CPAP_MaskPressure,ti); newsess=true; qDebug() << filenum << ":" << date << "couldn't find matching session for" << ts; } } const int samples_per_block=50; const double rate=1000.0/double(samples_per_block); // F&P Overwrites this file, not appends to it. flow=new EventList(EVL_Waveform,1.0,0,0,0,rate); //leak=new EventList(EVL_Event,1.0,0,0,0,rate*double(samples_per_block)); // 1 per second pressure=new EventList(EVL_Event,0.01,0,0,0,rate*double(samples_per_block)); // 1 per second flow->setFirst(ti); //leak->setFirst(ti); pressure->setFirst(ti); qint16 pr; quint16 lkaj; EventDataType val; qint16 tmp; bool corrupt=false; do { quint8 * p=buf; // Scan ahead looking for end of block, marked by ff ff do { p++; if (p>=endbuf) { delete flow; delete leak; delete pressure; return false; } } while (!((p[0]==0xff) && (p[1]==0xff))); // The Pressure and lkaj codes are before the end of block marker p-=3; pr=p[1] << 8 | p[0]; lkaj=p[2]; int i=0; pressure->AddEvent(ti,pr); //leak->AddEvent(ti,lkaj); do { tmp=buf[1] << 8 | buf[0]; val=(EventDataType(tmp)/100.0)-lkaj; if (val<-128) val=-128; else if (val>128) val=128; buf+=2; pbuf[i++]=val; } while (bufAddWaveform(ti,pbuf,i,rate); ti+=i*rate; buf=p+5; if (buf>=endbuf) break; } while (!((buf[0]==0xff) && (buf[1]==0x7f))); if (sess) { sess->setLast(CPAP_FlowRate,ti); sess->setLast(CPAP_MaskPressure,ti); sess->eventlist[CPAP_FlowRate].push_back(flow); // sess->eventlist[CPAP_Leak].push_back(leak); sess->eventlist[CPAP_MaskPressure].push_back(pressure); } if (newsess) mach->AddSession(sess,profile); return true; } //////////////////////////////////////////////////////////////////////////////////////////// // Open Summary file //////////////////////////////////////////////////////////////////////////////////////////// bool FPIconLoader::OpenSummary(Machine * mach,QString filename, Profile * profile) { qDebug() << filename; QByteArray header; QFile file(filename); if (!file.open(QFile::ReadOnly)) { qDebug() << "Couldn't open" << filename; return false; } header=file.read(0x200); if (header.size()!=0x200) { qDebug() << "Short file" << filename; return false; } unsigned char hsum=0xff; for (int i=0;i<0x1ff;i++) { hsum+=header[i]; } if (hsum!=header[0x1ff]) { qDebug() << "Header checksum mismatch" << filename; } QTextStream htxt(&header); QString h1,version,fname,serial,model,type; htxt >> h1; htxt >> version; htxt >> fname; htxt >> serial; htxt >> model; htxt >> type; mach->properties[STR_PROP_Model]=model+" "+type; QByteArray data; data=file.readAll(); //long size=data.size(),pos=0; QDataStream in(data); in.setVersion(QDataStream::Qt_4_6); in.setByteOrder(QDataStream::LittleEndian); quint16 t1;//,t2; quint32 ts; //QByteArray line; unsigned char a1,a2, a3,a4, a5, p1, p2, p3, p4, p5, j1, j2, j3 ,j4,j5,j6,j7, x1, x2; quint16 d1,d2,d3; QDateTime datetime; int runtime,usage; int day,month,year,hour,minute,second; QDate date; do { in >> a1; in >> a2; t1=a2 << 8 | a1; if (t1==0xfafe) break; day=t1 & 0x1f; month=(t1 >> 5) & 0x0f; year=2000+((t1 >> 9) & 0x3f); in >> a1; in >> a2; ts=((a2 << 8) | a1) << 1; ts|=(t1 >> 15) & 1; second=(ts & 0x3f); minute=(ts >> 6) & 0x3f; hour=(ts >> 12) & 0x1f; datetime=QDateTime(QDate(year,month,day),QTime(hour,minute,second)); date=datetime.date(); ts=datetime.toTime_t(); // the following two quite often match in value in >> a1; // 0x04 Run Time in >> a2; // 0x05 Usage Time runtime=a1 * 360; // durations are in tenth of an hour intervals usage=a2 * 360; in >> a3; // 0x06 // Ramps??? in >> a4; // 0x07 // a pressure value? in >> a5; // 0x08 // ?? varies.. always less than 90% leak.. in >> d1; // 0x09 in >> d2; // 0x0b in >> d3; // 0x0d // 90% Leak value.. in >> p1; // 0x0f in >> p2; // 0x10 in >> j1; // 0x11 in >> j2; // 0x12 // Apnea Events in >> j3; // 0x13 // Hypopnea events in >> j4; // 0x14 // Flow Limitation events in >> j5; // 0x15 in >> j6; // 0x16 in >> j7; // 0x17 in >> p3; // 0x18 in >> p4; // 0x19 in >> p5; // 0x1a in >> x1; // 0x1b in >> x2; // 0x1c // humidifier setting if (!mach->SessionExists(ts)) { Session *sess=new Session(mach,ts); sess->really_set_first(qint64(ts)*1000L); sess->really_set_last(qint64(ts+usage)*1000L); sess->SetChanged(true); sess->setCount(CPAP_Obstructive, j2); sess->setCount(CPAP_Hypopnea, j3); SessDate.insert(date,sess); // sess->setCount(CPAP_Obstructive,j1); // sess->setCount(CPAP_Hypopnea,j2); // sess->setCount(CPAP_ClearAirway,j3); // sess->setCount(CPAP_Apnea,j4); //sess->setCount(CPAP_,j5); if (p1!=p2) { sess->settings[CPAP_Mode]=(int)MODE_APAP; sess->settings[CPAP_PressureMin]=p3/10.0; sess->settings[CPAP_PressureMax]=p4/10.0; } else { sess->settings[CPAP_Mode]=(int)MODE_CPAP; sess->settings[CPAP_Pressure]=p1/10.0; } sess->settings[CPAP_HumidSetting]=x2; //sess->settings[CPAP_PresReliefType]=PR_SENSAWAKE; Sessions[ts]=sess; mach->AddSession(sess,profile); } } while (!in.atEnd()); return true; } bool FPIconLoader::OpenDetail(Machine * mach, QString filename, Profile * profile) { Q_UNUSED(mach); Q_UNUSED(profile); qDebug() << filename; QByteArray header; QFile file(filename); if (!file.open(QFile::ReadOnly)) { qDebug() << "Couldn't open" << filename; return false; } header=file.read(0x200); if (header.size()!=0x200) { qDebug() << "Short file" << filename; return false; } unsigned char hsum=0; for (int i=0;i<0x1ff;i++) { hsum+=header[i]; } if (hsum!=header[0x1ff]) { qDebug() << "Header checksum mismatch" << filename; } QByteArray index; index=file.read(0x800); //long size=index.size(),pos=0; QDataStream in(index); in.setVersion(QDataStream::Qt_4_6); in.setByteOrder(QDataStream::LittleEndian); quint32 ts; QDateTime datetime; QDate date; QTime time; //FPDetIdx *idx=(FPDetIdx *)index.data(); QVector times; QVector start; QVector records; quint16 t1; quint16 strt; quint8 recs,z1,z2; int day,month,year,hour,minute,second; int totalrecs=0; do { in >> z1; in >> z2; t1=z2 << 8 | z1; if (t1==0xfafe) break; day=t1 & 0x1f; month=(t1 >> 5) & 0x0f; year=2000+((t1 >> 9) & 0x3f); in >> z1; in >> z2; // ts=((z2 << 8) | z1) << 1; ts|=(t1 >> 15) & 1; // second=(ts & 0x3f); minute=(ts >> 6) & 0x3f; hour=(ts >> 12) & 0x1f; datetime=QDateTime(QDate(year,month,day),QTime(hour,minute,second)); //datetime=datetime.toTimeSpec(Qt::UTC); ts=datetime.toTime_t(); date=datetime.date(); time=datetime.time(); in >> strt; in >> recs; totalrecs+=recs; if (Sessions.contains(ts)) { times.push_back(ts); start.push_back(strt); records.push_back(recs); } } while (!in.atEnd()); QByteArray databytes=file.readAll(); in.setVersion(QDataStream::Qt_4_6); in.setByteOrder(QDataStream::BigEndian); // 5 byte repeating patterns quint8 * data=(quint8 *)databytes.data(); qint64 ti; quint8 pressure,leak, a1,a2,a3; SessionID sessid; Session *sess; int idx; for (int r=0;rreally_set_first(ti); EventList * LK=sess->AddEventList(CPAP_LeakTotal,EVL_Event,1); EventList * PR=sess->AddEventList(CPAP_Pressure,EVL_Event,0.1); EventList * FLG=sess->AddEventList(CPAP_FLG,EVL_Event); EventList * OA=sess->AddEventList(CPAP_Obstructive,EVL_Event); EventList * H=sess->AddEventList(CPAP_Hypopnea,EVL_Event); EventList * FL=sess->AddEventList(CPAP_FlowLimit,EVL_Event); unsigned stidx=start[r]; int rec=records[r]; idx=stidx*15; for (int i=0;iAddEvent(ti,pressure); LK->AddEvent(ti,leak); if (a1>0) OA->AddEvent(ti,a1); if (a2>0) H->AddEvent(ti,a2); if (a3>0) FL->AddEvent(ti,a3); FLG->AddEvent(ti,a3); ti+=120000L; idx+=5; } } // sess->really_set_last(ti-360000L); // sess->SetChanged(true); // mach->AddSession(sess,profile); } return 1; } Machine *FPIconLoader::CreateMachine(QString serial,Profile *profile) { if (!profile) return NULL; qDebug() << "Create Machine " << serial; QList ml=profile->GetMachines(MT_CPAP); bool found=false; QList::iterator i; Machine *m; for (i=ml.begin(); i!=ml.end(); i++) { if (((*i)->GetClass()==fpicon_class_name) && ((*i)->properties[STR_PROP_Serial]==serial)) { MachList[serial]=*i; found=true; m=*i; break; } } if (!found) { m=new FPIcon(profile,0); } m->properties[STR_PROP_Brand]="Fisher & Paykel"; m->properties[STR_PROP_Series]=STR_MACH_FPIcon; m->properties[STR_PROP_Model]=STR_MACH_FPIcon; if (found) return m; MachList[serial]=m; profile->AddMachine(m); m->properties[STR_PROP_Serial]=serial; m->properties[STR_PROP_DataVersion]=QString::number(fpicon_data_version); QString path="{"+STR_GEN_DataFolder+"}/"+m->GetClass()+"_"+serial+"/"; m->properties[STR_PROP_Path]=path; m->properties[STR_PROP_BackupPath]=path+"Backup/"; return m; } bool fpicon_initialized=false; void FPIconLoader::Register() { if (fpicon_initialized) return; qDebug() << "Registering F&P Icon Loader"; RegisterLoader(new FPIconLoader()); //InitModelMap(); fpicon_initialized=true; }