mirror of
https://gitlab.com/pholy/OSCAR-code.git
synced 2025-04-04 18:20:42 +00:00
2317 lines
57 KiB
C++
2317 lines
57 KiB
C++
/* SleepLib Session Implementation
|
|
* This stuff contains the base calculation smarts
|
|
*
|
|
* 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 "session.h"
|
|
#include <cmath>
|
|
#include <QDir>
|
|
#include <QDebug>
|
|
#include <QMessageBox>
|
|
#include <QMetaType>
|
|
#include <algorithm>
|
|
#include <limits>
|
|
|
|
#include "SleepLib/calcs.h"
|
|
#include "SleepLib/profiles.h"
|
|
|
|
using namespace std;
|
|
|
|
// This is the uber important database version for OSCR's internal storage
|
|
// Increment this after stuffing with Session's save & load code.
|
|
const quint16 summary_version = 18;
|
|
const quint16 events_version = 10;
|
|
|
|
Session::Session(Machine *m, SessionID session)
|
|
{
|
|
s_lonesession = false;
|
|
|
|
if (!session) {
|
|
session = m->CreateSessionID();
|
|
}
|
|
|
|
s_machine = m;
|
|
s_machtype = m->type();
|
|
s_session = session;
|
|
s_changed = false;
|
|
s_events_loaded = false;
|
|
s_summary_loaded = false;
|
|
_first_session = true;
|
|
s_enabled = true;
|
|
|
|
s_first = s_last = 0;
|
|
s_evchecksum_checked = false;
|
|
|
|
s_noSettings = s_summaryOnly = false;
|
|
|
|
destroyed = false;
|
|
}
|
|
|
|
Session::~Session()
|
|
{
|
|
TrashEvents();
|
|
destroyed = true;
|
|
}
|
|
|
|
void Session::TrashEvents()
|
|
// Trash this sessions Events and release memory.
|
|
{
|
|
QVector<EventList *>::iterator j;
|
|
QVector<EventList *>::iterator j_end;
|
|
QHash<ChannelID, QVector<EventList *> >::iterator i;
|
|
QHash<ChannelID, QVector<EventList *> >::iterator i_end=eventlist.end();
|
|
|
|
if (s_changed) {
|
|
// Save first..
|
|
}
|
|
|
|
for (i = eventlist.begin(); i != i_end; ++i) {
|
|
j_end=i.value().end();
|
|
for (j = i.value().begin(); j != j_end; ++j) {
|
|
EventList * ev = *j;
|
|
ev->clear();
|
|
ev->m_data.squeeze();
|
|
ev->m_data2.squeeze();
|
|
ev->m_time.squeeze();
|
|
delete ev;
|
|
}
|
|
}
|
|
|
|
s_events_loaded = false;
|
|
eventlist.clear();
|
|
eventlist.squeeze();
|
|
}
|
|
|
|
void Session::setEnabled(bool b)
|
|
{
|
|
s_enabled = b;
|
|
// not so simple.. we have to invalidate the hours cache in the day record..
|
|
|
|
Day * day = p_profile->findSessionDay(this);
|
|
if (day) {
|
|
day->invalidate();
|
|
}
|
|
}
|
|
|
|
QString Session::eventFile() const
|
|
{
|
|
return s_machine->getEventsPath()+QString().sprintf("%08lx.001", s_session);
|
|
}
|
|
|
|
//const int max_pack_size=128;
|
|
bool Session::OpenEvents()
|
|
{
|
|
if (s_events_loaded) {
|
|
return true;
|
|
}
|
|
|
|
s_events_loaded = eventlist.size() > 0;
|
|
|
|
if (s_events_loaded) {
|
|
return true;
|
|
}
|
|
|
|
|
|
QString filename = eventFile();
|
|
bool b = LoadEvents(filename);
|
|
|
|
if (!b) {
|
|
// qWarning() << "Error Loading Events" << filename;
|
|
return false;
|
|
}
|
|
qDebug() << "Loading" << s_machine->loaderName().toLocal8Bit().data() << "Events:" << filename.toLocal8Bit().data();
|
|
|
|
return s_events_loaded = true;
|
|
}
|
|
|
|
bool Session::Destroy()
|
|
{
|
|
QDir dir;
|
|
QString base;
|
|
base.sprintf("%08lx", s_session);
|
|
|
|
QString summaryfile = s_machine->getSummariesPath() + base + ".000";
|
|
QString eventfile = s_machine->getEventsPath() + base + ".001";
|
|
if (!dir.remove(summaryfile)) {
|
|
// qDebug() << "Could not delete" << summaryfile;
|
|
}
|
|
if (!dir.remove(eventfile)) {
|
|
// qDebug() << "Could not delete" << eventfile;
|
|
}
|
|
|
|
return s_machine->unlinkSession(this); //!dir.exists(base + ".000") && !dir.exists(base + ".001");
|
|
}
|
|
|
|
bool Session::Store(QString path)
|
|
// Storing Session Data in our format
|
|
// {DataDir}/{MachineID}/{SessionID}.{ext}
|
|
{
|
|
QDir dir(path);
|
|
|
|
if (!dir.exists(path)) {
|
|
dir.mkpath(path);
|
|
}
|
|
|
|
//qDebug() << "Storing Session: " << base;
|
|
bool a;
|
|
|
|
a = StoreSummary(); // if actually has events
|
|
|
|
//qDebug() << " Summary done";
|
|
if (eventlist.size() > 0) {
|
|
StoreEvents();
|
|
} else { // who cares..
|
|
//qDebug() << "Trying to save empty events file";
|
|
}
|
|
|
|
//qDebug() << " Events done";
|
|
s_changed = false;
|
|
s_events_loaded = true;
|
|
|
|
//} else {
|
|
// qDebug() << "Session::Store() No event data saved" << s_session;
|
|
//}
|
|
|
|
return a;
|
|
}
|
|
/*QDataStream & operator<<(QDataStream & out, const Session & session)
|
|
{
|
|
session.StoreSummaryData(out);
|
|
return out;
|
|
}
|
|
|
|
void Session::StoreSummaryData(QDataStream & out) const
|
|
{
|
|
out << summary_version;
|
|
out << (quint32)s_session;
|
|
out << s_first; // Session Start Time
|
|
out << s_last; // Duration of sesion in seconds.
|
|
|
|
out << settings;
|
|
out << m_cnt;
|
|
out << m_sum;
|
|
out << m_avg;
|
|
out << m_wavg;
|
|
|
|
out << m_min;
|
|
out << m_max;
|
|
|
|
out << m_physmin;
|
|
out << m_physmax;
|
|
|
|
out << m_cph;
|
|
out << m_sph;
|
|
|
|
out << m_firstchan;
|
|
out << m_lastchan;
|
|
|
|
out << m_valuesummary;
|
|
out << m_timesummary;
|
|
|
|
out << m_gain;
|
|
|
|
out << m_availableChannels;
|
|
|
|
out << m_timeAboveTheshold;
|
|
out << m_upperThreshold;
|
|
out << m_timeBelowTheshold;
|
|
out << m_lowerThreshold;
|
|
|
|
out << s_summaryOnly;
|
|
}
|
|
|
|
QDataStream & operator>>(QDataStream & in, Session & session)
|
|
{
|
|
session.LoadSummaryData(in);
|
|
return in;
|
|
}
|
|
|
|
|
|
void Session::LoadSummaryData(QDataStream & in)
|
|
{
|
|
quint16 version;
|
|
in >> version;
|
|
|
|
quint32 t32;
|
|
in >> t32; // Sessionid;
|
|
s_session = t32;
|
|
|
|
in >> s_first; // Start time
|
|
in >> s_last; // Duration
|
|
|
|
in >> settings;
|
|
in >> m_cnt;
|
|
in >> m_sum;
|
|
in >> m_avg;
|
|
in >> m_wavg;
|
|
|
|
in >> m_min;
|
|
in >> m_max;
|
|
|
|
in >> m_physmin;
|
|
in >> m_physmax;
|
|
|
|
in >> m_cph;
|
|
in >> m_sph;
|
|
in >> m_firstchan;
|
|
in >> m_lastchan;
|
|
|
|
in >> m_valuesummary;
|
|
in >> m_timesummary;
|
|
|
|
in >> m_gain;
|
|
|
|
in >> m_availableChannels;
|
|
in >> m_timeAboveTheshold;
|
|
in >> m_upperThreshold;
|
|
in >> m_timeBelowTheshold;
|
|
in >> m_lowerThreshold;
|
|
|
|
in >> s_summaryOnly;
|
|
|
|
s_enabled = 1;
|
|
} */
|
|
|
|
QDataStream & operator>>(QDataStream & in, SessionSlice & slice)
|
|
{
|
|
in >> slice.start;
|
|
quint32 length;
|
|
in >> length;
|
|
slice.end = slice.start + length;
|
|
|
|
quint16 i;
|
|
in >> i;
|
|
slice.status = (SliceStatus)i;
|
|
return in;
|
|
}
|
|
QDataStream & operator<<(QDataStream & out, const SessionSlice & slice)
|
|
{
|
|
out << slice.start;
|
|
quint32 length = slice.end - slice.start;
|
|
out << length;
|
|
out << (quint16)slice.status;
|
|
return out;
|
|
}
|
|
|
|
bool Session::StoreSummary()
|
|
{
|
|
QString filename = s_machine->getSummariesPath() + QString().sprintf("%08lx.000", s_session);
|
|
|
|
QFile file(filename);
|
|
if (!file.open(QIODevice::WriteOnly)) {
|
|
QDir dir;
|
|
dir.mkpath(s_machine->getSummariesPath());
|
|
|
|
if (!file.open(QIODevice::WriteOnly)) {
|
|
qDebug() << "Summary open for writing failed";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
QDataStream out(&file);
|
|
out.setVersion(QDataStream::Qt_4_6);
|
|
out.setByteOrder(QDataStream::LittleEndian);
|
|
|
|
out << (quint32)magic;
|
|
out << (quint16)summary_version;
|
|
out << (quint16)filetype_summary;
|
|
out << (quint32)s_machine->id();
|
|
|
|
out << (quint32)s_session;
|
|
out << s_first; // Session Start Time
|
|
out << s_last; // Duration of sesion in seconds.
|
|
//out << (quint16)settings.size();
|
|
|
|
out << settings;
|
|
out << m_cnt;
|
|
|
|
out << m_sum;
|
|
out << m_avg;
|
|
out << m_wavg;
|
|
|
|
out << m_min;
|
|
out << m_max;
|
|
out << m_physmin;
|
|
out << m_physmax;
|
|
out << m_cph;
|
|
out << m_sph;
|
|
out << m_firstchan;
|
|
out << m_lastchan;
|
|
|
|
// <- 8
|
|
out << m_valuesummary;
|
|
out << m_timesummary;
|
|
// 8 ->
|
|
|
|
// <- 9
|
|
out << m_gain;
|
|
// 9 ->
|
|
|
|
// <- 15
|
|
out << m_availableChannels;
|
|
out << m_timeAboveTheshold;
|
|
out << m_upperThreshold;
|
|
out << m_timeBelowTheshold;
|
|
out << m_lowerThreshold;
|
|
|
|
out << s_summaryOnly;
|
|
// 13 ->
|
|
|
|
out << s_noSettings; // 18
|
|
|
|
out << m_slices;
|
|
|
|
file.close();
|
|
return true;
|
|
}
|
|
|
|
|
|
bool Session::LoadSummary()
|
|
{
|
|
//static int sumcnt = 0;
|
|
|
|
if (s_summary_loaded) return true;
|
|
QString filename = s_machine->getSummariesPath() + QString().sprintf("%08lx.000", s_session);
|
|
|
|
|
|
if (filename.isEmpty()) {
|
|
qDebug() << "Empty summary filename";
|
|
return false;
|
|
}
|
|
|
|
QFile file(filename);
|
|
|
|
if (!file.open(QIODevice::ReadOnly)) {
|
|
qDebug() << "Couldn't open summary file" << filename;
|
|
return false;
|
|
}
|
|
|
|
|
|
// qDebug() << "Loading" << s_machine->loaderName() << "Summary" << filename << sumcnt++;
|
|
|
|
QDataStream in(&file);
|
|
in.setVersion(QDataStream::Qt_4_6);
|
|
in.setByteOrder(QDataStream::LittleEndian);
|
|
|
|
quint32 t32;
|
|
quint16 t16;
|
|
|
|
//QHash<ChannelID,MCDataType> mctype;
|
|
//QVector<ChannelID> mcorder;
|
|
in >> t32;
|
|
|
|
if (t32 != magic) {
|
|
qDebug() << "Wrong magic number in " << filename;
|
|
return false;
|
|
}
|
|
|
|
quint16 version;
|
|
in >> version; // DB Version
|
|
|
|
if (version < 6) {
|
|
//throw OldDBVersion();
|
|
qWarning() << "Old dbversion " << version <<
|
|
"summary file.. Sorry, you need to purge and reimport";
|
|
return false;
|
|
}
|
|
|
|
in >> t16; // File Type
|
|
|
|
if (t16 != filetype_summary) {
|
|
qDebug() << "Wrong file type"; //wrong file type
|
|
return false;
|
|
}
|
|
|
|
|
|
qint32 ts32;
|
|
in >> ts32; // MachineID (dont need this result)
|
|
|
|
bool upgrade = false;
|
|
if (ts32 != s_machine->id()) {
|
|
upgrade = true;
|
|
qWarning() << "Machine ID does not match in" << filename <<
|
|
" I will try to load anyway in case you know what your doing.";
|
|
}
|
|
|
|
in >> t32; // Sessionid;
|
|
s_session = t32;
|
|
|
|
in >> s_first; // Start time
|
|
in >> s_last; // Duration // (16bit==Limited to 18 hours)
|
|
|
|
QHash<ChannelID, EventDataType> cruft;
|
|
|
|
if (version < 7) {
|
|
// This code is deprecated.. just here incase anyone tries anything crazy...
|
|
QHash<QString, QVariant> v1;
|
|
in >> v1;
|
|
settings.clear();
|
|
ChannelID code;
|
|
|
|
for (QHash<QString, QVariant>::iterator i = v1.begin(); i != v1.end(); i++) {
|
|
code = schema::channel[i.key()].id();
|
|
settings[code] = i.value();
|
|
}
|
|
|
|
QHash<QString, int> zcnt;
|
|
in >> zcnt;
|
|
for (QHash<QString, int>::iterator i = zcnt.begin(); i != zcnt.end(); i++) {
|
|
code = schema::channel[i.key()].id();
|
|
m_cnt[code] = i.value();
|
|
}
|
|
|
|
QHash<QString, double> zsum;
|
|
in >> zsum;
|
|
|
|
for (QHash<QString, double>::iterator i = zsum.begin(); i != zsum.end(); i++) {
|
|
code = schema::channel[i.key()].id();
|
|
m_sum[code] = i.value();
|
|
}
|
|
|
|
QHash<QString, EventDataType> ztmp;
|
|
in >> ztmp; // avg
|
|
|
|
for (QHash<QString, EventDataType>::iterator i = ztmp.begin(); i != ztmp.end(); i++) {
|
|
code = schema::channel[i.key()].id();
|
|
m_avg[code] = i.value();
|
|
}
|
|
|
|
ztmp.clear();
|
|
in >> ztmp; // wavg
|
|
|
|
for (QHash<QString, EventDataType>::iterator i = ztmp.begin(); i != ztmp.end(); i++) {
|
|
code = schema::channel[i.key()].id();
|
|
m_wavg[code] = i.value();
|
|
}
|
|
|
|
ztmp.clear();
|
|
in >> ztmp; // 90p
|
|
ztmp.clear();
|
|
in >> ztmp; // min
|
|
|
|
for (QHash<QString, EventDataType>::iterator i = ztmp.begin(); i != ztmp.end(); i++) {
|
|
code = schema::channel[i.key()].id();
|
|
m_min[code] = i.value();
|
|
}
|
|
|
|
ztmp.clear();
|
|
in >> ztmp; // max
|
|
|
|
for (QHash<QString, EventDataType>::iterator i = ztmp.begin(); i != ztmp.end(); i++) {
|
|
code = schema::channel[i.key()].id();
|
|
m_max[code] = i.value();
|
|
}
|
|
|
|
ztmp.clear();
|
|
in >> ztmp; // cph
|
|
|
|
for (QHash<QString, EventDataType>::iterator i = ztmp.begin(); i != ztmp.end(); i++) {
|
|
code = schema::channel[i.key()].id();
|
|
m_cph[code] = i.value();
|
|
}
|
|
|
|
ztmp.clear();
|
|
in >> ztmp; // sph
|
|
|
|
for (QHash<QString, EventDataType>::iterator i = ztmp.begin(); i != ztmp.end(); i++) {
|
|
code = schema::channel[i.key()].id();
|
|
m_sph[code] = i.value();
|
|
}
|
|
|
|
QHash<QString, quint64> ztim;
|
|
in >> ztim; //firstchan
|
|
|
|
for (QHash<QString, quint64>::iterator i = ztim.begin(); i != ztim.end(); i++) {
|
|
code = schema::channel[i.key()].id();
|
|
m_firstchan[code] = i.value();
|
|
}
|
|
|
|
ztim.clear();
|
|
in >> ztim; // lastchan
|
|
|
|
for (QHash<QString, quint64>::iterator i = ztim.begin(); i != ztim.end(); i++) {
|
|
code = schema::channel[i.key()].id();
|
|
m_lastchan[code] = i.value();
|
|
}
|
|
|
|
//SetChanged(true);
|
|
} else {
|
|
// version > 7
|
|
|
|
in >> settings;
|
|
if (version < 13) {
|
|
QHash<ChannelID, int> cnt2;
|
|
in >> cnt2;
|
|
|
|
QHash<ChannelID, int>::iterator it;
|
|
|
|
for (it = cnt2.begin(); it != cnt2.end(); ++it) {
|
|
m_cnt[it.key()] = it.value();
|
|
}
|
|
} else {
|
|
in >> m_cnt;
|
|
}
|
|
in >> m_sum;
|
|
in >> m_avg;
|
|
in >> m_wavg;
|
|
|
|
if (version < 11) {
|
|
cruft.clear();
|
|
in >> cruft; // 90%
|
|
|
|
if (version >= 10) {
|
|
cruft.clear();
|
|
in >> cruft;// med
|
|
cruft.clear();
|
|
in >> cruft; //p95
|
|
}
|
|
}
|
|
|
|
in >> m_min;
|
|
in >> m_max;
|
|
|
|
// Added 24/10/2013 by MW to support physical graph min/max values
|
|
if (version >= 12) {
|
|
in >> m_physmin;
|
|
in >> m_physmax;
|
|
}
|
|
|
|
in >> m_cph;
|
|
in >> m_sph;
|
|
in >> m_firstchan;
|
|
in >> m_lastchan;
|
|
|
|
if (version >= 8) {
|
|
in >> m_valuesummary;
|
|
in >> m_timesummary;
|
|
|
|
if (version >= 9) {
|
|
in >> m_gain;
|
|
}
|
|
}
|
|
|
|
// screwed up with version 14
|
|
if (version >= 15) {
|
|
in >> m_availableChannels;
|
|
in >> m_timeAboveTheshold;
|
|
in >> m_upperThreshold;
|
|
in >> m_timeBelowTheshold;
|
|
in >> m_lowerThreshold;
|
|
} // else this is ugly.. forced machine database upgrade will solve it though.
|
|
|
|
if (version == 13) {
|
|
QHash<ChannelID, QVariant>::iterator it = settings.find(CPAP_SummaryOnly);
|
|
if (it != settings.end()) {
|
|
s_summaryOnly = (*it).toBool();
|
|
} else s_summaryOnly = false;
|
|
} else if (version > 13) {
|
|
in >> s_summaryOnly;
|
|
}
|
|
if (version >= 18) {
|
|
in >> s_noSettings;
|
|
} else {
|
|
s_noSettings = (settings.size() == 0);
|
|
}
|
|
|
|
if (version == 16) {
|
|
QList<SessionSlice> slices;
|
|
in >> slices;
|
|
m_slices.clear();
|
|
for (int i=0;i<slices.size(); ++i) {
|
|
m_slices.append(slices[i]);
|
|
}
|
|
} else if (version >= 17) {
|
|
in >> m_slices;
|
|
}
|
|
}
|
|
|
|
// not really a good idea to do this... should flag and do a reindex
|
|
if (upgrade || (version < summary_version)) {
|
|
|
|
qDebug() << "Upgrading Summary file to version" << summary_version;
|
|
if (!s_summaryOnly) {
|
|
OpenEvents();
|
|
UpdateSummaries();
|
|
TrashEvents();
|
|
} else {
|
|
// summary only upgrades go here.
|
|
}
|
|
StoreSummary();
|
|
}
|
|
|
|
s_summary_loaded = true;
|
|
return true;
|
|
}
|
|
|
|
const quint16 compress_method = 1;
|
|
|
|
bool Session::StoreEvents()
|
|
{
|
|
QString path = s_machine->getEventsPath();
|
|
QDir dir;
|
|
dir.mkpath(path);
|
|
QString filename = path+QString().sprintf("%08lx.001", s_session);
|
|
|
|
QFile file(filename);
|
|
file.open(QIODevice::WriteOnly);
|
|
|
|
QByteArray headerbytes;
|
|
QDataStream header(&headerbytes, QIODevice::WriteOnly);
|
|
header.setVersion(QDataStream::Qt_4_6);
|
|
header.setByteOrder(QDataStream::LittleEndian);
|
|
|
|
header << (quint32)magic; // New Magic Number
|
|
header << (quint16)events_version; // File Version
|
|
header << (quint16)filetype_data; // File type 1 == Event
|
|
header << (quint32)s_machine->id();// Machine Type
|
|
header << (quint32)s_session; // This session's ID
|
|
header << s_first;
|
|
header << s_last;
|
|
|
|
quint16 compress = 0;
|
|
|
|
if (p_profile->session->compressSessionData()) {
|
|
compress = compress_method;
|
|
}
|
|
|
|
header << (quint16)compress;
|
|
|
|
header << (quint16)s_machine->type();// Machine Type
|
|
|
|
QByteArray databytes;
|
|
QDataStream out(&databytes, QIODevice::WriteOnly);
|
|
out.setVersion(QDataStream::Qt_4_6);
|
|
out.setByteOrder(QDataStream::LittleEndian);
|
|
|
|
out << (qint16)eventlist.size(); // Number of event categories
|
|
|
|
QHash<ChannelID, QVector<EventList *> >::iterator i;
|
|
QHash<ChannelID, QVector<EventList *> >::iterator i_end=eventlist.end();
|
|
|
|
qint16 ev_size;
|
|
|
|
for (i = eventlist.begin(); i != i_end; i++) {
|
|
ev_size=i.value().size();
|
|
|
|
out << i.key(); // ChannelID
|
|
out << (qint16)ev_size;
|
|
|
|
|
|
for (int j = 0; j < ev_size; j++) {
|
|
EventList &e = *i.value()[j];
|
|
out << e.first();
|
|
out << e.last();
|
|
out << (qint32)e.count();
|
|
out << (qint8)e.type();
|
|
out << e.rate();
|
|
out << e.gain();
|
|
out << e.offset();
|
|
out << e.Min();
|
|
out << e.Max();
|
|
out << e.dimension();
|
|
out << e.hasSecondField();
|
|
|
|
if (e.hasSecondField()) {
|
|
out << e.min2();
|
|
out << e.max2();
|
|
}
|
|
}
|
|
}
|
|
for (i = eventlist.begin(); i != i_end; i++) {
|
|
ev_size=i.value().size();
|
|
|
|
for (int j = 0; j < ev_size; j++) {
|
|
EventList &e = *i.value()[j];
|
|
// ****** This is assuming little endian ******
|
|
|
|
// Store the raw event list data in EventStoreType (16bit short)
|
|
EventStoreType *ptr = e.m_data.data();
|
|
out.writeRawData((char *)ptr, e.count() << 1);
|
|
|
|
//*** Don't delete these comments ***
|
|
// for (quint32 c=0;c<e.count();c++) {
|
|
// out << *ptr++;//e.raw(c);
|
|
// }
|
|
|
|
// Store the second field, only if there
|
|
if (e.hasSecondField()) {
|
|
ptr = e.m_data2.data();
|
|
out.writeRawData((char *)ptr, e.count() << 1);
|
|
//*** Don't delete these comments ***
|
|
// for (quint32 c=0;c<e.count();c++) {
|
|
// out << *ptr++; //e.raw2(c);
|
|
// }
|
|
}
|
|
|
|
// Store the time delta fields for non-waveform EventLists
|
|
if (e.type() != EVL_Waveform) {
|
|
quint32 *tptr = e.m_time.data();
|
|
out.writeRawData((char *)tptr, e.count() << 2);
|
|
//*** Don't delete these comments ***
|
|
// for (quint32 c=0;c<e.count();c++) {
|
|
// out << *tptr++; //e.getTime()[c];
|
|
// }
|
|
}
|
|
}
|
|
}
|
|
|
|
qint32 datasize = databytes.size();
|
|
|
|
// Checksum the _uncompressed_ data
|
|
quint16 chk = 0;
|
|
|
|
if (compress) {
|
|
// This checksum is hideously slow.. only using during compression, and not sure I should at all :-/
|
|
chk = qChecksum(databytes.data(), databytes.size());
|
|
}
|
|
|
|
header << datasize;
|
|
header << chk;
|
|
|
|
QByteArray data;
|
|
|
|
if (compress > 0) {
|
|
data = qCompress(databytes);
|
|
} else {
|
|
data = databytes;
|
|
}
|
|
|
|
file.write(headerbytes);
|
|
file.write(data);
|
|
file.close();
|
|
return true;
|
|
}
|
|
bool Session::LoadEvents(QString filename)
|
|
{
|
|
quint32 magicnum, machid, sessid;
|
|
quint16 version, type, crc16, machtype, compmethod;
|
|
quint8 t8;
|
|
qint32 datasize;
|
|
|
|
if (filename.isEmpty()) {
|
|
qDebug() << "Session::LoadEvents() Filename is empty";
|
|
return false;
|
|
}
|
|
|
|
QFile file(filename);
|
|
|
|
if (!file.open(QIODevice::ReadOnly)) {
|
|
qDebug() << "No Event/Waveform data available for" << s_session;
|
|
return false;
|
|
}
|
|
|
|
QByteArray headerbytes = file.read(42);
|
|
|
|
QDataStream header(headerbytes);
|
|
header.setVersion(QDataStream::Qt_4_6);
|
|
header.setByteOrder(QDataStream::LittleEndian);
|
|
|
|
header >> magicnum; // Magic Number (quint32)
|
|
header >> version; // Version (quint16)
|
|
header >> type; // File type (quint16)
|
|
header >> machid; // Machine ID (quint32)
|
|
header >> sessid; //(quint32)
|
|
header >> s_first; //(qint64)
|
|
header >> s_last; //(qint64)
|
|
|
|
if (type != filetype_data) {
|
|
qDebug() << "Wrong File Type in " << filename;
|
|
return false;
|
|
}
|
|
|
|
if (magicnum != magic) {
|
|
qWarning() << "Wrong Magic number in " << filename;
|
|
return false;
|
|
}
|
|
|
|
if (version < 6) { // prior to version 6 is too old to deal with
|
|
qDebug() << "Old File Version, can't open file";
|
|
return false;
|
|
}
|
|
|
|
if (version < 10) {
|
|
file.seek(32);
|
|
} else {
|
|
header >> compmethod; // Compression Method (quint16)
|
|
header >> machtype; // Machine Type (quint16)
|
|
header >> datasize; // Size of Uncompressed Data (quint32)
|
|
header >> crc16; // CRC16 of Uncompressed Data (quint16)
|
|
}
|
|
|
|
QByteArray databytes, temp = file.readAll();
|
|
file.close();
|
|
|
|
if (version >= 10) {
|
|
if (compmethod > 0) {
|
|
databytes = qUncompress(temp);
|
|
|
|
if (!s_evchecksum_checked) {
|
|
if (databytes.size() != datasize) {
|
|
qDebug() << "File" << filename << "has returned wrong datasize";
|
|
return false;
|
|
}
|
|
|
|
quint16 crc = qChecksum(databytes.data(), databytes.size());
|
|
|
|
if (crc != crc16) {
|
|
qDebug() << "CRC Doesn't match in" << filename;
|
|
return false;
|
|
}
|
|
|
|
s_evchecksum_checked = true;
|
|
}
|
|
} else {
|
|
databytes = temp;
|
|
}
|
|
} else { databytes = temp; }
|
|
|
|
QDataStream in(databytes);
|
|
in.setVersion(QDataStream::Qt_4_6);
|
|
in.setByteOrder(QDataStream::LittleEndian);
|
|
|
|
qint16 mcsize;
|
|
in >> mcsize; // number of Machine Code lists
|
|
|
|
ChannelID code;
|
|
qint64 ts1, ts2;
|
|
qint32 evcount;
|
|
EventListType elt;
|
|
EventDataType rate, gain, offset, mn, mx;
|
|
qint16 size2;
|
|
QVector<ChannelID> mcorder;
|
|
QVector<qint16> sizevec;
|
|
QString dim;
|
|
|
|
for (int i = 0; i < mcsize; i++) {
|
|
if (version < 8) {
|
|
QString txt;
|
|
in >> txt;
|
|
code = schema::channel[txt].id();
|
|
} else {
|
|
in >> code;
|
|
}
|
|
|
|
mcorder.push_back(code);
|
|
in >> size2;
|
|
sizevec.push_back(size2);
|
|
|
|
for (int j = 0; j < size2; j++) {
|
|
in >> ts1;
|
|
in >> ts2;
|
|
in >> evcount;
|
|
in >> t8;
|
|
elt = (EventListType)t8;
|
|
in >> rate;
|
|
in >> gain;
|
|
in >> offset;
|
|
in >> mn;
|
|
in >> mx;
|
|
in >> dim;
|
|
bool second_field = false;
|
|
|
|
if (version >= 7) { // version 7 added this field
|
|
in >> second_field;
|
|
}
|
|
|
|
EventList *elist = AddEventList(code, elt, gain, offset, mn, mx, rate, second_field);
|
|
elist->setDimension(dim);
|
|
|
|
//eventlist[code].push_back(elist);
|
|
elist->m_count = evcount;
|
|
elist->m_first = ts1;
|
|
elist->m_last = ts2;
|
|
|
|
if (second_field) {
|
|
EventDataType min, max;
|
|
in >> min;
|
|
in >> max;
|
|
elist->setMin2(min);
|
|
elist->setMax2(max);
|
|
}
|
|
}
|
|
}
|
|
|
|
//EventStoreType t;
|
|
//quint32 x;
|
|
|
|
for (int i = 0; i < mcsize; i++) {
|
|
code = mcorder[i];
|
|
size2 = sizevec[i];
|
|
|
|
for (int j = 0; j < size2; j++) {
|
|
EventList &evec = *eventlist[code][j];
|
|
evec.m_data.resize(evec.m_count);
|
|
EventStoreType *ptr = evec.m_data.data();
|
|
|
|
// ****** This is assuming little endian ******
|
|
|
|
in.readRawData((char *)ptr, evec.m_count << 1);
|
|
|
|
//*** Don't delete these comments ***
|
|
// for (quint32 c=0;c<evec.m_count;c++) {
|
|
// in >> t;
|
|
// *ptr++=t;
|
|
// }
|
|
if (evec.hasSecondField()) {
|
|
evec.m_data2.resize(evec.m_count);
|
|
ptr = evec.m_data2.data();
|
|
|
|
in.readRawData((char *)ptr, evec.m_count << 1);
|
|
//*** Don't delete these comments ***
|
|
// for (quint32 c=0;c<evec.m_count;c++) {
|
|
// in >> t;
|
|
// *ptr++=t;
|
|
// }
|
|
}
|
|
|
|
if (evec.type() != EVL_Waveform) {
|
|
evec.m_time.resize(evec.m_count);
|
|
quint32 *tptr = evec.m_time.data();
|
|
|
|
in.readRawData((char *)tptr, evec.m_count << 2);
|
|
//*** Don't delete these comments ***
|
|
// for (quint32 c=0;c<evec.m_count;c++) {
|
|
// in >> x;
|
|
// *tptr++=x;
|
|
// }
|
|
}
|
|
}
|
|
}
|
|
|
|
if (version < events_version) {
|
|
qDebug() << "Upgrading Events file" << filename << "to version" << events_version;
|
|
UpdateSummaries();
|
|
StoreEvents();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Session::destroyEvent(ChannelID code)
|
|
{
|
|
QHash<ChannelID, QVector<EventList *> >::iterator it = eventlist.find(code);
|
|
|
|
if (it != eventlist.end()) {
|
|
for (int i = 0; i < it.value().size(); i++) {
|
|
delete it.value()[i];
|
|
}
|
|
|
|
eventlist.erase(it);
|
|
}
|
|
|
|
m_gain.erase(m_gain.find(code));
|
|
m_firstchan.erase(m_firstchan.find(code));
|
|
m_lastchan.erase(m_lastchan.find(code));
|
|
m_sph.erase(m_sph.find(code));
|
|
m_cph.erase(m_cph.find(code));
|
|
m_min.erase(m_min.find(code));
|
|
m_max.erase(m_max.find(code));
|
|
m_avg.erase(m_avg.find(code));
|
|
m_wavg.erase(m_wavg.find(code));
|
|
m_sum.erase(m_sum.find(code));
|
|
m_cnt.erase(m_cnt.find(code));
|
|
m_valuesummary.erase(m_valuesummary.find(code));
|
|
m_timesummary.erase(m_timesummary.find(code));
|
|
// does not trash settings..
|
|
}
|
|
|
|
void Session::updateCountSummary(ChannelID code)
|
|
{
|
|
QHash<ChannelID, QVector<EventList *> >::iterator ev = eventlist.find(code);
|
|
|
|
if (ev == eventlist.end()) { return; }
|
|
|
|
QHash<ChannelID, QHash<EventStoreType, EventStoreType> >::iterator vs = m_valuesummary.find(code);
|
|
|
|
if (vs != m_valuesummary.end()) { // already calculated?
|
|
return;
|
|
}
|
|
|
|
QHash<EventStoreType, EventStoreType> valsum;
|
|
QHash<EventStoreType, quint32> timesum;
|
|
|
|
QHash<EventStoreType, EventStoreType>::iterator it;
|
|
QHash<EventStoreType, EventStoreType>::iterator valsum_end;
|
|
|
|
EventDataType raw, lastraw = 0;
|
|
qint64 start, time, lasttime = 0;
|
|
qint32 len, cnt;
|
|
quint32 *tptr;
|
|
EventStoreType *dptr, * eptr;
|
|
|
|
int ev_size=ev.value().size();
|
|
for (int i = 0; i < ev_size; i++) {
|
|
EventList &e = *(ev.value()[i]);
|
|
start = e.first();
|
|
cnt = e.count();
|
|
dptr = e.rawData();
|
|
eptr = dptr + cnt;
|
|
|
|
EventDataType rate = 0;
|
|
|
|
m_gain[code] = e.gain();
|
|
|
|
if (e.type() == EVL_Event) {
|
|
lastraw = *dptr++;
|
|
tptr = e.rawTime();
|
|
lasttime = start + *tptr++;
|
|
// Event version
|
|
|
|
for (; dptr < eptr; dptr++) {
|
|
time = start + *tptr++;
|
|
raw = *dptr;
|
|
|
|
valsum[raw]++;
|
|
|
|
// elapsed time in seconds since last event occurred
|
|
len = (time - lasttime) / 1000L;
|
|
|
|
timesum[lastraw] += len;
|
|
|
|
lastraw = raw;
|
|
lasttime = time;
|
|
}
|
|
} else {
|
|
// Waveform version, first just count
|
|
for (; dptr < eptr; dptr++) {
|
|
raw = *dptr;
|
|
valsum[raw]++;
|
|
}
|
|
|
|
// Then process the list of values, time is simply (rate * count)
|
|
rate = e.rate();
|
|
EventDataType t;
|
|
|
|
QHash<EventStoreType, EventStoreType>::iterator it = valsum.begin();
|
|
QHash<EventStoreType, EventStoreType>::iterator valsum_end = valsum.end();
|
|
|
|
for (; it != valsum_end; ++it) {
|
|
t = EventDataType(it.value()) * rate;
|
|
timesum[it.key()] += t;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (valsum.size() == 0) { return; }
|
|
|
|
m_valuesummary[code] = valsum;
|
|
m_timesummary[code] = timesum;
|
|
}
|
|
|
|
void Session::UpdateSummaries()
|
|
{
|
|
ChannelID id;
|
|
|
|
// Generate that AHI per hour graph in daily view.
|
|
calcAHIGraph(this);
|
|
|
|
// Calculates RespRate and related waveforms (Tv, MV, Te, Ti) if missing
|
|
calcRespRate(this);
|
|
|
|
// Generate unintentional leaks if not present
|
|
calcLeaks(this);
|
|
|
|
// Flag the Large Leaks if unintentional leaks is available, and no LargeLeaks weren't flagged by the machine already.
|
|
flagLargeLeaks(this);
|
|
|
|
calcSPO2Drop(this);
|
|
calcPulseChange(this);
|
|
|
|
QHash<ChannelID, QVector<EventList *> >::iterator c = eventlist.begin();
|
|
QHash<ChannelID, QVector<EventList *> >::iterator ev_end = eventlist.end();
|
|
|
|
m_availableChannels.clear();
|
|
|
|
for (; c != ev_end; c++) {
|
|
id = c.key();
|
|
m_availableChannels.push_back(id);
|
|
|
|
schema::ChanType ctype = schema::channel[id].type();
|
|
if (ctype != schema::SETTING) {
|
|
//sum(id); // avg calculates this and cnt.
|
|
if (c.value().size() > 0) {
|
|
EventList *el = c.value()[0];
|
|
EventDataType gain = el->gain();
|
|
m_gain[id] = gain;
|
|
}
|
|
|
|
if (!((id == CPAP_FlowRate) || (id == CPAP_MaskPressureHi) || (id == CPAP_RespEvent)
|
|
|| (id == CPAP_MaskPressure))) {
|
|
updateCountSummary(id);
|
|
}
|
|
|
|
Min(id);
|
|
Max(id);
|
|
count(id);
|
|
last(id);
|
|
first(id);
|
|
|
|
if (((id == CPAP_FlowRate)
|
|
|| (id == CPAP_MaskPressureHi)
|
|
|| (id == CPAP_RespEvent)
|
|
|| (id == CPAP_MaskPressure))) {
|
|
continue;
|
|
}
|
|
|
|
cph(id);
|
|
sph(id);
|
|
avg(id);
|
|
wavg(id);
|
|
}
|
|
}
|
|
timeAboveThreshold(CPAP_Leak, p_profile->cpap->leakRedline());
|
|
|
|
s_machine->updateChannels(this);
|
|
}
|
|
|
|
EventDataType Session::SearchValue(ChannelID code, qint64 time, bool square)
|
|
{
|
|
qint64 t1, t2, start;
|
|
QHash<ChannelID, QVector<EventList *> >::iterator it;
|
|
it = eventlist.find(code);
|
|
quint32 *tptr;
|
|
int cnt;
|
|
|
|
EventDataType a,b,c,d,e;
|
|
if (it != eventlist.end()) {
|
|
int el_size=it.value().size();
|
|
for (int i = 0; i < el_size; i++) {
|
|
EventList *el = it.value()[i];
|
|
if ((time >= el->first()) && (time < el->last())) {
|
|
cnt = el->count();
|
|
|
|
if (el->type() == EVL_Waveform) {
|
|
qint64 tt = time - el->first();
|
|
|
|
double i = tt / el->rate();
|
|
if (i> cnt) {
|
|
qWarning() << "Session" << session() << "time bounds are broken.. There is a fault in the" << machine()->loaderName().toLocal8Bit().data() << "loader";
|
|
return 0;
|
|
}
|
|
|
|
int i1 = int(floor(i));
|
|
int i2 = int(ceil(i));
|
|
|
|
a = el->data(i1);
|
|
|
|
if (i2 >= cnt) { return a; }
|
|
|
|
qint64 t1 = i1 * el->rate();
|
|
qint64 t2 = i2 * el->rate();
|
|
|
|
c = EventDataType(t2 - t1);
|
|
if (c == 0) return 0;
|
|
d = EventDataType(t2 - tt);
|
|
|
|
e = d/c;
|
|
b = el->data(i2);
|
|
|
|
return b + ((a-b) * e);
|
|
|
|
} else {
|
|
start = el->first();
|
|
tptr = el->rawTime();
|
|
// TODO: square plots need fixing
|
|
if (square) {
|
|
for (int j = 0; j < cnt-1; ++j) {
|
|
tptr++;
|
|
t2 = start + *tptr;
|
|
if (t2 > time) {
|
|
return el->data(j);
|
|
}
|
|
}
|
|
} else {
|
|
for (int j = 0; j < cnt-1; ++j) {
|
|
tptr++;
|
|
t2 = start + *tptr;
|
|
if (t2 > time) {
|
|
tptr--;
|
|
t1 = start + *tptr;
|
|
c = EventDataType(t2 - t1);
|
|
d = EventDataType(t2 - time);
|
|
e = d/c;
|
|
a = el->data(j);
|
|
b = el->data(j+1);
|
|
if (a == b) {
|
|
return a;
|
|
} else {
|
|
return b + ((a-b) * e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
QString Session::dimension(ChannelID id)
|
|
{
|
|
// Cheat for now
|
|
return schema::channel[id].units();
|
|
}
|
|
|
|
EventDataType Session::Min(ChannelID id)
|
|
{
|
|
QHash<ChannelID, EventDataType>::iterator i = m_min.find(id);
|
|
|
|
if (i != m_min.end()) {
|
|
return i.value();
|
|
}
|
|
|
|
QHash<ChannelID, QVector<EventList *> >::iterator j = eventlist.find(id);
|
|
|
|
if (j == eventlist.end()) {
|
|
m_min[id] = 0;
|
|
return 0;
|
|
}
|
|
|
|
QVector<EventList *> &evec = j.value();
|
|
|
|
bool first = true;
|
|
EventDataType min = 0, t1;
|
|
|
|
int evec_size = evec.size();
|
|
|
|
for (int i = 0; i < evec_size; ++i) {
|
|
if (evec[i]->count() != 0) {
|
|
t1 = evec[i]->Min();
|
|
|
|
if ((t1 == 0) && (t1 == evec[i]->Max())) { continue; }
|
|
|
|
if (first) {
|
|
min = t1;
|
|
first = false;
|
|
} else {
|
|
if (min > t1) { min = t1; }
|
|
}
|
|
}
|
|
}
|
|
|
|
m_min[id] = min;
|
|
return min;
|
|
}
|
|
|
|
EventDataType Session::Max(ChannelID id)
|
|
{
|
|
QHash<ChannelID, EventDataType>::iterator i = m_max.find(id);
|
|
|
|
if (i != m_max.end()) {
|
|
return i.value();
|
|
}
|
|
|
|
QHash<ChannelID, QVector<EventList *> >::iterator j = eventlist.find(id);
|
|
|
|
if (j == eventlist.end()) {
|
|
m_max[id] = 0;
|
|
return 0;
|
|
}
|
|
|
|
QVector<EventList *> &evec = j.value();
|
|
|
|
bool first = true;
|
|
EventDataType max = 0, t1;
|
|
|
|
int evec_size=evec.size();
|
|
|
|
for (int i = 0; i < evec_size; ++i) {
|
|
if (evec.at(i)->count() != 0) {
|
|
t1 = evec.at(i)->Max();
|
|
|
|
if (t1 == 0 && t1 == evec.at(i)->Min()) { continue; }
|
|
|
|
if (first) {
|
|
max = t1;
|
|
first = false;
|
|
} else {
|
|
if (max < t1) { max = t1; }
|
|
}
|
|
}
|
|
}
|
|
|
|
m_max[id] = max;
|
|
return max;
|
|
}
|
|
|
|
////
|
|
EventDataType Session::physMin(ChannelID id)
|
|
{
|
|
QHash<ChannelID, EventDataType>::iterator i = m_physmin.find(id);
|
|
|
|
if (i != m_physmin.end()) {
|
|
return i.value();
|
|
}
|
|
|
|
QHash<ChannelID, QVector<EventList *> >::iterator j = eventlist.find(id);
|
|
|
|
if (j == eventlist.end()) {
|
|
m_physmin[id] = 0;
|
|
return 0;
|
|
}
|
|
|
|
EventDataType min = floor(Min(id));
|
|
m_physmin[id] = min;
|
|
return min;
|
|
}
|
|
|
|
EventDataType Session::physMax(ChannelID id)
|
|
{
|
|
QHash<ChannelID, EventDataType>::iterator i = m_physmax.find(id);
|
|
|
|
if (i != m_physmax.end()) {
|
|
return i.value();
|
|
}
|
|
|
|
QHash<ChannelID, QVector<EventList *> >::iterator j = eventlist.find(id);
|
|
|
|
if (j == eventlist.end()) {
|
|
m_physmax[id] = 0;
|
|
return 0;
|
|
}
|
|
|
|
EventDataType max = ceil(Max(id) + 0.5);
|
|
m_physmax[id] = max;
|
|
return max;
|
|
}
|
|
|
|
|
|
qint64 Session::first(ChannelID id)
|
|
{
|
|
qint64 drift = qint64(p_profile->cpap->clockDrift()) * 1000L;
|
|
qint64 tmp;
|
|
QHash<ChannelID, quint64>::iterator i = m_firstchan.find(id);
|
|
|
|
if (i != m_firstchan.end()) {
|
|
tmp = i.value();
|
|
|
|
if (s_machine->type() == MT_CPAP) {
|
|
tmp += drift;
|
|
}
|
|
|
|
return tmp;
|
|
}
|
|
|
|
QHash<ChannelID, QVector<EventList *> >::iterator j = eventlist.find(id);
|
|
|
|
if (j == eventlist.end()) {
|
|
return 0;
|
|
}
|
|
|
|
QVector<EventList *> &evec = j.value();
|
|
|
|
bool first = true;
|
|
qint64 min = 0, t1;
|
|
|
|
int evec_size=evec.size();
|
|
for (int i = 0; i < evec_size; ++i) {
|
|
t1 = evec[i]->first();
|
|
|
|
if (first) {
|
|
min = t1;
|
|
first = false;
|
|
} else {
|
|
if (min > t1) { min = t1; }
|
|
}
|
|
}
|
|
|
|
m_firstchan[id] = min;
|
|
|
|
if (s_machine->type() == MT_CPAP) {
|
|
min += drift;
|
|
}
|
|
|
|
return min;
|
|
}
|
|
qint64 Session::last(ChannelID id)
|
|
{
|
|
qint64 drift = qint64(p_profile->cpap->clockDrift()) * 1000L;
|
|
qint64 tmp;
|
|
QHash<ChannelID, quint64>::iterator i = m_lastchan.find(id);
|
|
|
|
if (i != m_lastchan.end()) {
|
|
tmp = i.value();
|
|
|
|
if (s_machine->type() == MT_CPAP) {
|
|
tmp += drift;
|
|
}
|
|
|
|
return tmp;
|
|
}
|
|
|
|
QHash<ChannelID, QVector<EventList *> >::iterator j = eventlist.find(id);
|
|
|
|
if (j == eventlist.end()) {
|
|
return 0;
|
|
}
|
|
|
|
QVector<EventList *> &evec = j.value();
|
|
|
|
bool first = true;
|
|
qint64 max = 0, t1;
|
|
|
|
int evec_size=evec.size();
|
|
|
|
for (int i = 0; i < evec_size; ++i) {
|
|
t1 = evec[i]->last();
|
|
|
|
if (first) {
|
|
max = t1;
|
|
first = false;
|
|
} else {
|
|
if (max < t1) { max = t1; }
|
|
}
|
|
}
|
|
|
|
m_lastchan[id] = max;
|
|
|
|
if (s_machine->type() == MT_CPAP) {
|
|
max += drift;
|
|
}
|
|
|
|
return max;
|
|
}
|
|
bool Session::channelDataExists(ChannelID id)
|
|
{
|
|
if (s_events_loaded) {
|
|
QHash<ChannelID, QVector<EventList *> >::iterator j = eventlist.find(id);
|
|
|
|
if (j == eventlist.end()) { // eventlist not loaded.
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
} else {
|
|
qDebug() << "Calling channelDataExists without open eventdata!";
|
|
}
|
|
|
|
return false;
|
|
}
|
|
bool Session::channelExists(ChannelID id)
|
|
{
|
|
if (!enabled()) { return false; }
|
|
|
|
if (s_events_loaded) {
|
|
QHash<ChannelID, QVector<EventList *> >::iterator j = eventlist.find(id);
|
|
|
|
if (j == eventlist.end()) { // eventlist not loaded.
|
|
return false;
|
|
}
|
|
} else {
|
|
QHash<ChannelID, EventDataType>::iterator q = m_cnt.find(id);
|
|
|
|
if (q == m_cnt.end()) {
|
|
return false;
|
|
}
|
|
|
|
if (q.value() == 0) { return false; }
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
EventDataType Session::countInsideSpan(ChannelID span, ChannelID code)
|
|
{
|
|
// TODO: Cache me!
|
|
|
|
QHash<ChannelID, QVector<EventList *> >::iterator j = eventlist.find(span);
|
|
|
|
if (j == eventlist.end()) {
|
|
return 0;
|
|
}
|
|
QVector<EventList *> &evec = j.value();
|
|
|
|
qint64 t1,t2;
|
|
|
|
int evec_size=evec.size();
|
|
|
|
QList<qint64> start;
|
|
QList<qint64> end;
|
|
|
|
// Simplify the span flags to start and end times list
|
|
for (int el = 0; el < evec_size; ++el) {
|
|
EventList &ev = *evec[el];
|
|
|
|
for (quint32 i=0; i < ev.count(); ++i) {
|
|
end.push_back(t2=ev.time(i));
|
|
start.push_back(t2 - (qint64(ev.data(i)) * 1000L));
|
|
}
|
|
}
|
|
|
|
j = eventlist.find(code);
|
|
|
|
if (j == eventlist.end()) {
|
|
return 0;
|
|
}
|
|
QVector<EventList *> &evec2 = j.value();
|
|
evec_size=evec2.size();
|
|
int count = 0;
|
|
|
|
int spans = start.size();
|
|
|
|
for (int el = 0; el < evec_size; ++el) {
|
|
EventList &ev = *evec2[el];
|
|
|
|
for (quint32 i=0; i < ev.count(); ++i) {
|
|
t1 = ev.time(i);
|
|
for (int z=0; z < spans; ++z) {
|
|
if ((t1 >= start.at(z)) && (t1 <= end.at(z))) {
|
|
count++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
EventDataType Session::rangeCount(ChannelID id, qint64 first, qint64 last)
|
|
{
|
|
QHash<ChannelID, QVector<EventList *> >::iterator j = eventlist.find(id);
|
|
|
|
if (j == eventlist.end()) {
|
|
return 0;
|
|
}
|
|
|
|
QVector<EventList *> &evec = j.value();
|
|
int total = 0, cnt;
|
|
|
|
qint64 t, start;
|
|
|
|
int evec_size=evec.size();
|
|
|
|
for (int i = 0; i < evec_size; ++i) {
|
|
EventList &ev = *evec[i];
|
|
|
|
if ((ev.last() < first) || (ev.first() > last)) {
|
|
continue;
|
|
}
|
|
|
|
if (ev.type() == EVL_Waveform) {
|
|
qint64 et = last;
|
|
|
|
if (et > ev.last()) {
|
|
et = ev.last();
|
|
}
|
|
|
|
qint64 st = first;
|
|
|
|
if (st < ev.first()) {
|
|
st = ev.first();
|
|
}
|
|
|
|
t = (et - st) / ev.rate();
|
|
total += t;
|
|
} else {
|
|
cnt = ev.count();
|
|
start = ev.first();
|
|
quint32 *tptr = ev.rawTime();
|
|
quint32 *eptr = tptr + cnt;
|
|
|
|
for (; tptr < eptr; tptr++) {
|
|
t = start + *tptr;
|
|
|
|
if (t >= first) {
|
|
if (t <= last) {
|
|
total++;
|
|
} else { break; }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return (EventDataType)total;
|
|
}
|
|
|
|
double Session::rangeSum(ChannelID id, qint64 first, qint64 last)
|
|
{
|
|
QHash<ChannelID, QVector<EventList *> >::iterator j = eventlist.find(id);
|
|
|
|
if (j == eventlist.end()) {
|
|
return 0;
|
|
}
|
|
|
|
QVector<EventList *> &evec = j.value();
|
|
double sum = 0, gain;
|
|
|
|
qint64 t, start;
|
|
EventStoreType *dptr, * eptr;
|
|
quint32 *tptr;
|
|
int cnt, idx = 0;
|
|
|
|
qint64 rate;
|
|
|
|
int evec_size=evec.size();
|
|
|
|
for (int i = 0; i < evec_size; i++) {
|
|
EventList &ev = *evec[i];
|
|
|
|
if ((ev.last() < first) || (ev.first() > last)) {
|
|
continue;
|
|
}
|
|
|
|
start = ev.first();
|
|
dptr = ev.rawData();
|
|
cnt = ev.count();
|
|
eptr = dptr + cnt;
|
|
gain = ev.gain();
|
|
rate = ev.rate();
|
|
|
|
if (ev.type() == EVL_Waveform) {
|
|
if (first > ev.first()) {
|
|
// Skip the samples before first
|
|
idx = (first - ev.first()) / rate;
|
|
}
|
|
|
|
dptr += idx; //???? foggy.
|
|
|
|
t = start;
|
|
|
|
for (; dptr < eptr; dptr++) { //int j=idx;j<cnt;j++) {
|
|
if (t <= last) {
|
|
sum += EventDataType(*dptr) * gain;
|
|
} else { break; }
|
|
|
|
t += rate;
|
|
}
|
|
} else {
|
|
tptr = ev.rawTime();
|
|
|
|
for (; dptr < eptr; dptr++) {
|
|
t = start + *tptr++;
|
|
|
|
if (t >= first) {
|
|
if (t <= last) {
|
|
sum += EventDataType(*dptr) * gain;
|
|
} else { break; }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return sum;
|
|
}
|
|
EventDataType Session::rangeMin(ChannelID id, qint64 first, qint64 last)
|
|
{
|
|
QHash<ChannelID, QVector<EventList *> >::iterator j = eventlist.find(id);
|
|
|
|
if (j == eventlist.end()) {
|
|
return 0;
|
|
}
|
|
|
|
QVector<EventList *> &evec = j.value();
|
|
EventDataType gain, v, min = std::numeric_limits<EventDataType>::max();
|
|
|
|
qint64 t, start, rate;
|
|
EventStoreType *dptr, * eptr;
|
|
quint32 *tptr;
|
|
int cnt, idx;
|
|
|
|
int evec_size=evec.size();
|
|
|
|
for (int i = 0; i < evec_size; ++i) {
|
|
EventList &ev = *evec[i];
|
|
|
|
if ((ev.last() < first) || (ev.first() > last)) {
|
|
continue;
|
|
}
|
|
|
|
dptr = ev.rawData();
|
|
start = ev.first();
|
|
cnt = ev.count();
|
|
eptr = dptr + cnt;
|
|
gain = ev.gain();
|
|
|
|
if (ev.type() == EVL_Waveform) {
|
|
rate = ev.rate();
|
|
t = start;
|
|
idx = 0;
|
|
|
|
if (first > ev.first()) {
|
|
// Skip the samples before first
|
|
idx = (first - ev.first()) / rate;
|
|
}
|
|
|
|
dptr += idx;
|
|
|
|
for (; dptr < eptr; dptr++) { //int j=idx;j<cnt;j++) {
|
|
if (t <= last) {
|
|
v = EventDataType(*dptr) * gain;
|
|
|
|
if (v < min) {
|
|
min = v;
|
|
}
|
|
} else { break; }
|
|
|
|
t += rate;
|
|
}
|
|
} else {
|
|
tptr = ev.rawTime();
|
|
|
|
for (; dptr < eptr; dptr++) { //int j=0;j<cnt;j++) {
|
|
t = start + *tptr++;
|
|
|
|
if (t >= first) {
|
|
if (t <= last) {
|
|
v = EventDataType(*dptr) * gain;
|
|
|
|
if (v < min) {
|
|
min = v;
|
|
}
|
|
} else { break; }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return min;
|
|
}
|
|
|
|
EventDataType Session::rangeMax(ChannelID id, qint64 first, qint64 last)
|
|
{
|
|
QHash<ChannelID, QVector<EventList *> >::iterator j = eventlist.find(id);
|
|
|
|
if (j == eventlist.end()) {
|
|
return 0;
|
|
}
|
|
|
|
QVector<EventList *> &evec = j.value();
|
|
EventDataType gain, v, max = std::numeric_limits<EventDataType>::min();
|
|
|
|
qint64 t, start, rate;
|
|
EventStoreType *dptr, * eptr;
|
|
quint32 *tptr;
|
|
int cnt, idx;
|
|
|
|
int evec_size=evec.size();
|
|
|
|
for (int i = 0; i < evec_size; i++) {
|
|
EventList &ev = *evec[i];
|
|
|
|
if ((ev.last() < first) || (ev.first() > last)) {
|
|
continue;
|
|
}
|
|
|
|
start = ev.first();
|
|
dptr = ev.rawData();
|
|
cnt = ev.count();
|
|
eptr = dptr + cnt;
|
|
gain = ev.gain();
|
|
|
|
if (ev.type() == EVL_Waveform) {
|
|
rate = ev.rate();
|
|
t = start;
|
|
idx = 0;
|
|
|
|
if (first > ev.first()) {
|
|
// Skip the samples before first
|
|
idx = (first - ev.first()) / rate;
|
|
}
|
|
|
|
dptr += idx;
|
|
|
|
for (; dptr < eptr; dptr++) { //int j=idx;j<cnt;j++) {
|
|
if (t <= last) {
|
|
v = EventDataType(*dptr) * gain;
|
|
|
|
if (v > max) { max = v; }
|
|
} else { break; }
|
|
|
|
t += rate;
|
|
}
|
|
} else {
|
|
tptr = ev.rawTime();
|
|
|
|
for (; dptr < eptr; dptr++) {
|
|
t = start + *tptr++;
|
|
|
|
if (t >= first) {
|
|
if (t <= last) {
|
|
v = EventDataType(*dptr) * gain;
|
|
|
|
if (v > max) { max = v; }
|
|
} else { break; }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return max;
|
|
}
|
|
|
|
EventDataType Session::count(ChannelID id)
|
|
{
|
|
QHash<ChannelID, EventDataType>::iterator i = m_cnt.find(id);
|
|
|
|
if (i != m_cnt.end()) {
|
|
return i.value();
|
|
}
|
|
|
|
QHash<ChannelID, QVector<EventList *> >::iterator j = eventlist.find(id);
|
|
|
|
if (j == eventlist.end()) {
|
|
// m_cnt[id] = 0;
|
|
return 0;
|
|
}
|
|
|
|
QVector<EventList *> &evec = j.value();
|
|
|
|
int sum = 0;
|
|
int evec_size=evec.size();
|
|
if (evec_size == 0)
|
|
return 0;
|
|
|
|
for (int i = 0; i < evec_size; ++i) {
|
|
sum += evec.at(i)->count();
|
|
}
|
|
|
|
m_cnt[id] = sum;
|
|
return sum;
|
|
}
|
|
|
|
double Session::sum(ChannelID id)
|
|
{
|
|
QHash<ChannelID, double>::iterator i = m_sum.find(id);
|
|
|
|
if (i != m_sum.end()) {
|
|
return i.value();
|
|
}
|
|
|
|
QHash<ChannelID, QVector<EventList *> >::iterator j = eventlist.find(id);
|
|
|
|
if (j == eventlist.end()) {
|
|
m_sum[id] = 0;
|
|
return 0;
|
|
}
|
|
|
|
QVector<EventList *> &evec = j.value();
|
|
|
|
double gain, sum = 0;
|
|
EventStoreType *dptr, * eptr;
|
|
int cnt;
|
|
|
|
int evec_size=evec.size();
|
|
|
|
for (int i = 0; i < evec_size; ++i) {
|
|
EventList &ev = *(evec[i]);
|
|
gain = ev.gain();
|
|
cnt = ev.count();
|
|
dptr = ev.rawData();
|
|
eptr = dptr + cnt;
|
|
|
|
for (; dptr < eptr; dptr++) {
|
|
sum += double(*dptr) * gain;
|
|
}
|
|
}
|
|
|
|
m_sum[id] = sum;
|
|
return sum;
|
|
}
|
|
|
|
EventDataType Session::avg(ChannelID id)
|
|
{
|
|
QHash<ChannelID, EventDataType>::iterator i = m_avg.find(id);
|
|
|
|
if (i != m_avg.end()) {
|
|
return i.value();
|
|
}
|
|
|
|
QHash<ChannelID, QVector<EventList *> >::iterator j = eventlist.find(id);
|
|
|
|
if (j == eventlist.end()) {
|
|
m_avg[id] = 0;
|
|
return 0;
|
|
}
|
|
|
|
QVector<EventList *> &evec = j.value();
|
|
|
|
double val = 0, gain;
|
|
int cnt = 0;
|
|
EventStoreType *dptr, * eptr;
|
|
int evec_size=evec.size();
|
|
|
|
for (int i = 0; i < evec_size; ++i) {
|
|
EventList &ev = *(evec[i]);
|
|
dptr = ev.rawData();
|
|
gain = ev.gain();
|
|
cnt = ev.count();
|
|
eptr = dptr + cnt;
|
|
|
|
for (; dptr < eptr; dptr++) {
|
|
val += double(*dptr) * gain;
|
|
}
|
|
}
|
|
|
|
if (cnt > 0) { // Shouldn't really happen.. Should aways contain data
|
|
val /= double(cnt);
|
|
}
|
|
|
|
m_avg[id] = val;
|
|
return val;
|
|
}
|
|
EventDataType Session::cph(ChannelID id) // count per hour
|
|
{
|
|
QHash<ChannelID, EventDataType>::iterator i = m_cph.find(id);
|
|
|
|
if (i != m_cph.end()) {
|
|
return i.value();
|
|
}
|
|
|
|
EventDataType val = count(id);
|
|
val /= hours();
|
|
|
|
m_cph[id] = val;
|
|
return val;
|
|
}
|
|
EventDataType Session::sph(ChannelID id) // sum per hour, assuming id is a time field in seconds
|
|
{
|
|
QHash<ChannelID, EventDataType>::iterator i = m_sph.find(id);
|
|
|
|
if (i != m_sph.end()) {
|
|
return i.value();
|
|
}
|
|
|
|
EventDataType val = sum(id) / 3600.0;
|
|
val = 100.0 / hours() * val;
|
|
m_sph[id] = val;
|
|
return val;
|
|
}
|
|
|
|
EventDataType Session::timeAboveThreshold(ChannelID id, EventDataType threshold)
|
|
{
|
|
QHash<ChannelID, EventDataType>::iterator th = m_upperThreshold.find(id);
|
|
if (th != m_upperThreshold.end()) {
|
|
if (fabs(th.value()-threshold) < 0.00000001) { // close enough
|
|
th = m_timeAboveTheshold.find(id);
|
|
if (th != m_timeAboveTheshold.end()) {
|
|
return th.value();
|
|
}
|
|
}
|
|
}
|
|
bool loaded = s_events_loaded;
|
|
|
|
OpenEvents();
|
|
QHash<ChannelID, QVector<EventList *> >::iterator j = eventlist.find(id);
|
|
if (j == eventlist.end()) {
|
|
if (!loaded) {
|
|
TrashEvents();
|
|
}
|
|
return 0.0f;
|
|
}
|
|
|
|
QVector<EventList *> &evec = j.value();
|
|
int evec_size=evec.size();
|
|
|
|
qint64 ti, started=0, total=0;
|
|
EventDataType data;
|
|
int elsize;
|
|
for (int i = 0; i < evec_size; ++i) {
|
|
EventList &ev = *(evec[i]);
|
|
elsize = ev.count();
|
|
|
|
for (int j=0; j < elsize; ++j) {
|
|
ti=ev.time(j);
|
|
data=ev.data(j);
|
|
|
|
if (started == 0) {
|
|
if (data >= threshold) {
|
|
started=ti;
|
|
}
|
|
} else {
|
|
if (data < threshold) {
|
|
total += ti-started;
|
|
started = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (started) {
|
|
total += ti-started;
|
|
}
|
|
EventDataType time = double(total) / 60000.0;
|
|
|
|
m_timeAboveTheshold[id] = time;
|
|
m_upperThreshold[id] = threshold;
|
|
if (!loaded) this->TrashEvents(); // otherwise leave it open
|
|
return time;
|
|
}
|
|
|
|
EventDataType Session::timeBelowThreshold(ChannelID id, EventDataType threshold)
|
|
{
|
|
QHash<ChannelID, EventDataType>::iterator th = m_lowerThreshold.find(id);
|
|
if (th != m_lowerThreshold.end()) {
|
|
if (fabs(th.value()-threshold) < 0.00000001) { // close enough
|
|
th = m_timeBelowTheshold.find(id);
|
|
if (th != m_timeBelowTheshold.end()) {
|
|
return th.value();
|
|
}
|
|
}
|
|
}
|
|
bool loaded = s_events_loaded;
|
|
|
|
QHash<ChannelID, QVector<EventList *> >::iterator j = eventlist.find(id);
|
|
if (j == eventlist.end()) {
|
|
return 0.0f;
|
|
}
|
|
|
|
QVector<EventList *> &evec = j.value();
|
|
int evec_size=evec.size();
|
|
|
|
qint64 ti, started=0, total=0;
|
|
EventDataType data;
|
|
int elsize;
|
|
for (int i = 0; i < evec_size; ++i) {
|
|
EventList &ev = *(evec[i]);
|
|
elsize = ev.count();
|
|
|
|
for (int j=0; j < elsize; ++j) {
|
|
ti=ev.time(j);
|
|
data=ev.data(j);
|
|
|
|
if (started == 0) {
|
|
if (data <= threshold) {
|
|
started=ti;
|
|
}
|
|
} else {
|
|
if (data > threshold) {
|
|
total += ti-started;
|
|
started = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (started) {
|
|
total += ti-started;
|
|
}
|
|
|
|
EventDataType time = double(total) / 60000.0;
|
|
|
|
m_timeBelowTheshold[id] = time;
|
|
m_lowerThreshold[id] = threshold;
|
|
if (!loaded) this->TrashEvents(); // otherwise leave it open
|
|
|
|
return time;
|
|
}
|
|
|
|
|
|
bool sortfunction(EventStoreType i, EventStoreType j) { return (i < j); }
|
|
|
|
EventDataType Session::percentile(ChannelID id, EventDataType percent)
|
|
{
|
|
QHash<ChannelID, QVector<EventList *> >::iterator jj = eventlist.find(id);
|
|
|
|
if (jj == eventlist.end()) {
|
|
return 0;
|
|
}
|
|
|
|
QVector<EventList *> &evec = jj.value();
|
|
|
|
if (percent > 1.0) {
|
|
qWarning() << "Session::percentile() called with > 1.0";
|
|
return 0;
|
|
}
|
|
|
|
int evec_size = evec.size();
|
|
|
|
if (evec_size == 0) {
|
|
return 0;
|
|
}
|
|
|
|
QVector<EventStoreType> array;
|
|
|
|
EventDataType gain = evec[0]->gain();
|
|
|
|
EventStoreType *dptr, * sptr, *eptr;
|
|
|
|
int tt = 0, cnt = 0;
|
|
|
|
for (int i = 0; i < evec_size; ++i) {
|
|
EventList &ev = *evec[i];
|
|
cnt = ev.count();
|
|
tt += cnt;
|
|
}
|
|
|
|
array.resize(tt);
|
|
|
|
for (int i = 0; i < evec_size; ++i) {
|
|
EventList &ev = *evec[i];
|
|
sptr = ev.rawData();
|
|
dptr = array.data();
|
|
|
|
eptr = sptr + cnt;
|
|
|
|
for (; sptr < eptr; sptr++) {
|
|
*dptr++ = * sptr;
|
|
}
|
|
}
|
|
|
|
int n = array.size() * percent;
|
|
|
|
if (n > array.size() - 1) { n--; }
|
|
|
|
nth_element(array.begin(), array.begin() + n, array.end());
|
|
|
|
// slack, no averaging.. fixme if this function is ever used..
|
|
return array[n] * gain;
|
|
}
|
|
|
|
EventDataType Session::wavg(ChannelID id)
|
|
{
|
|
QHash<EventStoreType, quint32> vtime;
|
|
QHash<ChannelID, EventDataType>::iterator i = m_wavg.find(id);
|
|
|
|
if (i != m_wavg.end()) {
|
|
return i.value();
|
|
}
|
|
|
|
updateCountSummary(id);
|
|
|
|
QHash<ChannelID, QHash<EventStoreType, quint32> >::iterator j2 = m_timesummary.find(id);
|
|
|
|
if (j2 == m_timesummary.end()) {
|
|
return 0;
|
|
}
|
|
|
|
QHash<EventStoreType, quint32> ×um = j2.value();
|
|
|
|
if (!m_gain.contains(id)) {
|
|
return 0;
|
|
}
|
|
|
|
double s0 = 0, s1 = 0, s2;
|
|
|
|
EventDataType val, gain = m_gain[id];
|
|
|
|
QHash<EventStoreType, quint32>::iterator vi = timesum.begin();
|
|
QHash<EventStoreType, quint32>::iterator ts_end = timesum.end();
|
|
|
|
for (; vi != ts_end; vi++) {
|
|
val = vi.key() * gain;
|
|
s2 = vi.value();
|
|
s0 += s2;
|
|
s1 += val * s2;
|
|
}
|
|
|
|
if (s0 > 0) {
|
|
val = s1 / s0;
|
|
} else { val = 0; }
|
|
|
|
m_wavg[id] = val;
|
|
return val;
|
|
}
|
|
|
|
EventDataType Session::calcMiddle(ChannelID code)
|
|
{
|
|
int c = p_profile->general->prefCalcMiddle();
|
|
|
|
if (c == 0) {
|
|
return percentile(code, 0.5); // Median
|
|
} else if (c == 1 ) {
|
|
return wavg(code); // Weighted Average
|
|
} else {
|
|
return avg(code); // Average
|
|
}
|
|
}
|
|
|
|
EventDataType Session::calcMax(ChannelID code)
|
|
{
|
|
return p_profile->general->prefCalcMax() ? percentile(code, 0.995f) : Max(code);
|
|
}
|
|
|
|
EventDataType Session::calcPercentile(ChannelID code)
|
|
{
|
|
double p = p_profile->general->prefCalcPercentile() / 100.0;
|
|
return percentile(code, p);
|
|
}
|
|
|
|
|
|
EventList *Session::AddEventList(ChannelID code, EventListType et, EventDataType gain,
|
|
EventDataType offset, EventDataType min, EventDataType max, EventDataType rate, bool second_field)
|
|
{
|
|
schema::Channel *channel = &schema::channel[code];
|
|
|
|
if (!channel) {
|
|
qWarning() << "Channel" << code << "does not exist!";
|
|
//return nullptr;
|
|
}
|
|
|
|
EventList *el = new EventList(et, gain, offset, min, max, rate, second_field);
|
|
|
|
eventlist[code].push_back(el);
|
|
//s_machine->registerChannel(chan);
|
|
return el;
|
|
}
|
|
void Session::offsetSession(qint64 offset)
|
|
{
|
|
//qDebug() << "Session starts" << QDateTime::fromTime_t(s_first/1000).toString("yyyy-MM-dd HH:mm:ss");
|
|
s_first += offset;
|
|
s_last += offset;
|
|
QHash<ChannelID, quint64>::iterator it;
|
|
|
|
QHash<ChannelID, quint64>::iterator end;
|
|
|
|
it = m_firstchan.begin();
|
|
end = m_firstchan.end();
|
|
for (; it != end; it++) {
|
|
if (it.value() > 0) {
|
|
it.value() += offset;
|
|
}
|
|
}
|
|
|
|
it = m_lastchan.begin();
|
|
end = m_lastchan.end();
|
|
for (; it != end; it++) {
|
|
if (it.value() > 0) {
|
|
it.value() += offset;
|
|
}
|
|
}
|
|
|
|
QHash<ChannelID, QVector<EventList *> >::iterator i;
|
|
QHash<ChannelID, QVector<EventList *> >::iterator el_end=eventlist.end();
|
|
|
|
int el_s;
|
|
|
|
for (i = eventlist.begin(); i != el_end; i++) {
|
|
el_s=i.value().size();
|
|
for (int j = 0; j < el_s; j++) {
|
|
EventList *e = i.value()[j];
|
|
|
|
e->setFirst(e->first() + offset);
|
|
e->setLast(e->last() + offset);
|
|
}
|
|
}
|
|
|
|
qDebug() << "Session now starts" << QDateTime::fromTime_t(s_first /
|
|
1000).toString("yyyy-MM-dd HH:mm:ss");
|
|
|
|
}
|
|
|
|
qint64 Session::first()
|
|
{
|
|
qint64 start = s_first;
|
|
|
|
if (s_machine->type() == MT_CPAP) {
|
|
start += qint64(p_profile->cpap->clockDrift()) * 1000L;
|
|
}
|
|
|
|
return start;
|
|
}
|
|
|
|
qint64 Session::last()
|
|
{
|
|
qint64 last = s_last;
|
|
|
|
if (s_machine->type() == MT_CPAP) {
|
|
last += qint64(p_profile->cpap->clockDrift()) * 1000L;
|
|
}
|
|
|
|
return last;
|
|
}
|