OSCAR-code/sleepyhead/SleepLib/loader_plugins/icon_loader.cpp
Sean Stangl 9dbe702a40 Standardize project modelines and include license and copyright.
Signed-off-by: Mark Watkins <jedimark@users.sourceforge.net>
2014-04-10 13:29:53 +10:00

838 lines
22 KiB
C++

/* -*- 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 <jedimark@users.sourceforge.net>
*
* 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 <QDir>
#include <QProgressBar>
#include <QMessageBox>
#include <QDataStream>
#include <QTextStream>
#include <cmath>
#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;i<flist.size();i++) {
QFileInfo fi=flist.at(i);
QString filename=fi.fileName();
filename.toInt(&ok);
if (ok) {
SerialNumbers.push_back(filename);
}
}
Machine *m;
QString npath;
for (int i=0;i<SerialNumbers.size();i++) {
QString & sn=SerialNumbers[i];
m=CreateMachine(sn,profile);
npath=newpath+"/"+sn;
try {
if (m) OpenMachine(m,npath,profile);
} catch(OneTypePerDay e) {
profile->DelMachine(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<flist.size();i++) {
QFileInfo fi=flist.at(i);
filename=fi.fileName();
fpath=path+"/"+filename;
if (filename.left(3).toUpper()=="SUM") {
summary.push_back(fpath);
OpenSummary(mach,fpath,profile);
} else if (filename.left(3).toUpper()=="DET") {
det.push_back(fpath);
} else if (filename.left(3).toUpper()=="FLW") {
flw.push_back(fpath);
} else if (filename.left(3).toUpper()=="LOG") {
log.push_back(fpath);
}
}
for (int i=0;i<det.size();i++) {
OpenDetail(mach,det[i],profile);
}
for (int i=0;i<flw.size();i++) {
OpenFLW(mach,flw[i],profile);
}
SessionID zz,sid;//,st;
float hours,dur,mins;
qDebug() << "Last 20 Sessions";
int cnt=0;
QDateTime dt;
QString a;
QMap<SessionID, Session *>::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()<date) break;
++cnt;
} while (it!=Sessions.begin());
// qDebug() << "Unmatched Sessions";
// QList<FPWaveChunk> chunks;
// for (QMap<int,QDate>::iterator dit=FLWDate.begin();dit!=FLWDate.end();dit++) {
// int k=dit.key();
// //QDate date=dit.value();
//// QList<Session *> values = SessDate.values(date);
// for (int j=0;j<FLWTS[k].size();j++) {
// FPWaveChunk chunk(FLWTS[k].at(j),FLWDuration[k].at(j),k);
// chunk.flow=FLWMapFlow[k].at(j);
// chunk.leak=FLWMapLeak[k].at(j);
// chunk.pressure=FLWMapPres[k].at(j);
// chunks.push_back(chunk);
// zz=FLWTS[k].at(j)/1000;
// dur=double(FLWDuration[k].at(j))/60000.0;
// bool b,c=false;
// if (Sessions.contains(zz)) b=true; else b=false;
// if (b) {
// if (Sessions[zz]->channelDataExists(CPAP_FlowRate)) c=true;
// }
// qDebug() << k << "-" <<j << ":" << zz << qRound(dur) << "minutes" << (b ? "*" : "") << (c ? QDateTime::fromTime_t(zz).toString() : "");
// }
// }
// qSort(chunks);
// bool b,c;
// for (int i=0;i<chunks.size();i++) {
// const FPWaveChunk & chunk=chunks.at(i);
// zz=chunk.st/1000;
// dur=double(chunk.duration)/60000.0;
// if (Sessions.contains(zz)) b=true; else b=false;
// if (b) {
// if (Sessions[zz]->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<SessionID, Session *>::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 (buf<p);
flow->AddWaveform(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<quint32> times;
QVector<quint16> start;
QVector<quint8> 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;r<start.size();r++) {
sessid=times[r];
sess=Sessions[sessid];
ti=qint64(sessid)*1000L;
sess->really_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;i<rec;i++) {
for (int j=0;j<3;j++) {
pressure=data[idx];
leak=data[idx+1];
a1=data[idx+2];
a2=data[idx+3];
a3=data[idx+4];
PR->AddEvent(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<Machine *> ml=profile->GetMachines(MT_CPAP);
bool found=false;
QList<Machine *>::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;
}