You've already forked M5Cardputer-UserDemo
mirror of
https://github.com/m5stack/M5Cardputer-UserDemo.git
synced 2026-05-20 11:03:59 -07:00
2fdbcd0566
When using the Keyboard BLE feature, pressing ALT-TAB to cycle through windows was not possible. This is now fixed. OPT-L (for screen lock) also works. (Previously ALT and OPT/META keys never set _modifier_mask, so they were sent as raw scan codes (0xe2/0xe3) in HID keycode slots instead of setting the modifier byte. The USB sender now skips keycode for modifier-only events.) (On non-modifier key release, both BLE and USB senders sent an all-zero HID report, dropping any held modifiers (e.g. ALT released on TAB-up). Release now always sends getModifierMask() in the modifier byte so held modifiers stay active -- enables ALT-TAB window cycling.) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
802 lines
23 KiB
C++
802 lines
23 KiB
C++
/*
|
|
* SPDX-FileCopyrightText: 2025 M5Stack Technology CO LTD
|
|
*
|
|
* SPDX-License-Identifier: MIT
|
|
*/
|
|
#include "hal.h"
|
|
#include "hal_config.h"
|
|
#include <apps/utils/audio/audio.h>
|
|
#include <mooncake_log.h>
|
|
#include <M5Unified.hpp>
|
|
#include <esp_mac.h>
|
|
#include <memory>
|
|
|
|
static std::unique_ptr<Hal> _hal_instance;
|
|
static const std::string _tag = "HAL";
|
|
|
|
Hal& GetHAL()
|
|
{
|
|
if (!_hal_instance) {
|
|
mclog::tagInfo(_tag, "creating hal instance");
|
|
_hal_instance = std::make_unique<Hal>();
|
|
}
|
|
return *_hal_instance.get();
|
|
}
|
|
|
|
void Hal::init()
|
|
{
|
|
mclog::tagInfo(_tag, "init");
|
|
|
|
M5.begin();
|
|
M5.Display.setBrightness(0);
|
|
M5.Speaker.begin(); // Codec takes some time to initialize
|
|
|
|
display_init();
|
|
i2c_scan();
|
|
keyboard_init();
|
|
setting_init();
|
|
spi_init();
|
|
}
|
|
|
|
void Hal::update()
|
|
{
|
|
M5.update();
|
|
keyboard.update();
|
|
capLora868.update();
|
|
}
|
|
|
|
void Hal::feedTheDog()
|
|
{
|
|
vTaskDelay(1);
|
|
}
|
|
|
|
std::vector<uint8_t> Hal::getDeviceMac()
|
|
{
|
|
std::vector<uint8_t> mac(6);
|
|
esp_read_mac(mac.data(), ESP_MAC_EFUSE_FACTORY);
|
|
return mac;
|
|
}
|
|
|
|
std::string Hal::getDeviceMacString()
|
|
{
|
|
auto mac = getDeviceMac();
|
|
return fmt::format("{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* Dispplay */
|
|
/* -------------------------------------------------------------------------- */
|
|
void Hal::display_init()
|
|
{
|
|
mclog::tagInfo(_tag, "display init");
|
|
|
|
canvas.createSprite(204, 109);
|
|
canvasKeyboardBar.createSprite(display.width() - canvas.width(), display.height());
|
|
canvasSystemBar.createSprite(canvas.width(), display.height() - canvas.height());
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* I2C */
|
|
/* -------------------------------------------------------------------------- */
|
|
void Hal::i2c_scan()
|
|
{
|
|
mclog::tagInfo(_tag, "i2c scan");
|
|
|
|
bool ret[128] = {false};
|
|
M5.In_I2C.scanID(ret);
|
|
|
|
uint8_t address;
|
|
printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n");
|
|
for (int i = 0; i < 128; i += 16) {
|
|
printf("%02x: ", i);
|
|
for (int j = 0; j < 16; j++) {
|
|
fflush(stdout);
|
|
address = i + j;
|
|
if (ret[address]) {
|
|
printf("%02x ", address);
|
|
} else {
|
|
printf("-- ");
|
|
}
|
|
}
|
|
printf("\r\n");
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* Settings */
|
|
/* -------------------------------------------------------------------------- */
|
|
// https://github.com/78/xiaozhi-esp32/blob/main/main/main.cc
|
|
|
|
void Hal::setting_init()
|
|
{
|
|
mclog::tagInfo(_tag, "setting init");
|
|
|
|
esp_err_t ret = nvs_flash_init();
|
|
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
|
mclog::tagWarn(_tag, "erasing NVS flash to fix corruption");
|
|
ESP_ERROR_CHECK(nvs_flash_erase());
|
|
ret = nvs_flash_init();
|
|
}
|
|
ESP_ERROR_CHECK(ret);
|
|
|
|
_settings = new Settings("cardputer", true);
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* Keyboard */
|
|
/* -------------------------------------------------------------------------- */
|
|
void Hal::keyboard_init()
|
|
{
|
|
mclog::tagInfo(_tag, "keyboard init");
|
|
|
|
if (!keyboard.init()) {
|
|
mclog::tagError(_tag, "keyboard init failed");
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* WiFI */
|
|
/* -------------------------------------------------------------------------- */
|
|
// https://github.com/espressif/esp-idf/blob/v5.3.3/examples/wifi/scan/main/scan.c
|
|
// https://github.com/espressif/esp-idf/blob/v5.4.2/examples/wifi/getting_started/station/main/station_example_main.c
|
|
// http://github.com/espressif/esp-idf/blob/v5.4.2/examples/protocols/sntp/main/sntp_example_main.c
|
|
#include <esp_wifi.h>
|
|
#include <nvs.h>
|
|
#include <nvs_flash.h>
|
|
#include <esp_netif.h>
|
|
#include <esp_err.h>
|
|
#include <esp_system.h>
|
|
#include <esp_event.h>
|
|
#include <lwip/err.h>
|
|
#include <lwip/sys.h>
|
|
#include <vector>
|
|
#include <time.h>
|
|
#include <sys/time.h>
|
|
#include <esp_sntp.h>
|
|
|
|
#define DEFAULT_SCAN_LIST_SIZE 6
|
|
static wifi_ap_record_t _ap_info[DEFAULT_SCAN_LIST_SIZE];
|
|
|
|
void Hal::wifiScan(std::vector<ScanResult_t>& scanResult)
|
|
{
|
|
mclog::tagInfo(_tag, "wifi scan");
|
|
|
|
scanResult.clear();
|
|
|
|
uint16_t number = DEFAULT_SCAN_LIST_SIZE;
|
|
uint16_t ap_count = 0;
|
|
memset(_ap_info, 0, sizeof(_ap_info));
|
|
|
|
// Start WiFi scan
|
|
esp_err_t ret = esp_wifi_scan_start(NULL, true);
|
|
if (ret != ESP_OK) {
|
|
mclog::tagError(_tag, "failed to start wifi scan: {}", esp_err_to_name(ret));
|
|
return;
|
|
}
|
|
|
|
ret = esp_wifi_scan_get_ap_num(&ap_count);
|
|
if (ret != ESP_OK) {
|
|
mclog::tagError(_tag, "failed to get AP number: {}", esp_err_to_name(ret));
|
|
return;
|
|
}
|
|
|
|
ret = esp_wifi_scan_get_ap_records(&number, _ap_info);
|
|
if (ret != ESP_OK) {
|
|
mclog::tagError(_tag, "failed to get AP records: {}", esp_err_to_name(ret));
|
|
return;
|
|
}
|
|
|
|
// Process scan results
|
|
for (int i = 0; i < number; i++) {
|
|
std::string ssid = (char*)_ap_info[i].ssid;
|
|
int rssi = _ap_info[i].rssi;
|
|
|
|
// Skip empty SSID
|
|
if (ssid.empty()) {
|
|
continue;
|
|
}
|
|
|
|
// Add to ap_list
|
|
scanResult.push_back(std::make_pair(rssi, ssid));
|
|
}
|
|
|
|
// Sort ap_list by RSSI (from strongest to weakest)
|
|
std::sort(scanResult.begin(), scanResult.end(),
|
|
[](const std::pair<int, std::string>& a, const std::pair<int, std::string>& b) {
|
|
return a.first > b.first; // Higher RSSI first
|
|
});
|
|
|
|
mclog::tagInfo(_tag, "wifi scan completed, found {} APs", scanResult.size());
|
|
}
|
|
|
|
static EventGroupHandle_t s_wifi_event_group = NULL;
|
|
static const int WIFI_CONNECTED_BIT = BIT0;
|
|
static const int WIFI_DISCONNECTED_BIT = BIT1;
|
|
static const int WIFI_FAIL_BIT = BIT2;
|
|
static const int WIFI_STARTED_BIT = BIT3;
|
|
|
|
static void wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
|
|
{
|
|
const char* TAG = "wifi";
|
|
|
|
// Wifi started
|
|
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
|
|
xEventGroupSetBits(s_wifi_event_group, WIFI_STARTED_BIT);
|
|
}
|
|
|
|
// Disconnected
|
|
else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
|
|
xEventGroupSetBits(s_wifi_event_group, WIFI_DISCONNECTED_BIT);
|
|
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
|
|
}
|
|
|
|
// Connected
|
|
else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
|
|
ip_event_got_ip_t* event = (ip_event_got_ip_t*)event_data;
|
|
ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
|
|
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
|
|
}
|
|
}
|
|
|
|
void Hal::wifiInit()
|
|
{
|
|
mclog::tagInfo(_tag, "wifi init");
|
|
|
|
if (_is_wifi_inited) {
|
|
return;
|
|
}
|
|
|
|
ESP_ERROR_CHECK(nvs_flash_init());
|
|
ESP_ERROR_CHECK(esp_netif_init());
|
|
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
|
esp_netif_t* sta_netif = esp_netif_create_default_wifi_sta();
|
|
assert(sta_netif);
|
|
|
|
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
|
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
|
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
|
|
|
|
if (!s_wifi_event_group) {
|
|
s_wifi_event_group = xEventGroupCreate();
|
|
}
|
|
|
|
ESP_ERROR_CHECK(
|
|
esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, nullptr, nullptr));
|
|
ESP_ERROR_CHECK(
|
|
esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, nullptr, nullptr));
|
|
|
|
ESP_ERROR_CHECK(esp_wifi_start());
|
|
_is_wifi_inited = true;
|
|
}
|
|
|
|
void Hal::wifiDeinit()
|
|
{
|
|
mclog::tagInfo(_tag, "wifi deinit");
|
|
|
|
if (!_is_wifi_inited) {
|
|
return;
|
|
}
|
|
|
|
esp_wifi_stop();
|
|
esp_wifi_deinit();
|
|
_is_wifi_inited = false;
|
|
}
|
|
|
|
bool Hal::wifiConnect(const std::string& ssid, const std::string& password)
|
|
{
|
|
mclog::tagInfo(_tag, "wifi connect to ssid: {} password: {}", ssid, password);
|
|
|
|
if (!_is_wifi_inited) {
|
|
wifiInit();
|
|
}
|
|
|
|
wifiDisconnect();
|
|
|
|
// Hold until wifi started
|
|
xEventGroupWaitBits(s_wifi_event_group, WIFI_STARTED_BIT, pdFALSE, pdFALSE, pdMS_TO_TICKS(3000));
|
|
|
|
// Reset event status
|
|
xEventGroupClearBits(s_wifi_event_group, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT | WIFI_DISCONNECTED_BIT);
|
|
|
|
// Set Wi-Fi config
|
|
wifi_config_t wifi_config = {};
|
|
strncpy((char*)wifi_config.sta.ssid, ssid.c_str(), sizeof(wifi_config.sta.ssid));
|
|
strncpy((char*)wifi_config.sta.password, password.c_str(), sizeof(wifi_config.sta.password));
|
|
|
|
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
|
|
ESP_ERROR_CHECK(esp_wifi_connect());
|
|
|
|
// Wait for connection result
|
|
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdTRUE, pdFALSE,
|
|
pdMS_TO_TICKS(10000));
|
|
|
|
if (bits & WIFI_CONNECTED_BIT) {
|
|
mclog::tagInfo(_tag, "connected to SSID: {}", ssid);
|
|
_is_wifi_connected = true;
|
|
start_sntp();
|
|
return true;
|
|
} else if (bits & WIFI_FAIL_BIT) {
|
|
mclog::tagError(_tag, "failed to connect to SSID: {}", ssid);
|
|
return false;
|
|
} else {
|
|
mclog::tagError(_tag, "wifi connect timeout");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void Hal::wifiDisconnect()
|
|
{
|
|
mclog::tagInfo(_tag, "wifi disconnect");
|
|
|
|
if (!_is_wifi_inited) {
|
|
return;
|
|
}
|
|
|
|
if (!_is_wifi_connected) {
|
|
return;
|
|
}
|
|
|
|
// Hold until wifi started
|
|
xEventGroupWaitBits(s_wifi_event_group, WIFI_STARTED_BIT, pdFALSE, pdFALSE, pdMS_TO_TICKS(3000));
|
|
|
|
// Disconnect old connection
|
|
ESP_ERROR_CHECK(esp_wifi_disconnect());
|
|
|
|
// Wait for disconnect result
|
|
xEventGroupWaitBits(s_wifi_event_group, WIFI_DISCONNECTED_BIT, pdFALSE, pdFALSE, pdMS_TO_TICKS(5000));
|
|
|
|
// Reset event status
|
|
xEventGroupClearBits(s_wifi_event_group, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT | WIFI_DISCONNECTED_BIT);
|
|
|
|
stop_sntp();
|
|
|
|
_is_wifi_connected = false;
|
|
}
|
|
|
|
void Hal::start_sntp()
|
|
{
|
|
mclog::tagInfo(_tag, "start sntp");
|
|
|
|
if (!_is_wifi_connected) {
|
|
mclog::tagError(_tag, "wifi not connected");
|
|
return;
|
|
}
|
|
|
|
// Set timezone to UTC+8
|
|
setenv("TZ", "CST-8", 1);
|
|
tzset();
|
|
|
|
esp_sntp_setoperatingmode(SNTP_OPMODE_POLL);
|
|
esp_sntp_setservername(0, "pool.ntp.org");
|
|
esp_sntp_init();
|
|
}
|
|
|
|
void Hal::stop_sntp()
|
|
{
|
|
mclog::tagInfo(_tag, "stop sntp");
|
|
|
|
if (!_is_wifi_connected) {
|
|
mclog::tagError(_tag, "wifi not connected");
|
|
return;
|
|
}
|
|
|
|
esp_sntp_stop();
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* EspNow */
|
|
/* -------------------------------------------------------------------------- */
|
|
// https://github.com/espressif/esp-now/blob/master/examples/get-started/main/app_main.c
|
|
#include <esp_mac.h>
|
|
#include <espnow.h>
|
|
#include <espnow_storage.h>
|
|
#include <espnow_utils.h>
|
|
#include <esp_check.h>
|
|
|
|
static std::string _espnow_received_data;
|
|
static esp_err_t _handle_espnow_received(uint8_t* src_addr, void* data, size_t size, wifi_pkt_rx_ctrl_t* rx_ctrl)
|
|
{
|
|
const char* TAG = "espnow";
|
|
|
|
ESP_PARAM_CHECK(src_addr);
|
|
ESP_PARAM_CHECK(data);
|
|
ESP_PARAM_CHECK(size);
|
|
ESP_PARAM_CHECK(rx_ctrl);
|
|
|
|
static uint32_t count = 0;
|
|
|
|
ESP_LOGI(TAG, "espnow_recv, <%" PRIu32 "> [" MACSTR "][%d][%d][%u]: %.*s", count++, MAC2STR(src_addr),
|
|
rx_ctrl->channel, rx_ctrl->rssi, size, size, (char*)data);
|
|
|
|
_espnow_received_data = std::string((char*)data, size);
|
|
|
|
return ESP_OK;
|
|
}
|
|
|
|
void Hal::espNowInit()
|
|
{
|
|
mclog::tagInfo(_tag, "esp now init");
|
|
|
|
if (!_is_wifi_inited) {
|
|
wifiInit();
|
|
}
|
|
|
|
if (_is_wifi_connected) {
|
|
wifiDisconnect();
|
|
espNowDeinit();
|
|
}
|
|
|
|
if (_is_esp_now_inited) {
|
|
mclog::tagInfo(_tag, "esp now already inited");
|
|
return;
|
|
}
|
|
|
|
// espnow_storage_init();
|
|
|
|
espnow_config_t espnow_config = ESPNOW_INIT_CONFIG_DEFAULT();
|
|
espnow_init(&espnow_config);
|
|
espnow_set_config_for_data_type(ESPNOW_DATA_TYPE_DATA, true, _handle_espnow_received);
|
|
|
|
_is_esp_now_inited = true;
|
|
}
|
|
|
|
void Hal::espNowDeinit()
|
|
{
|
|
mclog::tagInfo(_tag, "esp now deinit");
|
|
|
|
if (!_is_esp_now_inited) {
|
|
mclog::tagInfo(_tag, "esp now not inited");
|
|
return;
|
|
}
|
|
|
|
espnow_deinit();
|
|
|
|
_is_esp_now_inited = false;
|
|
}
|
|
|
|
void Hal::espNowSend(const std::string& data)
|
|
{
|
|
mclog::tagInfo(_tag, "esp now send: {}", data);
|
|
|
|
if (!_is_esp_now_inited) {
|
|
mclog::tagError(_tag, "esp now not inited");
|
|
return;
|
|
}
|
|
|
|
espnow_frame_head_t frame_head = ESPNOW_FRAME_CONFIG_DEFAULT();
|
|
auto ret = espnow_send(ESPNOW_DATA_TYPE_DATA, ESPNOW_ADDR_BROADCAST, data.c_str(), data.size(), &frame_head,
|
|
portMAX_DELAY);
|
|
|
|
if (ret != ESP_OK) {
|
|
mclog::tagError(_tag, "failed to send esp now: {}", esp_err_to_name(ret));
|
|
}
|
|
}
|
|
|
|
bool Hal::espNowAvailable()
|
|
{
|
|
return _espnow_received_data.size() > 0;
|
|
}
|
|
|
|
const std::string& Hal::espNowGetReceivedData()
|
|
{
|
|
return _espnow_received_data;
|
|
}
|
|
|
|
void Hal::espNowClearReceivedData()
|
|
{
|
|
_espnow_received_data.clear();
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* IR */
|
|
/* -------------------------------------------------------------------------- */
|
|
#include "utils/ir_nec/ir_helper.h"
|
|
|
|
void Hal::irInit()
|
|
{
|
|
mclog::tagInfo(_tag, "ir init");
|
|
|
|
if (_is_ir_inited) {
|
|
mclog::tagInfo(_tag, "ir already inited");
|
|
return;
|
|
}
|
|
|
|
ir_helper_init((gpio_num_t)HAL_PIN_IR_TX);
|
|
|
|
_is_ir_inited = true;
|
|
}
|
|
|
|
void Hal::irSend(uint8_t addr, uint8_t cmd)
|
|
{
|
|
mclog::tagInfo(_tag, "ir send: addr: {:02X}, cmd: {:02X}", addr, cmd);
|
|
|
|
if (!_is_ir_inited) {
|
|
mclog::tagError(_tag, "ir not inited");
|
|
return;
|
|
}
|
|
|
|
ir_helper_send(addr, cmd);
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* BLE */
|
|
/* -------------------------------------------------------------------------- */
|
|
#include "utils/ble_hid_device/ble_hid_device_helper.h"
|
|
|
|
void Hal::bleKeyboardInit()
|
|
{
|
|
if (_is_ble_keyboard_inited) {
|
|
mclog::tagWarn(_tag, "ble keyboard already initialized");
|
|
return;
|
|
}
|
|
|
|
mclog::tagInfo(_tag, "ble keyboard init");
|
|
|
|
// Initialize BLE HID device
|
|
ble_hid_device_helper_init();
|
|
|
|
// Register keyboard event callback to automatically forward keys
|
|
_ble_keyboard_event_slot_id = keyboard.onKeyEvent.connect(
|
|
[this](const Keyboard::KeyEvent_t& keyEvent) { handle_ble_keyboard_event(keyEvent); });
|
|
|
|
_is_ble_keyboard_inited = true;
|
|
mclog::tagInfo(_tag, "ble keyboard init done, auto-forwarding enabled");
|
|
}
|
|
|
|
bool Hal::bleKeyboardIsConnected() const
|
|
{
|
|
if (!_is_ble_keyboard_inited) {
|
|
return false;
|
|
}
|
|
|
|
auto state = ble_hid_device_helper_get_state();
|
|
return (state == BLE_HID_DEVICE_STATE_CONNECTED);
|
|
}
|
|
|
|
void Hal::handle_ble_keyboard_event(const Keyboard::KeyEvent_t& keyEvent)
|
|
{
|
|
// Only forward if BLE keyboard is connected
|
|
if (!bleKeyboardIsConnected()) {
|
|
return;
|
|
}
|
|
|
|
// Create HID buffer (8 bytes: modifier, reserved, keycode1-6)
|
|
uint8_t buffer[8] = {0};
|
|
|
|
// Handle key press/release
|
|
if (keyEvent.state) {
|
|
// Get current modifier state from keyboard
|
|
uint8_t modifierMask = keyboard.getModifierMask();
|
|
|
|
// Set modifier byte (physical modifiers + any firmware-injected ones, e.g. Fn+alpha -> LSHIFT)
|
|
buffer[0] = modifierMask | keyEvent.extraModifiers;
|
|
|
|
// For modifier keys themselves, don't set keycode
|
|
if (keyEvent.isModifier) {
|
|
buffer[2] = 0; // No keycode for pure modifier keys
|
|
} else {
|
|
buffer[2] = keyEvent.keyCode;
|
|
}
|
|
|
|
// Send key press
|
|
ble_hid_device_helper_send(buffer);
|
|
mclog::tagDebug(_tag, "ble keyboard sent key: {} (code: {}, modifier: {:08b})",
|
|
keyEvent.keyName ? keyEvent.keyName : "special", (int)keyEvent.keyCode, modifierMask);
|
|
} else {
|
|
// Key released - always preserve current modifier state so held modifiers (e.g. ALT) stay active
|
|
buffer[0] = keyboard.getModifierMask();
|
|
buffer[2] = 0;
|
|
ble_hid_device_helper_send(buffer);
|
|
mclog::tagDebug(_tag, "ble keyboard key released (modifier: {:08b})", buffer[0]);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* USB */
|
|
/* -------------------------------------------------------------------------- */
|
|
// https://github.com/espressif/esp-idf/blob/v5.4.2/examples/peripherals/usb/device/tusb_hid
|
|
#include "utils/tusb_hid_device/tusb_hid_device_helper.h"
|
|
|
|
void Hal::usbKeyboardInit()
|
|
{
|
|
if (_is_usb_keyboard_inited) {
|
|
mclog::tagWarn(_tag, "usb keyboard already initialized");
|
|
return;
|
|
}
|
|
|
|
mclog::tagInfo(_tag, "usb keyboard init");
|
|
|
|
delay(200);
|
|
|
|
tusb_hid_device_helper_init();
|
|
|
|
_usb_keyboard_event_slot_id = keyboard.onKeyEvent.connect(
|
|
[this](const Keyboard::KeyEvent_t& keyEvent) { handle_usb_keyboard_event(keyEvent); });
|
|
|
|
_is_usb_keyboard_inited = true;
|
|
}
|
|
|
|
bool Hal::usbKeyboardIsConnected() const
|
|
{
|
|
if (!_is_usb_keyboard_inited) {
|
|
return false;
|
|
}
|
|
|
|
return tusb_hid_device_helper_is_mounted();
|
|
}
|
|
|
|
void Hal::handle_usb_keyboard_event(const Keyboard::KeyEvent_t& keyEvent)
|
|
{
|
|
// Only forward if USB keyboard is connected
|
|
if (!usbKeyboardIsConnected()) {
|
|
return;
|
|
}
|
|
|
|
// Handle key press/release
|
|
if (keyEvent.state) {
|
|
uint8_t mod = GetHAL().keyboard.getModifierMask() | keyEvent.extraModifiers;
|
|
if (keyEvent.isModifier) {
|
|
tusb_hid_device_helper_report(mod, NULL);
|
|
} else {
|
|
uint8_t keycode[6] = {keyEvent.keyCode};
|
|
tusb_hid_device_helper_report(mod, keycode);
|
|
}
|
|
mclog::tagDebug(_tag, "usb keyboard sent key: {} (code: {}, modifier: {:08b})",
|
|
keyEvent.keyName ? keyEvent.keyName : "special", (int)keyEvent.keyCode, mod);
|
|
} else {
|
|
tusb_hid_device_helper_report(GetHAL().keyboard.getModifierMask(), NULL);
|
|
mclog::tagDebug(_tag, "usb keyboard key released");
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* SPI */
|
|
/* -------------------------------------------------------------------------- */
|
|
#include <driver/spi_master.h>
|
|
#include <driver/sdspi_host.h>
|
|
#include <driver/sdmmc_host.h>
|
|
|
|
static bool _spi_bus_initialized = false;
|
|
|
|
void Hal::spi_init()
|
|
{
|
|
mclog::tagInfo(_tag, "spi init");
|
|
|
|
esp_err_t ret;
|
|
|
|
// spi_host_device_t host_id = SPI2_HOST;
|
|
sdmmc_host_t host = SDSPI_HOST_DEFAULT();
|
|
|
|
spi_bus_config_t bus_cfg = {
|
|
.mosi_io_num = HAL_PIN_SPI_MOSI,
|
|
.miso_io_num = HAL_PIN_SPI_MISO,
|
|
.sclk_io_num = HAL_PIN_SPI_SCLK,
|
|
.quadwp_io_num = -1,
|
|
.quadhd_io_num = -1,
|
|
.max_transfer_sz = 4000,
|
|
};
|
|
|
|
// Initialize SPI bus only if not already initialized
|
|
if (!_spi_bus_initialized) {
|
|
ret = spi_bus_initialize((spi_host_device_t)host.slot, &bus_cfg, SDSPI_DEFAULT_DMA);
|
|
if (ret != ESP_OK) {
|
|
mclog::tagError(_tag, "failed to initialize SPI bus");
|
|
return;
|
|
}
|
|
_spi_bus_initialized = true;
|
|
mclog::tagInfo(_tag, "spi bus initialized");
|
|
} else {
|
|
mclog::tagWarn(_tag, "spi bus already initialized, reusing");
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* SD Card */
|
|
/* -------------------------------------------------------------------------- */
|
|
// https://github.com/espressif/esp-idf/blob/v5.3.3/examples/storage/sd_card/sdspi
|
|
// https://github.com/m5stack/M5PaperS3-UserDemo/blob/main/main/hal/hal.h
|
|
#include <driver/spi_master.h>
|
|
#include <driver/sdspi_host.h>
|
|
#include <driver/sdmmc_host.h>
|
|
#include <sdmmc_cmd.h>
|
|
#include <esp_vfs_fat.h>
|
|
|
|
#define MOUNT_POINT "/sdcard"
|
|
|
|
static sdmmc_card_t* _sd_card = nullptr;
|
|
|
|
void Hal::sd_card_init()
|
|
{
|
|
mclog::tagInfo(_tag, "sd card init");
|
|
|
|
if (!_spi_bus_initialized) {
|
|
spi_init();
|
|
}
|
|
|
|
// If already mounted successfully, return
|
|
if (_is_sd_card_mounted) {
|
|
mclog::tagInfo(_tag, "sd card already mounted");
|
|
return;
|
|
}
|
|
|
|
esp_err_t ret;
|
|
|
|
sdmmc_host_t host = SDSPI_HOST_DEFAULT();
|
|
|
|
// Options for mounting the filesystem
|
|
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
|
|
.format_if_mount_failed = false, .max_files = 5, .allocation_unit_size = 16 * 1024};
|
|
|
|
const char mount_point[] = MOUNT_POINT;
|
|
mclog::tagInfo(_tag, "initializing SD card");
|
|
|
|
// Initialize SD card slot
|
|
sdspi_device_config_t slot_config = SDSPI_DEVICE_CONFIG_DEFAULT();
|
|
slot_config.gpio_cs = HAL_PIN_SD_CARD_CS;
|
|
slot_config.host_id = (spi_host_device_t)host.slot;
|
|
|
|
mclog::tagInfo(_tag, "mounting filesystem");
|
|
ret = esp_vfs_fat_sdspi_mount(mount_point, &host, &slot_config, &mount_config, &_sd_card);
|
|
|
|
if (ret != ESP_OK) {
|
|
if (ret == ESP_FAIL) {
|
|
mclog::tagError(_tag, "failed to mount filesystem");
|
|
} else {
|
|
mclog::tagError(_tag, "failed to initialize the card, make sure SD card lines have pull-up resistors");
|
|
}
|
|
|
|
// Don't clean up SPI bus on failure - leave it for retry
|
|
mclog::tagInfo(_tag, "sd card init failed, but spi bus remains initialized for retry");
|
|
return;
|
|
}
|
|
|
|
mclog::tagInfo(_tag, "filesystem mounted successfully");
|
|
|
|
sdmmc_card_print_info(stdout, _sd_card);
|
|
|
|
_is_sd_card_mounted = true;
|
|
}
|
|
|
|
Hal::SdCardProbeResult_t Hal::sdCardProbe()
|
|
{
|
|
SdCardProbeResult_t result;
|
|
|
|
if (!_is_sd_card_mounted) {
|
|
sd_card_init();
|
|
if (!_is_sd_card_mounted) {
|
|
result.is_mounted = false;
|
|
result.size = "Not Found";
|
|
return result;
|
|
}
|
|
}
|
|
|
|
result.is_mounted = true;
|
|
|
|
// Try write to sd card
|
|
FILE* fp = fopen(MOUNT_POINT "/test.txt", "w");
|
|
if (fp) {
|
|
fwrite("Hello, World!", 1, 13, fp);
|
|
fclose(fp);
|
|
|
|
result.size =
|
|
fmt::format("Size: {:.1f} GB",
|
|
((float)((uint64_t)_sd_card->csd.capacity) * _sd_card->csd.sector_size) / (1024 * 1024 * 1024));
|
|
} else {
|
|
result.size = "Write Failed";
|
|
}
|
|
|
|
result.type = "Type: ";
|
|
if (_sd_card->is_sdio) {
|
|
result.type += "SDIO";
|
|
} else if (_sd_card->is_mmc) {
|
|
result.type += "MMC";
|
|
} else {
|
|
result.type += (_sd_card->ocr & (1 << 30)) ? "SDHC/SDXC" : "SDSC";
|
|
}
|
|
|
|
result.name = fmt::format("Name: {}", std::string(_sd_card->cid.name));
|
|
|
|
return result;
|
|
}
|