From c50679708e8ec9a7541f82ea8edda101810a45e1 Mon Sep 17 00:00:00 2001
From: fuchsi <fuchsi@hemio.de>
Date: Sat, 5 Oct 2019 22:10:02 +0200
Subject: [PATCH] settings menu for the ecg app

---
 preload/apps/ecg/__init__.py | 199 +++++++++++------------------------
 preload/apps/ecg/settings.py |  90 ++++++++++++++++
 2 files changed, 154 insertions(+), 135 deletions(-)
 create mode 100644 preload/apps/ecg/settings.py

diff --git a/preload/apps/ecg/__init__.py b/preload/apps/ecg/__init__.py
index da30babc..bd589238 100644
--- a/preload/apps/ecg/__init__.py
+++ b/preload/apps/ecg/__init__.py
@@ -7,12 +7,13 @@ import max30001
 import math
 import struct
 import itertools
+from ecg.settings import *
 
+config = ecg_settings()
 WIDTH = 160
 HEIGHT = 80
 OFFSET_Y = 49
-ECG_RATE = 128
-HISTORY_MAX = ECG_RATE * 4
+HISTORY_MAX = WIDTH * 4
 DRAW_AFTER_SAMPLES = 5
 SCALE_FACTOR = 30
 MODE_USB = "USB"
@@ -26,20 +27,9 @@ COLOR_MODE_USB = [0, 0, 255]
 COLOR_WRITE_FG = [255, 255, 255]
 COLOR_WRITE_BG = [255, 0, 0]
 
-current_mode = MODE_FINGER
-modes = itertools.cycle(
-    [
-        ({"bar", "pulse"}, {"text": "Top + Pulse", "posx": 0}),
-        ({}, {"text": "off", "posx": 55}),
-        ({"bar"}, {"text": "Top Only", "posx": 25}),
-        ({"pulse"}, {"text": "Pulse Only", "posx": 5}),
-    ]
-)
-led_mode = next(modes)[0]
 history = []
 filebuffer = bytearray()
 write = 0
-bias = True
 update_screen = 0
 pause_screen = 0
 pause_histogram = False
@@ -64,7 +54,7 @@ def update_history(datasets):
     global history, moving_average, alpha, beta, last_sample_count
     last_sample_count = len(datasets)
     for val in datasets:
-        if current_mode == MODE_FINGER:
+        if "HP" in config.get_option("Filter"):
             history.append(val - moving_average)
             moving_average += betadash * (val - moving_average)
             # identical to: moving_average = (alpha * moving_average + beta * val) / (alpha + beta)
@@ -81,7 +71,7 @@ samples_since_last_pulse = 0
 last_pulse_blink = 0
 q_threshold = -1
 r_threshold = 1
-q_spike = -ECG_RATE
+q_spike = -500 #just needs to be long ago
 
 
 def neighbours(n, lst):
@@ -103,6 +93,8 @@ def detect_pulse(num_new_samples):
     # consider ["CDE", "DEF"]
     # new samples: "GHI" => "ABCDEFGHI"
     # consider ["EFG", "FGH", "GHI"]
+    ecg_rate = config.get_option("Rate")
+
     for [prev, cur, next_] in neighbours(3, history[-(num_new_samples + 2) :]):
         samples_since_last_pulse += 1
 
@@ -113,17 +105,17 @@ def detect_pulse(num_new_samples):
         elif (
             prev < cur > next_
             and cur > r_threshold
-            and samples_since_last_pulse - q_spike < ECG_RATE // 10
+            and samples_since_last_pulse - q_spike < ecg_rate // 10
         ):
-            # the full QRS complex is < 0.1s long, so the q and r spike in particular cannot be more than ECG_RATE//10 samples apart
-            pulse = 60 * ECG_RATE // samples_since_last_pulse
+            # the full QRS complex is < 0.1s long, so the q and r spike in particular cannot be more than ecg_rate//10 samples apart
+            pulse = 60 * ecg_rate // samples_since_last_pulse
             samples_since_last_pulse = 0
-            q_spike = -ECG_RATE
+            q_spike = -ecg_rate
             if pulse < 30 or pulse > 210:
                 pulse = -1
             # we expect the next r-spike to be at least 60% as high as this one
             r_threshold = (cur * 3) // 5
-        elif samples_since_last_pulse > 2 * ECG_RATE:
+        elif samples_since_last_pulse > 2 * ecg_rate:
             q_threshold = -1
             r_threshold = 1
             pulse = -1
