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));
                }
            }));
        }
예제 #2
0
        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));
                }
            }));
        }