Fixes and new apps (#30)

- M5 Unit Modules library + M5 Unit Test app
- Minor fixes for TodoList, TwoEleven, Snake, Brainfuck and Breakout
- Fixed SerialConsole to use the uart controller as it was broken in one of the many updates
- Fixed keyboard input in TwoEleven, Breakout, Magic8Ball and Snake
- Bluetooth Media Keys app, supports pressing physical keys to trigger the corresponding buttonmatrix button
- Epub Reader app
This commit is contained in:
Shadowtrance
2026-06-07 23:56:32 +10:00
committed by GitHub
parent 1f959c8bbf
commit dbf850c434
127 changed files with 11156 additions and 174 deletions
@@ -0,0 +1,95 @@
/*
* UnitCardKB2.h - M5Stack CardKB V2 keyboard unit
*
* Supports two connection modes:
* I2C (addr 0x5F, factory default) - via GROVE port
* UART (115200-8N-1) - via GROVE port after Fn+Sym+2 mode switch
*
* I2C: plain 1-byte read returns ASCII of held key (0 = none).
*
* UART frame: AA [DATA_LEN=0x03] [KEY_ID] [KEY_STATE] [checksum]
* KEY_ID = row*11 + col (0-43, rows 0-3, cols 0-10)
* KEY_STATE = 0x01 pressed, 0x02 released
* checksum = (DATA_LEN + KEY_ID + KEY_STATE) & 0xFF
*
* Special keys (UART translation by this driver; NOT available in I2C mode):
* Esc=0x1B Del=0x08 Enter=0x0A Space=0x20
* Fn+D=0x1E(up) Fn+X=0x1F(down) Fn+Z=0x1D(left) Fn+C=0x1C(right)
*
* I2C mode: Fn+D/X/Z/C and Fn+1 (Esc) produce no output - the firmware only
* pushes regular ASCII into the I2C queue; Fn combos go to BLE HID directly.
* UART mode: Fn combos work; firmware sends raw KEY_ID, driver translates.
* BLE HID mode: USB HID keycodes (ESC=0x29, arrows=0x4F-0x52), handled by
* BluetoothHidHost → LV_KEY_*.
*
* Mode switch: Fn+Sym+1 = I2C, Fn+Sym+2 = UART (persists across reboot)
*/
#pragma once
#include <UnitCommon.h>
#include <tactility/drivers/uart_controller.h>
#include <cstdint>
class UnitCardKB2 {
public:
enum class Mode { I2C, Uart };
static constexpr uint8_t DEFAULT_ADDR = 0x5F;
UnitCardKB2() = default;
~UnitCardKB2() { end(); }
UnitCardKB2(const UnitCardKB2&) = delete;
UnitCardKB2& operator=(const UnitCardKB2&) = delete;
// Pass a I2C controller device
// I2C mode - probe and initialise. Returns false if not present.
[[nodiscard]] bool begin(Device* dev, uint8_t addr = DEFAULT_ADDR);
// Pass a UART controller device
// UART mode - set_config then open uart_controller.
// Returns false if the device is null or open fails.
[[nodiscard]] bool beginUart(Device* dev);
void end();
bool isPresent() const { return mode_ == Mode::I2C ? i2cDev_ != nullptr : uartDev_ != nullptr; }
// Only meaningful when isPresent() returns true.
// Call end() before switching modes (begin→beginUart or vice versa).
Mode mode() const { return mode_; }
// Returns the ASCII value of the currently/last pressed key, or 0 if none.
// In UART mode: drains available bytes from the UART RX buffer, parses frames,
// returns the ASCII of the most recently pressed key (releases are ignored).
// In I2C mode: behaviour unchanged from before.
char getKey();
// Returns true if a key is currently held (I2C mode only; always false in UART mode).
bool hasKey();
private:
// I2C
Device* i2cDev_ = nullptr;
uint8_t addr_ = DEFAULT_ADDR;
char cachedKey_ = 0;
// UART
Device* uartDev_ = nullptr;
// Frame parser state
enum class FrameState : uint8_t { WaitAA, WaitLen, WaitId, WaitState, WaitCsum };
FrameState frameState_ = FrameState::WaitAA;
uint8_t frameId_ = 0;
uint8_t frameKs_ = 0;
// UART modifier state (tracked from key events)
bool capsLock_ = false;
bool symMode_ = false;
bool fnHeld_ = false;
bool oneShiftPending_ = false; // Aa single-tap one-shot uppercase
uint32_t lastAATimestamp_ = 0; // ms timestamp of the Aa press that set oneShiftPending_
Mode mode_ = Mode::I2C;
char readFromI2C();
char pollUart();
};