@@ -183,9 +175,9 @@ def write_filebuffer():
 def open_sensor():
     global sensor
     sensor = max30001.MAX30001(
-        usb=(current_mode == MODE_USB),
-        bias=bias,
-        sample_rate=ECG_RATE,
+        usb=(config.get_option("Mode") == "USB"),
+        bias=config.get_option("Bias"),
+        sample_rate=config.get_option("Rate"),
         callback=callback_ecg,
     )
 
@@ -195,34 +187,6 @@ def close_sensor():
     sensor.close()
 
 
-def toggle_mode():
-    global current_mode, disp, pause_screen
-    if write > 0:
-        pause_screen = utime.time_ms() + 500
-        disp.clear(COLOR_BACKGROUND)
-        disp.print("Locked", posx=30, posy=30, fg=COLOR_TEXT)
-        disp.update()
-        return
-
-    close_sensor()
-    current_mode = MODE_USB if current_mode == MODE_FINGER else MODE_FINGER
-    open_sensor()
-
-
-def toggle_bias():
-    global bias, disp, pause_screen
-    if write > 0:
-        pause_screen = utime.time_ms() + 500
-        disp.clear(COLOR_BACKGROUND)
-        disp.print("Locked", posx=30, posy=30, fg=COLOR_TEXT)
-        disp.update()
-        return
-
-    close_sensor()
-    bias = not bias
-    open_sensor()
-
-
 def toggle_write():
     global write, disp, pause_screen
     pause_screen = utime.time_ms() + 1000
@@ -252,23 +216,14 @@ def toggle_pause():
     leds.clear()
 
 
-def toggle_leds():
-    global led_mode, disp, pause_screen, leds, modes
-    led_mode, display_args = next(modes)
-
-    pause_screen = utime.time_ms() + 250
-    disp.clear(COLOR_BACKGROUND)
-    disp.print("LEDs", posx=50, posy=20, fg=COLOR_TEXT)
-    disp.print(**display_args, posy=40, fg=COLOR_TEXT)
-    disp.update()
-    leds.clear()
-
 
 def draw_leds(vmin, vmax):
     # vmin should be in [0, -1]
     # vmax should be in [0, 1]
     global pulse, samples_since_last_pulse, last_pulse_blink
 
+    led_mode = config.get_option("LEDs")
+
     # stop blinking
     if not bool(led_mode):
         return
@@ -301,7 +256,7 @@ def draw_leds(vmin, vmax):
 
 
 def draw_histogram():
-    global disp, history, current_mode, bias, write, pause_screen, update_screen
+    global disp, history, write, pause_screen, update_screen
 
     # skip rendering due to message beeing shown
     if pause_screen == -1:
@@ -316,10 +271,10 @@ def draw_histogram():
     disp.clear(COLOR_BACKGROUND)
 
     # offset in pause_histogram mode
+    timeWindow = config.get_option("Window")
     window_end = int(len(history) - histogram_offset)
-    s_start = max(0, window_end - (ECG_RATE * 2))
     s_end = max(0, window_end)
-    s_draw = max(0, s_end - WIDTH)
+    s_start = max(0, s_end - WIDTH*timeWindow)
 
     # get max value and calc scale
     value_max = max(abs(x) for x in history[s_start:s_end])
@@ -327,11 +282,11 @@ def draw_histogram():
 
     # draw histogram
     # values need to be inverted so high values are drawn with low pixel coordinates (at the top of the screen)
-    draw_points = (int(-x * scale + OFFSET_Y) for x in history[s_draw:s_end])
+    draw_points = (int(-x * scale + OFFSET_Y) for x in history[s_start:s_end])
 
     prev = next(draw_points)
     for x, value in enumerate(draw_points):
-        disp.line(x, prev, x + 1, value, col=COLOR_LINE)
+        disp.line(x//timeWindow, prev, (x + 1)//timeWindow, value, col=COLOR_LINE)
         prev = value
 
     # draw text: mode/bias/write
@@ -339,7 +294,7 @@ def draw_histogram():
         disp.print(
             "Pause"
             + (
-                " -{:0.1f}s".format(histogram_offset / ECG_RATE)
+                " -{:0.1f}s".format(histogram_offset / config.get_option("Rate"))
                 if histogram_offset > 0
                 else ""
             ),
@@ -354,11 +309,11 @@ def draw_histogram():
         )
         if pulse < 0:
             disp.print(
-                current_mode + ("+Bias" if bias else ""),
+                config.get_option("Mode") + ("+Bias" if config.get_option("Bias") else ""),
                 posx=0,
                 posy=0,
                 fg=(
-                    COLOR_MODE_FINGER if current_mode == MODE_FINGER else COLOR_MODE_USB
+                    COLOR_MODE_FINGER if config.get_option("Mode") == MODE_FINGER else COLOR_MODE_USB
                 ),
             )
         else:
@@ -367,7 +322,7 @@ def draw_histogram():
                 posx=0,
                 posy=0,
                 fg=(
-                    COLOR_MODE_FINGER if current_mode == MODE_FINGER else COLOR_MODE_USB
+                    COLOR_MODE_FINGER if config.get_option("Mode") == MODE_FINGER else COLOR_MODE_USB
                 ),
             )
 
