From c8cdf948e0cad1719f648a74b5005eaae06b9cea Mon Sep 17 00:00:00 2001
From: moon2 <moon2protonmail@protonmail.com>
Date: Fri, 9 Jun 2023 21:39:41 +0200
Subject: [PATCH] badge link: some backend

---
 components/badge23/include/badge23/spio.h |  63 +++++-
 components/badge23/spio.c                 | 240 ++++++++++++++++++----
 usermodule/micropython.cmake              |   1 +
 usermodule/mp_badge_link.c                |  64 ++++++
 4 files changed, 326 insertions(+), 42 deletions(-)
 create mode 100644 usermodule/mp_badge_link.c

diff --git a/components/badge23/include/badge23/spio.h b/components/badge23/include/badge23/spio.h
index ee8f5f3cab..085dc306e8 100644
--- a/components/badge23/include/badge23/spio.h
+++ b/components/badge23/include/badge23/spio.h
@@ -6,13 +6,68 @@
 #define BUTTON_PRESSED_RIGHT 1
 #define BUTTON_NOT_PRESSED 0
 
-int8_t get_button_state(bool leftbutton);
-void update_button_state();
+#define BADGE_LINK_LINE_IN_TIP      0b0001
+#define BADGE_LINK_LINE_IN_RING     0b0010
+#define BADGE_LINK_LINE_OUT_TIP     0b0100
+#define BADGE_LINK_LINE_OUT_RING    0b1000
+#define BADGE_LINK_LINE_IN ((BADGE_LINK_LINE_IN_TIP) | (BADGE_LINK_LINE_IN_RING))
+#define BADGE_LINK_LINE_OUT ((BADGE_LINK_LINE_OUT_TIP) | (BADGE_LINK_LINE_OUT_RING))
+#define BADGE_LINK_ALL ((BADGE_LINK_LINE_IN) | (BADGE_LINK_LINE_OUT))
+
+#define BADGE_LINK_LINE_IN_TIP_PIN 4
+#define BADGE_LINK_LINE_IN_RING_PIN 5
+#define BADGE_LINK_LINE_OUT_TIP_PIN 6
+#define BADGE_LINK_LINE_OUT_RING_PIN 7
+
+/* Initializes GPIO modes, prefills structs, etc. Call before using library.
+ */
 void init_buttons();
 
+/* Gets data from I2C portexpanders and GPIOs. Requires I2C lock.
+ */
+void update_button_state();
+
+/* UI sugar: People might prefer using one button for in-application stuff and the other
+ * for entering main menu/volume control, depending on their handedness and how they hold
+ * the badge. This function allows them configure this and is meant to be only be used by
+ * the OS user config handler.
+ *
+ * Set to 1 to use the left shoulder button as the menu button, 0 for the right
+ */
+void spio_menu_button_set_left(bool left);
+
+/* Gets user menu button user preference as set with spio_menu_button_set_left.
+ */
+int8_t spio_menu_button_get_left();
+
+/* Read the state of the menu/application button at the last call of update_button_state.
+ * Compare with BUTTON_(NOT)PRESSED* constants for -h.
+ */
 int8_t spio_menu_button_get();
 int8_t spio_application_button_get();
+
+/* Read the state of the left/right button at the last call of update_button_state.
+ * Compare with BUTTON_(NOT)PRESSED* constants for -h.
+ * This ignores user preference and should be used only with good reason.
+ */
 int8_t spio_left_button_get();
 int8_t spio_right_button_get();
