mirror of
https://gitlab.com/pholy/OSCAR-code.git
synced 2025-04-04 02:00:43 +00:00
490 lines
15 KiB
C++
490 lines
15 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 RemStar M-Series 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 "mseries_loader.h"
|
|
extern QProgressBar *qprogress;
|
|
|
|
|
|
|
|
MSeries::MSeries(Profile *p,MachineID id)
|
|
:CPAP(p,id)
|
|
{
|
|
m_class=mseries_class_name;
|
|
properties[STR_PROP_Brand]="Respironics";
|
|
properties[STR_PROP_Model]=STR_MACH_MSeries;
|
|
}
|
|
|
|
MSeries::~MSeries()
|
|
{
|
|
}
|
|
|
|
MSeriesLoader::MSeriesLoader()
|
|
{
|
|
epoch=QDateTime(QDate(2000,1,1),QTime(0,0,0),Qt::UTC).toTime_t();
|
|
epoch-=QDateTime(QDate(1970,1,1),QTime(0,0,0),Qt::UTC).toTime_t();
|
|
}
|
|
|
|
MSeriesLoader::~MSeriesLoader()
|
|
{
|
|
}
|
|
|
|
//struct MSeriesHeader {
|
|
// quint8 b1; //0x52
|
|
// quint32 a32; //0x00000049
|
|
// quint16 u16[8];
|
|
// quint8 b2; //0x02
|
|
// char setname[16];
|
|
// char firstname[25];
|
|
// char lastname[25];
|
|
// char serial[50];
|
|
// quint16 b3; //0x00
|
|
// quint16 b4; //0x66
|
|
// quint16 b5; //0xff
|
|
|
|
//} __attribute__((packed));
|
|
/*
|
|
|
|
blockLayoutOffsets {
|
|
cardInformationBlock = 0,
|
|
brandID = 0,
|
|
cardType = 2,
|
|
cardVersion = 3
|
|
startUIDB = 4
|
|
endUIDB = 6,
|
|
startCPB = 8,
|
|
endCPB = 10,
|
|
startCDCB = 12,
|
|
endCDCB = 14,
|
|
startCDB = 0x10,
|
|
endCDB = 0x12,
|
|
checksum = 20,
|
|
|
|
userIDBlock = 0x15
|
|
personalID = 1,
|
|
patientFName = 0x11,
|
|
patientLName = 0x2a,
|
|
serialNumber = 0x43,
|
|
modelNumber = 0x4d,
|
|
textData = 0x57
|
|
checksum = 0x77,
|
|
|
|
cardPrescriptionBlock = 0x8d,
|
|
|
|
|
|
cardDataControlBlock = 0xa3,
|
|
validFlagOne = 3,
|
|
headPtrOne = 4,
|
|
tailPtrOne = 6,
|
|
cdbChecksumOne = 8,
|
|
validFlagTwo = 9
|
|
headPtrTwo = 10,
|
|
tailPtrTwo = 12,
|
|
cdbChecksumTwo = 14,
|
|
|
|
cardDataBlock = 0xb2,
|
|
basicCompliance = 1,
|
|
fosq = 2,
|
|
Invalid = 0xff,
|
|
sleepProfile = 8,
|
|
sleepProfile2 = 10,
|
|
sleepProfile3 = 14,
|
|
sleepTrend = 9,
|
|
sleepTrend2 = 11,
|
|
sleepTrend3 = 15,
|
|
smartAutoCPAPProfile = 3,
|
|
smartAutoCPAPTrend = 4,
|
|
ventCompliance2 = 13,
|
|
ventilatorCompliance = 7,
|
|
ventilatorProfile = 6,
|
|
ventProfile2 = 12
|
|
startChar = 0xfe,
|
|
stopChar = 0x7f
|
|
*/
|
|
|
|
|
|
int MSeriesLoader::Open(QString & path,Profile *profile)
|
|
{
|
|
Q_UNUSED(profile);
|
|
// Until a smartcard reader is written, this is not an auto-scanner.. it just opens a block file..
|
|
|
|
QFile file(path);
|
|
if (!file.exists()) return 0;
|
|
if (file.size()!=32768) // Check filesize matches smartcard?
|
|
return 0;
|
|
|
|
if (!file.open(QFile::ReadOnly)) {
|
|
qDebug() << "Couldn't open M-Series file:" << path;
|
|
return 0;
|
|
}
|
|
QByteArray block=file.readAll();
|
|
|
|
|
|
// Thanks to Phil Gillam for the pointers on this one..
|
|
|
|
const unsigned char * cardinfo=(unsigned char *)block.data();
|
|
quint16 magic=cardinfo[0] << 8 | cardinfo[1];
|
|
if (magic!=0x5249) { // "RI" Respironics Magic number
|
|
return 0;
|
|
}
|
|
//quint8 cardtype=cardinfo[2];
|
|
//quint8 cardver=cardinfo[3];
|
|
|
|
quint16 user_offset=(cardinfo[4] << 8) | cardinfo[5];
|
|
//quint16 rx_offset=(cardinfo[8] << 8) | cardinfo[9];
|
|
quint16 control_offset=(cardinfo[12] << 8) | cardinfo[13];
|
|
//quint16 data_offset=(cardinfo[16] << 8) | cardinfo[17];
|
|
|
|
|
|
const char * userinfo=block.data()+user_offset;
|
|
QString setname=QString(userinfo+0x1);
|
|
QString firstname=QString(userinfo+0x11);
|
|
QString lastname=QString(userinfo+0x2a);
|
|
QString serial=QString(userinfo+0x43);
|
|
serial.truncate(10);
|
|
QString model=QString(userinfo+0x4d);
|
|
QString textdata=QString(userinfo+0x57);
|
|
quint8 userinfochk=*(userinfo+0x77);
|
|
quint8 tmp=0;
|
|
for (int i=0;i<0x77;i++) {
|
|
tmp+=userinfo[i];
|
|
}
|
|
if (tmp!=userinfochk) {
|
|
qDebug() << "MSeries UserInfo block checksum failure" << path;
|
|
}
|
|
|
|
//const unsigned char * rxblock=(unsigned char *)block.data()+rx_offset;
|
|
|
|
unsigned char * controlblock=(unsigned char *)block.data()+control_offset;
|
|
quint16 count=controlblock[0] << 8 | controlblock[1]; // number of control blocks
|
|
if (controlblock[1]!=controlblock[2]) {
|
|
qDebug() << "Control block count does not match." << path;
|
|
}
|
|
QList<quint16> head, tail;
|
|
controlblock+=3;
|
|
quint16 datastarts,dataends,tmp16,h16,t16;
|
|
if (controlblock[0]) {
|
|
datastarts=controlblock[1] | (controlblock[2] << 8);
|
|
dataends=controlblock[3] | (controlblock[4] << 8);
|
|
}
|
|
controlblock+=6;
|
|
|
|
if (controlblock[0]) {
|
|
if ((controlblock[1] | (controlblock[2] << 8))!=datastarts) {
|
|
qDebug() << "Non matching card size start identifier" << path;
|
|
}
|
|
if ((controlblock[3] | (controlblock[4] << 8))!=dataends) {
|
|
qDebug() << "Non matching card size end identifier" << path;
|
|
}
|
|
}
|
|
controlblock+=6;
|
|
count-=2;
|
|
|
|
tmp16=controlblock[0] | controlblock[1] << 8;
|
|
|
|
controlblock+=2;
|
|
for (int i=0;i<count/2;i++) {
|
|
if (controlblock[0]) {
|
|
h16=controlblock[1] | (controlblock[2] << 8);
|
|
t16=controlblock[3] | (controlblock[4] << 8);
|
|
head.push_back(h16);
|
|
tail.push_back(t16);
|
|
}
|
|
controlblock+=6;
|
|
if (controlblock[0]) {
|
|
if ((controlblock[1] | (controlblock[2] << 8))!=h16) {
|
|
qDebug() << "Non matching control block head value" << path;
|
|
}
|
|
if ((controlblock[3] | (controlblock[4] << 8))!=t16) {
|
|
qDebug() << "Non matching control block tail value" << path;
|
|
}
|
|
}
|
|
controlblock+=6;
|
|
}
|
|
|
|
unsigned char *cb=controlblock;
|
|
quint16 u1,u2,u3,u4,d1;
|
|
quint32 ts,st,lt;
|
|
QDateTime dt;
|
|
QDate date;
|
|
QTime time;
|
|
|
|
for (int chk=0;chk<7;chk++) {
|
|
ts=cb[0] << 24 | cb[1] << 16 | cb[2] << 8 | cb[3];
|
|
//ts-=epoch;
|
|
dt=QDateTime::fromTime_t(ts);
|
|
date=dt.date();
|
|
time=dt.time();
|
|
qDebug() << "New Sparse Chunk" << chk << dt << hex << ts;
|
|
|
|
cb+=4;
|
|
quint8 sum=0;
|
|
for (int i=0;i<0x268;i++) sum+=cb[i];
|
|
if (cb[0x268]==sum) {
|
|
qDebug() << "Checksum bad for block" << chk << path;
|
|
}
|
|
cb+=0x26a;
|
|
}
|
|
|
|
unsigned char * endcard=(unsigned char *)block.data()+dataends;
|
|
bool done=false;
|
|
qint64 ti;
|
|
int cnt=0;
|
|
do {
|
|
ts=cb[0] << 24 | cb[1] << 16 | cb[2] << 8 | cb[3];
|
|
lt=st=ts;
|
|
ti=qint64(ts)*1000L;
|
|
dt=QDateTime::fromTime_t(ts);
|
|
date=dt.date();
|
|
time=dt.time();
|
|
qDebug() << "Details New Data Chunk" << cnt << dt << hex << ts;
|
|
|
|
cb+=4;
|
|
do {
|
|
if (cb[0]==0xfe) { // not sure what this means
|
|
cb++;
|
|
}
|
|
u1=cb[0] << 8 | cb[1]; // expecting 0xCXXX
|
|
|
|
if (u1==0xffff) { // adjust timestamp code
|
|
cb+=2;
|
|
u1=cb[0];
|
|
cb++;
|
|
|
|
if (cb[0]==0xfe) {
|
|
u1=cb[0] << 8 | cb[1]; // fe 0a, followed by timestamp
|
|
cb+=2;
|
|
break; // start on the next timestamp
|
|
}
|
|
} else {
|
|
if ((cb[0] & 0xc0) == 0xc0) {
|
|
cb+=2;
|
|
u1 &= 0x0fff; // time delta??
|
|
lt=ts;
|
|
ts=st+(u1*60);
|
|
ti=qint64(ts) * 1000L;
|
|
|
|
d1=cb[0] << 8 | cb[1];
|
|
u2=cb[2] << 8 | cb[3];
|
|
u3=cb[4] << 8 | cb[5];
|
|
u4=cb[6] << 8 | cb[7];
|
|
if ((d1!=0xf302) || (u2!=0xf097) || (u3!=0xf2ff) || (u4!=0xf281)) {
|
|
qDebug() << "Lost details sync reading M-Series file" << path;
|
|
return false;
|
|
}
|
|
cb+=8;
|
|
} else cb++;
|
|
dt=QDateTime::fromTime_t(ts);
|
|
qDebug() << "Details Data Chunk" << cnt++ << dt;
|
|
|
|
do {
|
|
d1=cb[0] << 8 | cb[1];
|
|
cb+=2;
|
|
|
|
if (d1==0x7f0a) { // end of entire block
|
|
done=true;
|
|
break;
|
|
}
|
|
if ((d1 & 0xb000) == 0xb000) {
|
|
qDebug() << "Duration" << (d1 & 0x7ff);
|
|
break; // end of section
|
|
}
|
|
// process binary data..
|
|
// 64 c0
|
|
} while (cb<endcard);
|
|
}
|
|
} while (cb<endcard && !done);
|
|
} while (cb < endcard && !done);
|
|
|
|
done=false;
|
|
//bool first=true;
|
|
quint8 exch;
|
|
cnt=0;
|
|
do {
|
|
u1=cb[0] << 8 | cb[1];
|
|
if (u1!=0xfe0b) {
|
|
done=true;
|
|
break;
|
|
}
|
|
cb+=2;
|
|
st=ts=cb[0] << 24 | cb[1] << 16 | cb[2] << 8 | cb[3];
|
|
dt=QDateTime::fromTime_t(ts);
|
|
date=dt.date();
|
|
time=dt.time();
|
|
//qDebug() << "Summary Data Chunk" << cnt << dt << hex << ts;
|
|
cb+=4;
|
|
while (cb < endcard) {
|
|
if (((cb[0]&0xc0)!=0xc0) || ((cb[0]&0xf0)==0xf0)) {
|
|
// what is this for??
|
|
exch=cb[0];
|
|
cb++;
|
|
}
|
|
|
|
u1=(cb[0] << 8 | cb[1]) & 0x7ff; // time delta
|
|
|
|
u2=(cb[2] << 8 | cb[3]) & 0x7ff; // 0xBX XX??
|
|
ts=st+u1*60;
|
|
dt=QDateTime::fromTime_t(ts);
|
|
//qDebug() << "Summary Sub Chunk" << dt << u1 << u2 << hex << ts;
|
|
cb+=4;
|
|
if (cb[0]==0xff) break;
|
|
}
|
|
cb++; // ff;
|
|
|
|
// 05905: "22 48 00 00 04 01 01 5C 9E 30 00 F0 00 01 73 00 00 00 F2 Sat Jul 9 2011 10:44:25"
|
|
// 05905: "20 58 00 00 00 00 00 32 69 88 00 70 00 01 73 00 00 00 AF Sun Jul 10 2011 05:09:21"
|
|
// 05906: "22 00 00 00 0B 00 01 4E 79 F8 02 70 00 01 73 00 00 00 56 Sun Jul 10 2011 10:27:05"
|
|
// 05907: "21 4C 00 00 11 00 01 5C 95 F8 01 F0 00 01 73 00 00 00 54 Mon Jul 11 2011 10:59:42"
|
|
// 05908: "20 A8 00 00 02 00 01 4E 7D 88 00 F0 00 01 73 00 00 00 90 Tue Jul 12 2011 03:44:38"
|
|
// 05909: "21 94 00 00 34 01 01 6A 96 D8 01 70 00 01 73 00 00 00 FC Tue Jul 12 2011 10:30:49"
|
|
// 05910: "21 84 00 00 19 01 01 6A A2 30 00 F0 00 01 73 00 00 00 3E Wed Jul 13 2011 10:30:14"
|
|
// 05911: "22 38 00 00 3F 01 01 86 B2 A0 00 F1 00 01 73 00 00 00 F4 Thu Jul 14 2011 10:01:50"
|
|
// 05912: "21 68 00 00 36 01 01 5C 91 F8 02 70 00 01 73 00 00 00 BF Fri Jul 15 2011 10:46:33"
|
|
// 05913: "22 6C 0E 00 A1 01 01 78 AB 10 00 F0 00 01 73 00 00 00 9A Sat Jul 16 2011 10:44:56"
|
|
|
|
|
|
// 0x04 Vibratory Snore
|
|
cnt++;
|
|
QString a;
|
|
for (int i=0;i<0x13;i++) {
|
|
a+=QString().sprintf("%02X ",cb[i]);
|
|
}
|
|
a+=" "+date.toString()+" "+time.toString();
|
|
qDebug() << a;
|
|
cb+=0x13;
|
|
} while (cb < endcard && !done);
|
|
|
|
//graph data
|
|
//starts with timestamp.. or time delta if high bit is set.
|
|
|
|
// validFlagOne = 3,
|
|
// headPtrOne = 4,
|
|
// tailPtrOne = 6,
|
|
// cdbChecksumOne = 8,
|
|
// validFlagTwo = 9
|
|
// headPtrTwo = 10,
|
|
// tailPtrTwo = 12,
|
|
// cdbChecksumTwo = 14,
|
|
|
|
// const char * datablock=block.data()+data_offset;
|
|
// quint8 basicCompliance=datablock[1];
|
|
// quint8 fosq=datablock[2];
|
|
// quint8 smartAutoCPAPProfile=datablock[3];
|
|
// quint8 smartAutoCPAPTrend=datablock[4];
|
|
// quint8 ventProfile=datablock[6];
|
|
// quint8 ventCompliance1=datablock[7];
|
|
// quint8 sleepProfile1=datablock[8];
|
|
// quint8 sleepTrend1=datablock[9];
|
|
// quint8 sleepProfile2=datablock[10];
|
|
// quint8 sleepTrend2=datablock[11];
|
|
// quint8 ventProfile2=datablock[12];
|
|
// quint8 ventCompliance2=datablock[13];
|
|
// quint8 sleepProfile3=datablock[14];
|
|
// quint8 sleepTrend3=datablock[15];
|
|
|
|
|
|
// 0xa6: 01 00 b2 7f ff 31
|
|
// 0xac: 01 00 b2 7f ff 31
|
|
|
|
// 0xb2: ??? block... ?
|
|
// 0xb2: 00 00
|
|
|
|
// 0xb4: 01 36 a3 36 a2 b2 // the last bytes of all these are 8 bit additive checksums.
|
|
// 0xba: 01 36 a3 36 a2 b2
|
|
// 0xc0: 01 00 26 00 07 2e
|
|
// 0xc6: 01 00 26 00 07 2e
|
|
// 0xcc: 01 52 5a 58 e6 eb
|
|
// 0xd2: 01 52 5a 58 e6 eb
|
|
|
|
// repeat 8 times
|
|
// 0xd8: 4e 1a 4a fe
|
|
// 268 bytes
|
|
// 1 byte checksum
|
|
// starting at 0xD8, with timestamp?
|
|
// 8 blocks of 0x26e in size
|
|
|
|
// idx 0x159 =
|
|
|
|
// basicCompliance = 1,
|
|
// fosq = 2,
|
|
// sleepProfile = 8,
|
|
// sleepProfile2 = 10,
|
|
// sleepProfile3 = 14,
|
|
// sleepTrend = 9,
|
|
// sleepTrend2 = 11,
|
|
// sleepTrend3 = 15,
|
|
// smartAutoCPAPProfile = 3,
|
|
// smartAutoCPAPTrend = 4,
|
|
// ventCompliance2 = 13,
|
|
// ventilatorCompliance = 7,
|
|
// ventilatorProfile = 6,
|
|
// ventProfile2 = 12
|
|
|
|
// Invalid = 0xff,
|
|
// startChar = 0xfe,
|
|
// stopChar = 0x7f
|
|
|
|
|
|
//Machine *mach=CreateMachine(serial,profile);
|
|
|
|
|
|
// 0xcount till next block (between f3 02... blocks)
|
|
// 0xc0 00 // varies
|
|
// 0xf3 02 f0 97 f2 ff f2 81
|
|
|
|
return 1;
|
|
}
|
|
|
|
Machine *MSeriesLoader::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;
|
|
for (i=ml.begin(); i!=ml.end(); i++) {
|
|
if (((*i)->GetClass()==mseries_class_name) && ((*i)->properties[STR_PROP_Serial]==serial)) {
|
|
MachList[serial]=*i;
|
|
found=true;
|
|
break;
|
|
}
|
|
}
|
|
if (found) return *i;
|
|
|
|
Machine *m=new MSeries(profile,0);
|
|
|
|
MachList[serial]=m;
|
|
profile->AddMachine(m);
|
|
|
|
m->properties[STR_PROP_Serial]=serial;
|
|
m->properties[STR_PROP_DataVersion]=QString::number(mseries_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 mseries_initialized=false;
|
|
void MSeriesLoader::Register()
|
|
{
|
|
if (mseries_initialized) return;
|
|
qDebug() << "Registering RemStar M-Series Loader";
|
|
RegisterLoader(new MSeriesLoader());
|
|
//InitModelMap();
|
|
mseries_initialized=true;
|
|
}
|