Compare commits

..
21 Commits
Author SHA1 Message Date
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
62 changed files with 2845 additions and 245 deletions
+948
View File
File diff suppressed because it is too large Load Diff
+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 2
#define ARDUINO_PICO_VERSION_STR "3.9.2"
+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.2'
# The full version, including alpha/beta/rc tags.
release = u'3.9.0'
release = u'3.9.2'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
+13 -9
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,10 @@ 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 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 +58,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
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,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>
@@ -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]);
@@ -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::list<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"
@@ -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");
}
+32
View File
@@ -0,0 +1,32 @@
#######################################
# 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
# BTDeviceInfo
deviceClass KEYWORD2
address 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,6 +24,7 @@
#include <memory>
#include "BluetoothHCI.h"
#include "BluetoothLock.h"
#define CCALLBACKNAME _BTHCICB
#include <ctocppcallback.h>
@@ -34,29 +35,28 @@
static_cast<btstack_packet_handler_t>(_BTHCICB<void(uint8_t, uint16_t, uint8_t*, uint16_t), __COUNTER__ - 1>::callback))
void BluetoothHCI_::install() {
void BluetoothHCI::install() {
// Register for HCI events.
hci_event_callback_registration.callback = PACKETHANDLERCB(BluetoothHCI_, hci_packet_handler);
hci_event_callback_registration.callback = PACKETHANDLERCB(BluetoothHCI, hci_packet_handler);
hci_add_event_handler(&hci_event_callback_registration);
}
void BluetoothHCI_::begin() {
void BluetoothHCI::begin() {
_running = true;
hci_power_control(HCI_POWER_ON);
}
void BluetoothHCI_::uninstall() {
__lockBluetooth();
void BluetoothHCI::uninstall() {
BluetoothLock b;
hci_remove_event_handler(&hci_event_callback_registration);
_running = false;
__unlockBluetooth();
}
bool BluetoothHCI_::running() {
bool BluetoothHCI::running() {
return _hciRunning;
}
std::list<BTDeviceInfo> BluetoothHCI_::scan(uint32_t mask, int scanTimeSec, bool async) {
std::list<BTDeviceInfo> BluetoothHCI::scan(uint32_t mask, int scanTimeSec, bool async) {
_scanMask = mask;
_btdList.clear();
if (!_running) {
@@ -72,9 +72,11 @@ std::list<BTDeviceInfo> BluetoothHCI_::scan(uint32_t mask, int scanTimeSec, bool
inquiryTime = 1;
}
DEBUGV("HCI::scan(): inquiry start\n");
__lockBluetooth();
gap_inquiry_start(inquiryTime);
__unlockBluetooth();
// Only need to lock around the inquiry start command, not the wait
{
BluetoothLock b;
gap_inquiry_start(inquiryTime);
}
if (async) {
return _btdList;
}
@@ -86,15 +88,15 @@ std::list<BTDeviceInfo> BluetoothHCI_::scan(uint32_t mask, int scanTimeSec, bool
return _btdList;
}
bool BluetoothHCI_::scanAsyncDone() {
bool BluetoothHCI::scanAsyncDone() {
return _scanning;
}
std::list<BTDeviceInfo> BluetoothHCI_::scanAsyncResult() {
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 BluetoothHCI::hci_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
(void)channel;
(void)size;
@@ -20,6 +20,7 @@
#pragma once
#include <_needsbt.h>
#include <Arduino.h>
#include <list>
#include <memory>
@@ -28,7 +29,7 @@
#include <btstack.h>
class BluetoothHCI_ {
class BluetoothHCI {
public:
void install();
void begin();
@@ -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,209 @@
// KeyboardPiano example - Released to the public domain in 2024 by Earle F. Philhower, III
//
// Demonstrates using the BluetoothHIDMaster class to use a Bluetooth keyboard as a
// piano keyboard on the PicoW
//
// Hook up a phono plug to GP0 and GP1 (and GND of course...the 1st 3 pins on the PCB)
// Connect wired earbuds up and connect over BT from your keyboard and play some music.
#include <BluetoothHIDMaster.h>
#include <PWMAudio.h>
// We need the inverse map, borrow from the Keyboard library
#include <HID_Keyboard.h>
extern const uint8_t KeyboardLayout_en_US[128];
BluetoothHIDMaster hid;
PWMAudio pwm;
HIDKeyStream keystream;
int16_t sine[1000]; // One complete precalculated sine wave for oscillator use
void precalculateSine() {
for (int i = 0; i < 1000; i++) {
sine[i] = (int16_t)(2000.0 * sin(i * 2 * 3.14159 / 1000.0)); // Only make amplitude ~1/16 max so we can sum up w/o clipping
}
}
// Simple variable frequency resampling oscillator state
typedef struct {
uint32_t key; // Identifier of which key started this tone
uint32_t pos; // Current sine offset
uint32_t step; // Delta in fixed point 16p16 format
} Oscillator;
Oscillator osc[6]; // Look, ma! 6-note polyphony!
// Quiet down, now!
void silenceOscillators() {
noInterrupts();
for (int i = 0; i < 6; i++) {
osc[i].pos = 0;
osc[i].step = 0;
}
interrupts();
}
// PWM callback, generates sum of online oscillators
void fill() {
int num_samples = pwm.availableForWrite() / 2;
int16_t buff[32 * 2];
while (num_samples > 63) {
// Run in 32 LR sample chunks for speed, less loop overhead
for (int o = 0; o < 32; o++) {
int32_t sum = 0;
for (int i = 0; i < 6; i++) {
if (osc[i].step) {
sum += sine[osc[i].pos >> 16];
osc[i].pos += osc[i].step;
while (osc[i].pos >= 1000 << 16) {
osc[i].pos -= 1000 << 16;
}
}
}
if (sum > 32767) {
sum = 32767;
} else if (sum < -32767) {
sum = -32767;
}
buff[o * 2] = (int16_t) sum;
buff[o * 2 + 1] = (int16_t) sum;
}
pwm.write((const uint8_t *)buff, sizeof(buff));
num_samples -= 64;
}
}
// Mouse callbacks. Could keep track of global mouse position, update a cursor, etc.
void mm(void *cbdata, int dx, int dy, int dw) {
(void) cbdata;
Serial.printf("Mouse: X:%d Y:%d Wheel:%d\n", dx, dy, dw);
}
// Buttons are sent separately from movement
void mb(void *cbdata, int butt, bool down) {
(void) cbdata;
Serial.printf("Mouse: Button %d %s\n", butt, down ? "DOWN" : "UP");
}
// Convert a hertz floating point into a step fixed point 16p16
inline uint32_t stepForHz(float hz) {
const float stepHz = 1000.0 / 44100.0;
const float step = hz * stepHz;
return (uint32_t)(step * 65536.0);
}
uint32_t keyStepMap[128]; // The frequency of any raw HID key
void setupKeyStepMap() {
for (int i = 0; i < 128; i++) {
keyStepMap[i] = 0;
}
// Implements the "standard" PC keyboard to piano setup
// https://ux.stackexchange.com/questions/46669/mapping-piano-keys-to-computer-keyboard
keyStepMap[KeyboardLayout_en_US['a']] = stepForHz(261.6256);
keyStepMap[KeyboardLayout_en_US['w']] = stepForHz(277.1826);
keyStepMap[KeyboardLayout_en_US['s']] = stepForHz(293.6648);
keyStepMap[KeyboardLayout_en_US['e']] = stepForHz(311.1270);
keyStepMap[KeyboardLayout_en_US['d']] = stepForHz(329.6276);
keyStepMap[KeyboardLayout_en_US['f']] = stepForHz(349.2282);
keyStepMap[KeyboardLayout_en_US['t']] = stepForHz(369.9944);
keyStepMap[KeyboardLayout_en_US['g']] = stepForHz(391.9954);
keyStepMap[KeyboardLayout_en_US['y']] = stepForHz(415.3047);
keyStepMap[KeyboardLayout_en_US['h']] = stepForHz(440.0000);
keyStepMap[KeyboardLayout_en_US['u']] = stepForHz(466.1638);
keyStepMap[KeyboardLayout_en_US['j']] = stepForHz(493.8833);
keyStepMap[KeyboardLayout_en_US['k']] = stepForHz(523.2511);
keyStepMap[KeyboardLayout_en_US['o']] = stepForHz(554.3653);
keyStepMap[KeyboardLayout_en_US['l']] = stepForHz(587.3295);
keyStepMap[KeyboardLayout_en_US['p']] = stepForHz(622.2540);
keyStepMap[KeyboardLayout_en_US[';']] = stepForHz(659.2551);
keyStepMap[KeyboardLayout_en_US['\'']] = stepForHz(698.4565);
}
// We get make/break for every key which lets us hold notes while a key is depressed
void kb(void *cbdata, int key) {
bool state = (bool)cbdata;
if (state && key < 128) {
// Starting a new note
for (int i = 0; i < 6; i++) {
if (osc[i].step == 0) {
// This one is free
osc[i].key = key;
osc[i].pos = 0;
osc[i].step = keyStepMap[key];
break;
}
}
} else {
for (int i = 0; i < 6; i++) {
if (osc[i].key == (uint32_t)key) {
osc[i].step = 0;
break;
}
}
}
// The HIDKeyStream object converts a key and state into ASCII. HID key IDs do not map 1:1 to ASCII!
// Write the key and make/break state, then read 1 ASCII char back out.
keystream.write((uint8_t)key);
keystream.write((uint8_t)state);
Serial.printf("Keyboard: %02x %s = '%c'\n", key, state ? "DOWN" : "UP", state ? keystream.read() : '-');
}
// Consumer keys are the special media keys on most modern keyboards (mute/etc.)
void ckb(void *cbdata, int key) {
bool state = (bool)cbdata;
Serial.printf("Consumer: %02x %s\n", key, state ? "DOWN" : "UP");
}
void setup() {
Serial.begin();
delay(3000);
Serial.printf("Starting HID master, put your device in pairing mode now.\n");
// Init the sound generator
precalculateSine();
silenceOscillators();
setupKeyStepMap();
// Setup the HID key to ASCII conversion
keystream.begin();
// Init the PWM audio output
pwm.setStereo(true);
pwm.setBuffers(16, 64);
pwm.onTransmit(fill);
pwm.begin(44100);
// Mouse buttons and movement reported separately
hid.onMouseMove(mm);
hid.onMouseButton(mb);
// We can use the cbData as a flag to see if we're making or breaking a key
hid.onKeyDown(kb, (void *)true);
hid.onKeyUp(kb, (void *)false);
// Consumer keys are the special function ones like "mute" or "home"
hid.onConsumerKeyDown(ckb, (void *)true);
hid.onConsumerKeyUp(ckb, (void *)false);
hid.begin();
hid.connectKeyboard();
// or hid.connectMouse();
}
void loop() {
if (BOOTSEL) {
while (BOOTSEL) {
delay(1);
}
hid.disconnect();
hid.clearPairing();
Serial.printf("Restarting HID master, put your device in pairing mode now.\n");
hid.connectKeyboard();
}
}
+42
View File
@@ -0,0 +1,42 @@
#######################################
# Syntax Coloring Map
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
BluetoothHIDMaster KEYWORD1
HIDKeyStream KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
begin KEYWORD2
end KEYWORD2
scan KEYWORD2
scanAsyncDone KEYWORD2
scanAsyncResult KEYWORD2
connectKeyboard KEYWORD2
connectMouse KEYWORD2
hidConnected KEYWORD2
onMouseMove KEYWORD2
onMouseButton KEYWORD2
onKeyDown KEYWORD2
onKeyUp KEYWORD2
onConsumerKeyDown KEYWORD2
onConsumerKeyUp KEYWORD2
# BTDeviceInfo
deviceClass KEYWORD2
address KEYWORD2
addressString KEYWORD2
rssi KEYWORD2
name KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################
@@ -0,0 +1,11 @@
name=BluetoothHIDMaster
version=1.0
author=Earle F. Philhower, III <earlephilhower@yahoo.com>
maintainer=Earle F. Philhower, III <earlephilhower@yahoo.com>
sentence=Classic Bluetooth HID (Keyboard/Mouse/Joystick) master mode
paragraph=Classic Bluetooth HID (Keyboard/Mouse/Joystick) master mode
category=Communication
url=http://github.com/earlephilhower/arduino-pico
architectures=rp2040
dot_a_linkage=true
depends=BluetoothHCI
@@ -0,0 +1,644 @@
/*
Bluetooth HCI packet handler class
Copyright (c) 2024 Earle F. Philhower, III <earlephilhower@yahoo.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
// Based off of the BlueKitchen HID master demo
/*
Copyright (C) 2023 BlueKitchen GmbH
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holders nor the names of
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
4. Any redistribution, use, or modification is done solely for
personal benefit and not for any commercial purpose or for
monetary gain.
THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN
GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Please inquire about commercial licensing options at
contact@bluekitchen-gmbh.com
*/
#include <Arduino.h>
#include "btstack.h"
#include <list>
#include <memory>
#include <BluetoothLock.h>
#include "BluetoothHIDMaster.h"
#define CCALLBACKNAME _BTHIDCB
#include <ctocppcallback.h>
#define PACKETHANDLERCB(class, cbFcn) \
(CCALLBACKNAME<void(uint8_t, uint16_t, uint8_t*, uint16_t), __COUNTER__>::func = std::bind(&class::cbFcn, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4), \
static_cast<btstack_packet_handler_t>(CCALLBACKNAME<void(uint8_t, uint16_t, uint8_t*, uint16_t), __COUNTER__ - 1>::callback))
void BluetoothHIDMaster::begin() {
// Initialize HID Host
hid_host_init(_hid_descriptor_storage, sizeof(_hid_descriptor_storage));
hid_host_register_packet_handler(PACKETHANDLERCB(BluetoothHIDMaster, hid_packet_handler));
// Initialize L2CAP
l2cap_init();
// Initialize LE Security Manager. Needed for cross-transport key derivation
sm_init();
// Allow sniff mode requests by HID device and support role switch
gap_set_default_link_policy_settings(LM_LINK_POLICY_ENABLE_SNIFF_MODE | LM_LINK_POLICY_ENABLE_ROLE_SWITCH);
// try to become master on incoming connections
hci_set_master_slave_policy(HCI_ROLE_MASTER);
// enabled EIR
hci_set_inquiry_mode(INQUIRY_MODE_RSSI_AND_EIR);
_hci.install();
_running = true;
_hci.begin();
}
void BluetoothHIDMaster::end() {
BluetoothLock b;
_hci.uninstall();
_running = false;
}
bool BluetoothHIDMaster::running() {
return _hci.running() && _hidConnected;
}
bool BluetoothHIDMaster::hciRunning() {
return _hci.running();
}
void BluetoothHIDMaster::onMouseMove(void (*cb)(void *, int, int, int), void *cbData) {
_mouseMoveCB = cb;
_mouseMoveData = cbData;
}
void BluetoothHIDMaster::onMouseButton(void (*cb)(void *, int, bool), void *cbData) {
_mouseButtonCB = cb;
_mouseButtonData = cbData;
}
void BluetoothHIDMaster::onKeyDown(void (*cb)(void *, int), void *cbData) {
_keyDownCB = cb;
_keyDownData = cbData;
}
void BluetoothHIDMaster::onKeyUp(void (*cb)(void *, int), void *cbData) {
_keyUpCB = cb;
_keyUpData = cbData;
}
void BluetoothHIDMaster::onConsumerKeyDown(void (*cb)(void *, int), void *cbData) {
_consumerKeyDownCB = cb;
_consumerKeyDownData = cbData;
}
void BluetoothHIDMaster::onConsumerKeyUp(void (*cb)(void *, int), void *cbData) {
_consumerKeyUpCB = cb;
_consumerKeyUpData = cbData;
}
std::list<BTDeviceInfo> BluetoothHIDMaster::scan(uint32_t mask, int scanTimeSec, bool async) {
return _hci.scan(mask, scanTimeSec, async);
}
bool BluetoothHIDMaster::scanAsyncDone() {
return _hci.scanAsyncDone();
}
std::list<BTDeviceInfo> BluetoothHIDMaster::scanAsyncResult() {
return _hci.scanAsyncResult();
}
bool BluetoothHIDMaster::connected() {
return _hidConnected && _hid_host_descriptor_available;
}
bool BluetoothHIDMaster::connect(const uint8_t *addr) {
if (!_running) {
return false;
}
while (!_hci.running()) {
delay(10);
}
uint8_t a[6];
memcpy(a, addr, sizeof(a));
return ERROR_CODE_SUCCESS == hid_host_connect(a, HID_PROTOCOL_MODE_REPORT, &_hid_host_cid);
}
bool BluetoothHIDMaster::connectCOD(uint32_t cod) {
if (!_running) {
return false;
}
while (!_hci.running()) {
delay(10);
}
uint8_t a[6];
clearPairing();
auto l = scan(cod);
for (auto e : l) {
DEBUGV("Scan connecting %s at %s ... ", e.name(), e.addressString());
memcpy(a, e.address(), sizeof(a));
if (ERROR_CODE_SUCCESS == hid_host_connect(a, HID_PROTOCOL_MODE_REPORT, &_hid_host_cid)) {
DEBUGV("Connection established\n");
return true;
}
DEBUGV("Failed\n");
}
return false;
}
bool BluetoothHIDMaster::connectKeyboard() {
return connectCOD(0x2540);
}
bool BluetoothHIDMaster::connectMouse() {
return connectCOD(0x2580);
}
bool BluetoothHIDMaster::disconnect() {
BluetoothLock b;
if (connected()) {
hid_host_disconnect(_hid_host_cid);
}
if (!_running || !connected()) {
return false;
}
_hid_host_descriptor_available = false;
return true;
}
void BluetoothHIDMaster::clearPairing() {
BluetoothLock b;
if (connected()) {
hid_host_disconnect(_hid_host_cid);
}
gap_delete_all_link_keys();
_hid_host_descriptor_available = false;
}
void BluetoothHIDMaster::hid_host_handle_interrupt_report(btstack_hid_parser_t * parser) {
uint8_t new_keys[NUM_KEYS];
uint8_t tosend[NUM_KEYS];
int tosendcnt = 0;
memset(new_keys, 0, sizeof(new_keys));
int new_keys_count = 0;
uint16_t new_consumer_key = 0;
uint8_t newMB = 0;
bool noMB = false;
bool updCons = false;
bool updKey = false;
bool updMB = false;
bool updMouse = false;
int dx = 0;
int dy = 0;
int dwheel = 0;
while (btstack_hid_parser_has_more(parser)) {
uint16_t usage_page;
uint16_t usage;
int32_t value;
btstack_hid_parser_get_field(parser, &usage_page, &usage, &value);
if (usage_page == 0x01) {
updMouse = true;
if (usage == 0x30) {
dx = value;
} else if (usage == 0x31) {
dy = value;
} else if (usage == 0x38) {
dwheel = value;
}
} else if (usage_page == 0x09) {
updMB = true;
if (usage == 0) {
noMB = true;
}
if (!noMB && value && (usage > 0) && (usage < 9)) {
newMB |= 1 << (usage - 1);
}
} else if (usage_page == 0x0c) {
updCons = true;
if (value) {
new_consumer_key = usage;
// check if usage was used last time (and ignore in that case)
if (usage == last_consumer_key) {
usage = 0;
}
if (usage == 0) {
continue;
}
if (last_consumer_key) {
if (_consumerKeyUpCB) {
_consumerKeyUpCB(_consumerKeyUpData, last_consumer_key);
}
}
if (_consumerKeyDownCB) {
_consumerKeyDownCB(_consumerKeyDownData, usage);
}
} else if (last_consumer_key == usage) {
if (_consumerKeyUpCB) {
_consumerKeyUpCB(_consumerKeyUpData, last_consumer_key);
}
}
} else if (usage_page == 0x07) {
updKey = true;
if (value) {
new_keys[new_keys_count++] = usage;
// check if usage was used last time (and ignore in that case)
int i;
for (i = 0; i < NUM_KEYS; i++) {
if (usage == last_keys[i]) {
usage = 0;
}
}
if (usage == 0) {
continue;
}
tosend[tosendcnt++] = usage;
}
}
}
if (updKey) {
bool found;
for (int i = 0; i < NUM_KEYS; i++) {
found = false;
for (int j = 0; j < NUM_KEYS; j++) {
if (last_keys[i] == new_keys[j]) {
found = true;
break;
}
}
if (!found && last_keys[i] && _keyUpCB) {
_keyUpCB(_keyUpData, last_keys[i]);
}
}
for (int i = 0; _keyDownCB && i < tosendcnt; i++) {
_keyDownCB(_keyDownData, tosend[i]);
}
memcpy(last_keys, new_keys, NUM_KEYS);
}
if (updCons) {
last_consumer_key = new_consumer_key;
}
if (updMB) {
if (lastMB != newMB) {
for (int i = 0; i < 8; i++) {
int mask = 1 << i;
if ((lastMB & mask) && !(newMB & mask) && _mouseButtonCB) {
_mouseButtonCB(_mouseButtonData, i, false);
} else if (!(lastMB & mask) && (newMB & mask) && _mouseButtonCB) {
_mouseButtonCB(_mouseButtonData, i, true);
}
}
lastMB = newMB;
}
}
if (updMouse && _mouseMoveCB) {
_mouseMoveCB(_mouseMoveData, dx, dy, dwheel);
}
}
void BluetoothHIDMaster::hid_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
(void)channel;
(void)size;
if ((packet_type != HCI_EVENT_PACKET) || (hci_event_packet_get_type(packet) != HCI_EVENT_HID_META)) {
return;
}
uint8_t status;
switch (hci_event_hid_meta_get_subevent_code(packet)) {
case HID_SUBEVENT_INCOMING_CONNECTION:
// There is an incoming connection: we can accept it or decline it.
// The hid_host_report_mode in the hid_host_accept_connection function
// allows the application to request a protocol mode.
// For available protocol modes, see hid_protocol_mode_t in btstack_hid.h file.
hid_host_accept_connection(hid_subevent_incoming_connection_get_hid_cid(packet), HID_PROTOCOL_MODE_REPORT);
break;
case HID_SUBEVENT_CONNECTION_OPENED:
// The status field of this event indicates if the control and interrupt
// connections were opened successfully.
status = hid_subevent_connection_opened_get_status(packet);
if (status != ERROR_CODE_SUCCESS) {
DEBUGV("Connection failed, status 0x%02x\n", status);
_hidConnected = false;
_hid_host_cid = 0;
return;
}
_hidConnected = true;
_hid_host_descriptor_available = false;
_hid_host_cid = hid_subevent_connection_opened_get_hid_cid(packet);
DEBUGV("HID Host connected.\n");
break;
case HID_SUBEVENT_DESCRIPTOR_AVAILABLE:
// This event will follows HID_SUBEVENT_CONNECTION_OPENED event.
// For incoming connections, i.e. HID Device initiating the connection,
// the HID_SUBEVENT_DESCRIPTOR_AVAILABLE is delayed, and some HID
// reports may be received via HID_SUBEVENT_REPORT event. It is up to
// the application if these reports should be buffered or ignored until
// the HID descriptor is available.
status = hid_subevent_descriptor_available_get_status(packet);
if (status == ERROR_CODE_SUCCESS) {
_hid_host_descriptor_available = true;
} else {
DEBUGV("Cannot handle input report, HID Descriptor is not available, status 0x%02x\n", status);
}
break;
case HID_SUBEVENT_REPORT:
// Handle input report.
if (_hid_host_descriptor_available) {
uint16_t report_len = hid_subevent_report_get_report_len(packet);
const uint8_t *report = hid_subevent_report_get_report(packet);
// check if HID Input Report
if ((report_len < 1) || (*report != 0xa1)) {
break;
}
report++;
report_len--;;
btstack_hid_parser_t parser;
btstack_hid_parser_init(&parser, hid_descriptor_storage_get_descriptor_data(_hid_host_cid), hid_descriptor_storage_get_descriptor_len(_hid_host_cid), HID_REPORT_TYPE_INPUT, report, report_len);
hid_host_handle_interrupt_report(&parser);
}
break;
case HID_SUBEVENT_SET_PROTOCOL_RESPONSE:
// For incoming connections, the library will set the protocol mode of the
// HID Device as requested in the call to hid_host_accept_connection. The event
// reports the result. For connections initiated by calling hid_host_connect,
// this event will occur only if the established report mode is boot mode.
status = hid_subevent_set_protocol_response_get_handshake_status(packet);
if (status != HID_HANDSHAKE_PARAM_TYPE_SUCCESSFUL) {
DEBUGV("Error set protocol, status 0x%02x\n", status);
break;
}
switch ((hid_protocol_mode_t)hid_subevent_set_protocol_response_get_protocol_mode(packet)) {
case HID_PROTOCOL_MODE_BOOT:
DEBUGV("Protocol mode set: BOOT.\n");
break;
case HID_PROTOCOL_MODE_REPORT:
DEBUGV("Protocol mode set: REPORT.\n");
break;
default:
DEBUGV("Unknown protocol mode.\n");
break;
}
break;
case HID_SUBEVENT_CONNECTION_CLOSED:
// The connection was closed.
_hidConnected = false;
_hid_host_cid = 0;
_hid_host_descriptor_available = false;
DEBUGV("HID Host disconnected.\n");
break;
default:
break;
}
}
// Simplified US Keyboard with Shift modifier
#define CHAR_ILLEGAL 0xff
#define CHAR_RETURN '\n'
#define CHAR_ESCAPE 27
#define CHAR_TAB '\t'
#define CHAR_BACKSPACE 0x7f
/**
English (US)
*/
static const uint8_t keytable_us_none [] = {
CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 0-3 */
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', /* 4-13 */
'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', /* 14-23 */
'u', 'v', 'w', 'x', 'y', 'z', /* 24-29 */
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', /* 30-39 */
CHAR_RETURN, CHAR_ESCAPE, CHAR_BACKSPACE, CHAR_TAB, ' ', /* 40-44 */
'-', '=', '[', ']', '\\', CHAR_ILLEGAL, ';', '\'', 0x60, ',', /* 45-54 */
'.', '/', CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 55-60 */
CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 61-64 */
CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 65-68 */
CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 69-72 */
CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 73-76 */
CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 77-80 */
CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 81-84 */
'*', '-', '+', '\n', '1', '2', '3', '4', '5', /* 85-97 */
'6', '7', '8', '9', '0', '.', 0xa7, /* 97-100 */
};
static const uint8_t keytable_us_shift[] = {
CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 0-3 */
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', /* 4-13 */
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', /* 14-23 */
'U', 'V', 'W', 'X', 'Y', 'Z', /* 24-29 */
'!', '@', '#', '$', '%', '^', '&', '*', '(', ')', /* 30-39 */
CHAR_RETURN, CHAR_ESCAPE, CHAR_BACKSPACE, CHAR_TAB, ' ', /* 40-44 */
'_', '+', '{', '}', '|', CHAR_ILLEGAL, ':', '"', 0x7E, '<', /* 45-54 */
'>', '?', CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 55-60 */
CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 61-64 */
CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 65-68 */
CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 69-72 */
CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 73-76 */
CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 77-80 */
CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 81-84 */
'*', '-', '+', '\n', '1', '2', '3', '4', '5', /* 85-97 */
'6', '7', '8', '9', '0', '.', 0xb1, /* 97-100 */
};
bool HIDKeyStream::setFIFOSize(size_t size) {
if (!size || _running) {
return false;
}
_fifoSize = size + 1; // Always 1 unused entry
return true;
}
HIDKeyStream::HIDKeyStream() {
}
HIDKeyStream::~HIDKeyStream() {
end();
}
void HIDKeyStream::begin() {
if (_running) {
end();
}
_queue = new uint8_t[_fifoSize];
_writer = 0;
_reader = 0;
_lshift = false;
_rshift = false;
_holding = false;
_running = true;
}
void HIDKeyStream::end() {
if (!_running) {
return;
}
_running = false;
delete[] _queue;
}
int HIDKeyStream::peek() {
if (!_running) {
return -1;
}
if (_writer != _reader) {
return _queue[_reader];
}
return -1;
}
int HIDKeyStream::read() {
if (!_running) {
return -1;
}
if (_writer != _reader) {
auto ret = _queue[_reader];
asm volatile("" ::: "memory"); // Ensure the value is read before advancing
auto next_reader = (_reader + 1) % _fifoSize;
asm volatile("" ::: "memory"); // Ensure the reader value is only written once, correctly
_reader = next_reader;
return ret;
}
return -1;
}
int HIDKeyStream::available() {
if (!_running) {
return 0;
}
return (_fifoSize + _writer - _reader) % _fifoSize;
}
int HIDKeyStream::availableForWrite() {
return 2 * _fifoSize - available() - 1; // Every 2 write = 1 read buffer insertion
}
void HIDKeyStream::flush() {
// We always send blocking
}
size_t HIDKeyStream::write(uint8_t c) {
if (!availableForWrite()) {
return 0;
}
if (_holding) {
_holding = false;
bool state = (bool)c;
if (_heldKey == 0xe1) {
_lshift = state;
return 1;
} else if (_heldKey == 0xe6) {
_rshift = state;
return 1;
} else if (state) {
auto ascii = (_lshift || _rshift) ? keytable_us_shift[_heldKey] : keytable_us_none[_heldKey];
if (ascii != CHAR_ILLEGAL) {
auto next_writer = _writer + 1;
if (next_writer == _fifoSize) {
next_writer = 0;
}
if (next_writer != _reader) {
_queue[_writer] = ascii;
asm volatile("" ::: "memory"); // Ensure the queue is written before the written count advances
_writer = next_writer;
}
}
return 1;
} else {
return 1;
}
} else {
if ((c < sizeof(keytable_us_shift)) || (c == 0xe1) || (c == 0xe6)) {
_holding = true;
_heldKey = c;
}
return 1;
}
}
size_t HIDKeyStream::write(const uint8_t *p, size_t len) {
if (!_running || !len) {
return 0;
}
size_t cnt = 0;
for (size_t i = 0; i < len; i++) {
if (!write(p[len])) {
return cnt;
}
cnt++;
}
return cnt;
}
HIDKeyStream::operator bool() {
return _running;
}
@@ -0,0 +1,124 @@
/*
Bluetooth HID Master class, can connect to keyboards, mice, and joypads
Copyright (c) 2024 Earle F. Philhower, III <earlephilhower@yahoo.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#pragma once
#include <_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();
bool connected();
void end();
bool hciRunning();
bool running();
static const uint32_t keyboard_cod = 0x2540;
static const uint32_t mouse_cod = 0x2540;
static const uint32_t any_cod = 0;
std::list<BTDeviceInfo> scan(uint32_t mask, int scanTimeSec = 5, bool async = false);
bool scanAsyncDone();
std::list<BTDeviceInfo> scanAsyncResult();
bool connect(const uint8_t *addr);
bool connectKeyboard();
bool connectMouse();
bool disconnect();
void clearPairing();
void onMouseMove(void (*)(void *, int, int, int), void *cbData = nullptr);
void onMouseButton(void (*)(void *, int, bool), void *cbData = nullptr);
void onKeyDown(void (*)(void *, int), void *cbData = nullptr);
void onKeyUp(void (*)(void *, int), void *cbData = nullptr);
void onConsumerKeyDown(void (*)(void *, int), void *cbData = nullptr);
void onConsumerKeyUp(void (*)(void *, int), void *cbData = nullptr);
private:
bool connectCOD(uint32_t cod);
BluetoothHCI _hci;
void hid_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
uint8_t lastMB = 0;
enum { NUM_KEYS = 6 };
uint8_t last_keys[NUM_KEYS] = { 0 };
uint16_t last_consumer_key = 0;
void hid_host_handle_interrupt_report(btstack_hid_parser_t * parser);
bool _running = false;
volatile bool _hidConnected = false;
uint16_t _hid_host_cid = 0;
bool _hid_host_descriptor_available = false;
uint8_t _hid_descriptor_storage[300];
void (*_mouseMoveCB)(void *, int, int, int) = nullptr;
void *_mouseMoveData;
void (*_mouseButtonCB)(void *, int, bool) = nullptr;
void *_mouseButtonData;
void (*_keyDownCB)(void *, int) = nullptr;
void *_keyDownData;
void (*_keyUpCB)(void *, int) = nullptr;
void *_keyUpData;
void (*_consumerKeyDownCB)(void *, int) = nullptr;
void *_consumerKeyDownData;
void (*_consumerKeyUpCB)(void *, int) = nullptr;
void *_consumerKeyUpData;
};
+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;
}
+35 -2
View File
@@ -33,6 +33,7 @@
#include <WiFiClientSecure.h>
#include <memory>
#include <vector>
#ifdef DEBUG_ESP_HTTP_CLIENT
#ifdef DEBUG_ESP_PORT
@@ -153,6 +154,28 @@ 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;
@@ -229,6 +252,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 +355,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 +382,7 @@ protected:
String _uri;
String _protocol;
bool _secure = false;
String _headers;
String _base64Authorization;
@@ -368,6 +401,6 @@ protected:
String _location;
transferEncoding_t _transferEncoding = HTTPC_TE_IDENTITY;
std::unique_ptr<StreamString> _payload;
// Cookie jar support
CookieJar *_cookieJar = nullptr;
};
+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
@@ -22,6 +22,7 @@
#include <SerialBT.h>
void setup() {
SerialBT.setName("PicoW UPPERCASE 00:00:00:00:00:00");
SerialBT.begin();
}
+1
View File
@@ -11,6 +11,7 @@ SerialBT KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
setName KEYWORD2
#######################################
# Constants (LITERAL1)
+4 -1
View File
@@ -76,7 +76,10 @@ void SerialBT_::begin(unsigned long baud, uint16_t config) {
gap_discoverable_control(1);
gap_ssp_set_io_capability(SSP_IO_CAPABILITY_DISPLAY_YES_NO);
gap_set_local_name("PicoW Serial 00:00:00:00:00:00");
if (!_name) {
setName("PicoW Serial 00:00:00:00:00:00");
}
gap_set_local_name(_name);
// Turn on!
hci_power_control(HCI_POWER_ON);
+10
View File
@@ -37,6 +37,14 @@ public:
SerialBT_();
bool setFIFOSize(size_t size);
bool setName(const char *name) {
if (_running) {
return false;
}
free(_name);
_name = strdup(name);
return true;
}
void begin(unsigned long baud = 115200) override {
begin(baud, SERIAL_8N1);
@@ -82,4 +90,6 @@ private:
volatile int _writeLen = 0;
const void *_writeBuff;
char *_name = nullptr;
};
@@ -60,8 +60,9 @@ void handleRoot() {
static int cnt = 0;
digitalWrite(led, 1);
int sec = millis() / 1000;
int min = sec / 60;
int hr = min / 60;
int hr = sec / 3600;
int min = (sec / 60) % 60;
sec = sec % 60;
StreamString temp;
temp.reserve(500); // Preallocate a large chunk to avoid memory fragmentation
@@ -80,7 +81,7 @@ void handleRoot() {
<p>Page Count: %d</p>\
<img src=\"/test.svg\" />\
</body>\
</html>", hr, min % 60, sec % 60, rp2040.getFreeHeap(), ++cnt);
</html>", hr, min, sec, rp2040.getFreeHeap(), ++cnt);
server.send(200, "text/html", temp);
digitalWrite(led, 0);
}
@@ -0,0 +1,13 @@
# Upload Huge File To Filesystem Over HTTP
This project is an example of an HTTP server designed to facilitate the transfer of large files using the PUT method, in accordance with RFC specifications.
### Example cURL Command
```bash
curl -X PUT -T ./my-file.mp3 http://pico-ip/upload/my-file.mp3
```
## Resources
- RFC HTTP/1.0 - Additional Request Methods - PUT : [Link](https://datatracker.ietf.org/doc/html/rfc1945#appendix-D.1.1)
@@ -0,0 +1,92 @@
#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <uri/UriRegex.h>
#include <LittleFS.h>
#ifndef STASSID
#define STASSID "your-ssid"
#define STAPSK "your-password"
#endif
const char *ssid = STASSID;
const char *password = STAPSK;
WebServer server(80);
File rawFile;
void handleCreate() {
server.send(200, "text/plain", "");
}
void handleCreateProcess() {
String path = "/" + server.pathArg(0);
HTTPRaw& raw = server.raw();
if (raw.status == RAW_START) {
if (LittleFS.exists((char *)path.c_str())) {
LittleFS.remove((char *)path.c_str());
}
rawFile = LittleFS.open(path.c_str(), "w");
Serial.print("Upload: START, filename: ");
Serial.println(path);
} else if (raw.status == RAW_WRITE) {
if (rawFile) {
rawFile.write(raw.buf, raw.currentSize);
}
Serial.print("Upload: WRITE, Bytes: ");
Serial.println(raw.currentSize);
} else if (raw.status == RAW_END) {
if (rawFile) {
rawFile.close();
}
Serial.print("Upload: END, Size: ");
Serial.println(raw.totalSize);
}
}
void returnFail(String msg) {
server.send(500, "text/plain", msg + "\r\n");
}
void handleNotFound() {
String message = "File Not Found\n\n";
message += "URI: ";
message += server.uri();
message += "\nMethod: ";
message += (server.method() == HTTP_GET) ? "GET" : "POST";
message += "\nArguments: ";
message += server.args();
message += "\n";
for (uint8_t i = 0; i < server.args(); i++) {
message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
}
server.send(404, "text/plain", message);
}
void setup(void) {
Serial.begin(115200);
LittleFS.begin();
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.print("Connected to ");
Serial.println(ssid);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
server.on(UriRegex("/upload/(.*)"), HTTP_PUT, handleCreate, handleCreateProcess);
server.onNotFound(handleNotFound);
server.begin();
Serial.println("HTTP server started");
}
void loop(void) {
server.handleClient();
delay(2);//allow the cpu to switch to other tasks
}
+19
View File
@@ -31,6 +31,7 @@
enum HTTPUploadStatus { UPLOAD_FILE_START, UPLOAD_FILE_WRITE, UPLOAD_FILE_END,
UPLOAD_FILE_ABORTED
};
enum HTTPRawStatus { RAW_START, RAW_WRITE, RAW_END, RAW_ABORTED };
enum HTTPClientStatus { HC_NONE, HC_WAIT_READ, HC_WAIT_CLOSE };
enum HTTPAuthMethod { BASIC_AUTH, DIGEST_AUTH };
@@ -40,6 +41,10 @@ enum HTTPAuthMethod { BASIC_AUTH, DIGEST_AUTH };
#define HTTP_UPLOAD_BUFLEN 1436
#endif
#ifndef HTTP_RAW_BUFLEN
#define HTTP_RAW_BUFLEN 1436
#endif
#define HTTP_MAX_DATA_WAIT 5000 //ms to wait for the client to send the request
#define HTTP_MAX_DATA_AVAILABLE_WAIT 30 //ms to wait for the client to send the request when there is another client with data available
#define HTTP_MAX_POST_WAIT 5000 //ms to wait for POST data to arrive
@@ -63,6 +68,16 @@ typedef struct {
uint8_t buf[HTTP_UPLOAD_BUFLEN];
} HTTPUpload;
typedef struct {
HTTPRawStatus status;
size_t totalSize; // content size
size_t currentSize; // size of data currently in buf
uint8_t buf[HTTP_UPLOAD_BUFLEN];
void *data; // additional data
} HTTPRaw;
#include "detail/RequestHandler.h"
namespace fs {
@@ -97,6 +112,9 @@ public:
HTTPUpload& upload() {
return *_currentUpload;
}
HTTPRaw& raw() {
return *_currentRaw;
}
String pathArg(unsigned int i); // get request path argument by number
String arg(String name); // get request argument value by name
@@ -257,6 +275,7 @@ protected:
RequestArgument* _postArgs;
std::unique_ptr<HTTPUpload> _currentUpload;
std::unique_ptr<HTTPRaw> _currentRaw;
int _headerKeysCount;
RequestArgument* _currentHeaders;
+88 -130
View File
@@ -37,7 +37,7 @@
#define __STR(a) #a
#define _STR(a) __STR(a)
const char * _http_method_str[] = {
static const char * _http_method_str[] = {
#define XX(num, name, string) _STR(name),
HTTP_METHOD_MAP(XX)
#undef XX
@@ -188,7 +188,30 @@ HTTPServer::ClientFuture HTTPServer::_parseRequest(WiFiClient* client) {
}
}
if (!isForm) {
if (!isForm && _currentHandler && _currentHandler->canRaw(_currentUri)) {
log_v("Parse raw");
_currentRaw.reset(new HTTPRaw());
_currentRaw->status = RAW_START;
_currentRaw->totalSize = 0;
_currentRaw->currentSize = 0;
log_v("Start Raw");
_currentHandler->raw(*this, _currentUri, *_currentRaw);
_currentRaw->status = RAW_WRITE;
while (_currentRaw->totalSize < (size_t)_clientContentLength) {
_currentRaw->currentSize = client->readBytes(_currentRaw->buf, HTTP_RAW_BUFLEN);
_currentRaw->totalSize += _currentRaw->currentSize;
if (_currentRaw->currentSize == 0) {
_currentRaw->status = RAW_ABORTED;
_currentHandler->raw(*this, _currentUri, *_currentRaw);
return CLIENT_MUST_STOP;
}
_currentHandler->raw(*this, _currentUri, *_currentRaw);
}
_currentRaw->status = RAW_END;
_currentHandler->raw(*this, _currentUri, *_currentRaw);
log_v("Finish Raw");
} else if (!isForm) {
size_t plainLength;
char* plainBuf = readBytesWithTimeout(client, _clientContentLength, plainLength, HTTP_MAX_POST_WAIT);
if ((int)plainLength < (int)_clientContentLength) {
@@ -333,51 +356,22 @@ void HTTPServer::_uploadWriteByte(uint8_t b) {
_currentUpload->buf[_currentUpload->currentSize++] = b;
}
int HTTPServer::_uploadReadByte(WiFiClient* client) {
int HTTPServer::_uploadReadByte(WiFiClient * client) {
int res = client->read();
if (res < 0) {
// keep trying until you either read a valid byte or timeout
unsigned long startMillis = millis();
unsigned long timeoutIntervalMillis = client->getTimeout();
bool timedOut = false;
for (;;) {
if (!client->connected()) {
return -1;
}
// loosely modeled after blinkWithoutDelay pattern
while (!timedOut && !client->available() && client->connected()) {
delay(2);
timedOut = millis() - startMillis >= timeoutIntervalMillis;
}
res = client->read();
if (res >= 0) {
return res; // exit on a valid read
}
// NOTE: it is possible to get here and have all of the following
// assertions hold true
//
// -- client->available() > 0
// -- client->connected == true
// -- res == -1
//
// a simple retry strategy overcomes this which is to say the
// assertion is not permanent, but the reason that this works
// is elusive, and possibly indicative of a more subtle underlying
// issue
timedOut = millis() - startMillis >= timeoutIntervalMillis;
if (timedOut) {
return res; // exit on a timeout
}
while (!client->available() && client->connected()) {
delay(2);
}
res = client->read();
}
return res;
}
bool HTTPServer::_parseForm(WiFiClient* client, String boundary, uint32_t len) {
(void) len;
bool HTTPServer::_parseForm(WiFiClient * client, String boundary, uint32_t len) {
(void)len;
log_v("Parse Form: Boundary: %s Length: %d", boundary.c_str(), len);
String line;
int retry = 0;
@@ -425,10 +419,12 @@ bool HTTPServer::_parseForm(WiFiClient* client, String boundary, uint32_t len) {
argType = FPSTR(mimeTable[txt].mimeType);
line = client->readStringUntil('\r');
client->readStringUntil('\n');
if (line.length() > 12 && line.substring(0, 12).equalsIgnoreCase(FPSTR(Content_Type))) {
argType = line.substring(line.indexOf(':') + 2);
//skip next line
client->readStringUntil('\r');
while (line.length() > 0) {
if (line.length() > 12 && line.substring(0, 12).equalsIgnoreCase(FPSTR(Content_Type))) {
argType = line.substring(line.indexOf(':') + 2);
}
//skip over any other headers
line = client->readStringUntil('\r');
client->readStringUntil('\n');
}
log_v("PostArg Type: %s", argType.c_str());
@@ -446,7 +442,7 @@ bool HTTPServer::_parseForm(WiFiClient* client, String boundary, uint32_t len) {
}
log_v("PostArg Value: %s", argValue.c_str());
RequestArgument& arg = _postArgs[_postArgsLen++];
RequestArgument &arg = _postArgs[_postArgsLen++];
arg.key = argName;
arg.value = argValue;
@@ -470,98 +466,60 @@ bool HTTPServer::_parseForm(WiFiClient* client, String boundary, uint32_t len) {
_currentHandler->upload(*this, _currentUri, *_currentUpload);
}
_currentUpload->status = UPLOAD_FILE_WRITE;
int argByte = _uploadReadByte(client);
readfile:
while (argByte != 0x0D) {
if (argByte < 0) {
int fastBoundaryLen = 4 /* \r\n-- */ + boundary.length() + 1 /* \0 */;
char fastBoundary[fastBoundaryLen];
snprintf(fastBoundary, fastBoundaryLen, "\r\n--%s", boundary.c_str());
int boundaryPtr = 0;
while (true) {
int ret = _uploadReadByte(client);
if (ret < 0) {
// Unexpected, we should have had data available per above
return _parseFormUploadAborted();
}
_uploadWriteByte(argByte);
argByte = _uploadReadByte(client);
}
argByte = _uploadReadByte(client);
if (argByte < 0) {
return _parseFormUploadAborted();
}
if (argByte == 0x0A) {
argByte = _uploadReadByte(client);
if (argByte < 0) {
return _parseFormUploadAborted();
}
if ((char)argByte != '-') {
//continue reading the file
_uploadWriteByte(0x0D);
_uploadWriteByte(0x0A);
goto readfile;
} else {
argByte = _uploadReadByte(client);
if (argByte < 0) {
return _parseFormUploadAborted();
}
if ((char)argByte != '-') {
//continue reading the file
_uploadWriteByte(0x0D);
_uploadWriteByte(0x0A);
_uploadWriteByte((uint8_t)('-'));
goto readfile;
}
}
uint8_t endBuf[boundary.length()];
uint32_t i = 0;
while (i < boundary.length()) {
argByte = _uploadReadByte(client);
if (argByte < 0) {
return _parseFormUploadAborted();
}
if ((char)argByte == 0x0D) {
_uploadWriteByte(0x0D);
_uploadWriteByte(0x0A);
_uploadWriteByte((uint8_t)('-'));
_uploadWriteByte((uint8_t)('-'));
for (uint32_t j = 0; j < i; j++) {
_uploadWriteByte(endBuf[j]);
}
goto readfile;
}
endBuf[i++] = (uint8_t)argByte;
}
if (strstr((const char*)endBuf, boundary.c_str()) != nullptr) {
if (_currentHandler && _currentHandler->canUpload(_currentUri)) {
_currentHandler->upload(*this, _currentUri, *_currentUpload);
}
_currentUpload->totalSize += _currentUpload->currentSize;
_currentUpload->status = UPLOAD_FILE_END;
if (_currentHandler && _currentHandler->canUpload(_currentUri)) {
_currentHandler->upload(*this, _currentUri, *_currentUpload);
}
log_v("End File: %s Type: %s Size: %d", _currentUpload->filename.c_str(), _currentUpload->type.c_str(), _currentUpload->totalSize);
line = client->readStringUntil(0x0D);
client->readStringUntil(0x0A);
if (line == "--") {
log_v("Done Parsing POST");
char in = (char)ret;
if (in == fastBoundary[boundaryPtr]) {
// The input matched the current expected character, advance and possibly exit this file
boundaryPtr++;
if (boundaryPtr == fastBoundaryLen - 1) {
// We read the whole boundary line, we're done here!
break;
}
continue;
} else {
_uploadWriteByte(0x0D);
_uploadWriteByte(0x0A);
_uploadWriteByte((uint8_t)('-'));
_uploadWriteByte((uint8_t)('-'));
for (uint32_t j = 0; j < boundary.length(); j++) {
_uploadWriteByte(endBuf[j]);
// The char doesn't match what we want, so dump whatever matches we had, the read in char, and reset ptr to start
for (int i = 0; i < boundaryPtr; i++) {
_uploadWriteByte(fastBoundary[i]);
}
if (in == fastBoundary[0]) {
// This could be the start of the real end, mark it so and don't emit/skip it
boundaryPtr = 1;
} else {
// Not the 1st char of our pattern, so emit and ignore
_uploadWriteByte(in);
boundaryPtr = 0;
}
argByte = _uploadReadByte(client);
goto readfile;
}
} else {
_uploadWriteByte(0x0D);
goto readfile;
}
break;
// Found the boundary string, finish processing this file upload
if (_currentHandler && _currentHandler->canUpload(_currentUri)) {
_currentHandler->upload(*this, _currentUri, *_currentUpload);
}
_currentUpload->totalSize += _currentUpload->currentSize;
_currentUpload->status = UPLOAD_FILE_END;
if (_currentHandler && _currentHandler->canUpload(_currentUri)) {
_currentHandler->upload(*this, _currentUri, *_currentUpload);
}
log_v("End File: %s Type: %s Size: %d", _currentUpload->filename.c_str(), _currentUpload->type.c_str(), (int)_currentUpload->totalSize);
if (!client->connected()) {
return _parseFormUploadAborted();
}
line = client->readStringUntil('\r');
client->readStringUntil('\n');
if (line == "--") { // extra two dashes mean we reached the end of all form fields
log_v("Done Parsing POST");
break;
}
continue;
}
}
}
@@ -570,7 +528,7 @@ readfile:
int iarg;
int totalArgs = ((WEBSERVER_MAX_POST_ARGS - _postArgsLen) < _currentArgCount) ? (WEBSERVER_MAX_POST_ARGS - _postArgsLen) : _currentArgCount;
for (iarg = 0; iarg < totalArgs; iarg++) {
RequestArgument& arg = _postArgs[_postArgsLen++];
RequestArgument &arg = _postArgs[_postArgsLen++];
arg.key = _currentArgs[iarg].key;
arg.value = _currentArgs[iarg].value;
}
@@ -579,7 +537,7 @@ readfile:
}
_currentArgs = new RequestArgument[_postArgsLen];
for (iarg = 0; iarg < _postArgsLen; iarg++) {
RequestArgument& arg = _currentArgs[iarg];
RequestArgument &arg = _currentArgs[iarg];
arg.key = _postArgs[iarg].key;
arg.value = _postArgs[iarg].value;
}
@@ -595,7 +553,7 @@ readfile:
return false;
}
String HTTPServer::urlDecode(const String& text) {
String HTTPServer::urlDecode(const String & text) {
String decoded = "";
char temp[] = "0x00";
unsigned int len = text.length();
@@ -171,6 +171,7 @@ void WebServerTemplate<ServerType, DefaultPort>::handleClient() {
}
_currentStatus = HC_NONE;
_currentUpload.reset();
_currentRaw.reset();
}
if (callYield) {
@@ -15,6 +15,10 @@ public:
(void) uri;
return false;
}
virtual bool canRaw(String uri) {
(void) uri;
return false;
}
virtual bool handle(HTTPServer& server, HTTPMethod requestMethod, String requestUri) {
(void) server;
(void) requestMethod;
@@ -26,6 +30,11 @@ public:
(void) requestUri;
(void) upload;
}
virtual void raw(HTTPServer& server, String requestUri, HTTPRaw& raw) {
(void) server;
(void) requestUri;
(void) raw;
}
RequestHandler* next() {
return _next;
@@ -42,6 +42,14 @@ public:
return true;
}
bool canRaw(String requestUri) override {
(void) requestUri;
if (!_ufn || _method == HTTP_GET) {
return false;
}
return true;
}
bool handle(HTTPServer& server, HTTPMethod requestMethod, String requestUri) override {
(void) server;
@@ -61,6 +69,14 @@ public:
}
}
void raw(HTTPServer& server, String requestUri, HTTPRaw& raw) override {
(void)server;
(void)raw;
if (canRaw(requestUri)) {
_ufn();
}
}
protected:
HTTPServer::THandlerFunction _fn;
HTTPServer::THandlerFunction _ufn;
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "framework-arduinopico",
"version": "1.30900.0",
"version": "1.30902.0",
"description": "Arduino Wiring-based Framework (RPi Pico RP2040)",
"keywords": [
"framework",
+1 -1
View File
@@ -20,7 +20,7 @@
# https://github.com/arduino/Arduino/wiki/Arduino-IDE-1.5---3rd-party-Hardware-specification
name=Raspberry Pi RP2040 Boards
version=3.9.0
version=3.9.2
# Required discoveries and monitors
# ---------------------------------
+2 -1
View File
@@ -15,7 +15,8 @@ for dir in ./cores/rp2040 ./libraries/EEPROM ./libraries/I2S ./libraries/SingleF
./libraries/JoystickBLE ./libraries/KeyboardBLE ./libraries/MouseBLE \
./libraries/lwIP_w5500 ./libraries/lwIP_w5100 ./libraries/lwIP_enc28j60 \
./libraries/SPISlave ./libraries/lwIP_ESPHost ./libraries/FatFS\
./libraries/FatFSUSB ./libraries/BluetoothAudio; do
./libraries/FatFSUSB ./libraries/BluetoothAudio ./libraries/BluetoothHCI \
./libraries/BluetoothHIDMaster; do
find $dir -type f \( -name "*.c" -o -name "*.h" -o -name "*.cpp" \) -a \! -path '*api*' -exec astyle --suffix=none --options=./tests/astyle_core.conf \{\} \;
find $dir -type f -name "*.ino" -exec astyle --suffix=none --options=./tests/astyle_examples.conf \{\} \;
done
+44 -1
View File
@@ -103,6 +103,12 @@ target_compile_definitions(picow-noipv6-nobtc-noble PUBLIC
LWIP_IPV6=0
)
add_library(picow-noipv6-nobtc-noble-big STATIC)
target_compile_definitions(picow-noipv6-nobtc-noble-big PUBLIC
__LWIP_MEMMULT=2
LWIP_IPV6=0
)
set(picow_link_libraries
common
cyw43_driver
@@ -121,20 +127,40 @@ target_link_libraries(picow-noipv6-nobtc-noble
${picow_link_libraries}
)
target_link_libraries(picow-noipv6-nobtc-noble-big
${picow_link_libraries}
)
add_library(picow-ipv6-nobtc-noble STATIC)
target_compile_definitions(picow-ipv6-nobtc-noble PUBLIC
LWIP_IPV6=1
)
add_library(picow-ipv6-nobtc-noble-big STATIC)
target_compile_definitions(picow-ipv6-nobtc-noble-big PUBLIC
__LWIP_MEMMULT=2
LWIP_IPV6=1
)
target_link_libraries(picow-ipv6-nobtc-noble
${picow_link_libraries}
)
target_link_libraries(picow-ipv6-nobtc-noble-big
${picow_link_libraries}
)
add_library(picow-noipv6-btc-ble STATIC)
target_compile_definitions(picow-noipv6-btc-ble PUBLIC
LWIP_IPV6=0
)
add_library(picow-noipv6-btc-ble-big STATIC)
target_compile_definitions(picow-noipv6-btc-ble-big PUBLIC
__LWIP_MEMMULT=2
LWIP_IPV6=0
)
set(picow_bt_link_libraries
pico_btstack_cyw43
pico_btstack_ble
@@ -148,17 +174,34 @@ target_link_libraries(picow-noipv6-btc-ble
${picow_bt_link_libraries}
)
target_link_libraries(picow-noipv6-btc-ble-big
${picow_link_libraries}
${picow_bt_link_libraries}
)
add_library(picow-ipv6-btc-ble STATIC)
target_compile_definitions(picow-ipv6-btc-ble PUBLIC
LWIP_IPV6=1
)
add_library(picow-ipv6-btc-ble-big STATIC)
target_compile_definitions(picow-ipv6-btc-ble-big PUBLIC
__LWIP_MEMMULT=2
LWIP_IPV6=1
)
target_link_libraries(picow-ipv6-btc-ble
${picow_link_libraries}
${picow_bt_link_libraries}
)
foreach(tgt pico picow-noipv6-nobtc-noble picow-ipv6-nobtc-noble picow-noipv6-btc-ble picow-ipv6-btc-ble)
target_link_libraries(picow-ipv6-btc-ble-big
${picow_link_libraries}
${picow_bt_link_libraries}
)
foreach(tgt pico picow-noipv6-nobtc-noble picow-ipv6-nobtc-noble picow-noipv6-btc-ble picow-ipv6-btc-ble picow-noipv6-nobtc-noble-big picow-ipv6-nobtc-noble-big picow-noipv6-btc-ble-big picow-ipv6-btc-ble-big)
add_custom_command(TARGET ${tgt} POST_BUILD
COMMAND ar d lib${tgt}.a stdio.c.obj stdio_uart.c.obj stdio_usb.c.obj stdio_usb_descriptors.c.obj pico_malloc.c.obj
COMMAND ar d lib${tgt}.a btstack_flash_bank.c.obj # Need to override with our own implementation
+13 -9
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,10 @@ 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 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 +58,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
+13
View File
@@ -117,6 +117,19 @@ def BuildIPBTStack(name):
print("%s.menu.ipbtstack.ipv4ipv6btcble=IPv4 + IPv6 + Bluetooth" % (name))
print('%s.menu.ipbtstack.ipv4ipv6btcble.build.libpicow=libpicow-ipv6-btc-ble.a' % (name))
print('%s.menu.ipbtstack.ipv4ipv6btcble.build.libpicowdefs=-DLWIP_IPV6=1 -DLWIP_IPV4=1 -DENABLE_CLASSIC=1 -DENABLE_BLE=1' % (name))
print("%s.menu.ipbtstack.ipv4onlybig=IPv4 Only - 32K" % (name))
print('%s.menu.ipbtstack.ipv4onlybig.build.libpicow=libpicow-noipv6-nobtc-noble-big.a' % (name))
print('%s.menu.ipbtstack.ipv4onlybig.build.libpicowdefs=-DLWIP_IPV6=0 -DLWIP_IPV4=1 -D__LWIP_MEMMULT=2' % (name))
print("%s.menu.ipbtstack.ipv4ipv6big=IPv4 + IPv6 - 32K" % (name))
print('%s.menu.ipbtstack.ipv4ipv6big.build.libpicow=libpicow-ipv6-nobtc-noble-big.a' % (name))
print('%s.menu.ipbtstack.ipv4ipv6big.build.libpicowdefs=-DLWIP_IPV6=1 -DLWIP_IPV4=1 -D__LWIP_MEMMULT=2' % (name))
print("%s.menu.ipbtstack.ipv4btcblebig=IPv4 + Bluetooth - 32K" % (name))
print('%s.menu.ipbtstack.ipv4btcblebig.build.libpicow=libpicow-noipv6-btc-ble-big.a' % (name))
print('%s.menu.ipbtstack.ipv4btcblebig.build.libpicowdefs=-DLWIP_IPV6=0 -DLWIP_IPV4=1 -DENABLE_CLASSIC=1 -DENABLE_BLE=1 -D__LWIP_MEMMULT=2' % (name))
print("%s.menu.ipbtstack.ipv4ipv6btcblebig=IPv4 + IPv6 + Bluetooth - 32K" % (name))
print('%s.menu.ipbtstack.ipv4ipv6btcblebig.build.libpicow=libpicow-ipv6-btc-ble-big.a' % (name))
print('%s.menu.ipbtstack.ipv4ipv6btcblebig.build.libpicowdefs=-DLWIP_IPV6=1 -DLWIP_IPV4=1 -DENABLE_CLASSIC=1 -DENABLE_BLE=1 -D__LWIP_MEMMULT=2' % (name))
def BuildUploadMethodMenu(name):
for a, b, c, d, e, f in [ ["default", "Default (UF2)", 256, "picoprobe_cmsis_dap.tcl", "uf2conv", "uf2conv-network"],
@@ -46,4 +46,35 @@
#define SPI_HOWMANY (2u)
#define WIRE_HOWMANY (2u)
#define GPIO0 (0u)
#define GPIO1 (1u)
#define GPIO2 (2u)
#define GPIO3 (3u)
#define GPIO4 (4u)
#define GPIO5 (5u)
#define GPIO6 (6u)
#define GPIO7 (7u)
#define GPIO8 (8u)
#define GPIO9 (9u)
#define GPIO10 (10u)
#define GPIO11 (11u)
#define GPIO12 (12u)
#define GPIO13 (13u)
#define GPIO14 (14u)
#define GPIO15 (15u)
#define GPIO16 (16u)
#define GPIO17 (17u)
#define GPIO18 (18u)
#define GPIO19 (19u)
#define GPIO20 (20u)
#define GPIO21 (21u)
#define GPIO22 (22u)
#define GPIO23 (23u)
#define GPIO24 (24u)
#define GPIO25 (25u)
#define GPIO26 (26u)
#define GPIO27 (27u)
#define GPIO28 (28u)
#define GPIO29 (29u)
#include "../generic/common.h"