-int8_t spio_menu_button_get_left();
-void spio_menu_button_set_left(bool left);
+
+/* Gets active badge links ports. Mask with BADGE_LINK_LINE_{IN/OUT}_{TIP/RING}. The corresponding
+ * GPIO indices are listed in BADGE_LINK_LINE_{OUT/IN}_{TIP/RING}_PIN.
+ */
+uint8_t spio_badge_link_get_active(uint8_t pin_mask);
+
+/* Disables badge link ports. Mask with BADGE_LINK_LINE_{IN/OUT}_{TIP/RING}. The corresponding
+ * GPIO indices are listed in BADGE_LINK_LINE_{OUT/IN}_{TIP/RING}_PIN.
+ * Returns the output of spio_badge_link_get_active after execution.
+ */
+uint8_t spio_badge_link_disable(uint8_t pin_mask);
+
+/* Enables badge link ports. Mask with BADGE_LINK_LINE_{IN/OUT}_{TIP/RING}. The corresponding
+ * GPIO indices are listed in BADGE_LINK_LINE_{OUT/IN}_{TIP/RING}_PIN.
+ * Returns the output of spio_badge_link_get_active after execution.
+ *
+ * Do NOT connect headphones to a badge link port. You might hear a ringing for a while. Warn user.
+ */
+uint8_t spio_badge_link_enable(uint8_t pin_mask);
diff --git a/components/badge23/spio.c b/components/badge23/spio.c
index e93bc90456..22946bee31 100644
--- a/components/badge23/spio.c
+++ b/components/badge23/spio.c
@@ -4,10 +4,123 @@
 #include "stdint.h"
 #include "badge23/spio.h"
 #include "badge23/lock.h"
+#include "badge23/audio.h"
+
+#include "driver/i2c.h"
+#define I2C_MASTER_NUM 0
+#define TIMEOUT_MS 1000
+
+
+#if defined(CONFIG_BADGE23_HW_GEN_P4) || defined(CONFIG_BADGE23_HW_GEN_P3)
+
+#define BADGE_LINK_LINE_OUT_TIP_ENABLE_PIN 6
+#define BADGE_LINK_LINE_OUT_RING_ENABLE_PIN 7
+#define BADGE_LINK_LINE_IN_TIP_ENABLE_PIN 5
+#define BADGE_LINK_LINE_IN_RING_ENABLE_PIN 4
+
+#define LEFT_BUTTON_RIGHT (0+8)
+#define RIGHT_BUTTON_LEFT (6+8)
+#define RIGHT_BUTTON_MID (5+8)
+#define RIGHT_BUTTON_RIGHT (7+8)
+
+#elif defined(CONFIG_BADGE23_HW_GEN_P6)
+
+#define BADGE_LINK_LINE_OUT_TIP_ENABLE_PIN 5
+#define BADGE_LINK_LINE_OUT_RING_ENABLE_PIN 6
+#define BADGE_LINK_LINE_IN_TIP_ENABLE_PIN 3
+
+#define BADGE_LINK_LINE_IN_RING_ENABLE_PIN 4
+#define LEFT_BUTTON_RIGHT (0+8)
+#define RIGHT_BUTTON_LEFT (4+8)
+#define RIGHT_BUTTON_MID (7+8)
+#define RIGHT_BUTTON_RIGHT (5+8)
+#endif
 
 static int8_t leftbutton = 0;
 static int8_t rightbutton = 0;
 
