Skip to content

Commit

Permalink
Added: ble, keymaps
Browse files Browse the repository at this point in the history
  • Loading branch information
65787978 committed Aug 26, 2024
1 parent 4c21b8f commit 394dc8f
Show file tree
Hide file tree
Showing 6 changed files with 737 additions and 193 deletions.
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,12 @@ embassy = ["esp-idf-svc/embassy-sync", "esp-idf-svc/critical-section", "esp-idf-

[dependencies]
log = { version = "0.4", default-features = false }
esp-idf-svc = { version = "0.49", default-features = false }
esp-idf-svc = { version = "0.49", default-features = false, features = ["alloc", "embassy-sync"] }
esp-idf-hal = "0.44.1"
chrono = "0.4.38"
esp32-nimble = "0.7.0"
anyhow = "1"

[build-dependencies]
anyhow = "1"
embuild = "0.32.0"
12 changes: 12 additions & 0 deletions sdkconfig.defaults
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,15 @@ CONFIG_ESP_MAIN_TASK_STACK_SIZE=8000
# Workaround for https://github.com/espressif/esp-idf/issues/7631
#CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=n
#CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=n
CONFIG_LOG_DEFAULT_LEVEL=5

CONFIG_BT_ENABLED=y
CONFIG_BT_BLE_ENABLED=y
CONFIG_BT_BLUEDROID_ENABLED=n
CONFIG_BT_NIMBLE_ENABLED=y
CONFIG_BT_NIMBLE_NVS_PERSIST=y
# CONFIG_BT_NIMBLE_EXT_ADV=y

CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n
CONFIG_BTDM_CTRL_MODE_BTDM=n
317 changes: 317 additions & 0 deletions src/ble_keyboard.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,317 @@
// originally: https://github.com/T-vK/ESP32-BLE-Keyboard
#![allow(dead_code)]

use esp32_nimble::{
enums::*, hid::*, utilities::mutex::Mutex, BLEAdvertisementData, BLECharacteristic, BLEDevice,
BLEHIDDevice, BLEServer,
};
use std::sync::Arc;

const KEYBOARD_ID: u8 = 0x01;
const MEDIA_KEYS_ID: u8 = 0x02;

const HID_REPORT_DISCRIPTOR: &[u8] = hid!(
(USAGE_PAGE, 0x01), // USAGE_PAGE (Generic Desktop Ctrls)
(USAGE, 0x06), // USAGE (Keyboard)
(COLLECTION, 0x01), // COLLECTION (Application)
// ------------------------------------------------- Keyboard
(REPORT_ID, KEYBOARD_ID), // REPORT_ID (1)
(USAGE_PAGE, 0x07), // USAGE_PAGE (Kbrd/Keypad)
(USAGE_MINIMUM, 0xE0), // USAGE_MINIMUM (0xE0)
(USAGE_MAXIMUM, 0xE7), // USAGE_MAXIMUM (0xE7)
(LOGICAL_MINIMUM, 0x00), // LOGICAL_MINIMUM (0)
(LOGICAL_MAXIMUM, 0x01), // Logical Maximum (1)
(REPORT_SIZE, 0x01), // REPORT_SIZE (1)
(REPORT_COUNT, 0x08), // REPORT_COUNT (8)
(HIDINPUT, 0x02), // INPUT (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
(REPORT_COUNT, 0x01), // REPORT_COUNT (1) ; 1 byte (Reserved)
(REPORT_SIZE, 0x08), // REPORT_SIZE (8)
(HIDINPUT, 0x01), // INPUT (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
(REPORT_COUNT, 0x05), // REPORT_COUNT (5) ; 5 bits (Num lock, Caps lock, Scroll lock, Compose, Kana)
(REPORT_SIZE, 0x01), // REPORT_SIZE (1)
(USAGE_PAGE, 0x08), // USAGE_PAGE (LEDs)
(USAGE_MINIMUM, 0x01), // USAGE_MINIMUM (0x01) ; Num Lock
(USAGE_MAXIMUM, 0x05), // USAGE_MAXIMUM (0x05) ; Kana
(HIDOUTPUT, 0x02), // OUTPUT (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
(REPORT_COUNT, 0x01), // REPORT_COUNT (1) ; 3 bits (Padding)
(REPORT_SIZE, 0x03), // REPORT_SIZE (3)
(HIDOUTPUT, 0x01), // OUTPUT (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
(REPORT_COUNT, 0x06), // REPORT_COUNT (6) ; 6 bytes (Keys)
(REPORT_SIZE, 0x08), // REPORT_SIZE(8)
(LOGICAL_MINIMUM, 0x00), // LOGICAL_MINIMUM(0)
(LOGICAL_MAXIMUM, 0x65), // LOGICAL_MAXIMUM(0x65) ; 101 keys
(USAGE_PAGE, 0x07), // USAGE_PAGE (Kbrd/Keypad)
(USAGE_MINIMUM, 0x00), // USAGE_MINIMUM (0)
(USAGE_MAXIMUM, 0x65), // USAGE_MAXIMUM (0x65)
(HIDINPUT, 0x00), // INPUT (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
(END_COLLECTION), // END_COLLECTION
// ------------------------------------------------- Media Keys
(USAGE_PAGE, 0x0C), // USAGE_PAGE (Consumer)
(USAGE, 0x01), // USAGE (Consumer Control)
(COLLECTION, 0x01), // COLLECTION (Application)
(REPORT_ID, MEDIA_KEYS_ID), // REPORT_ID (3)
(USAGE_PAGE, 0x0C), // USAGE_PAGE (Consumer)
(LOGICAL_MINIMUM, 0x00), // LOGICAL_MINIMUM (0)
(LOGICAL_MAXIMUM, 0x01), // LOGICAL_MAXIMUM (1)
(REPORT_SIZE, 0x01), // REPORT_SIZE (1)
(REPORT_COUNT, 0x10), // REPORT_COUNT (16)
(USAGE, 0xB5), // USAGE (Scan Next Track) ; bit 0: 1
(USAGE, 0xB6), // USAGE (Scan Previous Track) ; bit 1: 2
(USAGE, 0xB7), // USAGE (Stop) ; bit 2: 4
(USAGE, 0xCD), // USAGE (Play/Pause) ; bit 3: 8
(USAGE, 0xE2), // USAGE (Mute) ; bit 4: 16
(USAGE, 0xE9), // USAGE (Volume Increment) ; bit 5: 32
(USAGE, 0xEA), // USAGE (Volume Decrement) ; bit 6: 64
(USAGE, 0x23, 0x02), // Usage (WWW Home) ; bit 7: 128
(USAGE, 0x94, 0x01), // Usage (My Computer) ; bit 0: 1
(USAGE, 0x92, 0x01), // Usage (Calculator) ; bit 1: 2
(USAGE, 0x2A, 0x02), // Usage (WWW fav) ; bit 2: 4
(USAGE, 0x21, 0x02), // Usage (WWW search) ; bit 3: 8
(USAGE, 0x26, 0x02), // Usage (WWW stop) ; bit 4: 16
(USAGE, 0x24, 0x02), // Usage (WWW back) ; bit 5: 32
(USAGE, 0x83, 0x01), // Usage (Media sel) ; bit 6: 64
(USAGE, 0x8A, 0x01), // Usage (Mail) ; bit 7: 128
(HIDINPUT, 0x02), // INPUT (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
(END_COLLECTION), // END_COLLECTION
);

pub const SHIFT: u8 = 0x80;
pub const ASCII_MAP: &[u8] = &[
0x00, // NUL
0x00, // SOH
0x00, // STX
0x00, // ETX
0x00, // EOT
0x00, // ENQ
0x00, // ACK
0x00, // BEL
0x2a, // BS Backspace
0x2b, // TAB Tab
0x28, // LF Enter
0x00, // VT
0x00, // FF
0x00, // CR
0x00, // SO
0x00, // SI
0x00, // DEL
0x00, // DC1
0x00, // DC2
0x00, // DC3
0x00, // DC4
0x00, // NAK
0x00, // SYN
0x00, // ETB
0x00, // CAN
0x00, // EM
0x00, // SUB
0x00, // ESC
0x00, // FS
0x00, // GS
0x00, // RS
0x00, // US
0x2c, // ' '
0x1e | SHIFT, // !
0x34 | SHIFT, // "
0x20 | SHIFT, // #
0x21 | SHIFT, // $
0x22 | SHIFT, // %
0x24 | SHIFT, // &
0x34, // '
0x26 | SHIFT, // (
0x27 | SHIFT, // )
0x25 | SHIFT, // *
0x2e | SHIFT, // +
0x36, // ,
0x2d, // -
0x37, // .
0x38, // /
0x27, // 0
0x1e, // 1
0x1f, // 2
0x20, // 3
0x21, // 4
0x22, // 5
0x23, // 6
0x24, // 7
0x25, // 8
0x26, // 9
0x33 | SHIFT, // :
0x33, // ;
0x36 | SHIFT, // <
0x2e, // =
0x37 | SHIFT, // >
0x38 | SHIFT, // ?
0x1f | SHIFT, // @
0x04 | SHIFT, // A
0x05 | SHIFT, // B
0x06 | SHIFT, // C
0x07 | SHIFT, // D
0x08 | SHIFT, // E
0x09 | SHIFT, // F
0x0a | SHIFT, // G
0x0b | SHIFT, // H
0x0c | SHIFT, // I
0x0d | SHIFT, // J
0x0e | SHIFT, // K
0x0f | SHIFT, // L
0x10 | SHIFT, // M
0x11 | SHIFT, // N
0x12 | SHIFT, // O
0x13 | SHIFT, // P
0x14 | SHIFT, // Q
0x15 | SHIFT, // R
0x16 | SHIFT, // S
0x17 | SHIFT, // T
0x18 | SHIFT, // U
0x19 | SHIFT, // V
0x1a | SHIFT, // W
0x1b | SHIFT, // X
0x1c | SHIFT, // Y
0x1d | SHIFT, // Z
0x2f, // [
0x31, // bslash
0x30, // ]
0x23 | SHIFT, // ^
0x2d | SHIFT, // _
0x35, // `
0x04, // a
0x05, // b
0x06, // c
0x07, // d
0x08, // e
0x09, // f
0x0a, // g
0x0b, // h
0x0c, // i
0x0d, // j
0x0e, // k
0x0f, // l
0x10, // m
0x11, // n
0x12, // o
0x13, // p
0x14, // q
0x15, // r
0x16, // s
0x17, // t
0x18, // u
0x19, // v
0x1a, // w
0x1b, // x
0x1c, // y
0x1d, // z
0x2f | SHIFT, // {
0x31 | SHIFT, // |
0x30 | SHIFT, // }
0x35 | SHIFT, // ~
0, // DEL
];

#[repr(packed)]
struct KeyReport {
modifiers: u8,
reserved: u8,
keys: [u8; 6],
}

pub struct Keyboard {
server: &'static mut BLEServer,
input_keyboard: Arc<Mutex<BLECharacteristic>>,
output_keyboard: Arc<Mutex<BLECharacteristic>>,
input_media_keys: Arc<Mutex<BLECharacteristic>>,
key_report: KeyReport,
}

impl Keyboard {
pub fn new() -> anyhow::Result<Self> {
let device = BLEDevice::take();
device
.security()
.set_auth(AuthReq::all())
.set_io_cap(SecurityIOCap::NoInputNoOutput)
.resolve_rpa();

let server = device.get_server();
let mut hid = BLEHIDDevice::new(server);

let input_keyboard = hid.input_report(KEYBOARD_ID);
let output_keyboard = hid.output_report(KEYBOARD_ID);
let input_media_keys = hid.input_report(MEDIA_KEYS_ID);

hid.manufacturer("Espressif");
hid.pnp(0x02, 0x05ac, 0x820a, 0x0210);
hid.hid_info(0x00, 0x01);

hid.report_map(HID_REPORT_DISCRIPTOR);

hid.set_battery_level(100);

let ble_advertising = device.get_advertising();
ble_advertising.lock().scan_response(false).set_data(
BLEAdvertisementData::new()
.name("ESP32 Keyboard")
.appearance(0x03C1)
.add_service_uuid(hid.hid_service().lock().uuid()),
)?;
ble_advertising.lock().start()?;

Ok(Self {
server,
input_keyboard,
output_keyboard,
input_media_keys,
key_report: KeyReport {
modifiers: 0,
reserved: 0,
keys: [0; 6],
},
})
}

pub fn connected(&self) -> bool {
self.server.connected_count() > 0
}

pub fn write(&mut self, str: &str) {
for char in str.as_bytes() {
self.press(*char);
self.release();
}
}

pub fn press(&mut self, char: u8) {
let mut key = ASCII_MAP[char as usize];
if (key & SHIFT) > 0 {
self.key_report.modifiers |= 0x02;
key &= !SHIFT;
}
self.key_report.keys[0] = key;
self.send_report(&self.key_report);
}

pub fn release(&mut self) {
self.key_report.modifiers = 0;
self.key_report.keys.fill(0);
self.send_report(&self.key_report);
}

fn send_report(&self, keys: &KeyReport) {
self.input_keyboard.lock().set_from(keys).notify();
esp_idf_svc::hal::delay::Ets::delay_ms(7);
}
}

// fn main() -> anyhow::Result<()> {
// esp_idf_svc::sys::link_patches();
// esp_idf_svc::log::EspLogger::initialize_default();

// let mut keyboard = Keyboard::new()?;

// loop {
// if keyboard.connected() {
// ::log::info!("Sending 'Hello world'...");
// keyboard.write("Hello world\n");
// }
// esp_idf_svc::hal::delay::FreeRtos::delay_ms(5000);
// }
// }
Loading

0 comments on commit 394dc8f

Please sign in to comment.