2021-05-31 17:18:39 +00:00
/* SleepLib PRS1 Loader Parser Implementation
*
2021-11-02 20:34:12 +00:00
* Copyright ( c ) 2019 - 2022 The OSCAR Team
2021-05-31 17:18:39 +00:00
* Portions copyright ( c ) 2011 - 2018 Mark Watkins < mark @ jedimark . net >
*
* This file is subject to the terms and conditions of the GNU General Public
* License . See the file COPYING in the main directory of the source code
* for more details . */
# include "prs1_parser.h"
2021-06-01 01:23:03 +00:00
# include "prs1_loader.h"
2021-06-01 01:31:34 +00:00
# include "rawdata.h"
2021-05-31 17:18:39 +00:00
const PRS1ParsedEventType PRS1TidalVolumeEvent : : TYPE ;
const PRS1ParsedEventType PRS1SnoresAtPressureEvent : : TYPE ;
const PRS1ParsedEventType PRS1TimedBreathEvent : : TYPE ;
const PRS1ParsedEventType PRS1ObstructiveApneaEvent : : TYPE ;
const PRS1ParsedEventType PRS1ClearAirwayEvent : : TYPE ;
const PRS1ParsedEventType PRS1FlowLimitationEvent : : TYPE ;
const PRS1ParsedEventType PRS1PeriodicBreathingEvent : : TYPE ;
const PRS1ParsedEventType PRS1LargeLeakEvent : : TYPE ;
const PRS1ParsedEventType PRS1VariableBreathingEvent : : TYPE ;
const PRS1ParsedEventType PRS1HypopneaEvent : : TYPE ;
const PRS1ParsedEventType PRS1TotalLeakEvent : : TYPE ;
const PRS1ParsedEventType PRS1LeakEvent : : TYPE ;
const PRS1ParsedEventType PRS1AutoPressureSetEvent : : TYPE ;
const PRS1ParsedEventType PRS1PressureSetEvent : : TYPE ;
const PRS1ParsedEventType PRS1IPAPSetEvent : : TYPE ;
const PRS1ParsedEventType PRS1EPAPSetEvent : : TYPE ;
const PRS1ParsedEventType PRS1PressureAverageEvent : : TYPE ;
const PRS1ParsedEventType PRS1FlexPressureAverageEvent : : TYPE ;
const PRS1ParsedEventType PRS1IPAPAverageEvent : : TYPE ;
const PRS1ParsedEventType PRS1IPAPHighEvent : : TYPE ;
const PRS1ParsedEventType PRS1IPAPLowEvent : : TYPE ;
const PRS1ParsedEventType PRS1EPAPAverageEvent : : TYPE ;
const PRS1ParsedEventType PRS1RespiratoryRateEvent : : TYPE ;
const PRS1ParsedEventType PRS1PatientTriggeredBreathsEvent : : TYPE ;
const PRS1ParsedEventType PRS1MinuteVentilationEvent : : TYPE ;
const PRS1ParsedEventType PRS1SnoreEvent : : TYPE ;
const PRS1ParsedEventType PRS1VibratorySnoreEvent : : TYPE ;
const PRS1ParsedEventType PRS1PressurePulseEvent : : TYPE ;
const PRS1ParsedEventType PRS1RERAEvent : : TYPE ;
const PRS1ParsedEventType PRS1FlowRateEvent : : TYPE ;
const PRS1ParsedEventType PRS1Test1Event : : TYPE ;
const PRS1ParsedEventType PRS1Test2Event : : TYPE ;
const PRS1ParsedEventType PRS1HypopneaCount : : TYPE ;
const PRS1ParsedEventType PRS1ClearAirwayCount : : TYPE ;
const PRS1ParsedEventType PRS1ObstructiveApneaCount : : TYPE ;
//const PRS1ParsedEventType PRS1DisconnectAlarmEvent::TYPE;
const PRS1ParsedEventType PRS1ApneaAlarmEvent : : TYPE ;
//const PRS1ParsedEventType PRS1LowMinuteVentilationAlarmEvent::TYPE;
2021-06-01 01:23:03 +00:00
//********************************************************************************************
2021-05-31 17:18:39 +00:00
// MARK: Render parsed events as text
static QString hex ( int i )
{
return QString ( " 0x " ) + QString : : number ( i , 16 ) . toUpper ( ) ;
}
# define ENUMSTRING(ENUM) case ENUM: s = QStringLiteral(#ENUM); break
2021-05-31 18:53:23 +00:00
QString PRS1ParsedEvent : : typeName ( ) const
2021-05-31 17:18:39 +00:00
{
2021-05-31 18:53:23 +00:00
PRS1ParsedEventType t = m_type ;
2021-05-31 17:18:39 +00:00
QString s ;
switch ( t ) {
ENUMSTRING ( EV_PRS1_RAW ) ;
ENUMSTRING ( EV_PRS1_UNKNOWN ) ;
ENUMSTRING ( EV_PRS1_TB ) ;
ENUMSTRING ( EV_PRS1_OA ) ;
ENUMSTRING ( EV_PRS1_CA ) ;
ENUMSTRING ( EV_PRS1_FL ) ;
ENUMSTRING ( EV_PRS1_PB ) ;
ENUMSTRING ( EV_PRS1_LL ) ;
ENUMSTRING ( EV_PRS1_VB ) ;
ENUMSTRING ( EV_PRS1_HY ) ;
ENUMSTRING ( EV_PRS1_OA_COUNT ) ;
ENUMSTRING ( EV_PRS1_CA_COUNT ) ;
ENUMSTRING ( EV_PRS1_HY_COUNT ) ;
ENUMSTRING ( EV_PRS1_TOTLEAK ) ;
ENUMSTRING ( EV_PRS1_LEAK ) ;
ENUMSTRING ( EV_PRS1_AUTO_PRESSURE_SET ) ;
ENUMSTRING ( EV_PRS1_PRESSURE_SET ) ;
ENUMSTRING ( EV_PRS1_IPAP_SET ) ;
ENUMSTRING ( EV_PRS1_EPAP_SET ) ;
ENUMSTRING ( EV_PRS1_PRESSURE_AVG ) ;
ENUMSTRING ( EV_PRS1_FLEX_PRESSURE_AVG ) ;
ENUMSTRING ( EV_PRS1_IPAP_AVG ) ;
ENUMSTRING ( EV_PRS1_IPAPLOW ) ;
ENUMSTRING ( EV_PRS1_IPAPHIGH ) ;
ENUMSTRING ( EV_PRS1_EPAP_AVG ) ;
ENUMSTRING ( EV_PRS1_RR ) ;
ENUMSTRING ( EV_PRS1_PTB ) ;
ENUMSTRING ( EV_PRS1_MV ) ;
ENUMSTRING ( EV_PRS1_TV ) ;
ENUMSTRING ( EV_PRS1_SNORE ) ;
ENUMSTRING ( EV_PRS1_VS ) ;
ENUMSTRING ( EV_PRS1_PP ) ;
ENUMSTRING ( EV_PRS1_RERA ) ;
ENUMSTRING ( EV_PRS1_FLOWRATE ) ;
ENUMSTRING ( EV_PRS1_TEST1 ) ;
ENUMSTRING ( EV_PRS1_TEST2 ) ;
ENUMSTRING ( EV_PRS1_SETTING ) ;
ENUMSTRING ( EV_PRS1_SLICE ) ;
ENUMSTRING ( EV_PRS1_DISCONNECT_ALARM ) ;
ENUMSTRING ( EV_PRS1_APNEA_ALARM ) ;
ENUMSTRING ( EV_PRS1_LOW_MV_ALARM ) ;
ENUMSTRING ( EV_PRS1_SNORES_AT_PRESSURE ) ;
ENUMSTRING ( EV_PRS1_INTERVAL_BOUNDARY ) ;
default :
s = hex ( t ) ;
qDebug ( ) < < " Unknown PRS1ParsedEventType type: " < < qPrintable ( s ) ;
return s ;
}
return s . mid ( 8 ) . toLower ( ) ; // lop off initial EV_PRS1_
}
2021-05-31 18:53:23 +00:00
QString PRS1ParsedSettingEvent : : settingName ( ) const
2021-05-31 17:18:39 +00:00
{
2021-05-31 18:53:23 +00:00
PRS1ParsedSettingType t = m_setting ;
2021-05-31 17:18:39 +00:00
QString s ;
switch ( t ) {
ENUMSTRING ( PRS1_SETTING_CPAP_MODE ) ;
ENUMSTRING ( PRS1_SETTING_AUTO_TRIAL ) ;
ENUMSTRING ( PRS1_SETTING_PRESSURE ) ;
ENUMSTRING ( PRS1_SETTING_PRESSURE_MIN ) ;
ENUMSTRING ( PRS1_SETTING_PRESSURE_MAX ) ;
ENUMSTRING ( PRS1_SETTING_EPAP ) ;
ENUMSTRING ( PRS1_SETTING_EPAP_MIN ) ;
ENUMSTRING ( PRS1_SETTING_EPAP_MAX ) ;
ENUMSTRING ( PRS1_SETTING_IPAP ) ;
ENUMSTRING ( PRS1_SETTING_IPAP_MIN ) ;
ENUMSTRING ( PRS1_SETTING_IPAP_MAX ) ;
ENUMSTRING ( PRS1_SETTING_PS ) ;
ENUMSTRING ( PRS1_SETTING_PS_MIN ) ;
ENUMSTRING ( PRS1_SETTING_PS_MAX ) ;
ENUMSTRING ( PRS1_SETTING_BACKUP_BREATH_MODE ) ;
ENUMSTRING ( PRS1_SETTING_BACKUP_BREATH_RATE ) ;
ENUMSTRING ( PRS1_SETTING_BACKUP_TIMED_INSPIRATION ) ;
ENUMSTRING ( PRS1_SETTING_TIDAL_VOLUME ) ;
ENUMSTRING ( PRS1_SETTING_EZ_START ) ;
ENUMSTRING ( PRS1_SETTING_FLEX_LOCK ) ;
ENUMSTRING ( PRS1_SETTING_FLEX_MODE ) ;
ENUMSTRING ( PRS1_SETTING_FLEX_LEVEL ) ;
ENUMSTRING ( PRS1_SETTING_RISE_TIME ) ;
ENUMSTRING ( PRS1_SETTING_RISE_TIME_LOCK ) ;
ENUMSTRING ( PRS1_SETTING_RAMP_TYPE ) ;
ENUMSTRING ( PRS1_SETTING_RAMP_TIME ) ;
ENUMSTRING ( PRS1_SETTING_RAMP_PRESSURE ) ;
ENUMSTRING ( PRS1_SETTING_HUMID_STATUS ) ;
ENUMSTRING ( PRS1_SETTING_HUMID_MODE ) ;
ENUMSTRING ( PRS1_SETTING_HEATED_TUBE_TEMP ) ;
ENUMSTRING ( PRS1_SETTING_HUMID_LEVEL ) ;
ENUMSTRING ( PRS1_SETTING_HUMID_TARGET_TIME ) ;
ENUMSTRING ( PRS1_SETTING_MASK_RESIST_LOCK ) ;
ENUMSTRING ( PRS1_SETTING_MASK_RESIST_SETTING ) ;
ENUMSTRING ( PRS1_SETTING_HOSE_DIAMETER ) ;
ENUMSTRING ( PRS1_SETTING_TUBING_LOCK ) ;
ENUMSTRING ( PRS1_SETTING_AUTO_ON ) ;
ENUMSTRING ( PRS1_SETTING_AUTO_OFF ) ;
ENUMSTRING ( PRS1_SETTING_APNEA_ALARM ) ;
ENUMSTRING ( PRS1_SETTING_DISCONNECT_ALARM ) ;
ENUMSTRING ( PRS1_SETTING_LOW_MV_ALARM ) ;
ENUMSTRING ( PRS1_SETTING_LOW_TV_ALARM ) ;
ENUMSTRING ( PRS1_SETTING_MASK_ALERT ) ;
ENUMSTRING ( PRS1_SETTING_SHOW_AHI ) ;
default :
s = hex ( t ) ;
qDebug ( ) < < " Unknown PRS1ParsedSettingType type: " < < qPrintable ( s ) ;
return s ;
}
return s . mid ( 13 ) . toLower ( ) ; // lop off initial PRS1_SETTING_
}
2021-05-31 18:53:23 +00:00
QString PRS1ParsedSettingEvent : : modeName ( ) const
2021-05-31 17:18:39 +00:00
{
2021-05-31 18:53:23 +00:00
int m = value ( ) ;
2021-05-31 17:18:39 +00:00
QString s ;
switch ( ( PRS1Mode ) m ) {
ENUMSTRING ( PRS1_MODE_UNKNOWN ) ; // TODO: Remove this when all the parsers are complete.
ENUMSTRING ( PRS1_MODE_CPAP ) ;
ENUMSTRING ( PRS1_MODE_CPAPCHECK ) ;
ENUMSTRING ( PRS1_MODE_AUTOTRIAL ) ;
ENUMSTRING ( PRS1_MODE_AUTOCPAP ) ;
ENUMSTRING ( PRS1_MODE_BILEVEL ) ;
ENUMSTRING ( PRS1_MODE_AUTOBILEVEL ) ;
ENUMSTRING ( PRS1_MODE_ASV ) ;
ENUMSTRING ( PRS1_MODE_S ) ;
ENUMSTRING ( PRS1_MODE_ST ) ;
ENUMSTRING ( PRS1_MODE_PC ) ;
ENUMSTRING ( PRS1_MODE_ST_AVAPS ) ;
ENUMSTRING ( PRS1_MODE_PC_AVAPS ) ;
default :
s = hex ( m ) ;
qDebug ( ) < < " Unknown PRS1Mode: " < < qPrintable ( s ) ;
return s ;
}
return s . mid ( 10 ) . toLower ( ) ; // lop off initial PRS1_MODE_
}
2021-05-31 18:53:23 +00:00
QString PRS1ParsedEvent : : timeStr ( int t )
2021-05-31 17:18:39 +00:00
{
int h = t / 3600 ;
int m = ( t - ( h * 3600 ) ) / 60 ;
int s = t % 60 ;
# if 1
// Optimized after profiling regression tests.
return QString : : asprintf ( " %02d:%02d:%02d " , h , m , s ) ;
# else
// Unoptimized original, slows down regression tests.
return QString ( " %1:%2:%3 " ) . arg ( h , 2 , 10 , QChar ( ' 0 ' ) ) . arg ( m , 2 , 10 , QChar ( ' 0 ' ) ) . arg ( s , 2 , 10 , QChar ( ' 0 ' ) ) ;
# endif
}
static QString byteList ( QByteArray data , int limit = - 1 )
{
int count = data . size ( ) ;
if ( limit = = - 1 | | limit > count ) limit = count ;
QStringList l ;
for ( int i = 0 ; i < limit ; i + + ) {
l . push_back ( QString ( " %1 " ) . arg ( ( int ) data [ i ] & 0xFF , 2 , 16 , QChar ( ' 0 ' ) ) . toUpper ( ) ) ;
}
if ( limit < count ) l . push_back ( " ... " ) ;
QString s = l . join ( " " ) ;
return s ;
}
QMap < QString , QString > PRS1IntervalBoundaryEvent : : contents ( void )
{
QMap < QString , QString > out ;
out [ " start " ] = timeStr ( m_start ) ;
return out ;
}
QMap < QString , QString > PRS1ParsedDurationEvent : : contents ( void )
{
QMap < QString , QString > out ;
out [ " start " ] = timeStr ( m_start ) ;
out [ " duration " ] = timeStr ( m_duration ) ;
return out ;
}
QMap < QString , QString > PRS1ParsedValueEvent : : contents ( void )
{
QMap < QString , QString > out ;
out [ " start " ] = timeStr ( m_start ) ;
out [ " value " ] = QString : : number ( value ( ) ) ;
return out ;
}
QMap < QString , QString > PRS1UnknownDataEvent : : contents ( void )
{
QMap < QString , QString > out ;
out [ " pos " ] = QString : : number ( m_pos ) ;
out [ " data " ] = byteList ( m_data ) ;
return out ;
}
QMap < QString , QString > PRS1ParsedSettingEvent : : contents ( void )
{
QMap < QString , QString > out ;
QString v ;
if ( m_setting = = PRS1_SETTING_CPAP_MODE ) {
2021-05-31 18:53:23 +00:00
v = modeName ( ) ;
2021-05-31 17:18:39 +00:00
} else {
v = QString : : number ( value ( ) ) ;
}
2021-05-31 18:53:23 +00:00
out [ settingName ( ) ] = v ;
2021-05-31 17:18:39 +00:00
return out ;
}
QMap < QString , QString > PRS1ParsedSliceEvent : : contents ( void )
{
QMap < QString , QString > out ;
out [ " start " ] = timeStr ( m_start ) ;
QString s ;
switch ( ( SliceStatus ) m_value ) {
case MaskOn : s = " MaskOn " ; break ;
case MaskOff : s = " MaskOff " ; break ;
case EquipmentOff : s = " EquipmentOff " ; break ;
case UnknownStatus : s = " Unknown " ; break ;
}
out [ " status " ] = s ;
return out ;
}
QMap < QString , QString > PRS1ParsedAlarmEvent : : contents ( void )
{
QMap < QString , QString > out ;
out [ " start " ] = timeStr ( m_start ) ;
return out ;
}
QMap < QString , QString > PRS1SnoresAtPressureEvent : : contents ( void )
{
QString label ;
switch ( m_kind ) {
case 0 : label = " pressure " ; break ;
case 1 : label = " epap " ; break ;
case 2 : label = " ipap " ; break ;
default : label = " unknown_pressure " ; break ;
}
QMap < QString , QString > out ;
out [ " start " ] = timeStr ( m_start ) ;
out [ label ] = QString : : number ( value ( ) ) ;
out [ " count " ] = QString : : number ( m_count ) ;
return out ;
}
2021-06-01 00:24:09 +00:00
2021-06-01 01:23:03 +00:00
//********************************************************************************************
2021-06-01 00:24:09 +00:00
// MARK: -
2021-06-01 01:31:34 +00:00
// MARK: Parse chunk contents
2021-06-01 01:23:03 +00:00
bool PRS1DataChunk : : ParseCompliance ( void )
{
switch ( this - > family ) {
case 0 :
switch ( this - > familyVersion ) {
case 2 :
case 3 :
return this - > ParseComplianceF0V23 ( ) ;
case 4 :
return this - > ParseComplianceF0V4 ( ) ;
case 5 :
return this - > ParseComplianceF0V5 ( ) ;
case 6 :
return this - > ParseComplianceF0V6 ( ) ;
}
default :
;
}
qWarning ( ) < < " unexpected compliance family " < < this - > family < < " familyVersion " < < this - > familyVersion ;
return false ;
}
bool PRS1DataChunk : : ParseSummary ( )
{
switch ( this - > family ) {
case 0 :
if ( this - > familyVersion = = 6 ) {
return this - > ParseSummaryF0V6 ( ) ;
} else if ( this - > familyVersion = = 4 ) {
return this - > ParseSummaryF0V4 ( ) ;
} else {
return this - > ParseSummaryF0V23 ( ) ;
}
case 3 :
switch ( this - > familyVersion ) {
case 0 : return this - > ParseSummaryF3V03 ( ) ;
case 3 : return this - > ParseSummaryF3V03 ( ) ;
case 6 : return this - > ParseSummaryF3V6 ( ) ;
}
break ;
case 5 :
if ( this - > familyVersion = = 1 ) {
return this - > ParseSummaryF5V012 ( ) ;
} else if ( this - > familyVersion = = 0 ) {
return this - > ParseSummaryF5V012 ( ) ;
} else if ( this - > familyVersion = = 2 ) {
return this - > ParseSummaryF5V012 ( ) ;
} else if ( this - > familyVersion = = 3 ) {
return this - > ParseSummaryF5V3 ( ) ;
}
default :
;
}
qWarning ( ) < < " unexpected family " < < this - > family < < " familyVersion " < < this - > familyVersion ;
return false ;
}
// TODO: The nested switch statement below just begs for per-version subclasses.
bool PRS1DataChunk : : ParseEvents ( )
{
bool ok = false ;
switch ( this - > family ) {
case 0 :
switch ( this - > familyVersion ) {
case 2 : ok = this - > ParseEventsF0V23 ( ) ; break ;
case 3 : ok = this - > ParseEventsF0V23 ( ) ; break ;
case 4 : ok = this - > ParseEventsF0V4 ( ) ; break ;
case 6 : ok = this - > ParseEventsF0V6 ( ) ; break ;
}
break ;
case 3 :
switch ( this - > familyVersion ) {
case 0 : ok = this - > ParseEventsF3V03 ( ) ; break ;
case 3 : ok = this - > ParseEventsF3V03 ( ) ; break ;
case 6 : ok = this - > ParseEventsF3V6 ( ) ; break ;
}
break ;
case 5 :
switch ( this - > familyVersion ) {
case 0 : ok = this - > ParseEventsF5V0 ( ) ; break ;
case 1 : ok = this - > ParseEventsF5V1 ( ) ; break ;
case 2 : ok = this - > ParseEventsF5V2 ( ) ; break ;
case 3 : ok = this - > ParseEventsF5V3 ( ) ; break ;
}
break ;
default :
qDebug ( ) < < " Unknown PRS1 family " < < this - > family < < " familyVersion " < < this - > familyVersion ;
}
return ok ;
}
// TODO: This really should be in some kind of class hierarchy, once we figure out
// the right one.
const QVector < PRS1ParsedEventType > & GetSupportedEvents ( const PRS1DataChunk * chunk )
{
static const QVector < PRS1ParsedEventType > none ;
switch ( chunk - > family ) {
case 0 :
switch ( chunk - > familyVersion ) {
case 2 : return ParsedEventsF0V23 ; break ;
case 3 : return ParsedEventsF0V23 ; break ;
case 4 : return ParsedEventsF0V4 ; break ;
case 6 : return ParsedEventsF0V6 ; break ;
}
break ;
case 3 :
switch ( chunk - > familyVersion ) {
case 0 : return ParsedEventsF3V0 ; break ;
case 3 : return ParsedEventsF3V3 ; break ;
case 6 : return ParsedEventsF3V6 ; break ;
}
break ;
case 5 :
switch ( chunk - > familyVersion ) {
case 0 : return ParsedEventsF5V0 ; break ;
case 1 : return ParsedEventsF5V1 ; break ;
case 2 : return ParsedEventsF5V2 ; break ;
case 3 : return ParsedEventsF5V3 ; break ;
}
break ;
}
qWarning ( ) < < " Missing supported event list for family " < < chunk - > family < < " version " < < chunk - > familyVersion ;
return none ;
}
2021-06-01 00:24:09 +00:00
QString PRS1DataChunk : : DumpEvent ( int t , int code , const unsigned char * data , int size )
{
int s = t ;
int h = s / 3600 ; s - = h * 3600 ;
int m = s / 60 ; s - = m * 60 ;
QString dump = QString ( " %1:%2:%3 " )
. arg ( h , 2 , 10 , QChar ( ' 0 ' ) )
. arg ( m , 2 , 10 , QChar ( ' 0 ' ) )
. arg ( s , 2 , 10 , QChar ( ' 0 ' ) ) ;
dump = dump + " " + hex ( code ) + " : " ;
for ( int i = 0 ; i < size ; i + + ) {
dump = dump + QString ( " %1 " ) . arg ( data [ i ] ) ;
}
return dump ;
}
void PRS1DataChunk : : AddEvent ( PRS1ParsedEvent * const event )
{
m_parsedData . push_back ( event ) ;
}
2021-06-01 01:23:03 +00:00
//********************************************************************************************
// MARK: -
// MARK: Parse settings shared by multiple families
// Humid F0V2 confirmed
// 0x00 = Off (presumably no humidifier present)
// 0x80 = Off
// 0x81 = 1
// 0x82 = 2
// 0x83 = 3
// 0x84 = 4
// 0x85 = 5
// Humid F3V0 confirmed
// 0x03 = 3 (but no humidification shown on hours of usage chart)
// 0x04 = 4 (but no humidification shown on hours of usage chart)
// 0x80 = Off
// 0x81 = 1
// 0x82 = 2
// 0x83 = 3
// 0x84 = 4
// 0x85 = 5
// Humid F5V0 confirmed
// 0x00 = Off (presumably no humidifier present)
// 0x80 = Off
// 0x81 = 1, bypass = no
// 0x82 = 2, bypass = no
// 0x83 = 3, bypass = no
// 0x84 = 4, bypass = no
// 0x85 = 5, bypass = no
// 0xA0 = Off, bypass = yes
void PRS1DataChunk : : ParseHumidifierSetting50Series ( int humid , bool add_setting )
{
if ( humid & ( 0x40 | 0x10 | 0x08 ) ) UNEXPECTED_VALUE ( humid , " known bits " ) ;
if ( humid & 0x20 ) {
if ( this - > family = = 5 ) {
CHECK_VALUE ( humid , 0xA0 ) ; // only example of bypass set, unsure whether it can appear otherwise
} else {
CHECK_VALUE ( humid & 0x20 , 0 ) ; // only ever seen on 950P, where "Bypass System One humidification" is "Yes"
}
}
bool humidifier_present = ( ( humid & 0x80 ) ! = 0 ) ; // humidifier connected
int humidlevel = humid & 7 ; // humidification level
HumidMode humidmode = HUMID_Fixed ; // 50-Series didn't have adaptive or heated tube humidification
if ( add_setting ) {
this - > AddEvent ( new PRS1ParsedSettingEvent ( PRS1_SETTING_HUMID_STATUS , humidifier_present ) ) ;
if ( humidifier_present ) {
this - > AddEvent ( new PRS1ParsedSettingEvent ( PRS1_SETTING_HUMID_MODE , humidmode ) ) ;
this - > AddEvent ( new PRS1ParsedSettingEvent ( PRS1_SETTING_HUMID_LEVEL , humidlevel ) ) ;
}
}
// Check for truly unexpected values:
if ( humidlevel > 5 ) UNEXPECTED_VALUE ( humidlevel , " <= 5 " ) ;
2022-02-27 16:50:10 +00:00
//if (!humidifier_present) CHECK_VALUES(humidlevel, 0, 1); // Some devices appear to encode the humidlevel setting even when the humidifier is not present.
2021-06-01 01:23:03 +00:00
}
// F0V4 confirmed:
// B3 0A = HT=5, H=3, HT
// A3 0A = HT=5, H=2, HT
// 33 0A = HT=4, H=3, HT
// 23 4A = HT=4, H=2, HT
// B3 09 = HT=3, H=3, HT
// A4 09 = HT=3, H=2, HT
// A3 49 = HT=3, H=2, HT
// 22 09 = HT=2, H=2, HT
// 33 09 = HT=2, H=3, HT
// 21 09 = HT=2, H=2, HT
// 13 09 = HT=2, H=1, HT
// B5 08 = HT=1, H=3, HT
// 03 08 = HT=off, HT; data=tube t=0,h=0
// 05 24 = H=5, S1
// 95 06 = H=5, S1
// 95 05 = H=5, S1
// 94 05 = H=4, S1
// 04 24 = H=4, S1
// A3 05 = H=3, S1
// 92 05 = H=2, S1
// A2 05 = H=2, S1
// 01 24 = H=1, S1
// 90 05 = H=off, S1
// 30 05 = H=off, S1
// 95 41 = H=5, Classic
// A4 61 = H=4, Classic
// A3 61 = H=3, Classic
// A2 61 = H=2, Classic
// A1 61 = H=1, Classic
// 90 41 = H=Off, Classic; data=classic h=0
// 94 11 = H=3, S1, no data [note that bits encode H=4, so no data falls back to H=3]
// 93 11 = H=3, S1, no data
// 04 30 = H=3, S1, no data
// F0V5 confirmed:
// 00 60 = H=Off, Classic
// 02 60 = H=2, Classic
// 05 60 = H=5, Classic
// 00 70 = H=Off, no data in chart
// F5V1 confirmed:
// A0 4A = HT=5, H=2, HT
// B1 09 = HT=3, H=3, HT
// 91 09 = HT=3, H=1, HT
// 32 09 = HT=2, H=3, HT
// B2 08 = HT=1, H=3, HT
// 00 48 = HT=off, data=tube t=0,h=0
// 95 05 = H=5, S1
// 94 05 = H=4, S1
// 93 05 = H=3, S1
// 92 05 = H=2, S1
// 91 05 = H=1, S1
// 90 05 = H=Off, S1
// 95 41 = H=5, Classic
// 94 41 = H=4, Classic
// 93 41 = H=3, Classic
// 92 41 = H=2, Classic
// 01 60 = H=1, Classic
// 00 60 = H=Off, Classic
// 00 70 = H=3, S1, no data [no data ignores Classic mode, H bits, falls back to S1 H=3]
// F5V2 confirmed:
// 00 48 = HT=off, data=tube t=0,h=0
// 93 09 = HT=3, H=1, HT
// 00 10 = H=3, S1, no data
// XX XX = 60-Series Humidifier bytes
// 7 = humidity level without tube [on tube disconnect / system one with 22mm hose / classic] : 0 = humidifier off
// 8 = [never seen]
// 3 = humidity level with tube
// 4 = maybe part of humidity level? [never seen]
// 8 3 = tube temperature (high bit of humid 1 is low bit of temp)
// 4 = "System One" mode (valid even when humidifier is off)
// 8 = heated tube present
// 10 = no data in chart, maybe no humidifier attached? Seems to fall back on System One = 3 despite other (humidity level and S1) bits.
// 20 = unknown, something tube related since whenever it's set tubepresent is false
// 40 = "Classic" mode (valid even when humidifier is off, ignored when heated tube is present)
// 80 = [never seen]
void PRS1DataChunk : : ParseHumidifierSetting60Series ( unsigned char humid1 , unsigned char humid2 , bool add_setting )
{
int humidlevel = humid1 & 7 ; // Ignored when heated tube is present: humidifier setting on tube disconnect is always reported as 3
if ( humidlevel > 5 ) UNEXPECTED_VALUE ( humidlevel , " <= 5 " ) ;
CHECK_VALUE ( humid1 & 8 , 0 ) ; // never seen
int tubehumidlevel = ( humid1 > > 4 ) & 7 ; // This mask is a best guess based on other masks.
if ( tubehumidlevel > 5 ) UNEXPECTED_VALUE ( tubehumidlevel , " <= 5 " ) ;
CHECK_VALUE ( tubehumidlevel & 4 , 0 ) ; // never seen, but would clarify whether above mask is correct
int tubetemp = ( humid1 > > 7 ) | ( ( humid2 & 3 ) < < 1 ) ;
if ( tubetemp > 5 ) UNEXPECTED_VALUE ( tubetemp , " <= 5 " ) ;
CHECK_VALUE ( humid2 & 0x80 , 0 ) ; // never seen
bool humidclassic = ( humid2 & 0x40 ) ! = 0 ; // Set on classic mode reports; evidently ignored (sometimes set!) when tube is present
//bool no_tube? = (humid2 & 0x20) != 0; // Something tube related: whenever it is set, tube is never present (inverse is not true)
bool no_data = ( humid2 & 0x10 ) ! = 0 ; // As described in chart, settings still show up
int tubepresent = ( humid2 & 0x08 ) ! = 0 ;
bool humidsystemone = ( humid2 & 0x04 ) ! = 0 ; // Set on "System One" humidification mode reports when tubepresent is false
if ( humidsystemone & & tubepresent ) {
// On a 560P, we've observed a spurious tubepresent bit being set during two sessions.
// Those sessions (and the ones that followed) used a 22mm hose.
CHECK_VALUE ( add_setting , false ) ; // We've only seen this appear during a session, not in the initial settings.
tubepresent = false ;
}
// When no_data, reports always say "System One" with humidity level 3, regardless of humidlevel and humidsystemone
if ( humidsystemone + tubepresent + no_data = = 0 ) CHECK_VALUE ( humidclassic , true ) ; // Always set when everything else is off
2021-12-14 22:14:47 +00:00
if ( no_data & & humidsystemone & & add_setting = = false ) {
// This has been seen once on a 560P in a session that also generated a file in the error directory.
qWarning ( ) < < this - > sessionid < < " Humidification error during session? " ;
} else {
if ( humidsystemone + tubepresent + no_data > 1 ) UNEXPECTED_VALUE ( humid2 , " one bit set " ) ; // Only one of these ever seems to be set at a time
}
2021-06-01 01:23:03 +00:00
if ( tubepresent & & tubetemp = = 0 ) CHECK_VALUE ( tubehumidlevel , 0 ) ; // When the heated tube is off, tube humidity seems to be 0
if ( tubepresent ) humidclassic = false ; // Classic mode bit is evidently ignored when tube is present
if ( no_data ) humidclassic = false ; // Classic mode bit is evidently ignored when tube is present
//qWarning() << this->sessionid << (humidclassic ? "C" : ".") << (humid2 & 0x20 ? "?" : ".") << (tubepresent ? "T" : ".") << (no_data ? "X" : ".") << (humidsystemone ? "1" : ".");
/*
if ( tubepresent ) {
if ( tubetemp ) {
qWarning ( ) < < this - > sessionid < < " tube temp " < < tubetemp < < " tube humidity " < < tubehumidlevel < < ( humidclassic ? " classic " : " systemone " ) < < " humidity " < < humidlevel ;
} else {
qWarning ( ) < < this - > sessionid < < " heated tube off " < < ( humidclassic ? " classic " : " systemone " ) < < " humidity " < < humidlevel ;
}
} else {
qWarning ( ) < < this - > sessionid < < ( humidclassic ? " classic " : " systemone " ) < < " humidity " < < humidlevel ;
}
*/
HumidMode humidmode = HUMID_Fixed ;
if ( tubepresent ) {
humidmode = HUMID_HeatedTube ;
} else {
if ( humidsystemone + humidclassic > 1 ) UNEXPECTED_VALUE ( humid2 , " fixed or adaptive " ) ;
if ( humidsystemone ) humidmode = HUMID_Adaptive ;
}
if ( add_setting ) {
bool humidifier_present = ( no_data = = 0 ) ;
this - > AddEvent ( new PRS1ParsedSettingEvent ( PRS1_SETTING_HUMID_STATUS , humidifier_present ) ) ;
if ( humidifier_present ) {
this - > AddEvent ( new PRS1ParsedSettingEvent ( PRS1_SETTING_HUMID_MODE , humidmode ) ) ;
if ( humidmode = = HUMID_HeatedTube ) {
this - > AddEvent ( new PRS1ParsedSettingEvent ( PRS1_SETTING_HEATED_TUBE_TEMP , tubetemp ) ) ;
this - > AddEvent ( new PRS1ParsedSettingEvent ( PRS1_SETTING_HUMID_LEVEL , tubehumidlevel ) ) ;
} else {
this - > AddEvent ( new PRS1ParsedSettingEvent ( PRS1_SETTING_HUMID_LEVEL , humidlevel ) ) ;
}
}
}
// Check for previously unseen data that we expect to be normal:
if ( this - > family = = 0 ) {
// F0V4
if ( tubetemp & & ( tubehumidlevel < 1 | | tubehumidlevel > 3 ) ) UNEXPECTED_VALUE ( tubehumidlevel , " 1-3 " ) ;
} else if ( this - > familyVersion = = 1 ) {
// F5V1
if ( tubepresent ) {
// all tube temperatures seen
if ( tubetemp ) {
if ( tubehumidlevel = = 0 | | tubehumidlevel > 3 ) UNEXPECTED_VALUE ( tubehumidlevel , " 1-3 " ) ;
}
}
} else if ( this - > familyVersion = = 2 ) {
// F5V2
if ( tubepresent ) {
2022-04-24 21:02:25 +00:00
// all tube temperatures seen
2021-06-01 01:23:03 +00:00
if ( tubetemp ) {
2022-04-24 21:02:25 +00:00
CHECK_VALUES ( tubehumidlevel , 1 , 3 ) ;
2021-06-01 01:23:03 +00:00
}
}
CHECK_VALUE ( humidclassic , false ) ;
}
}
// F0V6 confirmed
// 90 B0 = HT=3!,H=3!,data=none [no humidifier appears to ignore HT and H bits and show HT=3,H=3 in details]
// 8C 6C = HT=3, H=3, data=none
// 80 00 = nothing listed in details, data=none, only seen on 400G and 502G
// 54 B4 = HT=5, H=5, data=tube
// 50 90 = HT=4, H=4, data=tube
// 4C 6C = HT=3, H=3, data=tube
// 48 68 = HT=3, H=2, data=tube
// 40 60 = HT=3, H=Off, data=tube t=3,h=0
// 50 50 = HT=2, H=4, data=tube
// 4C 4C = HT=2, H=3, data=tube
// 50 30 = HT=1, H=4, data=tube
// 4C 0C = HT=off, H=3, data=tube t=0,h=3
// 34 74 = HT=3, H=5, data=adaptive (5)
// 50 B0 = HT=5, H=4, adaptive
// 30 B0 = HT=3, H=4, data=adaptive (4)
// 30 50 = HT=3, H=4, data=adaptive (4)
// 30 10 = HT=3!,H=4, data=adaptive (4) [adaptive mode appears to ignore HT bits and show HT=3 in details]
// 30 70 = HT=3, H=4, data=adaptive (4)
// 2C 6C = HT=3, H=3, data=adaptive (3)
// 28 08 = H=2, data=adaptive (2), no details (400G)
// 28 48 = HT=3!,H=2, data=adaptive (2) [adaptive mode appears to ignore HT bits and show HT=3 in details]
// 28 68 = HT=3, H=2, data=adaptive (2)
// 24 64 = HT=3, H=1, data=adaptive (1)
// 20 60 = HT=3, H=off, data=adaptive (0)
// 14 74 = HT=3, H=5, data=fixed (5)
// 10 70 = HT=3, H=4, data=fixed (4)
// 0C 6C = HT=3, H=3, data=fixed (3)
// 08 48 = HT=3, H=2, data=fixed (2)
// 08 68 = HT=3, H=2, data=fixed (2)
// 04 64 = HT=3, H=1, data=fixed (1)
// 00 00 = HT=3, H=off, data=fixed (0)
// F5V3 confirmed:
// 90 70 = HT=3, H=3, adaptive, data=no data
// 54 14 = HT=Off, H=5, adaptive, data=tube t=0,h=5
// 54 34 = HT=1, H=5, adaptive, data=tube t=1,h=5
// 50 70 = HT=3, H=4, adaptive, data=tube t=3,h=4
// 4C 6C = HT=3, H=3, adaptive, data=tube t=3,h=3
// 4C 4C = HT=2, H=3, adaptive, data=tube t=2,h=3
// 4C 2C = HT=1, H=3, adaptive, data=tube t=1,h=3
// 4C 0C = HT=off, H=3, adaptive, data=tube t=0,h=3
// 48 08 = HT=off, H=2, adaptive, data=tube t=0,h=2
// 44 04 = HT=off, H=1, adaptive, data=tube t=0,h=1
// 40 00 = HT=off,H=off, adaptive, data=tube t=0,h=0
// 34 74 = HT=3, H=5, adaptive, data=s1 (5)
// 30 70 = HT=3, H=4, adaptive, data=s1 (4)
// 2C 6C = HT=3, H=3, adaptive, data=s1 (3)
// 28 68 = HT=3, H=2, adaptive, data=s1 (2)
// 24 64 = HT=3, H=1, adaptive, data=s1 (1)
// F3V6 confirmed:
// 84 24 = HT=3, H=3, disconnect=adaptive, data=no data
// 50 90 = HT=4, H=4, disconnect=adaptive, data=tube t=4,h=4
// 44 84 = HT=4, H=1, disconnect=adaptive, data=tube t=4,h=1
// 40 80 = HT=4, H=Off,disconnect=adaptive, data=tube t=4,h=0
// 4C 6C = HT=3, H=3, disconnect=adaptive, data=tube t=3,h=3
// 48 68 = HT=3, H=2, disconnect=adaptive, data=tube t=3,h=2
// 44 44 = HT=2, H=1, disconnect=adaptive, data=tube t=2,h=1
// 48 28 = HT=1, H=2, disconnect=adaptive, data=tube t=1,h=2
// 54 14 = HT=Off,H=5, disconnect=adaptive data=tube t=0,h=5
// 34 14 = HT=3, H=5, disconnect=adaptive, data=s1 (5)
// 30 70 = HT=3, H=4, disconnect=adaptive, data=s1 (4)
// 2C 6C = HT=3, H=3, disconnect=adaptive, data=s1 (3)
// 28 08 = HT=3, H=2, disconnect=adaptive, data=s1 (2)
// 20 20 = HT=3, H=Off, disconnect=adaptive, data=s1 (0)
// 14 14 = HT=3, H=3, disconnect=fixed, data=classic (5)
// 10 10 = HT=3, H=4, disconnect=fixed, data=classic (4) [fixed mode appears to ignore HT bits and show HT=3 in details]
// 0C 0C = HT=3, H=3, disconnect=fixed, data=classic (3)
// 08 08 = HT=3, H=2, disconnect=fixed, data=classic (2)
// 04 64 = HT=3, H=1, disconnect=fixed, data=classic (1)
// The data is consistent among all fileVersion 3 models: F0V6, F5V3, F3V6.
//
// NOTE: F5V3 and F3V6 charts report the "Adaptive" setting as "System One" and the "Fixed"
// setting as "Classic", despite labeling the settings "Adaptive" and "Fixed" just like F0V6.
// F0V6 is consistent and labels both settings and chart as "Adaptive" and "Fixed".
//
// 400G and 502G appear to omit the humidifier settings in their details, though they
// do support humidifiers, and will show the humidification in the charts.
void PRS1DataChunk : : ParseHumidifierSettingV3 ( unsigned char byte1 , unsigned char byte2 , bool add_setting )
{
bool humidifier_present = true ;
bool humidfixed = false ; // formerly called "Classic"
bool humidadaptive = false ; // formerly called "System One"
bool tubepresent = false ;
bool passover = false ;
bool error = false ;
// Byte 1: 0x90 (no humidifier data), 0x50 (15ht, tube 4/5, humid 4), 0x54 (15ht, tube 5, humid 5) 0x4c (15ht, tube temp 3, humidifier 3)
// 0x0c (15, tube 3, humid 3, fixed)
// 0b1001 0000 no humidifier data
// 0b0101 0000 tube 4 and 5, humidifier 4
// 0b0101 0100 15ht, tube 5, humidifier 5
// 0b0100 1100 15ht, tube 3, humidifier 3
// 0b1011 0000 15, tube 3, humidifier 3, "Error" on humidification chart with asterisk at 4
// 0b0111 0000 15, tube 3, humidifier 3, "Passover" on humidification chart with notch at 4
// 842 = humidifier status
// 1 84 = humidifier setting
// ??
CHECK_VALUE ( byte1 & 3 , 0 ) ;
int humid = byte1 > > 5 ;
switch ( humid ) {
case 0 : humidfixed = true ; break ; // fixed, ignores tubetemp bits and reports tubetemp=3
case 1 : humidadaptive = true ; break ; // adaptive, ignores tubetemp bits and reports tubetemp=3
case 2 : tubepresent = true ; break ; // heated tube
case 3 : passover = true ; break ; // passover mode (only visible in chart)
case 4 : humidifier_present = false ; break ; // no humidifier, reports tubetemp=3 and humidlevel=3
case 5 : error = true ; break ; // "Error" in humidification chart, reports tubetemp=3 and humidlevel=3 in settings
default :
UNEXPECTED_VALUE ( humid , " known value " ) ;
break ;
}
int humidlevel = ( byte1 > > 2 ) & 7 ;
// Byte 2: 0xB4 (15ht, tube 5, humid 5), 0xB0 (15ht, tube 5, humid 4), 0x90 (tube 4, humid 4), 0x6C (15ht, tube temp 3, humidifier 3)
// 0x80?
// 0b1011 0100 15ht, tube 5, humidifier 5
// 0b1011 0000 15ht, tube 5, humidifier 4
// 0b1001 0000 tube 4, humidifier 4
// 0b0110 1100 15ht, tube 3, humidifier 3
// 842 = tube temperature
// 1 84 = humidity level when using heated tube, thus far always identical to humidlevel
// ??
CHECK_VALUE ( byte2 & 3 , 0 ) ;
int tubehumidlevel = ( byte2 > > 2 ) & 7 ;
CHECK_VALUE ( humidlevel , tubehumidlevel ) ; // thus far always the same
int tubetemp = ( byte2 > > 5 ) & 7 ;
if ( humidifier_present ) {
if ( humidlevel > 5 | | humidlevel < 0 ) UNEXPECTED_VALUE ( humidlevel , " 0-5 " ) ; // 0=off is valid when a humidifier is attached
if ( humid = = 2 ) { // heated tube
if ( tubetemp > 5 | | tubetemp < 0 ) UNEXPECTED_VALUE ( tubetemp , " 0-5 " ) ; // TODO: maybe this is only if heated tube? 0=off is valid even in heated tube mode
}
}
// TODO: move this up into the switch statement above, given how many modes there now are.
HumidMode humidmode = HUMID_Fixed ;
if ( tubepresent ) {
humidmode = HUMID_HeatedTube ;
} else if ( humidadaptive ) {
humidmode = HUMID_Adaptive ;
} else if ( passover ) {
humidmode = HUMID_Passover ;
} else if ( error ) {
humidmode = HUMID_Error ;
}
if ( add_setting ) {
this - > AddEvent ( new PRS1ParsedSettingEvent ( PRS1_SETTING_HUMID_STATUS , humidifier_present ) ) ;
if ( humidifier_present ) {
this - > AddEvent ( new PRS1ParsedSettingEvent ( PRS1_SETTING_HUMID_MODE , humidmode ) ) ;
if ( humidmode = = HUMID_HeatedTube ) {
this - > AddEvent ( new PRS1ParsedSettingEvent ( PRS1_SETTING_HEATED_TUBE_TEMP , tubetemp ) ) ;
this - > AddEvent ( new PRS1ParsedSettingEvent ( PRS1_SETTING_HUMID_LEVEL , tubehumidlevel ) ) ;
} else {
this - > AddEvent ( new PRS1ParsedSettingEvent ( PRS1_SETTING_HUMID_LEVEL , humidlevel ) ) ;
}
}
}
// Check for previously unseen data that we expect to be normal:
if ( family = = 0 ) {
// All variations seen.
} else if ( family = = 5 ) {
if ( tubepresent ) {
// All tube temperature and humidity levels seen.
} else if ( humidadaptive ) {
// All humidity levels seen.
} else if ( humidfixed ) {
if ( humidlevel < 3 ) UNEXPECTED_VALUE ( humidlevel , " 3-5 " ) ;
}
} else if ( family = = 3 ) {
if ( tubepresent ) {
// All tube temperature and humidity levels seen.
} else if ( humidadaptive ) {
// All humidity levels seen.
} else if ( humidfixed ) {
// All humidity levels seen.
}
}
}
void PRS1DataChunk : : ParseTubingTypeV3 ( unsigned char type )
{
int diam ;
switch ( type ) {
case 0 : diam = 22 ; break ;
case 1 : diam = 15 ; break ;
case 2 : diam = 15 ; break ; // 15HT, though the reports only say "15" for DreamStation models
case 3 : diam = 12 ; break ; // seen on DreamStation Go models
2022-01-14 16:34:37 +00:00
case 4 : diam = 12 ; break ; // HT12, seen on DreamStation 2 models
2021-06-01 01:23:03 +00:00
default :
UNEXPECTED_VALUE ( type , " known tubing type " ) ;
return ;
}
this - > AddEvent ( new PRS1ParsedSettingEvent ( PRS1_SETTING_HOSE_DIAMETER , diam ) ) ;
}
2021-06-01 01:31:34 +00:00
//********************************************************************************************
// MARK: -
// MARK: Parse and verify chunk from stream
typedef quint16 crc16_t ;
typedef quint32 crc32_t ;
static crc16_t CRC16 ( unsigned char * data , size_t data_len , crc16_t crc = 0 ) ;
static crc32_t CRC32 ( const unsigned char * data , size_t data_len , crc32_t crc = 0xffffffffU ) ;
static crc32_t CRC32wchar ( const unsigned char * data , size_t data_len , crc32_t crc = 0xffffffffU ) ;
PRS1DataChunk : : PRS1DataChunk ( RawDataDevice & f , PRS1Loader * in_loader ) : loader ( in_loader )
{
m_path = f . name ( ) ;
}
PRS1DataChunk : : ~ PRS1DataChunk ( )
{
for ( int i = 0 ; i < m_parsedData . count ( ) ; i + + ) {
PRS1ParsedEvent * e = m_parsedData . at ( i ) ;
delete e ;
}
}
PRS1DataChunk * PRS1DataChunk : : ParseNext ( RawDataDevice & f , PRS1Loader * loader )
{
PRS1DataChunk * out_chunk = nullptr ;
PRS1DataChunk * chunk = new PRS1DataChunk ( f , loader ) ;
do {
// Parse the header and calculate its checksum.
bool ok = chunk - > ReadHeader ( f ) ;
if ( ! ok ) {
break ;
}
// Make sure the calculated checksum matches the stored checksum.
if ( chunk - > calcChecksum ! = chunk - > storedChecksum ) {
qWarning ( ) < < chunk - > m_path < < " header checksum calc " < < chunk - > calcChecksum < < " != stored " < < chunk - > storedChecksum ;
break ;
}
// Read the block's data and calculate the block CRC.
ok = chunk - > ReadData ( f ) ;
if ( ! ok ) {
break ;
}
// Make sure the calculated CRC over the entire chunk (header and data) matches the stored CRC.
if ( chunk - > calcCrc ! = chunk - > storedCrc ) {
// Corrupt data block, warn about it.
qWarning ( ) < < chunk - > m_path < < " @ " < < chunk - > m_filepos < < " block CRC calc " < < hex < < chunk - > calcCrc < < " != stored " < < hex < < chunk - > storedCrc ;
// TODO: When this happens, it's usually because the chunk was truncated and another chunk header
// exists within the blockSize bytes. In theory it should be possible to rewing and resync by
// looking for another chunk header with the same fileVersion, htype, family, familyVersion, and
// ext (blockSize and other fields could vary).
//
// But this is quite rare, so for now we bail on the rest of the file.
break ;
}
// Only return the chunk if it has passed all tests above.
out_chunk = chunk ;
} while ( false ) ;
if ( out_chunk = = nullptr ) delete chunk ;
return out_chunk ;
}
bool PRS1DataChunk : : ReadHeader ( RawDataDevice & f )
{
bool ok = false ;
do {
// Read common header fields.
this - > m_filepos = f . pos ( ) ;
this - > m_header = f . read ( 15 ) ;
if ( this - > m_header . size ( ) ! = 15 ) {
if ( this - > m_header . size ( ) = = 0 ) {
qWarning ( ) < < this - > m_path < < " empty, skipping " ;
} else {
qWarning ( ) < < this - > m_path < < " file too short? " ;
}
break ;
}
unsigned char * header = ( unsigned char * ) this - > m_header . data ( ) ;
this - > fileVersion = header [ 0 ] ; // Correlates to DataFileVersion in PROP[erties].TXT, only 2 or 3 has ever been observed
this - > blockSize = ( header [ 2 ] < < 8 ) | header [ 1 ] ;
this - > htype = header [ 3 ] ; // 00 = normal, 01=waveform
this - > family = header [ 4 ] ;
this - > familyVersion = header [ 5 ] ;
this - > ext = header [ 6 ] ;
this - > sessionid = ( header [ 10 ] < < 24 ) | ( header [ 9 ] < < 16 ) | ( header [ 8 ] < < 8 ) | header [ 7 ] ;
this - > timestamp = ( header [ 14 ] < < 24 ) | ( header [ 13 ] < < 16 ) | ( header [ 12 ] < < 8 ) | header [ 11 ] ;
// Do a few early sanity checks before any variable-length header data.
if ( this - > blockSize = = 0 ) {
qWarning ( ) < < this - > m_path < < " @ " < < hex < < this - > m_filepos < < " blocksize 0, skipping remainder of file " ;
break ;
}
if ( this - > fileVersion < 2 | | this - > fileVersion > 3 ) {
if ( this - > m_filepos > 0 ) {
qWarning ( ) < < this - > m_path < < " @ " < < hex < < this - > m_filepos < < " corrupt PRS1 header, skipping remainder of file " ;
} else {
qWarning ( ) < < this - > m_path < < " unsupported PRS1 header version " < < this - > fileVersion ;
}
break ;
}
if ( this - > htype ! = PRS1_HTYPE_NORMAL & & this - > htype ! = PRS1_HTYPE_INTERVAL ) {
qWarning ( ) < < this - > m_path < < " unexpected htype: " < < this - > htype ;
break ;
}
// Read format-specific variable-length header data.
bool hdr_ok = false ;
if ( this - > htype ! = PRS1_HTYPE_INTERVAL ) { // Not just waveforms: the 1160P uses this for its .002 events file.
// Not a waveform/interval chunk
switch ( this - > fileVersion ) {
case 2 :
hdr_ok = ReadNormalHeaderV2 ( f ) ;
break ;
case 3 :
hdr_ok = ReadNormalHeaderV3 ( f ) ;
break ;
default :
//hdr_ok remains false, warning is above
break ;
}
} else {
// Waveform/interval chunk
hdr_ok = ReadWaveformHeader ( f ) ;
}
if ( ! hdr_ok ) {
break ;
}
// The 8bit checksum comes at the end.
QByteArray checksum = f . read ( 1 ) ;
if ( checksum . size ( ) < 1 ) {
qWarning ( ) < < this - > m_path < < " read error header checksum " ;
break ;
}
this - > storedChecksum = checksum . data ( ) [ 0 ] ;
// Calculate 8bit additive header checksum.
header = ( unsigned char * ) this - > m_header . data ( ) ; // important because its memory location could move
int header_size = this - > m_header . size ( ) ;
quint8 achk = 0 ;
for ( int i = 0 ; i < header_size ; i + + ) {
achk + = header [ i ] ;
}
this - > calcChecksum = achk ;
// Append the stored checksum to the raw data *after* calculating the checksum on the preceding data.
this - > m_header . append ( checksum ) ;
ok = true ;
} while ( false ) ;
return ok ;
}
bool PRS1DataChunk : : ReadNormalHeaderV2 ( RawDataDevice & /*f*/ )
{
this - > m_headerblock = QByteArray ( ) ;
return true ; // always OK
}
bool PRS1DataChunk : : ReadNormalHeaderV3 ( RawDataDevice & f )
{
bool ok = false ;
unsigned char * header ;
QByteArray headerB2 ;
2022-02-27 16:50:10 +00:00
// This is a new device, byte 15 is header data block length
2021-06-01 01:31:34 +00:00
// followed by variable, data byte pairs
do {
QByteArray extra = f . read ( 1 ) ;
if ( extra . size ( ) < 1 ) {
qWarning ( ) < < this - > m_path < < " read error extended header " ;
break ;
}
this - > m_header . append ( extra ) ;
header = ( unsigned char * ) this - > m_header . data ( ) ;
int hdb_len = header [ 15 ] ;
int hdb_size = hdb_len * 2 ;
headerB2 = f . read ( hdb_size ) ;
if ( headerB2 . size ( ) ! = hdb_size ) {
qWarning ( ) < < this - > m_path < < " read error in extended header " ;
break ;
}
this - > m_headerblock = headerB2 ;
this - > m_header . append ( headerB2 ) ;
header = ( unsigned char * ) this - > m_header . data ( ) ;
const unsigned char * hd = ( unsigned char * ) headerB2 . constData ( ) ;
int pos = 0 ;
int recs = header [ 15 ] ;
for ( int i = 0 ; i < recs ; i + + ) {
this - > hblock [ hd [ pos ] ] = hd [ pos + 1 ] ;
pos + = 2 ;
}
ok = true ;
} while ( false ) ;
return ok ;
}
bool PRS1DataChunk : : ReadWaveformHeader ( RawDataDevice & f )
{
bool ok = false ;
unsigned char * header ;
do {
// Read the fixed-length waveform header.
QByteArray extra = f . read ( 4 ) ;
if ( extra . size ( ) ! = 4 ) {
qWarning ( ) < < this - > m_path < < " read error in waveform header " ;
break ;
}
this - > m_header . append ( extra ) ;
header = ( unsigned char * ) this - > m_header . data ( ) ;
// Parse the fixed-length portion.
this - > interval_count = header [ 0x0f ] | header [ 0x10 ] < < 8 ;
this - > interval_seconds = header [ 0x11 ] ; // not always 1 after all
this - > duration = this - > interval_count * this - > interval_seconds ; // ??? the last entry doesn't always seem to be a full interval?
quint8 wvfm_signals = header [ 0x12 ] ;
// Read the variable-length data + trailing byte.
int ws_size = ( this - > fileVersion = = 3 ) ? 4 : 3 ;
int sbsize = wvfm_signals * ws_size + 1 ;
extra = f . read ( sbsize ) ;
if ( extra . size ( ) ! = sbsize ) {
qWarning ( ) < < this - > m_path < < " read error in waveform header 2 " ;
break ;
}
this - > m_header . append ( extra ) ;
header = ( unsigned char * ) this - > m_header . data ( ) ;
// Parse the variable-length waveform information.
// TODO: move these checks into the parser, after the header checksum has been verified
// For now just skip them for the one known sample with a bad checksum.
if ( this - > sessionid = = 268962649 ) return true ;
int pos = 0x13 ;
for ( int i = 0 ; i < wvfm_signals ; + + i ) {
quint8 kind = header [ pos ] ;
CHECK_VALUE ( kind , i ) ; // always seems to range from 0...wvfm_signals-1, alert if not
quint16 interleave = header [ pos + 1 ] | header [ pos + 2 ] < < 8 ; // samples per interval
if ( this - > fileVersion = = 2 ) {
this - > waveformInfo . push_back ( PRS1Waveform ( interleave , kind ) ) ;
pos + = 3 ;
} else if ( this - > fileVersion = = 3 ) {
int always_8 = header [ pos + 3 ] ; // sample size in bits?
CHECK_VALUE ( always_8 , 8 ) ;
this - > waveformInfo . push_back ( PRS1Waveform ( interleave , kind ) ) ;
pos + = 4 ;
}
}
// And the trailing byte, whatever it is.
int always_0 = header [ pos ] ;
CHECK_VALUE ( always_0 , 0 ) ;
ok = true ;
} while ( false ) ;
return ok ;
}
bool PRS1DataChunk : : ReadData ( RawDataDevice & f )
{
bool ok = false ;
do {
// Read data block
int data_size = this - > blockSize - this - > m_header . size ( ) ;
if ( data_size < 0 ) {
qWarning ( ) < < this - > m_path < < " chunk size smaller than header " ;
break ;
}
this - > m_data = f . read ( data_size ) ;
if ( this - > m_data . size ( ) < data_size ) {
qWarning ( ) < < this - > m_path < < " less data in file than specified in header " ;
break ;
}
// Extract the stored CRC from the data buffer and calculate the current CRC.
if ( this - > fileVersion = = 3 ) {
// The last 4 bytes contain a CRC32 checksum of the data.
if ( ! ExtractStoredCrc ( 4 ) ) {
break ;
}
this - > calcCrc = CRC32wchar ( ( unsigned char * ) this - > m_data . data ( ) , this - > m_data . size ( ) ) ;
} else {
// The last 2 bytes contain a CRC16 checksum of the data.
if ( ! ExtractStoredCrc ( 2 ) ) {
break ;
}
this - > calcCrc = CRC16 ( ( unsigned char * ) this - > m_data . data ( ) , this - > m_data . size ( ) ) ;
}
ok = true ;
} while ( false ) ;
return ok ;
}
bool PRS1DataChunk : : ExtractStoredCrc ( int size )
{
// Make sure there's enough data for the CRC.
int offset = this - > m_data . size ( ) - size ;
if ( offset < 0 ) {
qWarning ( ) < < this - > m_path < < " chunk truncated " ;
return false ;
}
// Read the last 16- or 32-bit little-endian integer.
quint32 storedCrc = 0 ;
unsigned char * data = ( unsigned char * ) this - > m_data . data ( ) ;
for ( int i = 0 ; i < size ; i + + ) {
storedCrc | = data [ offset + i ] < < ( 8 * i ) ;
}
this - > storedCrc = storedCrc ;
// Drop the CRC from the data.
this - > m_data . chop ( size ) ;
return true ;
}
// CRC-16/KERMIT, polynomial: 0x11021, bit reverse algorithm
// Table generated by crcmod (crc-kermit)
typedef quint16 crc16_t ;
static crc16_t CRC16 ( unsigned char * data , size_t data_len , crc16_t crc )
{
static const crc16_t table [ 256 ] = {
0x0000U , 0x1189U , 0x2312U , 0x329bU , 0x4624U , 0x57adU , 0x6536U , 0x74bfU ,
0x8c48U , 0x9dc1U , 0xaf5aU , 0xbed3U , 0xca6cU , 0xdbe5U , 0xe97eU , 0xf8f7U ,
0x1081U , 0x0108U , 0x3393U , 0x221aU , 0x56a5U , 0x472cU , 0x75b7U , 0x643eU ,
0x9cc9U , 0x8d40U , 0xbfdbU , 0xae52U , 0xdaedU , 0xcb64U , 0xf9ffU , 0xe876U ,
0x2102U , 0x308bU , 0x0210U , 0x1399U , 0x6726U , 0x76afU , 0x4434U , 0x55bdU ,
0xad4aU , 0xbcc3U , 0x8e58U , 0x9fd1U , 0xeb6eU , 0xfae7U , 0xc87cU , 0xd9f5U ,
0x3183U , 0x200aU , 0x1291U , 0x0318U , 0x77a7U , 0x662eU , 0x54b5U , 0x453cU ,
0xbdcbU , 0xac42U , 0x9ed9U , 0x8f50U , 0xfbefU , 0xea66U , 0xd8fdU , 0xc974U ,
0x4204U , 0x538dU , 0x6116U , 0x709fU , 0x0420U , 0x15a9U , 0x2732U , 0x36bbU ,
0xce4cU , 0xdfc5U , 0xed5eU , 0xfcd7U , 0x8868U , 0x99e1U , 0xab7aU , 0xbaf3U ,
0x5285U , 0x430cU , 0x7197U , 0x601eU , 0x14a1U , 0x0528U , 0x37b3U , 0x263aU ,
0xdecdU , 0xcf44U , 0xfddfU , 0xec56U , 0x98e9U , 0x8960U , 0xbbfbU , 0xaa72U ,
0x6306U , 0x728fU , 0x4014U , 0x519dU , 0x2522U , 0x34abU , 0x0630U , 0x17b9U ,
0xef4eU , 0xfec7U , 0xcc5cU , 0xddd5U , 0xa96aU , 0xb8e3U , 0x8a78U , 0x9bf1U ,
0x7387U , 0x620eU , 0x5095U , 0x411cU , 0x35a3U , 0x242aU , 0x16b1U , 0x0738U ,
0xffcfU , 0xee46U , 0xdcddU , 0xcd54U , 0xb9ebU , 0xa862U , 0x9af9U , 0x8b70U ,
0x8408U , 0x9581U , 0xa71aU , 0xb693U , 0xc22cU , 0xd3a5U , 0xe13eU , 0xf0b7U ,
0x0840U , 0x19c9U , 0x2b52U , 0x3adbU , 0x4e64U , 0x5fedU , 0x6d76U , 0x7cffU ,
0x9489U , 0x8500U , 0xb79bU , 0xa612U , 0xd2adU , 0xc324U , 0xf1bfU , 0xe036U ,
0x18c1U , 0x0948U , 0x3bd3U , 0x2a5aU , 0x5ee5U , 0x4f6cU , 0x7df7U , 0x6c7eU ,
0xa50aU , 0xb483U , 0x8618U , 0x9791U , 0xe32eU , 0xf2a7U , 0xc03cU , 0xd1b5U ,
0x2942U , 0x38cbU , 0x0a50U , 0x1bd9U , 0x6f66U , 0x7eefU , 0x4c74U , 0x5dfdU ,
0xb58bU , 0xa402U , 0x9699U , 0x8710U , 0xf3afU , 0xe226U , 0xd0bdU , 0xc134U ,
0x39c3U , 0x284aU , 0x1ad1U , 0x0b58U , 0x7fe7U , 0x6e6eU , 0x5cf5U , 0x4d7cU ,
0xc60cU , 0xd785U , 0xe51eU , 0xf497U , 0x8028U , 0x91a1U , 0xa33aU , 0xb2b3U ,
0x4a44U , 0x5bcdU , 0x6956U , 0x78dfU , 0x0c60U , 0x1de9U , 0x2f72U , 0x3efbU ,
0xd68dU , 0xc704U , 0xf59fU , 0xe416U , 0x90a9U , 0x8120U , 0xb3bbU , 0xa232U ,
0x5ac5U , 0x4b4cU , 0x79d7U , 0x685eU , 0x1ce1U , 0x0d68U , 0x3ff3U , 0x2e7aU ,
0xe70eU , 0xf687U , 0xc41cU , 0xd595U , 0xa12aU , 0xb0a3U , 0x8238U , 0x93b1U ,
0x6b46U , 0x7acfU , 0x4854U , 0x59ddU , 0x2d62U , 0x3cebU , 0x0e70U , 0x1ff9U ,
0xf78fU , 0xe606U , 0xd49dU , 0xc514U , 0xb1abU , 0xa022U , 0x92b9U , 0x8330U ,
0x7bc7U , 0x6a4eU , 0x58d5U , 0x495cU , 0x3de3U , 0x2c6aU , 0x1ef1U , 0x0f78U ,
} ;
for ( size_t i = 0 ; i < data_len ; i + + ) {
crc = table [ ( * data ^ ( unsigned char ) crc ) & 0xFF ] ^ ( crc > > 8 ) ;
data + + ;
}
return crc ;
}
// CRC-32/MPEG-2, polynomial: 0x104C11DB7
// Table generated by crcmod (crc-32-mpeg)
static crc32_t CRC32 ( const unsigned char * data , size_t data_len , crc32_t crc )
{
static const crc32_t table [ 256 ] = {
0x00000000U , 0x04c11db7U , 0x09823b6eU , 0x0d4326d9U ,
0x130476dcU , 0x17c56b6bU , 0x1a864db2U , 0x1e475005U ,
0x2608edb8U , 0x22c9f00fU , 0x2f8ad6d6U , 0x2b4bcb61U ,
0x350c9b64U , 0x31cd86d3U , 0x3c8ea00aU , 0x384fbdbdU ,
0x4c11db70U , 0x48d0c6c7U , 0x4593e01eU , 0x4152fda9U ,
0x5f15adacU , 0x5bd4b01bU , 0x569796c2U , 0x52568b75U ,
0x6a1936c8U , 0x6ed82b7fU , 0x639b0da6U , 0x675a1011U ,
0x791d4014U , 0x7ddc5da3U , 0x709f7b7aU , 0x745e66cdU ,
0x9823b6e0U , 0x9ce2ab57U , 0x91a18d8eU , 0x95609039U ,
0x8b27c03cU , 0x8fe6dd8bU , 0x82a5fb52U , 0x8664e6e5U ,
0xbe2b5b58U , 0xbaea46efU , 0xb7a96036U , 0xb3687d81U ,
0xad2f2d84U , 0xa9ee3033U , 0xa4ad16eaU , 0xa06c0b5dU ,
0xd4326d90U , 0xd0f37027U , 0xddb056feU , 0xd9714b49U ,
0xc7361b4cU , 0xc3f706fbU , 0xceb42022U , 0xca753d95U ,
0xf23a8028U , 0xf6fb9d9fU , 0xfbb8bb46U , 0xff79a6f1U ,
0xe13ef6f4U , 0xe5ffeb43U , 0xe8bccd9aU , 0xec7dd02dU ,
0x34867077U , 0x30476dc0U , 0x3d044b19U , 0x39c556aeU ,
0x278206abU , 0x23431b1cU , 0x2e003dc5U , 0x2ac12072U ,
0x128e9dcfU , 0x164f8078U , 0x1b0ca6a1U , 0x1fcdbb16U ,
0x018aeb13U , 0x054bf6a4U , 0x0808d07dU , 0x0cc9cdcaU ,
0x7897ab07U , 0x7c56b6b0U , 0x71159069U , 0x75d48ddeU ,
0x6b93dddbU , 0x6f52c06cU , 0x6211e6b5U , 0x66d0fb02U ,
0x5e9f46bfU , 0x5a5e5b08U , 0x571d7dd1U , 0x53dc6066U ,
0x4d9b3063U , 0x495a2dd4U , 0x44190b0dU , 0x40d816baU ,
0xaca5c697U , 0xa864db20U , 0xa527fdf9U , 0xa1e6e04eU ,
0xbfa1b04bU , 0xbb60adfcU , 0xb6238b25U , 0xb2e29692U ,
0x8aad2b2fU , 0x8e6c3698U , 0x832f1041U , 0x87ee0df6U ,
0x99a95df3U , 0x9d684044U , 0x902b669dU , 0x94ea7b2aU ,
0xe0b41de7U , 0xe4750050U , 0xe9362689U , 0xedf73b3eU ,
0xf3b06b3bU , 0xf771768cU , 0xfa325055U , 0xfef34de2U ,
0xc6bcf05fU , 0xc27dede8U , 0xcf3ecb31U , 0xcbffd686U ,
0xd5b88683U , 0xd1799b34U , 0xdc3abdedU , 0xd8fba05aU ,
0x690ce0eeU , 0x6dcdfd59U , 0x608edb80U , 0x644fc637U ,
0x7a089632U , 0x7ec98b85U , 0x738aad5cU , 0x774bb0ebU ,
0x4f040d56U , 0x4bc510e1U , 0x46863638U , 0x42472b8fU ,
0x5c007b8aU , 0x58c1663dU , 0x558240e4U , 0x51435d53U ,
0x251d3b9eU , 0x21dc2629U , 0x2c9f00f0U , 0x285e1d47U ,
0x36194d42U , 0x32d850f5U , 0x3f9b762cU , 0x3b5a6b9bU ,
0x0315d626U , 0x07d4cb91U , 0x0a97ed48U , 0x0e56f0ffU ,
0x1011a0faU , 0x14d0bd4dU , 0x19939b94U , 0x1d528623U ,
0xf12f560eU , 0xf5ee4bb9U , 0xf8ad6d60U , 0xfc6c70d7U ,
0xe22b20d2U , 0xe6ea3d65U , 0xeba91bbcU , 0xef68060bU ,
0xd727bbb6U , 0xd3e6a601U , 0xdea580d8U , 0xda649d6fU ,
0xc423cd6aU , 0xc0e2d0ddU , 0xcda1f604U , 0xc960ebb3U ,
0xbd3e8d7eU , 0xb9ff90c9U , 0xb4bcb610U , 0xb07daba7U ,
0xae3afba2U , 0xaafbe615U , 0xa7b8c0ccU , 0xa379dd7bU ,
0x9b3660c6U , 0x9ff77d71U , 0x92b45ba8U , 0x9675461fU ,
0x8832161aU , 0x8cf30badU , 0x81b02d74U , 0x857130c3U ,
0x5d8a9099U , 0x594b8d2eU , 0x5408abf7U , 0x50c9b640U ,
0x4e8ee645U , 0x4a4ffbf2U , 0x470cdd2bU , 0x43cdc09cU ,
0x7b827d21U , 0x7f436096U , 0x7200464fU , 0x76c15bf8U ,
0x68860bfdU , 0x6c47164aU , 0x61043093U , 0x65c52d24U ,
0x119b4be9U , 0x155a565eU , 0x18197087U , 0x1cd86d30U ,
0x029f3d35U , 0x065e2082U , 0x0b1d065bU , 0x0fdc1becU ,
0x3793a651U , 0x3352bbe6U , 0x3e119d3fU , 0x3ad08088U ,
0x2497d08dU , 0x2056cd3aU , 0x2d15ebe3U , 0x29d4f654U ,
0xc5a92679U , 0xc1683bceU , 0xcc2b1d17U , 0xc8ea00a0U ,
0xd6ad50a5U , 0xd26c4d12U , 0xdf2f6bcbU , 0xdbee767cU ,
0xe3a1cbc1U , 0xe760d676U , 0xea23f0afU , 0xeee2ed18U ,
0xf0a5bd1dU , 0xf464a0aaU , 0xf9278673U , 0xfde69bc4U ,
0x89b8fd09U , 0x8d79e0beU , 0x803ac667U , 0x84fbdbd0U ,
0x9abc8bd5U , 0x9e7d9662U , 0x933eb0bbU , 0x97ffad0cU ,
0xafb010b1U , 0xab710d06U , 0xa6322bdfU , 0xa2f33668U ,
0xbcb4666dU , 0xb8757bdaU , 0xb5365d03U , 0xb1f740b4U ,
} ;
for ( size_t i = 0 ; i < data_len ; i + + ) {
crc = table [ ( * data ^ ( unsigned char ) ( crc > > 24 ) ) & 0xFF ] ^ ( crc < < 8 ) ;
data + + ;
}
return crc ;
}
2021-12-09 17:23:08 +00:00
// The PRS1 CRC32 considers every byte a 32-bit wchar_t, presumably due to
// use of the STM32 CRC calculation unit, in which "CRC computation is done
// on the whole 32-bit data word, and not byte per byte".
2021-06-01 01:31:34 +00:00
static crc32_t CRC32wchar ( const unsigned char * data , size_t data_len , crc32_t crc )
{
for ( size_t i = 0 ; i < data_len ; i + + ) {
static unsigned char wch [ 4 ] = { 0 , 0 , 0 , 0 } ;
wch [ 3 ] = * data + + ;
crc = CRC32 ( wch , 4 , crc ) ;
}
return crc ;
}