Compare commits
+948
File diff suppressed because it is too large
Load Diff
@@ -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 1
|
||||
#define ARDUINO_PICO_VERSION_STR "3.9.1"
|
||||
|
||||
+2
-2
@@ -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.1'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = u'3.9.0'
|
||||
release = u'3.9.1'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
||||
+13
-9
@@ -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
|
||||
|
||||
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.
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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];
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,6 @@
|
||||
#include "BluetoothDevice.h"
|
||||
#include "BluetoothHCI.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");
|
||||
}
|
||||
@@ -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)
|
||||
#######################################
|
||||
@@ -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
|
||||
+16
-14
@@ -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;
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@
|
||||
#include <btstack.h>
|
||||
|
||||
|
||||
class BluetoothHCI_ {
|
||||
class BluetoothHCI {
|
||||
public:
|
||||
void install();
|
||||
void begin();
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
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 <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();
|
||||
}
|
||||
}
|
||||
@@ -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,123 @@
|
||||
/*
|
||||
Bluetooth HID Master class, can connect to keyboards, mice, and joypads
|
||||
|
||||
Copyright (c) 2024 Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
|
||||
#include <BluetoothHCI.h>
|
||||
#include <btstack.h>
|
||||
|
||||
|
||||
// Write raw key up/down events, read ASCII chars out
|
||||
class HIDKeyStream : public Stream {
|
||||
public:
|
||||
HIDKeyStream();
|
||||
~HIDKeyStream();
|
||||
|
||||
bool setFIFOSize(size_t size);
|
||||
|
||||
void begin();
|
||||
void end();
|
||||
|
||||
virtual int peek() override;
|
||||
virtual int read() override;
|
||||
virtual int available() override;
|
||||
virtual int availableForWrite() override;
|
||||
virtual void flush() override;
|
||||
virtual size_t write(uint8_t c) override;
|
||||
virtual size_t write(const uint8_t *p, size_t len) override;
|
||||
using Print::write;
|
||||
operator bool();
|
||||
|
||||
private:
|
||||
bool _lshift = false;
|
||||
bool _rshift = false;
|
||||
bool _running = false;
|
||||
bool _holding = false;
|
||||
uint8_t _heldKey;
|
||||
|
||||
// Lockless, IRQ-handled circular queue
|
||||
uint32_t _writer;
|
||||
uint32_t _reader;
|
||||
size_t _fifoSize = 32;
|
||||
uint8_t *_queue;
|
||||
};
|
||||
|
||||
class BluetoothHIDMaster {
|
||||
public:
|
||||
void begin();
|
||||
bool connected();
|
||||
void end();
|
||||
bool hciRunning();
|
||||
bool running();
|
||||
|
||||
static const uint32_t keyboard_cod = 0x2540;
|
||||
static const uint32_t mouse_cod = 0x2540;
|
||||
static const uint32_t any_cod = 0;
|
||||
std::list<BTDeviceInfo> scan(uint32_t mask, int scanTimeSec = 5, bool async = false);
|
||||
bool scanAsyncDone();
|
||||
std::list<BTDeviceInfo> scanAsyncResult();
|
||||
|
||||
bool connect(const uint8_t *addr);
|
||||
bool connectKeyboard();
|
||||
bool connectMouse();
|
||||
bool disconnect();
|
||||
void clearPairing();
|
||||
|
||||
void onMouseMove(void (*)(void *, int, int, int), void *cbData = nullptr);
|
||||
void onMouseButton(void (*)(void *, int, bool), void *cbData = nullptr);
|
||||
void onKeyDown(void (*)(void *, int), void *cbData = nullptr);
|
||||
void onKeyUp(void (*)(void *, int), void *cbData = nullptr);
|
||||
void onConsumerKeyDown(void (*)(void *, int), void *cbData = nullptr);
|
||||
void onConsumerKeyUp(void (*)(void *, int), void *cbData = nullptr);
|
||||
|
||||
private:
|
||||
bool connectCOD(uint32_t cod);
|
||||
BluetoothHCI _hci;
|
||||
void hid_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
|
||||
uint8_t lastMB = 0;
|
||||
enum { NUM_KEYS = 6 };
|
||||
uint8_t last_keys[NUM_KEYS] = { 0 };
|
||||
uint16_t last_consumer_key = 0;
|
||||
void hid_host_handle_interrupt_report(btstack_hid_parser_t * parser);
|
||||
bool _running = false;
|
||||
volatile bool _hidConnected = false;
|
||||
uint16_t _hid_host_cid = 0;
|
||||
bool _hid_host_descriptor_available = false;
|
||||
uint8_t _hid_descriptor_storage[300];
|
||||
|
||||
void (*_mouseMoveCB)(void *, int, int, int) = nullptr;
|
||||
void *_mouseMoveData;
|
||||
void (*_mouseButtonCB)(void *, int, bool) = nullptr;
|
||||
void *_mouseButtonData;
|
||||
|
||||
void (*_keyDownCB)(void *, int) = nullptr;
|
||||
void *_keyDownData;
|
||||
void (*_keyUpCB)(void *, int) = nullptr;
|
||||
void *_keyUpData;
|
||||
|
||||
void (*_consumerKeyDownCB)(void *, int) = nullptr;
|
||||
void *_consumerKeyDownData;
|
||||
void (*_consumerKeyUpCB)(void *, int) = nullptr;
|
||||
void *_consumerKeyUpData;
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
Submodule libraries/LittleFS/lib/littlefs updated: 6a53d76e90...d01280e649
@@ -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;
|
||||
|
||||
@@ -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,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();
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ SerialBT KEYWORD1
|
||||
#######################################
|
||||
# Methods and Functions (KEYWORD2)
|
||||
#######################################
|
||||
setName KEYWORD2
|
||||
|
||||
#######################################
|
||||
# Constants (LITERAL1)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "framework-arduinopico",
|
||||
"version": "1.30900.0",
|
||||
"version": "1.30901.0",
|
||||
"description": "Arduino Wiring-based Framework (RPi Pico RP2040)",
|
||||
"keywords": [
|
||||
"framework",
|
||||
|
||||
+1
-1
@@ -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.1
|
||||
|
||||
# Required discoveries and monitors
|
||||
# ---------------------------------
|
||||
|
||||
+2
-1
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
Reference in New Issue
Block a user