From 37013b656f29ca128a56f60f342c856750cc623c Mon Sep 17 00:00:00 2001
From: moon2 <moon2protonmail@protonmail.com>
Date: Tue, 17 Dec 2024 16:13:08 +0100
Subject: [PATCH] captouch: tuplify, docs

---
 components/flow3r_bsp/flow3r_bsp_ad7147.c     | 20 ++++---
 .../micropython/usermodule/mp_captouch.c      | 46 ++++++++++++---
 docs/api/captouch.rst                         | 58 ++++++++++++++++---
 python_payload/apps/fil3s/reader.py           |  2 +-
 4 files changed, 101 insertions(+), 25 deletions(-)

diff --git a/components/flow3r_bsp/flow3r_bsp_ad7147.c b/components/flow3r_bsp/flow3r_bsp_ad7147.c
index 387cb25729..714b9e2ff2 100644
--- a/components/flow3r_bsp/flow3r_bsp_ad7147.c
+++ b/components/flow3r_bsp/flow3r_bsp_ad7147.c
@@ -33,6 +33,10 @@
 BABYどこまででも
 */
 
+#define MAX_POS_AFE (63)
+#define MAX_NEG_AFE (63)
+#define MAX_AFE (MAX_POS_AFE + MAX_NEG_AFE)
+
 static ad7147_chip_t _top = {
     .name = "top",
     .is_bot = false,
@@ -417,7 +421,7 @@ static int _get_calib_data_index(ad7147_chip_t *chip, int i, int *petal_ret,
 void get_stage_hw_config(ad7147_chip_t *chip, ad7147_sequence_t *seq_out,
                          uint8_t i, uint16_t channel_mask) {
     int offset = chip->stages[i].afe_offset;
-    offset = offset > 126 ? 126 : offset;
+    offset = offset > (MAX_AFE) ? (MAX_AFE) : offset;
     seq_out->stages[i].channel_mask = channel_mask;
     // DATASHEET VIOLATION 2
     // the datasheet recommends to connect captouch pads to the internal bias
@@ -449,12 +453,12 @@ void get_stage_hw_config(ad7147_chip_t *chip, ad7147_sequence_t *seq_out,
     // seq_out->stages[i].idle_to_bias = !chip->is_bot;
     // experiment C: none (winner)
     seq_out->stages[i].idle_to_bias = false;
-    if (offset < 63) {
+    if (offset <= MAX_NEG_AFE) {
         seq_out->stages[i].neg_afe_offset = offset;
         seq_out->stages[i].pos_afe_offset = 0;
     } else {
-        seq_out->stages[i].neg_afe_offset = 63;
-        seq_out->stages[i].pos_afe_offset = offset - 63;
+        seq_out->stages[i].neg_afe_offset = MAX_NEG_AFE;
+        seq_out->stages[i].pos_afe_offset = offset - MAX_NEG_AFE;
     }
 }
 
@@ -558,15 +562,15 @@ static bool _stage_afe_tweak(ad7147_chip_t *chip, size_t cix) {
     if (offset <= 0 && diff < 0) {
         return false;
     }
-    if (offset >= 126 && diff > 0) {
+    if (offset >= (MAX_AFE) && diff > 0) {
         return false;
     }
     offset += diff;
     if (offset < 0) {
         offset = 0;
     }
-    if (offset > 126) {
-        offset = 126;
+    if (offset > (MAX_AFE)) {
+        offset = (MAX_AFE);
     }
     chip->stages[cix].afe_offset = offset;
     return true;
@@ -1196,7 +1200,7 @@ static uint16_t amb_limit(int32_t data) {
 }
 
 static uint8_t afe_limit(int32_t data) {
-    return data > 126 ? 126 : (data < 0 ? 0 : data);
+    return data > (MAX_AFE) ? (MAX_AFE) : (data < 0 ? 0 : data);
 }
 
 void flow3r_bsp_ad7147_set_calibration_data(int32_t *data) {
diff --git a/components/micropython/usermodule/mp_captouch.c b/components/micropython/usermodule/mp_captouch.c
index 73e2f9821e..88a24d6dae 100644
--- a/components/micropython/usermodule/mp_captouch.c
+++ b/components/micropython/usermodule/mp_captouch.c
@@ -330,7 +330,7 @@ STATIC void mp_captouch_petal_state_attr(mp_obj_t self_in, qstr attr,
     switch (attr) {
         case MP_QSTR_log: {
             mp_obj_list_t *log = MP_OBJ_TO_PTR(self->log);
-            dest[0] = mp_obj_new_list(log->len, log->items);
+            dest[0] = mp_obj_new_tuple(log->len, log->items);
         } break;
         case MP_QSTR_top:
             dest[0] = mp_obj_new_bool(top);
@@ -438,15 +438,16 @@ mp_captouch_state_new(const flow3r_bsp_captouch_data_t *underlying) {
     memcpy(&captouch->underlying, underlying,
            sizeof(flow3r_bsp_captouch_data_t));
 
-    captouch->petals = mp_obj_new_list(0, NULL);
+    mp_obj_t petals[10];
     for (int i = 0; i < 10; i++) {
         mp_captouch_petal_state_t *petal = m_new_obj(mp_captouch_petal_state_t);
         petal->base.type = &captouch_petal_state_type;
         petal->captouch = MP_OBJ_FROM_PTR(captouch);
         petal->ix = i;
         petal->log = _get_list_from_log(i);
-        mp_obj_list_append(captouch->petals, MP_OBJ_FROM_PTR(petal));
+        petals[i] = MP_OBJ_FROM_PTR(petal);
     }
+    captouch->petals = mp_obj_new_tuple(10, petals);
 
     return MP_OBJ_FROM_PTR(captouch);
 }
@@ -669,9 +670,40 @@ typedef struct {
     mp_obj_t petals;
 } captouch_config_obj_t;
 
+#define ALLOW_COMBINED_TOP
+
+static int _check_petal_mode(int mode, int petal) {
+    if (petal & 1) {
+        if ((mode < 0) || (mode > 2)) {
+            mp_raise_ValueError(MP_ERROR_TEXT(
+                "mode not allowed (bottom petal modes: 0, 1, 2)"));
+        }
+    } else {
+#ifdef ALLOW_COMBINED_TOP
+        if ((mode < 0) || (mode > 3)) {
+            mp_raise_ValueError(
+                MP_ERROR_TEXT("mode not allowed (top petals modes: 0, 3)"));
+        }
+#else
+        if ((mode != 0) && (mode != 3)) {
+            mp_raise_ValueError(
+                MP_ERROR_TEXT("mode not allowed (top petals modes: 0, 3)"));
+        }
+#endif
+    }
+    return mode;
+}
+
 static int _limit_petal_mode(int mode, int petal) {
-    int limit = 3 - (petal & 1);
-    return mode > limit ? limit : (mode < 0 ? 0 : mode);
+    if (petal & 1) {
+        return mode > 2 ? 2 : (mode < 0 ? 0 : mode);
+    } else {
+#ifdef ALLOW_COMBINED_TOP
+        return mode > 3 ? 3 : (mode < 0 ? 0 : mode);
+#else
+        return mode > 0 ? 3 : 0;
+#endif
+    }
 }
 
 STATIC void captouch_petal_config_attr(mp_obj_t self_in, qstr attr,
@@ -680,7 +712,7 @@ STATIC void captouch_petal_config_attr(mp_obj_t self_in, qstr attr,
     if (dest[0] != MP_OBJ_NULL) {
         if (attr == MP_QSTR_mode) {
             self->mode =
-                _limit_petal_mode(mp_obj_get_int(dest[1]), self->index);
+                _check_petal_mode(mp_obj_get_int(dest[1]), self->index);
             dest[0] = MP_OBJ_NULL;
         } else if (attr == MP_QSTR_logging) {
             self->logging = mp_obj_is_true(dest[1]);
@@ -763,7 +795,7 @@ static mp_obj_t captouch_config_new(int mode, mp_obj_type_t *type) {
         }
         petals[petal] = MP_OBJ_FROM_PTR(petalconf);
     }
-    self->petals = mp_obj_new_list(10, petals);
+    self->petals = mp_obj_new_tuple(10, petals);
     return MP_OBJ_FROM_PTR(self);
 }
 
diff --git a/docs/api/captouch.rst b/docs/api/captouch.rst
index 38a626d914..6bc68e61da 100644
--- a/docs/api/captouch.rst
+++ b/docs/api/captouch.rst
@@ -17,7 +17,7 @@ You cannot instantiate this object directly, but for REPL experiments there is a
 .. py:class:: CaptouchState
 
     .. py:attribute:: petals
-        :type: List[CaptouchPetalState]
+        :type: Tuple[CaptouchPetalState]
 
         State of individual petals.
 
@@ -99,9 +99,36 @@ You cannot instantiate this object directly, but for REPL experiments there is a
 
         May be affected by ``captouch.Config``.
 
+    .. py:attribute:: log
+        :type: Tuple[PetalLogFrame]
+
+        Raw frame output of the captouch driver. Must be enabled by ``captouch.Config``.
+
+        Since micropython and the captouch driver are running asynchronously we're providing
+        a list of all raw data points collected since the last ``.think()`` call.
+
+        The lowest indices are the oldest frames, so that you could compile a complete log
+        (or one cropped to arbitrary length) simply by appending new data:
+
+        .. code-block:: python
+
+            def on_enter(self, vm):
+                super().on_enter(vm)
+                conf = captouch.Config.default()
+                conf.petals[0].logging = True
+                conf.apply()
+                self.log = list()
+
+            def think(self, ins, delta_ms):
+                super().think(ins, delta_ms)
+                # append new frames to end of log
+                self.log += ins.captouch.petals[0].log
+                # crop old frames
+                self.log = self.log[-100:]
+
 
 .. py:data:: PETAL_ANGLES
-   :type: list[complex]
+   :type: Tuple[complex]
  
    List of constants that can be used to rotate the output of the `.pos` attribute of both `CaptouchPetalState` and
    `PetalLogFrame` to align with the display (x, y) coordinates.
@@ -171,7 +198,7 @@ except for 2 and 8 for a "dual joystick" mode, you increase your frame rate to u
         if you already have one around.
 
     .. py:attribute:: petals
-        :type: List[PetalConfig]
+        :type: Tuple[PetalConfig]
 
         Config of individual petals, indexed as in the ``CaptouchState`` object.
 
@@ -180,17 +207,24 @@ except for 2 and 8 for a "dual joystick" mode, you increase your frame rate to u
     .. py:attribute:: mode
         :type: int
 
-        What kind of data should be collected for this petal.
+        What kind of data should be collected for this petal. Raises ``ValueError`` when set to an unallowed value.
+
+        0: No data at all. Allowed for all petals.
+
+        1: Button Mode: All pads combined, no positional output. Only allowed for bottom petals.
 
-        0: No data at all
+        2: 1D: Only radial position is provided. Only allowed for bottom petals.
 
-        1: Button Mode: All pads combined, no positional output
+        3: 2D: Full positional output. Only allowed for top petals.
 
-        2: 1D: Only radial position is provided
+        Defaults to the maximum allowed value.
 
-        3: 2D: Full positional output. Only available for top petals.
+        The integer value corresponds to the number of active chip channels. Data rate scales linearily per chip
+        at 0.75ms per channel plus a noisy overhead of 2-4ms typically. Bottom petals and petal 2 are connected to
+        one chip, the remaining top petals to another.
 
-        Default: 3 (top petals), 2 (bottom petals)
+        *Note: We discovered last-minute that modes 1 and 2 are not functioning properly for top petals, so they are
+        currently unavailable. We will try to fix them up in the future.*
 
     .. py:attribute:: logging
         :type: bool
@@ -201,6 +235,12 @@ except for 2 and 8 for a "dual joystick" mode, you increase your frame rate to u
 
         Default: False
 
+    .. py:method:: set_min_mode(mode: int) -> None:
+
+        If the current mode is lower than the argument, it gets increased to that value if allowed. If the value
+        is not allowed it is either set to the next-biggest allowed value or, if no such value exists, to the
+        largest allowed value.
+
 
 Gestures
 --------
diff --git a/python_payload/apps/fil3s/reader.py b/python_payload/apps/fil3s/reader.py
index b63c6005ef..dae2bdde98 100644
--- a/python_payload/apps/fil3s/reader.py
+++ b/python_payload/apps/fil3s/reader.py
@@ -63,7 +63,7 @@ class Reader(ActionView):
 
         self.captouch_config = captouch.Config.empty()
         for x in range(2, 10, 2):
-            self.captouch_config.petals[x].mode = 2
+            self.captouch_config.petals[x].mode = 3
 
         self.scroller = widgets.Scroller(
             self.captouch_config,
-- 
GitLab