@@ -382,7 +337,7 @@ def draw_histogram():
 
 
 def main():
-    global pause_histogram, histogram_offset
+    global pause_histogram, histogram_offset, pause_screen
 
     # show button layout
     disp.clear(COLOR_BACKGROUND)
@@ -392,10 +347,10 @@ def main():
         "       Pause >", posx=0, posy=28, fg=COLOR_MODE_FINGER, font=display.FONT16
     )
     disp.print(
-        "   Mode/Bias >", posx=0, posy=44, fg=COLOR_MODE_USB, font=display.FONT16
+        "    Settings >", posx=0, posy=44, fg=COLOR_MODE_USB, font=display.FONT16
     )
     disp.print(
-        "< LED/WriteLog", posx=0, posy=64, fg=COLOR_WRITE_BG, font=display.FONT16
+        "< WriteLog    ", posx=0, posy=64, fg=COLOR_WRITE_BG, font=display.FONT16
     )
     disp.update()
     utime.sleep(3)
@@ -410,90 +365,64 @@ def main():
             )
 
             # TOP RIGHT
+            #
+            # pause
 
             # down
             if button_pressed["TOP_RIGHT"] == 0 and v & buttons.TOP_RIGHT != 0:
-                button_pressed["TOP_RIGHT"] = utime.time_ms()
+                button_pressed["TOP_RIGHT"] = 1
                 toggle_pause()
+                
 
             # up
             if button_pressed["TOP_RIGHT"] > 0 and v & buttons.TOP_RIGHT == 0:
-                duration = utime.time_ms() - button_pressed["TOP_RIGHT"]
                 button_pressed["TOP_RIGHT"] = 0
 
             # BOTTOM LEFT
             #
             # on pause = shift view left
-            # long = toggle write
-            # short = toggle leds
-
-            # down, and still pressed
-            if (
-                button_pressed["BOTTOM_LEFT"] > 0
-                and v & buttons.BOTTOM_LEFT != 0
-                and not pause_histogram
-            ):
-                duration = utime.time_ms() - button_pressed["BOTTOM_LEFT"]
-                if duration > 1000:
-                    button_pressed["BOTTOM_LEFT"] = -1
-                    toggle_write()
-
-            # register down event
-            elif button_pressed["BOTTOM_LEFT"] == 0 and v & buttons.BOTTOM_LEFT != 0:
-                button_pressed["BOTTOM_LEFT"] = utime.time_ms()
+            # else = toggle write
 
-            # register up event but event already called
-            if button_pressed["BOTTOM_LEFT"] == -1 and v & buttons.BOTTOM_LEFT == 0:
-                button_pressed["BOTTOM_LEFT"] = 0
-
-            # register normal up event
-            elif button_pressed["BOTTOM_LEFT"] > 0 and v & buttons.BOTTOM_LEFT == 0:
-                duration = utime.time_ms() - button_pressed["BOTTOM_LEFT"]
-                button_pressed["BOTTOM_LEFT"] = 0
-                if not pause_histogram:
-                    toggle_leds()
-                else:
+            # down
+            if button_pressed["BOTTOM_LEFT"] == 0 and v & buttons.BOTTOM_LEFT != 0:
+                button_pressed["BOTTOM_LEFT"] = 1
+                if pause_histogram:
                     l = len(history)
-                    histogram_offset += ECG_RATE / 2
-                    if l - histogram_offset < WIDTH:
-                        histogram_offset = l - WIDTH
+                    histogram_offset += config.get_option("Rate") / 2
+                    if l - histogram_offset < WIDTH*config.get_option("Window"):
+                        histogram_offset = l - WIDTH*config.get_option("Window")
+                else:
+                    toggle_write()
+                
+            # up
+            if button_pressed["BOTTOM_LEFT"] > 0 and v & buttons.BOTTOM_LEFT == 0:
+                button_pressed["BOTTOM_LEFT"] = 0
 
             # BOTTOM RIGHT
             #
             # on pause = shift view right
