OSCAR-code/oscar/SleepLib/loader_plugins/zeo_loader.cpp
sawinglogz c5c6f779f7 Fix Zeo and Dreem loaders' mysteriously missing sessions.
This was the same issue seen in the Viatom loader, fixed at 5e07187,
and the same fix works here.
2020-02-13 14:31:32 -05:00

341 lines
9.7 KiB
C++

/* SleepLib ZEO Loader Implementation
*
* Copyright (c) 2020 The OSCAR Team
* 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. */
//********************************************************************************************
// IMPORTANT!!!
//********************************************************************************************
// Please INCREMENT the zeo_data_version in zel_loader.h when making changes to this loader
// that change loader behaviour or modify channels.
//********************************************************************************************
#include <QDir>
#include <QTextStream>
#include "zeo_loader.h"
#include "SleepLib/machine.h"
#include "csv.h"
ZEOLoader::ZEOLoader()
{
m_type = MT_SLEEPSTAGE;
}
ZEOLoader::~ZEOLoader()
{
closeCSV();
}
int ZEOLoader::Open(const QString & dirpath)
{
QString newpath;
QString dirtag = "zeo";
// Could Scan the ZEO folder for a list of CSVs
QString path(dirpath);
path = path.replace("\\", "/");
if (path.toLower().endsWith("/" + dirtag)) {
return 0;
//newpath=path;
} else {
newpath = path + "/" + dirtag.toUpper();
}
//QString filename;
// ZEO folder structure detection stuff here.
return 0; // number of machines affected
}
/*15233: "Sleep Date"
15234: "ZQ"
15236: "Total Z"
15237: "Time to Z"
15237: "Time in Wake"
15238: "Time in REM"
15238: "Time in Light"
15241: "Time in Deep"
15242: "Awakenings"
15245: "Start of Night"
15246: "End of Night"
15246: "Rise Time"
15247: "Alarm Reason"
15247: "Snooze Time"
15254: "Wake Tone"
15259: "Wake Window"
15259: "Alarm Type"
15260: "First Alarm Ring"
15261: "Last Alarm Ring"
15261: "First Snooze Time"
15265: "Last Snooze Time"
15266: "Set Alarm Time"
15266: "Morning Feel"
15267: "Sleep Graph"
15267: "Detailed Sleep Graph"
15268: "Firmware Version" */
int ZEOLoader::OpenFile(const QString & filename)
{
if (!openCSV(filename)) {
return -1;
}
int count = 0;
Session* sess;
// TODO: add progress bar support, perhaps move shared logic into shared parent class with Dreem loader
while ((sess = readNextSession()) != nullptr) {
sess->SetChanged(true);
mach->AddSession(sess);
count++;
}
if (count > 0) {
mach->Save();
mach->SaveSummaryCache();
p_profile->StoreMachines();
}
closeCSV();
return count;
}
bool ZEOLoader::openCSV(const QString & filename)
{
file.setFileName(filename);
if (filename.toLower().endsWith(".csv")) {
if (!file.open(QFile::ReadOnly)) {
qDebug() << "Couldn't open zeo file" << filename;
return false;
}
} else {// if (filename.toLower().endsWith(".dat")) {
// TODO: add direct support for .dat files
return false;
// not supported.
}
QStringList header;
csv = new CSVReader(file);
bool ok = csv->readRow(header);
if (!ok) {
qWarning() << "no header row";
return false;
}
csv->setFieldNames(header);
MachineInfo info = newInfo();
mach = p_profile->CreateMachine(info);
return true;
}
void ZEOLoader::closeCSV()
{
if (csv != nullptr) {
delete csv;
csv = nullptr;
}
if (file.isOpen()) {
file.close();
}
}
// int idxTotalZ = header.indexOf("Total Z");
// int idxAlarmReason = header.indexOf("Alarm Reason");
// int idxSnoozeTime = header.indexOf("Snooze Time");
// int idxWakeTone = header.indexOf("Wake Tone");
// int idxWakeWindow = header.indexOf("Wake Window");
// int idxAlarmType = header.indexOf("Alarm Type");
static const EventDataType GAIN = 0.25; // allow for fractional sleep stages (such as Deep (2))
Session* ZEOLoader::readNextSession()
{
if (csv == nullptr) {
qWarning() << "no CSV open!";
return nullptr;
}
Session* sess = nullptr;
QDateTime start_of_night; //, end_of_night, rise_time;
qint64 st, tt;
int stage;
int ZQ, TimeToZ, TimeInWake, TimeInREM, TimeInLight, TimeInDeep, Awakenings;
int MorningFeel;
//QString FirmwareVersion, MyZeoVersion;
//QDateTime FirstAlarmRing, LastAlarmRing, FirstSnoozeTime, LastSnoozeTime, SetAlarmTime;
QStringList /*SG,*/ DSG;
QHash<QString,QString> row;
while (csv->readRow(row)) {
SessionID sid = 0;
invalid_fields = false;
start_of_night = readDateTime(row["Start of Night"]);
if (start_of_night.isValid()) {
sid = start_of_night.toTime_t();
if (mach->SessionExists(sid)) {
continue;
}
} // else invalid_fields will be true
ZQ = readInt(row["ZQ"]);
TimeToZ = readInt(row["Time to Z"]);
TimeInWake = readInt(row["Time in Wake"]);
TimeInREM = readInt(row["Time in REM"]);
TimeInLight = readInt(row["Time in Light"]);
TimeInDeep = readInt(row["Time in Deep"]);
Awakenings = readInt(row["Awakenings"]);
//end_of_night = readDateTime(row["End of Night"]);
//rise_time = readDateTime(row["Rise Time"]);
//FirstAlarmRing = readDateTime(row["First Alarm Ring"], false);
//LastAlarmRing = readDateTime(row["Last Alarm Ring"], false);
//FirstSnoozeTime = readDateTime(row["First Snooze Time"], false);
//LastSnoozeTime = readDateTime(row["Last Snooze Time"], false);
//SetAlarmTime = readDateTime(row["Set Alarm Time"], false);
MorningFeel = readInt(row["Morning Feel"], false);
//FirmwareVersion = row["Firmware Version"];
//MyZeoVersion = row["My ZEO Version"];
if (invalid_fields) {
continue;
}
//SG = row["Sleep Graph"].trimmed().split(" ");
DSG = row["Detailed Sleep Graph"].trimmed().split(" ");
if (DSG.size() == 0) {
continue;
}
sess = new Session(mach, sid);
break;
};
if (sess) {
const int WindowSize = 30 * 1000;
m_session = sess;
sess->settings[ZEO_Awakenings] = Awakenings;
sess->settings[ZEO_MorningFeel] = MorningFeel;
sess->settings[ZEO_TimeToZ] = TimeToZ;
sess->settings[ZEO_ZQ] = ZQ;
sess->settings[ZEO_TimeInWake] = TimeInWake;
sess->settings[ZEO_TimeInREM] = TimeInREM;
sess->settings[ZEO_TimeInLight] = TimeInLight;
sess->settings[ZEO_TimeInDeep] = TimeInDeep;
st = qint64(start_of_night.toTime_t()) * 1000L;
sess->really_set_first(st);
tt = st;
for (int i = 0; i < DSG.size(); i++) {
bool ok;
stage = DSG[i].toInt(&ok);
if (ok) {
// 0 = no data, 1 = Awake, 2 = REM, 3 = Light Sleep, 4 = Deep Sleep, 6 = Deep Sleep (2), drawn slightly less deep
int value = -stage / GAIN; // use negative values so that the chart is oriented the right way
switch (stage) {
case 0:
EndEventList(ZEO_SleepStage, tt);
break;
case 6:
// According to ZeoViewer, 6 is a "Deep (2)" and is drawn somewhere between Light and Deep.
value = -3.75 / GAIN;
// fall through
case 1:
case 2:
case 3:
case 4:
AddEvent(ZEO_SleepStage, tt, value);
break;
default:
qWarning() << sess->session() << start_of_night << "@" << i << "unknown sleep stage" << stage;
break;
}
} else {
qWarning() << sess->session() << start_of_night << "@" << i << "unknown sleep stage" << DSG[i];
}
tt += WindowSize;
}
EndEventList(ZEO_SleepStage, tt);
sess->really_set_last(tt);
//int size = DSG.size();
//qDebug() << linecomp[0] << start_of_night << end_of_night << rise_time << size << "30 second chunks";
}
return sess;
}
void ZEOLoader::AddEvent(ChannelID channel, qint64 t, EventDataType value)
{
EventList* C = m_importChannels[channel];
if (C == nullptr) {
C = m_session->AddEventList(channel, EVL_Event, GAIN, 0, -5, 0);
Q_ASSERT(C); // Once upon a time AddEventList could return nullptr, but not any more.
m_importChannels[channel] = C;
}
// Add the event
C->AddEvent(t, value);
m_importLastValue[channel] = value;
}
void ZEOLoader::EndEventList(ChannelID channel, qint64 t)
{
EventList* C = m_importChannels[channel];
if (C != nullptr) {
C->AddEvent(t, m_importLastValue[channel]);
// Mark this channel's event list as ended.
m_importChannels[channel] = nullptr;
}
}
QDateTime ZEOLoader::readDateTime(const QString & text, bool required)
{
QDateTime dt = QDateTime::fromString(text, "MM/dd/yyyy HH:mm");
if (required || !text.isEmpty()) {
if (!dt.isValid()) {
dt = QDateTime::fromString(text, "yyyy-MM-dd HH:mm:ss");
if (!dt.isValid()) {
invalid_fields = true;
}
}
}
return dt;
}
int ZEOLoader::readInt(const QString & text, bool required)
{
bool ok;
int value = text.toInt(&ok);
if (!ok) {
if (required) {
invalid_fields = true;
} else {
value = 0;
}
}
return value;
}
static bool zeo_initialized = false;
void ZEOLoader::Register()
{
if (zeo_initialized) { return; }
qDebug("Registering ZEOLoader");
RegisterLoader(new ZEOLoader());
//InitModelMap();
zeo_initialized = true;
}