Bluetooth Master HID and musical keyboard example (#2195)
Adds BluetoothHIDMaster and HIDKeyStream which let the PicoW connect to and use Bluetooth Classic HID devices like keyboards and mice. An example that lets the PicoW use a BT keyboard as a piano is included and shows the use of the new classes.
This commit is contained in:
parent
f786583986
commit
f6d13d2b70
6 changed files with 1031 additions and 1 deletions
|
|
@ -0,0 +1,209 @@
|
|||
// KeyboardPiano example - Released to the public domain in 2024 by Earle F. Philhower, III
|
||||
//
|
||||
// Demonstrates using the BluetoothHIDMaster class to use a Bluetooth keyboard as a
|
||||
// piano keyboard on the PicoW
|
||||
//
|
||||
// Hook up a phono plug to GP0 and GP1 (and GND of course...the 1st 3 pins on the PCB)
|
||||
// Connect wired earbuds up and connect over BT from your keyboard and play some music.
|
||||
|
||||
|
||||
#include <BluetoothHIDMaster.h>
|
||||
#include <PWMAudio.h>
|
||||
|
||||
// We need the inverse map, borrow from the Keyboard library
|
||||
#include <HID_Keyboard.h>
|
||||
extern const uint8_t KeyboardLayout_en_US[128];
|
||||
|
||||
BluetoothHIDMaster hid;
|
||||
PWMAudio pwm;
|
||||
HIDKeyStream keystream;
|
||||
|
||||
|
||||
int16_t sine[1000]; // One complete precalculated sine wave for oscillator use
|
||||
void precalculateSine() {
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
sine[i] = (int16_t)(2000.0 * sin(i * 2 * 3.14159 / 1000.0)); // Only make amplitude ~1/16 max so we can sum up w/o clipping
|
||||
}
|
||||
}
|
||||
|
||||
// Simple variable frequency resampling oscillator state
|
||||
typedef struct {
|
||||
uint32_t key; // Identifier of which key started this tone
|
||||
uint32_t pos; // Current sine offset
|
||||
uint32_t step; // Delta in fixed point 16p16 format
|
||||
} Oscillator;
|
||||
Oscillator osc[6]; // Look, ma! 6-note polyphony!
|
||||
|
||||
// Quiet down, now!
|
||||
void silenceOscillators() {
|
||||
noInterrupts();
|
||||
for (int i = 0; i < 6; i++) {
|
||||
osc[i].pos = 0;
|
||||
osc[i].step = 0;
|
||||
}
|
||||
interrupts();
|
||||
}
|
||||
|
||||
// PWM callback, generates sum of online oscillators
|
||||
void fill() {
|
||||
int num_samples = pwm.availableForWrite() / 2;
|
||||
int16_t buff[32 * 2];
|
||||
|
||||
while (num_samples > 63) {
|
||||
// Run in 32 LR sample chunks for speed, less loop overhead
|
||||
for (int o = 0; o < 32; o++) {
|
||||
int32_t sum = 0;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (osc[i].step) {
|
||||
sum += sine[osc[i].pos >> 16];
|
||||
osc[i].pos += osc[i].step;
|
||||
while (osc[i].pos >= 1000 << 16) {
|
||||
osc[i].pos -= 1000 << 16;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sum > 32767) {
|
||||
sum = 32767;
|
||||
} else if (sum < -32767) {
|
||||
sum = -32767;
|
||||
}
|
||||
buff[o * 2] = (int16_t) sum;
|
||||
buff[o * 2 + 1] = (int16_t) sum;
|
||||
}
|
||||
pwm.write((const uint8_t *)buff, sizeof(buff));
|
||||
num_samples -= 64;
|
||||
}
|
||||
}
|
||||
|
||||
// Mouse callbacks. Could keep track of global mouse position, update a cursor, etc.
|
||||
void mm(void *cbdata, int dx, int dy, int dw) {
|
||||
(void) cbdata;
|
||||
Serial.printf("Mouse: X:%d Y:%d Wheel:%d\n", dx, dy, dw);
|
||||
}
|
||||
|
||||
// Buttons are sent separately from movement
|
||||
void mb(void *cbdata, int butt, bool down) {
|
||||
(void) cbdata;
|
||||
Serial.printf("Mouse: Button %d %s\n", butt, down ? "DOWN" : "UP");
|
||||
}
|
||||
|
||||
// Convert a hertz floating point into a step fixed point 16p16
|
||||
inline uint32_t stepForHz(float hz) {
|
||||
const float stepHz = 1000.0 / 44100.0;
|
||||
const float step = hz * stepHz;
|
||||
return (uint32_t)(step * 65536.0);
|
||||
}
|
||||
|
||||
uint32_t keyStepMap[128]; // The frequency of any raw HID key
|
||||
void setupKeyStepMap() {
|
||||
for (int i = 0; i < 128; i++) {
|
||||
keyStepMap[i] = 0;
|
||||
}
|
||||
// Implements the "standard" PC keyboard to piano setup
|
||||
// https://ux.stackexchange.com/questions/46669/mapping-piano-keys-to-computer-keyboard
|
||||
keyStepMap[KeyboardLayout_en_US['a']] = stepForHz(261.6256);
|
||||
keyStepMap[KeyboardLayout_en_US['w']] = stepForHz(277.1826);
|
||||
keyStepMap[KeyboardLayout_en_US['s']] = stepForHz(293.6648);
|
||||
keyStepMap[KeyboardLayout_en_US['e']] = stepForHz(311.1270);
|
||||
keyStepMap[KeyboardLayout_en_US['d']] = stepForHz(329.6276);
|
||||
keyStepMap[KeyboardLayout_en_US['f']] = stepForHz(349.2282);
|
||||
keyStepMap[KeyboardLayout_en_US['t']] = stepForHz(369.9944);
|
||||
keyStepMap[KeyboardLayout_en_US['g']] = stepForHz(391.9954);
|
||||
keyStepMap[KeyboardLayout_en_US['y']] = stepForHz(415.3047);
|
||||
keyStepMap[KeyboardLayout_en_US['h']] = stepForHz(440.0000);
|
||||
keyStepMap[KeyboardLayout_en_US['u']] = stepForHz(466.1638);
|
||||
keyStepMap[KeyboardLayout_en_US['j']] = stepForHz(493.8833);
|
||||
keyStepMap[KeyboardLayout_en_US['k']] = stepForHz(523.2511);
|
||||
keyStepMap[KeyboardLayout_en_US['o']] = stepForHz(554.3653);
|
||||
keyStepMap[KeyboardLayout_en_US['l']] = stepForHz(587.3295);
|
||||
keyStepMap[KeyboardLayout_en_US['p']] = stepForHz(622.2540);
|
||||
keyStepMap[KeyboardLayout_en_US[';']] = stepForHz(659.2551);
|
||||
keyStepMap[KeyboardLayout_en_US['\'']] = stepForHz(698.4565);
|
||||
}
|
||||
|
||||
// We get make/break for every key which lets us hold notes while a key is depressed
|
||||
void kb(void *cbdata, int key) {
|
||||
bool state = (bool)cbdata;
|
||||
if (state && key < 128) {
|
||||
// Starting a new note
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (osc[i].step == 0) {
|
||||
// This one is free
|
||||
osc[i].key = key;
|
||||
osc[i].pos = 0;
|
||||
osc[i].step = keyStepMap[key];
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (osc[i].key == (uint32_t)key) {
|
||||
osc[i].step = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// The HIDKeyStream object converts a key and state into ASCII. HID key IDs do not map 1:1 to ASCII!
|
||||
// Write the key and make/break state, then read 1 ASCII char back out.
|
||||
keystream.write((uint8_t)key);
|
||||
keystream.write((uint8_t)state);
|
||||
Serial.printf("Keyboard: %02x %s = '%c'\n", key, state ? "DOWN" : "UP", state ? keystream.read() : '-');
|
||||
}
|
||||
|
||||
|
||||
// Consumer keys are the special media keys on most modern keyboards (mute/etc.)
|
||||
void ckb(void *cbdata, int key) {
|
||||
bool state = (bool)cbdata;
|
||||
Serial.printf("Consumer: %02x %s\n", key, state ? "DOWN" : "UP");
|
||||
}
|
||||
|
||||
|
||||
void setup() {
|
||||
Serial.begin();
|
||||
delay(3000);
|
||||
|
||||
Serial.printf("Starting HID master, put your device in pairing mode now.\n");
|
||||
|
||||
// Init the sound generator
|
||||
precalculateSine();
|
||||
silenceOscillators();
|
||||
setupKeyStepMap();
|
||||
|
||||
// Setup the HID key to ASCII conversion
|
||||
keystream.begin();
|
||||
|
||||
// Init the PWM audio output
|
||||
pwm.setStereo(true);
|
||||
pwm.setBuffers(16, 64);
|
||||
pwm.onTransmit(fill);
|
||||
pwm.begin(44100);
|
||||
|
||||
// Mouse buttons and movement reported separately
|
||||
hid.onMouseMove(mm);
|
||||
hid.onMouseButton(mb);
|
||||
|
||||
// We can use the cbData as a flag to see if we're making or breaking a key
|
||||
hid.onKeyDown(kb, (void *)true);
|
||||
hid.onKeyUp(kb, (void *)false);
|
||||
|
||||
// Consumer keys are the special function ones like "mute" or "home"
|
||||
hid.onConsumerKeyDown(ckb, (void *)true);
|
||||
hid.onConsumerKeyUp(ckb, (void *)false);
|
||||
|
||||
hid.begin();
|
||||
|
||||
hid.connectKeyboard();
|
||||
// or hid.connectMouse();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (BOOTSEL) {
|
||||
while (BOOTSEL) {
|
||||
delay(1);
|
||||
}
|
||||
hid.disconnect();
|
||||
hid.clearPairing();
|
||||
Serial.printf("Restarting HID master, put your device in pairing mode now.\n");
|
||||
hid.connectKeyboard();
|
||||
}
|
||||
}
|
||||
42
libraries/BluetoothHIDMaster/keywords.txt
Normal file
42
libraries/BluetoothHIDMaster/keywords.txt
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
#######################################
|
||||
# Syntax Coloring Map
|
||||
#######################################
|
||||
|
||||
#######################################
|
||||
# Datatypes (KEYWORD1)
|
||||
#######################################
|
||||
|
||||
BluetoothHIDMaster KEYWORD1
|
||||
HIDKeyStream KEYWORD1
|
||||
|
||||
#######################################
|
||||
# Methods and Functions (KEYWORD2)
|
||||
#######################################
|
||||
begin KEYWORD2
|
||||
end KEYWORD2
|
||||
|
||||
scan KEYWORD2
|
||||
scanAsyncDone KEYWORD2
|
||||
scanAsyncResult KEYWORD2
|
||||
|
||||
connectKeyboard KEYWORD2
|
||||
connectMouse KEYWORD2
|
||||
|
||||
hidConnected KEYWORD2
|
||||
onMouseMove KEYWORD2
|
||||
onMouseButton KEYWORD2
|
||||
onKeyDown KEYWORD2
|
||||
onKeyUp KEYWORD2
|
||||
onConsumerKeyDown KEYWORD2
|
||||
onConsumerKeyUp KEYWORD2
|
||||
|
||||
# BTDeviceInfo
|
||||
deviceClass KEYWORD2
|
||||
address KEYWORD2
|
||||
addressString KEYWORD2
|
||||
rssi KEYWORD2
|
||||
name KEYWORD2
|
||||
|
||||
#######################################
|
||||
# Constants (LITERAL1)
|
||||
#######################################
|
||||
11
libraries/BluetoothHIDMaster/library.properties
Normal file
11
libraries/BluetoothHIDMaster/library.properties
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
name=BluetoothHIDMaster
|
||||
version=1.0
|
||||
author=Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
maintainer=Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
sentence=Classic Bluetooth HID (Keyboard/Mouse/Joystick) master mode
|
||||
paragraph=Classic Bluetooth HID (Keyboard/Mouse/Joystick) master mode
|
||||
category=Communication
|
||||
url=http://github.com/earlephilhower/arduino-pico
|
||||
architectures=rp2040
|
||||
dot_a_linkage=true
|
||||
depends=BluetoothHCI
|
||||
644
libraries/BluetoothHIDMaster/src/BluetoothHIDMaster.cpp
Normal file
644
libraries/BluetoothHIDMaster/src/BluetoothHIDMaster.cpp
Normal file
|
|
@ -0,0 +1,644 @@
|
|||
/*
|
||||
Bluetooth HCI packet handler class
|
||||
|
||||
Copyright (c) 2024 Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
// Based off of the BlueKitchen HID master demo
|
||||
/*
|
||||
Copyright (C) 2023 BlueKitchen GmbH
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
3. Neither the name of the copyright holders nor the names of
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
4. Any redistribution, use, or modification is done solely for
|
||||
personal benefit and not for any commercial purpose or for
|
||||
monetary gain.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
|
||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN
|
||||
GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
||||
AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGE.
|
||||
|
||||
Please inquire about commercial licensing options at
|
||||
contact@bluekitchen-gmbh.com
|
||||
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "btstack.h"
|
||||
#include <list>
|
||||
#include <memory>
|
||||
|
||||
#include <BluetoothLock.h>
|
||||
#include "BluetoothHIDMaster.h"
|
||||
|
||||
#define CCALLBACKNAME _BTHIDCB
|
||||
#include <ctocppcallback.h>
|
||||
|
||||
|
||||
#define PACKETHANDLERCB(class, cbFcn) \
|
||||
(CCALLBACKNAME<void(uint8_t, uint16_t, uint8_t*, uint16_t), __COUNTER__>::func = std::bind(&class::cbFcn, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4), \
|
||||
static_cast<btstack_packet_handler_t>(CCALLBACKNAME<void(uint8_t, uint16_t, uint8_t*, uint16_t), __COUNTER__ - 1>::callback))
|
||||
|
||||
|
||||
void BluetoothHIDMaster::begin() {
|
||||
// Initialize HID Host
|
||||
hid_host_init(_hid_descriptor_storage, sizeof(_hid_descriptor_storage));
|
||||
hid_host_register_packet_handler(PACKETHANDLERCB(BluetoothHIDMaster, hid_packet_handler));
|
||||
|
||||
// Initialize L2CAP
|
||||
l2cap_init();
|
||||
|
||||
// Initialize LE Security Manager. Needed for cross-transport key derivation
|
||||
sm_init();
|
||||
|
||||
// Allow sniff mode requests by HID device and support role switch
|
||||
gap_set_default_link_policy_settings(LM_LINK_POLICY_ENABLE_SNIFF_MODE | LM_LINK_POLICY_ENABLE_ROLE_SWITCH);
|
||||
|
||||
// try to become master on incoming connections
|
||||
hci_set_master_slave_policy(HCI_ROLE_MASTER);
|
||||
// enabled EIR
|
||||
hci_set_inquiry_mode(INQUIRY_MODE_RSSI_AND_EIR);
|
||||
|
||||
_hci.install();
|
||||
|
||||
_running = true;
|
||||
_hci.begin();
|
||||
}
|
||||
|
||||
void BluetoothHIDMaster::end() {
|
||||
BluetoothLock b;
|
||||
_hci.uninstall();
|
||||
_running = false;
|
||||
}
|
||||
|
||||
bool BluetoothHIDMaster::running() {
|
||||
return _hci.running() && _hidConnected;
|
||||
}
|
||||
|
||||
bool BluetoothHIDMaster::hciRunning() {
|
||||
return _hci.running();
|
||||
}
|
||||
|
||||
void BluetoothHIDMaster::onMouseMove(void (*cb)(void *, int, int, int), void *cbData) {
|
||||
_mouseMoveCB = cb;
|
||||
_mouseMoveData = cbData;
|
||||
}
|
||||
|
||||
void BluetoothHIDMaster::onMouseButton(void (*cb)(void *, int, bool), void *cbData) {
|
||||
_mouseButtonCB = cb;
|
||||
_mouseButtonData = cbData;
|
||||
}
|
||||
|
||||
void BluetoothHIDMaster::onKeyDown(void (*cb)(void *, int), void *cbData) {
|
||||
_keyDownCB = cb;
|
||||
_keyDownData = cbData;
|
||||
}
|
||||
|
||||
void BluetoothHIDMaster::onKeyUp(void (*cb)(void *, int), void *cbData) {
|
||||
_keyUpCB = cb;
|
||||
_keyUpData = cbData;
|
||||
}
|
||||
|
||||
void BluetoothHIDMaster::onConsumerKeyDown(void (*cb)(void *, int), void *cbData) {
|
||||
_consumerKeyDownCB = cb;
|
||||
_consumerKeyDownData = cbData;
|
||||
}
|
||||
|
||||
void BluetoothHIDMaster::onConsumerKeyUp(void (*cb)(void *, int), void *cbData) {
|
||||
_consumerKeyUpCB = cb;
|
||||
_consumerKeyUpData = cbData;
|
||||
}
|
||||
|
||||
std::list<BTDeviceInfo> BluetoothHIDMaster::scan(uint32_t mask, int scanTimeSec, bool async) {
|
||||
return _hci.scan(mask, scanTimeSec, async);
|
||||
}
|
||||
|
||||
bool BluetoothHIDMaster::scanAsyncDone() {
|
||||
return _hci.scanAsyncDone();
|
||||
}
|
||||
std::list<BTDeviceInfo> BluetoothHIDMaster::scanAsyncResult() {
|
||||
return _hci.scanAsyncResult();
|
||||
}
|
||||
|
||||
bool BluetoothHIDMaster::connected() {
|
||||
return _hidConnected && _hid_host_descriptor_available;
|
||||
}
|
||||
|
||||
bool BluetoothHIDMaster::connect(const uint8_t *addr) {
|
||||
if (!_running) {
|
||||
return false;
|
||||
}
|
||||
while (!_hci.running()) {
|
||||
delay(10);
|
||||
}
|
||||
uint8_t a[6];
|
||||
memcpy(a, addr, sizeof(a));
|
||||
return ERROR_CODE_SUCCESS == hid_host_connect(a, HID_PROTOCOL_MODE_REPORT, &_hid_host_cid);
|
||||
}
|
||||
|
||||
bool BluetoothHIDMaster::connectCOD(uint32_t cod) {
|
||||
if (!_running) {
|
||||
return false;
|
||||
}
|
||||
while (!_hci.running()) {
|
||||
delay(10);
|
||||
}
|
||||
|
||||
uint8_t a[6];
|
||||
clearPairing();
|
||||
auto l = scan(cod);
|
||||
for (auto e : l) {
|
||||
DEBUGV("Scan connecting %s at %s ... ", e.name(), e.addressString());
|
||||
memcpy(a, e.address(), sizeof(a));
|
||||
if (ERROR_CODE_SUCCESS == hid_host_connect(a, HID_PROTOCOL_MODE_REPORT, &_hid_host_cid)) {
|
||||
DEBUGV("Connection established\n");
|
||||
return true;
|
||||
}
|
||||
DEBUGV("Failed\n");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BluetoothHIDMaster::connectKeyboard() {
|
||||
return connectCOD(0x2540);
|
||||
}
|
||||
|
||||
bool BluetoothHIDMaster::connectMouse() {
|
||||
return connectCOD(0x2580);
|
||||
}
|
||||
|
||||
bool BluetoothHIDMaster::disconnect() {
|
||||
BluetoothLock b;
|
||||
if (connected()) {
|
||||
hid_host_disconnect(_hid_host_cid);
|
||||
}
|
||||
if (!_running || !connected()) {
|
||||
return false;
|
||||
}
|
||||
_hid_host_descriptor_available = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void BluetoothHIDMaster::clearPairing() {
|
||||
BluetoothLock b;
|
||||
if (connected()) {
|
||||
hid_host_disconnect(_hid_host_cid);
|
||||
}
|
||||
gap_delete_all_link_keys();
|
||||
_hid_host_descriptor_available = false;
|
||||
}
|
||||
|
||||
void BluetoothHIDMaster::hid_host_handle_interrupt_report(btstack_hid_parser_t * parser) {
|
||||
uint8_t new_keys[NUM_KEYS];
|
||||
uint8_t tosend[NUM_KEYS];
|
||||
int tosendcnt = 0;
|
||||
memset(new_keys, 0, sizeof(new_keys));
|
||||
int new_keys_count = 0;
|
||||
|
||||
uint16_t new_consumer_key = 0;
|
||||
uint8_t newMB = 0;
|
||||
bool noMB = false;
|
||||
|
||||
bool updCons = false;
|
||||
bool updKey = false;
|
||||
bool updMB = false;
|
||||
|
||||
bool updMouse = false;
|
||||
int dx = 0;
|
||||
int dy = 0;
|
||||
int dwheel = 0;
|
||||
|
||||
while (btstack_hid_parser_has_more(parser)) {
|
||||
uint16_t usage_page;
|
||||
uint16_t usage;
|
||||
int32_t value;
|
||||
btstack_hid_parser_get_field(parser, &usage_page, &usage, &value);
|
||||
if (usage_page == 0x01) {
|
||||
updMouse = true;
|
||||
if (usage == 0x30) {
|
||||
dx = value;
|
||||
} else if (usage == 0x31) {
|
||||
dy = value;
|
||||
} else if (usage == 0x38) {
|
||||
dwheel = value;
|
||||
}
|
||||
} else if (usage_page == 0x09) {
|
||||
updMB = true;
|
||||
if (usage == 0) {
|
||||
noMB = true;
|
||||
}
|
||||
if (!noMB && value && (usage > 0) && (usage < 9)) {
|
||||
newMB |= 1 << (usage - 1);
|
||||
}
|
||||
|
||||
} else if (usage_page == 0x0c) {
|
||||
updCons = true;
|
||||
if (value) {
|
||||
new_consumer_key = usage;
|
||||
// check if usage was used last time (and ignore in that case)
|
||||
if (usage == last_consumer_key) {
|
||||
usage = 0;
|
||||
}
|
||||
if (usage == 0) {
|
||||
continue;
|
||||
}
|
||||
if (last_consumer_key) {
|
||||
if (_consumerKeyUpCB) {
|
||||
_consumerKeyUpCB(_consumerKeyUpData, last_consumer_key);
|
||||
}
|
||||
}
|
||||
if (_consumerKeyDownCB) {
|
||||
_consumerKeyDownCB(_consumerKeyDownData, usage);
|
||||
}
|
||||
} else if (last_consumer_key == usage) {
|
||||
if (_consumerKeyUpCB) {
|
||||
_consumerKeyUpCB(_consumerKeyUpData, last_consumer_key);
|
||||
}
|
||||
}
|
||||
} else if (usage_page == 0x07) {
|
||||
updKey = true;
|
||||
if (value) {
|
||||
new_keys[new_keys_count++] = usage;
|
||||
// check if usage was used last time (and ignore in that case)
|
||||
int i;
|
||||
for (i = 0; i < NUM_KEYS; i++) {
|
||||
if (usage == last_keys[i]) {
|
||||
usage = 0;
|
||||
}
|
||||
}
|
||||
if (usage == 0) {
|
||||
continue;
|
||||
}
|
||||
tosend[tosendcnt++] = usage;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (updKey) {
|
||||
bool found;
|
||||
for (int i = 0; i < NUM_KEYS; i++) {
|
||||
found = false;
|
||||
for (int j = 0; j < NUM_KEYS; j++) {
|
||||
if (last_keys[i] == new_keys[j]) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found && last_keys[i] && _keyUpCB) {
|
||||
_keyUpCB(_keyUpData, last_keys[i]);
|
||||
}
|
||||
}
|
||||
for (int i = 0; _keyDownCB && i < tosendcnt; i++) {
|
||||
_keyDownCB(_keyDownData, tosend[i]);
|
||||
}
|
||||
memcpy(last_keys, new_keys, NUM_KEYS);
|
||||
}
|
||||
if (updCons) {
|
||||
last_consumer_key = new_consumer_key;
|
||||
}
|
||||
if (updMB) {
|
||||
if (lastMB != newMB) {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
int mask = 1 << i;
|
||||
if ((lastMB & mask) && !(newMB & mask) && _mouseButtonCB) {
|
||||
_mouseButtonCB(_mouseButtonData, i, false);
|
||||
} else if (!(lastMB & mask) && (newMB & mask) && _mouseButtonCB) {
|
||||
_mouseButtonCB(_mouseButtonData, i, true);
|
||||
}
|
||||
}
|
||||
lastMB = newMB;
|
||||
}
|
||||
}
|
||||
|
||||
if (updMouse && _mouseMoveCB) {
|
||||
_mouseMoveCB(_mouseMoveData, dx, dy, dwheel);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void BluetoothHIDMaster::hid_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
|
||||
(void)channel;
|
||||
(void)size;
|
||||
|
||||
if ((packet_type != HCI_EVENT_PACKET) || (hci_event_packet_get_type(packet) != HCI_EVENT_HID_META)) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t status;
|
||||
switch (hci_event_hid_meta_get_subevent_code(packet)) {
|
||||
|
||||
case HID_SUBEVENT_INCOMING_CONNECTION:
|
||||
// There is an incoming connection: we can accept it or decline it.
|
||||
// The hid_host_report_mode in the hid_host_accept_connection function
|
||||
// allows the application to request a protocol mode.
|
||||
// For available protocol modes, see hid_protocol_mode_t in btstack_hid.h file.
|
||||
hid_host_accept_connection(hid_subevent_incoming_connection_get_hid_cid(packet), HID_PROTOCOL_MODE_REPORT);
|
||||
break;
|
||||
|
||||
case HID_SUBEVENT_CONNECTION_OPENED:
|
||||
// The status field of this event indicates if the control and interrupt
|
||||
// connections were opened successfully.
|
||||
status = hid_subevent_connection_opened_get_status(packet);
|
||||
if (status != ERROR_CODE_SUCCESS) {
|
||||
DEBUGV("Connection failed, status 0x%02x\n", status);
|
||||
_hidConnected = false;
|
||||
_hid_host_cid = 0;
|
||||
return;
|
||||
}
|
||||
_hidConnected = true;
|
||||
_hid_host_descriptor_available = false;
|
||||
_hid_host_cid = hid_subevent_connection_opened_get_hid_cid(packet);
|
||||
DEBUGV("HID Host connected.\n");
|
||||
break;
|
||||
|
||||
case HID_SUBEVENT_DESCRIPTOR_AVAILABLE:
|
||||
// This event will follows HID_SUBEVENT_CONNECTION_OPENED event.
|
||||
// For incoming connections, i.e. HID Device initiating the connection,
|
||||
// the HID_SUBEVENT_DESCRIPTOR_AVAILABLE is delayed, and some HID
|
||||
// reports may be received via HID_SUBEVENT_REPORT event. It is up to
|
||||
// the application if these reports should be buffered or ignored until
|
||||
// the HID descriptor is available.
|
||||
status = hid_subevent_descriptor_available_get_status(packet);
|
||||
if (status == ERROR_CODE_SUCCESS) {
|
||||
_hid_host_descriptor_available = true;
|
||||
} else {
|
||||
DEBUGV("Cannot handle input report, HID Descriptor is not available, status 0x%02x\n", status);
|
||||
}
|
||||
break;
|
||||
|
||||
case HID_SUBEVENT_REPORT:
|
||||
// Handle input report.
|
||||
if (_hid_host_descriptor_available) {
|
||||
uint16_t report_len = hid_subevent_report_get_report_len(packet);
|
||||
const uint8_t *report = hid_subevent_report_get_report(packet);
|
||||
// check if HID Input Report
|
||||
if ((report_len < 1) || (*report != 0xa1)) {
|
||||
break;
|
||||
}
|
||||
report++;
|
||||
report_len--;;
|
||||
btstack_hid_parser_t parser;
|
||||
btstack_hid_parser_init(&parser, hid_descriptor_storage_get_descriptor_data(_hid_host_cid), hid_descriptor_storage_get_descriptor_len(_hid_host_cid), HID_REPORT_TYPE_INPUT, report, report_len);
|
||||
hid_host_handle_interrupt_report(&parser);
|
||||
}
|
||||
break;
|
||||
|
||||
case HID_SUBEVENT_SET_PROTOCOL_RESPONSE:
|
||||
// For incoming connections, the library will set the protocol mode of the
|
||||
// HID Device as requested in the call to hid_host_accept_connection. The event
|
||||
// reports the result. For connections initiated by calling hid_host_connect,
|
||||
// this event will occur only if the established report mode is boot mode.
|
||||
status = hid_subevent_set_protocol_response_get_handshake_status(packet);
|
||||
if (status != HID_HANDSHAKE_PARAM_TYPE_SUCCESSFUL) {
|
||||
DEBUGV("Error set protocol, status 0x%02x\n", status);
|
||||
break;
|
||||
}
|
||||
switch ((hid_protocol_mode_t)hid_subevent_set_protocol_response_get_protocol_mode(packet)) {
|
||||
case HID_PROTOCOL_MODE_BOOT:
|
||||
DEBUGV("Protocol mode set: BOOT.\n");
|
||||
break;
|
||||
case HID_PROTOCOL_MODE_REPORT:
|
||||
DEBUGV("Protocol mode set: REPORT.\n");
|
||||
break;
|
||||
default:
|
||||
DEBUGV("Unknown protocol mode.\n");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case HID_SUBEVENT_CONNECTION_CLOSED:
|
||||
// The connection was closed.
|
||||
_hidConnected = false;
|
||||
_hid_host_cid = 0;
|
||||
_hid_host_descriptor_available = false;
|
||||
DEBUGV("HID Host disconnected.\n");
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Simplified US Keyboard with Shift modifier
|
||||
|
||||
#define CHAR_ILLEGAL 0xff
|
||||
#define CHAR_RETURN '\n'
|
||||
#define CHAR_ESCAPE 27
|
||||
#define CHAR_TAB '\t'
|
||||
#define CHAR_BACKSPACE 0x7f
|
||||
|
||||
/**
|
||||
English (US)
|
||||
*/
|
||||
static const uint8_t keytable_us_none [] = {
|
||||
CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 0-3 */
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', /* 4-13 */
|
||||
'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', /* 14-23 */
|
||||
'u', 'v', 'w', 'x', 'y', 'z', /* 24-29 */
|
||||
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', /* 30-39 */
|
||||
CHAR_RETURN, CHAR_ESCAPE, CHAR_BACKSPACE, CHAR_TAB, ' ', /* 40-44 */
|
||||
'-', '=', '[', ']', '\\', CHAR_ILLEGAL, ';', '\'', 0x60, ',', /* 45-54 */
|
||||
'.', '/', CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 55-60 */
|
||||
CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 61-64 */
|
||||
CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 65-68 */
|
||||
CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 69-72 */
|
||||
CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 73-76 */
|
||||
CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 77-80 */
|
||||
CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 81-84 */
|
||||
'*', '-', '+', '\n', '1', '2', '3', '4', '5', /* 85-97 */
|
||||
'6', '7', '8', '9', '0', '.', 0xa7, /* 97-100 */
|
||||
};
|
||||
|
||||
static const uint8_t keytable_us_shift[] = {
|
||||
CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 0-3 */
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', /* 4-13 */
|
||||
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', /* 14-23 */
|
||||
'U', 'V', 'W', 'X', 'Y', 'Z', /* 24-29 */
|
||||
'!', '@', '#', '$', '%', '^', '&', '*', '(', ')', /* 30-39 */
|
||||
CHAR_RETURN, CHAR_ESCAPE, CHAR_BACKSPACE, CHAR_TAB, ' ', /* 40-44 */
|
||||
'_', '+', '{', '}', '|', CHAR_ILLEGAL, ':', '"', 0x7E, '<', /* 45-54 */
|
||||
'>', '?', CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 55-60 */
|
||||
CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 61-64 */
|
||||
CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 65-68 */
|
||||
CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 69-72 */
|
||||
CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 73-76 */
|
||||
CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 77-80 */
|
||||
CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 81-84 */
|
||||
'*', '-', '+', '\n', '1', '2', '3', '4', '5', /* 85-97 */
|
||||
'6', '7', '8', '9', '0', '.', 0xb1, /* 97-100 */
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
bool HIDKeyStream::setFIFOSize(size_t size) {
|
||||
if (!size || _running) {
|
||||
return false;
|
||||
}
|
||||
_fifoSize = size + 1; // Always 1 unused entry
|
||||
return true;
|
||||
}
|
||||
|
||||
HIDKeyStream::HIDKeyStream() {
|
||||
}
|
||||
|
||||
HIDKeyStream::~HIDKeyStream() {
|
||||
end();
|
||||
}
|
||||
|
||||
void HIDKeyStream::begin() {
|
||||
if (_running) {
|
||||
end();
|
||||
}
|
||||
_queue = new uint8_t[_fifoSize];
|
||||
_writer = 0;
|
||||
_reader = 0;
|
||||
|
||||
_lshift = false;
|
||||
_rshift = false;
|
||||
|
||||
_holding = false;
|
||||
|
||||
_running = true;
|
||||
}
|
||||
|
||||
void HIDKeyStream::end() {
|
||||
if (!_running) {
|
||||
return;
|
||||
}
|
||||
_running = false;
|
||||
|
||||
delete[] _queue;
|
||||
}
|
||||
|
||||
int HIDKeyStream::peek() {
|
||||
if (!_running) {
|
||||
return -1;
|
||||
}
|
||||
if (_writer != _reader) {
|
||||
return _queue[_reader];
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int HIDKeyStream::read() {
|
||||
if (!_running) {
|
||||
return -1;
|
||||
}
|
||||
if (_writer != _reader) {
|
||||
auto ret = _queue[_reader];
|
||||
asm volatile("" ::: "memory"); // Ensure the value is read before advancing
|
||||
auto next_reader = (_reader + 1) % _fifoSize;
|
||||
asm volatile("" ::: "memory"); // Ensure the reader value is only written once, correctly
|
||||
_reader = next_reader;
|
||||
return ret;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int HIDKeyStream::available() {
|
||||
if (!_running) {
|
||||
return 0;
|
||||
}
|
||||
return (_fifoSize + _writer - _reader) % _fifoSize;
|
||||
}
|
||||
|
||||
int HIDKeyStream::availableForWrite() {
|
||||
return 2 * _fifoSize - available() - 1; // Every 2 write = 1 read buffer insertion
|
||||
}
|
||||
|
||||
void HIDKeyStream::flush() {
|
||||
// We always send blocking
|
||||
}
|
||||
|
||||
size_t HIDKeyStream::write(uint8_t c) {
|
||||
if (!availableForWrite()) {
|
||||
return 0;
|
||||
}
|
||||
if (_holding) {
|
||||
_holding = false;
|
||||
bool state = (bool)c;
|
||||
if (_heldKey == 0xe1) {
|
||||
_lshift = state;
|
||||
return 1;
|
||||
} else if (_heldKey == 0xe6) {
|
||||
_rshift = state;
|
||||
return 1;
|
||||
} else if (state) {
|
||||
auto ascii = (_lshift || _rshift) ? keytable_us_shift[_heldKey] : keytable_us_none[_heldKey];
|
||||
if (ascii != CHAR_ILLEGAL) {
|
||||
auto next_writer = _writer + 1;
|
||||
if (next_writer == _fifoSize) {
|
||||
next_writer = 0;
|
||||
}
|
||||
if (next_writer != _reader) {
|
||||
_queue[_writer] = ascii;
|
||||
asm volatile("" ::: "memory"); // Ensure the queue is written before the written count advances
|
||||
_writer = next_writer;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
if ((c < sizeof(keytable_us_shift)) || (c == 0xe1) || (c == 0xe6)) {
|
||||
_holding = true;
|
||||
_heldKey = c;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
size_t HIDKeyStream::write(const uint8_t *p, size_t len) {
|
||||
if (!_running || !len) {
|
||||
return 0;
|
||||
}
|
||||
size_t cnt = 0;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
if (!write(p[len])) {
|
||||
return cnt;
|
||||
}
|
||||
cnt++;
|
||||
}
|
||||
return cnt;
|
||||
}
|
||||
|
||||
HIDKeyStream::operator bool() {
|
||||
return _running;
|
||||
}
|
||||
|
||||
123
libraries/BluetoothHIDMaster/src/BluetoothHIDMaster.h
Normal file
123
libraries/BluetoothHIDMaster/src/BluetoothHIDMaster.h
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
Bluetooth HID Master class, can connect to keyboards, mice, and joypads
|
||||
|
||||
Copyright (c) 2024 Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
|
||||
#include <BluetoothHCI.h>
|
||||
#include <btstack.h>
|
||||
|
||||
|
||||
// Write raw key up/down events, read ASCII chars out
|
||||
class HIDKeyStream : public Stream {
|
||||
public:
|
||||
HIDKeyStream();
|
||||
~HIDKeyStream();
|
||||
|
||||
bool setFIFOSize(size_t size);
|
||||
|
||||
void begin();
|
||||
void end();
|
||||
|
||||
virtual int peek() override;
|
||||
virtual int read() override;
|
||||
virtual int available() override;
|
||||
virtual int availableForWrite() override;
|
||||
virtual void flush() override;
|
||||
virtual size_t write(uint8_t c) override;
|
||||
virtual size_t write(const uint8_t *p, size_t len) override;
|
||||
using Print::write;
|
||||
operator bool();
|
||||
|
||||
private:
|
||||
bool _lshift = false;
|
||||
bool _rshift = false;
|
||||
bool _running = false;
|
||||
bool _holding = false;
|
||||
uint8_t _heldKey;
|
||||
|
||||
// Lockless, IRQ-handled circular queue
|
||||
uint32_t _writer;
|
||||
uint32_t _reader;
|
||||
size_t _fifoSize = 32;
|
||||
uint8_t *_queue;
|
||||
};
|
||||
|
||||
class BluetoothHIDMaster {
|
||||
public:
|
||||
void begin();
|
||||
bool connected();
|
||||
void end();
|
||||
bool hciRunning();
|
||||
bool running();
|
||||
|
||||
static const uint32_t keyboard_cod = 0x2540;
|
||||
static const uint32_t mouse_cod = 0x2540;
|
||||
static const uint32_t any_cod = 0;
|
||||
std::list<BTDeviceInfo> scan(uint32_t mask, int scanTimeSec = 5, bool async = false);
|
||||
bool scanAsyncDone();
|
||||
std::list<BTDeviceInfo> scanAsyncResult();
|
||||
|
||||
bool connect(const uint8_t *addr);
|
||||
bool connectKeyboard();
|
||||
bool connectMouse();
|
||||
bool disconnect();
|
||||
void clearPairing();
|
||||
|
||||
void onMouseMove(void (*)(void *, int, int, int), void *cbData = nullptr);
|
||||
void onMouseButton(void (*)(void *, int, bool), void *cbData = nullptr);
|
||||
void onKeyDown(void (*)(void *, int), void *cbData = nullptr);
|
||||
void onKeyUp(void (*)(void *, int), void *cbData = nullptr);
|
||||
void onConsumerKeyDown(void (*)(void *, int), void *cbData = nullptr);
|
||||
void onConsumerKeyUp(void (*)(void *, int), void *cbData = nullptr);
|
||||
|
||||
private:
|
||||
bool connectCOD(uint32_t cod);
|
||||
BluetoothHCI _hci;
|
||||
void hid_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
|
||||
uint8_t lastMB = 0;
|
||||
enum { NUM_KEYS = 6 };
|
||||
uint8_t last_keys[NUM_KEYS] = { 0 };
|
||||
uint16_t last_consumer_key = 0;
|
||||
void hid_host_handle_interrupt_report(btstack_hid_parser_t * parser);
|
||||
bool _running = false;
|
||||
volatile bool _hidConnected = false;
|
||||
uint16_t _hid_host_cid = 0;
|
||||
bool _hid_host_descriptor_available = false;
|
||||
uint8_t _hid_descriptor_storage[300];
|
||||
|
||||
void (*_mouseMoveCB)(void *, int, int, int) = nullptr;
|
||||
void *_mouseMoveData;
|
||||
void (*_mouseButtonCB)(void *, int, bool) = nullptr;
|
||||
void *_mouseButtonData;
|
||||
|
||||
void (*_keyDownCB)(void *, int) = nullptr;
|
||||
void *_keyDownData;
|
||||
void (*_keyUpCB)(void *, int) = nullptr;
|
||||
void *_keyUpData;
|
||||
|
||||
void (*_consumerKeyDownCB)(void *, int) = nullptr;
|
||||
void *_consumerKeyDownData;
|
||||
void (*_consumerKeyUpCB)(void *, int) = nullptr;
|
||||
void *_consumerKeyUpData;
|
||||
};
|
||||
|
|
@ -15,7 +15,8 @@ for dir in ./cores/rp2040 ./libraries/EEPROM ./libraries/I2S ./libraries/SingleF
|
|||
./libraries/JoystickBLE ./libraries/KeyboardBLE ./libraries/MouseBLE \
|
||||
./libraries/lwIP_w5500 ./libraries/lwIP_w5100 ./libraries/lwIP_enc28j60 \
|
||||
./libraries/SPISlave ./libraries/lwIP_ESPHost ./libraries/FatFS\
|
||||
./libraries/FatFSUSB ./libraries/BluetoothAudio ./libraries/BluetoothHCI; do
|
||||
./libraries/FatFSUSB ./libraries/BluetoothAudio ./libraries/BluetoothHCI \
|
||||
./libraries/BluetoothHIDMaster; do
|
||||
find $dir -type f \( -name "*.c" -o -name "*.h" -o -name "*.cpp" \) -a \! -path '*api*' -exec astyle --suffix=none --options=./tests/astyle_core.conf \{\} \;
|
||||
find $dir -type f -name "*.ino" -exec astyle --suffix=none --options=./tests/astyle_examples.conf \{\} \;
|
||||
done
|
||||
|
|
|
|||
Loading…
Reference in a new issue