/* SleepLib Somnopose Loader Implementation * * Copyright (c) 2019-2024 The OSCAR Team * Copyright (c) 2011-2018 Mark Watkins * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of the source code * for more details. */ //******************************************************************************************** // Please only INCREMENT the somnopose_data_version in somnopose_loader.h when making changes // that change loader behaviour or modify channels in a manner that fixes old data imports. // Note that changing the data version will require a reimport of existing data for which OSCAR // does not keep a backup - so it should be avoided if possible. // i.e. there is no need to change the version when adding support for new devices //******************************************************************************************** #include #include #include "somnopose_loader.h" #include "SleepLib/machine.h" SomnoposeLoader::SomnoposeLoader() { m_type = MT_POSITION; } SomnoposeLoader::~SomnoposeLoader() { } int SomnoposeLoader::OpenFile(const QString & filename) { QFile file(filename); if (filename.toLower().endsWith(".csv")) { if (!file.open(QFile::ReadOnly)) { qDebug() << "Couldn't open Somnopose data file" << filename; return -1; } } else { return -1; } qDebug() << "Opening file" << filename; QTextStream ts(&file); // Read header line and determine order of fields QString hdr = ts.readLine(); QStringList headers = hdr.split(","); QString model = ""; QString serial = ""; int col_inclination = -1, col_orientation = -1, col_timestamp = -1, col_movement = -1; int hdr_size = headers.size(); for (int i = 0; i < hdr_size; i++) { // Optional header model= if (headers.at(i).startsWith("model=", Qt::CaseInsensitive)) { model=headers.at(i).split("=")[1]; } // Optional header serial= if (headers.at(i).startsWith("serial=", Qt::CaseInsensitive)) { serial=headers.at(i).split("=")[1]; } if (headers.at(i).compare("timestamp", Qt::CaseInsensitive) == 0) { col_timestamp = i; } if (headers.at(i).compare("inclination", Qt::CaseInsensitive) == 0) { col_inclination = i; } if (headers.at(i).compare("orientation", Qt::CaseInsensitive) == 0) { col_orientation = i; } if (headers.at(i).compare("movement", Qt::CaseInsensitive) == 0) { col_movement = i; } } // Check we have all fields available if (col_timestamp < 0) { qDebug() << "Header missing timestamp"; return -1; } if ((col_inclination < 0) && (col_orientation < 0) && (col_movement < 0)) { qDebug() << "Header missing all of inclination, orientation, movement (at least one must be present)"; return -1; } #if QT_VERSION >= QT_VERSION_CHECK(5,14,0) QDateTime epoch(QDate(2001, 1, 1).startOfDay(Qt::OffsetFromUTC)); qint64 ep = epoch.toMSecsSinceEpoch() , time=0; #else QDateTime epoch(QDate(2001, 1, 1)); qint64 ep = qint64(epoch.toTime_t()+epoch.offsetFromUtc()) * 1000, time=0; #endif qDebug() << "Epoch starts at" << epoch.toString(); double timestamp, orientation=0, inclination=0, movement=0; QString data; QStringList fields; bool ok, orientation_ok, inclination_ok, movement_ok; bool first = true, skip_session = false; int session_count = 0; MachineInfo info = newInfo(); info.model = model; info.serial = serial; Machine *mach = p_profile->CreateMachine(info); Session *sess = nullptr; SessionID sid; EventList *ev_orientation = nullptr, *ev_inclination = nullptr, *ev_movement = nullptr; while (!(data = ts.readLine()).isEmpty()) { fields = data.split(","); if (fields.size() >= col_timestamp && fields[col_timestamp] == "-") { // Flag end of session... if (sess) { if (ev_orientation) { sess->setMin(POS_Orientation, ev_orientation->Min()); sess->setMax(POS_Orientation, ev_orientation->Max()); } if (ev_inclination) { sess->setMin(POS_Inclination, ev_inclination->Min()); sess->setMax(POS_Inclination, ev_inclination->Max()); } if (ev_movement) { sess->setMin(POS_Movement, ev_movement->Min()); sess->setMax(POS_Movement, ev_movement->Max()); } sess->really_set_last(time); sess->SetChanged(true); mach->AddSession(sess); session_count++; } // Prepare for potential next session... sess = nullptr; ev_orientation = ev_inclination = ev_movement = nullptr; first = true; skip_session = false; continue; } if (skip_session) { continue; } if (fields.size() < hdr_size) { // missing fields.. skip this record continue; } timestamp = fields[col_timestamp].toDouble(&ok); if (!ok) { continue; } orientation_ok = inclination_ok = movement_ok = false; if (col_orientation >= 0) { orientation = fields[col_orientation].toDouble(&orientation_ok); } if (col_inclination >= 0) { inclination = fields[col_inclination].toDouble(&inclination_ok); } if (col_movement >= 0) { movement = fields[col_movement].toDouble(&movement_ok); } if (!orientation_ok && !inclination_ok && !movement_ok) { continue; } // convert to milliseconds since epoch time = (timestamp * 1000.0) + ep; if (first) { first = false; sid = time / 1000; qDebug() << "First timestamp is" << QDateTime::fromMSecsSinceEpoch(time).toString(); if (mach->SessionExists(sid)) { qDebug() << "File " << filename << " session " << sid << " already loaded... skipping"; // Continue processing file to allow for case where new sessions are added to a file skip_session = true; continue; } sess = new Session(mach, sid); sess->really_set_first(time); if (col_orientation >= 0) { ev_orientation = sess->AddEventList(POS_Orientation, EVL_Event, 1, 0, 0, 0); } if (col_inclination >= 0) { ev_inclination = sess->AddEventList(POS_Inclination, EVL_Event, 1, 0, 0, 0); } if (col_movement >= 0) { ev_movement = sess->AddEventList(POS_Movement, EVL_Event, 1, 0, 0, 0); } } sess->set_last(time); if (ev_orientation && orientation_ok) { ev_orientation->AddEvent(time, orientation); } if (ev_inclination && inclination_ok) { ev_inclination->AddEvent(time, inclination); } if (ev_movement && movement_ok) { ev_movement->AddEvent(time, movement); } } if (sess) { if (ev_orientation) { sess->setMin(POS_Orientation, ev_orientation->Min()); sess->setMax(POS_Orientation, ev_orientation->Max()); } if (ev_inclination) { sess->setMin(POS_Inclination, ev_inclination->Min()); sess->setMax(POS_Inclination, ev_inclination->Max()); } if (ev_movement) { sess->setMin(POS_Movement, ev_movement->Min()); sess->setMax(POS_Movement, ev_movement->Max()); } sess->really_set_last(time); sess->SetChanged(true); mach->AddSession(sess); session_count++; } if (session_count) { mach->Save(); // Adding these to hopefully make data persistent... mach->SaveSummaryCache(); p_profile->StoreMachines(); } return session_count; } static bool somnopose_initialized = false; void SomnoposeLoader::Register() { if (somnopose_initialized) { return; } qDebug("Registering SomnoposeLoader"); RegisterLoader(new SomnoposeLoader()); //InitModelMap(); somnopose_initialized = true; }