From 5fa3bb583731d4df164188a5ca3c16d2d0a257d4 Mon Sep 17 00:00:00 2001 From: Serge Bazanski <q3k@q3k.org> Date: Sat, 15 Jul 2023 19:15:09 +0200 Subject: [PATCH] mpy: new captouch module --- components/badge23/include/badge23/captouch.h | 15 +- python_payload/mypystubs/captouch.pyi | 100 +++++++++++ sim/fakes/captouch.py | 86 ++++++++++ sim/fakes/hardware.py | 95 +++++++++-- usermodule/micropython.cmake | 1 + usermodule/mp_captouch.c | 158 ++++++++++++++++++ 6 files changed, 443 insertions(+), 12 deletions(-) create mode 100644 python_payload/mypystubs/captouch.pyi create mode 100644 sim/fakes/captouch.py create mode 100644 usermodule/mp_captouch.c diff --git a/components/badge23/include/badge23/captouch.h b/components/badge23/include/badge23/captouch.h index c35a2eab7c..1afe8cd681 100644 --- a/components/badge23/include/badge23/captouch.h +++ b/components/badge23/include/badge23/captouch.h @@ -64,14 +64,23 @@ void captouch_force_calibration(); uint8_t captouch_calibration_active(); typedef struct { + // Not all pads are present on all petals. + // Top petals have a base, cw and ccw pad. + // Bottom petlas have a base and a tip. + + // Is the tip pressed down? bool tip_pressed; + // Is the base pressed down? bool base_pressed; + // Is the clockwise pad pressed down? bool cw_pressed; + // Is the counter-clockwise pad pressed down? bool ccw_pressed; } captouch_pad_state_t; typedef struct { captouch_pad_state_t pads; + // Are any of the pads pressed down? bool pressed; } captouch_petal_state_t; @@ -79,9 +88,13 @@ typedef struct { captouch_petal_state_t petals[10]; } captouch_state_t; +/* Extened/new API for reading captouch state. Allows for access to individual + * pad data. + * + * Likely to evolce into the new st3m api for captouch. + */ void read_captouch_ex(captouch_state_t *state); - /* returns uint16_t which encodes each petal "touched" state as the bit * corresponding to the petal index. "touched" is determined by checking if * any of the pads belonging to the petal read a value higher than their diff --git a/python_payload/mypystubs/captouch.pyi b/python_payload/mypystubs/captouch.pyi new file mode 100644 index 0000000000..6a4d4c07b9 --- /dev/null +++ b/python_payload/mypystubs/captouch.pyi @@ -0,0 +1,100 @@ +from typing import Protocol, List + + +class CaptouchPetalPadsState(Protocol): + """ + Current state of pads on a captouch petal. + + Not all petals have all pads. Top petals have a a base, cw and ccw pad. + Bottom petals have a base and tip pad. + """ + + @property + def tip(self) -> bool: + """ + True if the petals's tip is currently touched. + """ + ... + + @property + def base(self) -> bool: + """ + True if the petal's base is currently touched. + """ + ... + + @property + def cw(self) -> bool: + """ + True if the petal's clockwise pad is currently touched. + """ + ... + + @property + def ccw(self) -> bool: + """ + True if the petal's counter clockwise pad is currently touched. + """ + ... + + +class CaptouchPetalState(Protocol): + @property + def pressed(self) -> bool: + """ + True if any of the petal's pads is currently touched. + """ + ... + + @property + def top(self) -> bool: + """ + True if this is a top petal. + """ + ... + + @property + def bottom(self) -> bool: + """ + True if this is a bottom petal. + """ + ... + + @property + def pads(self) -> CaptouchPetalPadsState: + """ + State of individual pads of the petal. + """ + ... + + +class CaptouchState(Protocol): + """ + State of captouch sensors, captured at some time. + """ + @property + def petals(self) -> List[CaptouchPetalState]: + """ + State of individual petals. + + Contains 10 elements, with the zeroth element being the pad closest to + the USB port. Then, every other pad in a counter-clockwise direction. + + Pads 0, 2, 4, 6, 8 are Top pads. + + Pads 1, 3, 5, 7, 9 are Bottom pads. + """ + ... + + +def read() -> CaptouchState: + """ + Reads current captouch state from hardware and returns a snapshot in time. + """ + ... + +def calibration_active() -> bool: + """ + Returns true if the captouch system is current recalibrating. + """ + ... \ No newline at end of file diff --git a/sim/fakes/captouch.py b/sim/fakes/captouch.py new file mode 100644 index 0000000000..4706637827 --- /dev/null +++ b/sim/fakes/captouch.py @@ -0,0 +1,86 @@ +from typing import List + + +class CaptouchPetalPadsState: + def __init__(self, tip, base, cw, ccw) -> None: + self._tip = tip + self._base = base + self._cw = cw + self._ccw = ccw + + @property + def tip(self) -> bool: + return self._tip + + @property + def base(self) -> bool: + return self._base + + @property + def cw(self) -> bool: + return self._cw + + @property + def ccw(self) -> bool: + return self._ccw + + +class CaptouchPetalState: + def __init__(self, ix: int, pads: CaptouchPetalPadsState): + self._pads = pads + self._ix = ix + + @property + def pressed(self) -> bool: + if self.top: + return self._pads.base or self._pads.ccw or self._pads.cw + else: + return self._pads.tip or self._pads.base + + @property + def top(self) -> bool: + return self._ix % 2 == 0 + + @property + def bottom(self) -> bool: + return not self.bottom + + @property + def pads(self) -> CaptouchPetalPadsState: + return self._pads + + +class CaptouchState: + def __init__(self, petals: List[CaptouchPetalState]): + self._petals = petals + + @property + def petals(self) -> List[CaptouchPetalState]: + return self._petals + + +def read() -> CaptouchState: + import hardware + hardware._sim.process_events() + hardware._sim.render_gui_lazy() + petals = hardware._sim.petals + + res = [] + for petal in range(10): + top = petal % 2 == 0 + if top: + ccw = petals.state_for_petal_pad(petal, 1) + cw = petals.state_for_petal_pad(petal, 2) + base = petals.state_for_petal_pad(petal, 3) + pads = CaptouchPetalPadsState(False, base, cw, ccw) + res.append(CaptouchPetalState(petal, pads)) + else: + tip = petals.state_for_petal_pad(petal, 0) + base = petals.state_for_petal_pad(petal, 3) + pads = CaptouchPetalPadsState(tip, base, False, False) + res.append(CaptouchPetalState(petal, pads)) + return CaptouchState(res) + + +def calibration_active() -> bool: + return False \ No newline at end of file diff --git a/sim/fakes/hardware.py b/sim/fakes/hardware.py index 05003e6e3a..3f6ef563b4 100644 --- a/sim/fakes/hardware.py +++ b/sim/fakes/hardware.py @@ -1,6 +1,7 @@ import math import os import time +import itertools import pygame @@ -42,8 +43,6 @@ class Input: def _mouse_coords_to_id(self, mouse_x, mouse_y): for i, (x, y) in enumerate(self.POSITIONS): - x += self.MARKER_SIZE // 2 - y += self.MARKER_SIZE // 2 dx = mouse_x - x dy = mouse_y - y if math.sqrt(dx**2 + dy**2) < self.MARKER_SIZE // 2: @@ -71,8 +70,6 @@ class Input: def render(self, surface): s = self.state() for i, (x, y) in enumerate(self.POSITIONS): - x += self.MARKER_SIZE // 2 - y += self.MARKER_SIZE // 2 if s[i]: pygame.draw.circle(surface, self.COLOR_HELD, (x, y), self.MARKER_SIZE//2) elif i == self._mouse_hover: @@ -82,16 +79,83 @@ class Input: class PetalsInput(Input): - # First petal is above USB-C jack, then CCW. - POSITIONS = [ - (356, 122), (163, 112), (114, 302), ( 49, 477), (204, 587), (352, 696), (504, 587), (660, 477), (602, 298), (547, 117), + _petal_positions_top = [ + (406, 172), (164, 352), (254, 637), (554, 637), (652, 348), + ] + _petal_positions_bottom = [ + (213, 162), (99, 527), (402, 746), (710, 527), (597, 167) ] + POSITIONS = list(itertools.chain(*[ + [ + (x + math.cos(i * -1.256 + 1.57) * 40, y + math.sin(i * -1.256 + 1.57) * 40), # base + (x + math.cos(i * -1.256 + 5.75) * 40, y + math.sin(i * -1.256 + 5.75) * 40), # cw + (x + math.cos(i * -1.256 + 3.66) * 40, y + math.sin(i * -1.256 + 3.66) * 40), # ccw + ] + for i, (x, y) in enumerate(_petal_positions_top) + ] + [ + [ + (x + math.cos(i * -1.256 - 2.20) * 40, y + math.sin(i * -1.256 - 2.20) * 40), # tip + (x + math.cos(i * -1.256 - 5.34) * 40, y + math.sin(i * -1.256 - 5.34) * 40), # base + ] + for i, (x, y) in enumerate(_petal_positions_bottom) + ])) + MARKER_SIZE = 40 + + def _index_for_petal_pad(self, petal, pad): + if petal >= 10: + raise ValueError("petal cannot be > 10") + + # convert from st3m/bsp index into input state index + top = False + if petal % 2 == 0: + top = True + res = petal // 2 + if top: + res *= 3 + else: + res *= 2 + res += 3 * 5 + + if top: + if pad == 1: # ccw + res += 2 + elif pad == 2: # cw + res += 1 + elif pad == 3: # base + res += 0 + else: + raise ValueError("invalid pad number") + else: + if pad == 0: # tip + res += 0 + elif pad == 3: # base + res += 1 + else: + raise ValueError("invalid pad number") + return res + + def state_for_petal_pad(self, petal, pad): + ix = self._index_for_petal_pad(petal, pad) + return self.state()[ix] + + def state_for_petal(self, petal): + res = False + if petal % 2 == 0: + # top + res = res or self.state_for_petal_pad(petal, 1) + res = res or self.state_for_petal_pad(petal, 2) + res = res or self.state_for_petal_pad(petal, 3) + else: + # bottom + res = res or self.state_for_petal_pad(petal, 0) + res = res or self.state_for_petal_pad(petal, 3) + return res class ButtonsInput(Input): POSITIONS = [ - ( 14, 230), ( 46, 230), ( 78, 230), - (714, 230), (746, 230), (778, 230), + ( 24, 240), ( 56, 240), ( 88, 240), + (724, 240), (756, 240), (788, 240), ] MARKER_SIZE = 20 COLOR_HELD = (0x80, 0x80, 0x80, 0xff) @@ -394,7 +458,7 @@ def menu_button_get_left(): def get_captouch(a): _sim.process_events() _sim.render_gui_lazy() - return _sim.petals.state()[a] + return _sim.petals.state_for_petal(a) #TODO(iggy/q3k do proper positional captouch) def captouch_get_petal_rad(a): @@ -423,4 +487,13 @@ def scope_draw(ctx): ctx.line_to(130, 0) ctx.line_to(130, 130) ctx.line_to(-130, 130) - ctx.line_to(-130, 0) \ No newline at end of file + ctx.line_to(-130, 0) + +def usb_connected(): + return True + +def usb_console_active(): + return True + +def i2c_scan(): + return [16, 44, 45, 85, 109, 110] diff --git a/usermodule/micropython.cmake b/usermodule/micropython.cmake index ab06366471..6cc83509d6 100644 --- a/usermodule/micropython.cmake +++ b/usermodule/micropython.cmake @@ -12,6 +12,7 @@ target_sources(usermod_badge23 INTERFACE ${CMAKE_CURRENT_LIST_DIR}/mp_badge_link.c ${CMAKE_CURRENT_LIST_DIR}/mp_kernel.c ${CMAKE_CURRENT_LIST_DIR}/mp_uctx.c + ${CMAKE_CURRENT_LIST_DIR}/mp_captouch.c ) target_include_directories(usermod_badge23 INTERFACE diff --git a/usermodule/mp_captouch.c b/usermodule/mp_captouch.c new file mode 100644 index 0000000000..e92fdc115a --- /dev/null +++ b/usermodule/mp_captouch.c @@ -0,0 +1,158 @@ +#include "py/runtime.h" +#include "py/builtin.h" + +#include "badge23/captouch.h" + +#include <string.h> + +STATIC mp_obj_t mp_captouch_calibration_active(void) +{ + return mp_obj_new_int(captouch_calibration_active()); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_captouch_calibration_active_obj, mp_captouch_calibration_active); + +typedef struct { + mp_obj_base_t base; + mp_obj_t petal; +} mp_captouch_petal_pads_state_t; + +const mp_obj_type_t captouch_petal_pads_state_type; + +typedef struct { + mp_obj_base_t base; + mp_obj_t captouch; + mp_obj_t pads; + size_t ix; +} mp_captouch_petal_state_t; + +const mp_obj_type_t captouch_petal_state_type; + +typedef struct { + mp_obj_base_t base; + mp_obj_t petals; + captouch_state_t underlying; +} mp_captouch_state_t; + +const mp_obj_type_t captouch_state_type; + +STATIC void mp_captouch_petal_pads_state_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + mp_captouch_petal_pads_state_t *self = MP_OBJ_TO_PTR(self_in); + if (dest[0] != MP_OBJ_NULL) { + return; + } + + mp_captouch_petal_state_t *petal = MP_OBJ_TO_PTR(self->petal); + mp_captouch_state_t *captouch = MP_OBJ_TO_PTR(petal->captouch); + captouch_petal_state_t *state = &captouch->underlying.petals[petal->ix]; + bool top = (petal->ix % 2) == 0; + + if (top) { + switch (attr) { + case MP_QSTR_base: dest[0] = mp_obj_new_bool(state->pads.base_pressed); break; + case MP_QSTR_cw: dest[0] = mp_obj_new_bool(state->pads.cw_pressed); break; + case MP_QSTR_ccw: dest[0] = mp_obj_new_bool(state->pads.ccw_pressed); break; + } + } else { + switch (attr) { + case MP_QSTR_tip: dest[0] = mp_obj_new_bool(state->pads.tip_pressed); break; + case MP_QSTR_base: dest[0] = mp_obj_new_bool(state->pads.base_pressed); break; + } + } +} + +STATIC void mp_captouch_petal_state_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + mp_captouch_petal_state_t *self = MP_OBJ_TO_PTR(self_in); + if (dest[0] != MP_OBJ_NULL) { + return; + } + + mp_captouch_state_t *captouch = MP_OBJ_TO_PTR(self->captouch); + captouch_petal_state_t *state = &captouch->underlying.petals[self->ix]; + + bool top = (self->ix % 2) == 0; + + switch (attr) { + case MP_QSTR_top: dest[0] = mp_obj_new_bool(top); break; + case MP_QSTR_bottom: dest[0] = mp_obj_new_bool(!top); break; + case MP_QSTR_pressed: dest[0] = mp_obj_new_bool(state->pressed); break; + case MP_QSTR_pads: dest[0] = self->pads; break; + } +} + +STATIC void mp_captouch_state_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { + mp_captouch_state_t *self = MP_OBJ_TO_PTR(self_in); + if (dest[0] != MP_OBJ_NULL) { + return; + } + + switch (attr) { + case MP_QSTR_petals: dest[0] = self->petals; break; + } +} + +MP_DEFINE_CONST_OBJ_TYPE( + captouch_petal_pads_state_type, + MP_QSTR_CaptouchPetalPadsState, + MP_TYPE_FLAG_NONE, + attr, mp_captouch_petal_pads_state_attr +); + +MP_DEFINE_CONST_OBJ_TYPE( + captouch_petal_state_type, + MP_QSTR_CaptouchPetalState, + MP_TYPE_FLAG_NONE, + attr, mp_captouch_petal_state_attr +); + +MP_DEFINE_CONST_OBJ_TYPE( + captouch_state_type, + MP_QSTR_CaptouchState, + MP_TYPE_FLAG_NONE, + attr, mp_captouch_state_attr +); + +STATIC mp_obj_t mp_captouch_state_new(const captouch_state_t *underlying) { + mp_captouch_state_t *captouch = m_new_obj(mp_captouch_state_t); + captouch->base.type = &captouch_state_type; + memcpy(&captouch->underlying, underlying, sizeof(captouch_state_t)); + + captouch->petals = mp_obj_new_list(0, NULL); + 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; + + mp_captouch_petal_pads_state_t *pads = m_new_obj(mp_captouch_petal_pads_state_t); + pads->base.type = &captouch_petal_pads_state_type; + pads->petal = MP_OBJ_FROM_PTR(petal); + petal->pads = MP_OBJ_FROM_PTR(pads); + + mp_obj_list_append(captouch->petals, MP_OBJ_FROM_PTR(petal)); + } + + return MP_OBJ_FROM_PTR(captouch); +} + +STATIC mp_obj_t mp_captouch_read(void) { + mp_captouch_state_t st; + read_captouch_ex(&st); + return mp_captouch_state_new(&st); +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_captouch_read_obj, mp_captouch_read); + +STATIC const mp_rom_map_elem_t globals_table[] = { + { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_captouch_read_obj) }, + { MP_ROM_QSTR(MP_QSTR_calibration_active), MP_ROM_PTR(&mp_captouch_calibration_active_obj) } +}; + +STATIC MP_DEFINE_CONST_DICT(globals, globals_table); + + +const mp_obj_module_t mp_module_captouch_user_cmodule = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_captouch, mp_module_captouch_user_cmodule); \ No newline at end of file -- GitLab