-            # long = toggle bias
-            # short = toggle mode (finger/usb)
-
-            # down, and still pressed
-            if (
-                button_pressed["BOTTOM_RIGHT"] > 0
-                and v & buttons.BOTTOM_RIGHT != 0
-                and not pause_histogram
-            ):
-                duration = utime.time_ms() - button_pressed["BOTTOM_RIGHT"]
-                if duration > 1000:
-                    button_pressed["BOTTOM_RIGHT"] = -1
-                    toggle_bias()
-
-            # register down event
-            elif button_pressed["BOTTOM_RIGHT"] == 0 and v & buttons.BOTTOM_RIGHT != 0:
-                button_pressed["BOTTOM_RIGHT"] = utime.time_ms()
-
-            # register up event but event already called
-            if button_pressed["BOTTOM_RIGHT"] == -1 and v & buttons.BOTTOM_RIGHT == 0:
-                button_pressed["BOTTOM_RIGHT"] = 0
+            # else = show settings
 
-            # register normal up event
-            elif button_pressed["BOTTOM_RIGHT"] > 0 and v & buttons.BOTTOM_RIGHT == 0:
-                duration = utime.time_ms() - button_pressed["BOTTOM_RIGHT"]
-                button_pressed["BOTTOM_RIGHT"] = 0
+            # down
+            if button_pressed["BOTTOM_RIGHT"] == 0 and v & buttons.BOTTOM_RIGHT != 0:
+                button_pressed["BOTTOM_RIGHT"] = 1
                 if pause_histogram:
-                    histogram_offset -= ECG_RATE / 2
-                    histogram_offset -= histogram_offset % (ECG_RATE / 2)
+                    histogram_offset -= config.get_option("Rate") / 2
+                    histogram_offset -= histogram_offset % (config.get_option("Rate") / 2)
                     if histogram_offset < 0:
                         histogram_offset = 0
                 else:
-                    toggle_mode()
+                    pause_screen = -1 # hide graph
+                    leds.clear() # disable all LEDs
+                    config.run() # show config menu
+                    close_sensor() # reset sensor in case mode or bias was changed TODO do not close sensor otherwise?
+                    open_sensor()
+                    pause_screen = 0 # start plotting graph again
+                    button_pressed["TOP_RIGHT"] = 1 # returning from menu was by pressing the TOP_RIGHT button
+                
+            # up
+            if button_pressed["BOTTOM_RIGHT"] > 0 and v & buttons.BOTTOM_RIGHT == 0:
+                button_pressed["BOTTOM_RIGHT"] = 0
 
 
 if __name__ == "__main__":
diff --git a/preload/apps/ecg/settings.py b/preload/apps/ecg/settings.py
new file mode 100644
index 00000000..e6da4e4b
--- /dev/null
+++ b/preload/apps/ecg/settings.py
@@ -0,0 +1,90 @@
+import color
+import simple_menu
+import itertools
+
+
+class Settings(simple_menu.Menu):
+    color_1 = color.CAMPGREEN
+    color_2 = color.CAMPGREEN_DARK
+
+    options = {}
+
+    def __init__(self):
+        super().__init__([("return",False)])
+
+    def on_select(self, value, index):
+        if index == 0:
+            self.exit()
+        else:
+            self.options[value[0]] = next(value[1])
+
+    def entry2name(self, value):
+        print(value, value[0])
+        if value[0]=="return":
+            return value[0]
+        else:
+            return "{}: {}".format(value[0], self.options[value[0]][0])
+    
+    def add_option(self, option):
+        self.entries.append(option)
+        self.options[option[0]] = next(option[1])
+
+    def get_option(self, name):
+        return self.options[name][1]
+
+
+def ecg_settings():
+    config = Settings()
+    config.add_option(("LEDs", 
+            itertools.cycle(
+                [
+                    ("off", {}),
+                    ("Pulse", {"pulse"}),
+                    ("Bar", {"bar"}),
+                    ("Full", {"pulse", "bar"}),
+                ]
+            )
+    ))
+    config.add_option(("Mode", 
+            itertools.cycle(
+                [
+                    ("Finger", "Finger"),
+                    ("USB", "USB")
+                ]
+            )
+    ))
+    config.add_option(("Bias", 
+            itertools.cycle(
+                [
+                    ("on", True),
+                    ("off", False)
+                ]
+            )
+    ))
+    config.add_option(("Filter", 
+            itertools.cycle(
+                [
+                    ("HP", {"HP"}),
+                    ("off", {})
+                ]
+            )
+    ))
+    config.add_option(("Rate", 
+            itertools.cycle(
+                [
+                    ("128Hz", 128),
+                    ("256Hz", 256)
+                ]
+            )
+    ))
+    config.add_option(("Window", 
+            itertools.cycle(
+                [
+                    ("1x", 1),
+                    ("2x", 2),
+                    ("3x", 3)
+                ]
+            )
+    ))
+
+    return config
-- 
GitLab