public Task Start(CancellationToken cancellationToken) { return(Task.Run(async() => { while (!cancellationToken.IsCancellationRequested) { var start = DateTime.Now; try { var settings = webSocketThread.Settings; CalculatedValues calculatedValues = new CalculatedValues(); // are we active? if (!(settings.ContainsKey("ACTIVE") && (int)settings["ACTIVE"] == 3)) { logger.LogDebug("Machine not active, wait for 2 seconds, current status: {0}", settings.ContainsKey("ACTIVE") ? settings["ACTIVE"] : -1); await Task.Delay(2000); continue; } else { if (!alarmThread.Active && settings.ContainsKey("RR")) { if ((DateTime.UtcNow - alarmThread.InactiveSince).TotalSeconds > 60.0f / (int)settings["RR"] * 5.0f) { // we have been inactive for 5 breathing cycles, check alarms again alarmThread.Active = true; } } } uint alarmBits = 0; var values = dbService.GetDocuments("measured_values", DateTime.UtcNow.AddSeconds(-70)); if (values.Count == 0) { // no data yet, wait for it to become available await Task.Delay(2000); continue; } // find the lowest datetime value that we have var maxDateTime = values.First().Value.ArduinoTime; values.Reverse(); // check breaths per minute List <Tuple <long, long> > breathingCycles = GetBreathingCyclesFromTargetPressure(values, maxDateTime); // do we have a full cycle if (breathingCycles.Count > 0) { long startBreathingCycle = breathingCycles.Last().Item1; long endBreathingCycle = breathingCycles.Last().Item2; var minValTargetPressure = GetMinimum(values, (valueEntry) => valueEntry.Value.TargetPressure, startBreathingCycle, endBreathingCycle); var maxValTargetPressure = GetMaximum(values, (valueEntry) => valueEntry.Value.TargetPressure, startBreathingCycle, endBreathingCycle); var targetPressureExhale = values .Where(v => v.Value.ArduinoTime > startBreathingCycle && v.Value.ArduinoTime <= endBreathingCycle && v.Value.TargetPressure == minValTargetPressure) .FirstOrDefault(); if (targetPressureExhale == null) { continue; } long exhalemoment = targetPressureExhale.Value.ArduinoTime - 40; if (targetPressureExhale == null) { continue; } var breathingCycleDuration = (endBreathingCycle - startBreathingCycle) / 1000.0; var bpm = 60.0 / breathingCycleDuration; calculatedValues.RespatoryRate = bpm; if (settings.ContainsKey("HRR")) { if (bpm >= (int)settings["HRR"] + 1) { alarmBits |= BPM_TOO_HIGH; } } if (settings.ContainsKey("LRR")) { if (bpm < (int)settings["LRR"]) { alarmBits |= BPM_TOO_LOW; } } else { if (settings.ContainsKey("RR") && bpm < (int)settings["RR"] - 1.0f) { alarmBits |= BPM_TOO_LOW; } } var inhaleTime = (exhalemoment - startBreathingCycle) / 1000.0; var exhaleTime = (endBreathingCycle - exhalemoment) / 1000.0; calculatedValues.IE = inhaleTime / breathingCycleDuration; var tidalVolume = GetMaximum(values, (valueEntry) => valueEntry.Value.Volume, startBreathingCycle, endBreathingCycle); calculatedValues.TidalVolume = tidalVolume; if (((int)settings["MODE"] & 4) > 0) { // we are in volume limited mode if (settings.ContainsKey("VT") && settings.ContainsKey("ADVT")) { // todo: in future versions it might be easier to convert ADVT and ADPK to absolute values var upperLimit = (int)settings["VT"] + (int)settings["ADVT"]; var lowerLimit = (int)settings["VT"] - (int)settings["ADVT"]; if (tidalVolume > upperLimit) { alarmBits |= VOLUME_NOT_OK; } else if (tidalVolume < lowerLimit) { alarmBits |= VOLUME_TOO_LOW; } } } else { // pressure control without volume limiting, only check if tidalVolume is above ADVT if (settings.ContainsKey("ADVT")) { if (tidalVolume < (int)settings["ADVT"]) { alarmBits |= TIDAL_VOLUME_TOO_LOW_PC_MODE; } } } var residualVolume = GetMinimum(values, (valueEntry) => valueEntry.Value.Volume, exhalemoment, endBreathingCycle - 10); int residualVolumeSetting = 50; if (settings.ContainsKey("RVOL")) { residualVolumeSetting = (int)settings["RVOL"]; } if (residualVolume > residualVolumeSetting) { alarmBits |= RESIDUAL_VOLUME_NOT_OK; } calculatedValues.ResidualVolume = residualVolume; var peakPressureMoment = values .Where(v => v.Value.ArduinoTime >= startBreathingCycle && v.Value.ArduinoTime <= exhalemoment) .Aggregate((i1, i2) => i1.Value.Pressure > i2.Value.Pressure ? i1 : i2); var plateauMinimumPressure = GetMinimum(values, (valueEntry) => valueEntry.Value.Pressure, peakPressureMoment.Value.ArduinoTime, exhalemoment); // temp code until arduino has the HPK and LPK if (settings.ContainsKey("ADPK") && settings.ContainsKey("PK")) { int adpk = (int)settings["ADPK"]; int pk = (int)settings["PK"]; if (!settings.ContainsKey("HPK")) { settings.AddOrUpdate("HPK", pk + adpk, (key, value) => pk + adpk); } if (!settings.ContainsKey("LPK")) { settings.AddOrUpdate("LPK", pk - adpk, (key, value) => pk - adpk); } } // end temp code if (settings.ContainsKey("HPK")) { int upperLimit = (int)settings["HPK"]; if (peakPressureMoment.Value.Pressure > upperLimit || plateauMinimumPressure > upperLimit) { alarmBits |= PRESSURE_NOT_OK; } } if (settings.ContainsKey("LPK")) { int lowerLimit = (int)settings["LPK"]; if (lowerLimit >= maxValTargetPressure) { // we are probably using PSUPPORT lowerLimit = (int)maxValTargetPressure - 5; } if (peakPressureMoment.Value.Pressure < lowerLimit || plateauMinimumPressure < lowerLimit) { alarmBits |= PRESSURE_TOO_LOW; } } calculatedValues.PeakPressure = peakPressureMoment.Value.Pressure; calculatedValues.PressurePlateau = plateauMinimumPressure; calculatedValues.LungCompliance = tidalVolume / plateauMinimumPressure; // check fio2 values in last cycle // get biggest FiO2 deviation if (settings.ContainsKey("FIO2") && settings.ContainsKey("ADFIO2")) { var upperLimit = (double)settings["FIO2"] + (double)settings["ADFIO2"]; var lowerLimit = (double)settings["FIO2"] - (double)settings["ADFIO2"]; var peakFio2moment = values .Where(v => v.Value.ArduinoTime >= startBreathingCycle && v.Value.ArduinoTime <= exhalemoment) .Aggregate((i1, i2) => Math.Abs(i1.Value.FiO2 - (double)settings["FIO2"]) > Math.Abs(i2.Value.FiO2 - (double)settings["FIO2"]) ? i1 : i2); if (peakFio2moment.Value.FiO2 > upperLimit) { alarmBits |= FIO2_TOO_HIGH; } else if (peakFio2moment.Value.FiO2 < lowerLimit) { alarmBits |= FIO2_TOO_LOW; } } // did we have a trigger within this cycle? var triggerMoment = values .FirstOrDefault(p => p.Value.ArduinoTime >= exhalemoment && p.Value.ArduinoTime <= endBreathingCycle && p.Value.Trigger > 0.0f); var endPeep = endBreathingCycle; if (triggerMoment != null) { endPeep = triggerMoment.Value.ArduinoTime - 50; } if (settings.ContainsKey("PP") && settings.ContainsKey("ADPP")) { bool firstPeepPressureIteration = true; ValueEntry previousPoint = null; List <float> slopes = new List <float>(); int plateauCounter = 0; bool foundPlateau = false; var pressureExhaleValues = values .Where(v => v.Value.ArduinoTime >= exhalemoment + 100 && v.Value.ArduinoTime <= endPeep) .OrderByDescending(v => v.Value.ArduinoTime) .ToList(); for (int i = 0; i < pressureExhaleValues.Count - 2; i += 2) { var valueEntry = pressureExhaleValues[i]; // if last value is above PEEP, assume everything is ok if (firstPeepPressureIteration) { if (valueEntry.Value.Pressure > (int)settings["PP"] - (int)settings["ADPP"]) { foundPlateau = true; calculatedValues.Peep = valueEntry.Value.Pressure; break; } firstPeepPressureIteration = false; } // we are still here, so last value was not in peep threshold // go back until we are above PEEP again and start calculating slope // when the average slope is steadily declining, we have a leak if (previousPoint != null) { var gradient = (previousPoint.Value.Pressure - valueEntry.Value.Pressure) / ((double)(previousPoint.Value.ArduinoTime - valueEntry.Value.ArduinoTime) / 1000.0); if (gradient > -1.0f) { plateauCounter++; if (plateauCounter == 3) { if (valueEntry.Value.Pressure > (int)settings["PP"] - (int)settings["ADPP"]) { foundPlateau = true; calculatedValues.Peep = valueEntry.Value.Pressure; break; } else { plateauCounter = 0; } } } else { plateauCounter = 0; } } previousPoint = valueEntry; } if (!foundPlateau) { // all points are below peep, clearly we should raise an alarm alarmBits |= PEEP_NOT_OK; } } // only for debug purposes, send the moments to the frontend //await serialThread.SendSettingToServer("breathingCycleStart", startBreathingCycle.Value.); } if (serialThread.ConnectionState != ConnectionState.Connected) { alarmBits |= ARDUINO_CONNECTION_NOT_OK; } if (alarmThread.Active) { alarmThread.SetPCAlarmBits(alarmBits, settings, calculatedValues); } if (breathingCycles.Count > 1) { foreach (var breathingCycle in breathingCycles) { var tidalVolume = values .Where(v => v.Value.ArduinoTime >= breathingCycle.Item1 && v.Value.ArduinoTime <= breathingCycle.Item2) .Max(v => v.Value.Volume); calculatedValues.VolumePerMinute += tidalVolume / 1000.0; } calculatedValues.VolumePerMinute = calculatedValues.VolumePerMinute / ((breathingCycles.Last().Item2 - breathingCycles.First().Item1) / 1000.0) * 60.0; } await apiService.SendCalculatedValuesToServerAsync(calculatedValues); // serialThread.WriteData(ASCIIEncoding.ASCII.GetBytes(string.Format("{0}={1}", "PEEP", calculatedValues.Peep.ToString("0.00")))); } catch (Exception e) { logger.LogError(e, e.Message); } var timeSpent = (DateTime.Now - start).TotalMilliseconds; // Console.WriteLine("Time taken processing: {0}", timeSpent); await Task.Delay(Math.Max(1, 500 - (int)timeSpent)); } })); }
public Task Start(CancellationToken cancellationToken) { CalculatedValues calculatedValues = new CalculatedValues(); return(Task.Run(async() => { while (!cancellationToken.IsCancellationRequested) { var start = DateTime.Now; try { var settings = webSocketThread.Settings; calculatedValues.ResetValues(); // are we active? if (!(settings.ContainsKey("ACTIVE") && settings["ACTIVE"] > 1.0f)) { // make sure we stop playing the alarm serialThread.ResetAlarm(); await Task.Delay(2000); continue; } uint alarmBits = 0; var values = GetDocuments("measured_values", DateTime.UtcNow.AddSeconds(-70)); if (values.Count == 0) { // no data yet, wait for it to become available await Task.Delay(2000); continue; } // find the lowest datetime value that we all have var maxDateTime = values.First().LoggedAt; values.Reverse(); // check breaths per minute List <Tuple <DateTime, DateTime> > breathingCycles = GetBreathingCyclesFromTargetPressure(values, maxDateTime); // do we have a full cycle if (breathingCycles.Count > 0) { DateTime startBreathingCycle = breathingCycles.Last().Item1; DateTime endBreathingCycle = breathingCycles.Last().Item2; var minValTargetPressure = GetMinimum(values, (valueEntry) => valueEntry.Value.TargetPressure, startBreathingCycle, endBreathingCycle); var maxValTargetPressure = GetMaximum(values, (valueEntry) => valueEntry.Value.TargetPressure, startBreathingCycle, endBreathingCycle); var targetPressureExhale = values .Where(v => v.LoggedAt > startBreathingCycle && v.LoggedAt <= endBreathingCycle && v.Value.TargetPressure == minValTargetPressure) .FirstOrDefault(); DateTime exhalemoment = targetPressureExhale.LoggedAt.AddMilliseconds(-40); var breathingCycleDuration = (endBreathingCycle - startBreathingCycle).TotalSeconds; var bpm = 60.0 / breathingCycleDuration; calculatedValues.RespatoryRate = bpm; if (settings.ContainsKey("RR")) { if (bpm <= settings["RR"] - 1.0) { alarmBits |= BPM_TOO_LOW; } } var inhaleTime = (exhalemoment - startBreathingCycle).TotalSeconds; var exhaleTime = (endBreathingCycle - exhalemoment).TotalSeconds; calculatedValues.IE = inhaleTime / breathingCycleDuration; var tidalVolume = GetMaximum(values, (valueEntry) => valueEntry.Value.Volume, startBreathingCycle, endBreathingCycle); if (settings.ContainsKey("VT") && settings.ContainsKey("ADVT")) { if (Math.Abs(tidalVolume - settings["VT"]) > settings["ADVT"]) { alarmBits |= VOLUME_NOT_OK; } } calculatedValues.TidalVolume = tidalVolume; var residualVolume = GetMinimum(values, (valueEntry) => valueEntry.Value.Volume, exhalemoment, endBreathingCycle.AddMilliseconds(-80)); if (Math.Abs(residualVolume) > 50) { alarmBits |= RESIDUAL_VOLUME_NOT_OK; } var peakPressureMoment = values .Where(v => v.LoggedAt >= startBreathingCycle && v.LoggedAt <= exhalemoment) .Aggregate((i1, i2) => i1.Value.Pressure > i2.Value.Pressure ? i1 : i2); var plateauMinimumPressure = GetMinimum(values, (valueEntry) => valueEntry.Value.Pressure, peakPressureMoment.LoggedAt, exhalemoment); if (settings.ContainsKey("ADPK")) { if (!(Math.Abs(peakPressureMoment.Value.Pressure - maxValTargetPressure) < settings["ADPK"] && Math.Abs(plateauMinimumPressure - maxValTargetPressure) < settings["ADPK"])) { alarmBits |= PRESSURE_NOT_OK; } } calculatedValues.PressurePlateau = plateauMinimumPressure; //did we have a trigger within this cycle? var triggerMoment = values .FirstOrDefault(p => p.LoggedAt >= exhalemoment && p.LoggedAt <= endBreathingCycle && p.Value.Trigger > 0.0f); var endPeep = endBreathingCycle; if (triggerMoment != null) { endPeep = triggerMoment.LoggedAt.AddMilliseconds(-50); } if (settings.ContainsKey("PP") && settings.ContainsKey("ADPP")) { bool firstPeepPressureIteration = true; ValueEntry previousPoint = null; List <float> slopes = new List <float>(); int plateauCounter = 0; bool foundPlateau = false; var pressureExhaleValues = values .Where(v => v.LoggedAt >= exhalemoment.AddMilliseconds(100) && v.LoggedAt <= endPeep) .OrderByDescending(v => v.LoggedAt) .ToList(); for (int i = 0; i < pressureExhaleValues.Count - 2; i += 2) { var valueEntry = pressureExhaleValues[i]; // if last value is above PEEP, assume everything is ok if (firstPeepPressureIteration) { if (valueEntry.Value.Pressure > settings["PP"] - settings["ADPP"]) { foundPlateau = true; break; } firstPeepPressureIteration = false; } // we are still here, so last value was not in peep threshold // go back until we are above PEEP again and start calculating slope // when the average slope is steadily declining, we have a leak if (previousPoint != null) { var gradient = (previousPoint.Value.Pressure - valueEntry.Value.Pressure) / ((float)(previousPoint.LoggedAt - valueEntry.LoggedAt).TotalSeconds); if (gradient > -1.0f) { plateauCounter++; if (plateauCounter == 3) { if (valueEntry.Value.Pressure > settings["PP"] - settings["ADPP"]) { foundPlateau = true; break; } else { plateauCounter = 0; } } } else { plateauCounter = 0; } } previousPoint = valueEntry; } if (!foundPlateau) { // all points are below peep, clearly we should raise an alarm alarmBits |= PEEP_NOT_OK; } } // only for debug purposes, send the moments to the frontend //await serialThread.SendSettingToServer("breathingCycleStart", startBreathingCycle.Value.); } serialThread.AlarmValue = alarmBits; if (breathingCycles.Count > 1) { foreach (var breathingCycle in breathingCycles) { var tidalVolume = values .Where(v => v.LoggedAt >= breathingCycle.Item1 && v.LoggedAt <= breathingCycle.Item2) .Max(v => v.Value.Volume); calculatedValues.VolumePerMinute += tidalVolume / 1000.0; } calculatedValues.VolumePerMinute = calculatedValues.VolumePerMinute / (breathingCycles.Last().Item2 - breathingCycles.First().Item1).TotalSeconds * 60.0; } await flurlClient.Request("/api/calculated_values") .PutJsonAsync(calculatedValues); } catch (Exception e) { Console.WriteLine(e.Message); Console.WriteLine(e.StackTrace); } var timeSpent = (DateTime.Now - start).TotalMilliseconds; // Console.WriteLine("Time taken processing: {0}", timeSpent); await Task.Delay(Math.Max(1, 500 - (int)timeSpent)); } })); }