2021-10-26 18:59:41 +00:00
/* SleepLib (DeVilbiss) Intellipap Loader Implementation
2014-04-09 21:01:57 +00:00
*
2018-03-28 07:10:52 +00:00
* Notes : Intellipap DV54 requires the SmartLink attachment to access this data .
2014-04-09 21:01:57 +00:00
*
2018-03-28 07:10:52 +00:00
* Copyright ( c ) 2011 - 2018 Mark Watkins < mark @ jedimark . net >
2021-11-02 20:34:12 +00:00
* Copyright ( c ) 2019 - 2022 The OSCAR Team
2014-04-09 21:01:57 +00:00
*
* This file is subject to the terms and conditions of the GNU General Public
2018-06-04 20:48:38 +00:00
* License . See the file COPYING in the main directory of the source code
* for more details . */
2011-11-20 02:59:00 +00:00
2011-11-21 02:15:10 +00:00
# include <QDir>
2021-06-18 20:31:56 +00:00
# include <QCoreApplication>
2011-11-21 02:15:10 +00:00
2011-11-20 02:59:00 +00:00
# include "intellipap_loader.h"
2021-01-26 02:56:59 +00:00
//#define DEBUG6
2014-08-03 13:00:13 +00:00
ChannelID INTP_SmartFlexMode , INTP_SmartFlexLevel ;
2018-06-04 23:26:46 +00:00
Intellipap : : Intellipap ( Profile * profile , MachineID id )
: CPAP ( profile , id )
2011-11-20 02:59:00 +00:00
{
}
Intellipap : : ~ Intellipap ( )
{
}
IntellipapLoader : : IntellipapLoader ( )
{
2014-09-17 17:20:01 +00:00
const QString INTELLIPAP_ICON = " :/icons/intellipap.png " ;
2018-04-22 12:06:48 +00:00
const QString DV6_ICON = " :/icons/dv64.png " ;
2014-09-17 17:20:01 +00:00
QString s = newInfo ( ) . series ;
m_pixmap_paths [ s ] = INTELLIPAP_ICON ;
m_pixmaps [ s ] = QPixmap ( INTELLIPAP_ICON ) ;
2018-04-22 12:06:48 +00:00
m_pixmap_paths [ " DV6 " ] = DV6_ICON ;
m_pixmaps [ " DV6 " ] = QPixmap ( DV6_ICON ) ;
2014-09-17 17:20:01 +00:00
2014-04-23 13:19:56 +00:00
m_buffer = nullptr ;
2014-05-25 07:07:08 +00:00
m_type = MT_CPAP ;
2011-11-20 02:59:00 +00:00
}
IntellipapLoader : : ~ IntellipapLoader ( )
{
}
2018-03-25 19:23:05 +00:00
const QString SET_BIN = " SET.BIN " ;
const QString SET1 = " SET1 " ;
const QString DV6 = " DV6 " ;
const QString SL = " SL " ;
const QString DV6_DIR = " / " + DV6 ;
const QString SL_DIR = " / " + SL ;
2014-04-28 19:24:39 +00:00
bool IntellipapLoader : : Detect ( const QString & givenpath )
2014-04-26 09:54:08 +00:00
{
2016-03-06 02:50:22 +00:00
QString path = givenpath ;
2018-03-25 19:23:05 +00:00
if ( path . endsWith ( SL_DIR ) ) {
2016-03-06 02:50:22 +00:00
path . chop ( 3 ) ;
}
2018-03-25 19:23:05 +00:00
if ( path . endsWith ( DV6_DIR ) ) {
path . chop ( 4 ) ;
}
2016-03-06 02:50:22 +00:00
QDir dir ( path ) ;
2014-04-28 19:24:39 +00:00
if ( ! dir . exists ( ) ) {
return false ;
}
2018-03-25 19:23:05 +00:00
// Intellipap DV54 has a folder called SL in the root directory, DV64 has DV6
if ( dir . cd ( SL ) ) {
// Test for presence of settings file
return dir . exists ( SET1 ) ? true : false ;
2014-04-28 19:24:39 +00:00
}
2018-03-25 19:23:05 +00:00
if ( dir . cd ( DV6 ) ) { // DV64
return dir . exists ( SET_BIN ) ? true : false ;
2014-04-28 19:24:39 +00:00
}
2018-03-25 19:23:05 +00:00
return false ;
2014-04-26 09:54:08 +00:00
}
2018-03-25 19:23:05 +00:00
enum INTPAP_Type { INTPAP_Unknown , INTPAP_DV5 , INTPAP_DV6 } ;
2013-09-14 23:32:14 +00:00
2011-11-21 02:15:10 +00:00
2018-04-27 04:29:03 +00:00
int IntellipapLoader : : OpenDV5 ( const QString & path )
2018-03-25 19:23:05 +00:00
{
QString newpath = path + SL_DIR ;
2011-11-21 10:20:11 +00:00
QString filename ;
2021-04-09 04:05:54 +00:00
qDebug ( ) < < " DV5 Loader started " ;
2018-03-25 19:23:05 +00:00
2011-11-21 10:20:11 +00:00
//////////////////////////
// Parse the Settings File
//////////////////////////
2018-03-25 19:23:05 +00:00
filename = newpath + " / " + SET1 ;
2011-11-21 02:15:10 +00:00
QFile f ( filename ) ;
2014-04-17 05:58:57 +00:00
2016-03-06 02:50:22 +00:00
if ( ! f . exists ( ) ) {
return - 1 ;
}
2014-04-17 05:58:57 +00:00
2011-11-21 10:20:11 +00:00
f . open ( QFile : : ReadOnly ) ;
QTextStream tstream ( & f ) ;
2014-07-28 13:56:29 +00:00
const QString INT_PROP_Serial = " Serial " ;
const QString INT_PROP_Model = " Model " ;
const QString INT_PROP_Mode = " Mode " ;
const QString INT_PROP_MaxPressure = " Max Pressure " ;
const QString INT_PROP_MinPressure = " Min Pressure " ;
const QString INT_PROP_IPAP = " IPAP " ;
const QString INT_PROP_EPAP = " EPAP " ;
const QString INT_PROP_PS = " PS " ;
const QString INT_PROP_RampPressure = " Ramp Pressure " ;
const QString INT_PROP_RampTime = " Ramp Time " ;
const QString INT_PROP_HourMeter = " Usage Hours " ;
const QString INT_PROP_ComplianceMeter = " Compliance Hours " ;
const QString INT_PROP_ErrorCode = " Error " ;
const QString INT_PROP_LastErrorCode = " Long Error " ;
const QString INT_PROP_LowUseThreshold = " Low Usage " ;
const QString INT_PROP_SmartFlex = " SmartFlex " ;
const QString INT_PROP_SmartFlexMode = " SmartFlexMode " ;
2014-05-06 17:39:05 +00:00
QHash < QString , QString > lookup ;
2014-07-28 13:56:29 +00:00
lookup [ " Sn " ] = INT_PROP_Serial ;
lookup [ " Mn " ] = INT_PROP_Model ;
lookup [ " Mo " ] = INT_PROP_Mode ; // 0 cpap, 1 auto
2014-05-06 17:39:05 +00:00
//lookup["Pn"]="??";
2014-07-28 13:56:29 +00:00
lookup [ " Pu " ] = INT_PROP_MaxPressure ;
lookup [ " Pl " ] = INT_PROP_MinPressure ;
lookup [ " Pi " ] = INT_PROP_IPAP ;
lookup [ " Pe " ] = INT_PROP_EPAP ; // == WF on Auto models
lookup [ " Ps " ] = INT_PROP_PS ; // == WF on Auto models, Pressure support
2014-05-06 17:39:05 +00:00
//lookup["Ds"]="??";
//lookup["Pc"]="??";
2014-07-28 13:56:29 +00:00
lookup [ " Pd " ] = INT_PROP_RampPressure ;
lookup [ " Dt " ] = INT_PROP_RampTime ;
2014-05-06 17:39:05 +00:00
//lookup["Ld"]="??";
//lookup["Lh"]="??";
//lookup["FC"]="??";
//lookup["FE"]="??";
//lookup["FL"]="??";
lookup [ " A% " ] = " ApneaThreshold " ;
lookup [ " Ad " ] = " ApneaDuration " ;
lookup [ " H% " ] = " HypopneaThreshold " ;
lookup [ " Hd " ] = " HypopneaDuration " ;
//lookup["Pi"]="??";
//lookup["Pe"]="??";
lookup [ " Ri " ] = " SmartFlexIRnd " ; // Inhale Rounding (0-5)
lookup [ " Re " ] = " SmartFlexERnd " ; // Inhale Rounding (0-5)
//lookup["Bu"]="??"; // WF
//lookup["Ie"]="??"; // 20
2014-07-28 13:56:29 +00:00
//lookup["Se"]="??"; // 05 //Inspiratory trigger?
//lookup["Si"]="??"; // 05 // Expiratory Trigger?
2014-05-06 17:39:05 +00:00
//lookup["Mi"]="??"; // 0
lookup [ " Uh " ] = " HoursMeter " ; // 0000.0
lookup [ " Up " ] = " ComplianceMeter " ; // 0000.00
//lookup["Er"]="ErrorCode";, // E00
2014-07-28 13:56:29 +00:00
//lookup["El"]="LongErrorCode"; // E00 00/00/0000
2014-05-06 17:39:05 +00:00
//lookup["Hp"]="??";, // 1
//lookup["Hs"]="??";, // 02
//lookup["Lu"]="LowUseThreshold"; // defaults to 0 (4 hours)
2014-07-28 13:56:29 +00:00
lookup [ " Sf " ] = INT_PROP_SmartFlex ;
lookup [ " Sm " ] = INT_PROP_SmartFlexMode ;
2014-05-06 17:39:05 +00:00
lookup [ " Ks=s " ] = " Ks_s " ;
lookup [ " Ks=i " ] = " ks_i " ;
2014-04-17 05:58:57 +00:00
QHash < QString , QString > set1 ;
QHash < QString , QString > : : iterator hi ;
2011-11-21 10:20:11 +00:00
2014-07-28 13:56:29 +00:00
Machine * mach = nullptr ;
MachineInfo info = newInfo ( ) ;
bool ok ;
2014-10-08 16:51:09 +00:00
//EventDataType min_pressure = 0, max_pressure = 0, set_ipap = 0, set_ps = 0,
EventDataType ramp_pressure = 0 , set_epap = 0 , ramp_time = 0 ;
2014-07-28 13:56:29 +00:00
int papmode = 0 , smartflex = 0 , smartflexmode = 0 ;
2011-11-21 10:20:11 +00:00
while ( 1 ) {
2014-04-17 05:58:57 +00:00
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 ( ) ;
2011-11-21 10:20:11 +00:00
}
2014-04-17 05:58:57 +00:00
QString value = line . section ( " \t " , 1 ) . trimmed ( ) ;
2014-07-28 13:56:29 +00:00
if ( key = = INT_PROP_Mode ) {
papmode = value . toInt ( & ok ) ;
} else if ( key = = INT_PROP_Serial ) {
info . serial = value ;
} else if ( key = = INT_PROP_Model ) {
info . model = value ;
} else if ( key = = INT_PROP_MinPressure ) {
2014-10-08 16:51:09 +00:00
//min_pressure = value.toFloat() / 10.0;
2014-07-28 13:56:29 +00:00
} else if ( key = = INT_PROP_MaxPressure ) {
2014-10-08 16:51:09 +00:00
//max_pressure = value.toFloat() / 10.0;
2014-07-28 13:56:29 +00:00
} else if ( key = = INT_PROP_IPAP ) {
2014-10-08 16:51:09 +00:00
//set_ipap = value.toFloat() / 10.0;
2014-07-28 13:56:29 +00:00
} else if ( key = = INT_PROP_EPAP ) {
set_epap = value . toFloat ( ) / 10.0 ;
} else if ( key = = INT_PROP_PS ) {
2014-10-08 16:51:09 +00:00
//set_ps = value.toFloat() / 10.0;
2014-07-28 13:56:29 +00:00
} else if ( key = = INT_PROP_RampPressure ) {
ramp_pressure = value . toFloat ( ) / 10.0 ;
} else if ( key = = INT_PROP_RampTime ) {
ramp_time = value . toFloat ( ) / 10.0 ;
} else if ( key = = INT_PROP_SmartFlex ) {
smartflex = value . toInt ( ) ;
} else if ( key = = INT_PROP_SmartFlexMode ) {
smartflexmode = value . toInt ( ) ;
} else {
set1 [ key ] = value ;
}
2011-11-21 10:20:11 +00:00
qDebug ( ) < < key < < " = " < < value ;
}
2011-11-21 02:15:10 +00:00
2014-07-28 13:56:29 +00:00
CPAPMode mode = MODE_UNKNOWN ;
switch ( papmode ) {
case 0 :
mode = MODE_CPAP ;
break ;
case 1 :
mode = ( set_epap > 0 ) ? MODE_BILEVEL_FIXED : MODE_APAP ;
break ;
default :
qDebug ( ) < < " New machine mode " ;
}
2014-04-17 05:58:57 +00:00
2014-07-28 13:56:29 +00:00
if ( ! info . serial . isEmpty ( ) ) {
2018-04-22 12:06:48 +00:00
mach = p_profile - > CreateMachine ( info ) ;
2011-11-21 10:20:11 +00:00
}
2014-04-17 05:58:57 +00:00
2011-11-21 10:20:11 +00:00
if ( ! mach ) {
qDebug ( ) < < " Couldn't get Intellipap machine record " ;
2014-07-29 14:38:59 +00:00
return - 1 ;
2011-11-21 10:20:11 +00:00
}
2011-11-21 02:15:10 +00:00
2014-07-28 13:56:29 +00:00
QString backupPath = mach - > getBackupPath ( ) ;
QString copypath = path ;
if ( QDir : : cleanPath ( path ) . compare ( QDir : : cleanPath ( backupPath ) ) ! = 0 ) {
copyPath ( path , backupPath ) ;
}
2011-11-21 10:20:11 +00:00
// Refresh properties data..
2014-04-17 05:58:57 +00:00
for ( QHash < QString , QString > : : iterator i = set1 . begin ( ) ; i ! = set1 . end ( ) ; i + + ) {
2021-10-26 18:59:41 +00:00
mach - > info . properties [ i . key ( ) ] = i . value ( ) ;
2011-11-21 10:20:11 +00:00
}
2014-04-17 05:58:57 +00:00
2011-11-21 10:20:11 +00:00
f . close ( ) ;
2014-07-28 13:56:29 +00:00
///////////////////////////////////////////////
// Parse the Session Index (U File)
///////////////////////////////////////////////
2011-11-21 10:20:11 +00:00
unsigned char buf [ 27 ] ;
2014-04-17 05:58:57 +00:00
filename = newpath + " /U " ;
2011-11-21 10:20:11 +00:00
f . setFileName ( filename ) ;
2014-04-17 05:58:57 +00:00
2014-07-29 14:38:59 +00:00
if ( ! f . exists ( ) ) { return - 1 ; }
2011-11-21 10:20:11 +00:00
QVector < quint32 > SessionStart ;
QVector < quint32 > SessionEnd ;
2014-04-17 05:58:57 +00:00
QHash < SessionID , Session * > Sessions ;
2011-11-21 10:20:11 +00:00
quint32 ts1 , ts2 ; //, length;
//unsigned char cs;
2011-11-21 02:15:10 +00:00
f . open ( QFile : : ReadOnly ) ;
2014-04-17 05:58:57 +00:00
int cnt = 0 ;
QDateTime epoch ( QDate ( 2002 , 1 , 1 ) , QTime ( 0 , 0 , 0 ) , Qt : : UTC ) ; // Intellipap Epoch
int ep = epoch . toTime_t ( ) ;
2011-11-21 02:15:10 +00:00
do {
2014-04-17 05:58:57 +00:00
cnt = f . read ( ( char * ) buf , 9 ) ;
2014-07-28 13:56:29 +00:00
// big endian
2014-04-17 05:58:57 +00:00
ts1 = ( buf [ 0 ] < < 24 ) | ( buf [ 1 ] < < 16 ) | ( buf [ 2 ] < < 8 ) | buf [ 3 ] ;
ts2 = ( buf [ 4 ] < < 24 ) | ( buf [ 5 ] < < 16 ) | ( buf [ 6 ] < < 8 ) | buf [ 7 ] ;
2014-07-28 13:56:29 +00:00
// buf[8] == ??? What is this byte? A Bit Field? A checksum?
2014-04-17 05:58:57 +00:00
ts1 + = ep ;
ts2 + = ep ;
2011-11-21 10:20:11 +00:00
SessionStart . append ( ts1 ) ;
SessionEnd . append ( ts2 ) ;
2014-04-17 05:58:57 +00:00
} while ( cnt > 0 ) ;
2011-11-21 10:20:11 +00:00
qDebug ( ) < < " U file logs " < < SessionStart . size ( ) < < " sessions. " ;
2011-11-21 02:15:10 +00:00
f . close ( ) ;
2014-07-28 13:56:29 +00:00
///////////////////////////////////////////////
// Parse the Session Data (L File)
///////////////////////////////////////////////
2014-04-17 05:58:57 +00:00
filename = newpath + " /L " ;
2011-11-21 02:15:10 +00:00
f . setFileName ( filename ) ;
2014-04-17 05:58:57 +00:00
2014-07-29 14:38:59 +00:00
if ( ! f . exists ( ) ) { return - 1 ; }
2011-11-21 02:15:10 +00:00
f . open ( QFile : : ReadOnly ) ;
2014-04-17 05:58:57 +00:00
long size = f . size ( ) ;
int recs = size / 26 ;
m_buffer = new unsigned char [ size ] ;
2011-11-21 02:15:10 +00:00
2014-04-17 05:58:57 +00:00
if ( size ! = f . read ( ( char * ) m_buffer , size ) ) {
qDebug ( ) < < " Couldn't read 'L' data " < < filename ;
2014-07-29 14:38:59 +00:00
return - 1 ;
2011-11-21 10:20:11 +00:00
}
Session * sess ;
SessionID sid ;
2014-07-28 13:56:29 +00:00
QHash < SessionID , qint64 > rampstart ;
2014-07-29 14:38:59 +00:00
QHash < SessionID , qint64 > rampend ;
2014-04-17 05:58:57 +00:00
for ( int i = 0 ; i < SessionStart . size ( ) ; i + + ) {
sid = SessionStart [ i ] ;
2011-11-21 10:20:11 +00:00
if ( mach - > SessionExists ( sid ) ) {
// knock out the already imported sessions..
2014-04-17 05:58:57 +00:00
SessionStart [ i ] = 0 ;
SessionEnd [ i ] = 0 ;
2011-11-21 10:20:11 +00:00
} else if ( ! Sessions . contains ( sid ) ) {
2014-04-17 05:58:57 +00:00
sess = Sessions [ sid ] = new Session ( mach , sid ) ;
2014-07-29 14:38:59 +00:00
sess - > really_set_first ( qint64 ( sid ) * 1000L ) ;
// sess->really_set_last(qint64(SessionEnd[i]) * 1000L);
2014-07-28 13:56:29 +00:00
rampstart [ sid ] = 0 ;
2014-07-29 14:38:59 +00:00
rampend [ sid ] = 0 ;
2011-11-21 10:20:11 +00:00
sess - > SetChanged ( true ) ;
2014-07-28 13:56:29 +00:00
if ( mode > = MODE_BILEVEL_FIXED ) {
sess - > AddEventList ( CPAP_IPAP , EVL_Event ) ;
sess - > AddEventList ( CPAP_EPAP , EVL_Event ) ;
sess - > AddEventList ( CPAP_PS , EVL_Event ) ;
} else {
sess - > AddEventList ( CPAP_Pressure , EVL_Event ) ;
}
2014-04-17 05:58:57 +00:00
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 ) ;
2014-07-29 17:29:54 +00:00
sess - > AddEventList ( CPAP_Obstructive , EVL_Event ) ;
2021-06-18 20:31:56 +00:00
sess - > AddEventList ( INTP_SnoreFlag , EVL_Event ) ;
2014-07-29 17:29:54 +00:00
sess - > AddEventList ( CPAP_Hypopnea , EVL_Event ) ;
sess - > AddEventList ( CPAP_NRI , EVL_Event ) ;
sess - > AddEventList ( CPAP_LeakFlag , EVL_Event ) ;
sess - > AddEventList ( CPAP_ExP , EVL_Event ) ;
2011-11-21 10:20:11 +00:00
} else {
// If there is a double up, null out the earlier session
// otherwise there will be a crash on shutdown.
2014-04-17 05:58:57 +00:00
for ( int z = 0 ; z < SessionStart . size ( ) ; z + + ) {
if ( SessionStart [ z ] = = ( quint32 ) sid ) {
SessionStart [ z ] = 0 ;
SessionEnd [ z ] = 0 ;
2011-11-21 10:20:11 +00:00
break ;
}
}
2014-04-17 05:58:57 +00:00
2021-06-18 20:31:56 +00:00
QDateTime d = QDateTime : : fromSecsSinceEpoch ( sid ) ;
2011-11-21 10:20:11 +00:00
qDebug ( ) < < sid < < " has double ups " < < d ;
/*Session *sess=Sessions[sid];
Sessions . erase ( Sessions . find ( sid ) ) ;
delete sess ;
SessionStart [ i ] = 0 ;
SessionEnd [ i ] = 0 ; */
}
}
2014-04-17 05:58:57 +00:00
long pos = 0 ;
2014-07-28 13:56:29 +00:00
int rampval = 0 ;
2014-07-29 14:38:59 +00:00
sid = 0 ;
2014-10-08 16:51:09 +00:00
//SessionID lastsid = 0;
2014-04-17 05:58:57 +00:00
2014-10-02 07:56:57 +00:00
//int last_minp=0, last_maxp=0, last_ps=0, last_pres = 0;
2014-08-03 13:00:13 +00:00
2014-04-17 05:58:57 +00:00
for ( int i = 0 ; i < recs ; i + + ) {
2011-11-21 10:20:11 +00:00
// convert timestamp to real epoch
2014-07-09 03:49:20 +00:00
ts1 = ( ( m_buffer [ pos ] < < 24 ) | ( m_buffer [ pos + 1 ] < < 16 ) | ( m_buffer [ pos + 2 ] < < 8 ) | m_buffer [ pos + 3 ] ) + ep ;
2014-04-17 05:58:57 +00:00
for ( int j = 0 ; j < SessionStart . size ( ) ; j + + ) {
sid = SessionStart [ j ] ;
if ( ! sid ) { continue ; }
2011-11-21 10:20:11 +00:00
2014-04-17 05:58:57 +00:00
if ( ( ts1 > = ( quint32 ) sid ) & & ( ts1 < = SessionEnd [ j ] ) ) {
Session * sess = Sessions [ sid ] ;
2014-07-29 14:38:59 +00:00
2014-04-17 05:58:57 +00:00
qint64 time = quint64 ( ts1 ) * 1000L ;
2014-07-29 15:00:27 +00:00
sess - > really_set_last ( time ) ;
2014-07-28 13:56:29 +00:00
sess - > settings [ CPAP_Mode ] = mode ;
int minp = m_buffer [ pos + 0x13 ] ;
int maxp = m_buffer [ pos + 0x14 ] ;
int ps = m_buffer [ pos + 0x15 ] ;
int pres = m_buffer [ pos + 0xd ] ;
if ( mode > = MODE_BILEVEL_FIXED ) {
rampval = maxp ;
} else {
rampval = minp ;
}
2014-08-03 13:00:13 +00:00
2014-07-28 13:56:29 +00:00
qint64 rs = rampstart [ sid ] ;
if ( pres < rampval ) {
if ( ! rs ) {
2014-08-03 13:00:13 +00:00
// ramp started
// int rv = pres-rampval;
// double ramp =
2014-07-28 13:56:29 +00:00
rampstart [ sid ] = time ;
}
2014-07-29 14:38:59 +00:00
rampend [ sid ] = time ;
2014-07-28 13:56:29 +00:00
} else {
if ( rs > 0 ) {
if ( ! sess - > eventlist . contains ( CPAP_Ramp ) ) {
sess - > AddEventList ( CPAP_Ramp , EVL_Event ) ;
}
int duration = ( time - rs ) / 1000L ;
sess - > eventlist [ CPAP_Ramp ] [ 0 ] - > AddEvent ( time , duration ) ;
2014-08-03 13:00:13 +00:00
rampstart . remove ( sid ) ;
rampend . remove ( sid ) ;
}
}
// Do this after ramp, because ramp calcs might need to insert interpolated pressure samples
if ( mode > = MODE_BILEVEL_FIXED ) {
sess - > settings [ CPAP_EPAP ] = float ( minp ) / 10.0 ;
sess - > settings [ CPAP_IPAP ] = float ( maxp ) / 10.0 ;
sess - > settings [ CPAP_PS ] = float ( ps ) / 10.0 ;
sess - > eventlist [ CPAP_IPAP ] [ 0 ] - > AddEvent ( time , float ( pres ) / 10.0 ) ;
sess - > eventlist [ CPAP_EPAP ] [ 0 ] - > AddEvent ( time , float ( pres - ps ) / 10.0 ) ;
// rampval = maxp;
} else {
sess - > eventlist [ CPAP_Pressure ] [ 0 ] - > AddEvent ( time , float ( pres ) / 10.0 ) ; // current pressure
// rampval = minp;
if ( mode = = MODE_APAP ) {
sess - > settings [ CPAP_PressureMin ] = float ( minp ) / 10.0 ;
sess - > settings [ CPAP_PressureMax ] = float ( maxp ) / 10.0 ;
} else if ( mode = = MODE_CPAP ) {
sess - > settings [ CPAP_Pressure ] = float ( maxp ) / 10.0 ;
2014-07-28 13:56:29 +00:00
}
}
2011-11-21 10:20:11 +00:00
2014-04-17 05:58:57 +00:00
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"
2011-11-21 10:20:11 +00:00
2014-04-17 05:58:57 +00:00
int rr = m_buffer [ pos + 0xa ] ;
sess - > eventlist [ CPAP_RespRate ] [ 0 ] - > AddEvent ( time , rr ) ; // Respiratory Rate
2014-07-28 13:56:29 +00:00
// sess->eventlist[INTELLIPAP_Unknown1][0]->AddEvent(time, m_buffer[pos + 0xf]); //
2014-04-17 05:58:57 +00:00
sess - > eventlist [ INTELLIPAP_Unknown1 ] [ 0 ] - > AddEvent ( time , m_buffer [ pos + 0xc ] ) ;
2011-11-21 10:20:11 +00:00
2014-04-17 05:58:57 +00:00
sess - > eventlist [ CPAP_Snore ] [ 0 ] - > AddEvent ( time , m_buffer [ pos + 0x4 ] ) ; //4/5??
2011-11-21 02:15:10 +00:00
2014-08-03 13:00:13 +00:00
if ( m_buffer [ pos + 0x4 ] > 0 ) {
2021-06-18 20:31:56 +00:00
sess - > eventlist [ INTP_SnoreFlag ] [ 0 ] - > AddEvent ( time , m_buffer [ pos + 0x5 ] ) ;
2014-08-03 13:00:13 +00:00
}
2011-11-24 12:47:25 +00:00
// 0x0f == Leak Event
// 0x04 == Snore?
2014-04-17 05:58:57 +00:00
if ( m_buffer [ pos + 0xf ] > 0 ) { // Leak Event
sess - > eventlist [ CPAP_LeakFlag ] [ 0 ] - > AddEvent ( time , m_buffer [ pos + 0xf ] ) ;
2011-11-24 12:47:25 +00:00
}
2014-04-17 05:58:57 +00:00
if ( m_buffer [ pos + 0x5 ] > 4 ) { // This matches Exhale Puff.. not sure why 4
2014-07-09 03:49:20 +00:00
//MW: Are the lower 2 bits something else?
2011-11-30 12:32:16 +00:00
2014-07-29 05:19:10 +00:00
sess - > eventlist [ CPAP_ExP ] [ 0 ] - > AddEvent ( time , m_buffer [ pos + 0x5 ] ) ;
2011-11-21 10:20:11 +00:00
}
2014-04-17 05:58:57 +00:00
if ( m_buffer [ pos + 0x10 ] > 0 ) {
2014-07-29 05:19:10 +00:00
sess - > eventlist [ CPAP_Obstructive ] [ 0 ] - > AddEvent ( time , m_buffer [ pos + 0x10 ] ) ;
2011-11-21 10:20:11 +00:00
}
2011-11-30 12:32:16 +00:00
2014-04-17 05:58:57 +00:00
if ( m_buffer [ pos + 0x11 ] > 0 ) {
2014-07-29 05:19:10 +00:00
sess - > eventlist [ CPAP_Hypopnea ] [ 0 ] - > AddEvent ( time , m_buffer [ pos + 0x11 ] ) ;
2011-11-21 10:20:11 +00:00
}
2014-04-17 05:58:57 +00:00
if ( m_buffer [ pos + 0x12 ] > 0 ) { // NRI // is this == to RERA?? CA??
2014-07-29 05:19:10 +00:00
sess - > eventlist [ CPAP_NRI ] [ 0 ] - > AddEvent ( time , m_buffer [ pos + 0x12 ] ) ;
2011-11-21 10:20:11 +00:00
}
2011-11-30 12:32:16 +00:00
2014-04-17 05:58:57 +00:00
quint16 tv = ( m_buffer [ pos + 0x8 ] < < 8 ) | m_buffer [ pos + 0x9 ] ; // correct
2011-11-30 12:32:16 +00:00
2014-04-17 05:58:57 +00:00
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 ) ;
2011-11-21 10:20:11 +00:00
break ;
2014-08-03 13:00:13 +00:00
} else {
2011-11-21 10:20:11 +00:00
}
2014-10-08 16:51:09 +00:00
//lastsid = sid;
2011-11-21 10:20:11 +00:00
}
2014-04-17 05:58:57 +00:00
pos + = 26 ;
2011-11-21 10:20:11 +00:00
}
2014-04-17 05:58:57 +00:00
2014-07-29 14:38:59 +00:00
// Close any open ramps and store the event.
QHash < SessionID , qint64 > : : iterator rit ;
QHash < SessionID , qint64 > : : iterator rit_end = rampstart . end ( ) ;
for ( rit = rampstart . begin ( ) ; rit ! = rit_end ; + + rit ) {
qint64 rs = rit . value ( ) ;
SessionID sid = rit . key ( ) ;
if ( rs > 0 ) {
qint64 re = rampend [ rit . key ( ) ] ;
Session * sess = Sessions [ sid ] ;
if ( ! sess - > eventlist . contains ( CPAP_Ramp ) ) {
sess - > AddEventList ( CPAP_Ramp , EVL_Event ) ;
}
int duration = ( re - rs ) / 1000L ;
sess - > eventlist [ CPAP_Ramp ] [ 0 ] - > AddEvent ( re , duration ) ;
rit . value ( ) = 0 ;
}
}
2014-04-17 05:58:57 +00:00
for ( int i = 0 ; i < SessionStart . size ( ) ; i + + ) {
SessionID sid = SessionStart [ i ] ;
2011-11-21 10:20:11 +00:00
if ( sid ) {
2014-04-17 05:58:57 +00:00
sess = Sessions [ sid ] ;
2014-07-30 17:14:28 +00:00
2014-07-29 15:00:27 +00:00
if ( ! sess ) continue ;
2014-07-09 03:49:20 +00:00
2014-07-30 17:14:28 +00:00
// quint64 first = qint64(sid) * 1000L;
2014-10-02 07:56:57 +00:00
//quint64 last = qint64(SessionEnd[i]) * 1000L;
2014-04-17 05:58:57 +00:00
2014-07-29 15:00:27 +00:00
if ( sess - > last ( ) > 0 ) {
2014-07-30 20:25:06 +00:00
// sess->really_set_last(last);
2014-07-30 17:14:28 +00:00
2014-08-03 13:00:13 +00:00
sess - > settings [ INTP_SmartFlexLevel ] = smartflex ;
2014-07-28 13:56:29 +00:00
2014-07-29 15:00:27 +00:00
if ( smartflexmode = = 0 ) {
2014-08-03 13:00:13 +00:00
sess - > settings [ INTP_SmartFlexMode ] = PM_FullTime ;
2014-07-29 15:00:27 +00:00
} else {
2014-08-03 13:00:13 +00:00
sess - > settings [ INTP_SmartFlexMode ] = PM_RampOnly ;
2014-07-29 15:00:27 +00:00
}
2014-07-28 13:56:29 +00:00
2014-07-29 15:00:27 +00:00
sess - > settings [ CPAP_RampPressure ] = ramp_pressure ;
sess - > settings [ CPAP_RampTime ] = ramp_time ;
2014-07-28 13:56:29 +00:00
2014-07-29 15:00:27 +00:00
sess - > UpdateSummaries ( ) ;
2014-09-17 06:12:38 +00:00
2014-07-30 20:33:24 +00:00
addSession ( sess ) ;
2014-07-29 15:00:27 +00:00
} else {
delete sess ;
}
2014-07-11 12:09:38 +00:00
2011-11-21 10:20:11 +00:00
}
}
2014-04-17 05:58:57 +00:00
2014-07-30 20:33:24 +00:00
finishAddingSessions ( ) ;
2011-11-21 10:20:11 +00:00
mach - > Save ( ) ;
2014-07-30 20:33:24 +00:00
2011-11-21 10:20:11 +00:00
delete [ ] m_buffer ;
f . close ( ) ;
2011-11-20 23:39:55 +00:00
2014-07-29 14:38:59 +00:00
int c = Sessions . size ( ) ;
return c ;
2011-11-20 02:59:00 +00:00
}
2021-01-26 02:56:59 +00:00
////////////////////////////////////////////////////////////////////////////
// Devilbiss DV64 Notes
// 1) High resolution data (flow and pressure) is kept on SD for only 100 hours
// 1a) Flow graph for days without high resolution data is absent
// 1b) Pressure graph is high resolution when high res data is available and
// only 1 per minute when using low resolution data.
// 2) Max and Average leak rates are as reported by DV64 machine but we're
// not sure how those measures relate to other machine's data. Leak rate
// seems to include the intentional mask leak.
// 2a) Not sure how SmartLink calculates the pct of time of poor mask fit.
// May be same as what we call large leak time for other machines?
////////////////////////////////////////////////////////////////////////////
struct DV6TestedModel
{
QString model ;
QString name ;
} ;
static const DV6TestedModel testedModels [ ] = {
{ " DV64D " , " Blue StandardPlus " } ,
{ " DV64E " , " Blue AutoPlus " } ,
{ " DV63E " , " Blue (IntelliPAP 2) AutoPlus " } ,
{ " " , " unknown product " } // List stopper -- must be last entry
} ;
struct DV6_S_Data // Daily summary
2018-03-25 19:23:05 +00:00
{
2021-01-26 02:56:59 +00:00
/***
2018-03-25 19:23:05 +00:00
Session * sess ;
unsigned char u1 ; //00 (position)
2021-01-26 02:56:59 +00:00
* * */
2021-04-02 04:39:06 +00:00
unsigned int start_time ; //01 Start time for date
unsigned int stop_time ; //05 End time
unsigned int written ; //09 timestamp when this record was written
2018-03-25 19:23:05 +00:00
EventDataType hours ; //13
2021-01-26 02:56:59 +00:00
// EventDataType unknown14; //14
2018-03-25 19:23:05 +00:00
EventDataType pressureAvg ; //15
EventDataType pressureMax ; //16
EventDataType pressure50 ; //17 50th percentile
EventDataType pressure90 ; //18 90th percentile
EventDataType pressure95 ; //19 95th percentile
EventDataType pressureStdDev ; //20 std deviation
2021-01-26 02:56:59 +00:00
// EventDataType unknown_21; //21
2018-03-25 19:23:05 +00:00
EventDataType leakAvg ; //22
EventDataType leakMax ; //23
EventDataType leak50 ; //24 50th percentile
EventDataType leak90 ; //25 90th percentile
EventDataType leak95 ; //26 95th percentile
EventDataType leakStdDev ; //27 std deviation
EventDataType tidalVolume ; //28 & 0x29
EventDataType avgBreathRate ; //30
2021-01-26 02:56:59 +00:00
EventDataType unknown_31 ; //31
EventDataType snores ; //32 snores / hypopnea per minute
2018-03-25 19:23:05 +00:00
EventDataType timeInExPuf ; //33 Time in Expiratory Puff
EventDataType timeInFL ; //34 Time in Flow Limitation
EventDataType timeInPB ; //35 Time in Periodic Breathing
EventDataType maskFit ; //36 mask fit (or rather, not fit) percentage
EventDataType indexOA ; //37 Obstructive
EventDataType indexCA ; //38 Central index
EventDataType indexHyp ; //39 Hypopnea Index
2021-01-26 02:56:59 +00:00
EventDataType unknown_40 ; //40 Reserved?
EventDataType unknown_41 ; //40 Reserved?
2018-03-25 19:23:05 +00:00
//42-48 unknown
EventDataType pressureSetMin ; //49
EventDataType pressureSetMax ; //50
} ;
2018-06-09 15:11:42 +00:00
# ifdef _MSC_VER
# define PACK( __Declaration__ ) __pragma( pack(push, 1) ) __Declaration__ __pragma( pack(pop) )
# else
# define PACK( __Declaration__ ) __Declaration__ __attribute__((__packed__))
# endif
2021-01-26 02:56:59 +00:00
// DV6_S_REC is the day structure in the S.BIN file
PACK ( struct DV6_S_REC {
unsigned char begin [ 4 ] ; //0 Beginning of day
unsigned char end [ 4 ] ; //4 End of day
unsigned char written [ 4 ] ; //8 When this record was written??
unsigned char hours ; //12 Hours in session * 10
unsigned char unknown_13 ; //13
unsigned char pressureAvg ; //14 All pressure settings are * 10
unsigned char pressureMax ; //15
unsigned char pressure50 ; //16 50th percentile
unsigned char pressure90 ; //17 90th percentile
unsigned char pressure95 ; //18 95th percentile
unsigned char pressureStdDev ; //19 std deviation
unsigned char unknown_20 ; //20
unsigned char leakAvg ; //21
unsigned char leakMax ; //22
unsigned char leak50 ; //23 50th percentile
unsigned char leak90 ; //24 90th percentile
unsigned char leak95 ; //25 95th percentile
unsigned char leakStdDev ; //26 std deviation
unsigned char tv1 ; //27 tidal volume = tv2 * 256 + tv1
unsigned char tv2 ; //28
unsigned char avgBreathRate ; //29
unsigned char unknown_30 ; //30
unsigned char snores ; //31 snores / hypopnea per minute
unsigned char timeInExPuf ; //32 % Time in Expiratory Puff * 2
unsigned char timeInFL ; //33 % Time in Flow Limitation * 2
unsigned char timeInPB ; //34 % Time in Periodic Breathing * 2
unsigned char maskFit ; //35 mask fit (or rather, not fit) percentage * 2
unsigned char indexOA ; //36 Obstructive index * 4
unsigned char indexCA ; //37 Central index * 4
unsigned char indexHyp ; //38 Hypopnea Index * 4
unsigned char unknown_39 ; //39 Reserved?
unsigned char unknown_40 ; //40 Reserved?
unsigned char unknown_41 ; //41
unsigned char unknown_42 ; //42
unsigned char unknown_43 ; //43
unsigned char unknown_44 ; //44 % time snoring *4
unsigned char unknown_45 ; //45
unsigned char unknown_46 ; //46
unsigned char unknown_47 ; //47 (related to smartflex and flow rounding?)
unsigned char pressureSetMin ; //48
unsigned char pressureSetMax ; //49
unsigned char unknown_50 ; //50
unsigned char unknown_51 ; //51
unsigned char unknown_52 ; //52
unsigned char unknown_53 ; //53
unsigned char checksum ; //54
} ) ;
2021-06-18 20:31:56 +00:00
// DV6 SET.BIN - structure of the entire settings file
2021-01-26 02:56:59 +00:00
PACK ( struct SET_BIN_REC {
2018-06-09 15:11:42 +00:00
char unknown_00 ; // assuming file version
char serial [ 11 ] ; // null terminated
2021-01-26 02:56:59 +00:00
unsigned char language ;
unsigned char capabilities ; // CPAP or APAP
unsigned char unknown_11 ;
unsigned char cpap_pressure ;
unsigned char unknown_12 ;
unsigned char max_pressure ;
unsigned char unknown_13 ;
unsigned char min_pressure ;
2018-06-09 15:11:42 +00:00
unsigned char alg_apnea_threshhold ; // always locked at 00
unsigned char alg_apnea_duration ;
unsigned char alg_hypop_threshold ;
unsigned char alg_hypop_duration ;
2021-01-26 02:56:59 +00:00
unsigned char ramp_pressure ;
unsigned char unknown_01 ;
unsigned char ramp_duration ;
unsigned char unknown_02 [ 3 ] ;
unsigned char smartflex_setting ;
unsigned char smartflex_when ;
2018-06-09 15:11:42 +00:00
unsigned char inspFlowRounding ;
unsigned char expFlowRounding ;
unsigned char complianceHours ;
2021-01-26 02:56:59 +00:00
unsigned char unknown_03 ;
unsigned char tubing_diameter ;
unsigned char autostart_setting ;
unsigned char unknown_04 ;
unsigned char show_hide ;
unsigned char unknown_05 ;
unsigned char lock_flags ;
unsigned char unknown_06 ;
2018-06-09 15:11:42 +00:00
unsigned char humidifier_setting ; // 0-5
2021-01-26 02:56:59 +00:00
unsigned char unknown_7 ;
unsigned char possible_alg_apnea ;
unsigned char unknown_8 [ 7 ] ;
unsigned char bacteria_filter ;
unsigned char unused [ 73 ] ;
2018-06-09 15:11:42 +00:00
unsigned char checksum ;
} ) ; // http://digitalvampire.org/blog/index.php/2006/07/31/why-you-shouldnt-use-__attribute__packed/
2021-01-26 02:56:59 +00:00
// Unless explicitly noted, all other DV6_x_REC are definitions for the repeating data structure that follows the header
PACK ( struct DV6_HEADER {
unsigned char unknown ; // 0 always zero
2021-04-02 04:39:06 +00:00
unsigned char filetype ; // 1 e.g. "R" for a R.BIN file
2021-01-26 02:56:59 +00:00
unsigned char serial [ 11 ] ; // 2 serial number
2021-04-02 04:39:06 +00:00
unsigned char numRecords [ 4 ] ; // 13 Number of records in file (always fixed, 180,000 for R.BIN)
2021-01-26 02:56:59 +00:00
unsigned char recordLength ; // 17 Length of data record (always 117)
unsigned char recordStart [ 4 ] ; // 18 First record in wrap-around buffer
unsigned char unknown_22 [ 21 ] ; // 22 Unknown values
2021-04-02 04:39:06 +00:00
unsigned char unknown_43 [ 8 ] ; // 43 Seems always to be zero
unsigned char lasttime [ 4 ] ; // 51 OSCAR only: Last timestamp, in history files only
2021-01-26 02:56:59 +00:00
unsigned char checksum ; // 55 Checksum
} ) ;
// DV6 E.BIN - event data
struct DV6_E_REC { // event log record
unsigned char begin [ 4 ] ;
unsigned char end [ 4 ] ;
unsigned char unknown_01 ;
unsigned char unknown_02 ;
unsigned char unknown_03 ;
unsigned char unknown_04 ;
unsigned char event_type ;
unsigned char event_severity ;
unsigned char value ;
unsigned char reduction ;
unsigned char duration ;
unsigned char unknown [ 7 ] ;
unsigned char checksum ;
} ;
2018-03-25 19:23:05 +00:00
2021-01-26 02:56:59 +00:00
// DV6 U.BIN - session start and stop times
struct DV6_U_REC {
unsigned char begin [ 4 ] ;
unsigned char end [ 4 ] ;
unsigned char checksum ; // possible checksum? Not really sure
} ;
// DV6 R.BIN - High resolution data (breath) and moderate resolution (pressure, flags)
2021-01-26 19:37:27 +00:00
struct DV6_R_REC {
2021-01-26 02:56:59 +00:00
unsigned char timestamp [ 4 ] ;
qint16 breath [ 50 ] ; // 50 breath flow records at 25 Hz
unsigned char pressure1 ; // pressure in first second of frame
unsigned char pressure2 ; // pressure in second second of frame
unsigned char unknown106 ;
unsigned char unknown107 ;
unsigned char flags1 [ 4 ] ; // flags for first second of frame
unsigned char flags2 [ 4 ] ; // flags for second second of frame
unsigned char checksum ;
2021-01-26 19:37:27 +00:00
} ;
2021-01-26 02:56:59 +00:00
// DV6 L.BIN - Low resolution data
PACK ( struct DV6_L_REC {
unsigned char timestamp [ 4 ] ; // 0 timestamp
unsigned char maxLeak ; // 4 lpm
unsigned char avgLeak ; // 5 lpm
unsigned char tidalVolume6 ; // 6
unsigned char tidalVolume7 ; // 7
unsigned char breathRate ; // 8 breaths per minute
unsigned char unknown9 ; // 9
unsigned char avgPressure ; // 10 pressure * 10
unsigned char unknown11 ; // 11 always zero?
unsigned char unknown12 ; // 12
unsigned char pressureLimitLow ; // 13 pressure * 10
unsigned char pressureLimitHigh ; // 14 pressure * 10
unsigned char timeSnoring ; // 15
unsigned char snoringSeverity ; // 16
unsigned char timeEP ; // 17
unsigned char epSeverity ; // 18
unsigned char timeX1 ; // 19 ??
unsigned char x1Severity ; // 20 ??
unsigned char timeX2 ; // 21 ??
unsigned char x2Severity ; // 22 ??
unsigned char timeX3 ; // 23 ??
unsigned char x3Severity ; // 24 ??
unsigned char apSeverity ; // 25
unsigned char TimeApnea ; // 26
unsigned char noaSeverity ; // 27
unsigned char timeNOA ; // 28
unsigned char ukSeverity ; // 29 ??
unsigned char timeUk ; // 30 ??
unsigned char unknown31 ; // 31
unsigned char unknown32 ; // 32
unsigned char unknown33 ; // 33
unsigned char unknownFlag34 ; // 34
unsigned char unknownTime35 ; // 35
unsigned char unknownFlag36 ; // 36
unsigned char unknown37 ; // 37
unsigned char unknown38 ; // 38
unsigned char unknown39 ; // 39
unsigned char unknown40 ; // 40
unsigned char unknown41 ; // 41
unsigned char unknown42 ; // 42
unsigned char unknown43 ; // 43
unsigned char checksum ; // 44
} ) ;
// Our structure for managing sessions
struct DV6_SessionInfo {
Session * sess ;
DV6_S_Data * dailyData ;
SET_BIN_REC * dv6Settings ;
unsigned int begin ;
unsigned int end ;
unsigned int written ;
2021-06-18 20:31:56 +00:00
// bool haveHighResData;
unsigned int firstHighRes ;
unsigned int lastHighRes ;
2021-01-26 02:56:59 +00:00
CPAPMode mode = MODE_UNKNOWN ;
} ;
2021-04-02 04:39:06 +00:00
QString card_path ;
QString backup_path ;
QString history_path ;
2021-06-18 20:31:56 +00:00
QString rebuild_path ;
2021-04-02 04:39:06 +00:00
MachineInfo info ;
Machine * mach = nullptr ;
bool rebuild_from_backups = false ;
bool create_backups = false ;
2021-06-18 20:31:56 +00:00
QStringList inputFilePaths ;
2021-04-02 04:39:06 +00:00
QMap < SessionID , DV6_S_Data > DailySummaries ;
QMap < SessionID , DV6_SessionInfo > SessionData ;
SET_BIN_REC * settings ;
2021-01-26 02:56:59 +00:00
unsigned int ep = 0 ;
// Convert a 4-character number in DV6 data file to a standard int
unsigned int convertNum ( unsigned char num [ ] ) {
return ( ( num [ 3 ] < < 24 ) + ( num [ 2 ] < < 16 ) + ( num [ 1 ] < < 8 ) + num [ 0 ] ) ;
}
// Convert a timestamp in DV6 data file to a standard Unix epoch timestamp as used in OSCAR
unsigned int convertTime ( unsigned char time [ ] ) {
if ( ep = = 0 ) {
QDateTime epoch ( QDate ( 2002 , 1 , 1 ) , QTime ( 0 , 0 , 0 ) , Qt : : UTC ) ; // Intellipap Epoch
ep = epoch . toTime_t ( ) ;
}
return ( ( time [ 3 ] < < 24 ) + ( time [ 2 ] < < 16 ) + ( time [ 1 ] < < 8 ) + time [ 0 ] ) + ep ; // Time as Unix epoch time
}
2021-04-02 04:39:06 +00:00
class RollingBackup
{
public :
RollingBackup ( ) { }
~ RollingBackup ( ) {
}
2021-06-18 20:31:56 +00:00
bool open ( const QString filetype , DV6_HEADER * newhdr , QByteArray * startTime ) ; // Open the file
2021-04-02 04:39:06 +00:00
bool close ( ) ; // close the file
2021-06-18 20:31:56 +00:00
bool save ( const QByteArray & dataBA ) ; // save the next record in the file
2021-04-02 04:39:06 +00:00
private :
2021-06-18 20:31:56 +00:00
DV6_HEADER hdr ; // file header
2021-04-02 04:39:06 +00:00
QString filetype ;
2021-06-18 20:31:56 +00:00
QFile histfile ;
2021-04-02 04:39:06 +00:00
2021-06-18 20:31:56 +00:00
const qint64 maxHistFileSize = 10000000 ; // Maximum size of file before we create a new file, in MB (40 MB)
// (While 40e6 would be easier to understand, 40e6 is a double, not an int)
2021-04-02 04:39:06 +00:00
2021-06-18 20:31:56 +00:00
unsigned int lastTimeInFile ; // Timestamp of last data record in history file
int numWritten ; // Number of records written
2021-04-02 04:39:06 +00:00
} ;
2021-06-18 20:31:56 +00:00
QStringList getHistoryFileNames ( const QString filetype , bool reversed = false ) {
QStringList filters ;
QDir hpath ( history_path ) ;
filters . append ( filetype ) ; // Assume one-letter file name like "S.BIN"
filters [ 0 ] . insert ( 1 , " _* " ) ; // Add a wild card like "S_*.BIN"
hpath . setNameFilters ( filters ) ;
hpath . setFilter ( QDir : : Files ) ;
hpath . setSorting ( QDir : : Name ) ;
if ( reversed ) hpath . setSorting ( QDir : : Name | QDir : : Reversed ) ;
return hpath . entryList ( ) ; // Get list of files
}
QString getNewFileName ( QString filetype , QByteArray * startTime , int offset = 0 ) {
unsigned char startTimeChar [ 5 ] ;
for ( int i = 0 ; i < 4 ; i + + )
startTimeChar [ i ] = startTime - > at ( offset + i ) ;
unsigned int ts = convertTime ( startTimeChar ) ;
QString newfile = filetype . left ( 1 ) + " _ " + QDateTime : : fromSecsSinceEpoch ( ts ) . toString ( " yyyyMMdd " ) + " .BIN " ;
qDebug ( ) < < " DV6 getNewFileName returns " < < newfile ;
return newfile ;
}
bool RollingBackup : : open ( const QString filetype , DV6_HEADER * inputhdr , QByteArray * startTimeOfBackup ) {
2021-04-02 04:39:06 +00:00
if ( ! create_backups )
return true ;
QDir hpath ( history_path ) ;
2021-06-18 20:31:56 +00:00
QString historypath = hpath . absolutePath ( ) + " / " ;
int histfilesize = 0 ;
2021-04-02 04:39:06 +00:00
2021-06-18 20:31:56 +00:00
this - > filetype = filetype ;
2021-04-02 04:39:06 +00:00
2021-06-18 20:31:56 +00:00
bool needNewFile = false ;
memcpy ( & hdr , inputhdr , sizeof ( DV6_HEADER ) ) ;
numWritten = 0 ;
2021-04-02 04:39:06 +00:00
2021-06-18 20:31:56 +00:00
QStringList fileNames = getHistoryFileNames ( filetype , true ) ;
2021-04-02 04:39:06 +00:00
// Handle first time a history file is being created
if ( fileNames . isEmpty ( ) ) {
for ( int i = 0 ; i < 4 ; i + + ) {
hdr . recordStart [ i ] = 0 ;
hdr . lasttime [ i ] = 0 ;
}
2021-06-18 20:31:56 +00:00
lastTimeInFile = 0 ;
histfile . setFileName ( historypath + getNewFileName ( filetype , startTimeOfBackup ) ) ;
needNewFile = true ;
2021-04-02 04:39:06 +00:00
}
2021-06-18 20:31:56 +00:00
2021-04-02 04:39:06 +00:00
// We have an existing history record
if ( ! fileNames . isEmpty ( ) ) {
2021-06-18 20:31:56 +00:00
histfile . setFileName ( historypath + fileNames . first ( ) ) ; // File names are in reverse order, so latest is first
// Open and read history file header and save the header
if ( ! histfile . open ( QIODevice : : ReadWrite ) ) {
qWarning ( ) < < " DV6 rb(open) could not open " < < fileNames . first ( ) < < " for readwrite, error code " < < histfile . error ( ) < < histfile . errorString ( ) ;
return false ;
}
histfilesize = histfile . size ( ) ;
QByteArray dataBA = histfile . read ( sizeof ( DV6_HEADER ) ) ;
memcpy ( & hdr , dataBA . data ( ) , sizeof ( DV6_HEADER ) ) ;
lastTimeInFile = convertTime ( hdr . lasttime ) ;
2021-04-02 04:39:06 +00:00
// See if this file is large enough that we want to create a new file
2021-06-18 20:31:56 +00:00
// If it is large, we'll start a new file.
2021-04-02 04:39:06 +00:00
if ( histfile . size ( ) > maxHistFileSize ) {
2021-06-18 20:31:56 +00:00
QString nextFile = historypath + getNewFileName ( filetype , & dataBA , 51 ) ;
QString hh = histfile . fileName ( ) ;
2021-04-02 04:39:06 +00:00
2021-06-18 20:31:56 +00:00
if ( hh ! = nextFile ) {
lastTimeInFile = convertTime ( hdr . lasttime ) ;
2021-04-13 23:48:39 +00:00
histfile . close ( ) ;
2021-06-18 20:31:56 +00:00
// Update header saying we are starting at record 0
for ( int i = 0 ; i < 4 ; i + + )
hdr . recordStart [ i ] = 0 ;
histfile . setFileName ( nextFile ) ;
needNewFile = true ;
2021-04-02 04:39:06 +00:00
}
2021-06-18 20:31:56 +00:00
}
}
2021-04-02 04:39:06 +00:00
2021-06-18 20:31:56 +00:00
if ( needNewFile ) {
if ( ! histfile . open ( QIODevice : : ReadWrite ) ) {
qWarning ( ) < < " DV6 rb(open) could not create new file " < < histfile . fileName ( ) < < " for readwrite, error code " < < histfile . error ( ) < < histfile . errorString ( ) ;
return false ;
2021-04-02 04:39:06 +00:00
}
2021-06-18 20:31:56 +00:00
if ( histfile . write ( ( char * ) & hdr . unknown , sizeof ( DV6_HEADER ) ) ! = sizeof ( DV6_HEADER ) ) {
qWarning ( ) < < " DV6 rb(open) could not write header to new file " < < histfile . fileName ( ) < < " for readwrite, error code " < < histfile . error ( ) < < histfile . errorString ( ) ;
histfile . close ( ) ;
return false ;
}
} else {
// qDebug() << "DV6 rb(open) history file size" << histfilesize;
histfile . seek ( histfilesize ) ;
2021-04-02 04:39:06 +00:00
}
return true ;
}
bool RollingBackup : : close ( ) {
if ( ! create_backups )
return true ;
2021-06-18 20:31:56 +00:00
qint32 size = histfile . size ( ) ;
if ( ! histfile . seek ( 0 ) ) {
qWarning ( ) < < " DV6 rb(close) unable to seek to file beginning " < < histfile . error ( ) < < histfile . errorString ( ) ;
histfile . close ( ) ;
return false ;
}
quint32 sizehdr = sizeof ( DV6_HEADER ) ;
quint32 reclen = hdr . recordLength ;
quint32 wrap_point = ( size - sizehdr ) / reclen ;
hdr . recordStart [ 0 ] = wrap_point & 0xff ;
hdr . recordStart [ 1 ] = ( wrap_point > > 8 ) & 0xff ;
hdr . recordStart [ 2 ] = ( wrap_point > > 16 ) & 0xff ;
hdr . recordStart [ 3 ] = ( wrap_point > > 24 ) & 0xff ;
if ( histfile . write ( ( char * ) & hdr , sizeof ( DV6_HEADER ) ) ! = sizeof ( DV6_HEADER ) ) {
qWarning ( ) < < " DV6 rb(close) could not write header to file " < < histfile . fileName ( ) < < " error code " < < histfile . error ( ) < < histfile . errorString ( ) ;
histfile . close ( ) ;
return false ;
}
histfile . close ( ) ;
qDebug ( ) < < " DV6 rb(close) wrote " < < numWritten < < " records. " ;
2021-04-02 04:39:06 +00:00
return true ;
}
2021-06-18 20:31:56 +00:00
bool RollingBackup : : save ( const QByteArray & dataBA ) {
2021-04-02 04:39:06 +00:00
if ( ! create_backups )
return true ;
2021-06-18 20:31:56 +00:00
unsigned char * data = ( unsigned char * ) dataBA . data ( ) ;
unsigned int thisTimeStamp = convertTime ( data ) ;
if ( thisTimeStamp > lastTimeInFile ) { // Is this data new to us?
// If so, save it to the history file.
if ( histfile . write ( dataBA ) = = - 1 ) {
qWarning ( ) < < " DV6 rb(save) could not save record " < < histfile . fileName ( ) < < " for readwrite, error code " < < histfile . error ( ) < < histfile . errorString ( ) ;
histfile . close ( ) ;
return false ;
}
memcpy ( & hdr . lasttime , data , 4 ) ;
// if (!histfile.seek(histfile.pos() + dataBA.length()))
// qWarning() << "DV6 rb(save) failed respositioning" << histfile.fileName() << "for readwrite, error code" << histfile.error() << histfile.errorString();
numWritten + + ;
}
/***
else {
qDebug ( ) < < " DV6 rb(save) skipping record " < < numWritten < < QDateTime : : fromSecsSinceEpoch ( thisTimeStamp ) . toString ( " MM/dd/yyyy hh:mm:ss " )
< < " last in file " < < QDateTime : : fromSecsSinceEpoch ( lastTimeInFile ) . toString ( " MM/dd/yyyy hh:mm:ss " ) ;
}
* * */
2021-04-02 04:39:06 +00:00
return true ;
}
class RollingFile
{
public :
RollingFile ( ) { }
~ RollingFile ( ) {
if ( data )
delete [ ] data ;
data = nullptr ;
if ( hdr )
delete hdr ;
hdr = nullptr ;
}
2021-06-18 20:31:56 +00:00
bool open ( QString fn , bool getNext = false ) ; // Open the file
2021-04-02 04:39:06 +00:00
bool close ( ) ; // close the file
unsigned char * get ( ) ; // read the next record in the file
int numread ( ) { return number_read ; } ; // Return number of records read
int recnum ( ) { return record_number ; } ; // Return last-read record number
RollingBackup rb ;
private :
QString filename ;
QFile file ;
int record_length ;
int wrap_record ;
bool wrapping = false ;
int number_read = 0 ; // Number of records read
int record_number = 0 ; // Number of record. First record in the file is #1. First record read is wrap_record;
DV6_HEADER * hdr ; // file header
unsigned char * data = nullptr ; // record pointer
} ;
2021-06-18 20:31:56 +00:00
bool RollingFile : : open ( QString filetype , bool getNext ) {
2021-01-26 02:56:59 +00:00
2021-04-02 04:39:06 +00:00
filename = filetype ;
2021-06-18 20:31:56 +00:00
if ( rebuild_from_backups ) {
// Building from backup
if ( ! getNext ) { // Initialize on first call
inputFilePaths . clear ( ) ;
QStringList histFileNames = getHistoryFileNames ( filetype ) ;
qDebug ( ) < < " DV6 rf(open) History file names " < < histFileNames ;
for ( int i = 0 ; i < histFileNames . size ( ) ; i + + ) {
file . setFileName ( history_path + " / " + histFileNames . at ( i ) ) ;
inputFilePaths . append ( file . fileName ( ) ) ;
}
}
if ( inputFilePaths . empty ( ) )
return false ;
file . setFileName ( inputFilePaths . at ( 0 ) ) ;
inputFilePaths . removeAt ( 0 ) ;
} else {
file . setFileName ( card_path + " / " + filetype ) ;
inputFilePaths . clear ( ) ;
}
2021-01-26 02:56:59 +00:00
if ( ! file . open ( QIODevice : : ReadOnly ) ) {
2021-06-18 20:31:56 +00:00
qWarning ( ) < < " DV6 rf(open) could not open " < < filename < < " for reading, error code " < < file . error ( ) < < file . errorString ( ) ;
2021-01-26 02:56:59 +00:00
return false ;
}
2021-04-02 04:39:06 +00:00
// Save header for use in making backups of data
hdr = new DV6_HEADER ;
2021-01-26 02:56:59 +00:00
QByteArray dataBA = file . read ( sizeof ( DV6_HEADER ) ) ;
2021-04-02 04:39:06 +00:00
memcpy ( hdr , dataBA . data ( ) , sizeof ( DV6_HEADER ) ) ;
// Extract control information from header
2021-01-26 02:56:59 +00:00
record_length = hdr - > recordLength ;
wrap_record = convertNum ( hdr - > recordStart ) ;
record_number = wrap_record ;
number_read = 0 ;
wrapping = false ;
2021-04-02 04:39:06 +00:00
// Create buffer to hold each record as it is read
2021-01-26 02:56:59 +00:00
data = new unsigned char [ record_length ] ;
2021-06-18 20:31:56 +00:00
// Seek to oldest data record in file, which is always at the wrap point
// wrap_record is the C offset where the next data record is to be written.
// Since C offsets begin with zero, it is also the number of records in the file.
int seekpos = sizeof ( DV6_HEADER ) + wrap_record * record_length ;
if ( ! file . seek ( seekpos ) ) {
qWarning ( ) < < " DV6 rf(open) unable to make initial seek to record " < < wrap_record < < " in " + filename < < file . error ( ) < < file . errorString ( ) ;
2021-01-26 02:56:59 +00:00
file . close ( ) ;
return false ;
}
2021-06-18 20:31:56 +00:00
qDebug ( ) < < " DV6 rf(open) " < < filetype < < " positioning to oldest record at pos " < < seekpos < < " after seek " < < file . pos ( ) ;
if ( file . atEnd ( ) ) {
file . seek ( sizeof ( DV6_HEADER ) ) ;
}
dataBA = file . read ( 4 ) ; // Read timestamp of newest data record
file . seek ( seekpos ) ; // Reset read position before what we just read so we start reading data records here
if ( ! rb . open ( filetype , hdr , & dataBA ) ) {
qWarning ( ) < < " DV6 rf(open) failed " ;
2021-04-02 04:39:06 +00:00
file . close ( ) ;
return false ;
}
2021-01-26 02:56:59 +00:00
2021-06-18 20:31:56 +00:00
qDebug ( ) < < " DV6 rf(open) " < < filename < < " at wrap record " < < wrap_record < < " now at pos " < < file . pos ( ) ;
2021-01-26 02:56:59 +00:00
return true ;
}
bool RollingFile : : close ( ) {
2021-06-18 20:31:56 +00:00
/*** Works for backing up but prevents chart appearing for the last day
// Flush any additional input that has not been backed up
if ( create_backups ) {
do {
DV6_U_REC * rec = ( DV6_U_REC * ) get ( ) ;
if ( rec = = nullptr )
break ;
} while ( true ) ;
}
* * */
2021-01-26 02:56:59 +00:00
file . close ( ) ;
2021-04-02 04:39:06 +00:00
rb . close ( ) ;
if ( data )
2021-01-26 02:56:59 +00:00
delete [ ] data ;
data = nullptr ;
2021-04-02 04:39:06 +00:00
if ( hdr )
delete hdr ;
hdr = nullptr ;
2021-01-26 02:56:59 +00:00
return true ;
}
2018-03-25 19:23:05 +00:00
2021-01-26 02:56:59 +00:00
unsigned char * RollingFile : : get ( ) {
2021-06-18 20:31:56 +00:00
// int readpos;
2021-01-26 02:56:59 +00:00
record_number + + ;
// If we have found the wrap record again, we are done
2021-06-18 20:31:56 +00:00
if ( wrapping & & record_number = = wrap_record ) {
// Unless we are rebuilding from backup and may have more files
if ( rebuild_from_backups & & ! inputFilePaths . empty ( ) ) {
qDebug ( ) < < " DV6 rf(get) closing " < < file . fileName ( ) ;
file . close ( ) ;
open ( inputFilePaths . at ( 0 ) , true ) ;
} else {
return nullptr ;
}
}
2021-01-26 02:56:59 +00:00
2021-06-18 20:31:56 +00:00
// Have we reached end of file and need to wrap around to beginning?
2021-01-26 02:56:59 +00:00
if ( file . atEnd ( ) ) {
if ( wrapping ) {
2021-06-18 20:31:56 +00:00
qDebug ( ) < < " DV6 rf(get) wrap - second time through " ;
2021-01-26 02:56:59 +00:00
return nullptr ;
}
2021-06-18 20:31:56 +00:00
qDebug ( ) < < " DV6 rf(get) wrapping to beginning of data in " < < filename < < " record number is " < < record_number - 1 < < " records read " < < number_read ;
record_number = 1 ;
2021-01-26 02:56:59 +00:00
wrapping = true ;
if ( ! file . seek ( sizeof ( DV6_HEADER ) ) ) {
file . close ( ) ;
2021-06-18 20:31:56 +00:00
qWarning ( ) < < " DV6 rf(get) unable to seek to first data record in file " ;
2021-01-26 02:56:59 +00:00
return nullptr ;
}
2021-06-18 20:31:56 +00:00
qDebug ( ) < < " DV6 rf(get) # " < < record_number < < " now at pos " < < file . pos ( ) ;
2021-01-26 02:56:59 +00:00
}
QByteArray dataBA ;
2021-06-18 20:31:56 +00:00
// readpos = file.pos();
2021-01-26 02:56:59 +00:00
dataBA = file . read ( record_length ) ; // read next record
if ( dataBA . size ( ) ! = record_length ) {
2021-06-18 20:31:56 +00:00
qWarning ( ) < < " DV6 rf(get) # " < < record_number < < " wrong length " ;
2021-01-26 02:56:59 +00:00
file . close ( ) ;
return nullptr ;
}
2021-06-18 20:31:56 +00:00
2021-04-02 04:39:06 +00:00
if ( ! rb . save ( dataBA ) ) {
2021-06-18 20:31:56 +00:00
qWarning ( ) < < " DV6 rf(get) failed " ;
2021-04-02 04:39:06 +00:00
}
2021-01-26 02:56:59 +00:00
number_read + + ;
2021-06-18 20:31:56 +00:00
// qDebug() << "DV6 rf(get)" << filename << "at start pos" << readpos << "end pos" << file.pos() << "record number" << record_number << "of length" << record_length << "number read so far" << number_read;
2021-01-26 02:56:59 +00:00
memcpy ( data , ( unsigned char * ) dataBA . data ( ) , record_length ) ;
return data ;
}
2021-04-02 04:39:06 +00:00
// Returns empty QByteArray() on failure.
QByteArray fileChecksum ( const QString & fileName ,
QCryptographicHash : : Algorithm hashAlgorithm )
{
QFile f ( fileName ) ;
if ( f . open ( QFile : : ReadOnly ) ) {
QCryptographicHash hash ( hashAlgorithm ) ;
bool res = hash . addData ( & f ) ;
f . close ( ) ;
if ( res ) {
return hash . result ( ) ;
}
}
return QByteArray ( ) ;
}
2021-01-26 02:56:59 +00:00
2021-04-02 04:39:06 +00:00
// Return date used within OSCAR, assuming day ends at split time in preferences (usually noon)
QDate getNominalDate ( QDateTime dt ) {
QDate d = dt . date ( ) ;
QTime tm = dt . time ( ) ;
QTime daySplitTime = p_profile - > session - > getPref ( STR_IS_DaySplitTime ) . toTime ( ) ;
if ( tm < daySplitTime )
d = d . addDays ( - 1 ) ;
return d ;
}
QDate getNominalDate ( unsigned int dt ) {
QDateTime xdt = QDateTime : : fromSecsSinceEpoch ( dt ) ;
return getNominalDate ( xdt ) ;
}
2021-01-26 02:56:59 +00:00
///////////////////////////////////////////////
// U.BIN - Open and parse session list and create session data structures
// with session start and stop times.
///////////////////////////////////////////////
2021-04-02 04:39:06 +00:00
bool load6Sessions ( ) {
2021-01-26 02:56:59 +00:00
RollingFile rf ;
unsigned int ts1 , ts2 ;
SessionData . clear ( ) ;
qDebug ( ) < < " Parsing U.BIN " ;
2021-04-02 04:39:06 +00:00
if ( ! rf . open ( " U.BIN " ) ) {
2021-01-26 02:56:59 +00:00
qWarning ( ) < < " Unable to open U.BIN " ;
return false ;
}
do {
DV6_U_REC * rec = ( DV6_U_REC * ) rf . get ( ) ;
if ( rec = = nullptr )
break ;
DV6_SessionInfo sinfo ;
// big endian
ts1 = convertTime ( rec - > begin ) ; // session start time (this is also the session id)
ts2 = convertTime ( rec - > end ) ; // session end time
2021-06-18 20:31:56 +00:00
//#ifdef DEBUG6
qDebug ( ) < < " U.BIN Session " < < QDateTime : : fromSecsSinceEpoch ( ts1 ) . toString ( " MM/dd/yyyy hh:mm:ss " ) < < ts1 < < " to " < < QDateTime : : fromSecsSinceEpoch ( ts2 ) . toString ( " MM/dd/yyyy hh:mm:ss " ) < < ts2 ;
//#endif
2021-01-26 02:56:59 +00:00
sinfo . sess = nullptr ;
sinfo . dailyData = nullptr ;
sinfo . begin = ts1 ;
sinfo . end = ts2 ;
sinfo . written = 0 ;
2021-06-18 20:31:56 +00:00
// sinfo.haveHighResData = false;
sinfo . firstHighRes = 0 ;
sinfo . lastHighRes = 0 ;
2021-01-26 02:56:59 +00:00
SessionData [ ts1 ] = sinfo ;
} while ( true ) ;
rf . close ( ) ;
qDebug ( ) < < " DV6 U.BIN processed " < < rf . numread ( ) < < " records " ;
return true ;
}
/////////////////////////////////////////////////////////////////////////////////
// Parse SET.BIN settings file
/////////////////////////////////////////////////////////////////////////////////
bool load6Settings ( const QString & path ) {
QByteArray dataBA ;
QFile f ( path + " / " + SET_BIN ) ;
2021-06-18 20:31:56 +00:00
if ( rebuild_from_backups )
f . setFileName ( rebuild_path + " / " + SET_BIN ) ;
2018-03-25 19:23:05 +00:00
if ( f . open ( QIODevice : : ReadOnly ) ) {
// Read and parse entire SET.BIN file
dataBA = f . readAll ( ) ;
f . close ( ) ;
2021-01-26 02:56:59 +00:00
settings = ( SET_BIN_REC * ) dataBA . data ( ) ;
2018-03-25 19:23:05 +00:00
} else { // if f.open settings file
// Settings file open failed, return
2021-01-26 02:56:59 +00:00
qWarning ( ) < < " Unable to open SET.BIN file " ;
return false ;
2018-03-25 19:23:05 +00:00
}
2021-01-26 02:56:59 +00:00
return true ;
}
////////////////////////////////////////////////////////////////////////////////////////
// S.BIN - Open and load day summary list
////////////////////////////////////////////////////////////////////////////////////////
2021-04-02 04:39:06 +00:00
bool load6DailySummaries ( ) {
2021-01-26 02:56:59 +00:00
RollingFile rf ;
DailySummaries . clear ( ) ;
2021-04-02 04:39:06 +00:00
if ( ! rf . open ( " S.BIN " ) ) {
2021-01-26 02:56:59 +00:00
qWarning ( ) < < " Unable to open S.BIN " ;
return false ;
}
qDebug ( ) < < " Reading S.BIN summaries " ;
do {
DV6_S_REC * rec = ( DV6_S_REC * ) rf . get ( ) ;
if ( rec = = nullptr )
break ;
DV6_S_Data dailyData ;
dailyData . start_time = convertTime ( rec - > begin ) ;
dailyData . stop_time = convertTime ( rec - > end ) ;
2021-04-02 04:39:06 +00:00
dailyData . written = convertTime ( rec - > written ) ;
# ifdef DEBUG6
qDebug ( ) < < " DV6 S.BIN start " < < dailyData . start_time
< < " stop " < < dailyData . stop_time
2021-04-09 04:05:54 +00:00
< < " written " < < dailyData . written ;
2021-04-02 04:39:06 +00:00
# endif
2021-01-26 02:56:59 +00:00
dailyData . hours = float ( rec - > hours ) / 10.0F ;
dailyData . pressureSetMin = float ( rec - > pressureSetMin ) / 10.0F ;
dailyData . pressureSetMax = float ( rec - > pressureSetMax ) / 10.0F ;
// The following stuff is not necessary to decode, but can be used to verify we are on the right track
dailyData . pressureAvg = float ( rec - > pressureAvg ) / 10.0F ;
dailyData . pressureMax = float ( rec - > pressureMax ) / 10.0F ;
dailyData . pressure50 = float ( rec - > pressure50 ) / 10.0F ;
dailyData . pressure90 = float ( rec - > pressure90 ) / 10.0F ;
dailyData . pressure95 = float ( rec - > pressure95 ) / 10.0F ;
dailyData . pressureStdDev = float ( rec - > pressureStdDev ) / 10.0F ;
dailyData . leakAvg = float ( rec - > leakAvg ) / 10.0F ;
dailyData . leakMax = float ( rec - > leakMax ) / 10.0F ;
dailyData . leak50 = float ( rec - > leak50 ) / 10.0F ;
dailyData . leak90 = float ( rec - > leak90 ) / 10.0F ;
dailyData . leak95 = float ( rec - > leak95 ) / 10.0F ;
dailyData . leakStdDev = float ( rec - > leakStdDev ) / 10.0F ;
dailyData . tidalVolume = float ( rec - > tv1 | rec - > tv2 < < 8 ) ;
dailyData . avgBreathRate = float ( rec - > avgBreathRate ) ;
dailyData . snores = float ( rec - > snores ) ;
dailyData . timeInExPuf = float ( rec - > timeInExPuf ) / 2.0F ;
dailyData . timeInFL = float ( rec - > timeInFL ) / 2.0F ;
dailyData . timeInPB = float ( rec - > timeInPB ) / 2.0F ;
dailyData . maskFit = float ( rec - > maskFit ) / 2.0F ;
dailyData . indexOA = float ( rec - > indexOA ) / 4.0F ;
dailyData . indexCA = float ( rec - > indexCA ) / 4.0F ;
dailyData . indexHyp = float ( rec - > indexHyp ) / 4.0F ;
DailySummaries [ dailyData . start_time ] = dailyData ;
2021-04-02 04:39:06 +00:00
/**** Previous loader did this:
if ( ! mach - > sessionlist . contains ( ts1 ) ) { // Check if already imported
qDebug ( ) < < " Detected new Session " < < ts1 ;
R . sess = new Session ( mach , ts1 ) ;
R . sess - > SetChanged ( true ) ;
R . sess - > really_set_first ( qint64 ( ts1 ) * 1000L ) ;
R . sess - > really_set_last ( qint64 ( ts2 ) * 1000L ) ;
if ( data [ 49 ] ! = data [ 50 ] ) {
R . sess - > settings [ CPAP_PressureMin ] = R . pressureSetMin ;
R . sess - > settings [ CPAP_PressureMax ] = R . pressureSetMax ;
R . sess - > settings [ CPAP_Mode ] = MODE_APAP ;
} else {
R . sess - > settings [ CPAP_Mode ] = MODE_CPAP ;
R . sess - > settings [ CPAP_Pressure ] = R . pressureSetMin ;
}
R . hasMaskPressure = false ;
* * */
2021-01-26 02:56:59 +00:00
} while ( true ) ;
rf . close ( ) ;
qDebug ( ) < < " DV6 S.BIN processed " < < rf . numread ( ) < < " records " ;
return true ;
}
////////////////////////////////////////////////////////////////////////////////////////
// Parse VER.BIN for model number, serial, etc.
////////////////////////////////////////////////////////////////////////////////////////
bool load6VersionInfo ( const QString & path ) {
QByteArray dataBA ;
QByteArray str ;
QFile f ( path + " /VER.BIN " ) ;
2021-06-18 20:31:56 +00:00
if ( rebuild_from_backups )
f . setFileName ( rebuild_path + " /VER.BIN " ) ;
2021-01-26 02:56:59 +00:00
info . series = " DV6 " ;
info . brand = " DeVilbiss " ;
2018-03-25 19:23:05 +00:00
2018-03-26 11:51:43 +00:00
if ( f . open ( QIODevice : : ReadOnly ) ) {
dataBA = f . readAll ( ) ;
f . close ( ) ;
int cnt = 0 ;
for ( int i = 0 ; i < dataBA . size ( ) ; + + i ) { // deliberately going one further to catch end condition
if ( ( dataBA . at ( i ) = = 0 ) | | ( i > = dataBA . size ( ) - 1 ) ) { // if null terminated or last byte
switch ( cnt ) {
case 1 : // serial
2021-01-26 02:56:59 +00:00
info . serial = str ;
2018-03-26 11:51:43 +00:00
break ;
2021-01-26 02:56:59 +00:00
case 2 : // modelnumber
// info.model = str;
info . modelnumber = str ;
info . modelnumber = info . modelnumber . trimmed ( ) ;
for ( int i = 0 ; i < ( int ) sizeof ( testedModels ) ; i + + ) {
if ( testedModels [ i ] . model = = info . modelnumber
| | testedModels [ i ] . model . isEmpty ( ) ) {
info . model = testedModels [ i ] . name ;
break ;
}
}
2018-03-26 11:51:43 +00:00
break ;
case 7 : // ??? V025RN20170
break ;
case 9 : // ??? V014BL20150630
break ;
case 11 : // ??? 01 09
break ;
case 12 : // ??? 0C 0C
break ;
case 14 : // ??? BA 0C
break ;
default :
break ;
}
// Clear and start a new data record
str . clear ( ) ;
cnt + + ;
} else {
// Add the character to the current string
str . append ( dataBA [ i ] ) ;
}
}
2021-01-26 02:56:59 +00:00
return true ;
2018-03-26 11:51:43 +00:00
} else { // if (f.open(...)
// VER.BIN open failed
2021-01-26 02:56:59 +00:00
qWarning ( ) < < " Unable to open VER.BIN " ;
return false ;
2018-03-26 11:51:43 +00:00
}
2021-01-26 02:56:59 +00:00
}
2018-03-25 19:23:05 +00:00
2021-01-26 02:56:59 +00:00
////////////////////////////////////////////////////////////////////////////////////////
// Create DV6_SessionInfo structures for each session and store in SessionData qmap
////////////////////////////////////////////////////////////////////////////////////////
2018-03-25 19:23:05 +00:00
2021-01-26 02:56:59 +00:00
int create6Sessions ( ) {
SessionID sid = 0 ;
Session * sess ;
for ( auto sinfo = SessionData . begin ( ) , end = SessionData . end ( ) ; sinfo ! = end ; + + sinfo ) {
sid = sinfo - > begin ;
2018-03-25 19:23:05 +00:00
2021-01-26 02:56:59 +00:00
if ( mach - > SessionExists ( sid ) ) {
// skip already imported sessions..
2021-06-18 20:31:56 +00:00
qDebug ( ) < < " Session already exists " < < QDateTime : : fromSecsSinceEpoch ( sid ) . toString ( " MM/dd/yyyy hh:mm:ss " ) ;
2021-01-26 02:56:59 +00:00
} else if ( sinfo - > sess = = nullptr ) {
// process new sessions
sess = new Session ( mach , sid ) ;
# ifdef DEBUG6
2021-06-18 20:31:56 +00:00
qDebug ( ) < < " Creating session " < < QDateTime : : fromSecsSinceEpoch ( sinfo - > begin ) . toString ( " MM/dd/yyyy hh:mm:ss " ) < < " to " < < QDateTime : : fromSecsSinceEpoch ( sinfo - > end ) . toString ( " MM/dd/yyyy hh:mm:ss " ) ;
2021-01-26 02:56:59 +00:00
# endif
2018-03-25 19:23:05 +00:00
2021-01-26 02:56:59 +00:00
sinfo - > sess = sess ;
sinfo - > dailyData = nullptr ;
sinfo - > written = 0 ;
2021-06-18 20:31:56 +00:00
// sinfo->haveHighResData = false;
sinfo - > firstHighRes = 0 ;
sinfo - > lastHighRes = 0 ;
2018-03-25 19:23:05 +00:00
2021-01-26 02:56:59 +00:00
sess - > really_set_first ( quint64 ( sinfo - > begin ) * 1000L ) ;
sess - > really_set_last ( quint64 ( sinfo - > end ) * 1000L ) ;
2018-03-25 19:23:05 +00:00
2021-01-26 02:56:59 +00:00
// rampstart[sid] = 0;
// rampend[sid] = 0;
sess - > SetChanged ( true ) ;
sess - > AddEventList ( INTELLIPAP_Unknown1 , EVL_Event ) ;
sess - > AddEventList ( INTELLIPAP_Unknown2 , EVL_Event ) ;
2018-03-25 19:23:05 +00:00
2021-01-26 02:56:59 +00:00
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 ) ;
2021-06-18 20:31:56 +00:00
sess - > AddEventList ( INTP_SnoreFlag , EVL_Event ) ;
2018-03-25 19:23:05 +00:00
2021-01-26 02:56:59 +00:00
sess - > AddEventList ( CPAP_Obstructive , EVL_Event ) ;
sess - > AddEventList ( CPAP_Hypopnea , EVL_Event ) ;
sess - > AddEventList ( CPAP_NRI , EVL_Event ) ;
// sess->AddEventList(CPAP_LeakFlag, EVL_Event);
sess - > AddEventList ( CPAP_ExP , EVL_Event ) ;
sess - > AddEventList ( CPAP_FlowLimit , EVL_Event ) ;
2018-03-26 11:51:43 +00:00
2018-03-25 19:23:05 +00:00
2021-01-26 02:56:59 +00:00
} else {
// If there is a duplicate session, null out the earlier session
// otherwise there will be a crash on shutdown.
//?? for (int z = 0; z < SessionStart.size(); z++) {
//?? if (SessionStart[z] == sid) {
//?? SessionStart[z] = 0;
//?? SessionEnd[z] = 0;
//?? break;
//?? }
2021-06-18 20:31:56 +00:00
qDebug ( ) < < sid < < " has double ups " < < QDateTime : : fromSecsSinceEpoch ( sid ) . toString ( " MM/dd/yyyy hh:mm:ss " ) ;
2021-01-26 02:56:59 +00:00
/*Session *sess=Sessions[sid];
Sessions . erase ( Sessions . find ( sid ) ) ;
delete sess ;
SessionStart [ i ] = 0 ;
SessionEnd [ i ] = 0 ; */
2018-03-25 19:23:05 +00:00
}
}
2021-01-26 02:56:59 +00:00
qDebug ( ) < < " Created " < < SessionData . size ( ) < < " sessions " ;
return SessionData . size ( ) ;
}
2018-03-25 19:23:05 +00:00
2021-01-26 02:56:59 +00:00
////////////////////////////////////////////////////////////////////////////////////////
// Parse R.BIN for high resolution flow data
////////////////////////////////////////////////////////////////////////////////////////
2018-03-25 19:23:05 +00:00
2021-04-02 04:39:06 +00:00
bool load6HighResData ( ) {
2021-01-26 02:56:59 +00:00
RollingFile rf ;
Session * sess = nullptr ;
unsigned int rec_ts1 , previousRecBegin = 0 ;
bool inSession = false ; // true if we are adding data to this session
2021-04-02 04:39:06 +00:00
if ( ! rf . open ( " R.BIN " ) ) {
2021-01-26 02:56:59 +00:00
qWarning ( ) < < " DV6 Unable to open R.BIN " ;
return false ;
}
qDebug ( ) < < " R.BIN starting at record " < < rf . recnum ( ) ;
sess = NULL ;
EventList * flow = NULL ;
EventList * pressure = NULL ;
EventList * FLG = NULL ;
EventList * snore = NULL ;
2018-03-26 11:51:43 +00:00
// EventList * leak = NULL;
2021-01-26 02:56:59 +00:00
/***
EventList * OA = NULL ;
EventList * HY = NULL ;
EventList * NOA = NULL ;
EventList * EXP = NULL ;
EventList * FL = NULL ;
EventList * PB = NULL ;
EventList * VS = NULL ;
EventList * LL = NULL ;
EventList * RE = NULL ;
bool inOA = false , inH = false , inCA = false , inExP = false , inVS = false , inFL = false , inPB = false , inRE = false , inLL = false ;
qint64 OAstart = 0 , OAend = 0 ;
qint64 Hstart = 0 , Hend = 0 ;
qint64 CAstart = 0 , CAend = 0 ;
qint64 ExPstart = 0 , ExPend = 0 ;
qint64 VSstart = 0 , VSend = 0 ;
qint64 FLstart = 0 , FLend = 0 ;
qint64 PBstart = 0 , PBend = 0 ;
qint64 REstart = 0 , REend = 0 ;
qint64 LLstart = 0 , LLend = 0 ;
// lastts1 = 0;
* * */
// sinfo is for walking through sessions when matching up with flow data records
QMap < SessionID , DV6_SessionInfo > : : iterator sinfo ;
sinfo = SessionData . begin ( ) ;
do {
DV6_R_REC * R = ( DV6_R_REC * ) rf . get ( ) ;
if ( R = = nullptr )
break ;
2018-03-25 19:23:05 +00:00
2021-01-26 02:56:59 +00:00
sess = sinfo - > sess ;
2018-03-25 19:23:05 +00:00
2021-01-26 02:56:59 +00:00
// Get the timestamp from the record
rec_ts1 = convertTime ( R - > timestamp ) ;
2018-03-25 19:23:05 +00:00
2021-01-26 02:56:59 +00:00
if ( rec_ts1 < previousRecBegin ) {
qWarning ( ) < < " R.BIN - Corruption/Out of sequence data found, skipping record " < < rf . recnum ( ) < < " , prev "
2021-06-18 20:31:56 +00:00
< < QDateTime : : fromSecsSinceEpoch ( previousRecBegin ) . toString ( " MM/dd/yyyy hh:mm:ss " ) < < previousRecBegin
2021-01-26 02:56:59 +00:00
< < " this "
2021-06-18 20:31:56 +00:00
< < QDateTime : : fromSecsSinceEpoch ( rec_ts1 ) . toString ( " MM/dd/yyyy hh:mm:ss " ) < < rec_ts1 ;
2021-01-26 02:56:59 +00:00
continue ;
}
// Look for a gap in DV6_R records. They should be at two second intervals.
// If there is a gap, we are probably in a new session
if ( inSession & & ( ( rec_ts1 - previousRecBegin ) > 2 ) ) {
if ( sess ) {
2018-06-10 08:38:03 +00:00
sess - > set_last ( flow - > last ( ) ) ;
2021-01-26 02:56:59 +00:00
if ( sess - > first ( ) = = 0 )
qWarning ( ) < < " R.BIN first = 0 - 1320 " ;
EventDataType min = flow - > Min ( ) ;
EventDataType max = flow - > Max ( ) ;
sess - > setMin ( CPAP_FlowRate , min ) ;
sess - > setMax ( CPAP_FlowRate , max ) ;
sess - > setPhysMax ( CPAP_FlowRate , min ) ; // not sure :/
sess - > setPhysMin ( CPAP_FlowRate , max ) ;
// sess->really_set_last(flow->last());
}
sess = nullptr ;
flow = nullptr ;
pressure = nullptr ;
FLG = nullptr ;
snore = nullptr ;
inSession = false ;
}
// Skip over sessions until we find one that this record is in
while ( rec_ts1 > sinfo - > end ) {
# ifdef DEBUG6
2021-06-18 20:31:56 +00:00
qDebug ( ) < < " R.BIN - skipping session " < < QDateTime : : fromSecsSinceEpoch ( sinfo - > begin ) . toString ( " MM/dd/yyyy hh:mm:ss " )
< < " looking for " < < QDateTime : : fromSecsSinceEpoch ( rec_ts1 ) . toString ( " MM/dd/yyyy hh:mm:ss " )
2021-01-26 02:56:59 +00:00
< < " record " < < rf . recnum ( ) ;
# endif
if ( inSession & & sess ) {
// update min and max
// then add to machine
if ( sess - > first ( ) = = 0 )
qWarning ( ) < < " R.BIN first = 0 - 1284 " ;
EventDataType min = flow - > Min ( ) ;
EventDataType max = flow - > Max ( ) ;
sess - > setMin ( CPAP_FlowRate , min ) ;
sess - > setMax ( CPAP_FlowRate , max ) ;
sess - > setPhysMax ( CPAP_FlowRate , min ) ; // not sure :/
sess - > setPhysMin ( CPAP_FlowRate , max ) ;
2018-06-10 08:38:03 +00:00
sess = nullptr ;
flow = nullptr ;
pressure = nullptr ;
2021-01-26 02:56:59 +00:00
FLG = nullptr ;
snore = nullptr ;
inSession = false ;
2018-06-10 08:38:03 +00:00
}
2021-01-26 02:56:59 +00:00
sinfo + + ;
if ( sinfo = = SessionData . end ( ) )
2018-03-25 19:23:05 +00:00
break ;
2021-01-26 02:56:59 +00:00
}
previousRecBegin = rec_ts1 ;
2018-03-25 19:23:05 +00:00
2021-01-26 02:56:59 +00:00
// If we have data beyond last session, we are in trouble (for unknown reasons)
if ( sinfo = = SessionData . end ( ) ) {
qWarning ( ) < < " DV6 R.BIN import ran out of sessions to match flow data, record " < < rf . recnum ( ) ;
break ;
}
// Check if record belongs in this session or a future session
if ( ! inSession & & rec_ts1 < = sinfo - > end ) {
sess = sinfo - > sess ; // this is the Session we want
if ( ! inSession & & sess ) {
inSession = true ;
flow = sess - > AddEventList ( CPAP_FlowRate , EVL_Waveform , 0.01f , 0.0f , 0.0f , 0.0f , double ( 2000 ) / double ( 50 ) ) ;
pressure = sess - > AddEventList ( CPAP_Pressure , EVL_Waveform , 0.1f , 0.0f , 0.0f , 0.0f , double ( 2000 ) / double ( 2 ) ) ;
FLG = sess - > AddEventList ( CPAP_FLG , EVL_Waveform , 1.0f , 0.0f , 0.0f , 0.0f , double ( 2000 ) / double ( 2 ) ) ;
snore = sess - > AddEventList ( CPAP_Snore , EVL_Waveform , 1.0f , 0.0f , 0.0f , 0.0f , double ( 2000 ) / double ( 2 ) ) ;
// sinfo->hasMaskPressure = true;
// leak = R->sess->AddEventList(CPAP_Leak, EVL_Waveform, 1.0, 0.0, 0.0, 0.0, double(2000) / double(1));
/***
2018-03-25 19:23:05 +00:00
OA = R - > sess - > AddEventList ( CPAP_Obstructive , EVL_Event ) ;
NOA = R - > sess - > AddEventList ( CPAP_NRI , EVL_Event ) ;
RE = R - > sess - > AddEventList ( CPAP_RERA , EVL_Event ) ;
VS = R - > sess - > AddEventList ( CPAP_VSnore , EVL_Event ) ;
HY = R - > sess - > AddEventList ( CPAP_Hypopnea , EVL_Event ) ;
EXP = R - > sess - > AddEventList ( CPAP_ExP , EVL_Event ) ;
FL = R - > sess - > AddEventList ( CPAP_FlowLimit , EVL_Event ) ;
PB = R - > sess - > AddEventList ( CPAP_PB , EVL_Event ) ;
LL = R - > sess - > AddEventList ( CPAP_LargeLeak , EVL_Event ) ;
2021-01-26 02:56:59 +00:00
* * */
}
}
if ( inSession ) {
// Record breath and pressure waveforms
qint64 ti = qint64 ( rec_ts1 ) * 1000 ;
flow - > AddWaveform ( ti , R - > breath , 50 , 2000 ) ;
pressure - > AddWaveform ( ti , & R - > pressure1 , 2 , 2000 ) ;
2021-06-18 20:31:56 +00:00
if ( sinfo - > firstHighRes = = 0 | | sinfo - > firstHighRes > rec_ts1 ) sinfo - > firstHighRes = rec_ts1 ;
if ( sinfo - > lastHighRes = = 0 | | sinfo - > lastHighRes < rec_ts1 + 2 ) sinfo - > lastHighRes = rec_ts1 + 2 ;
// sinfo->haveHighResData = true;
2021-01-26 02:56:59 +00:00
if ( sess - > first ( ) = = 0 )
qWarning ( ) < < " first = 0 - 1442 " ;
2021-06-18 20:31:56 +00:00
2021-01-26 02:56:59 +00:00
//////////////////////////////////////////////////////////////////
// Show Flow Limitation Events as a graph
//////////////////////////////////////////////////////////////////
qint16 severity = ( R - > flags1 [ 0 ] > > 4 ) & 0x03 ;
FLG - > AddWaveform ( ti , & severity , 1 , 1000 ) ;
severity = ( R - > flags2 [ 0 ] > > 4 ) & 0x03 ;
FLG - > AddWaveform ( ti + 1000 , & severity , 1 , 1000 ) ;
//////////////////////////////////////////////////////////////////
// Show Snore Events as a graph
//////////////////////////////////////////////////////////////////
severity = R - > flags1 [ 0 ] & 0x03 ;
snore - > AddWaveform ( ti , & severity , 1 , 1000 ) ;
severity = R - > flags2 [ 0 ] & 0x03 ;
snore - > AddWaveform ( ti + 1000 , & severity , 1 , 1000 ) ;
/****
// Fields data[107] && data[108] are bitfields default is 0x90, occasionally 0x98
d [ 0 ] = data [ 107 ] ;
d [ 1 ] = data [ 108 ] ;
//leak->AddWaveform(ti+40000, d, 2, 2000);
// Needs to track state to pull events out cleanly..
//////////////////////////////////////////////////////////////////
// High Leak
//////////////////////////////////////////////////////////////////
if ( data [ 110 ] & 3 ) { // LL state 1st second
if ( ! inLL ) {
LLstart = ti ;
inLL = true ;
}
LLend = ti + 1000L ;
} else {
if ( inLL ) {
inLL = false ;
LL - > AddEvent ( LLstart , ( LLend - LLstart ) / 1000L ) ;
LLstart = 0 ;
}
2018-03-25 19:23:05 +00:00
}
2021-01-26 02:56:59 +00:00
if ( data [ 114 ] & 3 ) {
if ( ! inLL ) {
LLstart = ti + 1000L ;
inLL = true ;
2018-03-26 04:11:42 +00:00
}
2021-01-26 02:56:59 +00:00
LLend = ti + 2000L ;
2018-03-26 04:11:42 +00:00
2021-01-26 02:56:59 +00:00
} else {
if ( inLL ) {
inLL = false ;
LL - > AddEvent ( LLstart , ( LLend - LLstart ) / 1000L ) ;
LLstart = 0 ;
2018-03-26 04:11:42 +00:00
}
2021-01-26 02:56:59 +00:00
}
2018-03-26 04:11:42 +00:00
2021-01-26 02:56:59 +00:00
//////////////////////////////////////////////////////////////////
// Obstructive Apnea
//////////////////////////////////////////////////////////////////
2018-03-26 04:11:42 +00:00
2021-01-26 02:56:59 +00:00
if ( data [ 110 ] & 12 ) { // OA state 1st second
if ( ! inOA ) {
OAstart = ti ;
inOA = true ;
2018-03-25 19:23:05 +00:00
}
2021-01-26 02:56:59 +00:00
OAend = ti + 1000L ;
} else {
if ( inOA ) {
inOA = false ;
OA - > AddEvent ( OAstart , ( OAend - OAstart ) / 1000L ) ;
OAstart = 0 ;
}
}
if ( data [ 114 ] & 12 ) {
if ( ! inOA ) {
OAstart = ti + 1000L ;
inOA = true ;
}
OAend = ti + 2000L ;
2018-03-26 04:11:42 +00:00
2021-01-26 02:56:59 +00:00
} else {
if ( inOA ) {
inOA = false ;
OA - > AddEvent ( OAstart , ( OAend - OAstart ) / 1000L ) ;
OAstart = 0 ;
2018-03-26 04:11:42 +00:00
}
2021-01-26 02:56:59 +00:00
}
2018-03-26 04:11:42 +00:00
2021-01-26 02:56:59 +00:00
//////////////////////////////////////////////////////////////////
// Hypopnea
//////////////////////////////////////////////////////////////////
2018-03-26 04:11:42 +00:00
2021-01-26 02:56:59 +00:00
if ( data [ 110 ] & 192 ) {
if ( ! inH ) {
Hstart = ti ;
inH = true ;
2018-03-25 19:23:05 +00:00
}
2021-01-26 02:56:59 +00:00
Hend = ti + 1000L ;
} else {
if ( inH ) {
inH = false ;
HY - > AddEvent ( Hstart , ( Hend - Hstart ) / 1000L ) ;
Hstart = 0 ;
}
}
2018-03-25 19:23:05 +00:00
2021-01-26 02:56:59 +00:00
if ( data [ 114 ] & 192 ) {
if ( ! inH ) {
Hstart = ti + 1000L ;
inH = true ;
}
Hend = ti + 2000L ;
} else {
if ( inH ) {
inH = false ;
HY - > AddEvent ( Hstart , ( Hend - Hstart ) / 1000L ) ;
Hstart = 0 ;
2018-03-26 04:11:42 +00:00
}
2021-01-26 02:56:59 +00:00
}
2018-03-26 04:11:42 +00:00
2021-01-26 02:56:59 +00:00
//////////////////////////////////////////////////////////////////
// Non Responding Apnea Event (Are these CA's???)
//////////////////////////////////////////////////////////////////
if ( data [ 110 ] & 48 ) { // OA state 1st second
if ( ! inCA ) {
CAstart = ti ;
inCA = true ;
2018-03-26 04:11:42 +00:00
}
2021-01-26 02:56:59 +00:00
CAend = ti + 1000L ;
} else {
if ( inCA ) {
inCA = false ;
NOA - > AddEvent ( CAstart , ( CAend - CAstart ) / 1000L ) ;
CAstart = 0 ;
}
}
if ( data [ 114 ] & 48 ) {
if ( ! inCA ) {
CAstart = ti + 1000L ;
inCA = true ;
}
CAend = ti + 2000L ;
2018-03-26 04:11:42 +00:00
2021-01-26 02:56:59 +00:00
} else {
if ( inCA ) {
inCA = false ;
NOA - > AddEvent ( CAstart , ( CAend - CAstart ) / 1000L ) ;
CAstart = 0 ;
2018-03-26 04:11:42 +00:00
}
2021-01-26 02:56:59 +00:00
}
2018-03-25 19:23:05 +00:00
2021-01-26 02:56:59 +00:00
//////////////////////////////////////////////////////////////////
// VSnore Event
//////////////////////////////////////////////////////////////////
if ( data [ 109 ] & 3 ) { // OA state 1st second
if ( ! inVS ) {
VSstart = ti ;
inVS = true ;
2018-03-26 04:11:42 +00:00
}
2021-01-26 02:56:59 +00:00
VSend = ti + 1000L ;
} else {
if ( inVS ) {
inVS = false ;
VS - > AddEvent ( VSstart , ( VSend - VSstart ) / 1000L ) ;
VSstart = 0 ;
}
}
if ( data [ 113 ] & 3 ) {
if ( ! inVS ) {
VSstart = ti + 1000L ;
inVS = true ;
}
VSend = ti + 2000L ;
2018-03-25 19:23:05 +00:00
2021-01-26 02:56:59 +00:00
} else {
if ( inVS ) {
inVS = false ;
VS - > AddEvent ( VSstart , ( VSend - VSstart ) / 1000L ) ;
VSstart = 0 ;
2018-03-26 04:11:42 +00:00
}
2021-01-26 02:56:59 +00:00
}
2018-03-25 19:23:05 +00:00
2021-01-26 02:56:59 +00:00
//////////////////////////////////////////////////////////////////
// Expiratory puff Event
//////////////////////////////////////////////////////////////////
if ( data [ 109 ] & 12 ) { // OA state 1st second
if ( ! inExP ) {
ExPstart = ti ;
inExP = true ;
2018-03-26 04:11:42 +00:00
}
2021-01-26 02:56:59 +00:00
ExPend = ti + 1000L ;
} else {
if ( inExP ) {
inExP = false ;
EXP - > AddEvent ( ExPstart , ( ExPend - ExPstart ) / 1000L ) ;
ExPstart = 0 ;
}
}
if ( data [ 113 ] & 12 ) {
if ( ! inExP ) {
ExPstart = ti + 1000L ;
inExP = true ;
}
ExPend = ti + 2000L ;
2018-03-26 04:11:42 +00:00
2021-01-26 02:56:59 +00:00
} else {
if ( inExP ) {
inExP = false ;
EXP - > AddEvent ( ExPstart , ( ExPend - ExPstart ) / 1000L ) ;
ExPstart = 0 ;
2018-03-26 04:11:42 +00:00
}
2021-01-26 02:56:59 +00:00
}
2018-03-26 04:11:42 +00:00
2021-01-26 02:56:59 +00:00
//////////////////////////////////////////////////////////////////
// Flow Limitation Event
//////////////////////////////////////////////////////////////////
if ( data [ 109 ] & 48 ) { // OA state 1st second
if ( ! inFL ) {
FLstart = ti ;
inFL = true ;
2018-03-26 04:11:42 +00:00
}
2021-01-26 02:56:59 +00:00
FLend = ti + 1000L ;
} else {
if ( inFL ) {
inFL = false ;
FL - > AddEvent ( FLstart , ( FLend - FLstart ) / 1000L ) ;
FLstart = 0 ;
}
}
if ( data [ 113 ] & 48 ) {
if ( ! inFL ) {
FLstart = ti + 1000L ;
inFL = true ;
}
FLend = ti + 2000L ;
2018-03-26 04:11:42 +00:00
2021-01-26 02:56:59 +00:00
} else {
if ( inFL ) {
inFL = false ;
FL - > AddEvent ( FLstart , ( FLend - FLstart ) / 1000L ) ;
FLstart = 0 ;
2018-03-26 04:11:42 +00:00
}
2021-01-26 02:56:59 +00:00
}
2018-03-26 04:11:42 +00:00
2021-01-26 02:56:59 +00:00
//////////////////////////////////////////////////////////////////
// Periodic Breathing Event
//////////////////////////////////////////////////////////////////
if ( data [ 109 ] & 192 ) { // OA state 1st second
if ( ! inPB ) {
PBstart = ti ;
inPB = true ;
2018-03-26 04:11:42 +00:00
}
2021-01-26 02:56:59 +00:00
PBend = ti + 1000L ;
} else {
if ( inPB ) {
inPB = false ;
PB - > AddEvent ( PBstart , ( PBend - PBstart ) / 1000L ) ;
PBstart = 0 ;
}
}
if ( data [ 113 ] & 192 ) {
if ( ! inPB ) {
PBstart = ti + 1000L ;
inPB = true ;
}
PBend = ti + 2000L ;
2018-03-26 04:11:42 +00:00
2021-01-26 02:56:59 +00:00
} else {
if ( inPB ) {
inPB = false ;
PB - > AddEvent ( PBstart , ( PBend - PBstart ) / 1000L ) ;
PBstart = 0 ;
2018-03-26 04:11:42 +00:00
}
2021-01-26 02:56:59 +00:00
}
2018-03-26 04:11:42 +00:00
2021-01-26 02:56:59 +00:00
//////////////////////////////////////////////////////////////////
// Respiratory Effort Related Arousal Event
//////////////////////////////////////////////////////////////////
if ( data [ 111 ] & 48 ) { // OA state 1st second
if ( ! inRE ) {
REstart = ti ;
inRE = true ;
2018-03-26 04:11:42 +00:00
}
2021-01-26 02:56:59 +00:00
REend = ti + 1000L ;
} else {
if ( inRE ) {
inRE = false ;
RE - > AddEvent ( REstart , ( REend - REstart ) / 1000L ) ;
REstart = 0 ;
}
}
if ( data [ 115 ] & 48 ) {
if ( ! inRE ) {
REstart = ti + 1000L ;
inRE = true ;
}
REend = ti + 2000L ;
2018-03-26 04:11:42 +00:00
2021-01-26 02:56:59 +00:00
} else {
if ( inRE ) {
inRE = false ;
RE - > AddEvent ( REstart , ( REend - REstart ) / 1000L ) ;
REstart = 0 ;
2018-03-26 04:11:42 +00:00
}
2018-03-25 19:23:05 +00:00
}
2021-01-26 02:56:59 +00:00
* * */
}
2018-03-25 19:23:05 +00:00
2021-01-26 02:56:59 +00:00
} while ( true ) ;
2018-06-10 08:14:28 +00:00
2021-01-26 02:56:59 +00:00
if ( inSession & & sess ) {
/***
2018-03-26 04:11:42 +00:00
// Close event states if they are still open, and write event.
if ( inH ) HY - > AddEvent ( Hstart , ( Hend - Hstart ) / 1000L ) ;
if ( inOA ) OA - > AddEvent ( OAstart , ( OAend - OAstart ) / 1000L ) ;
if ( inCA ) NOA - > AddEvent ( CAstart , ( CAend - CAstart ) / 1000L ) ;
if ( inLL ) LL - > AddEvent ( LLstart , ( LLend - LLstart ) / 1000L ) ;
if ( inVS ) HY - > AddEvent ( VSstart , ( VSend - VSstart ) / 1000L ) ;
if ( inExP ) EXP - > AddEvent ( ExPstart , ( ExPend - ExPstart ) / 1000L ) ;
if ( inFL ) FL - > AddEvent ( FLstart , ( FLend - FLstart ) / 1000L ) ;
if ( inPB ) PB - > AddEvent ( PBstart , ( PBend - PBstart ) / 1000L ) ;
if ( inPB ) RE - > AddEvent ( REstart , ( REend - REstart ) / 1000L ) ;
2021-01-26 02:56:59 +00:00
* * */
// update min and max
// then add to machine
if ( sess - > first ( ) = = 0 )
qWarning ( ) < < " R.BIN first = 0 - 1665 " ;
EventDataType min = flow - > Min ( ) ;
EventDataType max = flow - > Max ( ) ;
sess - > setMin ( CPAP_FlowRate , min ) ;
sess - > setMax ( CPAP_FlowRate , max ) ;
sess - > setPhysMax ( CPAP_FlowRate , min ) ; // TODO: not sure :/
sess - > setPhysMin ( CPAP_FlowRate , max ) ;
sess - > really_set_last ( flow - > last ( ) ) ;
sess = nullptr ;
inSession = false ;
}
2018-03-26 04:11:42 +00:00
2021-01-26 02:56:59 +00:00
rf . close ( ) ;
qDebug ( ) < < " DV6 R.BIN processed " < < rf . numread ( ) < < " records " ;
return true ;
}
2018-03-25 19:23:05 +00:00
2021-01-26 02:56:59 +00:00
////////////////////////////////////////////////////////////////////////////////////////
// Parse L.BIN for per minute data
////////////////////////////////////////////////////////////////////////////////////////
2018-03-25 19:23:05 +00:00
2021-04-02 04:39:06 +00:00
bool load6PerMinute ( ) {
2018-03-25 19:23:05 +00:00
2021-01-26 02:56:59 +00:00
RollingFile rf ;
Session * sess = nullptr ;
unsigned int rec_ts1 , previousRecBegin = 0 ;
bool inSession = false ; // true if we are adding data to this session
2018-03-25 19:23:05 +00:00
2021-04-02 04:39:06 +00:00
if ( ! rf . open ( " L.BIN " ) ) {
2021-01-26 02:56:59 +00:00
qWarning ( ) < < " DV6 Unable to open L.BIN " ;
return false ;
}
2018-03-25 19:23:05 +00:00
2021-01-26 02:56:59 +00:00
qDebug ( ) < < " L.BIN Minute Data starting at record " < < rf . recnum ( ) ;
2018-03-26 11:51:43 +00:00
2021-01-26 02:56:59 +00:00
EventList * leak = NULL ;
EventList * maxleak = NULL ;
2018-06-09 12:08:31 +00:00
EventList * RR = NULL ;
EventList * Pressure = NULL ;
EventList * TV = NULL ;
EventList * MV = NULL ;
2021-01-26 02:56:59 +00:00
QMap < SessionID , DV6_SessionInfo > : : iterator sinfo ;
sinfo = SessionData . begin ( ) ;
2018-06-09 12:08:31 +00:00
2021-01-26 02:56:59 +00:00
// Walk through all the records
do {
DV6_L_REC * rec = ( DV6_L_REC * ) rf . get ( ) ;
if ( rec = = nullptr )
break ;
2018-03-26 11:51:43 +00:00
2021-01-26 02:56:59 +00:00
sess = sinfo - > sess ;
2018-03-26 11:51:43 +00:00
2021-01-26 02:56:59 +00:00
// Get the timestamp from the record
rec_ts1 = convertTime ( rec - > timestamp ) ;
2018-03-26 11:51:43 +00:00
2021-01-26 02:56:59 +00:00
if ( rec_ts1 < previousRecBegin ) {
2021-06-18 20:31:56 +00:00
# ifdef DEBUG6
2021-01-26 02:56:59 +00:00
qWarning ( ) < < " L.BIN - Corruption/Out of sequence data found, skipping record " < < rf . recnum ( ) < < " , prev "
2021-06-18 20:31:56 +00:00
< < QDateTime : : fromSecsSinceEpoch ( previousRecBegin ) . toString ( " MM/dd/yyyy hh:mm:ss " ) < < previousRecBegin
2021-01-26 02:56:59 +00:00
< < " this "
2021-06-18 20:31:56 +00:00
< < QDateTime : : fromSecsSinceEpoch ( rec_ts1 ) . toString ( " MM/dd/yyyy hh:mm:ss " ) < < rec_ts1 ;
# endif
2021-01-26 02:56:59 +00:00
continue ;
}
2021-04-09 04:05:54 +00:00
/****
2021-01-26 02:56:59 +00:00
// Look for a gap in DV6_L records. They should be at one minute intervals.
// If there is a gap, we are probably in a new session
if ( inSession & & ( ( rec_ts1 - previousRecBegin ) > 60 ) ) {
2021-06-18 20:31:56 +00:00
qDebug ( ) < < " L.BIN record gap, current " < < QDateTime : : fromSecsSinceEpoch ( rec_ts1 ) . toString ( " MM/dd/yyyy hh:mm:ss " )
< < " previous " < < QDateTime : : fromSecsSinceEpoch ( previousRecBegin ) . toString ( " MM/dd/yyyy hh:mm:ss " ) ;
2021-01-26 02:56:59 +00:00
sess - > set_last ( maxleak - > last ( ) ) ;
sess = nullptr ;
leak = maxleak = MV = TV = RR = Pressure = nullptr ;
inSession = false ;
}
2021-04-09 04:05:54 +00:00
* * * */
2021-01-26 02:56:59 +00:00
// Skip over sessions until we find one that this record is in
while ( rec_ts1 > sinfo - > end ) {
# ifdef DEBUG6
2021-06-18 20:31:56 +00:00
qDebug ( ) < < " L.BIN - skipping session " < < QDateTime : : fromSecsSinceEpoch ( sinfo - > begin ) . toString ( " MM/dd/yyyy hh:mm:ss " ) < < " looking for " < < QDateTime : : fromSecsSinceEpoch ( rec_ts1 ) . toString ( " MM/dd/yyyy hh:mm:ss " ) ;
2021-01-26 02:56:59 +00:00
# endif
if ( inSession & & sess ) {
// Close the open session and update the min and max
2018-06-10 08:38:03 +00:00
sess - > set_last ( maxleak - > last ( ) ) ;
sess = nullptr ;
2021-01-26 02:56:59 +00:00
leak = maxleak = MV = TV = RR = Pressure = nullptr ;
inSession = false ;
2018-06-10 08:38:03 +00:00
}
2021-01-26 02:56:59 +00:00
sinfo + + ;
if ( sinfo = = SessionData . end ( ) )
2018-03-26 11:51:43 +00:00
break ;
2021-01-26 02:56:59 +00:00
}
2018-03-26 11:51:43 +00:00
2021-01-26 02:56:59 +00:00
previousRecBegin = rec_ts1 ;
2018-06-10 08:14:28 +00:00
2021-01-26 02:56:59 +00:00
// If we have data beyond last session, we are in trouble (for unknown reasons)
if ( sinfo = = SessionData . end ( ) ) {
qWarning ( ) < < " DV6 L.BIN import ran out of sessions to match flow data " ;
break ;
}
2018-03-26 11:51:43 +00:00
2021-01-26 02:56:59 +00:00
if ( rec_ts1 < previousRecBegin ) {
qWarning ( ) < < " L.BIN - Corruption/Out of sequence data found, stopping import, prev "
2021-06-18 20:31:56 +00:00
< < QDateTime : : fromSecsSinceEpoch ( previousRecBegin ) . toString ( " MM/dd/yyyy hh:mm:ss " )
2021-01-26 02:56:59 +00:00
< < " this "
2021-06-18 20:31:56 +00:00
< < QDateTime : : fromSecsSinceEpoch ( rec_ts1 ) . toString ( " MM/dd/yyyy hh:mm:ss " ) ;
2021-01-26 02:56:59 +00:00
break ;
}
2018-03-26 11:51:43 +00:00
2021-01-26 02:56:59 +00:00
// Check if record belongs in this session or a future session
if ( ! inSession & & rec_ts1 < = sinfo - > end ) {
sess = sinfo - > sess ; // this is the Session we want
if ( ! inSession & & sess ) {
leak = sess - > AddEventList ( CPAP_Leak , EVL_Event ) ; // , 1.0, 0.0, 0.0, 0.0, double(60000) / double(1));
maxleak = sess - > AddEventList ( CPAP_LeakTotal , EVL_Event ) ; // , 1.0, 0.0, 0.0, 0.0, double(60000) / double(1));
RR = sess - > AddEventList ( CPAP_RespRate , EVL_Event ) ;
MV = sess - > AddEventList ( CPAP_MinuteVent , EVL_Event ) ;
TV = sess - > AddEventList ( CPAP_TidalVolume , EVL_Event ) ;
if ( sess - > last ( ) / 1000 > sinfo - > end )
sinfo - > end = sess - > last ( ) / 1000 ;
2021-06-18 20:31:56 +00:00
// if (!sinfo->haveHighResData) {
2021-01-26 02:56:59 +00:00
// Don't use this pressure if we already have higher resolution data
2021-06-18 20:31:56 +00:00
2021-01-26 02:56:59 +00:00
if ( sinfo - > mode = = MODE_UNKNOWN ) {
if ( rec - > pressureLimitLow ! = rec - > pressureLimitHigh ) {
sess - > settings [ CPAP_PressureMin ] = rec - > pressureLimitLow / 10.0f ;
sess - > settings [ CPAP_PressureMax ] = rec - > pressureLimitHigh / 10.0f ;
// if available sess->settings[CPAP_PS) = ....
sess - > settings [ CPAP_Mode ] = MODE_APAP ;
sinfo - > mode = MODE_APAP ;
} else {
sess - > settings [ CPAP_Mode ] = MODE_CPAP ;
sess - > settings [ CPAP_Pressure ] = rec - > pressureLimitHigh / 10.0f ;
sinfo - > mode = MODE_CPAP ;
2018-06-09 12:08:31 +00:00
}
2021-01-26 02:56:59 +00:00
inSession = true ;
2018-03-26 11:51:43 +00:00
}
2021-01-26 02:56:59 +00:00
}
}
if ( inSession ) {
// Record breath and pressure waveforms
qint64 ti = qint64 ( rec_ts1 ) * 1000 ;
maxleak - > AddEvent ( ti , rec - > maxLeak ) ; //???
leak - > AddEvent ( ti , rec - > avgLeak ) ; //???
RR - > AddEvent ( ti , rec - > breathRate ) ;
2018-06-09 12:08:31 +00:00
2021-06-18 20:31:56 +00:00
if ( sinfo - > firstHighRes = = 0 // No high res data
| | rec_ts1 < sinfo - > firstHighRes // Before high res data begins
| | ( ( rec_ts1 > ( sinfo - > lastHighRes + 2 ) ) & & ( sinfo - > lastHighRes > 0 ) ) ) // or after high res data ends
{
if ( ! Pressure )
Pressure = sess - > AddEventList ( CPAP_Pressure , EVL_Event , 0.1f ) ;
// if (sinfo->firstHighRes == 0) {
Pressure - > AddEvent ( ti , rec - > avgPressure ) ; // average pressure for next minute
Pressure - > AddEvent ( ti + 59998 , rec - > avgPressure ) ; // end of pressure block
// } else {
// for (int i = 0; i < 60; i++) {
// Pressure->AddEvent(ti+i, rec->avgPressure); // average pressure for next minute
// }
// }
}
/***
if ( Pressure )
qDebug ( ) < < " Lowres pressure " < < QDateTime : : fromSecsSinceEpoch ( rec_ts1 ) . toString ( " MM/dd/yyyy hh:mm:ss " )
< < " rec_ts1 " < < rec_ts1 < < " firstHighRes " < < sinfo - > firstHighRes < < " last " < < sinfo - > lastHighRes
< < " Pressure " < < rec - > avgPressure / 10.0f ;
* * */
2021-01-26 02:56:59 +00:00
unsigned tv = rec - > tidalVolume6 + ( rec - > tidalVolume7 < < 8 ) ;
MV - > AddEvent ( ti , rec - > breathRate * tv / 1000.0 ) ;
TV - > AddEvent ( ti , tv ) ;
if ( ! sess - > channelExists ( CPAP_FlowRate ) ) {
// No flow rate, so lets grab this data...
2018-03-26 11:51:43 +00:00
}
}
2021-01-26 02:56:59 +00:00
} while ( true ) ;
if ( sess & & inSession ) {
sess - > set_last ( maxleak - > last ( ) ) ;
2018-03-25 19:23:05 +00:00
}
2021-01-26 02:56:59 +00:00
rf . close ( ) ;
qDebug ( ) < < " DV6 L.BIN processed " < < rf . numread ( ) < < " records " ;
2018-03-25 19:23:05 +00:00
2021-01-26 02:56:59 +00:00
return true ;
}
2018-03-25 19:23:05 +00:00
2021-01-26 02:56:59 +00:00
////////////////////////////////////////////////////////////////////////////////////////
// Parse E.BIN for event data
////////////////////////////////////////////////////////////////////////////////////////
2021-04-02 04:39:06 +00:00
bool load6EventData ( ) {
2021-01-26 02:56:59 +00:00
RollingFile rf ;
Session * sess = nullptr ;
unsigned int rec_ts1 , rec_ts2 , previousRecBegin ;
bool inSession = false ; // true if we are adding data to this session
2018-03-25 19:23:05 +00:00
2018-06-09 12:08:31 +00:00
EventList * OA = nullptr ;
EventList * CA = nullptr ;
2021-06-18 20:31:56 +00:00
EventList * H = nullptr ;
2018-06-09 12:08:31 +00:00
EventList * RE = nullptr ;
2021-01-26 02:56:59 +00:00
EventList * PB = nullptr ;
EventList * LL = nullptr ;
EventList * EP = nullptr ;
EventList * SN = nullptr ;
EventList * FL = nullptr ;
2021-06-18 20:31:56 +00:00
// EventList * FLG = nullptr;
2021-01-26 02:56:59 +00:00
2021-04-02 04:39:06 +00:00
if ( ! rf . open ( " E.BIN " ) ) {
2021-01-26 02:56:59 +00:00
qWarning ( ) < < " DV6 Unable to open E.BIN " ;
return false ;
}
2018-06-09 12:08:31 +00:00
2021-01-26 02:56:59 +00:00
qDebug ( ) < < " Processing E.BIN starting at record " < < rf . recnum ( ) ;
2018-03-25 19:23:05 +00:00
2021-01-26 02:56:59 +00:00
QMap < SessionID , DV6_SessionInfo > : : iterator sinfo ;
sinfo = SessionData . begin ( ) ;
2018-03-25 19:23:05 +00:00
2021-01-26 02:56:59 +00:00
// Walk through all the records
do {
DV6_E_REC * rec = ( DV6_E_REC * ) rf . get ( ) ;
if ( rec = = nullptr )
break ;
sess = sinfo - > sess ;
// Get the timestamp from the record
rec_ts1 = convertTime ( rec - > begin ) ;
rec_ts2 = convertTime ( rec - > end ) ;
// Skip over sessions until we find one that this record is in
while ( rec_ts1 > sinfo - > end ) {
# ifdef DEBUG6
2021-06-18 20:31:56 +00:00
qDebug ( ) < < " E.BIN - skipping session " < < QDateTime : : fromSecsSinceEpoch ( sinfo - > begin ) . toString ( " MM/dd/yyyy hh:mm:ss " ) < < " looking for " < < QDateTime : : fromSecsSinceEpoch ( rec_ts1 ) . toString ( " MM/dd/yyyy hh:mm:ss " ) ;
2021-01-26 02:56:59 +00:00
# endif
if ( inSession ) {
// Close the open session and update the min and max
if ( OA - > last ( ) > 0 )
2018-06-09 12:08:31 +00:00
sess - > set_last ( OA - > last ( ) ) ;
2021-01-26 02:56:59 +00:00
if ( CA - > last ( ) > 0 )
2018-06-09 12:08:31 +00:00
sess - > set_last ( CA - > last ( ) ) ;
2021-01-26 02:56:59 +00:00
if ( H - > last ( ) > 0 )
2018-06-09 12:08:31 +00:00
sess - > set_last ( H - > last ( ) ) ;
2021-01-26 02:56:59 +00:00
if ( RE - > last ( ) > 0 )
2018-06-09 12:08:31 +00:00
sess - > set_last ( RE - > last ( ) ) ;
2021-01-26 02:56:59 +00:00
if ( PB - > last ( ) > 0 )
sess - > set_last ( PB - > last ( ) ) ;
if ( LL - > last ( ) > 0 )
sess - > set_last ( LL - > last ( ) ) ;
if ( EP - > last ( ) > 0 )
sess - > set_last ( EP - > last ( ) ) ;
if ( FL - > last ( ) > 0 )
sess - > set_last ( FL - > last ( ) ) ;
2021-06-18 20:31:56 +00:00
if ( SN - > last ( ) > 0 )
sess - > set_last ( SN - > last ( ) ) ;
/***
if ( FLG - > last ( ) > 0 )
sess - > set_last ( FLG - > last ( ) ) ;
* * */
2021-01-26 02:56:59 +00:00
sess = nullptr ;
H = CA = RE = OA = PB = LL = EP = SN = FL = nullptr ;
inSession = false ;
2018-06-09 12:08:31 +00:00
}
2021-01-26 02:56:59 +00:00
sinfo + + ;
if ( sinfo = = SessionData . end ( ) )
2018-06-09 12:08:31 +00:00
break ;
2021-01-26 02:56:59 +00:00
}
2018-03-25 19:23:05 +00:00
2021-01-26 02:56:59 +00:00
previousRecBegin = rec_ts1 ;
// If we have data beyond last session, we are in trouble (for unknown reasons)
if ( sinfo = = SessionData . end ( ) ) {
2021-06-18 20:31:56 +00:00
qWarning ( ) < < " DV6 E.BIN import ran out of sessions, "
< < " event data begins "
< < QDateTime : : fromSecsSinceEpoch ( rec_ts1 ) . toString ( " MM/dd/yyyy hh:mm:ss " ) ;
2021-01-26 02:56:59 +00:00
break ;
}
if ( rec_ts1 < previousRecBegin ) {
2021-06-18 20:31:56 +00:00
qWarning ( ) < < " DV6 E.BIN - Out of sequence data found, skipping, prev "
< < QDateTime : : fromSecsSinceEpoch ( previousRecBegin ) . toString ( " MM/dd/yyyy hh:mm:ss " )
< < " this event "
< < QDateTime : : fromSecsSinceEpoch ( rec_ts1 ) . toString ( " MM/dd/yyyy hh:mm:ss " ) ;
continue ; // break;
2021-01-26 02:56:59 +00:00
}
// Check if record belongs in this session or a future session
if ( ! inSession & & rec_ts1 < = sinfo - > end ) {
sess = sinfo - > sess ; // this is the Session we want
if ( ! inSession & & sess ) {
OA = sess - > AddEventList ( CPAP_Obstructive , EVL_Event ) ;
H = sess - > AddEventList ( CPAP_Hypopnea , EVL_Event ) ;
RE = sess - > AddEventList ( CPAP_RERA , EVL_Event ) ;
CA = sess - > AddEventList ( CPAP_ClearAirway , EVL_Event ) ;
PB = sess - > AddEventList ( CPAP_PB , EVL_Event ) ;
LL = sess - > AddEventList ( CPAP_LargeLeak , EVL_Event ) ;
EP = sess - > AddEventList ( CPAP_ExP , EVL_Event ) ;
2021-06-18 20:31:56 +00:00
SN = sess - > AddEventList ( INTP_SnoreFlag , EVL_Event ) ;
2021-01-26 02:56:59 +00:00
FL = sess - > AddEventList ( CPAP_FlowLimit , EVL_Event ) ;
2021-06-18 20:31:56 +00:00
// FLG = sess->AddEventList(CPAP_FLG, EVL_Waveform, 1.0f, 0.0f, 0.0f, 0.0f, double(2000) / double(2));
// SN = sess->AddEventList(CPAP_Snore, EVL_Waveform, 1.0f, 0.0f, 0.0f, 0.0f, double(2000) / double(2));
2021-01-26 02:56:59 +00:00
inSession = true ;
}
}
if ( inSession ) {
qint64 duration = rec_ts2 - rec_ts1 ;
// We make an ad hoc adjustment to the start time so that the event lines up better with the flow graph
// TODO: We don't know what is really going on here. Is it sloppiness on the part of the DV6 in recording time stamps?
qint64 ti = qint64 ( rec_ts1 - ( duration / 2 ) ) * 1000L ;
if ( duration < 0 ) {
2021-06-18 20:31:56 +00:00
qDebug ( ) < < " E.BIN at " < < QDateTime : : fromSecsSinceEpoch ( rec_ts1 ) . toString ( " MM/dd/yyyy hh:mm:ss " )
2021-01-26 02:56:59 +00:00
< < " reports duration of " < < duration
2021-06-18 20:31:56 +00:00
< < " ending " < < QDateTime : : fromSecsSinceEpoch ( rec_ts2 ) . toString ( " MM/dd/yyyy hh:mm:ss " ) ;
2021-01-26 02:56:59 +00:00
}
int code = rec - > event_type ;
2021-06-18 20:31:56 +00:00
/***
2021-01-26 02:56:59 +00:00
//////////////////////////////////////////////////////////////////
// Show Snore Events as a graph
//////////////////////////////////////////////////////////////////
if ( code = = 9 ) {
qint16 severity = rec - > event_severity ;
SN - > AddWaveform ( ti , & severity , 1 , duration * 1000 ) ;
}
2021-06-18 20:31:56 +00:00
//////////////////////////////////////////////////////////////////
// Show Flow Limit Events as a graph
//////////////////////////////////////////////////////////////////
if ( code = = 10 ) {
qint16 severity = rec - > event_severity ;
FLG - > AddWaveform ( ti , & severity , 1 , duration * 1000 ) ;
}
* * */
2021-01-26 02:56:59 +00:00
if ( rec - > event_severity > = 3 )
switch ( code ) {
case 1 :
CA - > AddEvent ( ti , duration ) ;
break ;
case 2 :
OA - > AddEvent ( ti , duration ) ;
2021-06-18 20:31:56 +00:00
// qDebug() << "E.BIN - OA" << QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << "duration" << duration << "r" << rf.recnum();
2021-01-26 02:56:59 +00:00
break ;
case 4 :
H - > AddEvent ( ti , duration ) ;
break ;
case 5 :
RE - > AddEvent ( ti , duration ) ;
2021-06-18 20:31:56 +00:00
// qDebug() << "E.BIN - RERA" << QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << "duration" << duration << "r" << rf.recnum();
2021-01-26 02:56:59 +00:00
break ;
case 8 : // snore
SN - > AddEvent ( ti , duration ) ;
2021-06-18 20:31:56 +00:00
// qDebug() << "E.BIN - Snore" << QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << "duration" << duration << "r" << rf.recnum();
2021-01-26 02:56:59 +00:00
break ;
case 9 : // expiratory puff
EP - > AddEvent ( ti , duration ) ;
2021-06-18 20:31:56 +00:00
// qDebug() << "E.BIN - exhale puff" << QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << "duration" << duration << "r" << rf.recnum();
2021-01-26 02:56:59 +00:00
break ;
case 10 : // flow limitation
FL - > AddEvent ( ti , duration ) ;
2021-06-18 20:31:56 +00:00
// qDebug() << "E.BIN - flow limit" << QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << "duration" << duration << "r" << rf.recnum();
2021-01-26 02:56:59 +00:00
break ;
case 11 : // periodic breathing
PB - > AddEvent ( ti , duration ) ;
break ;
case 12 : // large leaks
LL - > AddEvent ( ti , duration ) ;
2021-06-18 20:31:56 +00:00
// qDebug() << "E.BIN - large leak" << QDateTime::fromSecsSinceEpoch(rec_ts1).toString("MM/dd/yyyy hh:mm:ss") << "duration" << duration << "r" << rf.recnum();
2021-01-26 02:56:59 +00:00
break ;
case 13 : // pressure change
break ;
case 14 : // start of session
break ;
default :
break ;
2018-06-09 12:08:31 +00:00
}
2021-01-26 02:56:59 +00:00
}
} while ( true ) ;
rf . close ( ) ;
qDebug ( ) < < " DV6 E.BIN processed " < < rf . numread ( ) < < " records " ;
return true ;
}
////////////////////////////////////////////////////////////////////////////////////////
// Finalize data and add to database
////////////////////////////////////////////////////////////////////////////////////////
int addSessions ( ) {
for ( auto si = SessionData . begin ( ) , end = SessionData . end ( ) ; si ! = end ; + + si ) {
Session * sess = si . value ( ) . sess ;
if ( sess ) {
if ( ! mach - > AddSession ( sess ) ) {
qWarning ( ) < < " Session " < < sess - > session ( ) < < " was not addded " ;
}
# ifdef DEBUG6
else
2021-06-18 20:31:56 +00:00
qDebug ( ) < < " Added session " < < sess - > session ( ) < < QDateTime : : fromSecsSinceEpoch ( sess - > session ( ) ) . toString ( " MM/dd/yyyy hh:mm:ss " ) ; ;
2021-01-26 02:56:59 +00:00
# endif
// Update indexes, process waveform and perform flagging
sess - > UpdateSummaries ( ) ;
2018-06-09 12:08:31 +00:00
2021-01-26 02:56:59 +00:00
// Save is not threadsafe
sess - > Store ( mach - > getDataPath ( ) ) ;
// Unload them from memory
sess - > TrashEvents ( ) ;
2021-06-18 20:31:56 +00:00
} // else
// qWarning() << "addSessions: session pointer is null";
2021-01-26 02:56:59 +00:00
}
return SessionData . size ( ) ;
}
2018-06-09 12:08:31 +00:00
2021-01-26 02:56:59 +00:00
////////////////////////////////////////////////////////////////////////////////////////
// Create backup of input files
2021-04-02 04:39:06 +00:00
// Create dated backup of settings file if changed
2021-01-26 02:56:59 +00:00
////////////////////////////////////////////////////////////////////////////////////////
bool backup6 ( const QString & path ) {
2021-04-02 04:39:06 +00:00
if ( rebuild_from_backups | | ! create_backups )
2021-01-26 02:56:59 +00:00
return true ;
QDir ipath ( path ) ;
2021-04-02 04:39:06 +00:00
QDir cpath ( card_path ) ;
2021-01-26 02:56:59 +00:00
QDir bpath ( backup_path ) ;
2021-06-18 20:31:56 +00:00
QDir hpath ( history_path ) ;
2018-03-25 19:23:05 +00:00
2021-01-26 02:56:59 +00:00
// Copy input data to backup location
2021-07-08 17:38:16 +00:00
copyPath ( ipath . absolutePath ( ) , bpath . absolutePath ( ) , true ) ;
2018-03-25 19:23:05 +00:00
2021-01-26 02:56:59 +00:00
// Create archive of settings file if needed (SET.BIN)
bool backup_settings = true ;
QStringList filters ;
2021-04-02 04:39:06 +00:00
QFile settingsFile ;
QString inputFile = cpath . absolutePath ( ) + " /SET.BIN " ;
settingsFile . setFileName ( inputFile ) ;
filters < < " SET_*.BIN " ;
2021-01-26 02:56:59 +00:00
hpath . setNameFilters ( filters ) ;
hpath . setFilter ( QDir : : Files ) ;
2021-04-02 04:39:06 +00:00
hpath . setSorting ( QDir : : Name | QDir : : Reversed ) ;
2021-01-26 02:56:59 +00:00
QStringList fileNames = hpath . entryList ( ) ; // Get list of files
if ( ! fileNames . isEmpty ( ) ) {
QString lastFile = fileNames . first ( ) ;
2021-05-16 03:57:44 +00:00
qDebug ( ) < < " last settings file is " < < lastFile < < " new file is " < < settingsFile . fileName ( ) ;
2021-04-02 04:39:06 +00:00
QByteArray newMD5 = fileChecksum ( settingsFile . fileName ( ) , QCryptographicHash : : Md5 ) ;
QByteArray oldMD5 = fileChecksum ( hpath . absolutePath ( ) + " / " + lastFile , QCryptographicHash : : Md5 ) ;
2021-01-26 02:56:59 +00:00
if ( newMD5 = = oldMD5 )
backup_settings = false ;
}
2018-03-25 19:23:05 +00:00
2021-04-02 04:39:06 +00:00
if ( backup_settings & & ! DailySummaries . isEmpty ( ) ) {
DV6_S_Data ds = DailySummaries . last ( ) ;
QString newFile = hpath . absolutePath ( ) + " /SET_ " + getNominalDate ( ds . start_time ) . toString ( " yyyyMMdd " ) + " .BIN " ;
if ( ! settingsFile . copy ( inputFile , newFile ) ) {
qWarning ( ) < < " DV6 backup could not copy " < < inputFile < < " to " < < newFile < < " , error code " < < settingsFile . error ( ) < < settingsFile . errorString ( ) ;
}
2021-01-26 02:56:59 +00:00
}
2018-03-25 19:23:05 +00:00
2021-01-26 02:56:59 +00:00
// We're done!
return true ;
}
2018-03-25 19:23:05 +00:00
2021-04-02 04:39:06 +00:00
////////////////////////////////////////////////////////////////////////////////////////
// Initialize DV6 environment
////////////////////////////////////////////////////////////////////////////////////////
bool init6Environment ( const QString & path ) {
// Create Machine database record if it doesn't exist already
mach = p_profile - > CreateMachine ( info ) ;
if ( mach = = nullptr ) {
qWarning ( ) < < " Could not create DV6 Machine data structure " ;
return false ;
}
backup_path = mach - > getBackupPath ( ) ;
history_path = backup_path + " /HISTORY " ;
2021-06-18 20:31:56 +00:00
rebuild_path = backup_path + " /DV6 " ;
2021-04-02 04:39:06 +00:00
// Compare QDirs rather than QStrings because separators may be different, especially on Windows.
QDir ipath ( path ) ;
QDir bpath ( backup_path ) ;
2021-06-18 20:31:56 +00:00
QDir hpath ( history_path ) ;
2021-04-02 04:39:06 +00:00
if ( ipath = = bpath ) {
// Don't create backups if importing from backup folder
rebuild_from_backups = true ;
create_backups = false ;
} else {
rebuild_from_backups = false ;
create_backups = p_profile - > session - > backupCardData ( ) ;
2021-06-18 20:31:56 +00:00
if ( ! bpath . exists ( ) ) {
if ( ! bpath . mkpath ( backup_path ) ) {
qWarning ( ) < < " Could not create DV6 backup directory " < < backup_path ;
return false ;
}
}
if ( ! hpath . exists ( ) ) {
if ( ! hpath . mkpath ( history_path ) ) {
qWarning ( ) < < " Could not create DV6 backup HISTORY directory " < < history_path ;
return false ;
}
}
2021-04-02 04:39:06 +00:00
}
return true ;
}
2021-01-26 02:56:59 +00:00
////////////////////////////////////////////////////////////////////////////////////////
// Open a DV6 SD card, parse everything, add to OSCAR database
////////////////////////////////////////////////////////////////////////////////////////
2018-03-25 19:23:05 +00:00
2021-01-26 02:56:59 +00:00
int IntellipapLoader : : OpenDV6 ( const QString & path )
{
2021-04-09 04:05:54 +00:00
qDebug ( ) < < " DV6 loader started " ;
2021-04-02 04:39:06 +00:00
card_path = path + DV6_DIR ;
2021-01-26 02:56:59 +00:00
2021-06-18 20:31:56 +00:00
emit updateMessage ( QObject : : tr ( " Getting Ready... " ) ) ;
emit setProgressValue ( 0 ) ;
QCoreApplication : : processEvents ( ) ;
2021-04-02 04:39:06 +00:00
// 1. Prime the machine database's info field with this machine
2021-01-26 02:56:59 +00:00
info = newInfo ( ) ;
2021-04-02 04:39:06 +00:00
// 2. VER.BIN - Parse model number, serial, etc. into info structure
if ( ! load6VersionInfo ( card_path ) )
2021-01-26 02:56:59 +00:00
return - 1 ;
2021-04-02 04:39:06 +00:00
// 3. Initialize rest of the DV6 loader environment
if ( ! init6Environment ( path ) )
2021-01-26 02:56:59 +00:00
return - 1 ;
2018-03-25 19:23:05 +00:00
2021-04-02 04:39:06 +00:00
// 4. SET.BIN - Parse settings file (which is only the latest settings)
if ( ! load6Settings ( card_path ) )
2021-01-26 02:56:59 +00:00
return - 1 ;
2021-04-02 04:39:06 +00:00
// 5. S.BIN - Open and parse day summary list and create a list of days
if ( ! load6DailySummaries ( ) )
2021-01-26 02:56:59 +00:00
return - 1 ;
2021-06-18 20:31:56 +00:00
emit updateMessage ( QObject : : tr ( " Backing up files... " ) ) ;
QCoreApplication : : processEvents ( ) ;
2021-04-02 04:39:06 +00:00
// 6. Back up data files (must do after parsing VER.BIN, S.BIN, and creating Machine)
2021-01-26 02:56:59 +00:00
if ( ! backup6 ( path ) )
return - 1 ;
2018-03-25 19:23:05 +00:00
2021-06-18 20:31:56 +00:00
emit updateMessage ( QObject : : tr ( " Reading data files... " ) ) ;
QCoreApplication : : processEvents ( ) ;
2021-04-02 04:39:06 +00:00
// 7. U.BIN - Open and parse session list and create a list of session times
2021-01-26 02:56:59 +00:00
// (S.BIN must already be loaded)
2021-04-02 04:39:06 +00:00
if ( ! load6Sessions ( ) )
2021-01-26 02:56:59 +00:00
return - 1 ;
// Create OSCAR session list from session times and summary data
if ( create6Sessions ( ) < = 0 )
return - 1 ;
// R.BIN - Open and parse flow data
2021-04-02 04:39:06 +00:00
if ( ! load6HighResData ( ) )
2021-01-26 02:56:59 +00:00
return - 1 ;
// L.BIN - Open and parse per minute data
2021-04-02 04:39:06 +00:00
if ( ! load6PerMinute ( ) )
2021-01-26 02:56:59 +00:00
return - 1 ;
// E.BIN - Open and parse event data
2021-04-02 04:39:06 +00:00
if ( ! load6EventData ( ) )
2021-01-26 02:56:59 +00:00
return - 1 ;
2021-06-18 20:31:56 +00:00
emit updateMessage ( QObject : : tr ( " Finishing up... " ) ) ;
QCoreApplication : : processEvents ( ) ;
2021-01-26 02:56:59 +00:00
// Finalize input
return addSessions ( ) ;
2018-03-25 19:23:05 +00:00
}
2018-04-27 04:29:03 +00:00
int IntellipapLoader : : Open ( const QString & dirpath )
2018-03-25 19:23:05 +00:00
{
// Check for SL directory
// Check for DV5MFirm.bin?
2018-04-27 04:29:03 +00:00
QString path ( dirpath ) ;
2018-03-25 19:23:05 +00:00
path = path . replace ( " \\ " , " / " ) ;
if ( path . endsWith ( SL_DIR ) ) {
path . chop ( 3 ) ;
} else if ( path . endsWith ( DV6_DIR ) ) {
path . chop ( 4 ) ;
}
QDir dir ;
2018-03-26 11:51:43 +00:00
int r = - 1 ;
// Sometimes there can be an SL folder because SmartLink dumps an old DV5 firmware in it, so check it first
2018-03-25 19:23:05 +00:00
if ( dir . exists ( path + SL_DIR ) )
2018-03-26 11:51:43 +00:00
r = OpenDV5 ( path ) ;
2018-03-25 19:23:05 +00:00
2018-03-26 11:51:43 +00:00
if ( ( r < 0 ) & & dir . exists ( path + DV6_DIR ) )
r = OpenDV6 ( path ) ;
2018-03-25 19:23:05 +00:00
2018-03-26 11:51:43 +00:00
return r ;
2018-03-25 19:23:05 +00:00
}
2014-08-06 14:06:44 +00:00
void IntellipapLoader : : initChannels ( )
2011-11-20 02:59:00 +00:00
{
2014-08-03 13:00:13 +00:00
using namespace schema ;
Channel * chan = nullptr ;
2014-08-23 06:21:50 +00:00
channel . add ( GRP_CPAP , chan = new Channel ( INTP_SmartFlexMode = 0x1165 , SETTING , MT_CPAP , SESSION ,
2014-08-03 13:00:13 +00:00
" INTPSmartFlexMode " , QObject : : tr ( " SmartFlex Mode " ) ,
QObject : : tr ( " Intellipap pressure relief mode. " ) ,
QObject : : tr ( " SmartFlex Mode " ) ,
" " , DEFAULT , Qt : : green ) ) ;
chan - > addOption ( 0 , STR_TR_Off ) ;
chan - > addOption ( 1 , QObject : : tr ( " Ramp Only " ) ) ;
chan - > addOption ( 2 , QObject : : tr ( " Full Time " ) ) ;
2014-08-23 06:21:50 +00:00
channel . add ( GRP_CPAP , chan = new Channel ( INTP_SmartFlexLevel = 0x1169 , SETTING , MT_CPAP , SESSION ,
2014-08-03 13:00:13 +00:00
" INTPSmartFlexLevel " , QObject : : tr ( " SmartFlex Level " ) ,
QObject : : tr ( " Intellipap pressure relief level. " ) ,
QObject : : tr ( " SmartFlex Level " ) ,
" " , DEFAULT , Qt : : green ) ) ;
2021-06-18 20:31:56 +00:00
channel . add ( GRP_CPAP , new Channel ( INTP_SnoreFlag = 0xe301 , FLAG , MT_CPAP , SESSION ,
" INTP_SnoreFlag " ,
QObject : : tr ( " Snore " ) ,
QObject : : tr ( " Snoring event. " ) ,
QObject : : tr ( " SN " ) ,
STR_UNIT_EventsPerHour , DEFAULT , QColor ( " #e20004 " ) ) ) ;
2011-11-20 02:59:00 +00:00
}
2014-08-06 14:06:44 +00:00
bool intellipap_initialized = false ;
void IntellipapLoader : : Register ( )
{
2021-01-26 02:56:59 +00:00
if ( ! intellipap_initialized ) {
qDebug ( ) < < " Registering IntellipapLoader " ;
RegisterLoader ( new IntellipapLoader ( ) ) ;
//InitModelMap();
intellipap_initialized = true ;
}
return ;
2014-08-06 14:06:44 +00:00
}