diff --git a/breath_plot.html b/breath_plot.html
new file mode 100644
index 0000000000000000000000000000000000000000..11aec55358c8183fe6ffe37e2a7c78d2f1034a41
--- /dev/null
+++ b/breath_plot.html
@@ -0,0 +1,1703 @@
+  <!--
+Breath Plot: COVID-19 Respiration Analysis Software
+    Copyright (C) 2020  Robert L. Read
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as
+    published by the Free Software Foundation, either version 3 of the
+    License, or (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+  -->
+<!doctype html>
+<html lang="en">
+  <head>
+    <!-- Required meta tags -->
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+
+    <!-- Bootstrap CSS -->
+    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
+
+	<!-- Load plotly.js into the DOM -->
+  <script src='https://cdn.plot.ly/plotly-latest.min.js'></script>
+
+    <title>Public Invention Respiration Analysis</title>
+
+</head>
+  <style>
+#calcarea {
+  flex-direction:column;
+  background: AliceBlue;
+}
+.calcnum {
+    color: blue;
+    font-size: xx-large;
+}
+.fence {
+width: 3em;
+}
+.value_and_fences {
+  display: flex;
+flex-direction: row;
+justify-content: space-between;
+}
+.limit {
+  display: flex;
+  flex-direction: row;
+}
+.limit  label {
+   width: 1em;
+}
+
+  .alarmred {
+    background: red;
+  }
+
+</style>
+
+<!-- Style for toggle switch -->
+  <style>
+  .switch {
+  position: relative;
+  display: inline-block;
+  width: 60px;
+  height: 34px;
+}
+
+.switch input {
+  opacity: 0;
+  width: 0;
+  height: 0;
+}
+
+.slider {
+  position: absolute;
+  cursor: pointer;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background-color: #ccc;
+  -webkit-transition: .4s;
+  transition: .4s;
+}
+
+.slider:before {
+  position: absolute;
+  content: "";
+  height: 26px;
+  width: 26px;
+  left: 4px;
+  bottom: 4px;
+  background-color: white;
+  -webkit-transition: .4s;
+  transition: .4s;
+}
+
+input:checked + .slider {
+  background-color: #2196F3;
+}
+
+input:focus + .slider {
+  box-shadow: 0 0 1px #2196F3;
+}
+
+input:checked + .slider:before {
+  -webkit-transform: translateX(26px);
+  -ms-transform: translateX(26px);
+  transform: translateX(26px);
+}
+
+/* Rounded sliders */
+.slider.round {
+  border-radius: 34px;
+}
+
+.slider.round:before {
+  border-radius: 50%;
+}
+</style>
+  <body>
+
+    <div class="container-fluid">
+
+      <div class="jumbotron">
+        <h1 class="display-4">VentMon Respiration Analysis</h1>
+        <p class="lead">This is a work in progress of <a href="https://www.pubinv.org">Public Invention</a>. It can be attached to a data server to produce
+an interactive or static analysis of a respiration. It&#39;s primary purpose is to test pandemic ventilators, but it is free software meant to be reused for other purposes.
+        </p>
+      </div>
+
+
+
+
+      <div class="input-group mb-3">
+        <div class="input-group-prepend">
+          <span class="input-group-text" id="basic-addon3">PIRDS data server url:</span>
+        </div>
+        <input type="text" class="form-control" id="dserverurl" aria-describedby="basic-addon3">
+
+        <div class="input-group-append">
+          <a class="btn btn-outline-dark btn-sm" href="#" role="button" id="useofficial">Use Ventmon Data Lake: ventmon.coslabs.com</a>
+        </div>
+      </div>
+
+      <div class="input-group mb-3">
+        <div class="input-group-prepend">
+          <span class="input-group-text" id="basic-addon3">Trace ID:</span>
+        </div>
+        <input type="text" class="form-control" id="traceid" aria-describedby="basic-addon3">
+      </div>
+
+      <div class="input-group mb-3">
+        <div class="input-group-prepend">
+  <span class="input-group-text" for="samples_to_plot">Number of Samples (~10s per 15000 samples):</span>
+        </div>
+        <input type="text" class="form-control" id="samples_to_plot" aria-describedby="samples_to_plot">
+      </div>
+
+
+  <label for="livetoggle">Plot Live:</label>
+<label class="switch">
+  <input type="checkbox" id="livetoggle" checked>
+  <span class="slider round"></span>
+</label>
+
+  <div class="container-fluid">
+    <div class="row">
+          <div class="col-9">
+            <div id='PFGraph'><!-- Pressure and Flow Graph --></div>
+            <div id='EventsGraph'><!-- Events --></div>
+          </div>
+          <div class="col-3">
+            <div class="alert alert-danger" role="alert" id="main_alert">
+  Danger! Flow limits exceeded; volumes will be incorrect.
+            </div>
+            <div class="container" id="calcarea">
+              <div>
+                <label for="max">PIP (max): </label>
+                  <div class="value_and_fences">
+                    <div class="calcnum">
+                      <label id="max"> </label>
+                    </div>
+                    <div class="vertical_alarms">
+                    <div class="limit max">
+                      <label for="max_h">H:</label>
+                      <input class="fence" id="max_h" type='text'> </input>
+                    </div>
+                    <div class="limit min">
+                      <label for="max_l">L:</label>
+                      <input class="fence" id="max_l" type='text'> </input>
+                    </div>
+                    </div>
+                  </div>
+             </div>
+             <div>
+                <label for="avg">P. Mean: </label>
+                  <div class="value_and_fences">
+                    <div class="calcnum">
+                      <label id="avg"> </label>
+                    </div>
+                    <div class="vertical_alarms">
+                    <div class="limit max">
+                      <label for="avg_h">H:</label>
+                      <input class="fence" id="avg_h" type='text'> </input>
+                    </div>
+                    <div class="limit min">
+                      <label for="avg_l">L:</label>
+                      <input class="fence" id="avg_l" type='text'> </input>
+                    </div>
+                    </div>
+                  </div>
+             </div>
+             <div>
+                <label for="min">PEEP (min): </label>
+                  <div class="value_and_fences">
+                    <div class="calcnum">
+                      <label id="min"> </label>
+                    </div>
+                    <div class="vertical_alarms">
+                    <div class="limit max">
+                      <label for="min_h">H:</label>
+                      <input class="fence" id="min_h" type='text'> </input>
+                    </div>
+                    <div class="limit min">
+                      <label for="min_l">L:</label>
+                      <input class="fence" id="min_l" type='text'> </input>
+                    </div>
+                    </div>
+                  </div>
+             </div>
+             <div>
+                <label for="mv">MVs (l/min): </label>
+                  <div class="value_and_fences">
+                    <div class="calcnum">
+                      <label id="mv"> </label>
+                    </div>
+                    <div class="vertical_alarms">
+                    <div class="limit max">
+                      <label for="mv_h">H:</label>
+                      <input class="fence" id="mv_h" type='text'> </input>
+                    </div>
+                    <div class="limit min">
+                      <label for="mv_l">L:</label>
+                      <input class="fence" id="mv_l" type='text'> </input>
+                    </div>
+                    </div>
+                  </div>
+                </div>
+              <div>
+                <label for="bpm">RR: </label>
+                  <div class="value_and_fences">
+                    <div class="calcnum">
+                      <label id="bpm"> </label>
+                    </div>
+                    <div class="vertical_alarms">
+                    <div class="limit max">
+                      <label for="bmp_h">H:</label>
+                      <input class="fence" id="bpm_h" type='text'> </input>
+                    </div>
+                    <div class="limit min">
+                      <label for="bpm_l">L:</label>
+                      <input class="fence" id="bpm_l" type='text'> </input>
+                    </div>
+                    </div>
+                  </div>
+                </div>
+                <div>
+                  <label for="ier">I:E ratio: </label>
+                  <div class="value_and_fences">
+                    <div class="calcnum">
+                      <label id="ier"> </label>
+                    </div>
+                    <div class="vertical_alarms">
+                    <div class="limit max">
+                      <label for="ier_h">H:</label>
+                      <input class="fence" id="ier_h" type='text'> </input>
+                    </div>
+                    <div class="limit min">
+                      <label for="ier_l">L:</label>
+                      <input class="fence" id="ier_l" type='text'> </input>
+                    </div>
+                    </div>
+                  </div>
+              </div>
+                <div>
+                <label for="tv">VTd (ml): </label>
+                  <div class="value_and_fences">
+                    <div class="calcnum">
+                      <label id="tv"> </label>
+                    </div>
+                    <div class="vertical_alarms">
+                    <div class="limit max">
+                      <label for="tv_h">H:</label>
+                      <input class="fence" id="tv_h" type='text'> </input>
+                    </div>
+                    <div class="limit min">
+                      <label for="tv_l">L:</label>
+                      <input class="fence" id="tv_l" type='text'> </input>
+                    </div>
+                    </div>
+                  </div>
+              </div>
+                <div>
+                  <label for="fio2">FiO2 Mean (%): </label>
+                  <div class="value_and_fences">
+                    <div class="calcnum">
+                      <label id="fio2"> </label>
+                    </div>
+                    <div class="vertical_alarms">
+                      <div class="limit max">
+                        <label for="fio2_h">H:</label>
+                        <input class="fence" id="fio2_h" type='text'> </input>
+                    </div>
+                    <div class="limit min">
+                      <label for="fio2_l">L:</label>
+                      <input class="fence" id="fio2_l" type='text'> </input>
+                    </div>
+                  </div>
+                </div>
+                <div>
+                  <label for="taip">TAIP (ms): </label>
+                  <div class="value_and_fences">
+                    <div class="calcnum">
+                      <label id="taip"> </label>
+                    </div>
+                    <div class="vertical_alarms">
+                      <div class="limit max">
+                        <label for="taip_h">H:</label>
+                        <input class="fence" id="taip_h" type='text'> </input>
+                    </div>
+                    <div class="limit min">
+                      <label for="taip_l">L:</label>
+                      <input class="fence" id="taip_l" type='text'> </input>
+                    </div>
+                  </div>
+                </div>
+              </div>
+                <div>
+                  <label for="taip">TRIP (ms): </label>
+                  <div class="value_and_fences">
+                    <div class="calcnum">
+                      <label id="trip"> </label>
+                    </div>
+                    <div class="vertical_alarms">
+                      <div class="limit max">
+                        <label for="trip_h">H:</label>
+                        <input class="fence" id="trip_h" type='text'> </input>
+                    </div>
+                    <div class="limit min">
+                      <label for="trip_l">L:</label>
+                      <input class="fence" id="trip_l" type='text'> </input>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div> <!-- end main area -->
+  </div>
+
+  <div>
+  Parameters:
+  <div>
+        <label for="tarip_h">TAIP/TRIP High Pressure (cm H2O):</label>
+  <input class="fence" id="tarip_h" type='text'> </input>
+  </div>
+  <div>
+        <label for="tarip_l">TAIP/TRIP Low Pressure (cm H2O):</label>
+  <input class="fence" id="tarip_l" type='text'> </input>
+  </div>
+</div>
+
+<div>
+  <button id="import">Import Trace</button>
+  <button id="export">Export Trace</button>
+</div>
+<div>
+  <textarea id="json_trace" rows="30" cols="80"></textarea>
+</div>
+
+<p>
+  This is a work in progress of <a href="https://www.pubinv.org">Public Invention</a>.
+
+<p>
+  This is a tester tool for open-source ventilators.
+  It uses the <a href="https://github.com/PubInv/respiration-data-standard">PIRDS data format</a>.
+
+<p>
+  The basic operation is receive data from web server specified in the URL above.
+  Probably for now that will be the VentMon Python web server that
+  listens on a serial port for the VentMon device or any other
+  device that streams PIRDS events.
+
+</div>
+</body>
+  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
+
+  <script>
+
+var TAIP_AND_TRIP_MIN = 2;
+var TAIP_AND_TRIP_MAX = 8;
+
+function getParameterByName(name, url) {
+    if (!url) url = window.location.href;
+    name = name.replace(/[\[\]]/g, '\\$&');
+    var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'),
+        results = regex.exec(url);
+    if (!results) return null;
+    if (!results[2]) return '';
+    return decodeURIComponent(results[2].replace(/\+/g, ' '));
+}
+
+// These are defined in PIRDS.h as "constant" key words of special meaning...
+var FLOW_LIMITS_EXCEEDED = false;
+const FLOW_TOO_HIGH = "FLOW OUT OF RANGE HIGH";
+const FLOW_TOO_LOW = "FLOW OUT OF RANGE LOW";
+
+const VENTMON_DATA_LAKE = "http://ventmon.coslabs.com";
+const queryString = window.location.search;
+const urlParams = new URLSearchParams(queryString);
+var TRACE_ID = urlParams.get('i')
+var DSERVER_URL = window.location.protocol + "//" + window.location.host;
+var NUM_TO_READ = 500;
+
+var DATA_RETRIEVAL_PERIOD = 50;
+
+var RESPIRATION_RATE_WINDOW_SECONDS = 20;
+
+var intervalID = null;
+
+
+// This sould be better as time, but is easier
+// to do as number of samples.
+var MAX_SAMPLES_TO_STORE_S = 32000;
+var samples = [];
+var INITS_ONLY = true;
+
+// This is just to get the party started!
+function init_samples() {
+    return [
+	{
+	    "event": "M",
+	    "type": "P",
+	    "ms": 19673310,
+	    "loc": "A",
+	    "num": "0",
+	    "val": 10111
+	},
+	{
+	    "event": "M",
+	    "type": "D",
+	    "ms": 19673310,
+	    "loc": "A",
+	    "num": "0",
+	    "val": 4
+	},
+	{
+	    "event": "M",
+	    "type": "F",
+	    "ms": 19673310,
+	    "loc": "A",
+	    "num": "0",
+	    "val": 0
+	},
+	{
+	    "event": "M",
+	    "type": "P",
+	    "ms": 19673376,
+	    "loc": "A",
+	    "num": "0",
+	    "val": 10111
+	},
+	{
+	    "event": "M",
+	    "type": "D",
+	    "ms": 19673376,
+	    "loc": "A",
+	    "num": "0",
+	    "val": 4
+	},
+	{
+	    "event": "M",
+	    "type": "F",
+	    "ms": 19673376,
+	    "loc": "A",
+	    "num": "0",
+	    "val": 0
+	},
+	{
+	    "event": "M",
+	    "type": "P",
+	    "ms": 19673442,
+	    "loc": "A",
+	    "num": "0",
+	    "val": 10110
+	},
+	{
+	    "event": "M",
+	    "type": "D",
+	    "ms": 19673442,
+	    "loc": "A",
+	    "num": "0",
+	    "val": 3
+	},
+    ];
+}
+
+
+
+function unpack(rows, key) {
+    return rows.map(function(row) { return row[key]; });
+}
+
+const CONVERT_PIRDS_TO_SLM = 1/1000;
+
+// we have now changed this, there will be flow and
+// pressure in the same samples, and we should filter.
+// TODO: I need to add maximal start and end
+// samples to equalize all the plots.
+function samplesToLine(samples) {
+    var flows = samples.filter(s => s.event == 'M' && s.type == 'F');
+
+    // These are slm/1000, or ml/minute...
+    // so we multiply by 1000 to get liters per minute
+    var flow_values = unpack(flows,"val").map(v => v * CONVERT_PIRDS_TO_SLM);
+    var fmillis = unpack(flows, 'ms');
+    // Convert to seconds...
+    var fmin = Math.min(...fmillis);
+    var fzeroed = fmillis.map(m =>(m-fmin)/1000.0);
+
+    var pressures = samples.filter(s => s.event == 'M' && s.type == 'D' && s.loc == 'A');
+
+    var pmillis = unpack(pressures, 'ms');
+    var pmin = Math.min(...pmillis);
+    var pzeroed = pmillis.map(m =>(m-pmin)/1000.0);
+    // the PIRDS standard is integral mm H2O, so we divide by 10
+    var delta_p = unpack(pressures, 'val').map(p => p / 10);
+    var diff_p = {type: "scatter", mode: "lines",
+		  name: "pressure",
+		  x: pzeroed,
+		  y: delta_p,
+		  line: {color: "#FF0000"}
+		 };
+
+  var flow = {type: "scatter", mode: "lines",
+		name: "flow",
+		x: fzeroed,
+	      y: flow_values,
+              xaxis: 'x2',
+              yaxis: 'y2',
+              fill: 'tozeroy',
+		line: {color: '#0000FF'}
+             };
+
+  var max_flow = flow_values.reduce(
+    function(a, b) {
+      return Math.max(Math.abs(a), Math.abs(b));
+    }
+    ,0);
+  var scaled_flow = flow_values.map(f => 100.0 * (f / max_flow));
+  var flow_hollow = {type: "scatter", mode: "lines",
+		name: "flow ghost",
+		     x: fzeroed,
+                     // Convert to a percentage
+	             y: scaled_flow,
+		line: {color: '#8888FF'}
+               };
+  return [diff_p,flow,flow_hollow];
+}
+function plot(samples, trans, breaths) {
+  var new_data = samplesToLine(samples);
+  var millis = unpack(samples, 'ms');
+  var min = Math.min(...millis);
+  var zeroed = millis.map(m =>(m-min)/1000.0);
+
+  {
+    var layout = {
+      title: 'VentMon Breath Analysis',
+      showlegend: false,
+      xaxis: {domain: [0.0,1.0]},
+      yaxis: {
+        title: 'Airway P(cm H2O)',
+        titlefont: {color: 'red'},
+        tickfont: {color: 'red'},
+      },
+      xaxis2: {domain: [0.0,1.0]},
+      yaxis2: {
+        title: 'Flow l/minute',
+        titlefont: {color: 'blue'},
+        tickfont: {color: 'blue'}
+      },
+      grid: {
+        rows: 2,
+        columns: 1,
+        pattern: 'independent',
+        roworder: 'top to bottom'}
+    }
+
+    var double_plot = [new_data[0],new_data[1]];
+
+    Plotly.newPlot('PFGraph', double_plot, layout);
+  }
+
+  // The Y-axis for the events will be percentage.
+  // This is somewhat abstract; each trace has
+  // a different meaning. In general it will be
+  // % as a function of some known value, which
+  // will be either a limit or a min or max.
+  {
+    var event_graph = [];
+    if (trans) {
+      // Transitions are simply scaled to 50%.
+      var tmillis = unpack(trans, 'ms');
+      var tzeroed = tmillis.map(m =>(m-min)/1000.0);
+      var tstates = unpack(trans, 'state');
+      var tstates_amped = tstates.map(m =>m*50);
+      var transPlot = {type: "scatter",
+                       mode: "lines+markers",
+		       name: "trans",
+		       x: tzeroed,
+		       y: tstates_amped,
+                       line: {shape: 'hv',
+                              color: 'dkGreen'},
+		      };
+      // We add a hollow flow line to see in position..
+      event_graph.push(new_data[2]);
+      event_graph.push(transPlot);
+    }
+
+    if (breaths) {
+      var bmillis = unpack(breaths, 'ms');
+      var bzeroed = bmillis.map(m =>(m-min)/1000.0);
+      // I'm goint to a add start and end transition to make the plot
+      // come out right
+      var ys = breaths.map( b => 0);
+      var breathPlot = {type: "scatter", mode: "markers",
+		        name: "Transitions",
+		        x: bzeroed,
+		        y: ys,
+		        marker: { size: 8, color: "red",symbol: "diamond" },
+                        textposition: 'bottom center',
+                        text: bzeroed
+		       };
+      event_graph.push(breathPlot);
+      // Now I attempt to extract volumes...
+      // Our breaths mark the END of a breath..
+      // so we want to draw the volumes that way.
+      const volume_ht_factor = 20;
+      var max_exh = unpack(breaths,'vol_e').reduce(
+        function(a, b) {
+          return Math.max(Math.abs(a), Math.abs(b));
+        }
+        ,0);
+      var max_inh = unpack(breaths,'vol_i').reduce(
+        function(a, b) {
+          return Math.max(Math.abs(a), Math.abs(b));
+        }
+        ,0);
+      var max_v = Math.max(max_inh,max_exh);
+
+      var exh_v = unpack(breaths, 'vol_e').map(e => 100 * e / max_v);
+      var t_exh_v =  unpack(breaths, 'vol_e').map(e => Math.round(e*1000.0)+"ml exh");
+      var inh_v = unpack(breaths, 'vol_i').map(i => 100 * i / max_v);
+      var t_inh_v = unpack(breaths, 'vol_i').map(e => Math.round(e*1000.0)+"ml inh");
+      // now to graph properly, I must find the center of an inhalation.
+      // I have packed these into the breaths...
+      var inhale_centers = breaths.map(b => ((trans[b.trans_begin_inhale].ms + trans[b.trans_cross_zero].ms) - 2*min) / (2.0 * 1000.0));
+      var exhale_centers = breaths.map(b => ((trans[b.trans_cross_zero].ms + trans[b.trans_end_exhale].ms) - 2*min) / (2.0 * 1000.0));
+
+      var inhPlot = {type: "scatter", mode: "markers+text",
+                     name: "Inh. ml",
+                     textposition: 'bottom center',
+                     x: inhale_centers,
+                     y: inh_v,
+                     text: t_inh_v,
+                     marker: { size: 8,
+                               color: 'black',
+                               symbol: 'triangle-down'}
+                    };
+      var exhPlot = {type: "scatter", mode: "markers+text",
+                     name: "Exh. ml",
+                     textposition: 'top center',
+                     marker : {
+                       sizer: 12,
+                       color: 'green',
+                       symbol: 'triangle-up' },
+                     x: exhale_centers,
+                     y: exh_v,
+                     text: t_exh_v,
+
+                    };
+      event_graph.push(inhPlot);
+      event_graph.push(exhPlot);
+    }
+
+    // Now I will attempt to add other markers, such as Humidity, Altitude, and other events,
+    // including possible warning events.
+
+    function gen_graph_measurement(samples, name, color, textposition, tp, lc, vf, tf) {
+      var selected = samples.filter(s => s.event == 'M' && s.type == tp && s.loc == lc);
+      return gen_graph(selected, name, color, textposition, vf, tf);
+    }
+    function gen_graph(selected,name,color, textposition, vf, tf) {
+      var millis = unpack(selected, 'ms');
+      var zeroed = millis.map(m =>(m-min)/1000.0);
+      var vs = selected.map(vf);
+      var vs_t = vs.map(tf);
+      var plot = {type: "scatter", mode: "markers+text",
+                     name: name,
+                     textposition: textposition,
+                     x: zeroed,
+                     y: vs,
+                     text: vs_t,
+                     marker: { size: 10, color: color }
+                    };
+      return plot;
+
+    }
+    function gen_clock_graph(selected,name,color, textposition) {
+      var millis = unpack(selected, 'ms');
+      var zeroed = millis.map(m =>(m-min)/1000.0);
+      var vs = selected.map( s => -30)
+      var vs_t = selected.map(v => v.buff);
+      var plot = {type: "scatter", mode: "markers+text",
+                     name: name,
+                     textposition: textposition,
+                     x: zeroed,
+                     y: vs,
+                     text: vs_t,
+                     marker: { size: 10, color: color }
+                    };
+      return plot;
+
+    }
+    function gen_message_events(samples,name,color, textposition) {
+      var messages = samples.filter(s => s.event == 'E' && s.type == 'M');
+      // Now I want to filter our all flow error messages...
+      // There are two special ones, "FLOW OUT OF RANGE LOW" and
+      // "FLOW OUT OF RANGE HIGH"
+      // Basically, we will show only the first of these in a "run"
+      // This is rather difficult; we will instead just use 200 ms
+      // as a window.
+      const time_window_ms = 200;
+
+      var lows = [];
+      var highs = [];
+      var others = [];
+      for(var i = 0; i < messages.length; i++) {
+        var m = messages[i];
+        if (m.buff == FLOW_TOO_LOW) {
+          if ((lows.length == 0) || ((lows.length > 0) && (lows[lows.length-1].ms < (m.ms - time_window_ms))))
+            lows.push(m);
+        } else if (m.buff == FLOW_TOO_HIGH) {
+          if ((highs.length == 0) || ((highs.length > 0) && (highs[highs.length-1].ms < (m.ms - time_window_ms))))
+            highs.push(m);
+        } else {
+          others.push(m);
+        }
+      }
+      if (lows.length > 0 || highs.length > 0) {
+        set_flow_alert();
+      } else {
+        unset_flow_alert();
+      }
+
+      var lowsPlot = gen_graph(lows,"Low Range","blue",'top center',
+                                    (s => -90.0),
+                                    (v =>  "LOW"));
+      var highsPlot = gen_graph(highs,"High Range","red",'bottom center',
+                                    (s => 90.0),
+                                    (v =>  "HIGH"));
+      var othersPlot = gen_graph(others,"messages","Aqua",'top center',
+                                    (s => -75.0),
+                                 (v =>  v.buff));
+      return [lowsPlot,highsPlot,othersPlot];
+    }
+    function gen_clock_events(samples,name,color, textposition) {
+      var messages = samples.filter(s => s.event == 'E' && s.type == 'C');
+      const time_window_ms = 200;
+
+      var clocks = [];
+      for(var i = 0; i < messages.length; i++) {
+        var m = messages[i];
+         if ((clocks.length == 0) || ((clocks.length > 0) && (clocks[clocks.length-1].ms < (m.ms - time_window_ms))))
+            clocks.push(m);
+      }
+
+      var clocksPlot = gen_clock_graph(clocks,name,color,textposition);
+      return clocksPlot;
+    }
+    {
+      var fio2AirwayPlot = gen_graph_measurement(samples,"FiO2 (%)","Black",'top center','O','A',
+                              (s => s.val),
+                              (v =>  "FiO2 (A): "+v.toFixed(1)+"%"));
+      event_graph.push(fio2AirwayPlot);
+    }
+
+    {
+      var humAirwayPlot = gen_graph_measurement(samples,"Hum (%)","Aqua",'top center','H','A',
+                              (s => s.val/100.0),
+                              (v =>  "H2O (A): "+v.toFixed(0)+"%"));
+      event_graph.push(humAirwayPlot);
+    }
+
+    {
+      var humAmbientPlot = gen_graph_measurement(samples,"Hum (%)","CornflowerBlue",'bottom center','H','B',
+                              (s => -s.val/100.0),
+                                      (v =>  "H2O (B): "+(-v).toFixed(0)+"%"));
+      event_graph.push(humAmbientPlot);
+    }
+
+    {
+      var tempAirwayPlot = gen_graph_measurement(samples,"Temp A","red",'bottom center','T','A',
+                              (s => s.val/100.0),
+                              (v =>  "T (A): "+v.toFixed(1)+"C"));
+      event_graph.push(tempAirwayPlot);
+    }
+
+    {
+      var tempAmbientPlot = gen_graph_measurement(samples,"Temp B","orange",'top center','T','B',
+                              (s => -s.val/100.0),
+                                      (v =>  "T (B): "+(-v).toFixed(1)+"C"));
+      event_graph.push(tempAmbientPlot);
+    }
+
+    // Altitude is really just a check that the system is working
+     {
+      var altAmbientPlot = gen_graph_measurement(samples,"Altitude","purple",'right','A','B',
+                              (s => s.val/20.0),
+                                      (v =>  "Alt: "+(v*20.0).toFixed(0)+"m"));
+      event_graph.push(altAmbientPlot);
+     }
+
+     {
+       var [l,h,o] = gen_message_events(samples,"Message","red",'right');
+       event_graph.push(l);
+       event_graph.push(h);
+       event_graph.push(o);
+     }
+
+     {
+       var cs = gen_clock_events(samples,"Wall Clock","black",'top center');
+
+       event_graph.push(cs);
+     }
+
+
+
+    // The BME680 sensor detects VOC gases. I don't think has any clinical value
+    // compared
+
+    // {
+    //   var gasAirwayPlot = gen_graph_measurement(samples,"Gas A","yellow",'bottom center','G','A',
+    //                           (s => s.val/1000.0),
+    //                                 (v =>  "G (A): "+(v*100).toFixed(1)+"Ohms"));
+    //   event_graph.push(gasAirwayPlot);
+    // }
+
+    // {
+    //   var gasAmbientPlot = gen_graph_measurement(samples,"Gas B","gold",'top center','G','B',
+    //                           (s => -s.val/1000.0),
+    //                                   (v =>  "G (B): "+(-v*100).toFixed(1)+"Ohms"));
+    //   event_graph.push(gasAmbientPlot);
+    // }
+
+
+
+    // I'm going to try putting the pressure
+    // in faintly to make the graphs match
+
+    var event_layout = {
+      title: 'Events',
+      showlegend: false,
+      xaxis: {domain: [0.0,1.0]},
+      yaxis: {
+        range: [-100.0, 100.0]
+      },
+    };
+    Plotly.newPlot('EventsGraph', event_graph,event_layout);
+  }
+}
+
+function set_flow_alert() {
+  $("#main_alert").show();
+}
+function unset_flow_alert() {
+  $("#main_alert").hide();
+}
+
+function check_alarms(limits,key,limit,val,f,ms) {
+  var alarms = [];
+  if (limits[key][limit] && (f(val,limits[key][limit]))) {
+    alarms.push({param: key,
+                 limit: limit,
+                 val: val,
+                 ms: ms});
+  }
+  return alarms;
+}
+
+
+// Return, [min,avg,max] pressures (no smoothing)!
+function compute_pressures(secs,samples) {
+  var pressures = samples.filter(s => s.event == 'M' && s.type == 'D' && s.loc == 'A');
+
+  if (pressures.length == 0) {
+    return [0,0,0,[]];
+  } else {
+    const recent_ms = pressures[pressures.length - 1].ms;
+    var cur_ms = recent_ms;
+    var cnt = 0.0;
+    var i = pressures.length - 1;
+    var cur_sample = pressures[i];
+
+    var min = Number.MAX_VALUE;
+    var max = Number.MIN_VALUE;
+    var sum = 0;
+    var alarms = [];
+    while((i >=0) && (pressures[i].ms > (cur_ms - secs*1000))) {
+      var p = pressures[i].val / 10.0;  // this is now cm H2O
+      if (p < min ) {
+        min = p;
+      }
+      if (p > max) {
+        max = p;
+      }
+      sum += p;
+      cnt++;
+      alarms = alarms.concat(check_alarms(LIMITS,"max","h",p,(a,b) =>(a > b),pressures[i].ms));
+      alarms = alarms.concat(check_alarms(LIMITS,"max","l",p,(a,b) =>(a < b),pressures[i].ms));
+      i--;
+    }
+    return [min,sum/cnt,max,alarms];
+  }
+}
+
+function compute_fio2_mean(secs,samples) {
+  var oxygens = samples.filter(s => s.event == 'M' && s.type == 'O' && s.loc == 'A');
+
+  if (oxygens.length == 0) {
+    return null;
+  } else {
+    const recent_ms = oxygens[oxygens.length - 1].ms;
+    var cur_ms = recent_ms;
+    var cnt = 0.0;
+    var i = oxygens.length - 1;
+
+    var sum = 0;
+    var alarms = [];
+    while((i >=0) && (oxygens[i].ms > (cur_ms - secs*1000))) {
+      var oxy = oxygens[i].val; // oxygen concentration as a percentage
+      sum += oxy;
+      cnt++;
+      i--;
+    }
+
+    var fio2_avg = sum / cnt;
+
+    return fio2_avg;
+  }
+}
+
+
+function compute_respiration_rate(secs,samples,transitions,breaths) {
+  // In order to compute the number of breaths
+  // in the last s seconds, I compute those breaths
+  // whose time stamp is s seconds from the most recent sample
+
+  // We will compute respiration rate by counting breaths
+  // and dividing (cnt - 1) by time the first and last inhalation
+  var first_inhale_ms = -1;
+  var last_inhale_ms = -1;
+  if (breaths.length == 0) {
+    return [0,0,0,"NA"];
+  } else {
+    const recent_ms = samples[samples.length - 1].ms;
+    var cur_ms = recent_ms;
+    var cnt = 0.0;
+    var vol_i = 0.0;
+    var vol_e = 0.0;
+    var i = breaths.length - 1;
+    var time_inh = 0;
+    var time_exh = 0;
+    // fully completed inhalation volume does not include the
+    // most recent breath; we need it to be able to accurately
+    // divide by the inhlation_duration.
+    var vol_ci = 0.0;
+
+    while((i >=0) && (breaths[i].ms > (cur_ms - secs*1000))) {
+      cnt++;
+      vol_i += breaths[i].vol_i;
+      if (i < (breaths.length -1)) {
+        vol_ci += breaths[i].vol_i;
+      }
+      vol_e += breaths[i].vol_e;
+
+      const inh_ms = transitions[breaths[i].trans_begin_inhale].ms;
+      // note i is counting down in this loop...
+      if (last_inhale_ms < 0) last_inhale_ms = inh_ms;
+      first_inhale_ms = inh_ms;
+      const exh_ms = transitions[breaths[i].trans_end_exhale].ms;
+      const zero_ms = transitions[breaths[i].trans_cross_zero].ms;
+      time_inh += (zero_ms - inh_ms);
+      time_exh += (exh_ms -  zero_ms);
+      i--;
+    }
+    if ((cnt > 1) && (first_inhale_ms != last_inhale_ms)) {
+      var inhalation_duration = last_inhale_ms - first_inhale_ms;
+      var inhalation_duration_min = inhalation_duration / (60.0 * 1000.0);
+      var rr = (cnt - 1) / inhalation_duration_min;
+      var duration_minutes = secs / 60.0;
+
+      // This is liters per minute. vol_ci is in liters.
+      // inhalation_duration is in ms.
+      var minute_volume =  vol_ci / inhalation_duration_min;
+
+      var tidal_volume = 1000.0 * vol_i / cnt;
+
+      var EIratio = (time_inh == 0) ? null : time_exh / time_inh;
+      return  [
+        rr,
+        tidal_volume,
+        minute_volume,
+        EIratio];
+    } else {
+      return [0,0,0,"NA"];
+    }
+  }
+      }
+
+var LIMITS = {
+  max: { h: 40,
+         l: 0},
+  avg: { h: 30,
+         l: 0},
+  min: { h: 10,
+         l: -10},
+  mv: { h: 10,
+         l: 1},
+  bpm: { h: 40,
+         l: 5},
+  ier: { h: 4,
+         l: 0.25},
+  tv: { h: 1000,
+         l: 200},
+  fio2: { h: 100,
+         l: 20},
+  taip: { h: 100, // These are in ms
+         l: 0},
+  trip: { h: 100, // These are in ms
+         l: 0},
+}
+
+function load_ui_with_defaults(limits) {
+  var Lkeys = Object.keys(limits);
+  Lkeys.forEach((key,index) => {
+    ["h","l"].forEach(limit => {
+      $("#"+key+"_"+limit).val(limits[key][limit]);
+    })
+  });
+}
+
+function set_rise_time_pressures()
+{
+  $("#tarip_l").val(TAIP_AND_TRIP_MIN);
+  $('#tarip_l').change(function () {
+    TAIP_AND_TRIP_MIN = $("#tarip_l").val();
+  });
+  $("#tarip_h").val(TAIP_AND_TRIP_MAX);
+  $('#tarip_h').change(function () {
+    TAIP_AND_TRIP_MAX = $("#tarip_h").val();
+  });
+}
+
+function reflectAlarmsInGUI(alarms) {
+  var Lkeys = Object.keys(LIMITS);
+  Lkeys.forEach((key,index) => {
+    ["h","l"].forEach(limit => {
+      var alarms_for_key = alarms.filter(s => s.param == key && s.limit == limit);
+      if (alarms_for_key.length > 0) {
+        $("#"+key).addClass("alarmred");
+        $("#"+key+"_"+limit).addClass("alarmred");
+      } else {
+        $("#"+key).removeClass("alarmred");
+        $("#"+key+"_"+limit).removeClass("alarmred");
+      }
+    })
+  });
+}
+
+function process(samples) {
+  const t = 200; // size of the window is 200ms
+  const v = 50; // min volume in ml
+  var [transitions,breaths] = computeMovingWindowTrace(samples,t,v);
+  plot(samples,transitions,breaths);
+  // How many seconds backwards should we look? Perhaps 20?
+  var [bpm,tv,mv,EIratio,fio2] = compute_respiration_rate(RESPIRATION_RATE_WINDOW_SECONDS,samples,transitions,breaths);
+
+  var alarms = [];
+
+  $("#bpm").text(bpm.toFixed(1));
+  $("#tv").text(tv.toFixed(0));
+  $("#mv").text((mv).toFixed(2));
+  if (EIratio == "NA") {
+    $("#ier").text("NA");
+  } else {
+    $("#ier").text((1.0 / EIratio).toFixed(1));
+  }
+
+  var final_ms = samples[samples.length -1].ms;
+  var b_ms = 0;
+  if (breaths.length > 0) {
+    b_ms = breaths[breaths.length -1].ms;
+  } else {
+    b_ms = final_ms;
+  }
+  function check_high_and_low(limits,key,v,ms) {
+    var al1 = check_alarms(limits,key,"h",v,(a,b) =>(a > b),ms);
+    var al2 = check_alarms(limits,key,"l",v,(a,b) =>(a < b),ms);
+
+    return al1.concat(al2);
+  }
+
+  alarms = alarms.concat(check_high_and_low(LIMITS,"bpm",bpm.toFixed(1),b_ms));
+  alarms = alarms.concat(check_high_and_low(LIMITS,"tv",tv.toFixed(0),b_ms));
+  alarms = alarms.concat(check_high_and_low(LIMITS,"mv",(mv).toFixed(2),b_ms));
+  alarms = alarms.concat(check_high_and_low(LIMITS,"ier",(1.0 / EIratio).toFixed(1),b_ms));
+
+  // These must be moved out; in fact,
+  // they must be made configurable!
+
+  var taip = compute_mean_TRIP_or_TAIP(
+    TAIP_AND_TRIP_MIN,
+    TAIP_AND_TRIP_MAX,
+    samples,
+    true);
+  if (taip != "NA") {
+    $("#taip").text(taip.toFixed(1));
+    alarms = alarms.concat(
+      check_high_and_low(LIMITS,"taip",taip,b_ms));
+  } else {
+    $("#taip").text("NA");
+  }
+
+  var trip = compute_mean_TRIP_or_TAIP(
+    TAIP_AND_TRIP_MIN,
+    TAIP_AND_TRIP_MAX,
+    samples,
+    false);
+  if (trip != "NA") {
+    $("#trip").text(trip.toFixed(1));
+    alarms = alarms.concat(
+      check_high_and_low(LIMITS,"trip",trip,b_ms));
+  } else {
+    $("#trip").text("NA");
+  }
+
+  var [min,avg,max,palarms] = compute_pressures(RESPIRATION_RATE_WINDOW_SECONDS,samples);
+  alarms = alarms.concat(palarms);
+
+  $("#min").text(min.toFixed(1));
+  $("#avg").text(avg.toFixed(1));
+  $("#max").text(max.toFixed(1));
+
+  var fio2 = compute_fio2_mean(RESPIRATION_RATE_WINDOW_SECONDS,samples);
+
+   if(fio2 == null){
+    $("#fio2").text("NA");
+   } else {
+    alarms = alarms.concat(check_high_and_low(LIMITS,"fio2",fio2.toFixed(1),b_ms));
+    $("#fio2").text(fio2.toFixed(1));
+   }
+
+  reflectAlarmsInGUI(alarms);
+}
+
+// WARNING: This is a hack...if the timestamp is negative,
+// we treat it as a limited (beyond range of sensor) measurement.
+// Our goal is to warn about this, but for now we will just
+// ignore and correct.
+function sanitize_samples(samples) {
+  samples.forEach(s =>
+                  {
+                    if (s.event == "M") {
+                      if ("string" == (typeof s.ms))
+                        s.ms = parseInt(s.ms);
+                      if ("string" == (typeof s.val))
+                        s.val = parseInt(s.val);
+                      if ("string" == (typeof s.num))
+                        s.num = parseInt(s.num);
+                      if (s.ms < 0) {
+                        s.ms = -s.ms;
+                      } else if (s.event == "E") {
+                      }
+                    }
+
+                  });
+  return samples;
+}
+
+function retrieveAndPlot(){
+  var trace_piece = (TRACE_ID) ? "/" + TRACE_ID : "";
+  DSERVER_URL = $("#dserverurl").val();
+  var url =  DSERVER_URL + trace_piece + "/json?n="+ NUM_TO_READ;
+
+  $.ajax({url: url,
+          success: function(cur_sam){
+
+              // WARNING: This is a hack...if the timestamp is negative,
+              // we treat it as a limited (beyond range of sensor) measurement.
+              // Our goal is to warn about this, but for now we will just
+              // ignore and correct.
+            if (cur_sam && cur_sam.length == 0) {
+              console.log("no samples; potential misconfiguration!");
+            } else {
+              cur_sam = sanitize_samples(cur_sam);
+	      if (INITS_ONLY) {
+	        samples = cur_sam;
+	        INITS_ONLY = false;
+	      } else {
+                var discard = Math.max(0,
+                                       samples.length + cur_sam.length - MAX_SAMPLES_TO_STORE_S);
+	        samples = samples.slice(discard);
+	      }
+
+
+	      samples = samples.concat(cur_sam);
+	      // We are not guaranteeed to get samples in order
+	      // we sort them....
+	      samples = samples.sort((a,b) => a.ms - b.ms);
+              process(samples);
+            }
+          },
+          error: function(xhr, ajaxOptions, thrownError) {
+	    console.log("Error!" + xhr.status);
+	    console.log(thrownError);
+            //            clearInterval(intervalID);
+            stop_interval_timer();
+            $("#livetoggle").prop("checked",false);
+          }
+         });
+}
+
+
+function compute_transitions(vm,flows) {
+  var transitions = [];
+  var state = 0; // Let 1 mean inspiration, -1 mean expiration, 0 neither
+  for(var i = 0; i < flows.length; i++) {
+    var f = flows[i].val * CONVERT_PIRDS_TO_SLM;
+    //    var ms = flows[i].ms-first_time;
+    var ms = flows[i].ms;
+    if (state == 0) {
+      if (f > vm) {
+	state = 1;
+	transitions.push({ state: 1, sample: i, ms: ms})
+      } else if (f < -vm) {
+	state = -1;
+	transitions.push({ state: -1, sample: i, ms: ms})
+      }
+    } else if (state == 1) {
+      if (f < -vm) {
+	state = -1;
+	transitions.push({ state: -1, sample: i, ms: ms})
+      } else if (f < vm) {
+	state = 0;
+	transitions.push({ state: 0, sample: i, ms: ms})
+      }
+    } else if (state == -1) {
+      if (f > vm) {
+	state = 1;
+	transitions.push({ state: 1, sample: i, ms: ms})
+      } else if (f > 0) {
+	state = 0;
+	transitions.push({ state: 0, sample: i, ms: ms})
+      }
+    }
+  }
+  return transitions;
+}
+
+// produces a set of rising signals, time in ms of the leading edge of the rise and the trailing edge of the rise
+// an array of 2-tuple
+// taip == true implies compute TAIP, else compute TRIP
+// Possibly this routine should be generalized to a general rise-time routine.
+function compute_TAIP_or_TRIP_signals(min,max,pressures,taip) {
+  var pressures = pressures.filter(s => s.event == 'M' && s.type == 'D' && s.loc == 'A');
+  const responseBegin = 0.1;
+  const responseEnd = 0.9;
+
+  var signals = [];
+  var foundMinSignal = false;
+
+  const highFence = (min + (responseEnd * (max - min)))*10;
+  const lowFence = (min + (responseBegin * (max - min)))*10;
+
+  var cur_signal_start;
+  var state = -1; // Let 1 mean rising, -1 mean fallen, 0 risen, but not fallen
+  var first_sample_index = taip ? 0 : pressures.length-1 ;
+  var last_sample_index = taip ? pressures.length-1 : 0;
+  var increment = taip ? 1 : -1;
+  for(var i = first_sample_index; i != last_sample_index; i+=increment) {
+    var p = pressures[i].val;
+    var ms = pressures[i].ms;
+    if (state == -1) {
+      if (p >= lowFence) {
+	      state = 1;
+        cur_signal_start = pressures[i];
+      }
+      if (p >= highFence){
+        signals.push([ms,ms]);
+      }
+    } else if (state == 1) {
+      //console.log("state = 1",cur_signal_start); for debugging
+      if (p >= highFence) {
+        signals.push(taip ? [cur_signal_start.ms,ms] : [ms,cur_signal_start.ms])
+        state = 0;
+      } else if (p <= lowFence) {
+        state = -1;
+        cur_signal_start = null;
+      }
+    } else if (state == 0) {
+      if (p <= lowFence) {
+	state = -1;
+      }
+    }
+  }
+  return signals;
+}
+
+function compute_mean_TRIP_or_TAIP_sigs(sigs,min,max,pressures,taip){
+  if (sigs.length == 0){
+    return "NA";
+  } else {
+    var sum = 0;
+    for(var i = 0; i < sigs.length; i++) {
+      sum += sigs[i][1] - sigs[i][0];  // time in ms
+    }
+    return sum / sigs.length;
+  }
+}
+
+function testdata(){
+  //0 (not good enough), 10 (rising), 20 (above threshold) all 10 ms apart
+  //saw tooth function
+  var data = []; // pushing 50 things into it
+  for(var i = 0; i < 10; i++) {
+    var ms = i*5*10;
+    data[i*5+0] = {event:'M',loc:'A',ms:ms + 0,type:'D',val: 0};
+    data[i*5+1] = {event:'M',loc:'A',ms:ms + 10,type:'D',val: 100};
+    data[i*5+2] = {event:'M',loc:'A',ms:ms + 20,type:'D',val: 200};
+    data[i*5+3] = {event:'M',loc:'A',ms:ms + 30,type:'D',val: 100};
+    data[i*5+4] = {event:'M',loc:'A',ms:ms + 40,type:'D',val: 0};
+  }
+  return data;
+}
+
+function testdataSine(period_sm){ // period expressed in # of samples, each sample 10 ms
+  //0 (not good enough), 10 (rising), 20 (above threshold) all 10 ms apart
+  //sine tooth function
+  var data = []; // pushing 50 things into it
+  for(var i = 0; i < 1000; i++) {
+    var ms = i*10;
+    data[i] = {event:'M',loc:'A',ms:ms + 20,type:'D',val: 200*Math.sin(2*Math.PI*i/period_sm)};
+  }
+  return data;
+}
+
+function compute_mean_TRIP_or_TAIP(min,max,samples,taip) {
+  return compute_mean_TRIP_or_TAIP_sigs(
+    compute_TAIP_or_TRIP_signals(min,max,samples,taip),
+    min,max,samples,taip);
+}
+
+function test_compute_TAIP() {
+  var samples = testdata();
+  const TAIP_min = 0; // cm of H2O
+  const TAIP_max = 20; // cm of H2O
+  var TAIP_m = compute_mean_TRIP_or_TAIP(min,max,samples,true);
+  console.assert(TAIP_m == 10);
+  for (i = 50; i<150; i+=10) {
+    var sinewave = testdataSine(i);
+    var TAIP_m = compute_mean_TRIP_or_TAIP(min,max,sinewave,true);
+  }
+}
+
+function compute_current_TAIP(TAIP_min,TAIP_max){ //uses samples from a global var
+  var TAIP_m = compute_mean_TRIP_or_TAIP(TAIP_min,TAIP_max,samples,true);
+  return TAIP_m;
+}
+
+// Because TAIP and TRIP are symmetric when viewed from
+// from the direction of the samples; this tests that
+// as a prelude to computing a single way.
+
+function reverseArray(arr) {
+  var newArray = [];
+  for (var i = arr.length - 1; i >= 0; i--) {
+    newArray.push(arr[i]);
+  }
+  return newArray;
+}
+function test_TRIP_and_TAIP_are_symmetric() {
+  var samples = testdata();
+  var rsamples = reverseArray(samples);
+  const min = 0;
+  const max = 20;
+  var TRIP_m = compute_mean_TRIP_or_TAIP(min,max,samples,false);
+  var TRIP_m_r = -compute_mean_TRIP_or_TAIP(min,max,rsamples,true);
+  console.assert(TRIP_m == TRIP_m_r);
+  console.assert(TRIP_m == 10);
+  for (i = 50; i<150; i+=10) {
+    var sinewave = testdataSine(i);
+    var rsinewave = reverseArray(sinewave);
+    var TRIP_m = compute_mean_TRIP_or_TAIP(min,max,samples,false);
+    var TRIP_m_r = -compute_mean_TRIP_or_TAIP(min,max,rsamples,true);
+    console.assert(TRIP_m == TRIP_m_r);
+    var TAIP_m = compute_mean_TRIP_or_TAIP(min,max,samples,true);
+    var TAIP_m_r = -compute_mean_TRIP_or_TAIP(min,max,rsamples,false);
+    console.assert(TAIP_m == TAIP_m_r);
+  }
+}
+
+function compute_current_TRIP(TRIP_min,TRIP_max, samples)
+{ //uses samples from a global var
+  if (samples.length == 0){
+    return "NA";
+  }
+  else {
+    var TRIP_m = compute_mean_TAIP_or_TRIP(TRIP_min,TRIP_max,false);
+    return TRIP_m;
+  }
+}
+// A simple computation of a moving window trace
+// computing [A + -B], where A is volume to left
+// of sample int time window t, and B is volume to right
+// t is in milliseconds
+function computeMovingWindowTrace(samples,t,v) {
+
+  var flows = samples.filter(s => s.event == 'M' && s.type == 'F');
+  var first_time = flows[0].ms;
+  var last_time = flows[flows.length - 1].ms;
+  var duration = last_time - first_time;
+
+  // Here is an idea...
+  // We define you to be in one of three states:
+  // Inspiring, expiring, or neither.
+  // Every transition between these states is logged.
+  // Having two inspirations between an expiration is
+  // weird but could happen.
+  // We record transitions.
+  // When the time series crossed a fixed threshold
+  // or zero, it causes a transition. If you are inspiring,
+  // you have to cross zero to transition to neither,
+  // and you start expiring when you cross the treshold.
+
+  // This is measured in standard liters per minute.
+  const vm = 10; // previously used 4
+
+  // We will model this as a list of transitions.
+  // A breath is any number of inspirations followed by
+  // any number of expirations. (I+)(E+)
+
+  var transitions = compute_transitions(vm,flows);
+
+  // Now that we have transitions, we can apply a
+  // diferrent algorithm to try to define "breaths".
+  // Because a breath is defined as an inspiration
+  // and then an expiration, we will define a breath
+  // as from the first inspiration, until there has
+  // been one expiration, until the next inspiration.
+  var breaths = [];
+  var expiring = false;
+
+  // This should be in liters...
+  function integrateSamples(a,z) {
+    // -1 for quadilateral approximation
+    var vol = 0;
+    for(var j = a; j < z-1; j++) {
+      // I'll use qadrilateral approximation.
+      // We'll form each quadrilateral between two samples.
+      var ms = flows[j+1].ms - flows[j].ms;
+      var ht = ((flows[j+1].val + flows[j].val )/2) * CONVERT_PIRDS_TO_SLM;
+      // Flow is actually in standard liters per minute,
+      // so to get liters we divide by 60 to it l/s,
+      // and and divde by 1000 to convert ms to seconds.
+      // We could do that here, but will move constants
+      // to end...
+      vol += ms * ht;
+      if (isNaN(vol)) {
+        debugger;
+      }
+    }
+    return vol/(60*1000);
+  }
+
+  function compute_breaths_based_on_exhalations(transitions) {
+    var beg = 0;
+    var zero = 0;
+    var last = 0;
+    var voli = 0;
+    var vole = 0;
+
+    for(var i = 0; i < transitions.length; i++) {
+      // We're looking for the end of the inhalation here!!
+      if (((i -1) >= 0) && transitions[i-1].state == 1 && (transitions[i].state == 0 || transitions[i].state == -1 )) {
+        zero = i;
+      }
+      if (expiring && transitions[i].state == 1) {
+        breaths.push({ ms: transitions[i].ms,
+		       sample: transitions[i].sample,
+		       vol_e: vole,
+		       vol_i: voli,
+                       trans_begin_inhale: beg,
+                       trans_cross_zero: zero,
+                       trans_end_exhale: i,
+		     }
+		    );
+        beg = i;
+        expiring = false;
+        vole = integrateSamples(last,transitions[i].sample);
+        last = transitions[i].sample;
+      }
+      if (!expiring && transitions[i].state == -1) {
+        expiring = true;
+        voli = integrateSamples(last,transitions[i].sample);
+        last = transitions[i].sample;
+      }
+    }
+  }
+  // This is based only on inhalations, and
+  // is therefore functional when there is a check valve
+  // in place. Such a system will rarely
+  // have negative flows, and we must mark
+  // the beginning of a breath from a transition to a "1"
+  // state from any other state.
+  // This algorithm is simple: A breath begins on a trasition
+  // to 1 from a not 1 state. This algorithm is susceptible
+  // to "stutter" near the boundary point, but if necessary
+  // a digital filter would sove that; we have not yet found
+  // that level of sophistication needed.
+  // We still want to track zeros, but now must strack them
+  // as a falling signal.
+  function compute_breaths_based_without_negative_flow(transitions) {
+    var beg = 0;
+    var zero = 0;
+    var last = 0;
+    var voli = 0;
+    var vole = 0;
+
+    var breaths = [];
+    var expiring = true;
+
+    for(var i = 0; i < transitions.length; i++) {
+      // We're looking for the end of the inhalation here!!
+      if (((i -1) >= 0) && transitions[i-1].state == 1 &&
+          (transitions[i].state == 0 || transitions[i].state == -1 )) {
+        zero = i;
+      }
+      if (expiring && transitions[i].state == 1) {
+        breaths.push({ ms: transitions[i].ms,
+		       sample: transitions[i].sample,
+		       vol_e: vole,
+		       vol_i: voli,
+                       trans_begin_inhale: beg,
+                       trans_cross_zero: zero,
+                       trans_end_exhale: i,
+		     }
+		    );
+        beg = i;
+        expiring = false;
+        vole = integrateSamples(last,transitions[i].sample);
+
+        last = transitions[i].sample;
+      }
+      if (!expiring && ((transitions[i].state == -1) || (transitions[i].state == 0)))  {
+        expiring = true;
+        voli = integrateSamples(last,transitions[i].sample);
+        last = transitions[i].sample;
+      }
+    }
+    return breaths;
+  }
+
+  breaths = compute_breaths_based_without_negative_flow(transitions);
+
+  return [transitions,breaths];
+}
+
+
+$("#useofficial").click(function() {
+    DSERVER_URL = VENTMON_DATA_LAKE;
+    $("#dserverurl").val(DSERVER_URL);
+});
+
+$("#import").click(function() {
+    var input_trace = $("#json_trace").val();
+    samples = JSON.parse(input_trace);
+});
+
+$("#export").click(function() {
+    $("#json_trace").val(JSON.stringify(samples,null,2));
+});
+
+
+  // Experimental timing against the data server
+
+function stop_interval_timer() {
+  clearInterval(intervalID);
+  intervalID = null;
+  $("#livetoggle").prop("checked",false);
+}
+function start_interval_timer() {
+  if (intervalID) {
+    stop_interval_timer();
+  }
+  intervalID = setInterval(
+    function() {
+      retrieveAndPlot();
+    },
+    DATA_RETRIEVAL_PERIOD);
+  $("#livetoggle").prop("checked",true);
+}
+function toggle_interval_timer() {
+  if (intervalID) {
+    stop_interval_timer();
+  } else {
+    start_interval_timer();
+  }
+}
+
+function setLimit(ed) {
+  if(ed.which == 13) {
+    var id = ed.target.id;
+    var v = $("#"+id).val();
+    var vf = parseFloat(v);
+    var destruct = id.split("_");
+    LIMITS[destruct[0]][destruct[1]] = isNaN(vf) ? null : vf;
+  }
+}
+
+$(".fence").keypress(setLimit);
+
+$("#livetoggle").change(toggle_interval_timer);
+
+$("#startoperation").click(start_interval_timer);
+
+$("#stopoperation").click(stop_interval_timer);
+
+$( document ).ready(function() {
+
+  if (window.location.protocol == "http:")
+    DSERVER_URL = window.location.protocol + "//" + window.location.host;
+  else
+    DSERVER_URL = "http://localhost";
+
+
+  $( "#dserverurl" ).val( DSERVER_URL );
+  $('#dserverurl').change(function () {
+    DSERVER_URL = $("#dserverurl").val();
+    start_interval_timer();
+  });
+
+  // $( "#num_to_read" ).val( NUM_TO_READ );
+  // $('#num_to_read').change(function () {
+  //   NUM_TO_READ = $("#num_to_read").val();
+  // });
+
+  $( "#traceid" ).val( TRACE_ID );
+  $('#traceid').change(function () {
+    TRACE_ID = $("#traceid").val();
+    start_interval_timer();
+  });
+
+
+  $( "#samples_to_plot" ).val( MAX_SAMPLES_TO_STORE_S );
+  $('#samples_to_plot').change(function () {
+    MAX_SAMPLES_TO_STORE_S = $("#samples_to_plot").val();
+  });
+
+  samples = init_samples();
+  process(samples);
+  start_interval_timer();
+
+  load_ui_with_defaults(LIMITS);
+
+  set_rise_time_pressures(
+    TAIP_AND_TRIP_MIN,
+    TAIP_AND_TRIP_MAX,
+  );
+
+});
+
+
+</script>