From e2e8f5f5a95ff25f0a25299a758c53cef5ec40fb Mon Sep 17 00:00:00 2001
From: Daniel Hoffend <dh@dotlan.net>
Date: Fri, 23 Aug 2019 19:45:22 +0200
Subject: [PATCH] feat(ecg) add ecg application

---
 preload/apps/ecg/__init__.py   | 242 +++++++++++++++++++++++++++++++++
 preload/apps/ecg/metadata.json |   1 +
 2 files changed, 243 insertions(+)
 create mode 100644 preload/apps/ecg/__init__.py
 create mode 100644 preload/apps/ecg/metadata.json

diff --git a/preload/apps/ecg/__init__.py b/preload/apps/ecg/__init__.py
new file mode 100644
index 000000000..ccc38843d
--- /dev/null
+++ b/preload/apps/ecg/__init__.py
@@ -0,0 +1,242 @@
+import os
+import display
+import utime
+import buttons
+import max30001
+import math
+import struct
+
+WIDTH = 160
+HEIGHT = 80
+OFFSET = 50
+ECG_RATE = 128
+HISTORY_MAX = ECG_RATE * 2
+DRAW_AFTER_SAMPLES = 5
+SCALE_FACTOR = 30
+MODE_USB = "USB"
+MODE_FINGER = "Finger"
+FILEBUFFERBLOCK = 4096
+COLOR_BACKGROUND = [0, 0, 0]
+COLOR_LINE = [255, 255, 255]
+COLOR_TEXT = [255, 255, 255]
+COLOR_MODE_FINGER = [0, 255, 0]
+COLOR_MODE_USB = [0, 0, 255]
+COLOR_WRITE_FG = [255, 255, 255]
+COLOR_WRITE_BG = [255, 0, 0]
+
+current_mode = MODE_FINGER
+history = []
+filebuffer = bytearray()
+write = 0
+bias = True
+update_screen = 0
+pause_screen = 0
+sensor = 0
+disp = display.open()
+
+
+def callback_ecg(datasets):
+    global update_screen, history, filebuffer, write
+    if len(datasets) > 0:
+        for value in datasets:
+            history.append(value)
+        update_screen += len(datasets)
+    if len(history) > HISTORY_MAX:
+        r = len(history) - HISTORY_MAX
+        for i in range(r):
+            history.pop(0)
+
+    # buffer for writes
+    if write > 0:
+        if len(datasets) > 0:
+            for value in datasets:
+                filebuffer.extend(struct.pack("h", value))
+                if len(filebuffer) >= FILEBUFFERBLOCK:
+                    write_filebuffer()
+
+    # don't update on every callback
+    if update_screen >= DRAW_AFTER_SAMPLES:
+        # print("history: %i, filebuffer: %i" % (len(history), len(filebuffer)))
+        draw_histogram()
+        update_screen = 0
+
+
+def write_filebuffer():
+    global write, filebuffer
+    # write to file
+    chars = ""
+    lt = utime.localtime(write)
+    filename = "/ecg-%04d-%02d-%02d_%02d%02d%02d.log" % (
+        lt[0],
+        lt[1],
+        lt[2],
+        lt[3],
+        lt[4],
+        lt[5],
+    )
+
+    # write stuff to disk
+    # print("Write %i bytes to %s" % (len(filebuffer), filename))
+    try:
+        f = open(filename, "ab")
+        f.write(filebuffer)
+        f.close()
+    except OSError as e:
+        print("Please check the file or filesystem", e)
+        write = 0
+        pause_screen = -1
+        disp.clear(COLOR_BACKGROUND)
+        disp.print("IO Error", posy=0, fg=COLOR_TEXT)
+        disp.print("Please check", posy=20, fg=COLOR_TEXT)
+        disp.print("your", posy=40, fg=COLOR_TEXT)
+        disp.print("filesystem", posy=60, fg=COLOR_TEXT)
+        disp.update()
+        close_sensor()
+    except:
+        print("Unexpected error, stop writeing logfile")
+        write = 0
+
+    filebuffer = bytearray()
+    return
+
+
+def open_sensor():
+    global sensor
+    sensor = max30001.MAX30001(
+        usb=(False if current_mode == MODE_FINGER else True),
+        bias=bias,
+        sample_rate=ECG_RATE,
+        callback=callback_ecg,
+    )
+
+
+def close_sensor():
+    global sensor
+    sensor.close()
+
+
+def toggle_mode():
+    global current_mode
+    close_sensor()
+    current_mode = MODE_USB if current_mode == MODE_FINGER else MODE_FINGER
+    open_sensor()
+
+
+def toggle_bias():
+    global bias
+    close_sensor()
+    bias = False if bias == True else True
+    open_sensor()
+    return
+
+
+def toggle_write():
+    global write, disp, pause_screen
+    pause_screen = utime.time_ms() + 1000
+    disp.clear(COLOR_BACKGROUND)
+    if write > 0:
+        write_filebuffer()
+        write = 0
+        disp.print("Stop", posx=50, posy=20, fg=COLOR_TEXT)
+        disp.print("logging", posx=30, posy=40, fg=COLOR_TEXT)
+    else:
+        filebuffer = bytearray()
+        write = utime.time()
+        disp.print("Start", posx=45, posy=20, fg=COLOR_TEXT)
+        disp.print("logging", posx=30, posy=40, fg=COLOR_TEXT)
+
+    disp.update()
+    return
+
+
+def draw_histogram():
+    global disp, history, current_mode, bias, write, pause_screen
+
+    # skip rendering due to message beeing shown
+    if pause_screen == -1:
+        return
+    elif pause_screen > 0:
+        t = utime.time_ms()
+        if t > pause_screen:
+            pause_screen = 0
+        else:
+            return
+
+    disp.clear(COLOR_BACKGROUND)
+
+    # get max value and calc scale
+    value_max = 0
+    for value in history:
+        if abs(value) > value_max:
+            value_max = abs(value)
+    scale = SCALE_FACTOR / (value_max if value_max > 0 else 1)
+
+    # draw histogram
+    old = False
+    x = 0
+    samples = len(history)
+    for i, value in enumerate(history):
+        if old == False:
+            old = (x, int(value * scale) + OFFSET)
+            x += 1
+            continue
+        elif i > samples - WIDTH:
+            disp.line(old[0], old[1], x, int(value * scale) + OFFSET, col=COLOR_LINE)
+            old = (x, int(value * scale) + OFFSET)
+            x += 1
+
+    # draw text: mode/bias/write
+    disp.print(
+        current_mode + ("+Bias" if bias else ""),
+        posx=0,
+        posy=0,
+        fg=(COLOR_MODE_FINGER if current_mode == MODE_FINGER else COLOR_MODE_USB),
+    )
+
+    # announce writing ecg log
+    if write > 0:
+        t = utime.time()
+        if write > 0 and t % 2 == 0:
+            disp.print("LOG", posx=0, posy=60, fg=COLOR_WRITE_FG, bg=COLOR_WRITE_BG)
+
+    disp.update()
+
+
+def main():
+    # show button layout
+    disp.clear(COLOR_BACKGROUND)
+    disp.print("  BUTTONS  ", posx=0, posy=0, fg=COLOR_TEXT)
+    disp.print("Finger/USB>", posx=0, posy=20, fg=COLOR_MODE_FINGER)
+    disp.print("     Bias >", posx=0, posy=40, fg=COLOR_MODE_USB)
+    disp.print("< Write Log", posx=0, posy=60, fg=COLOR_WRITE_BG)
+    disp.update()
+    utime.sleep(3)
+
+    # start ecg
+    open_sensor()
+    while True:
+        button_pressed = False
+        while True:
+            v = buttons.read(
+                buttons.BOTTOM_LEFT | buttons.BOTTOM_RIGHT | buttons.TOP_RIGHT
+            )
+            if v == 0:
+                button_pressed = False
+
+            if not button_pressed and v & buttons.BOTTOM_LEFT != 0:
+                button_pressed = True
+                toggle_write()
+
+            elif not button_pressed and v & buttons.BOTTOM_RIGHT != 0:
+                button_pressed = True
+                toggle_bias()
+
+            elif not button_pressed and v & buttons.TOP_RIGHT != 0:
+                button_pressed = True
+                toggle_mode()
+
+        pass
+
+
+if __name__ == "__main__":
+    main()
diff --git a/preload/apps/ecg/metadata.json b/preload/apps/ecg/metadata.json
new file mode 100644
index 000000000..8c553a32e
--- /dev/null
+++ b/preload/apps/ecg/metadata.json
@@ -0,0 +1 @@
+{"name":"ECG","description":"A simple ecg monitor which displays the heart rate and allows you to switch between usb and finger reader.","category":"hardware","author":"griffon","revision":1}
-- 
GitLab