Files
wadamesh/variants/attaky_mesh_series/AttakyMeshSeriesKeyboard.cpp
T
Attaky b67f559d15 Attaky mesh series (#158)
* attaky_mesh_series: add board variant with FT6636 touch and landscape UI

* attaky_mesh_series: P3 battery (MAX17048) + GPS + shared-I2C mutex

- AttakySharedI2C: one Wire.begin() + a mutex serializing the shared board
  I2C bus (SDA=2/SCL=1) across the FT6636 touch poll (core 0), the MAX17048
  fuel gauge (core 1), and the future AW9523 keyboard. Arduino's TwoWire
  buffers are not concurrency-safe.
- board.begin() owns the authoritative bus init; the touch driver now takes
  the lock around every transaction instead of its own Wire.begin().
- getBattMilliVolts(): real MAX17048 VCELL read (78.125 uV/LSB -> mV = raw*5/64),
  2 s cache + lock, replacing the 4000 mV stub. Upstream maps mV -> % itself.
  Hardware-verified ~4058 mV.
- GPS: rename the dead GPS_BAUD flag to GPS_BAUD_RATE (the core reads that; 9600
  only worked via the #else default). Pins verified on hardware: ESP RX = IO17
  receives ATGM336H NMEA; MeshCore's module-centric PIN_GPS_TX=17 maps correctly.

* attaky_mesh_series: Control_Keyboard_(Bottom) dual-AW9523 keyboard

Add the Control_Keyboard_(Bottom)_1.0 module as an ADDITIVE hardware
keyboard on the touch board. Two AW9523 expanders (0x5A left / 0x5B
right) each scan a 5x5 matrix over the shared board I2C bus, serialized
through the AttakySharedI2C mutex alongside the FT6636 touch poll and the
MAX17048 fuel gauge.

The driver emits final bytes (printable ASCII, backspace, enter, space)
with momentary Shift resolved to uppercase in-driver. UITask injects them
into the focused textarea exactly like the web-mirror keystroke path, so
CAP_KEYBOARD stays 0 and the on-screen keyboard is not suppressed (touch
typing does not regress). Focus-gated: the matrices are only scanned
while an editable field is bound, so a key has zero side effect when
nothing is focused.

Hardware-verified, both halves: q/w/e/r/t (0x5A) + y (0x5B), Shift
uppercase, backspace, space, enter; on-screen keyboard intact.

* attaky_mesh_series: use reliable 115200 upload speed

* attaky_mesh_series: use core default GPS baud

* attaky_mesh_series: restore GPS_BAUD setting

* attaky_mesh_series: rename PlatformIO environment
2026-07-19 13:58:05 +02:00

159 lines
4.5 KiB
C++

// SPDX-License-Identifier: GPL-3.0-or-later
#include "AttakyMeshSeriesKeyboard.h"
#if defined(HAS_ATTAKY_MESH_KEYBOARD) && defined(ESP32)
#include <Arduino.h>
#include <Wire.h>
#include "AttakySharedI2C.h"
static const uint8_t AW_REG_IN_P0 = 0x00;
static const uint8_t AW_REG_OUT_P1 = 0x03;
static const uint8_t AW_REG_CFG_P0 = 0x04;
static const uint8_t AW_REG_CFG_P1 = 0x05;
static const uint8_t AW_REG_OUT_P0 = 0x02;
static const uint8_t AW_REG_MODE_P0 = 0x12;
static const uint8_t AW_REG_MODE_P1 = 0x13;
static const uint8_t AW_REG_SWRST = 0x7F;
static const uint8_t KB_ADDR[2] = { 0x5A, 0x5B };
static const uint8_t P1_IDLE = 0xE0;
static const uint8_t ROW_DRIVE[5] = { 0xFE, 0xFD, 0xFB, 0xF7, 0xEF };
#define K_NOP 0x00
#define K_SFT 0x01
#define K_BSP 0x08
#define K_TAB 0x09
#define K_ENT 0x0D
#define K_SPC 0x20
static const uint8_t KEYMAP_L[5][5] = {
{ '1', '2', '3', '4', '5' },
{ 'q', 'w', 'e', 'r', 't' },
{ 'a', 's', 'd', 'f', 'g' },
{ K_SFT, 'z', 'x', 'c', 'v' },
{ K_NOP, K_TAB, K_NOP, ',', K_SPC },
};
static const uint8_t KEYMAP_R[5][5] = {
{ '6', '7', '8', '9', '0' },
{ 'y', 'u', 'i', 'o', 'p' },
{ 'h', 'j', 'k', 'l', K_BSP },
{ 'b', 'n', 'm', K_NOP, K_ENT },
{ K_NOP, '.', K_NOP, '#', K_NOP },
};
static const uint8_t SHIFT_HALF = 0, SHIFT_ROW = 3, SHIFT_COL = 0;
static bool s_inited = false;
static bool s_present[2] = { false, false };
static uint8_t s_prev[2][5] = { { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF },
{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } };
static volatile uint8_t s_ring[16];
static volatile uint8_t s_head = 0, s_tail = 0;
static void ringPush(uint8_t b) {
uint8_t nh = (uint8_t)((s_head + 1) & 15);
if (nh != s_tail) { s_ring[s_head] = b; s_head = nh; }
}
static void ringClear() { s_head = s_tail = 0; }
static bool awWrite(uint8_t addr, uint8_t reg, uint8_t val) {
Wire.beginTransmission(addr);
Wire.write(reg);
Wire.write(val);
return Wire.endTransmission() == 0;
}
static uint8_t awRead(uint8_t addr, uint8_t reg) {
Wire.beginTransmission(addr);
Wire.write(reg);
if (Wire.endTransmission(false) != 0) return 0xFF;
if (Wire.requestFrom((int)addr, 1) != 1) return 0xFF;
return (uint8_t)Wire.read();
}
static bool initHalf(uint8_t addr) {
bool ok = true;
ok &= awWrite(addr, AW_REG_SWRST, 0x00);
delay(2);
ok &= awWrite(addr, AW_REG_MODE_P0, 0xFF);
ok &= awWrite(addr, AW_REG_MODE_P1, 0xFF);
ok &= awWrite(addr, AW_REG_CFG_P0, 0xFF);
ok &= awWrite(addr, AW_REG_CFG_P1, 0x00);
ok &= awWrite(addr, AW_REG_OUT_P0, 0x00);
ok &= awWrite(addr, AW_REG_OUT_P1, P1_IDLE);
return ok;
}
static void ensureInit() {
if (s_inited) return;
s_inited = true;
for (int h = 0; h < 2; h++) {
if (!attakyI2cLock(30)) continue;
s_present[h] = initHalf(KB_ADDR[h]);
attakyI2cUnlock();
}
}
static void scanHalf(uint8_t addr, uint8_t cols[5]) {
for (uint8_t row = 0; row < 5; row++) {
awWrite(addr, AW_REG_OUT_P1, (uint8_t)(ROW_DRIVE[row] | 0xE0));
delayMicroseconds(50);
cols[row] = (uint8_t)(awRead(addr, AW_REG_IN_P0) & 0x1F);
}
awWrite(addr, AW_REG_OUT_P1, P1_IDLE);
}
void attakyKeyboardPoll(bool active) {
ensureInit();
if (!active) {
for (int h = 0; h < 2; h++)
for (int r = 0; r < 5; r++) s_prev[h][r] = 0xFF;
ringClear();
return;
}
uint8_t cur[2][5] = { { 0x1F,0x1F,0x1F,0x1F,0x1F }, { 0x1F,0x1F,0x1F,0x1F,0x1F } };
for (int h = 0; h < 2; h++) {
if (!s_present[h]) continue;
if (!attakyI2cLock(20)) continue;
scanHalf(KB_ADDR[h], cur[h]);
attakyI2cUnlock();
}
const bool shift_down =
s_present[SHIFT_HALF] && !(cur[SHIFT_HALF][SHIFT_ROW] & (1u << SHIFT_COL));
for (int h = 0; h < 2; h++) {
if (!s_present[h]) continue;
for (int row = 0; row < 5; row++) {
uint8_t now_bits = cur[h][row];
uint8_t prev_bits = s_prev[h][row];
for (int col = 0; col < 5; col++) {
const uint8_t mask = (uint8_t)(1u << col);
const bool down_now = !(now_bits & mask);
const bool down_prev = !(prev_bits & mask);
if (down_now && !down_prev) {
uint8_t k = (h == 0) ? KEYMAP_L[row][col] : KEYMAP_R[row][col];
if (k == K_NOP || k == K_SFT) continue;
if (shift_down && k >= 'a' && k <= 'z') k = (uint8_t)(k - 0x20);
ringPush(k);
}
}
s_prev[h][row] = now_bits;
}
}
}
int attakyKeyboardReadKey() {
if (s_tail == s_head) return 0;
uint8_t b = s_ring[s_tail];
s_tail = (uint8_t)((s_tail + 1) & 15);
return b;
}
bool attakyKeyboardPresent() { return s_present[0] || s_present[1]; }
#endif