Compare commits

...
78 Commits
Author SHA1 Message Date
Earle F. Philhower, III bd64b97a20 Update version 2024-08-08 11:00:00 -07:00
Earle F. Philhower, III 525408e181 Add RP2040.memcpyDMA for DMA-managed memory copies (#2324)
RP2040::memcpyDMA implements a DMA-controlled memory copy call identical in
function to standard memcpy, but using an onboard DMA engine.  For large
memory transfers this can be significantly faster than using the CPU-based
memcpy.  Only 4-byte aligned source, destination, and counts are allowed.
If any inputs are not 4-byte aligned, then standard memcpy will occur so
it will behave correctly for any inputs.
2024-08-08 10:49:33 -07:00
Earle F. Philhower, III e022d47e80 Minor BearSSL changes for 8266/upstream (#2323)
No functionality differences expected.
2024-08-08 08:44:17 -07:00
Earle F. Philhower, III 28b0edac21 Add checks for Adafruit TinyUSB to USB libs (#2319)
The included USB libraries are not compatible with Adafruit TinyUSB, so
add a #error message when they're built with the define set.
2024-08-04 10:10:08 -07:00
Dominic Pearman 4bd7d99c96 Documentation: moved 'ESP32 Compatibility' to subsection (#2311)
Lowered level of segment 'ESP32 Compatibility' in documentation of 'WiFiClientSecure Class' to be a subsection thereof.
2024-08-03 07:32:39 -07:00
Earle F. Philhower, III 06572e365b Fix minor LWIP wrapper errors (#2310)
Somehow returning the results of a `void` function from another `void`
wrapper didn't trigger any warnings.  Also missed tcp_bind actual
GCC wrapping.
2024-08-02 16:24:54 -07:00
Earle F. Philhower, III e86f8f5002 Really remove dup'd TUSB GAMEPAD16 HID descriptor (#2306) 2024-07-30 09:54:12 -07:00
Earle F. Philhower, III 4f4e638aee Replace most remaining ESP_ debug macros (#2305)
In ported libraries there were still some remaining DEBUG_ESP_PORT
references.  Moved to their RP2040 equivalents.
2024-07-28 09:36:10 -07:00
Earle F. Philhower, III 34f386a21b LwIpIntfDev.end() check _started to prevent crash (#2304)
From @JAndrassy https://github.com/esp8266/Arduino/pull/9173
2024-07-27 11:46:39 -07:00
Earle F. Philhower, III dd1c9095e8 Factor out cut-n-pasted GAMEPAD16 HID structures (#2302)
Create a single spot with the gamepad16's HID report descriptor and
report structure.  Avoids cut-n-pasted code.
2024-07-26 09:11:41 -07:00
Earle F. Philhower, III 8140c354c7 Fix HTTPClient debug output (#2300)
Found via #2296, the HTTPClient was looking for old ESP8266 defines and
not the RP2040 core ones to enable it.  Now dump on `Core` level.
2024-07-25 22:21:38 -07:00
Earle F. Philhower, III 5c4eb022c1 Rename Picoprobe upload to DebugProbe new name (#2298)
Picoprobe was rechristened Debugprobe earlier this year, add a note
in the menus to be specific about it.

See https://github.com/raspberrypi/debugprobe
2024-07-25 10:10:33 -07:00
Earle F. Philhower, III 6e5b3897b7 Fix BT/BLE Joystick reports (#2293)
Underlying HID_Joystick now always using 16-bit format axes, need to update
BT and BLE descriptors sent to the BT master or it will misinterpret the
reports and the reported joystick state will be read as garbage.

Fixes bug introduced in #2276, oops!
2024-07-23 21:11:08 -07:00
Earle F. Philhower, III f3b8c58157 Remove obsolete TRAVIS CI variables (#2292)
Use GH native ones instead.
2024-07-23 09:48:33 -07:00
Earle F. Philhower, III 6d115cdcec Update version 2024-07-22 12:51:07 -07:00
Earle F. Philhower, III 9f66e834d4 Split variant build, combine spell + style checks (#2291)
Variant builds were taking longer than the actual individual CI jobs, so
split it up.

Combine the spelling and style checks, they ran very fast and spent more
time in checking out than in running.
2024-07-22 12:46:47 -07:00
Earle F. Philhower, III 096990123d Fix timeout in WebServer::_uploadReadByte and handleClient() (#2290)
Upstream patch https://github.com/espressif/arduino-esp32/pull/9991
2024-07-22 12:21:42 -07:00
Earle F. Philhower, III ea2de469a2 FreeRTOS/Arduino header include fix (#2288)
Fixes #2287
2024-07-21 05:13:43 -07:00
deltaford 80196d570b Add Pintronix PinMax board (#2286) 2024-07-20 13:08:05 -07:00
Amken USAandH.Keni 1f71135a2b Add 4 Amken boards (#2283)
Added 4 new boards from Amken LLC.
* Amken Bunny
* Amken Revelop
* Amken Revelop Plus
* Amken Revelop eS

Co-authored-by: H.Keni <151807089+hrken1@users.noreply.github.com>
2024-07-19 14:17:53 -07:00
Earle F. Philhower, III c4e713247e Make CI use current JSON, not upstream (#2277) 2024-07-18 16:33:23 -07:00
Earle F. Philhower, III bd902a93f5 Add true 10- and 16-bit joystick modes (#2276)
Fixes #2275

Adds `Joystick.use10bit` and `Joystick.use16bit` methods.  10-bit is
unsigned from 0...1023 while 16-bit is signed -32767..32767.
Defines a new HID descriptor to support the increased resolution.
2024-07-18 16:07:29 -07:00
Heng Teng YiandHTY2003 372fef06e1 Add board EVN Alpha (#2263)
Co-authored-by: HTY2003 <randumbperson@gmail.com>
2024-07-13 11:21:04 -07:00
Earle F. Philhower, III 41ce4b2f8b Ensure makeboards.py writes UNIX newlines (#2264)
The repo and development use only '\n' (UNIX) EOLs.  When a user runs
makeboards on a Windows system they end up changing every line in boards.txt
and the JSON files to Windows '\r\n' format.

Explicitly set the newline character when opening the output files to
avoid this.
2024-07-12 17:53:34 -07:00
Earle F. Philhower, III e8a2654296 Add a build of all variants to CI (#2262)
* Add a build of all variants to CI using P.IO
* Split out into separate job, use BOOTSEL sketch
* Fix Breadstick variant
* Fix Bridgetech boards with illegal define names
  Dash(-) to underscore(_) in define and variant for the -7 and -43.
* Bridgetech JSON updates
* Temporarily remove Bridgetech boards from CI
  Needs an update to the P.IO external repo to work since the names
  of the boards have changed.
2024-07-12 15:50:03 -07:00
AcThPaU a584d10321 Fix D pin mapping on Adafruit IB/Feather RP2040 (#2259)
* Fix D pins on Adafruit IB RP2040
* Fix Adafruit Feather D pins mapping as well
* Change common.h to accept D pin def per board
2024-07-12 14:41:06 -07:00
Earle F. Philhower, III 5cdd58dfb8 Add FreeRTOS functionality tests (#2257)
FreeRTOS often seems to have interesting corner cases.  Add two simple
tests that have been useful while debugging issues found from users or
from FreeRTOS updates.
2024-07-08 18:07:28 -07:00
Earle F. Philhower, III 96a2e925cf Fix leak in HTTPSClient (#2256)
Fixes #2254

The faked certificate was allocated but not deleted in certain cases.  Make sure
to clean up in the destructor.
2024-07-08 09:21:46 -07:00
Maximilian Gerhardt 8cb8807573 Fix assembler flags (#2255)
An oversight in the order of updating the ASFLAGS with a copy of the CCFLAGS (see line 110) and then updating the CCFLAGS (without resyncing the ASFLAGS) leads to a fatal compilation error in the Adafruit PicoDVI library, in which `tmds_encode.S` fails to find the `pico/config.h` include file. This fix updates the ASFLAGS manually after changing the CCFLAGS so that they're equal again, and the library can be compiled.
2024-07-08 09:02:15 -07:00
Earle F. Philhower, III 2cde8bd789 Avoid deadlock BT/LE HID send when disconnected (#2252)
Fixes #2251

The 2-phase send could get out of whack if transmission was attempted when
no device was connected. Clear things up so if things aren't connected,
then no data gets set as pending.
2024-07-04 13:58:33 -07:00
Earle F. Philhower, III 99b32b8436 Update to latest FreeRTOS main, not SMP branch (#2250)
FreeRTOS has merged the SMP branch into its main, so move to that and
adjust the core accordingly.  V11.1.0 + several minor edits.
2024-07-03 17:31:22 -07:00
Pontus OldbergandPontus Oldberg 91240567ce Fixed incorrect AVR compatibility macros. (#2249)
* Fixed incorrect AVR compatibility macros.

---------

Co-authored-by: Pontus Oldberg <pontus.oldberg@non.se.com>
2024-07-03 08:01:20 -07:00
chungsoftvn-tuannguyenandTuan Nguyen 247e48fa85 Add board BridgeTek IDM2040-43A (#2246)
- Board information brtchip.com/product/idm2040-43a

Signed-off-by: Tuan Nguyen <tuan.nguyen@brtchip.com>
Co-authored-by: Tuan Nguyen <tuan.nguyen@brtchip.com>
2024-06-27 11:30:53 -07:00
Earle F. Philhower, III 268d0aa1c5 On Timer::once execution, delete the alarm_id (#2245)
A Timer is not active after the alarm fires once, so clear the
alarm ID so we know we're not running.
2024-06-24 16:07:19 -07:00
Earle F. Philhower, III 553f7604ea Add ESP32-compatible Ticker library (#2244)
Uses the Pico SDK alarms and repeated-timers to provide for IRQ-level
periodic tasks to be scheduled.
2024-06-24 14:47:46 -07:00
Earle F. Philhower, III 1357e4a37b Receive GATT characteristic updates in BTStackLib (#2241)
Fixes #2231
2024-06-24 08:48:07 -07:00
Earle F. Philhower, III fdc361aff9 Remove old ESP8266 files from AsyncUDP (#2238) 2024-06-19 15:51:34 -07:00
Earle F. Philhower, III 7df8c35e81 Use built-in LWIP call to determine NetBIOS IP (#2235)
Instead of manually iterating over netifs to find the one a packet came
in over for NetBIOS name lookup, use a built-in LWIP macro that's
available for udp_recv callbacks.  Shrinks and simplifies NetBIOS code.
2024-06-18 13:42:23 -07:00
Earle F. Philhower, III 2297d61d92 Update version 2024-06-18 09:44:58 -07:00
Limor "Ladyada" Friedandhathach 6d601250d6 Add Adafruit Adalogger Feather (#2229)
Co-authored-by: hathach <thach@tinyusb.org>
2024-06-17 21:00:45 -07:00
Earle F. Philhower, III c99614c1f5 Add AsyncUDP and simple NetBIOS name lookup server (#2234)
Thanks to @me-no-dev's code.  Lets Windows look up the PicoW by name
using NBNS and needs much less memory and code than mDNS.

AsyncUDP ported from the old ESP8266 version, only minimal changes.  Will
probably only be valid in IPv4 environments and may not match current
ESP32 AsyncUDP interfaces.
2024-06-17 17:46:53 -07:00
Mete K. Atay 352d363463 Add METE HOCA Akana R1 (#2230) 2024-06-17 09:53:02 -07:00
Ayush Sharma 1775fedf44 Webserver: Add support for filters and removable routes (#2225)
This PR implements filters and removable routes in RP2040 arduino core, making it API compatible with recent changes to ESP32 & ESP8266 WebServer API.
2024-06-13 15:32:17 -07:00
Earle F. Philhower, III 4ab0ba6133 Fix crash on SD.end() without initial SD.begin() (#2222)
Fixes #2220
2024-06-11 13:40:27 -07:00
Earle F. Philhower, III eb0badd817 Avoid malloc/free while in HCI callbacks (#2219)
Bluetooth operates at IRQ level, so using std::list (which needs to
new and delete objects) is not legal.  Use a fixed, preallocated
vector instead.
2024-06-10 16:38:50 -07:00
Earle F. Philhower, III f272995536 For consistency, BTHID Joypad->Joystick (#2218)
Matches existing library names and nomenclature
2024-06-10 13:34:59 -07:00
Earle F. Philhower, III db13d3c1f8 Add basic A2DP BluetoothAudio documentation (#2217) 2024-06-09 17:47:13 -07:00
Earle F. Philhower, III f8c1ec100f Add BluetoothHIDMaster documentation (#2216)
Also link in FatFSUSB docs, d'oh!
2024-06-09 16:51:35 -07:00
Earle F. Philhower, III 1fd66bf9c2 Add joypad HID master support (#2214)
Play games on your Pico using Bluetooth gamepads!
2024-06-09 11:47:13 -07:00
Earle F. Philhower, III 151c52c1a0 Remove leftover LWIP debug/redefines (#2213)
Fixes #2211
2024-06-09 10:10:20 -07:00
Earle F. Philhower, III bde21e5ae1 Add BLE support to BluetoothHIDMaster (#2208)
Support Bluetooth BLE keyboard and mice using the same HID master
infrastructure as the BT Classic.
2024-06-07 14:52:52 -07:00
Earle F. Philhower, III c104c6717c Upgrade to Adafruit TinyUSB 3.1.5 (#2206) 2024-06-05 22:36:30 -07:00
Michael Rangen b0ffd89dbb Added Raspberry Breadstick (#2205)
https://shop.breadstick.ca/products/raspberry-breadstick-rp2040
I think I did the pin definitions correctly... other boards used generic pin numbers based on the GPIO pins but I've mapped GPIO to the silkscreen pin labels on our board.
2024-06-05 22:15:05 -07:00
Earle F. Philhower, III 0f05ad1cc2 Use block writes for BT audio consumers (#2204)
Around 2x the performance, and every bit is needed w/BT SBC compression
and decompression.
2024-06-05 16:03:35 -07:00
Zillion 0ec12aa49f Add GroundStudio Marble Pico board (#2203) 2024-06-05 15:18:21 -07:00
Earle F. Philhower, III f997a9c3bd Update README.md 2024-06-05 13:51:03 -07:00
Earle F. Philhower, III 9039089067 Fix PWMAudio::write(buffer, len) (#2202)
PWMAudio was only ever writing one half the buffer passed in because
of an off-by-2 error.  Fixes the sine output in KeyboardPiano.
2024-06-05 13:08:08 -07:00
Earle F. Philhower, III 8bc8c824d3 Update version 2024-06-05 12:03:10 -07:00
Earle F. Philhower, III f08ba6bd42 Rebuild OTA bootloader for newer LittleFS version (#2199)
The LittleFS folks changed the on-flash format in 2.6.0, making
it unmountable with earlier precompiled code.

Rebuild the OTA bootloader using the 2.9.3 LittleFS release,
matching the core version.

Fixes #2198
2024-06-05 11:48:16 -07:00
Earle F. Philhower, III 1ef61d725b Clean up OTA example references to ESP8266 (#2200) 2024-06-05 11:40:37 -07:00
Earle F. Philhower, III b42083f20a Add "Needs Bluetooth" compile warning (#2197) 2024-06-05 11:08:20 -07:00
Earle F. Philhower, III f820dc134a Update version 2024-06-04 14:46:13 -07:00
Earle F. Philhower, III f6d13d2b70 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.
2024-06-04 14:09:28 -07:00
Earle F. Philhower, III f786583986 Split out BluetoothHCI for shared usage (#2194) 2024-06-03 15:09:55 -07:00
Earle F. Philhower, III 5f6fb75505 Move to LittleFS 2.9.3. (#2193)
On-flash format changed after 2.5.1, but this can read prior versions
and will upgrade on-device to the later version.
2024-06-03 09:32:52 -07:00
Christian Halter 962dedad21 Fix folder name for Archi board (#2191)
* fix: Changed folder name to match build.variant property
* Added GPIO definitions for Archi board
Co-authored-by: Christian Halter <christian.halter@newsan.com.ar>
2024-06-03 09:20:59 -07:00
Earle F. Philhower, III ce45c65568 Make LittleFS filenames support full size (#2192)
Support 255 character names, not just 32, in LittleFS filesystems.
2024-06-03 09:11:01 -07:00
Earle F. Philhower, III 6d6433f256 Add track info support for BT audio sink (#2190) 2024-05-31 13:08:41 -07:00
Earle F. Philhower, III bf385490d3 Fix crash on audio end from IRQ, refactor A2DP (#2189)
Fixes #2188

We get a call to stop the audio channel from a timer/IRQ context, so can't
safely remove the IRQ handler for the AudioBufferManager.  The SDK will panic.

Because the IRQ handler will be a noop if it's not uninstalled, we will
instead just leave our shared handler in place and let it do nothing.

Use a common BluetoothLock RAII in BluetoothAudio to clen up the code and
automatically lock BT for the AVRCP button methods.
2024-05-31 12:14:57 -07:00
Earle F. Philhower, III 919a754ef8 Add double-mem LWIP option (#2187)
Add a "-32K" option to all the IP stack options that doubles the PCB and memory
pools from default.  For most use cases this is not necessary, but it could be
helpful in cases where large numbers of TCP clients are connected or high
bandwidth applications.

Fixes #2050
2024-05-30 18:30:50 -07:00
Earle F. Philhower, III 0a2b616c8a Add Cookies to HTTPClient (#2186)
From:
https://github.com/espressif/arduino-esp32/pull/6216
https://github.com/espressif/arduino-esp32/pull/6280
https://github.com/espressif/arduino-esp32/pull/7112
2024-05-30 13:27:43 -07:00
Earle F. Philhower, III 56bd539528 Webserver Ignore extra headers in multipart forms (#2184)
From https://github.com/espressif/arduino-esp32/pull/9253
2024-05-30 13:16:15 -07:00
Earle F. Philhower, III 3839f07afe HTTPClient - Fix case sensitivity for header keys (#2185)
From https://github.com/espressif/arduino-esp32/pull/8713
2024-05-30 13:05:49 -07:00
Earle F. Philhower, III 2043623ce6 Fix POST form parser edge cases (#2182)
From https://github.com/espressif/arduino-esp32/pull/9167
2024-05-30 12:19:44 -07:00
Earle F. Philhower, III a3dba8be5d Fix AdvancedWebServer.ino uptime conversion (#2183)
From https://github.com/espressif/arduino-esp32/pull/9224
2024-05-30 12:07:28 -07:00
Earle F. Philhower, III 1f9350dc2a Allow setting SerialBT advertised name (#2181)
Trivial fix #2179
2024-05-30 11:40:40 -07:00
Earle F. Philhower, III 361b4e0862 Allow uploading huge files to WebServer (#2180)
From https://github.com/espressif/arduino-esp32/pull/9440
2024-05-30 11:24:18 -07:00
Earle F. Philhower, III fa2bfdc2ba Small RAM savings (128b) in WebServer (#2178)
From https://github.com/espressif/arduino-esp32/pull/9594
2024-05-30 10:38:50 -07:00
183 changed files with 11695 additions and 959 deletions
+61 -21
View File
@@ -6,35 +6,23 @@ name: Arduino-Pico CI
on:
pull_request:
env:
TRAVIS_BUILD_DIR: ${{ github.workspace }}
TRAVIS_TAG: ${{ github.ref }}
jobs:
# Me no spell so good
code-spell:
name: Check spelling
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Run codespell
uses: codespell-project/actions-codespell@master
with:
skip: ./ArduinoCore-API,./libraries/ESP8266SdFat,./libraries/Adafruit_TinyUSB_Arduino,./libraries/LittleFS/lib,./tools/pyserial,./pico-sdk,./.github,./docs/i2s.rst,./cores/rp2040/api,./libraries/FreeRTOS,./tools/libbearssl/bearssl,./include,./libraries/WiFi/examples/BearSSL_Server,./ota/uzlib,./libraries/http-parser/lib,./libraries/WebServer/examples/HelloServerBearSSL/HelloServerBearSSL.ino,./libraries/HTTPUpdateServer/examples/SecureBearSSLUpdater/SecureBearSSLUpdater.ino,./.git,./libraries/FatFS/lib/fatfs,./libraries/FatFS/src/diskio.h,./libraries/FatFS/src/ff.cpp,./libraries/FatFS/src/ffconf.h,./libraries/FatFS/src/ffsystem.cpp,./libraries/FatFS/src/ff.h,./libraries/lwIP_WINC1500/src/driver,./libraries/lwIP_WINC1500/src/common,./libraries/lwIP_WINC1500/src/bus_wrapper,./libraries/lwIP_WINC1500/src/spi_flash
ignore_words_list: ser,dout,shiftIn
# Consistent style
# Consistent style, spelling
astyle:
name: Style, Boards, Package
name: Spelling, Style, Boards, Package
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: false
- name: Run codespell
uses: codespell-project/actions-codespell@master
with:
skip: ./ArduinoCore-API,./libraries/ESP8266SdFat,./libraries/Adafruit_TinyUSB_Arduino,./libraries/LittleFS/lib,./tools/pyserial,./pico-sdk,./.github,./docs/i2s.rst,./cores/rp2040/api,./libraries/FreeRTOS,./tools/libbearssl/bearssl,./include,./libraries/WiFi/examples/BearSSL_Server,./ota/uzlib,./libraries/http-parser/lib,./libraries/WebServer/examples/HelloServerBearSSL/HelloServerBearSSL.ino,./libraries/HTTPUpdateServer/examples/SecureBearSSLUpdater/SecureBearSSLUpdater.ino,./.git,./libraries/FatFS/lib/fatfs,./libraries/FatFS/src/diskio.h,./libraries/FatFS/src/ff.cpp,./libraries/FatFS/src/ffconf.h,./libraries/FatFS/src/ffsystem.cpp,./libraries/FatFS/src/ff.h,./libraries/lwIP_WINC1500/src/driver,./libraries/lwIP_WINC1500/src/common,./libraries/lwIP_WINC1500/src/bus_wrapper,./libraries/lwIP_WINC1500/src/spi_flash
ignore_words_list: ser,dout,shiftIn,acount
- name: Get submodules for following tests
run: git submodule update --init
- name: Check package references
run: |
./tests/ci/pkgrefs_test.sh
@@ -223,3 +211,55 @@ jobs:
run: pio ci --board=rpipicow -O "platform_packages=framework-arduinopico@symlink:///home/runner/work/arduino-pico/arduino-pico" libraries/ArduinoOTA/examples/SignedOTA/SignedOTA.ino
- name: Build Bluetooth Example
run: pio ci --board=rpipicow -O "platform_packages=framework-arduinopico@symlink:///home/runner/work/arduino-pico/arduino-pico" -O "build_flags=-DPIO_FRAMEWORK_ARDUINO_ENABLE_BLUETOOTH" libraries/MouseBLE/examples/BLECircle/BLECircle.ino
# Build every variant using PIO for simplicity
build-variants:
name: Build Every Variant ${{ matrix.chunk }}
runs-on: ubuntu-latest
strategy:
matrix:
chunk: [0, 1]
steps:
- uses: actions/checkout@v4
with:
submodules: 'true'
- name: Initialize needed submodules
run: |
cd pico-sdk
git submodule update --init
cd ../libraries/Adafruit_TinyUSB_Arduino
git submodule update --init
cd ../..
- name: Cache pip
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Cache PlatformIO
uses: actions/cache@v4
with:
path: ~/.platformio
key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install PlatformIO
run: |
python -m pip install --upgrade pip
pip install --upgrade platformio
pio pkg install --global --platform https://github.com/maxgerhardt/platform-raspberrypi.git
pio pkg install --global --tool symlink://.
cp -f /home/runner/work/arduino-pico/arduino-pico/tools/json/*.json /home/runner/.platformio/platforms/raspberrypi/boards/.
- name: Build Every Variant
run: |
cnt=0
for b in $(cut -f1 -d. /home/runner/work/arduino-pico/arduino-pico/boards.txt | sed 's/#.*//' | sed 's/^menu$//' | sort -u); do
cnt=$((cnt + 1))
rem=$((cnt % 2))
if [ $rem == ${{ matrix.chunk }} ]; then
pio ci --board=$b -O "platform_packages=framework-arduinopico@symlink:///home/runner/work/arduino-pico/arduino-pico" libraries/rp2040/examples/Bootsel/Bootsel.ino
fi
done
-1
View File
@@ -33,7 +33,6 @@ jobs:
# pip install --upgrade platformio
- name: Deploy updated JSON
env:
TRAVIS_BUILD_DIR: ${{ github.workspace }}
BUILD_TYPE: package
CI_GITHUB_API_KEY: ${{ secrets.GITHUB_TOKEN }}
PLATFORMIO_AUTH_TOKEN: ${{ secrets.PLATFORMIO_AUTH_TOKEN }}
@@ -24,7 +24,6 @@ jobs:
python-version: '3.x'
- name: Build package JSON
env:
TRAVIS_BUILD_DIR: ${{ github.workspace }}
BUILD_TYPE: package
CI_GITHUB_API_KEY: ${{ secrets.GITHUB_TOKEN }}
run: |
+3
View File
@@ -40,3 +40,6 @@
[submodule "libraries/FatFS/lib/SPIFTL"]
path = libraries/FatFS/lib/SPIFTL
url = https://github.com/earlephilhower/SPIFTL.git
[submodule "libraries/AsyncUDP"]
path = libraries/AsyncUDP
url = https://github.com/earlephilhower/AsyncUDP.git
+12 -1
View File
@@ -25,9 +25,15 @@ Read the [Contributing Guide](https://github.com/earlephilhower/arduino-pico/blo
* Adafruit QTPy RP2040
* Adafruit STEMMA Friend RP2040
* Adafruit Trinkey RP2040 QT
* Amken Bunny
* Amken Revelop
* Amken Revelop Plus
* Amken Revelop eS
* Arduino Nano RP2040 Connect
* ArtronShop RP2 Nano
* Breadstick Raspberry
* BridgeTek IDM2040-7A
* BridgeTek IDM2040-43A
* Cytron Maker Pi RP2040
* Cytron Maker Nano RP2040
* Cytron Maker Uno RP2040
@@ -36,7 +42,9 @@ Read the [Contributing Guide](https://github.com/earlephilhower/arduino-pico/blo
* DeRuiLab FlyBoard2040 Core
* DFRobot Beetle RP2040
* ElectronicCats Hunter Cat NFC
* EVN Alpha
* ExtremeElectronics RC2040
* GroundStudio Marble Pico
* Invector Labs Challenger RP2040 WiFi
* Invector Labs Challenger RP2040 WiFi/BLE
* Invector Labs Challenger RP2040 WiFi6/BLE
@@ -49,6 +57,7 @@ Read the [Contributing Guide](https://github.com/earlephilhower/arduino-pico/blo
* Invector Labs RPICO32
* Melopero Cookie RP2040
* Melopero Shake RP2040
* METE HOCA Akana R1
* Neko Systems BL2040 Mini
* Olimex RP2040-Pico30
* Newsan Archi
@@ -56,6 +65,7 @@ Read the [Contributing Guide](https://github.com/earlephilhower/arduino-pico/blo
* Pimoroni PGA2040
* Pimoroni Plasma2040
* Pimoroni Tiny2040
* Pintronix PinMax
* RAKwireless RAK11300
* Redscorp RP2040-Eins
* Redscorp RP2040-ProMini
@@ -85,6 +95,7 @@ Read the [Contributing Guide](https://github.com/earlephilhower/arduino-pico/blo
# Features
* Adafruit TinyUSB Arduino (USB mouse, keyboard, flash drive, generic HID, CDC Serial, MIDI, WebUSB, others)
* Bluetooth on the PicoW (Classic and BLE) with Keyboard, Mouse, Joystick, and Virtual Serial
* Bluetooth Classic and BLE HID master mode (connect to BT keyboard, mouse, or joystick)
* Generic Arduino USB Serial, Keyboard, Joystick, and Mouse emulation
* WiFi (Pico W, ESP32-based ESPHost, Atmel WINC1500)
* Ethernet (Wired W5500, W5100, ENC28J60)
@@ -98,7 +109,7 @@ Read the [Contributing Guide](https://github.com/earlephilhower/arduino-pico/blo
* digitalWrite/Read, shiftIn/Out, tone, analogWrite(PWM)/Read, temperature
* Analog stereo audio in using DMA and the built-in ADC
* Analog stereo audio out using PWM hardware
* Bluetooth A2DP audio source (output) on the PicoW
* Bluetooth A2DP audio source (output) and sink (input) on the PicoW
* USB drive mode for data loggers (SingleFileDrive, FatFSUSB)
* Peripherals: SPI master/slave, Wire(I2C) master/slave, dual UART, emulated EEPROM, I2S audio input/output, Servo
* printf (i.e. debug) output over USB serial
+5423 -337
View File
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,23 @@
// Padded and checksummed version of: generated\Winbond\W25Q32JVxQ\boot2.bin
.cpu cortex-m0plus
.thumb
.section .boot2, "ax"
.byte 0xf7, 0xb5, 0x73, 0x46, 0x21, 0x22, 0x02, 0x25, 0x01, 0x93, 0x29, 0x4b, 0xc0, 0x24, 0x5a, 0x60
.byte 0x9a, 0x68, 0x01, 0x26, 0xaa, 0x43, 0xda, 0x60, 0x9a, 0x60, 0x1a, 0x61, 0x5a, 0x61, 0x00, 0x23
.byte 0x64, 0x05, 0xa3, 0x60, 0x04, 0x33, 0x63, 0x61, 0x22, 0x4b, 0x28, 0x00, 0x1e, 0x60, 0xe0, 0x23
.byte 0xdb, 0x02, 0x23, 0x60, 0x35, 0x23, 0xa6, 0x60, 0x23, 0x66, 0x23, 0x66, 0x00, 0xf0, 0x4a, 0xf8
.byte 0xc0, 0xb2, 0xa8, 0x42, 0x12, 0xd0, 0x06, 0x23, 0x30, 0x00, 0x23, 0x66, 0x00, 0xf0, 0x42, 0xf8
.byte 0x31, 0x23, 0x28, 0x00, 0x23, 0x66, 0x25, 0x66, 0x00, 0xf0, 0x3c, 0xf8, 0x03, 0x35, 0x25, 0x66
.byte 0x02, 0x20, 0x25, 0x66, 0x00, 0xf0, 0x36, 0xf8, 0x30, 0x42, 0xf8, 0xd1, 0x00, 0x25, 0x12, 0x4b
.byte 0xa5, 0x60, 0x12, 0x4f, 0x23, 0x60, 0x12, 0x4b, 0x65, 0x60, 0x01, 0x26, 0x3b, 0x60, 0xeb, 0x23
.byte 0xa6, 0x60, 0x23, 0x66, 0x4b, 0x3b, 0x23, 0x66, 0x02, 0x20, 0x00, 0xf0, 0x23, 0xf8, 0x0d, 0x4b
.byte 0xa5, 0x60, 0x3b, 0x60, 0xa6, 0x60, 0x01, 0x9b, 0xab, 0x42, 0x08, 0xd1, 0x0a, 0x4b, 0x0b, 0x4a
.byte 0x13, 0x60, 0x1b, 0x68, 0x83, 0xf3, 0x08, 0x88, 0x09, 0x4b, 0x1b, 0x68, 0x18, 0x47, 0xf7, 0xbd
.byte 0x00, 0x00, 0x02, 0x40, 0xf0, 0x00, 0x00, 0x18, 0x00, 0x03, 0x5f, 0x00, 0xf4, 0x00, 0x00, 0x18
.byte 0x21, 0x22, 0x00, 0x00, 0x22, 0x20, 0x00, 0xa0, 0x00, 0x01, 0x00, 0x10, 0x08, 0xed, 0x00, 0xe0
.byte 0x04, 0x01, 0x00, 0x10, 0xc0, 0x23, 0x02, 0x00, 0x04, 0x21, 0x5b, 0x05, 0x98, 0x6a, 0x08, 0x42
.byte 0xfc, 0xd0, 0x01, 0x21, 0x98, 0x6a, 0x08, 0x42, 0xfc, 0xd1, 0x18, 0x6e, 0x01, 0x2a, 0x00, 0xd0
.byte 0x18, 0x6e, 0x70, 0x47, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x2d, 0x68, 0xca
+3 -3
View File
@@ -65,9 +65,9 @@ void noInterrupts();
#define digitalPinToTimer(pin) (0)
#define digitalPinToInterrupt(pin) (pin)
#define NOT_AN_INTERRUPT (-1)
#define portOutputRegister(port) ((volatile uint32_t*) sio_hw->gpio_out)
#define portInputRegister(port) ((volatile uint32_t*) sio_hw->gpio_in)
#define portModeRegister(port) ((volatile uint32_t*) sio_hw->gpio_oe)
#define portOutputRegister(port) ((volatile uint32_t *)&(sio_hw->gpio_out))
#define portInputRegister(port) ((volatile uint32_t *)&(sio_hw->gpio_in))
#define portModeRegister(port) ((volatile uint32_t *)&(sio_hw->gpio_oe))
#define digitalWriteFast(pin, val) (val ? sio_hw->gpio_set = (1 << pin) : sio_hw->gpio_clr = (1 << pin))
#define digitalReadFast(pin) ((1 << pin) & sio_hw->gpio_in)
#define sei() interrupts()
+32
View File
@@ -27,6 +27,7 @@
#include <hardware/structs/rosc.h>
#include <hardware/structs/systick.h>
#include <pico/multicore.h>
#include <hardware/dma.h>
#include <pico/rand.h>
#include <pico/util/queue.h>
#include <pico/bootrom.h>
@@ -311,6 +312,36 @@ public:
return id;
}
#pragma GCC push_options
#pragma GCC optimize ("Os")
void *memcpyDMA(void *dest, const void *src, size_t n) {
// Allocate a DMA channel on 1st call, reuse it every call after
if (memcpyDMAChannel < 1) {
memcpyDMAChannel = dma_claim_unused_channel(true);
dma_channel_config c = dma_channel_get_default_config(memcpyDMAChannel);
channel_config_set_transfer_data_size(&c, DMA_SIZE_32);
channel_config_set_read_increment(&c, true);
channel_config_set_write_increment(&c, true);
channel_config_set_irq_quiet(&c, true);
dma_channel_set_config(memcpyDMAChannel, &c, false);
}
// If there's any misalignment or too small, use regular memcpy which can handle it
if ((n < 64) || (((uint32_t)dest) | ((uint32_t)src) | n) & 3) {
return memcpy(dest, src, n);
}
int words = n / 4;
dma_channel_set_read_addr(memcpyDMAChannel, src, false);
dma_channel_set_write_addr(memcpyDMAChannel, dest, false);
dma_channel_set_trans_count(memcpyDMAChannel, words, false);
dma_channel_start(memcpyDMAChannel);
while (dma_channel_is_busy(memcpyDMAChannel)) {
/* busy wait dma */
}
return dest;
}
#pragma GCC pop_options
// Multicore comms FIFO
_MFIFO fifo;
@@ -336,4 +367,5 @@ private:
PIO _pio;
int _sm;
PIOProgram *_ccountPgm;
int memcpyDMAChannel = -1;
};
+3 -2
View File
@@ -36,6 +36,7 @@
#include <hardware/watchdog.h>
#include <pico/bootrom.h>
#include "sdkoverride/tusb_absmouse.h"
#include "sdkoverride/tusb_gamepad16.h"
#include <device/usbd_pvt.h>
// Big, global USB mutex, shared with all USB devices to make sure we don't
@@ -159,7 +160,7 @@ void __SetupDescHIDReport() {
//allocate memory for the HID report descriptors. We don't use them, but need the size here.
uint8_t desc_hid_report_mouse[] = { TUD_HID_REPORT_DESC_MOUSE(HID_REPORT_ID(1)) };
uint8_t desc_hid_report_absmouse[] = { TUD_HID_REPORT_DESC_ABSMOUSE(HID_REPORT_ID(1)) };
uint8_t desc_hid_report_joystick[] = { TUD_HID_REPORT_DESC_GAMEPAD(HID_REPORT_ID(1)) };
uint8_t desc_hid_report_joystick[] = { TUD_HID_REPORT_DESC_GAMEPAD16(HID_REPORT_ID(1)) };
uint8_t desc_hid_report_keyboard[] = { TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(1)), TUD_HID_REPORT_DESC_CONSUMER(HID_REPORT_ID(2)) };
int size = 0;
@@ -229,7 +230,7 @@ void __SetupDescHIDReport() {
reportid++;
offset += sizeof(desc_hid_report_absmouse);
}
uint8_t desc_local[] = { TUD_HID_REPORT_DESC_GAMEPAD(HID_REPORT_ID(reportid)) };
uint8_t desc_local[] = { TUD_HID_REPORT_DESC_GAMEPAD16(HID_REPORT_ID(reportid)) };
memcpy(__hid_report + offset, desc_local, sizeof(desc_local));
}
}
+2 -2
View File
@@ -1,5 +1,5 @@
#pragma once
#define ARDUINO_PICO_MAJOR 3
#define ARDUINO_PICO_MINOR 9
#define ARDUINO_PICO_REVISION 0
#define ARDUINO_PICO_VERSION_STR "3.9.0"
#define ARDUINO_PICO_REVISION 5
#define ARDUINO_PICO_VERSION_STR "3.9.5"
-2
View File
@@ -31,12 +31,10 @@ extern bool __isFreeRTOS;
extern volatile bool __freeRTOSinitted;
extern "C" {
#ifndef INC_FREERTOS_H
struct QueueDefinition; /* Using old naming convention so as not to break kernel aware debuggers. */
typedef struct QueueDefinition * QueueHandle_t;
typedef QueueHandle_t SemaphoreHandle_t;
typedef int32_t BaseType_t;
#endif
extern bool __freertos_check_if_in_isr() __attribute__((weak));
+3 -3
View File
@@ -247,19 +247,19 @@ extern "C" {
extern void __real_tcp_setprio(struct tcp_pcb *pcb, u8_t prio);
void __wrap_tcp_setprio(struct tcp_pcb *pcb, u8_t prio) {
LWIPMutex m;
return __real_tcp_setprio(pcb, prio);
__real_tcp_setprio(pcb, prio);
}
extern void __real_tcp_backlog_delayed(struct tcp_pcb* pcb);
void __wrap_tcp_backlog_delayed(struct tcp_pcb* pcb) {
LWIPMutex m;
return __real_tcp_backlog_delayed(pcb);
__real_tcp_backlog_delayed(pcb);
}
extern void __real_tcp_backlog_accepted(struct tcp_pcb* pcb);
void __wrap_tcp_backlog_accepted(struct tcp_pcb* pcb) {
LWIPMutex m;
return __real_tcp_backlog_accepted(pcb);
__real_tcp_backlog_accepted(pcb);
}
extern struct udp_pcb *__real_udp_new(void);
struct udp_pcb *__wrap_udp_new(void) {
+75
View File
@@ -0,0 +1,75 @@
/*
16-bit axis gamepad definition
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 "tusb.h"
#include "class/hid/hid_device.h"
#define TUD_HID_REPORT_DESC_GAMEPAD16(...) \
HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\
HID_USAGE ( HID_USAGE_DESKTOP_GAMEPAD ) ,\
HID_COLLECTION ( HID_COLLECTION_APPLICATION ) ,\
/* Report ID if any */\
__VA_ARGS__ \
/* 16 bit X, Y, Z, Rz, Rx, Ry (min -32767, max 32767 ) */ \
HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\
HID_USAGE ( HID_USAGE_DESKTOP_X ) ,\
HID_USAGE ( HID_USAGE_DESKTOP_Y ) ,\
HID_USAGE ( HID_USAGE_DESKTOP_Z ) ,\
HID_USAGE ( HID_USAGE_DESKTOP_RZ ) ,\
HID_USAGE ( HID_USAGE_DESKTOP_RX ) ,\
HID_USAGE ( HID_USAGE_DESKTOP_RY ) ,\
HID_LOGICAL_MIN_N ( -32767, 2 ) ,\
HID_LOGICAL_MAX_N ( 32767, 2 ) ,\
HID_REPORT_COUNT ( 6 ) ,\
HID_REPORT_SIZE ( 16 ) ,\
HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\
/* 8 bit DPad/Hat Button Map */ \
HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\
HID_USAGE ( HID_USAGE_DESKTOP_HAT_SWITCH ) ,\
HID_LOGICAL_MIN ( 1 ) ,\
HID_LOGICAL_MAX ( 8 ) ,\
HID_PHYSICAL_MIN ( 0 ) ,\
HID_PHYSICAL_MAX_N ( 315, 2 ) ,\
HID_REPORT_COUNT ( 1 ) ,\
HID_REPORT_SIZE ( 8 ) ,\
HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\
/* 32 bit Button Map */ \
HID_USAGE_PAGE ( HID_USAGE_PAGE_BUTTON ) ,\
HID_USAGE_MIN ( 1 ) ,\
HID_USAGE_MAX ( 32 ) ,\
HID_LOGICAL_MIN ( 0 ) ,\
HID_LOGICAL_MAX ( 1 ) ,\
HID_REPORT_COUNT ( 32 ) ,\
HID_REPORT_SIZE ( 1 ) ,\
HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\
HID_COLLECTION_END \
// HID Gamepad Protocol Report.
typedef struct TU_ATTR_PACKED {
int16_t x; ///< Delta x movement of left analog-stick
int16_t y; ///< Delta y movement of left analog-stick
int16_t z; ///< Delta z movement of right analog-joystick
int16_t rz; ///< Delta Rz movement of right analog-joystick
int16_t rx; ///< Delta Rx movement of analog left trigger
int16_t ry; ///< Delta Ry movement of analog right trigger
uint8_t hat; ///< Buttons mask for currently pressed buttons in the DPad/hat
uint32_t buttons; ///< Buttons mask for currently pressed buttons
} hid_gamepad16_report_t;
+39
View File
@@ -0,0 +1,39 @@
Bluetooth Audio (A2DP Source and Sink)
======================================
The PicoW can be used as a Bluetooth Audio sink or source with the ``BluetoothAudio`` class.
Operation is generally handled "automatically" in the background so while the audio is
playing or streaming the main application can perform other operations (like displaying
playback info, polling buttons for controls, etc.)
.. code :: cpp
#include <BluetoothAudio.h>
...
**Note about CPU usage:** Bluetooth SBC audio is a compressed format. That means
that it takes non-trivial amounts of CPU to compress on send, or decompress on receive.
Transmitting precompressed audio from, say, MP3 or AAC, requires first decompressing
the source file into raw PCM and then re-compressing them in the SBC format. You may
want to consider overclocking in this case to avoid underflow.
A2DPSink
--------
This class implements slave sink-mode operation with player control (play, pause, etc.) and
can play the received and decoded SBC audio to ``PWMAudio``, ``I2S``, or a user-created
`BluetoothAudioConsumer`` class.
The ``A2DPSink.ino`` example demonstrates turning a PicoW into a Bluetooth headset with
``PWMAudio``.
A2DPSource
-----------
This class implements a master source-mode SBC Bluetooth A2DP audio connection which
transmits audio using the standard ``Stream`` interface (like ``I2S`` or ``PWMAudio``.
The main application connects to a Bluetooth speaker and then writes samples into a buffer
that's automatically transmitted behind the scenes.
The ``A2DPSource.ino`` example shows how to connect to a Bluetooth speaker, transmit
data, and respond to commands from the speaker.
+1 -1
View File
@@ -120,7 +120,7 @@ Valid values for min and max are `BR_TLS10`, `BR_TLS11`, `BR_TLS12`. Min and ma
ESP32 Compatibility
===================
~~~~~~~~~~~~~~~~~~~
Simple ESP32 ``WiFiClientSecure`` compatibility is built-in, allow for some sketches to run without any modification.
The following methods are implemented:
+5 -3
View File
@@ -2,9 +2,6 @@ Bluetooth on PicoW Support
==========================
As of the Pico-SDK version 1.5.0, the PicoW has **BETA** Bluetooth support.
So, since this core builds off the SDK the best that can be suggested it
that we have **ALPHA** Bluetooth support. As such, bug reports are welcome,
but Pull Requests fixing problems you find are seriously appreciated.
Enabling Bluetooth
------------------
@@ -24,6 +21,11 @@ Bluetooth Low Energy (BLE) HID device using the same API as their USB versions.
The ``SerialBT`` library implements a very simple SPP (Serial Port Profile)
Serial-compatible port.
Connect and use Bluetooth peripherals with the PicoW using the
``BluetoothHIDMaster`` library.
``BluetoothAudio`` (A2DP) is also supported, both sink and source.
Writing Custom Bluetooth Applications
-------------------------------------
You may also write full applications using the ``BTStack`` standard callback
+2 -2
View File
@@ -54,9 +54,9 @@ author = u'Earle F. Philhower, III'
# built documents.
#
# The short X.Y version.
version = u'3.9.0'
version = u'3.9.5'
# The full version, including alpha/beta/rc tags.
release = u'3.9.0'
release = u'3.9.5'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
+2 -2
View File
@@ -598,7 +598,7 @@ Close the file. No other operations should be performed on *File* object
after ``close`` function was called.
openNextFile (compatibility method, not recommended for new code)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code:: cpp
@@ -610,7 +610,7 @@ Opens the next file in the directory pointed to by the File. Only valid
when ``File.isDirectory() == true``.
rewindDirectory (compatibility method, not recommended for new code)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code:: cpp
+208
View File
@@ -0,0 +1,208 @@
Bluetooth HID Master
====================
The PicoW can connect to a Bluetooth Classic or Bluetooth BLE keyboard,
mouse, or joystick and receive input events from it. As opposed to
the ``Keyboard``, ``Mouse``, and ``Joystick`` libraries, which make
the PicoW into a peripheral others can use, this lets the PicoW use the
same kinds of peripherals in a master rols.
BTDeviceInfo Class
------------------
The ``BluetoothHCI`` class implements a scanning function for classic
and BLE devices and can return a ``std::vector`` of discovered devices to an application.
Iterate over the list using any of the STL iteration methods (i.e. ``for (auto e : list)``).
The elements of this list are ``BTDeviceInfo`` objects which have the following
member functions:
``BTDeviceInfo::deviceClass()`` returns the Bluetooth BLE UUID or the Blustooth device
class for the device. This specifies the general class of the device (keyboard, mouse,
etc.).
``BTDeviceInfo::address()`` and ``BTDeviceInfo::addressString()`` return the
Bluetooth address as a binary array or a string that can be used to ``print``.
``BTDeviceInfo::addressType()`` returns whether the BLE address is random or not, and
is not generally needed by a user application.
``BTDeviceInfo::rssi()`` returns an approximate dB level for the device. Less
negative is stronger signal.
``BTDeviceInfo::name()`` returns the advertised name of the device, if present. Some
devices or scans do not return names for valid devices.
BluetoothHCI Class
------------------
The ``BluetoothHCI`` class is responsible for scanning for devices and the lower-level
housekeeping for a master-mode Bluetooth application. Most applications will not need
to access it directly, but it can be used to discover nearby BT devices. As
part of the application Bluetooth setup, call ``BluetoothHCI::install()`` and then
``BluetoothHCI::begin()`` to start BT processing. Your application is still responsible
for all the non-default HCI initialization and customization. See the ``BluetoothScanner.ino``
example for more info.
BluetoothHIDMaster Operation
----------------------------
Most applications will use the ``BluetoothHIDMaster`` class and, after connecting, receive
callbacks from the Bluetooth stack when input events (key presses, mouse moves, button
mashes) occur.
Application flow will generally be:
1. Install the appropriate callbacks using the ``BluetoothHIDMaster::onXXX`` methods
2. Start the Bluetooth processing with ``BluetoothHIDMaster::begin()`` or ``BluetoothHIDMaster::beginBLE()``
3. Connect to the first device found with ``BluetoothHIDMaster::connectXXX()`` and start receiving callbacks.
4. Callbacks will come at interrupt time and set global state variables, which the main ``loop()`` will process
Callback Event Handlers
-----------------------
The main application is informed of new inputs via a standard callback mechanism. These callbacks run at
interrupt time and should not do significant work, ``delay()``, or allocate or free memory. The most common
way of handling this is setting global ``volatile`` flags and variables that the main ``loop()`` will poll
and process.
Mouse Callbacks
~~~~~~~~~~~~~~~
The ``BluetoothHIDMaster::onMouseMove`` callback gets the delta X, Y, and wheel reported by the device.
The ``BluetoothHIDMaster::onMouseButton`` gets a button number and state (up or down) and will be called
each time an individual button is reported changed by the mouse.
.. code :: cpp
void mouseMoveCB(void *cbdata, int dx, int dy, int dw) {
// Process the deltas, adjust global mouse state
}
void mouseButtonCB(void *cbdata, int butt, bool down) {
// Set the global button array with this new info
}
Meyboard Callbacks
~~~~~~~~~~~~~~~~~~
The `BluetoothHIDMaster::onKeyDown`` callback receives the raw HID key (**NOT ASCII**) sent by the device on a key press
while `BluetoothHIDMaster::onKeyUp`` gets the same when a key is released. Note that up to 6 keys can be pressed at any
one time. For media keys ("consumer keys" in the USB HID documentation) the ``BluetoothHIDMaster::onConsumerKeyDown`` and
``BluetoothHIDMaster::onConsumerKeyUp`` perform the same function (and receive the consumer key IDs defined by the
USB HID spec and not ASCII).
To convert the key press and release (including SHIFT handling), use a ``HIDKeyStream`` object. Simply write the raw
HID key and the up/down state to the stream and read back the ASCII for use in an application.
.. code :: cpp
HIDKeyStream keystream;
void keyDownCB(void *cbdata, int key) {
keystream.write((uint8_t )key);
keystream.write((uint8_t) true); // Keystream now has 1 ASCII character to read out and use
char ascii = keystream.read();
// ....
}
void keyUpCB(void *cbdata, int key) {
// Normally don't do anything on this, the character was read in the keyDownCB
}
void consumerKeyDownCB(void *cbdata, int key) {
// switch(key) and use cases from the USB Consumer Key page
}
void consumerKeyUpCB(void *cbdata, int key) {
// switch(key) and use cases from the USB Consumer Key page
}
Joystick Callbacks
~~~~~~~~~~~~~~~~~~
A single ``BluetoothHIDMaster::onJoystick`` callback gets activated every time a report from a joystick is processed.
It receives (potentially, if supported by the device) 4 analog axes, one 8-way digital hat switch position, and up
to 32 button states at a time.
.. code :: cpp
void joystickCB(void *cbdata, int x, int y, int z, int rz, uint8_t hat, uint32_t buttons) {
// HAT 0 = UP and continues clockwise. If no hat direction it is set to 0x0f.
// Use "buttons & (1 << buttonNumber)" to look at the individual button states
// ...
}
PianoKeyboard Example
~~~~~~~~~~~~~~~~~~~~~
See the ``PianoKeyboard.ino`` and ``PianoKeyboardBLE.ino`` examples for more information on callback operation.
BluetoothHIDMaster::onXXX Callback Installers
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code :: cpp
void BluetoothHIDMaster::onMouseMove(void (*)(void *, int, int, int), void *cbData = nullptr);
void BluetoothHIDMaster::onMouseButton(void (*)(void *, int, bool), void *cbData = nullptr);
void BluetoothHIDMaster::onKeyDown(void (*)(void *, int), void *cbData = nullptr);
void BluetoothHIDMaster::onKeyUp(void (*)(void *, int), void *cbData = nullptr);
void BluetoothHIDMaster::onConsumerKeyDown(void (*)(void *, int), void *cbData = nullptr);
void BluetoothHIDMaster::onConsumerKeyUp(void (*)(void *, int), void *cbData = nullptr);
void BluetoothHIDMaster::onJoystick(void (*)(void *, int, int, int, int, uint8_t, uint32_t), void *cbData = nullptr);
BluetoothHIDMaster Class
------------------------
bool BluetoothHIDMaster::begin()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Installs and configures the Bluetooth Classic stack and starts processing events. No connections are made at this point.
When running in Classic mode, no BLE devices can be detected or used.
bool BluetoothHIDMaster::begin(const char *BLEName)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Installs and configures the Bluetooth BLE stack and starts processing events. No connections are made at this point.
When running in BLE mode, no Classic devices can be detected or used.
bool BluetoothHIDMaster::connected()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Returns if the Bluetooth stack is up and running and a connection to a device is currently active.
void BluetoothHIDMaster::end()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Disables the Bluetooth stack. Note that with the current Bluetooth implementation restarting the stack (i.e. calling ``begin()`` after ``end()``) is not stable and will not work. Consider storing state and rebooting completely if this is necessary.
bool BluetoothHIDMaster::running()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Returns if the Bluetooth stack is running at all. Does not indicate if there is an active connection or not.
bool BluetoothHIDMaster::hciRunning()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Returns if the Bluetooth stack has passed the initial HCI start up phase. Until this returns ``true`` no Bluetooth operations can be performed.
std::vector<BTDeviceInfo> BluetoothHIDMaster::scan(uint32_t mask, int scanTimeSec, bool async)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Passes through the ``BluetoothHCI::scan()`` function to manually scan for a list of nearby devices. If you want to connect to the first found device, this is not needed.
bool BluetoothHIDMaster::connect(const uint8_t *addr)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Start the connection process to the Bluetooth Classic device with the given MAC. Note that this returns immediately, but it may take several seconds until ``connected()`` reports that the connection has been established.
bool BluetoothHIDMaster::connectKeyboard(), connectMouse(), connectJoystick(), connectAny()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Connect to the first found specified Bluetooth Classic device type (or any HID device) in pairing mode. No need to call ``scan()`` or have an address.
bool BluetoothHIDMaster::connectBLE(const uint8_t *addr, int addrType)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Start the connection process to the Bluetooth BLE device with the given MAC. Note that this returns immediately, but it may take several seconds until ``connected()`` reports that the connection has been established.
bool BluetoothHIDMaster::connectBLE()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Connect to the first found BLE device that has a HID service UUID (keyboard, mouse, or joystick)
bool BluetoothHIDMaster::disconnect()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Shuts down the connection to the currently connected device.
void BluetoothHIDMaster::clearPairing()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Erases all Bluetooth keys from memory. This effectively "forgets" all pairing between devices and can help avoid issues with the beta Bluetooth stack in the SDK.
+4 -1
View File
@@ -44,9 +44,12 @@ For the latest version, always check https://github.com/earlephilhower/arduino-p
USB (Arduino and Adafruit_TinyUSB) <usb>
Multicore Processing <multicore>
Bluetooth (Alpha/Beta) <bluetooth>
Bluetooth <bluetooth>
Bluetooth HID Master <hidmaster>
Bluetooth Audio (A2DP) <a2dp>
Single File USB Drive <singlefile>
FatFSUSB - full FS access over USB <fatfsusb>
FreeRTOS SMP (multicore) <freertos>
+10
View File
@@ -99,3 +99,13 @@ bootloader.
void rp2040.rebootToBootloader()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Will reboot the RP2040 into USB UF2 upload mode.
DMA-based MEMCPY
----------------
The onboard DMA engines can copy 4-byte aligned quantities faster than the CPU.
void \*rp2040.memcpyDMA(void \*dest, const void \*src, size_t count);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Uses a DMA engine to transfer data from src to dest in 4-byte chunks without CPU
intervention. If any arguments are not 4-byte aligned, or if the count is not a
multiple of 4, then it will fall back to CPU-managed memcpy.
+1 -1
View File
@@ -1,2 +1,2 @@
// Do not edit -- Automatically generated by tools/sdk/ssl/bearssl/Makefile
#define BEARSSL_GIT f294aa0
#define BEARSSL_GIT 5166f2b
+16 -17
View File
@@ -14,6 +14,10 @@ extern void interrupts();
extern unsigned long __lwip_rand(void);
#define LWIP_RAND() __lwip_rand()
#ifndef __LWIP_MEMMULT
#define __LWIP_MEMMULT 1
#endif
// Common settings used in most of the pico_w examples
// (see https://www.nongnu.org/lwip/2_1_x/group__lwip__opts.html for details)
@@ -22,10 +26,10 @@ extern unsigned long __lwip_rand(void);
#define MEM_LIBC_MALLOC 0
#define MEM_ALIGNMENT 4
#define MEM_SIZE 16384
#define MEMP_NUM_TCP_SEG 32
#define MEMP_NUM_ARP_QUEUE 10
#define PBUF_POOL_SIZE 24
#define MEM_SIZE (__LWIP_MEMMULT * 16384)
#define MEMP_NUM_TCP_SEG (32)
#define MEMP_NUM_ARP_QUEUE (10)
#define PBUF_POOL_SIZE (__LWIP_MEMMULT > 1 ? 32 : 24)
#define LWIP_ARP 2
#define LWIP_ETHERNET 1
#define LWIP_ICMP 1
@@ -38,11 +42,12 @@ extern unsigned long __lwip_rand(void);
#define LWIP_NETIF_LINK_CALLBACK 1
#define LWIP_NETIF_HOSTNAME 1
#define LWIP_NETCONN 0
#define MEM_STATS 1
#define SYS_STATS 1
#define MEMP_STATS 1
#define LWIP_STATS 0
#define LWIP_STATS_DISPLAY 0
#define MEM_STATS 0
#define SYS_STATS 0
#define MEMP_STATS 0
#define LINK_STATS 0
// #define ETH_PAD_SIZE 2
#define LWIP_CHKSUM_ALGORITHM 0
#define LWIP_DHCP 1
#define LWIP_IPV4 1
@@ -55,7 +60,8 @@ extern unsigned long __lwip_rand(void);
#define LWIP_DHCP_DOES_ACD_CHECK 0
// See #1285
#define MEMP_NUM_UDP_PCB 6
#define MEMP_NUM_UDP_PCB (__LWIP_MEMMULT * 6)
#define MEMP_NUM_TCP_PCB (__LWIP_MEMMULT * 5)
#if LWIP_IPV6
#define LWIP_IPV6_DHCP6 1
@@ -68,14 +74,7 @@ extern void __setSystemTime(unsigned long long sec, unsigned long us);
//#define SNTP_SERVER_ADDRESS "pool.ntp.org"
#define SNTP_SERVER_DNS 1
#ifndef NDEBUG
#define LWIP_DEBUG 1
#define LWIP_STATS 1
#define LWIP_STATS_DISPLAY 1
#define MEMP_STATS 1
#define MEM_STATS 1
#endif
#define LWIP_DEBUG 0
#define ETHARP_DEBUG LWIP_DBG_OFF
#define NETIF_DEBUG LWIP_DBG_OFF
#define PBUF_DEBUG LWIP_DBG_OFF
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
+1
View File
@@ -166,6 +166,7 @@
-Wl,--wrap=tcp_arg
-Wl,--wrap=tcp_new
-Wl,--wrap=tcp_bind
-Wl,--wrap=tcp_listen
-Wl,--wrap=tcp_listen_with_backlog
-Wl,--wrap=tcp_accept
@@ -1,5 +1,5 @@
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <WiFi.h>
#include <LEAmDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
@@ -22,11 +22,11 @@ void setup() {
rp2040.restart();
}
// Port defaults to 8266
// ArduinoOTA.setPort(8266);
// Port defaults to 2040
// ArduinoOTA.setPort(2040);
// Hostname defaults to esp8266-[ChipID]
// ArduinoOTA.setHostname("myesp8266");
// Hostname defaults to pico-[ChipID]
// ArduinoOTA.setHostname("mypico");
// No authentication by default
// ArduinoOTA.setPassword("admin");
@@ -1,5 +1,5 @@
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <WiFi.h>
#include <LEAmDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
+8 -9
View File
@@ -22,23 +22,22 @@
#include <functional>
#include <WiFiUdp.h>
#include "ArduinoOTA.h"
#include "MD5Builder.h"
#include <MD5Builder.h>
#include <PicoOTA.h>
#include <StreamString.h>
#include "lwip/udp.h"
#include "include/UdpContext.h"
#include <lwip/udp.h>
#include <include/UdpContext.h>
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_MDNS)
#include <LEAmDNS.h>
#endif
//#ifdef DEBUG_ESP_OTA
//#ifdef DEBUG_ESP_PORT
//#define OTA_DEBUG DEBUG_ESP_PORT
//#endif
//#endif
#define OTA_DEBUG Serial
#ifdef DEBUG_RP2040_CORE
#ifdef DEBUG_RP2040_PORT
#define OTA_DEBUG DEBUG_RP2040_PORT
#endif
#endif
ArduinoOTAClass::ArduinoOTAClass() {
}
Submodule libraries/AsyncUDP added at 9a718fb3dc
@@ -24,8 +24,9 @@
#include <hardware/irq.h>
#include "AudioBufferManager.h"
static int __channelCount = 0; // # of channels left. When we hit 0, then remove our handler
static AudioBufferManager* __channelMap[12]; // Lets the IRQ handler figure out where to dispatch to
static int __channelCount = 0; // # of channels left. When we hit 0, then remove our handler
static AudioBufferManager* __channelMap[12]; // Lets the IRQ handler figure out where to dispatch to
static bool __irqInstalled = false; // Have we put in our IRQ handler yet?
AudioBufferManager::AudioBufferManager(size_t bufferCount, size_t bufferWords, int32_t silenceSample, PinMode direction, enum dma_channel_transfer_size dmaSize) {
_running = false;
@@ -78,11 +79,6 @@ AudioBufferManager::~AudioBufferManager() {
dma_channel_unclaim(_channelDMA[i]);
__channelCount--;
}
if (!__channelCount) {
irq_set_enabled(DMA_IRQ_0, false);
// TODO - how can we know if there are no other parts of the core using DMA0 IRQ??
irq_remove_handler(DMA_IRQ_0, _irq);
}
}
interrupts();
for (int i = 0; i < 2; i++) {
@@ -120,7 +116,7 @@ bool AudioBufferManager::begin(int dreq, volatile void *pioFIFOAddr) {
return false;
}
}
bool needSetIRQ = __channelCount == 0;
// Need to know both channels to set up ping-pong, so do in 2 stages
for (auto i = 0; i < 2; i++) {
dma_channel_config c = dma_channel_get_default_config(_channelDMA[i]);
@@ -146,9 +142,10 @@ bool AudioBufferManager::begin(int dreq, volatile void *pioFIFOAddr) {
__channelMap[_channelDMA[i]] = this;
__channelCount++;
}
if (needSetIRQ) {
if (!__irqInstalled) {
irq_add_shared_handler(DMA_IRQ_0, _irq, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
irq_set_enabled(DMA_IRQ_0, true);
__irqInstalled = true;
}
dma_channel_start(_channelDMA[0]);
+6
View File
@@ -526,6 +526,10 @@ const gatt_client_characteristic_t * BLECharacteristic::getCharacteristic(void)
return &characteristic;
}
gatt_client_notification_t *BLECharacteristic::getNotifier() {
return &notify;
}
BLEService::BLEService(void) {
}
@@ -703,6 +707,7 @@ int BTstackManager::writeCharacteristicWithoutResponse(BLEDevice * device, BLEC
}
int BTstackManager::subscribeForNotifications(BLEDevice * device, BLECharacteristic * characteristic) {
gattAction = gattActionSubscribe;
gatt_client_listen_for_characteristic_value_updates(characteristic->getNotifier(), gatt_client_callback, device->getHandle(), (gatt_client_characteristic_t*) characteristic->getCharacteristic());
return gatt_client_write_client_characteristic_configuration(gatt_client_callback, device->getHandle(), (gatt_client_characteristic_t*) characteristic->getCharacteristic(),
GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION);
}
@@ -713,6 +718,7 @@ int BTstackManager::subscribeForIndications(BLEDevice * device, BLECharacteristi
}
int BTstackManager::unsubscribeFromNotifications(BLEDevice * device, BLECharacteristic * characteristic) {
gattAction = gattActionUnsubscribe;
gatt_client_stop_listening_for_characteristic_value_updates(characteristic->getNotifier());
return gatt_client_write_client_characteristic_configuration(gatt_client_callback, device->getHandle(), (gatt_client_characteristic_t*) characteristic->getCharacteristic(),
GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NONE);
}
+2
View File
@@ -84,6 +84,7 @@ extern "C" {
class BLECharacteristic {
private:
gatt_client_notification_t notify;
gatt_client_characteristic_t characteristic;
UUID uuid;
public:
@@ -93,6 +94,7 @@ extern "C" {
bool matches(UUID * uuid);
bool isValueHandle(uint16_t value_handle);
const gatt_client_characteristic_t * getCharacteristic();
gatt_client_notification_t *getNotifier();
};
class BLEService {
@@ -43,9 +43,10 @@ void setup() {
a2dp.begin();
}
char *nowPlaying = nullptr;
void loop() {
if (BOOTSEL) {
__lockBluetooth();
if (status == A2DPSink::PAUSED) {
a2dp.play();
Serial.printf("Resuming\n");
@@ -53,7 +54,11 @@ void loop() {
a2dp.pause();
Serial.printf("Pausing\n");
}
__unlockBluetooth();
while (BOOTSEL);
}
if (!nowPlaying || strcmp(nowPlaying, a2dp.trackTitle())) {
free(nowPlaying);
nowPlaying = strdup(a2dp.trackTitle());
Serial.printf("NOW PLAYING: %s\n", nowPlaying);
}
}
+1 -9
View File
@@ -7,9 +7,7 @@
#######################################
BluetoothAudio KEYWORD1
BluetoothHCI KEYWORD1
A2DPSource KEYWORD1
BTDeviceInfo KEYWORD1
BluetoothAudioConsumer KEYWORD1
BluetoothAudioConsumerI2S KEYWORD1
BluetoothAudioConsumerPWM KEYWORD1
@@ -18,6 +16,7 @@ A2DPSink KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
scan KEYWORD2
begin KEYWORD2
end KEYWORD2
@@ -52,13 +51,6 @@ onVolume KEYWORD2
onConnect KEYWORD2
onPlaybackStatus KEYWORD2
# BTDeviceInfo
deviceClass KEYWORD2
address KEYWORD2
addressString KEYWORD2
rssi KEYWORD2
name KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################
@@ -8,3 +8,4 @@ category=Communication
url=http://github.com/earlephilhower/arduino-pico
architectures=rp2040
dot_a_linkage=true
depends=BluetoothHCI
+24 -6
View File
@@ -191,9 +191,10 @@ bool A2DPSink::begin() {
}
bool A2DPSink::disconnect() {
__lockBluetooth();
a2dp_sink_disconnect(a2dp_sink_a2dp_connection.a2dp_cid);
__unlockBluetooth();
BluetoothLock b;
if (_connected) {
a2dp_sink_disconnect(a2dp_sink_a2dp_connection.a2dp_cid);
}
if (!_running || !_connected) {
return false;
}
@@ -202,10 +203,11 @@ bool A2DPSink::disconnect() {
}
void A2DPSink::clearPairing() {
disconnect();
__lockBluetooth();
BluetoothLock b;
if (_connected) {
a2dp_sink_disconnect(a2dp_sink_a2dp_connection.a2dp_cid);
}
gap_delete_all_link_keys();
__unlockBluetooth();
}
@@ -557,6 +559,14 @@ void A2DPSink::avrcp_controller_packet_handler(uint8_t packet_type, uint16_t cha
break;
case AVRCP_SUBEVENT_NOTIFICATION_TRACK_CHANGED:
_title[0] = 0;
_artist[0] = 0;
_album[0] = 0;
_genre[0] = 0;
avrcp_controller_get_now_playing_info(avrcp_connection->avrcp_cid);
if (_trackChangedCB) {
_trackChangedCB(_trackChangedData);
}
DEBUGV("AVRCP Controller: Track changed\n");
break;
@@ -583,6 +593,8 @@ void A2DPSink::avrcp_controller_packet_handler(uint8_t packet_type, uint16_t cha
case AVRCP_SUBEVENT_NOW_PLAYING_TITLE_INFO:
if (avrcp_subevent_now_playing_title_info_get_value_len(packet) > 0) {
memcpy(avrcp_subevent_value, avrcp_subevent_now_playing_title_info_get_value(packet), avrcp_subevent_now_playing_title_info_get_value_len(packet));
strncpy(_title, (char *)avrcp_subevent_value, sizeof(_title));
_title[sizeof(_title) - 1] = 0;
DEBUGV("AVRCP Controller: Title %s\n", avrcp_subevent_value);
}
break;
@@ -590,6 +602,8 @@ void A2DPSink::avrcp_controller_packet_handler(uint8_t packet_type, uint16_t cha
case AVRCP_SUBEVENT_NOW_PLAYING_ARTIST_INFO:
if (avrcp_subevent_now_playing_artist_info_get_value_len(packet) > 0) {
memcpy(avrcp_subevent_value, avrcp_subevent_now_playing_artist_info_get_value(packet), avrcp_subevent_now_playing_artist_info_get_value_len(packet));
strncpy(_artist, (char *)avrcp_subevent_value, sizeof(_artist));
_artist[sizeof(_artist) - 1] = 0;
DEBUGV("AVRCP Controller: Artist %s\n", avrcp_subevent_value);
}
break;
@@ -597,6 +611,8 @@ void A2DPSink::avrcp_controller_packet_handler(uint8_t packet_type, uint16_t cha
case AVRCP_SUBEVENT_NOW_PLAYING_ALBUM_INFO:
if (avrcp_subevent_now_playing_album_info_get_value_len(packet) > 0) {
memcpy(avrcp_subevent_value, avrcp_subevent_now_playing_album_info_get_value(packet), avrcp_subevent_now_playing_album_info_get_value_len(packet));
strncpy(_album, (char *)avrcp_subevent_value, sizeof(_album));
_album[sizeof(_album) - 1] = 0;
DEBUGV("AVRCP Controller: Album %s\n", avrcp_subevent_value);
}
break;
@@ -604,6 +620,8 @@ void A2DPSink::avrcp_controller_packet_handler(uint8_t packet_type, uint16_t cha
case AVRCP_SUBEVENT_NOW_PLAYING_GENRE_INFO:
if (avrcp_subevent_now_playing_genre_info_get_value_len(packet) > 0) {
memcpy(avrcp_subevent_value, avrcp_subevent_now_playing_genre_info_get_value(packet), avrcp_subevent_now_playing_genre_info_get_value_len(packet));
strncpy(_genre, (char *)avrcp_subevent_value, sizeof(_genre));
_genre[sizeof(_genre) - 1] = 0;
DEBUGV("AVRCP Controller: Genre %s\n", avrcp_subevent_value);
}
break;
+46 -2
View File
@@ -21,7 +21,8 @@
#pragma once
#include <Arduino.h>
#include "BluetoothHCI.h"
#include <BluetoothHCI.h>
#include <BluetoothLock.h>
#include "BluetoothAudioConsumer.h"
#include "BluetoothMediaConfigurationSBC.h"
@@ -32,6 +33,10 @@
class A2DPSink : public Stream {
public:
A2DPSink() {
_title[0] = 0;
_artist[0] = 0;
_album[0] = 0;
_genre[0] = 0;
}
virtual int available() override {
return 0; // Unreadable, this is output only
@@ -95,6 +100,11 @@ public:
_connectData = cbData;
}
void onTrackChanged(void (*cb)(void *), void *cbData = nullptr) {
_trackChangedCB = cb;
_trackChangedData = cbData;
}
typedef enum { STOPPED, PLAYING, PAUSED } PlaybackStatus;
void onPlaybackStatus(void (*cb)(void *, PlaybackStatus), void *cbData = nullptr) {
_playbackStatusCB = cb;
@@ -120,65 +130,91 @@ public:
void playback_handler(int16_t * buffer, uint16_t num_audio_frames);
void play() {
BluetoothLock b;
if (_connected) {
avrcp_controller_play(a2dp_sink_avrcp_connection.avrcp_cid);
}
}
void stop() {
BluetoothLock b;
if (_connected) {
avrcp_controller_stop(a2dp_sink_avrcp_connection.avrcp_cid);
}
}
void pause() {
BluetoothLock b;
if (_connected) {
avrcp_controller_pause(a2dp_sink_avrcp_connection.avrcp_cid);
}
}
void fastForward() {
BluetoothLock b;
if (_connected) {
avrcp_controller_fast_forward(a2dp_sink_avrcp_connection.avrcp_cid);
}
}
void rewind() {
BluetoothLock b;
if (_connected) {
avrcp_controller_rewind(a2dp_sink_avrcp_connection.avrcp_cid);
}
}
void forward() {
BluetoothLock b;
if (_connected) {
avrcp_controller_forward(a2dp_sink_avrcp_connection.avrcp_cid);
}
}
void backward() {
BluetoothLock b;
if (_connected) {
avrcp_controller_backward(a2dp_sink_avrcp_connection.avrcp_cid);
}
}
void volumeUp() {
BluetoothLock b;
if (_connected) {
avrcp_controller_volume_up(a2dp_sink_avrcp_connection.avrcp_cid);
}
}
void volumeDown() {
BluetoothLock b;
if (_connected) {
avrcp_controller_volume_down(a2dp_sink_avrcp_connection.avrcp_cid);
}
}
void mute() {
BluetoothLock b;
if (_connected) {
avrcp_controller_mute(a2dp_sink_avrcp_connection.avrcp_cid);
}
}
const char *trackTitle() {
return _title;
}
const char *trackArtist() {
return _artist;
}
const char *trackAlbum() {
return _album;
}
const char *trackGenre() {
return _genre;
}
private:
void handle_pcm_data(int16_t * data, int num_audio_frames, int num_channels, int sample_rate, void * context);
@@ -195,7 +231,7 @@ private:
void avrcp_target_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
void a2dp_sink_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
BluetoothHCI_ _hci;
BluetoothHCI _hci;
BluetoothAudioConsumer_ *_consumer = nullptr;
bool _running = false;
bool _connected = false;
@@ -213,6 +249,9 @@ private:
void *_connectData;
void (*_playbackStatusCB)(void *, PlaybackStatus) = nullptr;
void *_playbackStatusData;
void (*_trackChangedCB)(void *) = nullptr;
void *_trackChangedData;
char *_name = nullptr;
uint8_t _sourceAddress[6];
@@ -290,4 +329,9 @@ private:
a2dp_sink_avrcp_connection_t a2dp_sink_avrcp_connection;
int16_t output_buffer[(128 + 16) * NUM_CHANNELS]; // 16 * 8 * 2
char _title[64];
char _artist[64];
char _album[64];
char _genre[32];
};
+12 -11
View File
@@ -196,11 +196,11 @@ bool A2DPSource::connect(const uint8_t *addr) {
return false;
}
}
bool A2DPSource::disconnect() {
__lockBluetooth();
a2dp_source_disconnect(media_tracker.a2dp_cid);
__unlockBluetooth();
BluetoothLock b;
if (_connected) {
a2dp_source_disconnect(media_tracker.a2dp_cid);
}
if (!_running || !_connected) {
return false;
}
@@ -209,17 +209,20 @@ bool A2DPSource::disconnect() {
}
void A2DPSource::clearPairing() {
disconnect();
__lockBluetooth();
BluetoothLock b;
if (_connected) {
a2dp_source_disconnect(media_tracker.a2dp_cid);
}
gap_delete_all_link_keys();
__unlockBluetooth();
}
// from Print (see notes on write() methods below)
size_t A2DPSource::write(const uint8_t *buffer, size_t size) {
BluetoothLock b;
size_t count = 0;
size /= 2;
__lockBluetooth();
// First copy from writer to either end of
uint32_t start = _pcmWriter;
uint32_t end = _pcmReader > _pcmWriter ? _pcmReader : _pcmBufferSize;
@@ -244,13 +247,12 @@ size_t A2DPSource::write(const uint8_t *buffer, size_t size) {
_pcmWriter += end - start;
_pcmWriter %= _pcmBufferSize;
}
__unlockBluetooth();
return count;
}
int A2DPSource::availableForWrite() {
BluetoothLock b;
int avail = 0;
__lockBluetooth();
if (_pcmWriter == _pcmReader) {
avail = _pcmBufferSize - 1;
} else if (_pcmReader > _pcmWriter) {
@@ -258,7 +260,6 @@ int A2DPSource::availableForWrite() {
} else {
avail = _pcmBufferSize - _pcmWriter + _pcmReader - 1;
}
__unlockBluetooth();
return avail;
}
+5 -5
View File
@@ -21,7 +21,8 @@
#pragma once
#include <Arduino.h>
#include "BluetoothHCI.h"
#include <BluetoothHCI.h>
#include <BluetoothLock.h>
#include "BluetoothMediaConfigurationSBC.h"
#include <functional>
#include <list>
@@ -84,13 +85,12 @@ public:
}
bool getUnderflow() {
BluetoothLock b;
if (!_running) {
return false;
}
__lockBluetooth();
auto ret = _underflow;
_underflow = false;
__unlockBluetooth();
return ret;
}
@@ -104,7 +104,7 @@ public:
bool begin();
std::list<BTDeviceInfo> scan(uint32_t mask = BluetoothHCI_::speaker_cod, int scanTimeSec = 5, bool async = false) {
std::vector<BTDeviceInfo> scan(uint32_t mask = BluetoothHCI::speaker_cod, int scanTimeSec = 5, bool async = false) {
return _hci.scan(mask, scanTimeSec, async);
}
@@ -144,7 +144,7 @@ public:
private:
BluetoothHCI_ _hci;
BluetoothHCI _hci;
bool _running = false;
bool _connected = false;
@@ -1,5 +1,7 @@
#include "BluetoothDevice.h"
#include "BluetoothHCI.h"
#include <_needsbt.h>
#include <BluetoothLock.h>
#include <BluetoothDevice.h>
#include <BluetoothHCI.h>
#include "BluetoothMediaConfigurationSBC.h"
#include "A2DPSource.h"
#include "A2DPSink.h"
@@ -75,10 +75,8 @@ void BluetoothAudioConsumerI2S::fill() {
_a2dpSink->playback_handler((int16_t *) buff, 32);
num_samples -= 64;
for (int i = 0; i < 64; i++) {
int32_t tmp = buff[i];
tmp *= _gain;
tmp >>= 8;
_i2s->write((int16_t)tmp);
buff[i] = (((int32_t)buff[i]) * _gain) >> 8;
}
_i2s->write((uint8_t *)buff, 64 * 2);
}
}
@@ -76,10 +76,8 @@ void BluetoothAudioConsumerPWM::fill() {
_a2dpSink->playback_handler((int16_t *) buff, 32);
num_samples -= 64;
for (int i = 0; i < 64; i++) {
int32_t tmp = buff[i];
tmp *= _gain;
tmp >>= 8;
_pwm->write((int16_t)tmp);
buff[i] = (((int32_t)buff[i]) * _gain) >> 8;
}
_pwm->write((uint8_t *)buff, 64 * 2);
}
}
@@ -1,162 +0,0 @@
/*
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
*/
#include <Arduino.h>
#include "btstack.h"
#include <list>
#include <memory>
#include "BluetoothHCI.h"
#define CCALLBACKNAME _BTHCICB
#include <ctocppcallback.h>
#define PACKETHANDLERCB(class, cbFcn) \
(_BTHCICB<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>(_BTHCICB<void(uint8_t, uint16_t, uint8_t*, uint16_t), __COUNTER__ - 1>::callback))
void BluetoothHCI_::install() {
// Register for HCI events.
hci_event_callback_registration.callback = PACKETHANDLERCB(BluetoothHCI_, hci_packet_handler);
hci_add_event_handler(&hci_event_callback_registration);
}
void BluetoothHCI_::begin() {
_running = true;
hci_power_control(HCI_POWER_ON);
}
void BluetoothHCI_::uninstall() {
__lockBluetooth();
hci_remove_event_handler(&hci_event_callback_registration);
_running = false;
__unlockBluetooth();
}
bool BluetoothHCI_::running() {
return _hciRunning;
}
std::list<BTDeviceInfo> BluetoothHCI_::scan(uint32_t mask, int scanTimeSec, bool async) {
_scanMask = mask;
_btdList.clear();
if (!_running) {
return _btdList;
}
_scanning = true;
while (!_hciRunning) {
DEBUGV("HCI::scan(): Waiting for HCI to come up\n");
delay(10);
}
int inquiryTime = (scanTimeSec * 1000) / 1280; // divide by 1.280
if (!inquiryTime) {
inquiryTime = 1;
}
DEBUGV("HCI::scan(): inquiry start\n");
__lockBluetooth();
gap_inquiry_start(inquiryTime);
__unlockBluetooth();
if (async) {
return _btdList;
}
while (_scanning) {
delay(10);
}
DEBUGV("HCI::scan(): inquiry end\n");
return _btdList;
}
bool BluetoothHCI_::scanAsyncDone() {
return _scanning;
}
std::list<BTDeviceInfo> BluetoothHCI_::scanAsyncResult() {
return _btdList;
}
void BluetoothHCI_::hci_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) {
return;
}
bd_addr_t address;
uint32_t cod;
int8_t rssi;
const char *name;
char name_buffer[241];
switch (hci_event_packet_get_type(packet)) {
case BTSTACK_EVENT_STATE:
_hciRunning = (btstack_event_state_get_state(packet) == HCI_STATE_WORKING);
break;
case HCI_EVENT_PIN_CODE_REQUEST:
hci_event_pin_code_request_get_bd_addr(packet, address);
gap_pin_code_response(address, "0000");
break;
case GAP_EVENT_INQUIRY_RESULT:
if (!_scanning) {
return;
}
gap_event_inquiry_result_get_bd_addr(packet, address);
cod = gap_event_inquiry_result_get_class_of_device(packet);
if (gap_event_inquiry_result_get_rssi_available(packet)) {
rssi = gap_event_inquiry_result_get_rssi(packet);
} else {
rssi = -128;
}
if (gap_event_inquiry_result_get_name_available(packet)) {
int name_len = gap_event_inquiry_result_get_name_len(packet);
memcpy(name_buffer, gap_event_inquiry_result_get_name(packet), name_len);
name_buffer[name_len] = 0;
name = name_buffer;
} else {
name = "";
}
DEBUGV("HCI: Scan found '%s', COD 0x%08X, RSSI %d, MAC %02X:%02X:%02X:%02X:%02X:%02X\n", name, (unsigned int)cod, rssi, address[0], address[1], address[2], address[3], address[4], address[5]);
if ((_scanMask & cod) == _scanMask) {
// Sometimes we get multiple reports for the same MAC, so remove any old reports since newer will have newer RSSI
bool updated = false;
for (auto itr = _btdList.begin(); (itr != _btdList.end()) && !updated; itr++) {
if (!memcmp(itr->address(), address, sizeof(address))) {
// Sometimes the name is missing on reports, so if we found it once preserve it
if (!name_buffer[0] && itr->name()[0]) {
strcpy(name_buffer, itr->name());
}
_btdList.erase(itr);
updated = true;
}
}
BTDeviceInfo btd(cod, address, rssi, name);
_btdList.push_back(btd);
}
break;
case GAP_EVENT_INQUIRY_COMPLETE:
_scanning = false;
break;
default:
break;
}
}
@@ -0,0 +1,34 @@
#include <BluetoothHCI.h>
BluetoothHCI hci;
void BTBasicSetup() {
l2cap_init();
gatt_client_init();
sm_init();
sm_set_io_capabilities(IO_CAPABILITY_NO_INPUT_NO_OUTPUT);
gap_set_default_link_policy_settings(LM_LINK_POLICY_ENABLE_SNIFF_MODE | LM_LINK_POLICY_ENABLE_ROLE_SWITCH);
hci_set_master_slave_policy(HCI_ROLE_MASTER);
hci_set_inquiry_mode(INQUIRY_MODE_RSSI_AND_EIR);
hci.setBLEName("Pico BLE Scanner");
hci.install();
hci.begin();
}
void setup() {
delay(5000);
BTBasicSetup();
}
void loop() {
Serial.printf("BEGIN BLE SCAN @%lu ...", millis());
auto l = hci.scanBLE(BluetoothHCI::any_cod);
Serial.printf("END BLE SCAN @%lu\n\n", millis());
Serial.printf("%-8s | %-17s | %-4s | %s\n", "Class", "Address", "RSSI", "Name");
Serial.printf("%-8s | %-17s | %-4s | %s\n", "--------", "-----------------", "----", "----------------");
for (auto e : l) {
Serial.printf("%08lx | %17s | %4d | %s\n", e.deviceClass(), e.addressString(), e.rssi(), e.name());
}
Serial.printf("\n\n\n");
}
@@ -0,0 +1,31 @@
#include <BluetoothHCI.h>
BluetoothHCI hci;
void BTBasicSetup() {
l2cap_init();
sm_init();
gap_set_default_link_policy_settings(LM_LINK_POLICY_ENABLE_SNIFF_MODE | LM_LINK_POLICY_ENABLE_ROLE_SWITCH);
hci_set_master_slave_policy(HCI_ROLE_MASTER);
hci_set_inquiry_mode(INQUIRY_MODE_RSSI_AND_EIR);
hci.install();
hci.begin();
}
void setup() {
delay(5000);
BTBasicSetup();
}
void loop() {
Serial.printf("BEGIN SCAN @%lu ...", millis());
auto l = hci.scan(BluetoothHCI::any_cod);
Serial.printf("END SCAN @%lu\n\n", millis());
Serial.printf("%-8s | %-17s | %-4s | %s\n", "Class", "Address", "RSSI", "Name");
Serial.printf("%-8s | %-17s | %-4s | %s\n", "--------", "-----------------", "----", "----------------");
for (auto e : l) {
Serial.printf("%08lx | %17s | %4d | %s\n", e.deviceClass(), e.addressString(), e.rssi(), e.name());
}
Serial.printf("\n\n\n");
}
+36
View File
@@ -0,0 +1,36 @@
#######################################
# Syntax Coloring Map
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
BluetoothHCI KEYWORD1
BTDeviceInfo KEYWORD1
BluetoothLock KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
begin KEYWORD2
end KEYWORD2
scan KEYWORD2
scanAsyncDone KEYWORD2
scanAsyncResult KEYWORD2
setName KEYWORD2
scanBLE KEYWORD2
# BTDeviceInfo
deviceClass KEYWORD2
address KEYWORD2
addressType KEYWORD2
addressString KEYWORD2
rssi KEYWORD2
name KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################
+10
View File
@@ -0,0 +1,10 @@
name=BluetoothHCI
version=1.0
author=Earle F. Philhower, III <earlephilhower@yahoo.com>
maintainer=Earle F. Philhower, III <earlephilhower@yahoo.com>
sentence=Classic Bluetooth HCI functionality
paragraph=Classic Bluetooth HCI functionality
category=Communication
url=http://github.com/earlephilhower/arduino-pico
architectures=rp2040
dot_a_linkage=true
@@ -24,43 +24,60 @@
class BTDeviceInfo {
public:
// Classic Bluetooth Device
BTDeviceInfo(uint32_t dc, const uint8_t addr[6], int rssi, const char *name) {
_deviceClass = dc;
memcpy(_address, addr, sizeof(_address));
_addressType = -1;
sprintf(_addressString, "%02x:%02x:%02x:%02x:%02x:%02x", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
_rssi = rssi;
_name = strdup(name);
strncpy(_name, name, sizeof(_name));
_name[sizeof(_name) - 1] = 0;
}
// Copy constructor to ensure we deep-copy the string
BTDeviceInfo(const BTDeviceInfo &b) {
_deviceClass = b._deviceClass;
memcpy(_address, b._address, sizeof(_address));
memcpy(_addressString, b._addressString, sizeof(_addressString));
_rssi = b._rssi;
_name = strdup(b._name);
// Bluetooth BLE Device
BTDeviceInfo(uint32_t dc, const uint8_t addr[6], int addressType, int rssi, const char *name, size_t nameLen) {
_deviceClass = dc;
memcpy(_address, addr, sizeof(_address));
sprintf(_addressString, "%02x:%02x:%02x:%02x:%02x:%02x", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
_addressType = addressType;
_rssi = rssi;
memcpy(_name, name, std::min(nameLen, sizeof(_name)));
_name[std::min(nameLen, sizeof(_name) - 1)] = 0;
}
~BTDeviceInfo() {
free(_name);
}
uint32_t deviceClass() {
return _deviceClass;
}
const uint8_t *address() {
return _address;
}
const char *addressString() {
return _addressString;
}
int rssi() {
return _rssi;
}
const char *name() {
return _name;
}
int addressType() {
return _addressType;
}
private:
uint32_t _deviceClass;
uint8_t _address[6];
int _addressType;
char _addressString[18];
int8_t _rssi;
char *_name;
char _name[241];
};
+443
View File
@@ -0,0 +1,443 @@
/*
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
*/
#include <Arduino.h>
#include "btstack.h"
#include <list>
#include <memory>
#include "BluetoothHCI.h"
#include "BluetoothLock.h"
#define CCALLBACKNAME _BTHCICB
#include <ctocppcallback.h>
#define PACKETHANDLERCB(class, cbFcn) \
(_BTHCICB<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>(_BTHCICB<void(uint8_t, uint16_t, uint8_t*, uint16_t), __COUNTER__ - 1>::callback))
void BluetoothHCI::setBLEName(const char *name) {
if (_att) {
free(_att);
_att = nullptr;
}
_att = (uint8_t *)malloc(1 + 0x0a + 0x0d + 0x08 + strlen(name) + 0x02);
uint8_t *ptr = _att;
const uint8_t head[] = {
// ATT DB Version
1,
// 0x0001 PRIMARY_SERVICE-GAP_SERVICE
0x0a, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x28, 0x00, 0x18,
// 0x0002 CHARACTERISTIC-GAP_DEVICE_NAME - READ
0x0d, 0x00, 0x02, 0x00, 0x02, 0x00, 0x03, 0x28, 0x02, 0x03, 0x00, 0x00, 0x2a
};
memcpy(ptr, head, sizeof(head));
ptr += sizeof(head);
*ptr++ = 8 + strlen(name); // len of item
*ptr++ = 0x00;
*ptr++ = 0x02;
*ptr++ = 0x00;
*ptr++ = 0x03;
*ptr++ = 0x00;
*ptr++ = 0x00;
*ptr++ = 0x2a;
memcpy(ptr, name, strlen(name));
ptr += strlen(name);
// End it
*ptr++ = 0x00;
*ptr++ = 0x00;
DEBUGV("ATTDB: ");
for (size_t i = 0; i < 1 + 0x0a + 0x0d + 0x08 + strlen(name) + 0x02; i++) {
DEBUGV("%02X ", _att[i]);
}
DEBUGV("\n");
}
void BluetoothHCI::install() {
hci_set_inquiry_mode(INQUIRY_MODE_RSSI_AND_EIR);
// Register for HCI events.
hci_event_callback_registration.callback = PACKETHANDLERCB(BluetoothHCI, hci_packet_handler);
hci_add_event_handler(&hci_event_callback_registration);
// BLE set our visible name
if (_att) {
att_server_init(_att, nullptr, nullptr);
}
}
void BluetoothHCI::begin() {
_running = true;
hci_power_control(HCI_POWER_ON);
}
void BluetoothHCI::uninstall() {
BluetoothLock b;
hci_remove_event_handler(&hci_event_callback_registration);
_running = false;
free(_att);
_att = nullptr;
}
bool BluetoothHCI::running() {
return _hciRunning;
}
std::vector<BTDeviceInfo> BluetoothHCI::scan(uint32_t mask, int scanTimeSec, bool async) {
_scanMask = mask;
_btdList.reserve(MAX_DEVICES_TO_DISCOVER);
_btdList.clear();
if (!_running) {
return _btdList;
}
_scanning = true;
while (!_hciRunning) {
DEBUGV("HCI::scan(): Waiting for HCI to come up\n");
delay(10);
}
int inquiryTime = (scanTimeSec * 1000) / 1280; // divide by 1.280
if (!inquiryTime) {
inquiryTime = 1;
}
DEBUGV("HCI::scan(): inquiry start\n");
// Only need to lock around the inquiry start command, not the wait
{
BluetoothLock b;
gap_inquiry_start(inquiryTime);
}
if (async) {
return _btdList;
}
while (_scanning) {
delay(10);
}
DEBUGV("HCI::scan(): inquiry end\n");
return _btdList;
}
std::vector<BTDeviceInfo> BluetoothHCI::scanBLE(uint32_t uuid, int scanTimeSec) {
_scanMask = uuid;
_btdList.reserve(MAX_DEVICES_TO_DISCOVER);
_btdList.clear();
if (!_running) {
return _btdList;
}
_scanning = true;
while (!_hciRunning) {
DEBUGV("HCI::scanBLE(): Waiting for HCI to come up\n");
delay(10);
}
uint32_t inquiryTime = scanTimeSec * 1000;
if (!inquiryTime) {
inquiryTime = 1000;
}
DEBUGV("HCI::scan(): BLE advertise inquiry start\n");
// Only need to lock around the inquiry start command, not the wait
{
BluetoothLock b;
gap_set_scan_params(0, 75, 50, 0); // TODO - anything better for these params?
gap_start_scan();
}
uint32_t scanStart = millis();
while (_scanning && ((millis() - scanStart) < inquiryTime)) {
delay(10);
}
DEBUGV("HCI::scanBLE(): inquiry end\n");
gap_stop_scan();
return _btdList;
}
void BluetoothHCI::scanFree() {
_btdList.clear();
_btdList.shrink_to_fit();
}
void BluetoothHCI::parse_advertisement_data(uint8_t *packet) {
bd_addr_t address;
gap_event_advertising_report_get_address(packet, address);
//int event_type = gap_event_advertising_report_get_advertising_event_type(packet);
int address_type = gap_event_advertising_report_get_address_type(packet);
int8_t rssi = gap_event_advertising_report_get_rssi(packet);
uint8_t adv_size = gap_event_advertising_report_get_data_length(packet);
const uint8_t * adv_data = gap_event_advertising_report_get_data(packet);
uint16_t uuid = 0;
const char *nameptr = nullptr;
int namelen = 0;
ad_context_t context;
uint8_t uuid_128[16];
for (ad_iterator_init(&context, adv_size, (uint8_t *)adv_data) ; ad_iterator_has_more(&context) ; ad_iterator_next(&context)) {
uint8_t data_type = ad_iterator_get_data_type(&context);
uint8_t size = ad_iterator_get_data_len(&context);
const uint8_t * data = ad_iterator_get_data(&context);
// if (data_type > 0 && data_type < 0x1B){
// printf(" %s: ", ad_types[data_type]);
// }
int i;
// Assigned Numbers GAP
switch (data_type) {
case BLUETOOTH_DATA_TYPE_FLAGS:
// show only first octet, ignore rest
//for (i=0; i<8;i++){
// if (data[0] & (1<<i)){
// printf("%s; ", flags[i]);
// }
//}
break;
case BLUETOOTH_DATA_TYPE_INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS:
case BLUETOOTH_DATA_TYPE_COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS:
case BLUETOOTH_DATA_TYPE_LIST_OF_16_BIT_SERVICE_SOLICITATION_UUIDS:
for (i = 0; i < size; i += 2) {
uint16_t thisuuid = little_endian_read_16(data, i);
if (!_scanMask || (_scanMask == thisuuid)) {
uuid = thisuuid;
}
DEBUGV("uuid %02X ", thisuuid);
}
break;
case BLUETOOTH_DATA_TYPE_INCOMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS:
case BLUETOOTH_DATA_TYPE_COMPLETE_LIST_OF_32_BIT_SERVICE_CLASS_UUIDS:
case BLUETOOTH_DATA_TYPE_LIST_OF_32_BIT_SERVICE_SOLICITATION_UUIDS:
for (i = 0; i < size; i += 4) {
uint32_t thisuuid = little_endian_read_32(data, i);
if (!_scanMask || (_scanMask == thisuuid)) {
uuid = thisuuid;
}
DEBUGV("%04" PRIX32, thisuuid);
}
break;
case BLUETOOTH_DATA_TYPE_INCOMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS:
case BLUETOOTH_DATA_TYPE_COMPLETE_LIST_OF_128_BIT_SERVICE_CLASS_UUIDS:
case BLUETOOTH_DATA_TYPE_LIST_OF_128_BIT_SERVICE_SOLICITATION_UUIDS:
// Unhandled yet
reverse_128(data, uuid_128);
DEBUGV("%s", uuid128_to_str(uuid_128));
break;
case BLUETOOTH_DATA_TYPE_SHORTENED_LOCAL_NAME:
if (!nameptr) {
nameptr = (const char *)data;
namelen = size;
}
for (i = 0; i < size; i++) {
DEBUGV("%c", (char)(data[i]));
}
break;
case BLUETOOTH_DATA_TYPE_COMPLETE_LOCAL_NAME:
nameptr = (const char *)data;
namelen = size;
for (i = 0; i < size; i++) {
DEBUGV("%c", (char)(data[i]));
}
break;
case BLUETOOTH_DATA_TYPE_TX_POWER_LEVEL:
DEBUGV("%d dBm", *(int8_t*)data);
break;
case BLUETOOTH_DATA_TYPE_SLAVE_CONNECTION_INTERVAL_RANGE:
DEBUGV("Connection Interval Min = %u ms, Max = %u ms", little_endian_read_16(data, 0) * 5 / 4, little_endian_read_16(data, 2) * 5 / 4);
break;
case BLUETOOTH_DATA_TYPE_SERVICE_DATA:
//printf_hexdump(data, size);
break;
case BLUETOOTH_DATA_TYPE_PUBLIC_TARGET_ADDRESS:
case BLUETOOTH_DATA_TYPE_RANDOM_TARGET_ADDRESS:
reverse_bd_addr(data, address);
DEBUGV("%s", bd_addr_to_str(address));
break;
case BLUETOOTH_DATA_TYPE_APPEARANCE:
// https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.gap.appearance.xml
DEBUGV("%02X", little_endian_read_16(data, 0));
break;
case BLUETOOTH_DATA_TYPE_ADVERTISING_INTERVAL:
DEBUGV("%u ms", little_endian_read_16(data, 0) * 5 / 8);
break;
case BLUETOOTH_DATA_TYPE_3D_INFORMATION_DATA:
//printf_hexdump(data, size);
break;
case BLUETOOTH_DATA_TYPE_MANUFACTURER_SPECIFIC_DATA: // Manufacturer Specific Data
break;
case BLUETOOTH_DATA_TYPE_CLASS_OF_DEVICE:
case BLUETOOTH_DATA_TYPE_SIMPLE_PAIRING_HASH_C:
case BLUETOOTH_DATA_TYPE_SIMPLE_PAIRING_RANDOMIZER_R:
case BLUETOOTH_DATA_TYPE_DEVICE_ID:
case BLUETOOTH_DATA_TYPE_SECURITY_MANAGER_OUT_OF_BAND_FLAGS:
default:
DEBUGV("Advertising Data Type 0x%2x not handled yet", data_type);
break;
}
DEBUGV("\n");
}
DEBUGV("\n");
if (uuid) {
bool seen = false;
for (auto itr = _btdList.begin(); (itr != _btdList.end()) && !seen; itr++) {
if (!memcmp(itr->address(), address, sizeof(address))) {
seen = true;
}
}
if (!seen) {
BTDeviceInfo btd(uuid, address, address_type, rssi, nameptr ? nameptr : "", nameptr ? namelen : 0);
if (_btdList.size() < MAX_DEVICES_TO_DISCOVER) {
_btdList.push_back(btd);
}
}
}
}
static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
UNUSED(packet_type);
UNUSED(channel);
UNUSED(size);
gatt_client_service_t service;
gatt_client_characteristic_t characteristic;
switch (hci_event_packet_get_type(packet)) {
case GATT_EVENT_SERVICE_QUERY_RESULT:
gatt_event_service_query_result_get_service(packet, &service);
// dump_service(&service);
// services[service_count++] = service;
break;
case GATT_EVENT_CHARACTERISTIC_QUERY_RESULT:
gatt_event_characteristic_query_result_get_characteristic(packet, &characteristic);
// dump_characteristic(&characteristic);
break;
case GATT_EVENT_QUERY_COMPLETE:
// GATT_EVENT_QUERY_COMPLETE of search characteristics
// if (service_index < service_count) {
// service = services[service_index++];
// printf("\nGATT browser - CHARACTERISTIC for SERVICE %s, [0x%04x-0x%04x]\n",
// uuid128_to_str(service.uuid128), service.start_group_handle, service.end_group_handle);
// gatt_client_discover_characteristics_for_service(handle_gatt_client_event, connection_handle, &service);
// break;
// }
// service_index = 0;
break;
default:
break;
}
}
void BluetoothHCI::hci_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) {
return;
}
bd_addr_t address;
uint32_t cod;
int8_t rssi;
const char *name;
char name_buffer[241];
switch (hci_event_packet_get_type(packet)) {
case BTSTACK_EVENT_STATE:
_hciRunning = (btstack_event_state_get_state(packet) == HCI_STATE_WORKING);
break;
case HCI_EVENT_PIN_CODE_REQUEST:
hci_event_pin_code_request_get_bd_addr(packet, address);
gap_pin_code_response(address, "0000");
break;
case GAP_EVENT_INQUIRY_RESULT:
if (!_scanning) {
return;
}
gap_event_inquiry_result_get_bd_addr(packet, address);
cod = gap_event_inquiry_result_get_class_of_device(packet);
if (gap_event_inquiry_result_get_rssi_available(packet)) {
rssi = gap_event_inquiry_result_get_rssi(packet);
} else {
rssi = -128;
}
if (gap_event_inquiry_result_get_name_available(packet)) {
int name_len = gap_event_inquiry_result_get_name_len(packet);
memcpy(name_buffer, gap_event_inquiry_result_get_name(packet), name_len);
name_buffer[name_len] = 0;
name = name_buffer;
} else {
name = "";
}
DEBUGV("HCI: Scan found '%s', COD 0x%08X, RSSI %d, MAC %02X:%02X:%02X:%02X:%02X:%02X\n", name, (unsigned int)cod, rssi, address[0], address[1], address[2], address[3], address[4], address[5]);
if ((_scanMask & cod) == _scanMask) {
// Sometimes we get multiple reports for the same MAC, so remove any old reports since newer will have newer RSSI
bool updated = false;
for (auto itr = _btdList.begin(); (itr != _btdList.end()) && !updated; itr++) {
if (!memcmp(itr->address(), address, sizeof(address))) {
// Sometimes the name is missing on reports, so if we found it once preserve it
if (!name_buffer[0] && itr->name()[0]) {
strcpy(name_buffer, itr->name());
}
_btdList.erase(itr);
updated = true;
}
}
BTDeviceInfo btd(cod, address, rssi, name);
if (_btdList.size() < MAX_DEVICES_TO_DISCOVER) {
_btdList.push_back(btd);
}
}
break;
case GAP_EVENT_INQUIRY_COMPLETE:
_scanning = false;
break;
case GAP_EVENT_ADVERTISING_REPORT:
if (_scanning) {
parse_advertisement_data(packet);
}
break;
case HCI_EVENT_DISCONNECTION_COMPLETE:
_hciConn = HCI_CON_HANDLE_INVALID;
DEBUGV("HCI Disconnected\n");
break;
case HCI_EVENT_LE_META:
// wait for connection complete
if (hci_event_le_meta_get_subevent_code(packet) != HCI_SUBEVENT_LE_CONNECTION_COMPLETE) {
break;
}
DEBUGV("HCI Connected\n");
_hciConn = hci_subevent_le_connection_complete_get_connection_handle(packet);
if (_smPair) {
sm_request_pairing(_hciConn);
} else {
// query primary services - not used yet
gatt_client_discover_primary_services(handle_gatt_client_event, _hciConn);
}
break;
default:
break;
}
}
@@ -20,6 +20,7 @@
#pragma once
#include <_needsbt.h>
#include <Arduino.h>
#include <list>
#include <memory>
@@ -28,25 +29,50 @@
#include <btstack.h>
class BluetoothHCI_ {
class BluetoothHCI {
public:
void install();
void setBLEName(const char *bleMasterName);
void begin();
void uninstall();
bool running();
static const uint32_t speaker_cod = 0x200000 | 0x040000 | 0x000400; // Service Class: Rendering | Audio, Major Device Class: Audio
static const uint32_t any_cod = 0;
std::list<BTDeviceInfo> scan(uint32_t mask, int scanTimeSec = 5, bool async = false);
std::vector<BTDeviceInfo> scan(uint32_t mask, int scanTimeSec = 5, bool async = false);
bool scanAsyncDone();
std::list<BTDeviceInfo> scanAsyncResult();
std::vector<BTDeviceInfo> scanAsyncResult();
std::vector<BTDeviceInfo> scanBLE(uint32_t uuid, int scanTimeSec = 5);
void scanFree(); // Free allocated scan buffers
friend class BluetoothHIDMaster;
protected:
hci_con_handle_t getHCIConn() {
return _hciConn;
}
void setPairOnMeta(bool v) {
_smPair = v;
}
private:
void hci_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
btstack_packet_callback_registration_t hci_event_callback_registration;
volatile bool _hciRunning = false;
uint32_t _scanMask;
std::list<BTDeviceInfo> _btdList;
// Use a .reserve()'d vector to avoid any memory allocations in the
// HCI callback, since the callback happens at IRQ time. Any more
// elements than MAX_DEVICES_TO_DISCOVER will be thrown out
enum { MAX_DEVICES_TO_DISCOVER = 32 };
std::vector<BTDeviceInfo> _btdList;
volatile bool _scanning = false;
bool _running = false;
// BLE specific
uint8_t *_att = nullptr;
void parse_advertisement_data(uint8_t *packet);
volatile hci_con_handle_t _hciConn = HCI_CON_HANDLE_INVALID;
bool _smPair = false;
};
@@ -0,0 +1,34 @@
/*
Bluetooth lock helper 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
*/
#pragma once
#include <_needsbt.h>
#include <Arduino.h>
class BluetoothLock {
public:
BluetoothLock() {
__lockBluetooth();
}
~BluetoothLock() {
__unlockBluetooth();
}
};
@@ -0,0 +1,223 @@
// 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");
}
// Joystick can get reports of 4 analog axes, 1 d-pad bitfield, and up to 32 buttons
// Axes and hats that aren't reported by the joystick are read as 0
void joy(void *cbdata, int x, int y, int z, int rz, uint8_t hat, uint32_t buttons) {
(void) cbdata;
const char *hats[16] = { "U", "UR", "R", "DR", "D", "DL", "L", "UL", "", "", "", "", "", "", "", "." };
Serial.printf("Joystick: (%4d, %4d) (%4d, %4d), Hat: %-2s, Buttons:", x, y, z, rz, hats[hat & 15]);
for (int i = 0; i < 32; i++) {
Serial.printf(" %c", (buttons & 1 << i) ? '*' : '.');
}
Serial.println();
}
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.onJoystick(joy);
hid.begin();
hid.connectAny();
// 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.connectAny();
}
}
@@ -0,0 +1,222 @@
// 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");
}
// Joystick can get reports of 4 analog axes, 1 d-pad bitfield, and up to 32 buttons
// Axes and hats that aren't reported by the joystick are read as 0
void joy(void *cbdata, int x, int y, int z, int rz, uint8_t hat, uint32_t buttons) {
(void) cbdata;
const char *hats[16] = { "U", "UR", "R", "DR", "D", "DL", "L", "UL", "", "", "", "", "", "", "", "." };
Serial.printf("Joystick: (%4d, %4d) (%4d, %4d), Hat: %-2s, Buttons:", x, y, z, rz, hats[hat & 15]);
for (int i = 0; i < 32; i++) {
Serial.printf(" %c", (buttons & 1 << i) ? '*' : '.');
}
Serial.println();
}
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.onJoystick(joy);
hid.begin(true);
hid.connectBLE();
}
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.connectBLE();
}
}
+45
View File
@@ -0,0 +1,45 @@
#######################################
# 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
connectJoypad KEYWORD2
connectAny KEYWORD2
hidConnected KEYWORD2
onMouseMove KEYWORD2
onMouseButton KEYWORD2
onKeyDown KEYWORD2
onKeyUp KEYWORD2
onConsumerKeyDown KEYWORD2
onConsumerKeyUp KEYWORD2
onJoypad KEYWORD2
# BTDeviceInfo
deviceClass KEYWORD2
address KEYWORD2
addressString KEYWORD2
rssi KEYWORD2
name KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################
@@ -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=Bluetooth Classic and BLE HID (Keyboard/Mouse/Joystick) master mode
paragraph=Bluetooth Classic and BLE HID (Keyboard/Mouse/Joystick) master mode
category=Communication
url=http://github.com/earlephilhower/arduino-pico
architectures=rp2040
dot_a_linkage=true
depends=BluetoothHCI
@@ -0,0 +1,825 @@
/*
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(bool ble, const char *bleName) {
_ble = ble;
if (!ble) {
// Initialize HID Host
hid_host_init(_hid_descriptor_storage, sizeof(_hid_descriptor_storage));
hid_host_register_packet_handler(PACKETHANDLERCB(BluetoothHIDMaster, hid_packet_handler));
} else {
if (bleName) {
_hci.setBLEName(bleName);
}
_hci.setPairOnMeta(true);
}
// Initialize L2CAP
l2cap_init();
// Initialize LE Security Manager. Needed for cross-transport key derivation
if (ble) {
// register for events from Security Manager
_sm_event_callback_registration.callback = PACKETHANDLERCB(BluetoothHIDMaster, sm_packet_handler);
sm_add_event_handler(&_sm_event_callback_registration);
}
sm_init();
if (ble) {
gatt_client_init();
hids_client_init(_hid_descriptor_storage, sizeof(_hid_descriptor_storage));
} else {
// 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;
}
void BluetoothHIDMaster::onJoystick(void (*cb)(void *, int, int, int, int, uint8_t, uint32_t), void *cbData) {
_joystickCB = cb;
_joystickData = cbData;
}
std::vector<BTDeviceInfo> BluetoothHIDMaster::scan(uint32_t mask, int scanTimeSec, bool async) {
return _hci.scan(mask, scanTimeSec, async);
}
bool BluetoothHIDMaster::scanAsyncDone() {
return _hci.scanAsyncDone();
}
std::vector<BTDeviceInfo> BluetoothHIDMaster::scanAsyncResult() {
return _hci.scanAsyncResult();
}
bool BluetoothHIDMaster::connected() {
return _hidConnected && _hid_host_descriptor_available;
}
bool BluetoothHIDMaster::connect(const uint8_t *addr) {
if (!_running || _ble) {
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 || _ble) {
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::connectBLE(const uint8_t *addr, int addrType) {
if (!_running || !_ble) {
return false;
}
while (!_hci.running()) {
delay(10);
}
uint8_t a[6];
memcpy(a, addr, sizeof(a));
return ERROR_CODE_SUCCESS == gap_connect(a, (bd_addr_type_t)addrType);
}
bool BluetoothHIDMaster::connectBLE() {
if (!_running || !_ble) {
return false;
}
while (!_hci.running()) {
delay(10);
}
clearPairing();
auto l = _hci.scanBLE(0x1812 /* HID */);
for (auto e : l) {
DEBUGV("Scan connecting %s at %s ... ", e.name(), e.addressString());
if (connectBLE(e.address(), e.addressType())) {
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::connectJoystick() {
return connectCOD(0x2508);
}
bool BluetoothHIDMaster::connectAny() {
return connectCOD(0x2500);
}
bool BluetoothHIDMaster::disconnect() {
BluetoothLock b;
if (!_running || !connected()) {
return false;
}
if (!_ble && connected()) {
hid_host_disconnect(_hid_host_cid);
} else if (_ble && connected()) {
gap_disconnect(_hci.getHCIConn());
}
_hid_host_descriptor_available = false;
return true;
}
void BluetoothHIDMaster::clearPairing() {
BluetoothLock b;
if (connected()) {
if (_ble) {
gap_disconnect(_hci.getHCIConn());
} else {
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;
uint32_t newMB = 0;
bool noMB = false;
bool updCons = false;
bool updKey = false;
bool updMB = false;
bool updJoy = false;
bool updMouse = false;
int dx = 0;
int dy = 0;
int dz = 0;
int rz = 0;
int dwheel = 0;
uint8_t hat = 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;
updJoy = true;
if (usage == 0x30) {
dx = value;
} else if (usage == 0x31) {
dy = value;
} else if (usage == 0x32) {
dz = value;
} else if (usage == 0x35) {
rz = value;
} else if (usage == 0x38) {
dwheel = value;
} else if (usage == 0x39) {
hat = value & 0xff;
}
} else if (usage_page == 0x09) {
updMB = true;
if (usage == 0) {
noMB = true;
}
if (!noMB && value && (usage > 0)) {
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 && _mouseButtonCB) {
if (lastMB != newMB) {
for (int i = 0; i < 8; i++) {
int mask = 1 << i;
if ((lastMB & mask) && !(newMB & mask)) {
_mouseButtonCB(_mouseButtonData, i, false);
} else if (!(lastMB & mask) && (newMB & mask)) {
_mouseButtonCB(_mouseButtonData, i, true);
}
}
lastMB = newMB;
}
}
if (updMouse && _mouseMoveCB) {
_mouseMoveCB(_mouseMoveData, dx, dy, dwheel);
}
if (updJoy && _joystickCB) {
_joystickCB(_joystickData, dx, dy, dz, rz, hat, newMB);
}
}
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;
}
}
void BluetoothHIDMaster::sm_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
UNUSED(channel);
UNUSED(size);
if (packet_type != HCI_EVENT_PACKET) {
return;
}
switch (hci_event_packet_get_type(packet)) {
case SM_EVENT_JUST_WORKS_REQUEST:
DEBUGV("Just works requested\n");
sm_just_works_confirm(sm_event_just_works_request_get_handle(packet));
break;
case SM_EVENT_NUMERIC_COMPARISON_REQUEST:
DEBUGV("Confirming numeric comparison: %" PRIu32 "\n", sm_event_numeric_comparison_request_get_passkey(packet));
sm_numeric_comparison_confirm(sm_event_passkey_display_number_get_handle(packet));
break;
case SM_EVENT_PASSKEY_DISPLAY_NUMBER:
DEBUGV("Display Passkey: %" PRIu32 "\n", sm_event_passkey_display_number_get_passkey(packet));
break;
case SM_EVENT_PAIRING_COMPLETE:
switch (sm_event_pairing_complete_get_status(packet)) {
case ERROR_CODE_SUCCESS:
DEBUGV("Pairing complete, success\n");
// continue - query primary services
DEBUGV("Search for HID service.\n");
//app_state = W4_HID_CLIENT_CONNECTED;
hids_client_connect(_hci.getHCIConn(), PACKETHANDLERCB(BluetoothHIDMaster, handle_gatt_client_event), HID_PROTOCOL_MODE_REPORT, &_hid_host_cid);
break;
case ERROR_CODE_CONNECTION_TIMEOUT:
DEBUGV("Pairing failed, timeout\n");
break;
case ERROR_CODE_REMOTE_USER_TERMINATED_CONNECTION:
DEBUGV("Pairing failed, disconnected\n");
break;
case ERROR_CODE_AUTHENTICATION_FAILURE:
DEBUGV("Pairing failed, reason = %u\n", sm_event_pairing_complete_get_reason(packet));
break;
default:
break;
}
break;
default:
break;
}
}
void BluetoothHIDMaster::handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
UNUSED(packet_type);
UNUSED(channel);
UNUSED(size);
uint8_t status;
int idx;
if (hci_event_packet_get_type(packet) != HCI_EVENT_GATTSERVICE_META) {
return;
}
switch (hci_event_gattservice_meta_get_subevent_code(packet)) {
case GATTSERVICE_SUBEVENT_HID_SERVICE_CONNECTED:
status = gattservice_subevent_hid_service_connected_get_status(packet);
switch (status) {
case ERROR_CODE_SUCCESS:
DEBUGV("HID service client connected, found %d services\n", gattservice_subevent_hid_service_connected_get_num_instances(packet));
_hidConnected = true;
_hid_host_descriptor_available = true;
break;
default:
DEBUGV("HID service client connection failed, status 0x%02x.\n", status);
gap_disconnect(_hci.getHCIConn());
//handle_outgoing_connection_error();
break;
}
break;
case GATTSERVICE_SUBEVENT_HID_REPORT:
idx = gattservice_subevent_hid_report_get_service_index(packet);
btstack_hid_parser_t parser;
btstack_hid_parser_init(&parser, hids_client_descriptor_storage_get_descriptor_data(_hid_host_cid, idx), hids_client_descriptor_storage_get_descriptor_len(_hid_host_cid, idx), HID_REPORT_TYPE_INPUT, gattservice_subevent_hid_report_get_report(packet), gattservice_subevent_hid_report_get_report_len(packet));
hid_host_handle_interrupt_report(&parser);
//hid_handle_input_report(
// gattservice_subevent_hid_report_get_service_index(packet),
// gattservice_subevent_hid_report_get_report(packet),
// gattservice_subevent_hid_report_get_report_len(packet));
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;
}
@@ -0,0 +1,145 @@
/*
Bluetooth HID Master class, can connect to keyboards, mice, and joysticks
Works with Bluetooth Classic and BLE devices
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 <_needsbt.h>
#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(const char *bleName) {
begin(true, bleName);
}
void begin(bool ble = false, const char *bleName = nullptr);
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 joystick_cod = 0x2508;
static const uint32_t any_cod = 0;
std::vector<BTDeviceInfo> scan(uint32_t mask, int scanTimeSec = 5, bool async = false);
bool scanAsyncDone();
std::vector<BTDeviceInfo> scanAsyncResult();
bool connect(const uint8_t *addr);
bool connectKeyboard();
bool connectMouse();
bool connectJoystick();
bool connectAny();
bool connectBLE(const uint8_t *addr, int addrType);
bool connectBLE();
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);
void onJoystick(void (*)(void *, int, int, int, int, uint8_t, uint32_t), void *cbData = nullptr);
private:
bool _ble = false;
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;
void (*_joystickCB)(void *, int, int, int, int, uint8_t, uint32_t) = nullptr;
void *_joystickData;
btstack_packet_callback_registration_t _sm_event_callback_registration;
void sm_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
};
+2 -2
View File
@@ -24,8 +24,8 @@
#include <lwip/def.h>
#include <Arduino.h>
#ifdef DEBUG_ESP_PORT
#define CONSOLE DEBUG_ESP_PORT
#ifdef DEBUG_RP2040_PORT
#define CONSOLE DEBUG_RP2040_PORT
#else
#define CONSOLE Serial
#endif
+4
View File
@@ -19,6 +19,10 @@
#pragma once
#ifdef USE_TINYUSB
#error FatFSUSB is not compatible with Adafruit TinyUSB
#endif
#include <Arduino.h>
class FatFSUSBClass {
+15 -6
View File
@@ -1,10 +1,10 @@
#define configNUM_CORES 2
#define configUSE_CORE_AFFINITY 1
#define configRUN_MULTIPLE_PRIORITIES 1
#define configNUMBER_OF_CORES 2
#define configUSE_CORE_AFFINITY 1
#define configRUN_MULTIPLE_PRIORITIES 1
#define configUSE_PREEMPTION 1
#define configUSE_IDLE_HOOK 1
#define configUSE_MINIMAL_IDLE_HOOK 1
#define configUSE_PASSIVE_IDLE_HOOK 1
#define configUSE_TICK_HOOK 1
#define configCPU_CLOCK_HZ ( ( unsigned long ) F_CPU )
#define configTICK_RATE_HZ ( ( TickType_t ) 1000 )
@@ -38,7 +38,7 @@ extern unsigned long ulMainGetRunTimeCounterValue(void);
#define portGET_RUN_TIME_COUNTER_VALUE() ulMainGetRunTimeCounterValue()
/* Co-routine definitions. */
#define configUSE_CO_ROUTINES 1
#define configUSE_CO_ROUTINES 0
#define configMAX_CO_ROUTINE_PRIORITIES ( 2 )
/* Software timer definitions. */
@@ -99,7 +99,14 @@ extern unsigned long ulMainGetRunTimeCounterValue(void);
See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */
#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
extern void rtosFatalError(void);
#ifdef __cplusplus
extern "C" {
#endif
void rtosFatalError(void);
#ifdef __cplusplus
};
#endif
#define configASSERT( x ) \
if( ( x ) == 0 ) { portDISABLE_INTERRUPTS(); rtosFatalError(); }
@@ -107,4 +114,6 @@ extern void rtosFatalError(void);
#define configSUPPORT_PICO_SYNC_INTEROP 1
#define configSUPPORT_PICO_TIME_INTEROP 1
#define LIB_PICO_MULTICORE 1
#include "rp2040_config.h"
@@ -1 +0,0 @@
#include "../lib/FreeRTOS-Kernel/portable/ThirdParty/GCC/RP2040/idle_task_static_memory.c"
+2 -2
View File
@@ -1,6 +1,6 @@
#include "../lib/FreeRTOS-Kernel/tasks.c"
//See https://github.com/FreeRTOS/FreeRTOS-Kernel/pull/496
// See https://github.com/FreeRTOS/FreeRTOS-Kernel/pull/496
struct _reent* __wrap___getreent(void) {
// No lock needed because if this changes, we won't be running anymore.
TCB_t *pxCurTask = xTaskGetCurrentTaskHandle();
@@ -9,6 +9,6 @@ struct _reent* __wrap___getreent(void) {
return _GLOBAL_REENT;
} else {
// We have a task; return its reentrant struct.
return &pxCurTask->xNewLib_reent;
return &pxCurTask->xTLSBlock;
}
}
+23 -7
View File
@@ -258,7 +258,7 @@ void vApplicationIdleHook(void) {
#endif /* configUSE_IDLE_HOOK == 1 */
/*-----------------------------------------------------------*/
#if ( configUSE_MINIMAL_IDLE_HOOK == 1 )
//#if ( configUSE_MINIMAL_IDLE_HOOK == 1 )
/*
Call the user defined minimalIdle() function from within the idle task.
This allows the application designer to add background functionality
@@ -267,17 +267,17 @@ void vApplicationIdleHook(void) {
NOTE: vApplicationMinimalIdleHook() MUST NOT, UNDER ANY CIRCUMSTANCES, CALL A FUNCTION THAT MIGHT BLOCK.
*/
void minimalIdle(void) __attribute__((weak));
void minimalIdle() {} //Empty minimalIdle function
void passiveIdle(void) __attribute__((weak));
void passiveIdle() {} //Empty minimalIdle function
extern "C"
void vApplicationMinimalIdleHook(void) __attribute__((weak));
//void vApplicationPassiveIdleHook(void) __attribute__((weak));
void vApplicationMinimalIdleHook(void) {
minimalIdle();
void vApplicationPassiveIdleHook(void) {
passiveIdle();
}
#endif /* configUSE_MINIMAL_IDLE_HOOK == 1 */
//#endif /* configUSE_MINIMAL_IDLE_HOOK == 1 */
/*-----------------------------------------------------------*/
#if ( configUSE_TICK_HOOK == 1 )
@@ -394,6 +394,22 @@ void vApplicationStackOverflowHook(TaskHandle_t xTask __attribute__((unused)),
#endif /* configCHECK_FOR_STACK_OVERFLOW >= 1 */
/*-----------------------------------------------------------*/
extern "C" void vApplicationGetPassiveIdleTaskMemory(StaticTask_t ** ppxIdleTaskTCBBuffer,
StackType_t ** ppxIdleTaskStackBuffer,
configSTACK_DEPTH_TYPE * puxIdleTaskStackSize,
BaseType_t xPassiveIdleTaskIndex) {
static StaticTask_t xIdleTaskTCBs[ configNUMBER_OF_CORES ];
static StackType_t uxIdleTaskStacks[ configNUMBER_OF_CORES ][ configMINIMAL_STACK_SIZE ];
*ppxIdleTaskTCBBuffer = &(xIdleTaskTCBs[ xPassiveIdleTaskIndex ]);
*ppxIdleTaskStackBuffer = &(uxIdleTaskStacks[ xPassiveIdleTaskIndex ][ 0 ]);
*puxIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}
#if ( configSUPPORT_STATIC_ALLOCATION >= 1 )
extern "C"
+1
View File
@@ -0,0 +1 @@
This folder contains FreeRTOS functionality tests, not generally useful for users.
@@ -0,0 +1,32 @@
// Simple stress test to ensure each thread has its own Newlib reent structure
// The random numbers from each task should be identical.
#include <FreeRTOS.h>
#include <task.h>
void go(void *param) {
(void) param;
srand(0);
int i = 0;
while(1) {
char buff[100];
TaskStatus_t st;
vTaskGetInfo(NULL, &st, pdFALSE, eInvalid);
sprintf(buff, "task %ld: %d = %d\n", st.xTaskNumber, i++, rand());
Serial.print(buff);
delay(1000);
}
}
void setup() {
delay(5000);
// put your setup code here, to run once:
xTaskCreate(go, "c1", 1024, nullptr, 1, nullptr);
xTaskCreate(go, "c2", 1024, nullptr, 1, nullptr);
xTaskCreate(go, "c3", 1024, nullptr, 1, nullptr);
}
void loop() {
// put your main code here, to run repeatedly:
}
@@ -0,0 +1,319 @@
// FreeRTOS system call/mutex stress test
#include <Arduino.h>
#include <FreeRTOS.h>
#include <task.h>
#include <semphr.h>
#define DELAY 1
#define SERIAL_DEBUG Serial1
#define STACK_SIZE 512
#define CORE_0 (1 << 0)
#define CORE_1 (1 << 1)
void semphrTakeConditional(bool useMutexOn, SemaphoreHandle_t xSemaphore);
void semphrGiveConditional(bool useMutexOn, SemaphoreHandle_t xSemaphore);
/*
I want to keep the possibility of using different and independent
mutexes for each of the functions that operate on the heap.
If you want to enable the use of these mutexes remove the defines
on lines 30 and 31 and enable the their initialization in setup()
*/
SemaphoreHandle_t xSemaphoreMalloc = NULL;
// SemaphoreHandle_t xSemaphoreRealloc = NULL;
// SemaphoreHandle_t xSemaphoreFree = NULL;
/*
A lazy way to use the same mutex for malloc, realloc and free
in order to bring us back to the same situation as the MCVE
posted here: https://github.com/earlephilhower/arduino-pico/issues/795#issuecomment-1227122082
*/
#define xSemaphoreRealloc xSemaphoreMalloc
#define xSemaphoreFree xSemaphoreMalloc
const bool useMutexOnMalloc = false;
const bool useMutexOnRealloc = false;
const bool useMutexOnFree = false;
/*
Enabling this, a realloc will be performed and the string "_realloc"
will be concateneted to *tmp
*/
const bool tryRealloc = true;
TaskHandle_t loop2Handle = NULL;
TaskHandle_t loop3Handle = NULL;
TaskHandle_t loop4Handle = NULL;
TaskHandle_t loop5Handle = NULL;
TaskHandle_t loop6Handle = NULL;
TaskHandle_t loop7Handle = NULL;
void loop2(void *pvPramaters);
void loop3(void *pvPramaters);
void loop4(void *pvPramaters);
void loop5(void *pvPramaters);
void loop6(void *pvPramaters);
void loop7(void *pvPramaters);
void setup()
{
pinMode(LED_BUILTIN, OUTPUT);
xSemaphoreMalloc = xSemaphoreCreateMutex();
// xSemaphoreRealloc = xSemaphoreCreateMutex();
// xSemaphoreFree = xSemaphoreCreateMutex();
xTaskCreate(loop2, "loop2", STACK_SIZE, NULL, 1, &loop2Handle);
vTaskCoreAffinitySet(loop2Handle, CORE_0);
xTaskCreate(loop3, "loop3", STACK_SIZE, NULL, 1, &loop3Handle);
vTaskCoreAffinitySet(loop3Handle, CORE_1);
xTaskCreate(loop4, "loop4", STACK_SIZE, NULL, 1, &loop4Handle);
vTaskCoreAffinitySet(loop4Handle, CORE_0);
xTaskCreate(loop5, "loop5", STACK_SIZE, NULL, 1, &loop5Handle);
vTaskCoreAffinitySet(loop5Handle, CORE_1);
xTaskCreate(loop6, "loop6", STACK_SIZE, NULL, 1, &loop6Handle);
vTaskCoreAffinitySet(loop6Handle, CORE_0);
xTaskCreate(loop7, "loop7", STACK_SIZE, NULL, 1, &loop7Handle);
vTaskCoreAffinitySet(loop7Handle, CORE_1);
}
static int _loop[8];
void loop()
{
while (1)
{
_loop[0]++;
digitalWrite(LED_BUILTIN, HIGH);
delay(500);
digitalWrite(LED_BUILTIN, LOW);
delay(500);
for (int i=0; i<8; i++) Serial.printf("%d ", _loop[i]);
Serial.println("");
}
}
void loop1()
{
while (1)
{
_loop[1]++;
char *tmp;
semphrTakeConditional(useMutexOnMalloc, xSemaphoreMalloc);
tmp = (char *)malloc(10 * sizeof(char));
semphrGiveConditional(useMutexOnMalloc, xSemaphoreMalloc);
strcpy(tmp, "foo");
if (tryRealloc)
{
semphrTakeConditional(useMutexOnRealloc, xSemaphoreRealloc);
tmp = (char *)realloc(tmp, 20 * sizeof(char));
semphrGiveConditional(useMutexOnRealloc, xSemaphoreRealloc);
strcat(tmp, "_realloc");
}
semphrTakeConditional(useMutexOnFree, xSemaphoreFree);
free(tmp);
semphrGiveConditional(useMutexOnFree, xSemaphoreFree);
delay(DELAY);
}
}
void loop2(void *pvPramaters)
{
(void) pvPramaters;
while (1)
{
_loop[2]++;
char *tmp;
semphrTakeConditional(useMutexOnMalloc, xSemaphoreMalloc);
tmp = (char *)malloc(10 * sizeof(char));
semphrGiveConditional(useMutexOnMalloc, xSemaphoreMalloc);
strcpy(tmp, "bar");
if (tryRealloc)
{
semphrTakeConditional(useMutexOnRealloc, xSemaphoreRealloc);
tmp = (char *)realloc(tmp, 20 * sizeof(char));
semphrGiveConditional(useMutexOnRealloc, xSemaphoreRealloc);
strcat(tmp, "_realloc");
}
semphrTakeConditional(useMutexOnFree, xSemaphoreFree);
free(tmp);
semphrGiveConditional(useMutexOnFree, xSemaphoreFree);
delay(DELAY);
}
}
void loop3(void *pvPramaters)
{
(void) pvPramaters;
while (1)
{
_loop[3]++;
char *tmp;
semphrTakeConditional(useMutexOnMalloc, xSemaphoreMalloc);
tmp = (char *)malloc(10 * sizeof(char));
semphrGiveConditional(useMutexOnMalloc, xSemaphoreMalloc);
strcpy(tmp, "yeah");
if (tryRealloc)
{
semphrTakeConditional(useMutexOnRealloc, xSemaphoreRealloc);
tmp = (char *)realloc(tmp, 20 * sizeof(char));
semphrGiveConditional(useMutexOnRealloc, xSemaphoreRealloc);
strcat(tmp, "_realloc");
}
semphrTakeConditional(useMutexOnFree, xSemaphoreFree);
free(tmp);
semphrGiveConditional(useMutexOnFree, xSemaphoreFree);
delay(DELAY);
}
}
void loop4(void *pvPramaters)
{
(void) pvPramaters;
while (1)
{
_loop[4]++;
char *tmp;
semphrTakeConditional(useMutexOnMalloc, xSemaphoreMalloc);
tmp = (char *)malloc(10 * sizeof(char));
semphrGiveConditional(useMutexOnMalloc, xSemaphoreMalloc);
strcpy(tmp, "baz");
if (tryRealloc)
{
semphrTakeConditional(useMutexOnRealloc, xSemaphoreRealloc);
tmp = (char *)realloc(tmp, 20 * sizeof(char));
semphrGiveConditional(useMutexOnRealloc, xSemaphoreRealloc);
strcat(tmp, "_realloc");
}
semphrTakeConditional(useMutexOnFree, xSemaphoreFree);
free(tmp);
semphrGiveConditional(useMutexOnFree, xSemaphoreFree);
delay(DELAY);
}
}
void loop5(void *pvPramaters)
{
(void) pvPramaters;
while (1)
{
_loop[5]++;
char *tmp;
semphrTakeConditional(useMutexOnMalloc, xSemaphoreMalloc);
tmp = (char *)malloc(10 * sizeof(char));
semphrGiveConditional(useMutexOnMalloc, xSemaphoreMalloc);
strcpy(tmp, "asd");
if (tryRealloc)
{
semphrTakeConditional(useMutexOnRealloc, xSemaphoreRealloc);
tmp = (char *)realloc(tmp, 20 * sizeof(char));
semphrGiveConditional(useMutexOnRealloc, xSemaphoreRealloc);
strcat(tmp, "_realloc");
}
semphrTakeConditional(useMutexOnFree, xSemaphoreFree);
free(tmp);
semphrGiveConditional(useMutexOnFree, xSemaphoreFree);
delay(DELAY);
}
}
void loop6(void *pvPramaters)
{
(void) pvPramaters;
while (1)
{
_loop[6]++;
char *tmp;
semphrTakeConditional(useMutexOnMalloc, xSemaphoreMalloc);
tmp = (char *)malloc(10 * sizeof(char));
semphrGiveConditional(useMutexOnMalloc, xSemaphoreMalloc);
strcpy(tmp, "lol");
if (tryRealloc)
{
semphrTakeConditional(useMutexOnRealloc, xSemaphoreRealloc);
tmp = (char *)realloc(tmp, 20 * sizeof(char));
semphrGiveConditional(useMutexOnRealloc, xSemaphoreRealloc);
strcat(tmp, "_realloc");
}
semphrTakeConditional(useMutexOnFree, xSemaphoreFree);
free(tmp);
semphrGiveConditional(useMutexOnFree, xSemaphoreFree);
delay(DELAY);
}
}
void loop7(void *pvPramaters)
{
(void) pvPramaters;
while (1)
{
_loop[7]++;
char *tmp;
semphrTakeConditional(useMutexOnMalloc, xSemaphoreMalloc);
tmp = (char *)malloc(10 * sizeof(char));
semphrGiveConditional(useMutexOnMalloc, xSemaphoreMalloc);
strcpy(tmp, "yay");
if (tryRealloc)
{
semphrTakeConditional(useMutexOnRealloc, xSemaphoreRealloc);
tmp = (char *)realloc(tmp, 20 * sizeof(char));
semphrGiveConditional(useMutexOnRealloc, xSemaphoreRealloc);
strcat(tmp, "_realloc");
}
semphrTakeConditional(useMutexOnFree, xSemaphoreFree);
free(tmp);
semphrGiveConditional(useMutexOnFree, xSemaphoreFree);
delay(DELAY);
}
}
void semphrTakeConditional(bool useMutexOn, SemaphoreHandle_t xSemaphore)
{
if (useMutexOn)
{
xSemaphoreTake(xSemaphore, TickType_t(portMAX_DELAY));
}
}
void semphrGiveConditional(bool useMutexOn, SemaphoreHandle_t xSemaphore)
{
if (useMutexOn)
{
xSemaphoreGive(xSemaphore);
}
}
@@ -1,4 +1,5 @@
#include "HID_Bluetooth.h"
#include <sdkoverride/tusb_gamepad16.h>
//setup the report map.
//more generic function to be used with BLE & BT Classis
@@ -6,7 +7,7 @@ void __SetupHIDreportmap(void (*WeakMouse)(), void (*WeakKeyboard)(), void (*Wea
//allocate memory for the HID report descriptors. We don't use them, but need the size here.
uint8_t desc_hid_report_mouse[] = { TUD_HID_REPORT_DESC_MOUSE(HID_REPORT_ID(1)) };
uint8_t desc_hid_report_absmouse[] = { TUD_HID_REPORT_DESC_ABSMOUSE(HID_REPORT_ID(1)) };
uint8_t desc_hid_report_joystick[] = { TUD_HID_REPORT_DESC_GAMEPAD(HID_REPORT_ID(1)) };
uint8_t desc_hid_report_joystick[] = { TUD_HID_REPORT_DESC_GAMEPAD16(HID_REPORT_ID(1)) };
uint8_t desc_hid_report_keyboard[] = { TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(1)), TUD_HID_REPORT_DESC_CONSUMER(HID_REPORT_ID(2)) };
int size = 0;
@@ -111,7 +112,7 @@ void __SetupHIDreportmap(void (*WeakMouse)(), void (*WeakKeyboard)(), void (*Wea
reportid++;
offset += sizeof(desc_hid_report_absmouse);
}
uint8_t desc_local[] = { TUD_HID_REPORT_DESC_GAMEPAD(HID_REPORT_ID(reportid)) };
uint8_t desc_local[] = { TUD_HID_REPORT_DESC_GAMEPAD16(HID_REPORT_ID(reportid)) };
memcpy(*reportmap + offset, desc_local, sizeof(desc_local));
}
@@ -178,11 +178,13 @@ public:
while (connected() && _needToSend) {
/* noop busy wait */
}
_needToSend = true;
_sendReport = rpt;
_sendReportLen = len;
__lockBluetooth();
hids_device_request_can_send_now_event(_con_handle);
if (connected()) {
_needToSend = true;
_sendReport = rpt;
_sendReportLen = len;
hids_device_request_can_send_now_event(_con_handle);
}
__unlockBluetooth();
while (connected() && _needToSend) {
/* noop busy wait */
@@ -142,12 +142,14 @@ public:
}
bool send(int id, void *rpt, int len) {
_needToSend = true;
_sendReportID = id;
_sendReport = rpt;
_sendReportLen = len;
__lockBluetooth();
hid_device_request_can_send_now_event(getCID());
if (connected()) {
_needToSend = true;
_sendReportID = id;
_sendReport = rpt;
_sendReportLen = len;
hid_device_request_can_send_now_event(getCID());
}
__unlockBluetooth();
while (connected() && _needToSend) {
/* noop busy wait */
+209 -10
View File
@@ -24,10 +24,12 @@
*/
#include <Arduino.h>
#include "HTTPClient.h"
#include <WiFi.h>
#include <time.h>
#include "base64.h"
extern "C" char *strptime(const char *__restrict, const char *__restrict, struct tm *__restrict); // Not exposed by headers?
// per https://github.com/esp8266/Arduino/issues/8231
// make sure HTTPClient can be utilized as a movable class member
@@ -205,6 +207,7 @@ bool HTTPClient::begin(WiFiClient &client, const String& url) {
}
_port = (protocol == "https" ? 443 : 80);
_secure = (protocol == "https");
_clientIn = client.clone();
_clientGiven = true;
if (_clientMade) {
@@ -246,6 +249,7 @@ bool HTTPClient::begin(WiFiClient &client, const String& host, uint16_t port, co
_port = port;
_uri = uri;
_protocol = (https ? "https" : "http");
_secure = https;
return true;
}
@@ -590,6 +594,12 @@ int HTTPClient::sendRequest(const char * type, const uint8_t * payload, size_t s
addHeader(F("Content-Length"), String(payload && size > 0 ? size : 0));
// add cookies to header, if present
String cookie_string;
if (generateCookieString(&cookie_string)) {
addHeader("Cookie", cookie_string);
}
// send Header
if (!sendHeader(type)) {
return returnError(HTTPC_ERROR_SEND_HEADER_FAILED);
@@ -691,6 +701,12 @@ int HTTPClient::sendRequest(const char * type, Stream * stream, size_t size) {
addHeader(F("Content-Length"), String(size));
}
// add cookies to header, if present
String cookie_string;
if (generateCookieString(&cookie_string)) {
addHeader("Cookie", cookie_string);
}
// send Header
if (!sendHeader(type)) {
return returnError(HTTPC_ERROR_SEND_HEADER_FAILED);
@@ -955,7 +971,7 @@ void HTTPClient::collectHeaders(const char* headerKeys[], const size_t headerKey
String HTTPClient::header(const char* name) {
for (size_t i = 0; i < _headerKeysCount; ++i) {
if (_currentHeaders[i].key == name) {
if (_currentHeaders[i].key.equalsIgnoreCase(name)) {
return _currentHeaders[i].value;
}
}
@@ -982,7 +998,7 @@ int HTTPClient::headers() {
bool HTTPClient::hasHeader(const char* name) {
for (size_t i = 0; i < _headerKeysCount; ++i) {
if ((_currentHeaders[i].key == name) && (_currentHeaders[i].value.length() > 0)) {
if ((_currentHeaders[i].key.equalsIgnoreCase(name)) && (_currentHeaders[i].value.length() > 0)) {
return true;
}
}
@@ -1100,6 +1116,7 @@ int HTTPClient::handleHeaderResponse() {
_transferEncoding = HTTPC_TE_IDENTITY;
unsigned long lastDataTime = millis();
String date;
while (connected()) {
size_t len = _client()->available();
@@ -1127,6 +1144,10 @@ int HTTPClient::handleHeaderResponse() {
_size = headerValue.toInt();
}
if (headerName.equalsIgnoreCase("Date")) {
date = headerValue;
}
if (_canReuse && headerName.equalsIgnoreCase(F("Connection"))) {
if (headerValue.indexOf(F("close")) >= 0 &&
headerValue.indexOf(F("keep-alive")) < 0) {
@@ -1142,15 +1163,20 @@ int HTTPClient::handleHeaderResponse() {
_location = headerValue;
}
if (headerName.equalsIgnoreCase("Set-Cookie")) {
setCookie(date, headerValue);
}
for (size_t i = 0; i < _headerKeysCount; i++) {
if (_currentHeaders[i].key.equalsIgnoreCase(headerName)) {
if (_currentHeaders[i].value != "") {
// Existing value, append this one with a comma
_currentHeaders[i].value += ',';
_currentHeaders[i].value += headerValue;
} else {
_currentHeaders[i].value = headerValue;
}
// Uncomment the following lines if you need to add support for multiple headers with the same key:
// if (!_currentHeaders[i].value.isEmpty()) {
// // Existing value, append this one with a comma
// _currentHeaders[i].value += ',';
// _currentHeaders[i].value += headerValue;
// } else {
_currentHeaders[i].value = headerValue;
// }
break; // We found a match, stop looking
}
}
@@ -1210,3 +1236,176 @@ int HTTPClient::returnError(int error) {
}
return error;
}
void HTTPClient::setCookieJar(CookieJar* cookieJar) {
_cookieJar = cookieJar;
}
void HTTPClient::resetCookieJar() {
_cookieJar = nullptr;
}
void HTTPClient::clearAllCookies() {
if (_cookieJar) {
_cookieJar->clear();
}
}
void HTTPClient::setCookie(String date, String headerValue) {
if (!_cookieJar) {
return;
}
#define HTTP_TIME_PATTERN "%a, %d %b %Y %H:%M:%S"
Cookie cookie;
String value;
int pos1, pos2;
struct tm tm;
strptime(date.c_str(), HTTP_TIME_PATTERN, &tm);
cookie.date = mktime(&tm);
pos1 = headerValue.indexOf('=');
pos2 = headerValue.indexOf(';');
if (pos1 >= 0 && pos2 > pos1) {
cookie.name = headerValue.substring(0, pos1);
cookie.value = headerValue.substring(pos1 + 1, pos2);
} else {
return; // invalid cookie header
}
// only Cookie Attributes are case insensitive from this point on
headerValue.toLowerCase();
// expires
if (headerValue.indexOf("expires=") >= 0) {
pos1 = headerValue.indexOf("expires=") + strlen("expires=");
pos2 = headerValue.indexOf(';', pos1);
if (pos2 > pos1) {
value = headerValue.substring(pos1, pos2);
} else {
value = headerValue.substring(pos1);
}
strptime(value.c_str(), HTTP_TIME_PATTERN, &tm);
cookie.expires.date = mktime(&tm);
cookie.expires.valid = true;
}
// max-age
if (headerValue.indexOf("max-age=") >= 0) {
pos1 = headerValue.indexOf("max-age=") + strlen("max-age=");
pos2 = headerValue.indexOf(';', pos1);
if (pos2 > pos1) {
value = headerValue.substring(pos1, pos2);
} else {
value = headerValue.substring(pos1);
}
cookie.max_age.duration = value.toInt();
cookie.max_age.valid = true;
}
// domain
if (headerValue.indexOf("domain=") >= 0) {
pos1 = headerValue.indexOf("domain=") + strlen("domain=");
pos2 = headerValue.indexOf(';', pos1);
if (pos2 > pos1) {
value = headerValue.substring(pos1, pos2);
} else {
value = headerValue.substring(pos1);
}
if (value.startsWith(".")) {
value.remove(0, 1);
}
if (_host.indexOf(value) >= 0) {
cookie.domain = value;
} else {
return; // server tries to set a cookie on a different domain; ignore it
}
} else {
pos1 = _host.lastIndexOf('.', _host.lastIndexOf('.') - 1);
if (pos1 >= 0) {
cookie.domain = _host.substring(pos1 + 1);
} else {
cookie.domain = _host;
}
}
// path
if (headerValue.indexOf("path=") >= 0) {
pos1 = headerValue.indexOf("path=") + strlen("path=");
pos2 = headerValue.indexOf(';', pos1);
if (pos2 > pos1) {
cookie.path = headerValue.substring(pos1, pos2);
} else {
cookie.path = headerValue.substring(pos1);
}
}
// HttpOnly
cookie.http_only = (headerValue.indexOf("httponly") >= 0);
// secure
cookie.secure = (headerValue.indexOf("secure") >= 0);
// overwrite or delete cookie in/from cookie jar
time_t now_local = time(NULL);
time_t now_gmt = mktime(gmtime(&now_local));
bool found = false;
for (auto c = _cookieJar->begin(); c != _cookieJar->end(); ++c) {
if (c->domain == cookie.domain && c->name == cookie.name) {
// when evaluating, max-age takes precedence over expires if both are defined
if ((cookie.max_age.valid && ((cookie.date + cookie.max_age.duration) < now_gmt || (cookie.max_age.duration <= 0)))
|| (!cookie.max_age.valid && cookie.expires.valid && (cookie.expires.date < now_gmt))) {
_cookieJar->erase(c);
c--;
} else {
*c = cookie;
}
found = true;
}
}
// add cookie to jar
if (!found && !(cookie.max_age.valid && cookie.max_age.duration <= 0)) {
_cookieJar->push_back(cookie);
}
}
bool HTTPClient::generateCookieString(String *cookieString) {
if (!_cookieJar) {
return false;
}
time_t now_local = time(NULL);
time_t now_gmt = mktime(gmtime(&now_local));
*cookieString = "";
bool found = false;
for (auto c = _cookieJar->begin(); c != _cookieJar->end(); ++c) {
if ((c->max_age.valid && ((c->date + c->max_age.duration) < now_gmt)) || (!c->max_age.valid && c->expires.valid && (c->expires.date < now_gmt))) {
_cookieJar->erase(c);
c--;
} else if (_host.indexOf(c->domain) >= 0 && (!c->secure || _secure)) {
if (*cookieString == "") {
*cookieString = c->name + "=" + c->value;
} else {
*cookieString += " ;" + c->name + "=" + c->value;
}
found = true;
}
}
return found;
}
+43 -6
View File
@@ -33,10 +33,11 @@
#include <WiFiClientSecure.h>
#include <memory>
#include <vector>
#ifdef DEBUG_ESP_HTTP_CLIENT
#ifdef DEBUG_ESP_PORT
#define DEBUG_HTTPCLIENT(fmt, ...) DEBUG_ESP_PORT.printf_P( (PGM_P)PSTR(fmt), ## __VA_ARGS__ )
#ifdef DEBUG_RP2040_CORE
#ifdef DEBUG_RP2040_PORT
#define DEBUG_HTTPCLIENT(fmt, ...) DEBUG_RP2040_PORT.printf_P( (PGM_P)PSTR(fmt), ## __VA_ARGS__ )
#endif
#endif
@@ -153,10 +154,36 @@ typedef enum {
class TransportTraits;
typedef std::unique_ptr<TransportTraits> TransportTraitsPtr;
// cookie jar support
typedef struct {
String host; // host which tries to set the cookie
time_t date; // timestamp of the response that set the cookie
String name;
String value;
String domain;
String path = "";
struct {
time_t date = 0;
bool valid = false;
} expires;
struct {
time_t duration = 0;
bool valid = false;
} max_age;
bool http_only = false;
bool secure = false;
} Cookie;
typedef std::vector<Cookie> CookieJar;
class HTTPClient {
public:
HTTPClient() = default;
~HTTPClient() = default;
~HTTPClient() {
if (_clientMade) {
delete _clientMade;
}
}
HTTPClient(HTTPClient&&) = default;
HTTPClient& operator=(HTTPClient&&) = default;
@@ -229,6 +256,11 @@ public:
const String& getString(void);
static String errorToString(int error);
// Cookie jar support
void setCookieJar(CookieJar* cookieJar);
void resetCookieJar();
void clearAllCookies();
// ----------------------------------------------------------------------------------------------
// HTTPS support, mirrors the WiFiClientSecure interface
// Could possibly use a virtual interface class between the two, but for now it is more
@@ -327,6 +359,10 @@ protected:
int handleHeaderResponse();
int writeToStreamDataBlock(Stream * stream, int len);
// Cookie jar support
void setCookie(String date, String headerValue);
bool generateCookieString(String *cookieString);
WiFiClient *_clientMade = nullptr;
bool _clientTLS = false;
@@ -350,6 +386,7 @@ protected:
String _uri;
String _protocol;
bool _secure = false;
String _headers;
String _base64Authorization;
@@ -368,6 +405,6 @@ protected:
String _location;
transferEncoding_t _transferEncoding = HTTPC_TE_IDENTITY;
std::unique_ptr<StreamString> _payload;
// Cookie jar support
CookieJar *_cookieJar = nullptr;
};
+3 -3
View File
@@ -31,9 +31,9 @@
#include <WiFiUdp.h>
#include <HTTPClient.h>
#ifdef DEBUG_ESP_HTTP_UPDATE
#ifdef DEBUG_ESP_PORT
#define DEBUG_HTTP_UPDATE(fmt, ...) DEBUG_ESP_PORT.printf_P( (PGM_P)PSTR(fmt), ## __VA_ARGS__ )
#ifdef DEBUG_RP2040_CORE
#ifdef DEBUG_RP2040_PORT
#define DEBUG_HTTP_UPDATE(fmt, ...) DEBUG_RP2040_PORT.printf_P( (PGM_P)PSTR(fmt), ## __VA_ARGS__ )
#endif
#endif
+5 -2
View File
@@ -156,8 +156,6 @@ Switch axis value range between 10bit and 8bit.
* Default: 10bit, range for an axis from 0 to 1023
* 8bit mode: range from -127 to 127.
__Note:__ due to the gamepad descriptor of tinyUSB, the maximum range is -127/127. 10bit mode enables mapping, not a higher resolution.
#### Syntax
@@ -216,6 +214,11 @@ void loop() {
* [Joystick.sliderRight()](#joysticksliderright)
### `Joystick.use10bit()`
### `Joystick.use16bit()`
Set axis value range to 10-bit (0...1024) or 16-bit (-32767...32767).
### `Joystick.useManualSend()`
To fully control transmitting the USB-HID reports, enable manual sending.
@@ -33,8 +33,8 @@ void loop() {
}
Joystick.useManualSend(false);
Joystick.use10bit();
// Iterate all joystick axis
// Note: although you can use 0-1023 here (10bit), internally 8bits are used (-127 to 127)
Serial.println("Joystick X");
for (uint16_t i = 0; i < 1023; i++) {
Joystick.X(i);
@@ -74,7 +74,7 @@ void loop() {
// Use int8 mode for the axis.
// Note: hat is not used differently.
Serial.println("Now all axis in 8bit mode, -127 to 127");
Joystick.use8bit(true);
Joystick.use8bit();
Serial.println("Joystick X");
for (int16_t i = -127; i < 128; i++) {
Joystick.X(i);
@@ -105,6 +105,6 @@ void loop() {
Joystick.sliderRight(i);
delay(2);
} Joystick.sliderRight(0);
Joystick.use8bit(false);
Joystick.use10bit();
}
}
@@ -0,0 +1,22 @@
#include <Joystick.h>
// Set 1KHz polling frequency (1000/second)
int usb_hid_poll_interval = 1;
void setup() {
Joystick.use16bit();
Joystick.begin();
}
void loop() {
static int16_t delta = 1;
static int16_t p = 0;
if (p == 32767) {
delta = -1;
} else if (p == -32767) {
delta = 1;
}
p += delta;
Joystick.X(p);
Joystick.Y(-p);
}
+6 -5
View File
@@ -18,11 +18,14 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef _JOYSTICK_h
#define _JOYSTICK_h
#pragma once
#ifdef USE_TINYUSB
#error Joystick is not compatible with Adafruit TinyUSB
#endif
#include <HID_Joystick.h>
#include "class/hid/hid.h"
#include <class/hid/hid.h>
//======================================================================
class Joystick_ : public HID_Joystick {
@@ -31,5 +34,3 @@ public:
virtual void send_now(void) override;
};
extern Joystick_ Joystick;
#endif /* _JOYSTICK_h */
+1 -1
View File
@@ -65,7 +65,7 @@ void JoystickBLE_::setBattery(int lvl) {
void JoystickBLE_::send_now() {
//insert report ID; not part of the hid_gamepad_report_t
uint8_t *report = (uint8_t *)malloc(sizeof(hid_gamepad_report_t) +1);
uint8_t *report = (uint8_t *)malloc(sizeof(hid_gamepad16_report_t) + 1);
if (report) {
report[0] = __BLEGetJoystickReportID();
memcpy(&report[1], (uint8_t*)&data, sizeof(data));
+5 -4
View File
@@ -20,8 +20,11 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef KEYBOARD_h
#define KEYBOARD_h
#pragma once
#ifdef USE_TINYUSB
#error Keyboard is not compatible with Adafruit TinyUSB
#endif
#include <HID_Keyboard.h>
@@ -34,5 +37,3 @@ public:
Keyboard_(void);
};
extern Keyboard_ Keyboard;
#endif
+2 -2
View File
@@ -68,10 +68,10 @@ void KeyboardBLE_::sendReport(KeyReport* keys) {
memcpy(data.keycode, keys->keys, sizeof(data.keycode));
//stitch in report id
static uint8_t report[sizeof(hid_keyboard_report_t) +1];
static uint8_t report[sizeof(hid_keyboard_report_t) + 1];
report[0] = __BLEGetKeyboardReportID();
memcpy(&report[1], (uint8_t*)&data, sizeof(hid_keyboard_report_t));
PicoBluetoothBLEHID.send(&report, sizeof(hid_keyboard_report_t) +1);
PicoBluetoothBLEHID.send(&report, sizeof(hid_keyboard_report_t) + 1);
}
void KeyboardBLE_::sendConsumerReport(uint16_t key) {
+1 -1
View File
@@ -29,7 +29,7 @@
#include <FS.h>
#include <FSImpl.h>
#define LFS_NAME_MAX 32
#define LFS_NAME_MAX 255
#include "../lib/littlefs/lfs.h"
using namespace fs;
+1 -1
View File
@@ -2,7 +2,7 @@
// Just have a stub here that redirects to the actual source file
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
#define LFS_NAME_MAX 32
#define LFS_NAME_MAX 255
#define LFS_NO_DEBUG
#define LFS_NO_WARN
#define LFS_NO_ERROR
+1 -1
View File
@@ -1,4 +1,4 @@
#define LFS_NAME_MAX 32
#define LFS_NAME_MAX 255
#define LFS_NO_DEBUG
#define LFS_NO_WARN
#define LFS_NO_ERROR
+5 -4
View File
@@ -19,8 +19,11 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef MOUSE_h
#define MOUSE_h
#pragma once
#ifdef USE_TINYUSB
#error Mouse is not compatible with Adafruit TinyUSB
#endif
#include <HID_Mouse.h>
@@ -30,5 +33,3 @@ public:
virtual void move(int x, int y, signed char wheel = 0) override;
};
extern Mouse_ Mouse;
#endif
+6 -5
View File
@@ -1,5 +1,5 @@
/*
Mouse.h
MouseAbsolute.h
Copyright (c) 2015, Arduino LLC
Original code (pre-library): Copyright (c) 2011, Peter Barrett
@@ -19,8 +19,11 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef MOUSEABSOLUTE_h
#define MOUSEABSOLUTE_h
#pragma once
#ifdef USE_TINYUSB
#error MouseAbsolute is not compatible with Adafruit TinyUSB
#endif
#include <HID_Mouse.h>
@@ -30,5 +33,3 @@ public:
virtual void move(int x, int y, signed char wheel = 0) override;
};
extern MouseAbsolute_ MouseAbsolute;
#endif
+1 -1
View File
@@ -68,7 +68,7 @@ void MouseBLE_::setAbsolute(bool absolute) {
}
void MouseBLE_::move(int x, int y, signed char wheel) {
static uint8_t report[sizeof(hid_abs_mouse_report_t) +1];
static uint8_t report[sizeof(hid_abs_mouse_report_t) + 1];
if (!_absolute) {
hid_mouse_report_t data;

Some files were not shown because too many files have changed in this diff Show More