New variant variants/m5stack_cardputer_adv for the Cardputer-Adv (ESP32-S3, 8 MB) with the Cap LoRa-1262 (SX1262 + GNSS): - Envs: companion_radio_ble, companion_radio_usb, repeater, room_server. - Board: enables the Cap's antenna path via the PI4IOE5V6408 on the internal I2C bus (Wire1, G8/G9); Wire stays on the Grove port. LoRa and microSD share FSPI because the display owns SPI3. TCXO 1.8 V. - Companion UI adapted from MultiMote/meshcore-cardputer-adv (MIT, LICENSE.MultiMote), running on the standard companion so the USB terminal CLI stays available. Contacts/identity stay in internal SPIFFS; the microSD card is optional (chat history, keyboard layouts). - Sticky Shift, one-shot Fn with Fn+letter shortcuts, top button (G0) sends a flood advert, brightness control. - Text size setting (Normal/Large/Extra large) with DejaVu Sans fonts generated by tools/gen_vlw_font.py; layouts derive from the font size. - Power save maps to the companion's powersaving pref; the UI vetoes light sleep while the screen is on. Low-battery power-off at 3.4 V. - win_long_cmdline.py: Windows-only -iprefix include shortening so the ESP32 compile command stays under the 32,767-character limit. Shared companion changes are guarded by CARDPUTER_ADV_UI: main.cpp selects CardputerDataStore/CardputerUITask/CardputerMesh, and MyMesh.h maps the_mesh to the subclass like the PSRAM storage path does. Tested on hardware (910.525 MHz mesh): radio RX/TX, USB CLI, BLE, UI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
209 lines
6.7 KiB
C++
209 lines
6.7 KiB
C++
#include "CardputerLayout.h"
|
|
|
|
using namespace Keyboard::Layout;
|
|
|
|
static LayoutLayer default_layout_layers[2] = {
|
|
{ (Keyboard::Layout::LAYER_MASK_BASE),
|
|
{ "`", "", "", "", "1", "q", "", "", "2", "w", "a", "", "3", "e", "s", "z", "4", "r", "d",
|
|
"x", "5", "t", "f", "c", "6", "y", "g", "v", "7", "u", "h", "b", "8", "i", "j", "n", "9", "o",
|
|
"k", "m", "0", "p", "l", ",", "-", "[", ";", ".", "=", "]", "'", "/", "", "\\", "", "" } },
|
|
{ (Keyboard::Layout::LAYER_MASK_SHIFT),
|
|
{ "~", "", "", "", "!", "Q", "", "", "@", "W", "A", "", "#", "E", "S", "Z", "$", "R", "D",
|
|
"X", "%", "T", "F", "C", "^", "Y", "G", "V", "&", "U", "H", "B", "*", "I", "J", "N", "(", "O",
|
|
"K", "M", ")", "P", "L", "<", "_", "{", ":", ">", "+", "}", "\"", "?", "", "|", "", "" } }
|
|
};
|
|
|
|
static LayoutData default_layout = { .language_code = "en",
|
|
.layers_count = 2,
|
|
.layers = default_layout_layers };
|
|
|
|
uint8_t CardputerLayout::getLayerMask(Keyboard::Modifiers modifiers) const {
|
|
return (modifiers.fn ? LAYER_MASK_FN : 0) | (modifiers.ctrl ? LAYER_MASK_CTRL : 0) |
|
|
(modifiers.shift ? LAYER_MASK_SHIFT : 0) | (modifiers.opt ? LAYER_MASK_OPT : 0) |
|
|
(modifiers.alt ? LAYER_MASK_ALT : 0);
|
|
}
|
|
|
|
Keyboard::Layout::CardputerLayout::CardputerLayout()
|
|
: main_layout(&default_layout), current_layout(&default_layout) {}
|
|
|
|
|
|
const char *CardputerLayout::lookup(Keyboard::Event &event, bool force_default) {
|
|
if (event.key == Keyboard::KEY_NONE || event.key > TOTAL_KEYS || !current_layout) {
|
|
return "";
|
|
}
|
|
|
|
const LayoutData *tgt = force_default ? &default_layout : current_layout;
|
|
uint8_t mask = getLayerMask(event.modifiers);
|
|
uint8_t key = event.key - 1;
|
|
|
|
// Try to find an exact match in the current modifier layer
|
|
for (uint8_t i = 0; i < tgt->layers_count; i++) {
|
|
if (mask == tgt->layers[i].modifiers_mask) {
|
|
const char *data = tgt->layers[i].entries[key].data;
|
|
if (data && data[0]) {
|
|
return data;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If Shift is pressed and nothing was found, try the dedicated SHIFT layer
|
|
if (event.modifiers.shift && mask != LAYER_MASK_SHIFT) {
|
|
for (uint8_t i = 0; i < tgt->layers_count; i++) {
|
|
if (LAYER_MASK_SHIFT == tgt->layers[i].modifiers_mask) {
|
|
const char *data = tgt->layers[i].entries[key].data;
|
|
if (data && data[0]) {
|
|
return data;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fallback to the BASE layer if we haven't returned anything yet
|
|
if (mask != LAYER_MASK_BASE) {
|
|
for (uint8_t i = 0; i < tgt->layers_count; i++) {
|
|
if (LAYER_MASK_BASE == tgt->layers[i].modifiers_mask) {
|
|
const char *data = tgt->layers[i].entries[key].data;
|
|
if (data && data[0]) {
|
|
return data;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle space if it not overriden
|
|
if (event.key == Keyboard::KEY_SPACE && !event.modifiers.ctrl) {
|
|
return " ";
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
void CardputerLayout::begin(FS &fs) {
|
|
parseLayout(fs, LAYOUT_TYPE_MAIN);
|
|
parseLayout(fs, LAYOUT_TYPE_ALT);
|
|
}
|
|
|
|
// Uses dynamic allocation
|
|
void CardputerLayout::parseLayout(FS &fs, LayoutType layoutType) {
|
|
const char *filename = (layoutType == LAYOUT_TYPE_MAIN) ? KB_LAYOUT_MAIN_FILE : KB_LAYOUT_ALT_FILE;
|
|
if (!fs.exists(filename)) {
|
|
return;
|
|
}
|
|
|
|
File layout_file = fs.open(filename, FILE_READ, false);
|
|
if (!layout_file) {
|
|
return;
|
|
}
|
|
|
|
LayoutData *layout = new LayoutData();
|
|
layout->language_code[0] = '\0';
|
|
layout->layers_count = 0;
|
|
layout->layers = nullptr;
|
|
|
|
int current_layer_index = -1;
|
|
|
|
while (layout_file.available()) {
|
|
String line = layout_file.readStringUntil('\n');
|
|
if (line.endsWith("\r")) {
|
|
line.remove(line.length() - 1);
|
|
}
|
|
|
|
if (line.length() < 1 || line.charAt(0) == '#') {
|
|
continue;
|
|
}
|
|
|
|
// Lang code (format "N xx")
|
|
if (line.charAt(0) == 'N' && line.length() >= 4) {
|
|
strncpy(layout->language_code, line.c_str() + 2, 2);
|
|
layout->language_code[2] = '\0';
|
|
}
|
|
|
|
// Layer mask (format "L BFCSOA", B = base, F = fn, C = ctrl, S = shift, O = opt, A = alt)
|
|
else if (line.charAt(0) == 'L' && line.length() >= 3) {
|
|
uint8_t mask = 0;
|
|
|
|
for (unsigned int i = 2; i < line.length(); i++) {
|
|
char mod = line.charAt(i);
|
|
if (mod == 'B') {
|
|
mask = LAYER_MASK_BASE;
|
|
} else if (mod == 'F') {
|
|
mask |= LAYER_MASK_FN;
|
|
} else if (mod == 'C') {
|
|
mask |= LAYER_MASK_CTRL;
|
|
} else if (mod == 'S') {
|
|
mask |= LAYER_MASK_SHIFT;
|
|
} else if (mod == 'O') {
|
|
mask |= LAYER_MASK_OPT;
|
|
} else if (mod == 'A') {
|
|
mask |= LAYER_MASK_ALT;
|
|
}
|
|
}
|
|
|
|
// Dynamically expand the layout array for the new layer
|
|
uint8_t new_count = layout->layers_count + 1;
|
|
LayoutLayer *new_layers = new LayoutLayer[new_count];
|
|
|
|
memset(new_layers, 0, sizeof(LayoutLayer) * new_count);
|
|
|
|
if (layout->layers != nullptr) {
|
|
// Copy old array contents into the expanded array space
|
|
memcpy(new_layers, layout->layers, sizeof(LayoutLayer) * layout->layers_count);
|
|
delete[] layout->layers; // Clear old buffer
|
|
}
|
|
|
|
// Setup new layer
|
|
new_layers[new_count - 1].modifiers_mask = mask;
|
|
layout->layers = new_layers;
|
|
layout->layers_count = new_count;
|
|
current_layer_index = new_count - 1;
|
|
}
|
|
|
|
// Key replacements (format "K NN=u8char")
|
|
else if (line.charAt(0) == 'K' && line.length() >= 5 && line.charAt(4) == '=' &&
|
|
current_layer_index != -1) {
|
|
String id_str = line.substring(2, 4);
|
|
int key_id = id_str.toInt();
|
|
|
|
if (key_id >= 1 && key_id <= TOTAL_KEYS) {
|
|
int key_idx = key_id - 1;
|
|
String val_str = line.substring(5);
|
|
|
|
char *dest = layout->layers[current_layer_index].entries[key_idx].data;
|
|
memset(dest, 0, KB_LAYOUT_CHAR_MAX + 1);
|
|
strncpy(dest, val_str.c_str(), KB_LAYOUT_CHAR_MAX);
|
|
}
|
|
}
|
|
}
|
|
layout_file.close();
|
|
|
|
if (layout->layers_count == 0) {
|
|
delete layout;
|
|
return;
|
|
}
|
|
|
|
if (layoutType == LAYOUT_TYPE_MAIN) {
|
|
if (main_layout && main_layout != &default_layout) {
|
|
delete[] main_layout->layers;
|
|
delete main_layout;
|
|
}
|
|
main_layout = layout;
|
|
current_layout = main_layout;
|
|
} else {
|
|
if (alternate_layout) {
|
|
delete[] alternate_layout->layers;
|
|
delete alternate_layout;
|
|
}
|
|
alternate_layout = layout;
|
|
}
|
|
}
|
|
|
|
void Keyboard::Layout::CardputerLayout::switchLayout() {
|
|
LayoutType new_type = (current_layout_type == LAYOUT_TYPE_MAIN) ? LAYOUT_TYPE_ALT : LAYOUT_TYPE_MAIN;
|
|
LayoutData *new_layout = (new_type == LAYOUT_TYPE_MAIN) ? main_layout : alternate_layout;
|
|
|
|
if (new_layout) {
|
|
current_layout_type = new_type;
|
|
current_layout = new_layout;
|
|
}
|
|
};
|