+static bool menu_button_left = 0;
+
+static uint8_t badge_link_enabled = 0;
+
+/* The MAX7321 doesn't have any input/output pin configuration methods. Instead,
+ * it has a ~40k pullup resistor to VCC and a programmable shunt transistor to VEE.
+ * Writing a byte to the IC closes each shunt with a LO at its index.
+ * Reading a byte returns the state of each pin.
+ * 
+ * This means that changing a single output bit cannot be changed without some
+ * information about the other pins. Also a output pin set to HI can read as LO
+ * if there's an outside shunt.
+ */
+typedef struct{
+    uint8_t address;
+    uint8_t is_output_pin;  // mask for pins we wish to use as outputs
+    uint8_t read_state;     //
+    uint8_t output_state;   // goal output state
+} max7321_t;
+
+max7321_t port_expanders[2]; 
+
+void _max7321_update(max7321_t *max){
+    uint8_t rx = 0;
+    uint8_t tx = (~(max->is_output_pin)) | max->output_state;
+
+    xSemaphoreTake(mutex_i2c, portMAX_DELAY);
+    esp_err_t tx_error = i2c_master_write_to_device(I2C_MASTER_NUM, max->address, &tx, sizeof(tx), TIMEOUT_MS / portTICK_PERIOD_MS);
+    esp_err_t rx_error = i2c_master_read_from_device(I2C_MASTER_NUM, max->address, &rx, sizeof(rx), TIMEOUT_MS / portTICK_PERIOD_MS);
+    xSemaphoreGive(mutex_i2c);
+    max->read_state = rx;
+}
+
+void max7321s_update(){
+    _max7321_update(&port_expanders[0]);
+    _max7321_update(&port_expanders[1]);
+}
+
+bool max7321s_get_pin(uint8_t pin){
+    if(pin>15) return 0;
+    max7321_t * pe = &port_expanders[0];
+    if(pin>7){
+        pe = &port_expanders[1];
+        pin -= 8;
+    }
+    return ((pe->read_state) >> pin) & 1;
+}
+
+bool max7321s_set_pin(uint8_t pin, bool on){
+    if(pin>15) return 0;
+    max7321_t * pe = &port_expanders[0];
+    if(pin>7){
+        pe = &port_expanders[1];
+        pin -= 8;
+    }
+
+    if(((pe->is_output_pin) >> pin) & 1){
+        if(on){
+            pe->output_state |= 1<<pin;
+        } else {
+            pe->output_state &= ~(1<<pin);
+        }
+        return 1;
+    } else {
+        return 0;
+    }
+}
+
+void max7321s_set_pinmode_output(uint8_t pin, bool output){
+    if(pin>15) return 0;
+    max7321_t * pe = &port_expanders[0];
+    if(pin>7){
+        pe = &port_expanders[1];
+        pin -= 8;
+    }
+    if(output){
+        pe->is_output_pin |= (1<<pin);
+    } else {
+        pe->is_output_pin &= ~(1<<pin);
+    }
+}
+
 #if defined(CONFIG_BADGE23_HW_GEN_P1)
 
 #define RIGHT_BUTTON_LEFT 37
@@ -61,21 +174,16 @@ void update_button_state(){
     }
 }
 
-#elif defined(CONFIG_BADGE23_HW_GEN_P3) || defined(CONFIG_BADGE23_HW_GEN_P4)
-
-#include "driver/i2c.h"
-#define I2C_MASTER_NUM 0
-#define TIMEOUT_MS 1000
+#elif defined(CONFIG_BADGE23_HW_GEN_P3) || defined(CONFIG_BADGE23_HW_GEN_P4) || defined(CONFIG_BADGE23_HW_GEN_P6)
 
 //on ESP32
 #define LEFT_BUTTON_LEFT 3
 #define LEFT_BUTTON_MID 0
 
 //on PORTEXPANDER
