/* -*- 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 (DeVilbiss) Intellipap Loader Implementation * Notes: Intellipap requires the SmartLink attachment to access this data. * * Copyright (c) 2011-2014 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 "intellipap_loader.h" extern QProgressBar *qprogress; Intellipap::Intellipap(Profile *p, MachineID id) : CPAP(p, id) { m_class = intellipap_class_name; } Intellipap::~Intellipap() { } IntellipapLoader::IntellipapLoader() { m_buffer = nullptr; } IntellipapLoader::~IntellipapLoader() { } int IntellipapLoader::Open(QString &path, Profile *profile) { // Check for SL directory // Check for DV5MFirm.bin? QString newpath; path = path.replace("\\", "/"); QString dirtag = "SL"; if (path.endsWith("/" + dirtag)) { return 0; //newpath=path; } else { newpath = path + "/" + dirtag; } QString filename; ////////////////////////// // Parse the Settings File ////////////////////////// filename = newpath + "/SET1"; QFile f(filename); if (!f.exists()) { return 0; } f.open(QFile::ReadOnly); QTextStream tstream(&f); QHash lookup; lookup["Sn"] = STR_PROP_Serial; lookup["Mn"] = STR_PROP_ModelNumber; lookup["Mo"] = "PAPMode"; // 0=cpap, 1=auto //lookup["Pn"]="Pn"; lookup["Pu"] = "MaxPressure"; lookup["Pl"] = "MinPressure"; //lookup["Ds"]="Ds"; //lookup["Pc"]="Pc"; lookup["Pd"] = "RampPressure"; // Delay Pressure lookup["Dt"] = "RampTime"; // Delay Time //lookup["Ld"]="Ld"; //lookup["Lh"]="Lh"; //lookup["FC"]="FC"; //lookup["FE"]="FE"; //lookup["FL"]="FL"; lookup["A%"] = "ApneaThreshold"; lookup["Ad"] = "ApneaDuration"; lookup["H%"] = "HypopneaThreshold"; lookup["Hd"] = "HypopneaDuration"; //lookup["Pi"]="Pi"; //080 //lookup["Pe"]="Pe"; //WF lookup["Ri"] = "SmartFlexIRnd"; // Inhale Rounding (0-5) lookup["Re"] = "SmartFlexERnd"; // Exhale Rounding (0-5) //lookup["Bu"]="Bu"; //WF //lookup["Ie"]="Ie"; //20 //lookup["Se"]="Se"; //05 //lookup["Si"]="Si"; //05 //lookup["Mi"]="Mi"; //0 lookup["Uh"] = "HoursMeter"; //0000.0 lookup["Up"] = "ComplianceMeter"; //0000.0 //lookup["Er"]="ErrorCode"; // E00 //lookup["El"]="LastErrorCode"; // E00 00/00/0000 //lookup["Hp"]="Hp"; //1 //lookup["Hs"]="Hs"; //02 //lookup["Lu"]="LowUseThreshold"; // defaults to 0 (4 hours) lookup["Sf"] = "SmartFlex"; lookup["Sm"] = "SmartFlexMode"; lookup["Ks=s"] = "Ks_s"; lookup["Ks=i"] = "Ks_i"; QHash set1; QHash::iterator hi; while (1) { QString line = tstream.readLine(); if ((line.length() <= 2) || (line.isNull())) { break; } QString key = line.section("\t", 0, 0).trimmed(); hi = lookup.find(key); if (hi != lookup.end()) { key = hi.value(); } QString value = line.section("\t", 1).trimmed(); set1[key] = value; qDebug() << key << "=" << value; } Machine *mach = nullptr; if (set1.contains(STR_PROP_Serial)) { mach = CreateMachine(set1[STR_PROP_Serial], profile); } if (!mach) { qDebug() << "Couldn't get Intellipap machine record"; return 0; } // Refresh properties data.. for (QHash::iterator i = set1.begin(); i != set1.end(); i++) { mach->properties[i.key()] = i.value(); } mach->properties[STR_PROP_Model] = STR_MACH_Intellipap + " " + mach->properties[STR_PROP_ModelNumber]; f.close(); ////////////////////////// // Parse the Session Index ////////////////////////// unsigned char buf[27]; filename = newpath + "/U"; f.setFileName(filename); if (!f.exists()) { return 0; } QVector SessionStart; QVector SessionEnd; QHash Sessions; quint32 ts1, ts2;//, length; //unsigned char cs; f.open(QFile::ReadOnly); int cnt = 0; QDateTime epoch(QDate(2002, 1, 1), QTime(0, 0, 0), Qt::UTC); // Intellipap Epoch int ep = epoch.toTime_t(); do { cnt = f.read((char *)buf, 9); ts1 = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; ts2 = (buf[4] << 24) | (buf[5] << 16) | (buf[6] << 8) | buf[7]; ts1 += ep; ts2 += ep; SessionStart.append(ts1); SessionEnd.append(ts2); //cs=buf[8]; } while (cnt > 0); qDebug() << "U file logs" << SessionStart.size() << "sessions."; f.close(); ////////////////////////// // Parse the Session Data ////////////////////////// filename = newpath + "/L"; f.setFileName(filename); if (!f.exists()) { return 0; } f.open(QFile::ReadOnly); long size = f.size(); int recs = size / 26; m_buffer = new unsigned char [size]; if (size != f.read((char *)m_buffer, size)) { qDebug() << "Couldn't read 'L' data" << filename; return 0; } Session *sess; SessionID sid; for (int i = 0; i < SessionStart.size(); i++) { sid = SessionStart[i]; if (mach->SessionExists(sid)) { // knock out the already imported sessions.. SessionStart[i] = 0; SessionEnd[i] = 0; } else if (!Sessions.contains(sid)) { sess = Sessions[sid] = new Session(mach, sid); sess->SetChanged(true); sess->AddEventList(CPAP_IPAP, EVL_Event); sess->AddEventList(CPAP_EPAP, EVL_Event); sess->AddEventList(CPAP_Pressure, EVL_Event); sess->AddEventList(INTELLIPAP_Unknown1, EVL_Event); sess->AddEventList(INTELLIPAP_Unknown2, EVL_Event); sess->AddEventList(CPAP_LeakTotal, EVL_Event); sess->AddEventList(CPAP_MaxLeak, EVL_Event); sess->AddEventList(CPAP_TidalVolume, EVL_Event); sess->AddEventList(CPAP_MinuteVent, EVL_Event); sess->AddEventList(CPAP_RespRate, EVL_Event); sess->AddEventList(CPAP_Snore, EVL_Event); } else { // If there is a double up, null out the earlier session // otherwise there will be a crash on shutdown. for (int z = 0; z < SessionStart.size(); z++) { if (SessionStart[z] == (quint32)sid) { SessionStart[z] = 0; SessionEnd[z] = 0; break; } } QDateTime d = QDateTime::fromTime_t(sid); qDebug() << sid << "has double ups" << d; /*Session *sess=Sessions[sid]; Sessions.erase(Sessions.find(sid)); delete sess; SessionStart[i]=0; SessionEnd[i]=0; */ } } long pos = 0; for (int i = 0; i < recs; i++) { // convert timestamp to real epoch ts1 = ((m_buffer[pos] << 24) | (m_buffer[pos + 1] << 16) | (m_buffer[pos + 2] << 8) | m_buffer[pos + 3]) + ep; for (int j = 0; j < SessionStart.size(); j++) { sid = SessionStart[j]; if (!sid) { continue; } if ((ts1 >= (quint32)sid) && (ts1 <= SessionEnd[j])) { Session *sess = Sessions[sid]; qint64 time = quint64(ts1) * 1000L; sess->eventlist[CPAP_Pressure][0]->AddEvent(time, m_buffer[pos + 0xd] / 10.0); // current pressure sess->eventlist[CPAP_EPAP][0]->AddEvent(time, m_buffer[pos + 0x13] / 10.0); // epap / low sess->eventlist[CPAP_IPAP][0]->AddEvent(time, m_buffer[pos + 0x14] / 10.0); // ipap / high sess->eventlist[CPAP_LeakTotal][0]->AddEvent(time, m_buffer[pos + 0x7]); // "Average Leak" sess->eventlist[CPAP_MaxLeak][0]->AddEvent(time, m_buffer[pos + 0x6]); // "Max Leak" int rr = m_buffer[pos + 0xa]; sess->eventlist[CPAP_RespRate][0]->AddEvent(time, rr); // Respiratory Rate sess->eventlist[INTELLIPAP_Unknown1][0]->AddEvent(time, m_buffer[pos + 0xf]); // sess->eventlist[INTELLIPAP_Unknown1][0]->AddEvent(time, m_buffer[pos + 0xc]); sess->eventlist[CPAP_Snore][0]->AddEvent(time, m_buffer[pos + 0x4]); //4/5?? // 0x0f == Leak Event // 0x04 == Snore? if (m_buffer[pos + 0xf] > 0) { // Leak Event if (!sess->eventlist.contains(CPAP_LeakFlag)) { sess->AddEventList(CPAP_LeakFlag, EVL_Event); } sess->eventlist[CPAP_LeakFlag][0]->AddEvent(time, m_buffer[pos + 0xf]); } if (m_buffer[pos + 0x5] > 4) { // This matches Exhale Puff.. not sure why 4 if (!sess->eventlist.contains(CPAP_ExP)) { sess->AddEventList(CPAP_ExP, EVL_Event); } for (int q = 0; q < m_buffer[pos + 0x5]; q++) { sess->eventlist[CPAP_ExP][0]->AddEvent(time, m_buffer[pos + 0x5]); } } if (m_buffer[pos + 0x10] > 0) { if (!sess->eventlist.contains(CPAP_Obstructive)) { sess->AddEventList(CPAP_Obstructive, EVL_Event); } for (int q = 0; q < m_buffer[pos + 0x10]; q++) { sess->eventlist[CPAP_Obstructive][0]->AddEvent(time, m_buffer[pos + 0x10]); } } if (m_buffer[pos + 0x11] > 0) { if (!sess->eventlist.contains(CPAP_Hypopnea)) { sess->AddEventList(CPAP_Hypopnea, EVL_Event); } for (int q = 0; q < m_buffer[pos + 0x11]; q++) { sess->eventlist[CPAP_Hypopnea][0]->AddEvent(time, m_buffer[pos + 0x11]); } } if (m_buffer[pos + 0x12] > 0) { // NRI // is this == to RERA?? CA?? if (!sess->eventlist.contains(CPAP_NRI)) { sess->AddEventList(CPAP_NRI, EVL_Event); } for (int q = 0; q < m_buffer[pos + 0x12]; q++) { sess->eventlist[CPAP_NRI][0]->AddEvent(time, m_buffer[pos + 0x12]); } } quint16 tv = (m_buffer[pos + 0x8] << 8) | m_buffer[pos + 0x9]; // correct sess->eventlist[CPAP_TidalVolume][0]->AddEvent(time, tv); EventDataType mv = tv * rr; // MinuteVent=TidalVolume * Respiratory Rate sess->eventlist[CPAP_MinuteVent][0]->AddEvent(time, mv / 1000.0); break; } } pos += 26; } for (int i = 0; i < SessionStart.size(); i++) { SessionID sid = SessionStart[i]; if (sid) { sess = Sessions[sid]; //if (sess->eventlist.size()==0) { // delete sess; // continue; //} quint64 first = qint64(sid) * 1000L; quint64 last = qint64(SessionEnd[i]) * 1000L; sess->settings[CPAP_PresReliefType] = (PRTypes)PR_SMARTFLEX; int i = set1["SmartFlex"].toInt(); sess->settings[CPAP_PresReliefSet] = i; int sfm = set1["SmartFlexMode"].toInt(); if (sfm == 0) { sess->settings[CPAP_PresReliefMode] = PM_FullTime; } else { sess->settings[CPAP_PresReliefMode] = PM_RampOnly; } EventDataType max = sess->Max(CPAP_IPAP); EventDataType min = sess->Min(CPAP_EPAP); EventDataType pres = sess->Min(CPAP_Pressure); if (max == min) { sess->settings[CPAP_Mode] = (int)MODE_CPAP; sess->settings[CPAP_Pressure] = min; } else { sess->settings[CPAP_Mode] = (int)MODE_APAP; sess->settings[CPAP_PressureMin] = min; sess->settings[CPAP_PressureMax] = max; } sess->eventlist.erase(sess->eventlist.find(CPAP_IPAP)); sess->eventlist.erase(sess->eventlist.find(CPAP_EPAP)); sess->m_min.erase(sess->m_min.find(CPAP_EPAP)); sess->m_max.erase(sess->m_max.find(CPAP_EPAP)); if (pres < min) { sess->settings[CPAP_RampPressure] = pres; } //quint64 len=last-first; //if (len>0) { //if (!sess->first()) { sess->set_first(first); sess->set_last(last); // } sess->UpdateSummaries(); mach->AddSession(sess, profile); /*} else { delete sess; }*/ } } mach->properties[STR_PROP_DataVersion] = QString().sprintf("%i", intellipap_data_version); mach->Save(); delete [] m_buffer; if (qprogress) { qprogress->setValue(100); } f.close(); return 1; } Machine *IntellipapLoader::CreateMachine(QString serial, Profile *profile) { if (!profile) { return nullptr; } qDebug() << "Create Machine " << serial; QList ml = profile->GetMachines(MT_CPAP); bool found = false; QList::iterator i; Machine *m = nullptr; for (i = ml.begin(); i != ml.end(); i++) { if (((*i)->GetClass() == intellipap_class_name) && ((*i)->properties[STR_PROP_Serial] == serial)) { MachList[serial] = *i; //static_cast(*i); found = true; m = *i; break; } } if (!found) { m = new Intellipap(profile, 0); } m->properties[STR_PROP_Brand] = "DeVilbiss"; m->properties[STR_PROP_Series] = STR_MACH_Intellipap; if (found) { return m; } MachList[serial] = m; profile->AddMachine(m); m->properties[STR_PROP_Serial] = serial; m->properties[STR_PROP_DataVersion] = QString::number(intellipap_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 intellipap_initialized = false; void IntellipapLoader::Register() { if (intellipap_initialized) { return; } qDebug() << "Registering IntellipapLoader"; RegisterLoader(new IntellipapLoader()); //InitModelMap(); intellipap_initialized = true; }