-#define LEFT_BUTTON_RIGHT 0
-#define RIGHT_BUTTON_LEFT 6
-#define RIGHT_BUTTON_MID 5
-#define RIGHT_BUTTON_RIGHT 7
+
+max7321_t port_expanders[] = {  {0b01101110, 0, 255, 255}, 
+                                {0b01101101, 0, 255, 255}  };
 
 static void _init_buttons(){
     //configure all buttons as pullup
@@ -90,42 +198,45 @@ static void _init_buttons(){
     cfg.pin_bit_mask = 1;
     cfg.pull_up_en = GPIO_PULLUP_DISABLE;
     ESP_ERROR_CHECK(gpio_config(&cfg));
-    printf("nya\n");
+    
+    max7321s_set_pinmode_output(RIGHT_BUTTON_RIGHT, 0);
+    max7321s_set_pinmode_output(RIGHT_BUTTON_MID, 0);
+    max7321s_set_pinmode_output(RIGHT_BUTTON_LEFT, 0);
+    max7321s_set_pinmode_output(LEFT_BUTTON_RIGHT, 0);
 }
 
-void update_button_state(){
-    uint8_t port;
-
-    xSemaphoreTake(mutex_i2c, portMAX_DELAY);
-    esp_err_t ret = i2c_master_read_from_device(I2C_MASTER_NUM, 0b1101101, &port, sizeof(port), TIMEOUT_MS / portTICK_PERIOD_MS);
-    xSemaphoreGive(mutex_i2c);
+int8_t process_button_state(bool r, bool m, bool l){
+    if(!l){
+        return BUTTON_PRESSED_LEFT;
+    } else if(!m){
+        return BUTTON_PRESSED_DOWN;
+    } else if(!r){
+        return BUTTON_PRESSED_RIGHT;
+    } else {
+        return BUTTON_NOT_PRESSED;
+    }
+}
 
-    uint8_t rr = port & (1ULL << RIGHT_BUTTON_RIGHT);
-    uint8_t rm = port & (1ULL << RIGHT_BUTTON_MID);
-    uint8_t rl = port & (1ULL << RIGHT_BUTTON_LEFT);
-    uint8_t lr = port & (1ULL << LEFT_BUTTON_RIGHT);
+void update_button_state(){
+    max7321s_update();
+    uint8_t rr = max7321s_get_pin(RIGHT_BUTTON_RIGHT);
+    uint8_t rm = max7321s_get_pin(RIGHT_BUTTON_MID);
+    uint8_t rl = max7321s_get_pin(RIGHT_BUTTON_LEFT);
+    uint8_t lr = max7321s_get_pin(LEFT_BUTTON_RIGHT);
     uint8_t ll = gpio_get_level(LEFT_BUTTON_LEFT);
     uint8_t lm = gpio_get_level(LEFT_BUTTON_MID);
 
-    if(!rl){
-        rightbutton = BUTTON_PRESSED_LEFT;
-    } else if(!rm){
-        rightbutton = BUTTON_PRESSED_DOWN;
-    } else if(!rr){
-        rightbutton = BUTTON_PRESSED_RIGHT;
-    } else {
-        rightbutton = BUTTON_NOT_PRESSED;
+    int8_t new_rightbutton = process_button_state(rr, rm, rl);
+    int8_t new_leftbutton = process_button_state(lr, lm, ll);
+    if(new_rightbutton != rightbutton){
+        //TODO: CALLBACK button_state_has_changed_to(new_rightbutton)
+        //note: consider menubutton/application button config option
     }
-
-    if(!ll){
-        leftbutton = BUTTON_PRESSED_LEFT;
-    } else if(!lm){
-        leftbutton = BUTTON_PRESSED_DOWN;
-    } else if(!lr){
-        leftbutton = BUTTON_PRESSED_RIGHT;
-    } else {
-        leftbutton = BUTTON_NOT_PRESSED;
+    if(new_leftbutton != leftbutton){
+        //TODO: CALLBACK button_state_has_changed_to(new_leftbutton)
     }
+    rightbutton = new_rightbutton;
+    leftbutton = new_leftbutton;
 }
 
 #else
@@ -145,7 +256,6 @@ int8_t get_button_state(bool left){
     return rightbutton;
 }
 
-static bool menu_button_left = 0;
 
 void spio_menu_button_set_left(bool left){
     menu_button_left = 1;
@@ -170,3 +280,57 @@ int8_t spio_right_button_get(){
 int8_t spio_menu_button_get_left(){
     return menu_button_left;
 }
+
+uint8_t spio_badge_link_get_active(uint8_t pin_mask){
+    return badge_link_enabled & pin_mask;
+}
+
+#if defined(CONFIG_BADGE23_HW_GEN_P1)
+
+static uint8_t spio_badge_link_set(uint8_t pin_mask, uint8_t state){
+    return 0; // no badge link here (yet)
+}
+
+#elif defined(CONFIG_BADGE23_HW_GEN_P3) || defined(CONFIG_BADGE23_HW_GEN_P4) || defined(CONFIG_BADGE23_HW_GEN_P6)
+
+#define USER_WARNINGS_ENABLED
+
+static int8_t spio_badge_link_set(uint8_t pin_mask, bool state){
+    if(state) {
+        if((pin_mask & BADGE_LINK_LINE_OUT_RING) || (pin_mask & BADGE_LINK_LINE_OUT_TIP)){
+            if(!audio_headphones_are_connected()) {
+                pin_mask &= ~BADGE_LINK_LINE_OUT_RING;
+                pin_mask &= ~BADGE_LINK_LINE_OUT_TIP;
+#ifdef USER_WARNINGS_ENABLED
+                printf("cannot enable line out badge link without cable plugged in for safety reasons\n");
+            } else {
+                printf("badge link enabled on line out. please make sure not to have headphones or sound sources connected before transmitting data.\n");
+#endif
+            }
+        }
+    }
+
+    if(pin_mask & BADGE_LINK_LINE_IN_RING) max7321s_set_pinmode_output(BADGE_LINK_LINE_IN_RING_ENABLE_PIN, 1);
+    if(pin_mask & BADGE_LINK_LINE_IN_TIP) max7321s_set_pinmode_output(BADGE_LINK_LINE_IN_TIP_ENABLE_PIN, 1);
+    if(pin_mask & BADGE_LINK_LINE_OUT_RING) max7321s_set_pinmode_output(BADGE_LINK_LINE_OUT_RING_ENABLE_PIN, 1);
+    if(pin_mask & BADGE_LINK_LINE_OUT_TIP) max7321s_set_pinmode_output(BADGE_LINK_LINE_OUT_TIP_ENABLE_PIN, 1);
+
+    if(pin_mask & BADGE_LINK_LINE_IN_RING) max7321s_set_pin(BADGE_LINK_LINE_IN_RING_ENABLE_PIN, !state);
+    if(pin_mask & BADGE_LINK_LINE_IN_TIP) max7321s_set_pin(BADGE_LINK_LINE_IN_TIP_ENABLE_PIN, !state);
+    if(pin_mask & BADGE_LINK_LINE_OUT_RING) max7321s_set_pin(BADGE_LINK_LINE_OUT_RING_ENABLE_PIN, !state);
+    if(pin_mask & BADGE_LINK_LINE_OUT_TIP) max7321s_set_pin(BADGE_LINK_LINE_OUT_TIP_ENABLE_PIN, !state);
+
+    max7321s_update();
+
+    badge_link_enabled = (badge_link_enabled & (~pin_mask)) | (pin_mask & (state ? 255: 0));
+    return spio_badge_link_get_active(pin_mask);
+}
+#endif
+
+uint8_t spio_badge_link_disable(uint8_t pin_mask){
+    return spio_badge_link_set(pin_mask, 0);
+}
+
+uint8_t spio_badge_link_enable(uint8_t pin_mask){
+    return spio_badge_link_set(pin_mask, 1);
+}
diff --git a/usermodule/micropython.cmake b/usermodule/micropython.cmake
index 94c79a75f7..2912379039 100644
--- a/usermodule/micropython.cmake
+++ b/usermodule/micropython.cmake
@@ -7,6 +7,7 @@ add_library(usermod_badge23 INTERFACE)
 target_sources(usermod_badge23 INTERFACE
     ${CMAKE_CURRENT_LIST_DIR}/mp_hardware.c
     ${CMAKE_CURRENT_LIST_DIR}/mp_audio.c
+    ${CMAKE_CURRENT_LIST_DIR}/mp_badge_link.c
     ${CMAKE_CURRENT_LIST_DIR}/mp_synth.c
     ${CMAKE_CURRENT_LIST_DIR}/mp_kernel.c
 )
diff --git a/usermodule/mp_badge_link.c b/usermodule/mp_badge_link.c
new file mode 100644
index 0000000000..cfb8fb4104
--- /dev/null
+++ b/usermodule/mp_badge_link.c
@@ -0,0 +1,64 @@
+// probably doesn't need all of these idk
+#include <stdio.h>
+#include <string.h>
+
+#include "py/runtime.h"
+#include "py/mphal.h"
+#include "mphalport.h"
+#include "modmachine.h"
+#include "extmod/virtpin.h"
+#include "machine_rtc.h"
+#include "py/builtin.h"
+#include "py/runtime.h"
+
+#include "badge23/audio.h"
+#include "badge23/leds.h"
+#include "badge23/captouch.h"
+#include "badge23/display.h"
+#include "badge23/spio.h"
+#include "badge23/espan.h"
+#include "badge23_hwconfig.h"
+
+mp_obj_t mp_ctx_from_ctx(Ctx *ctx);
+
+STATIC mp_obj_t mp_get_active(mp_obj_t pin_mask) {
+    return mp_obj_new_int(spio_badge_link_get_active(mp_obj_get_int(pin_mask)));
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_get_active_obj, mp_get_active);
+
+STATIC mp_obj_t mp_enable(mp_obj_t pin_mask) {
+    return mp_obj_new_int(spio_badge_link_enable(mp_obj_get_int(pin_mask)));
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_enable_obj, mp_enable);
+
+STATIC mp_obj_t mp_disable(mp_obj_t pin_mask) {
+    return mp_obj_new_int(spio_badge_link_disable(mp_obj_get_int(pin_mask)));
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_disable_obj, mp_disable);
+
+STATIC const mp_rom_map_elem_t mp_module_badge_link_globals_table[] = {
+    { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_badge_link) },
+    { MP_ROM_QSTR(MP_QSTR_get_active), MP_ROM_PTR(&mp_get_active_obj) },
+    { MP_ROM_QSTR(MP_QSTR_enable), MP_ROM_PTR(&mp_enable_obj) },
+    { MP_ROM_QSTR(MP_QSTR_disable), MP_ROM_PTR(&mp_disable_obj) },
+
+    { MP_ROM_QSTR(MP_QSTR_BADGE_LINK_LINE_IN_TIP), MP_ROM_INT(BADGE_LINK_LINE_IN_TIP) },
+    { MP_ROM_QSTR(MP_QSTR_BADGE_LINK_LINE_IN_RING), MP_ROM_INT(BADGE_LINK_LINE_IN_RING) },
+    { MP_ROM_QSTR(MP_QSTR_BADGE_LINK_LINE_OUT_TIP), MP_ROM_INT(BADGE_LINK_LINE_OUT_TIP) },
+    { MP_ROM_QSTR(MP_QSTR_BADGE_LINK_LINE_OUT_RING), MP_ROM_INT(BADGE_LINK_LINE_OUT_RING) },
+    { MP_ROM_QSTR(MP_QSTR_BADGE_LINK_ALL), MP_ROM_INT(BADGE_LINK_ALL) },
+    { MP_ROM_QSTR(MP_QSTR_BADGE_LINK_LINE_IN_TIP_PIN), MP_ROM_INT(BADGE_LINK_LINE_IN_TIP_PIN) },
+    { MP_ROM_QSTR(MP_QSTR_BADGE_LINK_LINE_IN_RING_PIN), MP_ROM_INT(BADGE_LINK_LINE_IN_RING_PIN) },
+    { MP_ROM_QSTR(MP_QSTR_BADGE_LINK_LINE_OUT_TIP_PIN), MP_ROM_INT(BADGE_LINK_LINE_OUT_TIP_PIN) },
+    { MP_ROM_QSTR(MP_QSTR_BADGE_LINK_LINE_OUT_RING_PIN), MP_ROM_INT(BADGE_LINK_LINE_OUT_RING_PIN) },
+};
+
+STATIC MP_DEFINE_CONST_DICT(mp_module_badge_link_globals, mp_module_badge_link_globals_table);
+
+const mp_obj_module_t mp_module_badge_link = {
+    .base = { &mp_type_module },
+    .globals = (mp_obj_dict_t *)&mp_module_badge_link_globals,
+};
+
+MP_REGISTER_MODULE(MP_QSTR_badge_link, mp_module_badge_link);
+
-- 
GitLab