Req. Arduino Core 3.0.2
This commit is contained in:
parent
21f4d6c054
commit
825e48086b
|
|
@ -40,7 +40,8 @@ void checkAndUpdateWiFiCredentials() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IRAM_ATTR void checkWiFi(unsigned long tickMillis) {
|
IRAM_ATTR
|
||||||
|
void checkWiFi(unsigned long tickMillis) {
|
||||||
static unsigned long lastAttempt = 0;
|
static unsigned long lastAttempt = 0;
|
||||||
static bool bConnecting = false;
|
static bool bConnecting = false;
|
||||||
|
|
||||||
|
|
|
||||||
39
HermitCrab.h
39
HermitCrab.h
|
|
@ -20,7 +20,7 @@
|
||||||
#ifndef DEBUG
|
#ifndef DEBUG
|
||||||
#define DEBUG 1 // Set to 0 to disable debug output
|
#define DEBUG 1 // Set to 0 to disable debug output
|
||||||
#endif
|
#endif
|
||||||
#undef DEBUG
|
|
||||||
|
|
||||||
//#define BLE_DEBUG
|
//#define BLE_DEBUG
|
||||||
#ifdef BLE_DEBUG
|
#ifdef BLE_DEBUG
|
||||||
|
|
@ -96,28 +96,27 @@
|
||||||
// End of PIN Definition
|
// End of PIN Definition
|
||||||
// =======================
|
// =======================
|
||||||
|
|
||||||
|
|
||||||
|
// Resolution for all Channels
|
||||||
#define PWM_RESOLUTION 10 // 10-bit resolution
|
#define PWM_RESOLUTION 10 // 10-bit resolution
|
||||||
#define PWM_MOTOR_RESOLUTION 8
|
|
||||||
|
|
||||||
#define PWM_AP_CHANNEL 9
|
|
||||||
#define PWM_SC_CHANNEL 10
|
|
||||||
#define PWM_AP_FREQ 1
|
|
||||||
#define PWM_SC_FREQ 2
|
|
||||||
|
|
||||||
#define PWM_MIST_CHANNEL 8 // MIST
|
|
||||||
#define PWM_MIST_FREQ 1 // MIST
|
|
||||||
|
|
||||||
#define PWM_HEATER1_CHANNEL 2
|
|
||||||
#define PWM_HEATER2_CHANNEL 3
|
|
||||||
#define PWM_LIGHT_CHANNEL 4
|
|
||||||
#define PWM_MOTOR_CHANNEL 5
|
|
||||||
#define PWM_FAN_CHANNEL 6
|
|
||||||
|
|
||||||
#define PWM_1KHZ_FREQ 1000 // 1 kHz frequency
|
|
||||||
#define PWM_25KHZ_FREQ 25000 // 25KHz
|
|
||||||
|
|
||||||
#define PWM_FULL 1023
|
#define PWM_FULL 1023
|
||||||
#define PWM_OFF 0
|
#define PWM_OFF 0
|
||||||
|
|
||||||
|
// High Speed Group - Motor and Fan (26KHz)
|
||||||
|
#define PWM_MOTOR_CHANNEL 0
|
||||||
|
#define PWM_FAN_CHANNEL 1
|
||||||
|
// Medium Speed Group - LED & Heater (1KHz)
|
||||||
|
#define PWM_LIGHT_CHANNEL 2
|
||||||
|
#define PWM_HEATER1_CHANNEL 3
|
||||||
|
#define PWM_HEATER2_CHANNEL 4
|
||||||
|
// Low Speed Group - Mist, WifiLED (1Hz)
|
||||||
|
#define PWM_MIST_CHANNEL 8
|
||||||
|
#define PWM_WIFI_LED_CHANNEL 9
|
||||||
|
|
||||||
|
#define PWM_1HZ_FREQ 1 // 1 Hz
|
||||||
|
#define PWM_1KHZ_FREQ 1221 // 1 kHz
|
||||||
|
#define PWM_26KHZ_FREQ 26042 // 26KHz
|
||||||
|
|
||||||
#define TAG "HC"
|
#define TAG "HC"
|
||||||
|
|
||||||
enum EVENT_TYPE {
|
enum EVENT_TYPE {
|
||||||
|
|
|
||||||
6
LED0.cpp
6
LED0.cpp
|
|
@ -15,7 +15,7 @@ void CLED0::setup(uint8_t _pin, uint16_t _freq, uint16_t _channel) {
|
||||||
setDuty(duty);
|
setDuty(duty);
|
||||||
};
|
};
|
||||||
|
|
||||||
MY_IRAM_ATTR void CLED0::setFreq(uint16_t _freq) {
|
void CLED0::setFreq(uint16_t _freq) {
|
||||||
if (freq != _freq) {
|
if (freq != _freq) {
|
||||||
if (_freq == 0) {
|
if (_freq == 0) {
|
||||||
ledcDetach(pin);
|
ledcDetach(pin);
|
||||||
|
|
@ -31,7 +31,7 @@ MY_IRAM_ATTR void CLED0::setFreq(uint16_t _freq) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MY_IRAM_ATTR void CLED0::setDuty() {
|
void CLED0::setDuty() {
|
||||||
uint16_t _duty;
|
uint16_t _duty;
|
||||||
|
|
||||||
if (bAC) _duty = LED0_DUTY_AC;
|
if (bAC) _duty = LED0_DUTY_AC;
|
||||||
|
|
@ -41,7 +41,7 @@ MY_IRAM_ATTR void CLED0::setDuty() {
|
||||||
ledcWrite(PIN_LED_WIFI, PWM_FULL * (100 - _duty) / 100); // Light Blink
|
ledcWrite(PIN_LED_WIFI, PWM_FULL * (100 - _duty) / 100); // Light Blink
|
||||||
}
|
}
|
||||||
|
|
||||||
MY_IRAM_ATTR void CLED0::setDuty(uint16_t _duty) {
|
void CLED0::setDuty(uint16_t _duty) {
|
||||||
if (duty != _duty) {
|
if (duty != _duty) {
|
||||||
if (bPWMMode)
|
if (bPWMMode)
|
||||||
ledcWrite(PIN_LED_WIFI, PWM_FULL * (100 - _duty) / 100); // Light Blink
|
ledcWrite(PIN_LED_WIFI, PWM_FULL * (100 - _duty) / 100); // Light Blink
|
||||||
|
|
|
||||||
965
Libraries/Adafruit_SSD1306.cpp
Normal file
965
Libraries/Adafruit_SSD1306.cpp
Normal file
|
|
@ -0,0 +1,965 @@
|
||||||
|
/*!
|
||||||
|
* @file Adafruit_SSD1306.cpp
|
||||||
|
*
|
||||||
|
* @mainpage Arduino library for monochrome OLEDs based on SSD1306 drivers.
|
||||||
|
*
|
||||||
|
* @section intro_sec Introduction
|
||||||
|
*
|
||||||
|
* This is documentation for Adafruit's SSD1306 library for monochrome
|
||||||
|
* OLED displays: http://www.adafruit.com/category/63_98
|
||||||
|
*
|
||||||
|
* These displays use I2C or SPI to communicate. I2C requires 2 pins
|
||||||
|
* (SCL+SDA) and optionally a RESET pin. SPI requires 4 pins (MOSI, SCK,
|
||||||
|
* select, data/command) and optionally a reset pin. Hardware SPI or
|
||||||
|
* 'bitbang' software SPI are both supported.
|
||||||
|
*
|
||||||
|
* Adafruit invests time and resources providing this open source code,
|
||||||
|
* please support Adafruit and open-source hardware by purchasing
|
||||||
|
* products from Adafruit!
|
||||||
|
*
|
||||||
|
* @section dependencies Dependencies
|
||||||
|
*
|
||||||
|
* This library depends on <a
|
||||||
|
* href="https://github.com/adafruit/Adafruit-GFX-Library"> Adafruit_GFX</a>
|
||||||
|
* being present on your system. Please make sure you have installed the latest
|
||||||
|
* version before using this library.
|
||||||
|
*
|
||||||
|
* @section author Author
|
||||||
|
*
|
||||||
|
* Written by Limor Fried/Ladyada for Adafruit Industries, with
|
||||||
|
* contributions from the open source community.
|
||||||
|
*
|
||||||
|
* @section license License
|
||||||
|
*
|
||||||
|
* BSD license, all text above, and the splash screen included below,
|
||||||
|
* must be included in any redistribution.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <pgmspace.h>
|
||||||
|
#include "Adafruit_SSD1306.h"
|
||||||
|
#include <Adafruit_GFX.h>
|
||||||
|
|
||||||
|
// SOME DEFINES AND STATIC VARIABLES USED INTERNALLY -----------------------
|
||||||
|
|
||||||
|
#if defined(I2C_BUFFER_LENGTH)
|
||||||
|
#define WIRE_MAX min(256, I2C_BUFFER_LENGTH) ///< Particle or similar Wire lib
|
||||||
|
#else
|
||||||
|
#define WIRE_MAX 32 ///< Use common Arduino core default
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define ssd1306_swap(a, b) \
|
||||||
|
(((a) ^= (b)), ((b) ^= (a)), ((a) ^= (b))) ///< No-temp-var swap operation
|
||||||
|
|
||||||
|
#if ARDUINO >= 100
|
||||||
|
#define WIRE_WRITE wire->write ///< Wire write function in recent Arduino lib
|
||||||
|
#else
|
||||||
|
#define WIRE_WRITE wire->send ///< Wire write function in older Arduino lib
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define SSD1306_SELECT digitalWrite(csPin, LOW); ///< Device select
|
||||||
|
#define SSD1306_DESELECT digitalWrite(csPin, HIGH); ///< Device deselect
|
||||||
|
#define SSD1306_MODE_COMMAND digitalWrite(dcPin, LOW); ///< Command mode
|
||||||
|
#define SSD1306_MODE_DATA digitalWrite(dcPin, HIGH); ///< Data mode
|
||||||
|
|
||||||
|
#if (ARDUINO >= 157) && !defined(ARDUINO_STM32_FEATHER)
|
||||||
|
#define SETWIRECLOCK wire->setClock(wireClk) ///< Set before I2C transfer
|
||||||
|
#define RESWIRECLOCK wire->setClock(restoreClk) ///< Restore after I2C xfer
|
||||||
|
#else // setClock() is not present in older Arduino Wire lib (or WICED)
|
||||||
|
#define SETWIRECLOCK ///< Dummy stand-in define
|
||||||
|
#define RESWIRECLOCK ///< keeps compiler happy
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// The definition of 'transaction' is broadened a bit in the context of
|
||||||
|
// this library -- referring not just to SPI transactions (if supported
|
||||||
|
// in the version of the SPI library being used), but also chip select
|
||||||
|
// (if SPI is being used, whether hardware or soft), and also to the
|
||||||
|
// beginning and end of I2C transfers (the Wire clock may be sped up before
|
||||||
|
// issuing data to the display, then restored to the default rate afterward
|
||||||
|
// so other I2C device types still work). All of these are encapsulated
|
||||||
|
// in the TRANSACTION_* macros.
|
||||||
|
|
||||||
|
// Check first if Wire, then hardware SPI, then soft SPI:
|
||||||
|
#define TRANSACTION_START SETWIRECLOCK;
|
||||||
|
#define TRANSACTION_END RESWIRECLOCK; \
|
||||||
|
|
||||||
|
// CONSTRUCTORS, DESTRUCTOR ------------------------------------------------
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Constructor for I2C-interfaced SSD1306 displays.
|
||||||
|
@param w
|
||||||
|
Display width in pixels
|
||||||
|
@param h
|
||||||
|
Display height in pixels
|
||||||
|
@param twi
|
||||||
|
Pointer to an existing TwoWire instance (e.g. &Wire, the
|
||||||
|
microcontroller's primary I2C bus).
|
||||||
|
@param rst_pin
|
||||||
|
Reset pin (using Arduino pin numbering), or -1 if not used
|
||||||
|
(some displays might be wired to share the microcontroller's
|
||||||
|
reset pin).
|
||||||
|
@param clkDuring
|
||||||
|
Speed (in Hz) for Wire transmissions in SSD1306 library calls.
|
||||||
|
Defaults to 400000 (400 KHz), a known 'safe' value for most
|
||||||
|
microcontrollers, and meets the SSD1306 datasheet spec.
|
||||||
|
Some systems can operate I2C faster (800 KHz for ESP32, 1 MHz
|
||||||
|
for many other 32-bit MCUs), and some (perhaps not all)
|
||||||
|
SSD1306's can work with this -- so it's optionally be specified
|
||||||
|
here and is not a default behavior. (Ignored if using pre-1.5.7
|
||||||
|
Arduino software, which operates I2C at a fixed 100 KHz.)
|
||||||
|
@param clkAfter
|
||||||
|
Speed (in Hz) for Wire transmissions following SSD1306 library
|
||||||
|
calls. Defaults to 100000 (100 KHz), the default Arduino Wire
|
||||||
|
speed. This is done rather than leaving it at the 'during' speed
|
||||||
|
because other devices on the I2C bus might not be compatible
|
||||||
|
with the faster rate. (Ignored if using pre-1.5.7 Arduino
|
||||||
|
software, which operates I2C at a fixed 100 KHz.)
|
||||||
|
@return Adafruit_SSD1306 object.
|
||||||
|
@note Call the object's begin() function before use -- buffer
|
||||||
|
allocation is performed there!
|
||||||
|
*/
|
||||||
|
Adafruit_SSD1306::Adafruit_SSD1306(uint8_t w, uint8_t h, TwoWire *twi,
|
||||||
|
int8_t rst_pin, uint32_t clkDuring,
|
||||||
|
uint32_t clkAfter)
|
||||||
|
: Adafruit_GFX(w, h), spi(NULL), wire(twi ? twi : &Wire), buffer(NULL),
|
||||||
|
mosiPin(-1), clkPin(-1), dcPin(-1), csPin(-1), rstPin(rst_pin)
|
||||||
|
#if ARDUINO >= 157
|
||||||
|
,
|
||||||
|
wireClk(clkDuring), restoreClk(clkAfter)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Constructor for SPI SSD1306 displays, using software (bitbang)
|
||||||
|
SPI.
|
||||||
|
@param w
|
||||||
|
Display width in pixels
|
||||||
|
@param h
|
||||||
|
Display height in pixels
|
||||||
|
@param mosi_pin
|
||||||
|
MOSI (master out, slave in) pin (using Arduino pin numbering).
|
||||||
|
This transfers serial data from microcontroller to display.
|
||||||
|
@param sclk_pin
|
||||||
|
SCLK (serial clock) pin (using Arduino pin numbering).
|
||||||
|
This clocks each bit from MOSI.
|
||||||
|
@param dc_pin
|
||||||
|
Data/command pin (using Arduino pin numbering), selects whether
|
||||||
|
display is receiving commands (low) or data (high).
|
||||||
|
@param rst_pin
|
||||||
|
Reset pin (using Arduino pin numbering), or -1 if not used
|
||||||
|
(some displays might be wired to share the microcontroller's
|
||||||
|
reset pin).
|
||||||
|
@param cs_pin
|
||||||
|
Chip-select pin (using Arduino pin numbering) for sharing the
|
||||||
|
bus with other devices. Active low.
|
||||||
|
@return Adafruit_SSD1306 object.
|
||||||
|
@note Call the object's begin() function before use -- buffer
|
||||||
|
allocation is performed there!
|
||||||
|
*/
|
||||||
|
Adafruit_SSD1306::Adafruit_SSD1306(uint8_t w, uint8_t h, int8_t mosi_pin,
|
||||||
|
int8_t sclk_pin, int8_t dc_pin,
|
||||||
|
int8_t rst_pin, int8_t cs_pin)
|
||||||
|
: Adafruit_GFX(w, h), spi(NULL), wire(NULL), buffer(NULL),
|
||||||
|
mosiPin(mosi_pin), clkPin(sclk_pin), dcPin(dc_pin), csPin(cs_pin),
|
||||||
|
rstPin(rst_pin) {}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Constructor for SPI SSD1306 displays, using native hardware SPI.
|
||||||
|
@param w
|
||||||
|
Display width in pixels
|
||||||
|
@param h
|
||||||
|
Display height in pixels
|
||||||
|
@param spi_ptr
|
||||||
|
Pointer to an existing SPIClass instance (e.g. &SPI, the
|
||||||
|
microcontroller's primary SPI bus).
|
||||||
|
@param dc_pin
|
||||||
|
Data/command pin (using Arduino pin numbering), selects whether
|
||||||
|
display is receiving commands (low) or data (high).
|
||||||
|
@param rst_pin
|
||||||
|
Reset pin (using Arduino pin numbering), or -1 if not used
|
||||||
|
(some displays might be wired to share the microcontroller's
|
||||||
|
reset pin).
|
||||||
|
@param cs_pin
|
||||||
|
Chip-select pin (using Arduino pin numbering) for sharing the
|
||||||
|
bus with other devices. Active low.
|
||||||
|
@param bitrate
|
||||||
|
SPI clock rate for transfers to this display. Default if
|
||||||
|
unspecified is 8000000UL (8 MHz).
|
||||||
|
@return Adafruit_SSD1306 object.
|
||||||
|
@note Call the object's begin() function before use -- buffer
|
||||||
|
allocation is performed there!
|
||||||
|
*/
|
||||||
|
Adafruit_SSD1306::Adafruit_SSD1306(uint8_t w, uint8_t h, SPIClass *spi_ptr,
|
||||||
|
int8_t dc_pin, int8_t rst_pin, int8_t cs_pin,
|
||||||
|
uint32_t bitrate)
|
||||||
|
: Adafruit_GFX(w, h), spi(spi_ptr ? spi_ptr : &SPI), wire(NULL),
|
||||||
|
buffer(NULL), mosiPin(-1), clkPin(-1), dcPin(dc_pin), csPin(cs_pin),
|
||||||
|
rstPin(rst_pin) {
|
||||||
|
#ifdef SPI_HAS_TRANSACTION
|
||||||
|
spiSettings = SPISettings(bitrate, MSBFIRST, SPI_MODE0);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Issue single command to SSD1306, using I2C or hard/soft SPI as
|
||||||
|
needed. Because command calls are often grouped, SPI transaction and
|
||||||
|
selection must be started/ended in calling function for efficiency. This is a
|
||||||
|
protected function, not exposed (see ssd1306_command() instead).
|
||||||
|
|
||||||
|
@param c
|
||||||
|
the command character to send to the display.
|
||||||
|
Refer to ssd1306 data sheet for commands
|
||||||
|
@return None (void).
|
||||||
|
@note
|
||||||
|
*/
|
||||||
|
void Adafruit_SSD1306::ssd1306_command1(uint8_t c) {
|
||||||
|
{ // I2C
|
||||||
|
wire->beginTransmission(i2caddr);
|
||||||
|
WIRE_WRITE((uint8_t)0x00); // Co = 0, D/C = 0
|
||||||
|
WIRE_WRITE(c);
|
||||||
|
wire->endTransmission();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Issue list of commands to SSD1306, same rules as above re:
|
||||||
|
transactions. This is a protected function, not exposed.
|
||||||
|
@param c
|
||||||
|
pointer to list of commands
|
||||||
|
|
||||||
|
@param n
|
||||||
|
number of commands in the list
|
||||||
|
|
||||||
|
@return None (void).
|
||||||
|
@note
|
||||||
|
*/
|
||||||
|
void Adafruit_SSD1306::ssd1306_commandList(const uint8_t *c, uint8_t n) {
|
||||||
|
{ // I2C
|
||||||
|
wire->beginTransmission(i2caddr);
|
||||||
|
WIRE_WRITE((uint8_t)0x00); // Co = 0, D/C = 0
|
||||||
|
uint16_t bytesOut = 1;
|
||||||
|
while (n--) {
|
||||||
|
if (bytesOut >= WIRE_MAX) {
|
||||||
|
wire->endTransmission();
|
||||||
|
wire->beginTransmission(i2caddr);
|
||||||
|
WIRE_WRITE((uint8_t)0x00); // Co = 0, D/C = 0
|
||||||
|
bytesOut = 1;
|
||||||
|
}
|
||||||
|
WIRE_WRITE(pgm_read_byte(c++));
|
||||||
|
bytesOut++;
|
||||||
|
}
|
||||||
|
wire->endTransmission();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A public version of ssd1306_command1(), for existing user code that
|
||||||
|
// might rely on that function. This encapsulates the command transfer
|
||||||
|
// in a transaction start/end, similar to old library's handling of it.
|
||||||
|
/*!
|
||||||
|
@brief Issue a single low-level command directly to the SSD1306
|
||||||
|
display, bypassing the library.
|
||||||
|
@param c
|
||||||
|
Command to issue (0x00 to 0xFF, see datasheet).
|
||||||
|
@return None (void).
|
||||||
|
*/
|
||||||
|
void Adafruit_SSD1306::ssd1306_command(uint8_t c) {
|
||||||
|
TRANSACTION_START
|
||||||
|
ssd1306_command1(c);
|
||||||
|
TRANSACTION_END
|
||||||
|
}
|
||||||
|
|
||||||
|
// ALLOCATE & INIT DISPLAY -------------------------------------------------
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Allocate RAM for image buffer, initialize peripherals and pins.
|
||||||
|
@param vcs
|
||||||
|
VCC selection. Pass SSD1306_SWITCHCAPVCC to generate the display
|
||||||
|
voltage (step up) from the 3.3V source, or SSD1306_EXTERNALVCC
|
||||||
|
otherwise. Most situations with Adafruit SSD1306 breakouts will
|
||||||
|
want SSD1306_SWITCHCAPVCC.
|
||||||
|
@param addr
|
||||||
|
I2C address of corresponding SSD1306 display (or pass 0 to use
|
||||||
|
default of 0x3C for 128x32 display, 0x3D for all others).
|
||||||
|
SPI displays (hardware or software) do not use addresses, but
|
||||||
|
this argument is still required (pass 0 or any value really,
|
||||||
|
it will simply be ignored). Default if unspecified is 0.
|
||||||
|
@param reset
|
||||||
|
If true, and if the reset pin passed to the constructor is
|
||||||
|
valid, a hard reset will be performed before initializing the
|
||||||
|
display. If using multiple SSD1306 displays on the same bus, and
|
||||||
|
if they all share the same reset pin, you should only pass true
|
||||||
|
on the first display being initialized, false on all others,
|
||||||
|
else the already-initialized displays would be reset. Default if
|
||||||
|
unspecified is true.
|
||||||
|
@param periphBegin
|
||||||
|
If true, and if a hardware peripheral is being used (I2C or SPI,
|
||||||
|
but not software SPI), call that peripheral's begin() function,
|
||||||
|
else (false) it has already been done in one's sketch code.
|
||||||
|
Cases where false might be used include multiple displays or
|
||||||
|
other devices sharing a common bus, or situations on some
|
||||||
|
platforms where a nonstandard begin() function is available
|
||||||
|
(e.g. a TwoWire interface on non-default pins, as can be done
|
||||||
|
on the ESP8266 and perhaps others).
|
||||||
|
@return true on successful allocation/init, false otherwise.
|
||||||
|
Well-behaved code should check the return value before
|
||||||
|
proceeding.
|
||||||
|
@note MUST call this function before any drawing or updates!
|
||||||
|
*/
|
||||||
|
bool Adafruit_SSD1306::begin(uint8_t vcs, uint8_t addr, bool reset,
|
||||||
|
bool periphBegin) {
|
||||||
|
|
||||||
|
if ((!buffer) && !(buffer = (uint8_t *)malloc(WIDTH * ((HEIGHT + 7) / 8))))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
clearDisplay();
|
||||||
|
|
||||||
|
vccstate = vcs;
|
||||||
|
|
||||||
|
// Setup pin directions
|
||||||
|
{ // Using I2C
|
||||||
|
// If I2C address is unspecified, use default
|
||||||
|
// (0x3C for 32-pixel-tall displays, 0x3D for all others).
|
||||||
|
i2caddr = addr ? addr : ((HEIGHT == 32) ? 0x3C : 0x3D);
|
||||||
|
// TwoWire begin() function might be already performed by the calling
|
||||||
|
// function if it has unusual circumstances (e.g. TWI variants that
|
||||||
|
// can accept different SDA/SCL pins, or if two SSD1306 instances
|
||||||
|
// with different addresses -- only a single begin() is needed).
|
||||||
|
if (periphBegin)
|
||||||
|
wire->begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset SSD1306 if requested and reset pin specified in constructor
|
||||||
|
if (reset && (rstPin >= 0)) {
|
||||||
|
pinMode(rstPin, OUTPUT);
|
||||||
|
digitalWrite(rstPin, HIGH);
|
||||||
|
delay(1); // VDD goes high at start, pause for 1 ms
|
||||||
|
digitalWrite(rstPin, LOW); // Bring reset low
|
||||||
|
delay(10); // Wait 10 ms
|
||||||
|
digitalWrite(rstPin, HIGH); // Bring out of reset
|
||||||
|
}
|
||||||
|
|
||||||
|
TRANSACTION_START
|
||||||
|
|
||||||
|
// Init sequence
|
||||||
|
static const uint8_t init1[] = {SSD1306_DISPLAYOFF, // 0xAE
|
||||||
|
SSD1306_SETDISPLAYCLOCKDIV, // 0xD5
|
||||||
|
0x80, // the suggested ratio 0x80
|
||||||
|
SSD1306_SETMULTIPLEX}; // 0xA8
|
||||||
|
ssd1306_commandList(init1, sizeof(init1));
|
||||||
|
ssd1306_command1(HEIGHT - 1);
|
||||||
|
|
||||||
|
static const uint8_t init2[] = {SSD1306_SETDISPLAYOFFSET, // 0xD3
|
||||||
|
0x0, // no offset
|
||||||
|
SSD1306_SETSTARTLINE | 0x0, // line #0
|
||||||
|
SSD1306_CHARGEPUMP}; // 0x8D
|
||||||
|
ssd1306_commandList(init2, sizeof(init2));
|
||||||
|
|
||||||
|
ssd1306_command1((vccstate == SSD1306_EXTERNALVCC) ? 0x10 : 0x14);
|
||||||
|
|
||||||
|
static const uint8_t init3[] = {SSD1306_MEMORYMODE, // 0x20
|
||||||
|
0x00, // 0x0 act like ks0108
|
||||||
|
SSD1306_SEGREMAP | 0x1,
|
||||||
|
SSD1306_COMSCANDEC};
|
||||||
|
ssd1306_commandList(init3, sizeof(init3));
|
||||||
|
|
||||||
|
uint8_t comPins = 0x02;
|
||||||
|
contrast = 0x8F;
|
||||||
|
|
||||||
|
if ((WIDTH == 128) && (HEIGHT == 32)) {
|
||||||
|
comPins = 0x02;
|
||||||
|
contrast = 0x8F;
|
||||||
|
} else if ((WIDTH == 128) && (HEIGHT == 64)) {
|
||||||
|
comPins = 0x12;
|
||||||
|
contrast = (vccstate == SSD1306_EXTERNALVCC) ? 0x9F : 0xCF;
|
||||||
|
} else if ((WIDTH == 96) && (HEIGHT == 16)) {
|
||||||
|
comPins = 0x2; // ada x12
|
||||||
|
contrast = (vccstate == SSD1306_EXTERNALVCC) ? 0x10 : 0xAF;
|
||||||
|
} else {
|
||||||
|
// Other screen varieties -- TBD
|
||||||
|
}
|
||||||
|
|
||||||
|
ssd1306_command1(SSD1306_SETCOMPINS);
|
||||||
|
ssd1306_command1(comPins);
|
||||||
|
ssd1306_command1(SSD1306_SETCONTRAST);
|
||||||
|
ssd1306_command1(contrast);
|
||||||
|
|
||||||
|
ssd1306_command1(SSD1306_SETPRECHARGE); // 0xd9
|
||||||
|
ssd1306_command1((vccstate == SSD1306_EXTERNALVCC) ? 0x22 : 0xF1);
|
||||||
|
static const uint8_t init5[] = {
|
||||||
|
SSD1306_SETVCOMDETECT, // 0xDB
|
||||||
|
0x40,
|
||||||
|
SSD1306_DISPLAYALLON_RESUME, // 0xA4
|
||||||
|
SSD1306_NORMALDISPLAY, // 0xA6
|
||||||
|
SSD1306_DEACTIVATE_SCROLL,
|
||||||
|
SSD1306_DISPLAYON}; // Main screen turn on
|
||||||
|
ssd1306_commandList(init5, sizeof(init5));
|
||||||
|
|
||||||
|
TRANSACTION_END
|
||||||
|
|
||||||
|
return true; // Success
|
||||||
|
}
|
||||||
|
|
||||||
|
// DRAWING FUNCTIONS -------------------------------------------------------
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Set/clear/invert a single pixel. This is also invoked by the
|
||||||
|
Adafruit_GFX library in generating many higher-level graphics
|
||||||
|
primitives.
|
||||||
|
@param x
|
||||||
|
Column of display -- 0 at left to (screen width - 1) at right.
|
||||||
|
@param y
|
||||||
|
Row of display -- 0 at top to (screen height -1) at bottom.
|
||||||
|
@param color
|
||||||
|
Pixel color, one of: SSD1306_BLACK, SSD1306_WHITE or
|
||||||
|
SSD1306_INVERSE.
|
||||||
|
@return None (void).
|
||||||
|
@note Changes buffer contents only, no immediate effect on display.
|
||||||
|
Follow up with a call to display(), or with other graphics
|
||||||
|
commands as needed by one's own application.
|
||||||
|
*/
|
||||||
|
void Adafruit_SSD1306::drawPixel(int16_t x, int16_t y, uint16_t color) {
|
||||||
|
if ((x >= 0) && (x < width()) && (y >= 0) && (y < height())) {
|
||||||
|
// Pixel is in-bounds. Rotate coordinates if needed.
|
||||||
|
switch (getRotation()) {
|
||||||
|
case 1:
|
||||||
|
ssd1306_swap(x, y);
|
||||||
|
x = WIDTH - x - 1;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
x = WIDTH - x - 1;
|
||||||
|
y = HEIGHT - y - 1;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
ssd1306_swap(x, y);
|
||||||
|
y = HEIGHT - y - 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
switch (color) {
|
||||||
|
case SSD1306_WHITE:
|
||||||
|
buffer[x + (y / 8) * WIDTH] |= (1 << (y & 7));
|
||||||
|
break;
|
||||||
|
case SSD1306_BLACK:
|
||||||
|
buffer[x + (y / 8) * WIDTH] &= ~(1 << (y & 7));
|
||||||
|
break;
|
||||||
|
case SSD1306_INVERSE:
|
||||||
|
buffer[x + (y / 8) * WIDTH] ^= (1 << (y & 7));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Clear contents of display buffer (set all pixels to off).
|
||||||
|
@return None (void).
|
||||||
|
@note Changes buffer contents only, no immediate effect on display.
|
||||||
|
Follow up with a call to display(), or with other graphics
|
||||||
|
commands as needed by one's own application.
|
||||||
|
*/
|
||||||
|
void Adafruit_SSD1306::clearDisplay(void) {
|
||||||
|
memset(buffer, 0, WIDTH * ((HEIGHT + 7) / 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Draw a horizontal line. This is also invoked by the Adafruit_GFX
|
||||||
|
library in generating many higher-level graphics primitives.
|
||||||
|
@param x
|
||||||
|
Leftmost column -- 0 at left to (screen width - 1) at right.
|
||||||
|
@param y
|
||||||
|
Row of display -- 0 at top to (screen height -1) at bottom.
|
||||||
|
@param w
|
||||||
|
Width of line, in pixels.
|
||||||
|
@param color
|
||||||
|
Line color, one of: SSD1306_BLACK, SSD1306_WHITE or SSD1306_INVERSE.
|
||||||
|
@return None (void).
|
||||||
|
@note Changes buffer contents only, no immediate effect on display.
|
||||||
|
Follow up with a call to display(), or with other graphics
|
||||||
|
commands as needed by one's own application.
|
||||||
|
*/
|
||||||
|
void Adafruit_SSD1306::drawFastHLine(int16_t x, int16_t y, int16_t w,
|
||||||
|
uint16_t color) {
|
||||||
|
bool bSwap = false;
|
||||||
|
switch (rotation) {
|
||||||
|
case 1:
|
||||||
|
// 90 degree rotation, swap x & y for rotation, then invert x
|
||||||
|
bSwap = true;
|
||||||
|
ssd1306_swap(x, y);
|
||||||
|
x = WIDTH - x - 1;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
// 180 degree rotation, invert x and y, then shift y around for height.
|
||||||
|
x = WIDTH - x - 1;
|
||||||
|
y = HEIGHT - y - 1;
|
||||||
|
x -= (w - 1);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
// 270 degree rotation, swap x & y for rotation,
|
||||||
|
// then invert y and adjust y for w (not to become h)
|
||||||
|
bSwap = true;
|
||||||
|
ssd1306_swap(x, y);
|
||||||
|
y = HEIGHT - y - 1;
|
||||||
|
y -= (w - 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bSwap)
|
||||||
|
drawFastVLineInternal(x, y, w, color);
|
||||||
|
else
|
||||||
|
drawFastHLineInternal(x, y, w, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Draw a horizontal line with a width and color. Used by public
|
||||||
|
methods drawFastHLine,drawFastVLine
|
||||||
|
@param x
|
||||||
|
Leftmost column -- 0 at left to (screen width - 1) at right.
|
||||||
|
@param y
|
||||||
|
Row of display -- 0 at top to (screen height -1) at bottom.
|
||||||
|
@param w
|
||||||
|
Width of line, in pixels.
|
||||||
|
@param color
|
||||||
|
Line color, one of: SSD1306_BLACK, SSD1306_WHITE or
|
||||||
|
SSD1306_INVERSE.
|
||||||
|
@return None (void).
|
||||||
|
@note Changes buffer contents only, no immediate effect on display.
|
||||||
|
Follow up with a call to display(), or with other graphics
|
||||||
|
commands as needed by one's own application.
|
||||||
|
*/
|
||||||
|
void Adafruit_SSD1306::drawFastHLineInternal(int16_t x, int16_t y, int16_t w,
|
||||||
|
uint16_t color) {
|
||||||
|
|
||||||
|
if ((y >= 0) && (y < HEIGHT)) { // Y coord in bounds?
|
||||||
|
if (x < 0) { // Clip left
|
||||||
|
w += x;
|
||||||
|
x = 0;
|
||||||
|
}
|
||||||
|
if ((x + w) > WIDTH) { // Clip right
|
||||||
|
w = (WIDTH - x);
|
||||||
|
}
|
||||||
|
if (w > 0) { // Proceed only if width is positive
|
||||||
|
uint8_t *pBuf = &buffer[(y / 8) * WIDTH + x], mask = 1 << (y & 7);
|
||||||
|
switch (color) {
|
||||||
|
case SSD1306_WHITE:
|
||||||
|
while (w--) {
|
||||||
|
*pBuf++ |= mask;
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case SSD1306_BLACK:
|
||||||
|
mask = ~mask;
|
||||||
|
while (w--) {
|
||||||
|
*pBuf++ &= mask;
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case SSD1306_INVERSE:
|
||||||
|
while (w--) {
|
||||||
|
*pBuf++ ^= mask;
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Draw a vertical line. This is also invoked by the Adafruit_GFX
|
||||||
|
library in generating many higher-level graphics primitives.
|
||||||
|
@param x
|
||||||
|
Column of display -- 0 at left to (screen width -1) at right.
|
||||||
|
@param y
|
||||||
|
Topmost row -- 0 at top to (screen height - 1) at bottom.
|
||||||
|
@param h
|
||||||
|
Height of line, in pixels.
|
||||||
|
@param color
|
||||||
|
Line color, one of: SSD1306_BLACK, SSD1306_WHITE or SSD1306_INVERSE.
|
||||||
|
@return None (void).
|
||||||
|
@note Changes buffer contents only, no immediate effect on display.
|
||||||
|
Follow up with a call to display(), or with other graphics
|
||||||
|
commands as needed by one's own application.
|
||||||
|
*/
|
||||||
|
void Adafruit_SSD1306::drawFastVLine(int16_t x, int16_t y, int16_t h,
|
||||||
|
uint16_t color) {
|
||||||
|
bool bSwap = false;
|
||||||
|
switch (rotation) {
|
||||||
|
case 1:
|
||||||
|
// 90 degree rotation, swap x & y for rotation,
|
||||||
|
// then invert x and adjust x for h (now to become w)
|
||||||
|
bSwap = true;
|
||||||
|
ssd1306_swap(x, y);
|
||||||
|
x = WIDTH - x - 1;
|
||||||
|
x -= (h - 1);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
// 180 degree rotation, invert x and y, then shift y around for height.
|
||||||
|
x = WIDTH - x - 1;
|
||||||
|
y = HEIGHT - y - 1;
|
||||||
|
y -= (h - 1);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
// 270 degree rotation, swap x & y for rotation, then invert y
|
||||||
|
bSwap = true;
|
||||||
|
ssd1306_swap(x, y);
|
||||||
|
y = HEIGHT - y - 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bSwap)
|
||||||
|
drawFastHLineInternal(x, y, h, color);
|
||||||
|
else
|
||||||
|
drawFastVLineInternal(x, y, h, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Draw a vertical line with a width and color. Used by public method
|
||||||
|
drawFastHLine,drawFastVLine
|
||||||
|
@param x
|
||||||
|
Leftmost column -- 0 at left to (screen width - 1) at right.
|
||||||
|
@param __y
|
||||||
|
Row of display -- 0 at top to (screen height -1) at bottom.
|
||||||
|
@param __h height of the line in pixels
|
||||||
|
@param color
|
||||||
|
Line color, one of: SSD1306_BLACK, SSD1306_WHITE or
|
||||||
|
SSD1306_INVERSE.
|
||||||
|
@return None (void).
|
||||||
|
@note Changes buffer contents only, no immediate effect on display.
|
||||||
|
Follow up with a call to display(), or with other graphics
|
||||||
|
commands as needed by one's own application.
|
||||||
|
*/
|
||||||
|
void Adafruit_SSD1306::drawFastVLineInternal(int16_t x, int16_t __y,
|
||||||
|
int16_t __h, uint16_t color) {
|
||||||
|
|
||||||
|
if ((x >= 0) && (x < WIDTH)) { // X coord in bounds?
|
||||||
|
if (__y < 0) { // Clip top
|
||||||
|
__h += __y;
|
||||||
|
__y = 0;
|
||||||
|
}
|
||||||
|
if ((__y + __h) > HEIGHT) { // Clip bottom
|
||||||
|
__h = (HEIGHT - __y);
|
||||||
|
}
|
||||||
|
if (__h > 0) { // Proceed only if height is now positive
|
||||||
|
// this display doesn't need ints for coordinates,
|
||||||
|
// use local byte registers for faster juggling
|
||||||
|
uint8_t y = __y, h = __h;
|
||||||
|
uint8_t *pBuf = &buffer[(y / 8) * WIDTH + x];
|
||||||
|
|
||||||
|
// do the first partial byte, if necessary - this requires some masking
|
||||||
|
uint8_t mod = (y & 7);
|
||||||
|
if (mod) {
|
||||||
|
// mask off the high n bits we want to set
|
||||||
|
mod = 8 - mod;
|
||||||
|
// note - lookup table results in a nearly 10% performance
|
||||||
|
// improvement in fill* functions
|
||||||
|
// uint8_t mask = ~(0xFF >> mod);
|
||||||
|
static const uint8_t PROGMEM premask[8] = {0x00, 0x80, 0xC0, 0xE0,
|
||||||
|
0xF0, 0xF8, 0xFC, 0xFE};
|
||||||
|
uint8_t mask = pgm_read_byte(&premask[mod]);
|
||||||
|
// adjust the mask if we're not going to reach the end of this byte
|
||||||
|
if (h < mod)
|
||||||
|
mask &= (0XFF >> (mod - h));
|
||||||
|
|
||||||
|
switch (color) {
|
||||||
|
case SSD1306_WHITE:
|
||||||
|
*pBuf |= mask;
|
||||||
|
break;
|
||||||
|
case SSD1306_BLACK:
|
||||||
|
*pBuf &= ~mask;
|
||||||
|
break;
|
||||||
|
case SSD1306_INVERSE:
|
||||||
|
*pBuf ^= mask;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pBuf += WIDTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (h >= mod) { // More to go?
|
||||||
|
h -= mod;
|
||||||
|
// Write solid bytes while we can - effectively 8 rows at a time
|
||||||
|
if (h >= 8) {
|
||||||
|
if (color == SSD1306_INVERSE) {
|
||||||
|
// separate copy of the code so we don't impact performance of
|
||||||
|
// black/white write version with an extra comparison per loop
|
||||||
|
do {
|
||||||
|
*pBuf ^= 0xFF; // Invert byte
|
||||||
|
pBuf += WIDTH; // Advance pointer 8 rows
|
||||||
|
h -= 8; // Subtract 8 rows from height
|
||||||
|
} while (h >= 8);
|
||||||
|
} else {
|
||||||
|
// store a local value to work with
|
||||||
|
uint8_t val = (color != SSD1306_BLACK) ? 255 : 0;
|
||||||
|
do {
|
||||||
|
*pBuf = val; // Set byte
|
||||||
|
pBuf += WIDTH; // Advance pointer 8 rows
|
||||||
|
h -= 8; // Subtract 8 rows from height
|
||||||
|
} while (h >= 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (h) { // Do the final partial byte, if necessary
|
||||||
|
mod = h & 7;
|
||||||
|
// this time we want to mask the low bits of the byte,
|
||||||
|
// vs the high bits we did above
|
||||||
|
// uint8_t mask = (1 << mod) - 1;
|
||||||
|
// note - lookup table results in a nearly 10% performance
|
||||||
|
// improvement in fill* functions
|
||||||
|
static const uint8_t PROGMEM postmask[8] = {0x00, 0x01, 0x03, 0x07,
|
||||||
|
0x0F, 0x1F, 0x3F, 0x7F};
|
||||||
|
uint8_t mask = pgm_read_byte(&postmask[mod]);
|
||||||
|
switch (color) {
|
||||||
|
case SSD1306_WHITE:
|
||||||
|
*pBuf |= mask;
|
||||||
|
break;
|
||||||
|
case SSD1306_BLACK:
|
||||||
|
*pBuf &= ~mask;
|
||||||
|
break;
|
||||||
|
case SSD1306_INVERSE:
|
||||||
|
*pBuf ^= mask;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // endif positive height
|
||||||
|
} // endif x in bounds
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Return color of a single pixel in display buffer.
|
||||||
|
@param x
|
||||||
|
Column of display -- 0 at left to (screen width - 1) at right.
|
||||||
|
@param y
|
||||||
|
Row of display -- 0 at top to (screen height -1) at bottom.
|
||||||
|
@return true if pixel is set (usually SSD1306_WHITE, unless display invert
|
||||||
|
mode is enabled), false if clear (SSD1306_BLACK).
|
||||||
|
@note Reads from buffer contents; may not reflect current contents of
|
||||||
|
screen if display() has not been called.
|
||||||
|
*/
|
||||||
|
bool Adafruit_SSD1306::getPixel(int16_t x, int16_t y) {
|
||||||
|
if ((x >= 0) && (x < width()) && (y >= 0) && (y < height())) {
|
||||||
|
// Pixel is in-bounds. Rotate coordinates if needed.
|
||||||
|
switch (getRotation()) {
|
||||||
|
case 1:
|
||||||
|
ssd1306_swap(x, y);
|
||||||
|
x = WIDTH - x - 1;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
x = WIDTH - x - 1;
|
||||||
|
y = HEIGHT - y - 1;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
ssd1306_swap(x, y);
|
||||||
|
y = HEIGHT - y - 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return (buffer[x + (y / 8) * WIDTH] & (1 << (y & 7)));
|
||||||
|
}
|
||||||
|
return false; // Pixel out of bounds
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Get base address of display buffer for direct reading or writing.
|
||||||
|
@return Pointer to an unsigned 8-bit array, column-major, columns padded
|
||||||
|
to full byte boundary if needed.
|
||||||
|
*/
|
||||||
|
uint8_t *Adafruit_SSD1306::getBuffer(void) { return buffer; }
|
||||||
|
|
||||||
|
// REFRESH DISPLAY ---------------------------------------------------------
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Push data currently in RAM to SSD1306 display.
|
||||||
|
@return None (void).
|
||||||
|
@note Drawing operations are not visible until this function is
|
||||||
|
called. Call after each graphics command, or after a whole set
|
||||||
|
of graphics commands, as best needed by one's own application.
|
||||||
|
*/
|
||||||
|
void Adafruit_SSD1306::display(void) {
|
||||||
|
TRANSACTION_START
|
||||||
|
static const uint8_t dlist1[] = {
|
||||||
|
SSD1306_PAGEADDR,
|
||||||
|
0, // Page start address
|
||||||
|
0xFF, // Page end (not really, but works here)
|
||||||
|
SSD1306_COLUMNADDR, 0}; // Column start address
|
||||||
|
ssd1306_commandList(dlist1, sizeof(dlist1));
|
||||||
|
ssd1306_command1(WIDTH - 1); // Column end address
|
||||||
|
|
||||||
|
#if defined(ESP8266)
|
||||||
|
// ESP8266 needs a periodic yield() call to avoid watchdog reset.
|
||||||
|
// With the limited size of SSD1306 displays, and the fast bitrate
|
||||||
|
// being used (1 MHz or more), I think one yield() immediately before
|
||||||
|
// a screen write and one immediately after should cover it. But if
|
||||||
|
// not, if this becomes a problem, yields() might be added in the
|
||||||
|
// 32-byte transfer condition below.
|
||||||
|
yield();
|
||||||
|
#endif
|
||||||
|
uint16_t count = WIDTH * ((HEIGHT + 7) / 8);
|
||||||
|
uint8_t *ptr = buffer;
|
||||||
|
if (wire) { // I2C
|
||||||
|
wire->beginTransmission(i2caddr);
|
||||||
|
WIRE_WRITE((uint8_t)0x40);
|
||||||
|
uint16_t bytesOut = 1;
|
||||||
|
while (count--) {
|
||||||
|
if (bytesOut >= WIRE_MAX) {
|
||||||
|
wire->endTransmission();
|
||||||
|
wire->beginTransmission(i2caddr);
|
||||||
|
WIRE_WRITE((uint8_t)0x40);
|
||||||
|
bytesOut = 1;
|
||||||
|
}
|
||||||
|
WIRE_WRITE(*ptr++);
|
||||||
|
bytesOut++;
|
||||||
|
}
|
||||||
|
wire->endTransmission();
|
||||||
|
} else { // SPI
|
||||||
|
SSD1306_MODE_DATA
|
||||||
|
while (count--)
|
||||||
|
SPIwrite(*ptr++);
|
||||||
|
}
|
||||||
|
TRANSACTION_END
|
||||||
|
#if defined(ESP8266)
|
||||||
|
yield();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// SCROLLING FUNCTIONS -----------------------------------------------------
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Activate a right-handed scroll for all or part of the display.
|
||||||
|
@param start
|
||||||
|
First row.
|
||||||
|
@param stop
|
||||||
|
Last row.
|
||||||
|
@return None (void).
|
||||||
|
*/
|
||||||
|
// To scroll the whole display, run: display.startscrollright(0x00, 0x0F)
|
||||||
|
void Adafruit_SSD1306::startscrollright(uint8_t start, uint8_t stop) {
|
||||||
|
TRANSACTION_START
|
||||||
|
static const uint8_t PROGMEM scrollList1a[] = {
|
||||||
|
SSD1306_RIGHT_HORIZONTAL_SCROLL, 0X00};
|
||||||
|
ssd1306_commandList(scrollList1a, sizeof(scrollList1a));
|
||||||
|
ssd1306_command1(start);
|
||||||
|
ssd1306_command1(0X00);
|
||||||
|
ssd1306_command1(stop);
|
||||||
|
static const uint8_t PROGMEM scrollList1b[] = {0X00, 0XFF,
|
||||||
|
SSD1306_ACTIVATE_SCROLL};
|
||||||
|
ssd1306_commandList(scrollList1b, sizeof(scrollList1b));
|
||||||
|
TRANSACTION_END
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Activate a left-handed scroll for all or part of the display.
|
||||||
|
@param start
|
||||||
|
First row.
|
||||||
|
@param stop
|
||||||
|
Last row.
|
||||||
|
@return None (void).
|
||||||
|
*/
|
||||||
|
// To scroll the whole display, run: display.startscrollleft(0x00, 0x0F)
|
||||||
|
void Adafruit_SSD1306::startscrollleft(uint8_t start, uint8_t stop) {
|
||||||
|
TRANSACTION_START
|
||||||
|
static const uint8_t PROGMEM scrollList2a[] = {SSD1306_LEFT_HORIZONTAL_SCROLL,
|
||||||
|
0X00};
|
||||||
|
ssd1306_commandList(scrollList2a, sizeof(scrollList2a));
|
||||||
|
ssd1306_command1(start);
|
||||||
|
ssd1306_command1(0X00);
|
||||||
|
ssd1306_command1(stop);
|
||||||
|
static const uint8_t PROGMEM scrollList2b[] = {0X00, 0XFF,
|
||||||
|
SSD1306_ACTIVATE_SCROLL};
|
||||||
|
ssd1306_commandList(scrollList2b, sizeof(scrollList2b));
|
||||||
|
TRANSACTION_END
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Activate a diagonal scroll for all or part of the display.
|
||||||
|
@param start
|
||||||
|
First row.
|
||||||
|
@param stop
|
||||||
|
Last row.
|
||||||
|
@return None (void).
|
||||||
|
*/
|
||||||
|
// display.startscrolldiagright(0x00, 0x0F)
|
||||||
|
void Adafruit_SSD1306::startscrolldiagright(uint8_t start, uint8_t stop) {
|
||||||
|
TRANSACTION_START
|
||||||
|
static const uint8_t PROGMEM scrollList3a[] = {
|
||||||
|
SSD1306_SET_VERTICAL_SCROLL_AREA, 0X00};
|
||||||
|
ssd1306_commandList(scrollList3a, sizeof(scrollList3a));
|
||||||
|
ssd1306_command1(HEIGHT);
|
||||||
|
static const uint8_t PROGMEM scrollList3b[] = {
|
||||||
|
SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL, 0X00};
|
||||||
|
ssd1306_commandList(scrollList3b, sizeof(scrollList3b));
|
||||||
|
ssd1306_command1(start);
|
||||||
|
ssd1306_command1(0X00);
|
||||||
|
ssd1306_command1(stop);
|
||||||
|
static const uint8_t PROGMEM scrollList3c[] = {0X01, SSD1306_ACTIVATE_SCROLL};
|
||||||
|
ssd1306_commandList(scrollList3c, sizeof(scrollList3c));
|
||||||
|
TRANSACTION_END
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Activate alternate diagonal scroll for all or part of the display.
|
||||||
|
@param start
|
||||||
|
First row.
|
||||||
|
@param stop
|
||||||
|
Last row.
|
||||||
|
@return None (void).
|
||||||
|
*/
|
||||||
|
// To scroll the whole display, run: display.startscrolldiagleft(0x00, 0x0F)
|
||||||
|
void Adafruit_SSD1306::startscrolldiagleft(uint8_t start, uint8_t stop) {
|
||||||
|
TRANSACTION_START
|
||||||
|
static const uint8_t PROGMEM scrollList4a[] = {
|
||||||
|
SSD1306_SET_VERTICAL_SCROLL_AREA, 0X00};
|
||||||
|
ssd1306_commandList(scrollList4a, sizeof(scrollList4a));
|
||||||
|
ssd1306_command1(HEIGHT);
|
||||||
|
static const uint8_t PROGMEM scrollList4b[] = {
|
||||||
|
SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL, 0X00};
|
||||||
|
ssd1306_commandList(scrollList4b, sizeof(scrollList4b));
|
||||||
|
ssd1306_command1(start);
|
||||||
|
ssd1306_command1(0X00);
|
||||||
|
ssd1306_command1(stop);
|
||||||
|
static const uint8_t PROGMEM scrollList4c[] = {0X01, SSD1306_ACTIVATE_SCROLL};
|
||||||
|
ssd1306_commandList(scrollList4c, sizeof(scrollList4c));
|
||||||
|
TRANSACTION_END
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Cease a previously-begun scrolling action.
|
||||||
|
@return None (void).
|
||||||
|
*/
|
||||||
|
void Adafruit_SSD1306::stopscroll(void) {
|
||||||
|
TRANSACTION_START
|
||||||
|
ssd1306_command1(SSD1306_DEACTIVATE_SCROLL);
|
||||||
|
TRANSACTION_END
|
||||||
|
}
|
||||||
|
|
||||||
|
// OTHER HARDWARE SETTINGS -------------------------------------------------
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Enable or disable display invert mode (white-on-black vs
|
||||||
|
black-on-white).
|
||||||
|
@param i
|
||||||
|
If true, switch to invert mode (black-on-white), else normal
|
||||||
|
mode (white-on-black).
|
||||||
|
@return None (void).
|
||||||
|
@note This has an immediate effect on the display, no need to call the
|
||||||
|
display() function -- buffer contents are not changed, rather a
|
||||||
|
different pixel mode of the display hardware is used. When
|
||||||
|
enabled, drawing SSD1306_BLACK (value 0) pixels will actually draw
|
||||||
|
white, SSD1306_WHITE (value 1) will draw black.
|
||||||
|
*/
|
||||||
|
void Adafruit_SSD1306::invertDisplay(bool i) {
|
||||||
|
TRANSACTION_START
|
||||||
|
ssd1306_command1(i ? SSD1306_INVERTDISPLAY : SSD1306_NORMALDISPLAY);
|
||||||
|
TRANSACTION_END
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Dim the display.
|
||||||
|
@param dim
|
||||||
|
true to enable lower brightness mode, false for full brightness.
|
||||||
|
@return None (void).
|
||||||
|
@note This has an immediate effect on the display, no need to call the
|
||||||
|
display() function -- buffer contents are not changed.
|
||||||
|
*/
|
||||||
|
void Adafruit_SSD1306::dim(bool dim) {
|
||||||
|
// the range of contrast to too small to be really useful
|
||||||
|
// it is useful to dim the display
|
||||||
|
TRANSACTION_START
|
||||||
|
ssd1306_command1(SSD1306_SETCONTRAST);
|
||||||
|
ssd1306_command1(dim ? 0 : contrast);
|
||||||
|
TRANSACTION_END
|
||||||
|
}
|
||||||
167
Libraries/Adafruit_SSD1306.h
Normal file
167
Libraries/Adafruit_SSD1306.h
Normal file
|
|
@ -0,0 +1,167 @@
|
||||||
|
/*!
|
||||||
|
* @file Adafruit_SSD1306.h
|
||||||
|
*
|
||||||
|
* This is part of for Adafruit's SSD1306 library for monochrome
|
||||||
|
* OLED displays: http://www.adafruit.com/category/63_98
|
||||||
|
*
|
||||||
|
* These displays use I2C or SPI to communicate. I2C requires 2 pins
|
||||||
|
* (SCL+SDA) and optionally a RESET pin. SPI requires 4 pins (MOSI, SCK,
|
||||||
|
* select, data/command) and optionally a reset pin. Hardware SPI or
|
||||||
|
* 'bitbang' software SPI are both supported.
|
||||||
|
*
|
||||||
|
* Adafruit invests time and resources providing this open source code,
|
||||||
|
* please support Adafruit and open-source hardware by purchasing
|
||||||
|
* products from Adafruit!
|
||||||
|
*
|
||||||
|
* Written by Limor Fried/Ladyada for Adafruit Industries, with
|
||||||
|
* contributions from the open source community.
|
||||||
|
*
|
||||||
|
* BSD license, all text above, and the splash screen header file,
|
||||||
|
* must be included in any redistribution.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _Adafruit_SSD1306_H_
|
||||||
|
#define _Adafruit_SSD1306_H_
|
||||||
|
|
||||||
|
// ONE of the following three lines must be #defined:
|
||||||
|
//#define SSD1306_128_64 ///< DEPRECTAED: old way to specify 128x64 screen
|
||||||
|
#define SSD1306_128_32 ///< DEPRECATED: old way to specify 128x32 screen
|
||||||
|
//#define SSD1306_96_16 ///< DEPRECATED: old way to specify 96x16 screen
|
||||||
|
// This establishes the screen dimensions in old Adafruit_SSD1306 sketches
|
||||||
|
// (NEW CODE SHOULD IGNORE THIS, USE THE CONSTRUCTORS THAT ACCEPT WIDTH
|
||||||
|
// AND HEIGHT ARGUMENTS).
|
||||||
|
|
||||||
|
// Uncomment to disable Adafruit splash logo
|
||||||
|
//#define SSD1306_NO_SPLASH
|
||||||
|
#include <Adafruit_GFX.h>
|
||||||
|
#include <Wire.h>
|
||||||
|
|
||||||
|
/// The following "raw" color names are kept for backwards client compatability
|
||||||
|
/// They can be disabled by predefining this macro before including the Adafruit
|
||||||
|
/// header client code will then need to be modified to use the scoped enum
|
||||||
|
/// values directly
|
||||||
|
#ifndef NO_ADAFRUIT_SSD1306_COLOR_COMPATIBILITY
|
||||||
|
#define BLACK SSD1306_BLACK ///< Draw 'off' pixels
|
||||||
|
#define WHITE SSD1306_WHITE ///< Draw 'on' pixels
|
||||||
|
#define INVERSE SSD1306_INVERSE ///< Invert pixels
|
||||||
|
#endif
|
||||||
|
/// fit into the SSD1306_ naming scheme
|
||||||
|
#define SSD1306_BLACK 0 ///< Draw 'off' pixels
|
||||||
|
#define SSD1306_WHITE 1 ///< Draw 'on' pixels
|
||||||
|
#define SSD1306_INVERSE 2 ///< Invert pixels
|
||||||
|
|
||||||
|
#define SSD1306_MEMORYMODE 0x20 ///< See datasheet
|
||||||
|
#define SSD1306_COLUMNADDR 0x21 ///< See datasheet
|
||||||
|
#define SSD1306_PAGEADDR 0x22 ///< See datasheet
|
||||||
|
#define SSD1306_SETCONTRAST 0x81 ///< See datasheet
|
||||||
|
#define SSD1306_CHARGEPUMP 0x8D ///< See datasheet
|
||||||
|
#define SSD1306_SEGREMAP 0xA0 ///< See datasheet
|
||||||
|
#define SSD1306_DISPLAYALLON_RESUME 0xA4 ///< See datasheet
|
||||||
|
#define SSD1306_DISPLAYALLON 0xA5 ///< Not currently used
|
||||||
|
#define SSD1306_NORMALDISPLAY 0xA6 ///< See datasheet
|
||||||
|
#define SSD1306_INVERTDISPLAY 0xA7 ///< See datasheet
|
||||||
|
#define SSD1306_SETMULTIPLEX 0xA8 ///< See datasheet
|
||||||
|
#define SSD1306_DISPLAYOFF 0xAE ///< See datasheet
|
||||||
|
#define SSD1306_DISPLAYON 0xAF ///< See datasheet
|
||||||
|
#define SSD1306_COMSCANINC 0xC0 ///< Not currently used
|
||||||
|
#define SSD1306_COMSCANDEC 0xC8 ///< See datasheet
|
||||||
|
#define SSD1306_SETDISPLAYOFFSET 0xD3 ///< See datasheet
|
||||||
|
#define SSD1306_SETDISPLAYCLOCKDIV 0xD5 ///< See datasheet
|
||||||
|
#define SSD1306_SETPRECHARGE 0xD9 ///< See datasheet
|
||||||
|
#define SSD1306_SETCOMPINS 0xDA ///< See datasheet
|
||||||
|
#define SSD1306_SETVCOMDETECT 0xDB ///< See datasheet
|
||||||
|
|
||||||
|
#define SSD1306_SETLOWCOLUMN 0x00 ///< Not currently used
|
||||||
|
#define SSD1306_SETHIGHCOLUMN 0x10 ///< Not currently used
|
||||||
|
#define SSD1306_SETSTARTLINE 0x40 ///< See datasheet
|
||||||
|
|
||||||
|
#define SSD1306_EXTERNALVCC 0x01 ///< External display voltage source
|
||||||
|
#define SSD1306_SWITCHCAPVCC 0x02 ///< Gen. display voltage from 3.3V
|
||||||
|
|
||||||
|
#define SSD1306_RIGHT_HORIZONTAL_SCROLL 0x26 ///< Init rt scroll
|
||||||
|
#define SSD1306_LEFT_HORIZONTAL_SCROLL 0x27 ///< Init left scroll
|
||||||
|
#define SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL 0x29 ///< Init diag scroll
|
||||||
|
#define SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL 0x2A ///< Init diag scroll
|
||||||
|
#define SSD1306_DEACTIVATE_SCROLL 0x2E ///< Stop scroll
|
||||||
|
#define SSD1306_ACTIVATE_SCROLL 0x2F ///< Start scroll
|
||||||
|
#define SSD1306_SET_VERTICAL_SCROLL_AREA 0xA3 ///< Set scroll range
|
||||||
|
|
||||||
|
// Deprecated size stuff for backwards compatibility with old sketches
|
||||||
|
#if defined SSD1306_128_64
|
||||||
|
#define SSD1306_LCDWIDTH 128 ///< DEPRECATED: width w/SSD1306_128_64 defined
|
||||||
|
#define SSD1306_LCDHEIGHT 64 ///< DEPRECATED: height w/SSD1306_128_64 defined
|
||||||
|
#endif
|
||||||
|
#if defined SSD1306_128_32
|
||||||
|
#define SSD1306_LCDWIDTH 128 ///< DEPRECATED: width w/SSD1306_128_32 defined
|
||||||
|
#define SSD1306_LCDHEIGHT 32 ///< DEPRECATED: height w/SSD1306_128_32 defined
|
||||||
|
#endif
|
||||||
|
#if defined SSD1306_96_16
|
||||||
|
#define SSD1306_LCDWIDTH 96 ///< DEPRECATED: width w/SSD1306_96_16 defined
|
||||||
|
#define SSD1306_LCDHEIGHT 16 ///< DEPRECATED: height w/SSD1306_96_16 defined
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Class that stores state and functions for interacting with
|
||||||
|
SSD1306 OLED displays.
|
||||||
|
*/
|
||||||
|
class Adafruit_SSD1306 : public Adafruit_GFX {
|
||||||
|
public:
|
||||||
|
// NEW CONSTRUCTORS -- recommended for new projects
|
||||||
|
Adafruit_SSD1306(uint8_t w, uint8_t h, TwoWire *twi = &Wire,
|
||||||
|
int8_t rst_pin = -1, uint32_t clkDuring = 400000UL,
|
||||||
|
uint32_t clkAfter = 100000UL);
|
||||||
|
Adafruit_SSD1306(uint8_t w, uint8_t h, int8_t mosi_pin, int8_t sclk_pin,
|
||||||
|
int8_t dc_pin, int8_t rst_pin, int8_t cs_pin);
|
||||||
|
Adafruit_SSD1306(uint8_t w, uint8_t h, SPIClass *spi, int8_t dc_pin,
|
||||||
|
int8_t rst_pin, int8_t cs_pin, uint32_t bitrate = 8000000UL);
|
||||||
|
|
||||||
|
~Adafruit_SSD1306(void);
|
||||||
|
|
||||||
|
bool begin(uint8_t switchvcc = SSD1306_SWITCHCAPVCC, uint8_t i2caddr = 0,
|
||||||
|
bool reset = true, bool periphBegin = true);
|
||||||
|
void display(void);
|
||||||
|
void clearDisplay(void);
|
||||||
|
void invertDisplay(bool i);
|
||||||
|
void dim(bool dim);
|
||||||
|
void drawPixel(int16_t x, int16_t y, uint16_t color);
|
||||||
|
virtual void drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color);
|
||||||
|
virtual void drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color);
|
||||||
|
void startscrollright(uint8_t start, uint8_t stop);
|
||||||
|
void startscrollleft(uint8_t start, uint8_t stop);
|
||||||
|
void startscrolldiagright(uint8_t start, uint8_t stop);
|
||||||
|
void startscrolldiagleft(uint8_t start, uint8_t stop);
|
||||||
|
void stopscroll(void);
|
||||||
|
void ssd1306_command(uint8_t c);
|
||||||
|
bool getPixel(int16_t x, int16_t y);
|
||||||
|
uint8_t *getBuffer(void);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void drawFastHLineInternal(int16_t x, int16_t y, int16_t w, uint16_t color);
|
||||||
|
void drawFastVLineInternal(int16_t x, int16_t y, int16_t h, uint16_t color);
|
||||||
|
void ssd1306_command1(uint8_t c);
|
||||||
|
void ssd1306_commandList(const uint8_t *c, uint8_t n);
|
||||||
|
|
||||||
|
SPIClass *spi; ///< Initialized during construction when using SPI. See
|
||||||
|
///< SPI.cpp, SPI.h
|
||||||
|
TwoWire *wire; ///< Initialized during construction when using I2C. See
|
||||||
|
///< Wire.cpp, Wire.h
|
||||||
|
uint8_t *buffer; ///< Buffer data used for display buffer. Allocated when
|
||||||
|
///< begin method is called.
|
||||||
|
int8_t i2caddr; ///< I2C address initialized when begin method is called.
|
||||||
|
int8_t vccstate; ///< VCC selection, set by begin method.
|
||||||
|
int8_t page_end; ///< not used
|
||||||
|
int8_t mosiPin; ///< (Master Out Slave In) set when using SPI set during
|
||||||
|
///< construction.
|
||||||
|
int8_t clkPin; ///< (Clock Pin) set when using SPI set during construction.
|
||||||
|
int8_t dcPin; ///< (Data Pin) set when using SPI set during construction.
|
||||||
|
int8_t
|
||||||
|
csPin; ///< (Chip Select Pin) set when using SPI set during construction.
|
||||||
|
int8_t rstPin; ///< Display reset pin assignment. Set during construction.
|
||||||
|
|
||||||
|
uint32_t wireClk; ///< Wire speed for SSD1306 transfers
|
||||||
|
uint32_t restoreClk; ///< Wire speed following SSD1306 transfers
|
||||||
|
uint8_t contrast; ///< normal contrast setting for this device
|
||||||
|
};
|
||||||
|
|
||||||
|
endif // _Adafruit_SSD1306_H_
|
||||||
680
Libraries/NetworkClient.cpp
Normal file
680
Libraries/NetworkClient.cpp
Normal file
|
|
@ -0,0 +1,680 @@
|
||||||
|
/*
|
||||||
|
Client.h - Client class for Raspberry Pi
|
||||||
|
Copyright (c) 2016 Hristo Gochkov All right reserved.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "NetworkClient.h"
|
||||||
|
#include "NetworkManager.h"
|
||||||
|
#include <lwip/sockets.h>
|
||||||
|
#include <lwip/netdb.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#define IN6_IS_ADDR_V4MAPPED(a) ((((__const uint32_t *)(a))[0] == 0) && (((__const uint32_t *)(a))[1] == 0) && (((__const uint32_t *)(a))[2] == htonl(0xffff)))
|
||||||
|
|
||||||
|
#define WIFI_CLIENT_DEF_CONN_TIMEOUT_MS (3000)
|
||||||
|
#define WIFI_CLIENT_MAX_WRITE_RETRY (10)
|
||||||
|
#define WIFI_CLIENT_SELECT_TIMEOUT_US (1000000)
|
||||||
|
#define WIFI_CLIENT_FLUSH_BUFFER_SIZE (1024)
|
||||||
|
|
||||||
|
#undef connect
|
||||||
|
#undef write
|
||||||
|
#undef read
|
||||||
|
|
||||||
|
class NetworkClientRxBuffer {
|
||||||
|
private:
|
||||||
|
size_t _size;
|
||||||
|
uint8_t *_buffer;
|
||||||
|
size_t _pos;
|
||||||
|
size_t _fill;
|
||||||
|
int _fd;
|
||||||
|
bool _failed;
|
||||||
|
|
||||||
|
size_t r_available() {
|
||||||
|
if (_fd < 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int count;
|
||||||
|
#ifdef ESP_IDF_VERSION_MAJOR
|
||||||
|
int res = lwip_ioctl(_fd, FIONREAD, &count);
|
||||||
|
#else
|
||||||
|
int res = lwip_ioctl_r(_fd, FIONREAD, &count);
|
||||||
|
#endif
|
||||||
|
if (res < 0) {
|
||||||
|
_failed = true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t fillBuffer() {
|
||||||
|
if (!_buffer) {
|
||||||
|
_buffer = (uint8_t *)malloc(_size);
|
||||||
|
if (!_buffer) {
|
||||||
|
log_e("Not enough memory to allocate buffer");
|
||||||
|
_failed = true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_fill && _pos == _fill) {
|
||||||
|
_fill = 0;
|
||||||
|
_pos = 0;
|
||||||
|
}
|
||||||
|
if (!_buffer || _size <= _fill || !r_available()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int res = recv(_fd, _buffer + _fill, _size - _fill, MSG_DONTWAIT);
|
||||||
|
if (res < 0) {
|
||||||
|
if (errno != EWOULDBLOCK) {
|
||||||
|
_failed = true;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
_fill += res;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
NetworkClientRxBuffer(int fd, size_t size = 1436) : _size(size), _buffer(NULL), _pos(0), _fill(0), _fd(fd), _failed(false) {
|
||||||
|
//_buffer = (uint8_t *)malloc(_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
~NetworkClientRxBuffer() {
|
||||||
|
free(_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool failed() {
|
||||||
|
return _failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
int read(uint8_t *dst, size_t len) {
|
||||||
|
if (!dst || !len || (_pos == _fill && !fillBuffer())) {
|
||||||
|
return _failed ? -1 : 0;
|
||||||
|
}
|
||||||
|
size_t a = _fill - _pos;
|
||||||
|
if (len <= a || ((len - a) <= (_size - _fill) && fillBuffer() >= (len - a))) {
|
||||||
|
if (len == 1) {
|
||||||
|
*dst = _buffer[_pos];
|
||||||
|
} else {
|
||||||
|
memcpy(dst, _buffer + _pos, len);
|
||||||
|
}
|
||||||
|
_pos += len;
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
size_t left = len;
|
||||||
|
size_t toRead = a;
|
||||||
|
uint8_t *buf = dst;
|
||||||
|
memcpy(buf, _buffer + _pos, toRead);
|
||||||
|
_pos += toRead;
|
||||||
|
left -= toRead;
|
||||||
|
buf += toRead;
|
||||||
|
while (left) {
|
||||||
|
if (!fillBuffer()) {
|
||||||
|
return len - left;
|
||||||
|
}
|
||||||
|
a = _fill - _pos;
|
||||||
|
toRead = (a > left) ? left : a;
|
||||||
|
memcpy(buf, _buffer + _pos, toRead);
|
||||||
|
_pos += toRead;
|
||||||
|
left -= toRead;
|
||||||
|
buf += toRead;
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
int peek() {
|
||||||
|
if (_pos == _fill && !fillBuffer()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return _buffer[_pos];
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t available() {
|
||||||
|
return _fill - _pos + r_available();
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
if (r_available()) {
|
||||||
|
_pos = _fill;
|
||||||
|
while (fillBuffer()) {
|
||||||
|
_pos = _fill;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_pos = 0;
|
||||||
|
_fill = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class NetworkClientSocketHandle {
|
||||||
|
private:
|
||||||
|
int sockfd;
|
||||||
|
|
||||||
|
public:
|
||||||
|
NetworkClientSocketHandle(int fd) : sockfd(fd) {}
|
||||||
|
|
||||||
|
~NetworkClientSocketHandle() {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void close() {
|
||||||
|
if (sockfd >= 0) {
|
||||||
|
::close(sockfd);
|
||||||
|
sockfd = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int fd() {
|
||||||
|
return sockfd;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
NetworkClient::NetworkClient() : _rxBuffer(nullptr), _connected(false), _sse(false), _timeout(WIFI_CLIENT_DEF_CONN_TIMEOUT_MS), next(NULL) {}
|
||||||
|
|
||||||
|
NetworkClient::NetworkClient(int fd) : _connected(true), _timeout(WIFI_CLIENT_DEF_CONN_TIMEOUT_MS), next(NULL) {
|
||||||
|
clientSocketHandle.reset(new NetworkClientSocketHandle(fd));
|
||||||
|
_rxBuffer.reset(new NetworkClientRxBuffer(fd));
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkClient::~NetworkClient() {}
|
||||||
|
|
||||||
|
void NetworkClient::stop() {
|
||||||
|
if (clientSocketHandle) {
|
||||||
|
clientSocketHandle->close();
|
||||||
|
}
|
||||||
|
clientSocketHandle = NULL;
|
||||||
|
_rxBuffer = NULL;
|
||||||
|
_connected = false;
|
||||||
|
_lastReadTimeout = 0;
|
||||||
|
_lastWriteTimeout = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int NetworkClient::connect(IPAddress ip, uint16_t port) {
|
||||||
|
return connect(ip, port, _timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
int NetworkClient::connect(IPAddress ip, uint16_t port, int32_t timeout_ms) {
|
||||||
|
struct sockaddr_storage serveraddr = {};
|
||||||
|
_timeout = timeout_ms;
|
||||||
|
int sockfd = -1;
|
||||||
|
|
||||||
|
if (ip.type() == IPv6) {
|
||||||
|
struct sockaddr_in6 *tmpaddr = (struct sockaddr_in6 *)&serveraddr;
|
||||||
|
sockfd = socket(AF_INET6, SOCK_STREAM, 0);
|
||||||
|
tmpaddr->sin6_family = AF_INET6;
|
||||||
|
memcpy(tmpaddr->sin6_addr.un.u8_addr, &ip[0], 16);
|
||||||
|
tmpaddr->sin6_port = htons(port);
|
||||||
|
tmpaddr->sin6_scope_id = ip.zone();
|
||||||
|
} else {
|
||||||
|
struct sockaddr_in *tmpaddr = (struct sockaddr_in *)&serveraddr;
|
||||||
|
sockfd = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
tmpaddr->sin_family = AF_INET;
|
||||||
|
tmpaddr->sin_addr.s_addr = ip;
|
||||||
|
tmpaddr->sin_port = htons(port);
|
||||||
|
}
|
||||||
|
if (sockfd < 0) {
|
||||||
|
log_e("socket: %d", errno);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFL, 0) | O_NONBLOCK);
|
||||||
|
|
||||||
|
fd_set fdset;
|
||||||
|
struct timeval tv;
|
||||||
|
FD_ZERO(&fdset);
|
||||||
|
FD_SET(sockfd, &fdset);
|
||||||
|
tv.tv_sec = _timeout / 1000;
|
||||||
|
tv.tv_usec = (_timeout % 1000) * 1000;
|
||||||
|
|
||||||
|
#ifdef ESP_IDF_VERSION_MAJOR
|
||||||
|
int res = lwip_connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
|
||||||
|
#else
|
||||||
|
int res = lwip_connect_r(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
|
||||||
|
#endif
|
||||||
|
if (res < 0 && errno != EINPROGRESS) {
|
||||||
|
log_e("connect on fd %d, errno: %d, \"%s\"", sockfd, errno, strerror(errno));
|
||||||
|
close(sockfd);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
res = select(sockfd + 1, nullptr, &fdset, nullptr, _timeout < 0 ? nullptr : &tv);
|
||||||
|
if (res < 0) {
|
||||||
|
log_e("select on fd %d, errno: %d, \"%s\"", sockfd, errno, strerror(errno));
|
||||||
|
close(sockfd);
|
||||||
|
return 0;
|
||||||
|
} else if (res == 0) {
|
||||||
|
log_i("select returned due to timeout %d ms for fd %d", _timeout, sockfd);
|
||||||
|
close(sockfd);
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
int sockerr;
|
||||||
|
socklen_t len = (socklen_t)sizeof(int);
|
||||||
|
res = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &sockerr, &len);
|
||||||
|
|
||||||
|
if (res < 0) {
|
||||||
|
log_e("getsockopt on fd %d, errno: %d, \"%s\"", sockfd, errno, strerror(errno));
|
||||||
|
close(sockfd);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sockerr != 0) {
|
||||||
|
log_e("socket error on fd %d, errno: %d, \"%s\"", sockfd, sockerr, strerror(sockerr));
|
||||||
|
close(sockfd);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define ROE_WIFICLIENT(x, msg) \
|
||||||
|
{ \
|
||||||
|
if (((x) < 0)) { \
|
||||||
|
log_e("Setsockopt '" msg "'' on fd %d failed. errno: %d, \"%s\"", sockfd, errno, strerror(errno)); \
|
||||||
|
return 0; \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
ROE_WIFICLIENT(setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)), "SO_SNDTIMEO");
|
||||||
|
ROE_WIFICLIENT(setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)), "SO_RCVTIMEO");
|
||||||
|
|
||||||
|
// These are also set in NetworkClientSecure, should be set here too?
|
||||||
|
//ROE_WIFICLIENT(setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(enable)),"TCP_NODELAY");
|
||||||
|
//ROE_WIFICLIENT (setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &enable, sizeof(enable)),"SO_KEEPALIVE");
|
||||||
|
|
||||||
|
fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFL, 0) & (~O_NONBLOCK));
|
||||||
|
clientSocketHandle.reset(new NetworkClientSocketHandle(sockfd));
|
||||||
|
_rxBuffer.reset(new NetworkClientRxBuffer(sockfd));
|
||||||
|
|
||||||
|
_connected = true;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int NetworkClient::connect(const char *host, uint16_t port) {
|
||||||
|
return connect(host, port, _timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
int NetworkClient::connect(const char *host, uint16_t port, int32_t timeout_ms) {
|
||||||
|
IPAddress srv((uint32_t)0);
|
||||||
|
if (!Network.hostByName(host, srv)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return connect(srv, port, timeout_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
int NetworkClient::setSocketOption(int option, char *value, size_t len) {
|
||||||
|
return setSocketOption(SOL_SOCKET, option, (const void *)value, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
int NetworkClient::setSocketOption(int level, int option, const void *value, size_t len) {
|
||||||
|
int res = setsockopt(fd(), level, option, value, len);
|
||||||
|
if (res < 0) {
|
||||||
|
log_e("fail on %d, errno: %d, \"%s\"", fd(), errno, strerror(errno));
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
int NetworkClient::getSocketOption(int level, int option, const void *value, size_t size) {
|
||||||
|
int res = getsockopt(fd(), level, option, (char *)value, (socklen_t *)&size);
|
||||||
|
if (res < 0) {
|
||||||
|
log_e("fail on fd %d, errno: %d, \"%s\"", fd(), errno, strerror(errno));
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
int NetworkClient::setOption(int option, int *value) {
|
||||||
|
return setSocketOption(IPPROTO_TCP, option, (const void *)value, sizeof(int));
|
||||||
|
}
|
||||||
|
|
||||||
|
int NetworkClient::getOption(int option, int *value) {
|
||||||
|
socklen_t size = sizeof(int);
|
||||||
|
int res = getsockopt(fd(), IPPROTO_TCP, option, (char *)value, &size);
|
||||||
|
if (res < 0) {
|
||||||
|
log_e("fail on fd %d, errno: %d, \"%s\"", fd(), errno, strerror(errno));
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkClient::setConnectionTimeout(uint32_t milliseconds) {
|
||||||
|
_timeout = milliseconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
int NetworkClient::setNoDelay(bool nodelay) {
|
||||||
|
int flag = nodelay;
|
||||||
|
return setOption(TCP_NODELAY, &flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NetworkClient::getNoDelay() {
|
||||||
|
int flag = 0;
|
||||||
|
getOption(TCP_NODELAY, &flag);
|
||||||
|
return flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t NetworkClient::write(uint8_t data) {
|
||||||
|
return write(&data, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int NetworkClient::read() {
|
||||||
|
uint8_t data = 0;
|
||||||
|
int res = read(&data, 1);
|
||||||
|
if (res < 0) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
if (res == 0) { // No data available.
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkClient::flush() {
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t NetworkClient::write(const uint8_t *buf, size_t size) {
|
||||||
|
int res = 0;
|
||||||
|
int retry = WIFI_CLIENT_MAX_WRITE_RETRY;
|
||||||
|
int socketFileDescriptor = fd();
|
||||||
|
size_t totalBytesSent = 0;
|
||||||
|
size_t bytesRemaining = size;
|
||||||
|
|
||||||
|
if (!_connected || (socketFileDescriptor < 0)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (retry) {
|
||||||
|
//use select to make sure the socket is ready for writing
|
||||||
|
fd_set set;
|
||||||
|
struct timeval tv;
|
||||||
|
FD_ZERO(&set); // empties the set
|
||||||
|
FD_SET(socketFileDescriptor, &set); // adds FD to the set
|
||||||
|
tv.tv_sec = 0;
|
||||||
|
tv.tv_usec = WIFI_CLIENT_SELECT_TIMEOUT_US;
|
||||||
|
retry--;
|
||||||
|
|
||||||
|
if (_lastWriteTimeout != _timeout) {
|
||||||
|
if (fd() >= 0) {
|
||||||
|
struct timeval timeout_tv;
|
||||||
|
timeout_tv.tv_sec = _timeout / 1000;
|
||||||
|
timeout_tv.tv_usec = (_timeout % 1000) * 1000;
|
||||||
|
if (setSocketOption(SO_SNDTIMEO, (char *)&timeout_tv, sizeof(struct timeval)) >= 0) {
|
||||||
|
_lastWriteTimeout = _timeout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (select(socketFileDescriptor + 1, NULL, &set, NULL, &tv) < 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FD_ISSET(socketFileDescriptor, &set)) {
|
||||||
|
res = send(socketFileDescriptor, (void *)buf, bytesRemaining, MSG_DONTWAIT);
|
||||||
|
if (res > 0) {
|
||||||
|
totalBytesSent += res;
|
||||||
|
if (totalBytesSent >= size) {
|
||||||
|
//completed successfully
|
||||||
|
retry = 0;
|
||||||
|
} else {
|
||||||
|
buf += res;
|
||||||
|
bytesRemaining -= res;
|
||||||
|
retry = WIFI_CLIENT_MAX_WRITE_RETRY;
|
||||||
|
}
|
||||||
|
} else if (res < 0) {
|
||||||
|
log_e("fail on fd %d, errno: %d, \"%s\"", fd(), errno, strerror(errno));
|
||||||
|
if (errno != EAGAIN) {
|
||||||
|
//if resource was busy, can try again, otherwise give up
|
||||||
|
stop();
|
||||||
|
res = 0;
|
||||||
|
retry = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Try again
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return totalBytesSent;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t NetworkClient::write_P(PGM_P buf, size_t size) {
|
||||||
|
return write(buf, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t NetworkClient::write(Stream &stream) {
|
||||||
|
uint8_t *buf = (uint8_t *)malloc(1360);
|
||||||
|
if (!buf) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
size_t toRead = 0, toWrite = 0, written = 0;
|
||||||
|
size_t available = stream.available();
|
||||||
|
while (available) {
|
||||||
|
toRead = (available > 1360) ? 1360 : available;
|
||||||
|
toWrite = stream.readBytes(buf, toRead);
|
||||||
|
written += write(buf, toWrite);
|
||||||
|
available = stream.available();
|
||||||
|
}
|
||||||
|
free(buf);
|
||||||
|
return written;
|
||||||
|
}
|
||||||
|
|
||||||
|
int NetworkClient::read(uint8_t *buf, size_t size) {
|
||||||
|
if (_lastReadTimeout != _timeout) {
|
||||||
|
if (fd() >= 0) {
|
||||||
|
struct timeval timeout_tv;
|
||||||
|
timeout_tv.tv_sec = _timeout / 1000;
|
||||||
|
timeout_tv.tv_usec = (_timeout % 1000) * 1000;
|
||||||
|
if (setSocketOption(SO_RCVTIMEO, (char *)&timeout_tv, sizeof(struct timeval)) >= 0) {
|
||||||
|
_lastReadTimeout = _timeout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int res = -1;
|
||||||
|
if (_rxBuffer) {
|
||||||
|
res = _rxBuffer->read(buf, size);
|
||||||
|
if (_rxBuffer->failed()) {
|
||||||
|
log_e("fail on fd %d, errno: %d, \"%s\"", fd(), errno, strerror(errno));
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t NetworkClient::readBytes(char *buffer, size_t length) {
|
||||||
|
size_t left = length, sofar = 0;
|
||||||
|
int r = 0, to = millis() + getTimeout();
|
||||||
|
while (left) {
|
||||||
|
r = read((uint8_t *)buffer + sofar, left);
|
||||||
|
if (r < 0) {
|
||||||
|
// Error has occurred
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (r > 0) {
|
||||||
|
// We got some data
|
||||||
|
left -= r;
|
||||||
|
sofar += r;
|
||||||
|
to = millis() + getTimeout();
|
||||||
|
} else {
|
||||||
|
// We got no data
|
||||||
|
if (millis() >= to) {
|
||||||
|
// We have waited for data enough
|
||||||
|
log_w("Timeout waiting for data on fd %d", fd());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Allow other tasks to run
|
||||||
|
delay(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sofar;
|
||||||
|
}
|
||||||
|
|
||||||
|
int NetworkClient::peek() {
|
||||||
|
int res = -1;
|
||||||
|
if (fd() >= 0 && _rxBuffer) {
|
||||||
|
res = _rxBuffer->peek();
|
||||||
|
if (_rxBuffer->failed()) {
|
||||||
|
log_e("fail on fd %d, errno: %d, \"%s\"", fd(), errno, strerror(errno));
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
int NetworkClient::available() {
|
||||||
|
if (fd() < 0 || !_rxBuffer) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int res = _rxBuffer->available();
|
||||||
|
if (_rxBuffer->failed()) {
|
||||||
|
log_e("fail on fd %d, errno: %d, \"%s\"", fd(), errno, strerror(errno));
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkClient::clear() {
|
||||||
|
if (_rxBuffer != nullptr) {
|
||||||
|
_rxBuffer->clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t NetworkClient::connected() {
|
||||||
|
if (fd() == -1 && _connected) {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
if (_connected) {
|
||||||
|
uint8_t dummy;
|
||||||
|
int res = recv(fd(), &dummy, 0, MSG_DONTWAIT);
|
||||||
|
// avoid unused var warning by gcc
|
||||||
|
(void)res;
|
||||||
|
// recv only sets errno if res is <= 0
|
||||||
|
if (res <= 0) {
|
||||||
|
switch (errno) {
|
||||||
|
case EWOULDBLOCK:
|
||||||
|
case ENOENT: //caused by vfs
|
||||||
|
_connected = true;
|
||||||
|
break;
|
||||||
|
case ENOTCONN:
|
||||||
|
case EPIPE:
|
||||||
|
case ECONNRESET:
|
||||||
|
case ECONNREFUSED:
|
||||||
|
case ECONNABORTED:
|
||||||
|
_connected = false;
|
||||||
|
log_d("Disconnected: RES: %d, ERR: %d", res, errno);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
log_i("Unexpected: RES: %d, ERR: %d", res, errno);
|
||||||
|
_connected = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_connected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
IPAddress NetworkClient::remoteIP(int fd) const {
|
||||||
|
struct sockaddr_storage addr;
|
||||||
|
socklen_t len = sizeof addr;
|
||||||
|
getpeername(fd, (struct sockaddr *)&addr, &len);
|
||||||
|
|
||||||
|
// IPv4 socket, old way
|
||||||
|
if (((struct sockaddr *)&addr)->sa_family == AF_INET) {
|
||||||
|
struct sockaddr_in *s = (struct sockaddr_in *)&addr;
|
||||||
|
return IPAddress((uint32_t)(s->sin_addr.s_addr));
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPv6, but it might be IPv4 mapped address
|
||||||
|
if (((struct sockaddr *)&addr)->sa_family == AF_INET6) {
|
||||||
|
struct sockaddr_in6 *saddr6 = (struct sockaddr_in6 *)&addr;
|
||||||
|
if (IN6_IS_ADDR_V4MAPPED(saddr6->sin6_addr.un.u32_addr)) {
|
||||||
|
return IPAddress(IPv4, (uint8_t *)saddr6->sin6_addr.s6_addr + IPADDRESS_V4_BYTES_INDEX);
|
||||||
|
} else {
|
||||||
|
return IPAddress(IPv6, (uint8_t *)(saddr6->sin6_addr.s6_addr), saddr6->sin6_scope_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log_e("NetworkClient::remoteIP Not AF_INET or AF_INET6?");
|
||||||
|
return (IPAddress(0, 0, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t NetworkClient::remotePort(int fd) const {
|
||||||
|
struct sockaddr_storage addr;
|
||||||
|
socklen_t len = sizeof addr;
|
||||||
|
getpeername(fd, (struct sockaddr *)&addr, &len);
|
||||||
|
struct sockaddr_in *s = (struct sockaddr_in *)&addr;
|
||||||
|
return ntohs(s->sin_port);
|
||||||
|
}
|
||||||
|
|
||||||
|
IPAddress NetworkClient::remoteIP() const {
|
||||||
|
return remoteIP(fd());
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t NetworkClient::remotePort() const {
|
||||||
|
return remotePort(fd());
|
||||||
|
}
|
||||||
|
|
||||||
|
IPAddress NetworkClient::localIP(int fd) const {
|
||||||
|
struct sockaddr_storage addr;
|
||||||
|
socklen_t len = sizeof addr;
|
||||||
|
getsockname(fd, (struct sockaddr *)&addr, &len);
|
||||||
|
|
||||||
|
// IPv4 socket, old way
|
||||||
|
if (((struct sockaddr *)&addr)->sa_family == AF_INET) {
|
||||||
|
struct sockaddr_in *s = (struct sockaddr_in *)&addr;
|
||||||
|
return IPAddress((uint32_t)(s->sin_addr.s_addr));
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPv6, but it might be IPv4 mapped address
|
||||||
|
if (((struct sockaddr *)&addr)->sa_family == AF_INET6) {
|
||||||
|
struct sockaddr_in6 *saddr6 = (struct sockaddr_in6 *)&addr;
|
||||||
|
if (IN6_IS_ADDR_V4MAPPED(saddr6->sin6_addr.un.u32_addr)) {
|
||||||
|
return IPAddress(IPv4, (uint8_t *)saddr6->sin6_addr.s6_addr + IPADDRESS_V4_BYTES_INDEX);
|
||||||
|
} else {
|
||||||
|
return IPAddress(IPv6, (uint8_t *)(saddr6->sin6_addr.s6_addr), saddr6->sin6_scope_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log_e("NetworkClient::localIP Not AF_INET or AF_INET6?");
|
||||||
|
return (IPAddress(0, 0, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t NetworkClient::localPort(int fd) const {
|
||||||
|
struct sockaddr_storage addr;
|
||||||
|
socklen_t len = sizeof addr;
|
||||||
|
getsockname(fd, (struct sockaddr *)&addr, &len);
|
||||||
|
struct sockaddr_in *s = (struct sockaddr_in *)&addr;
|
||||||
|
return ntohs(s->sin_port);
|
||||||
|
}
|
||||||
|
|
||||||
|
IPAddress NetworkClient::localIP() const {
|
||||||
|
return localIP(fd());
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t NetworkClient::localPort() const {
|
||||||
|
return localPort(fd());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NetworkClient::operator==(const NetworkClient &rhs) {
|
||||||
|
return clientSocketHandle == rhs.clientSocketHandle && remotePort() == rhs.remotePort() && remoteIP() == rhs.remoteIP();
|
||||||
|
}
|
||||||
|
|
||||||
|
int NetworkClient::fd() const {
|
||||||
|
if (clientSocketHandle == NULL) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
return clientSocketHandle->fd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkClient::setSSE(bool sse) {
|
||||||
|
_sse = sse;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NetworkClient::isSSE() {
|
||||||
|
return _sse;
|
||||||
|
}
|
||||||
111
Libraries/NetworkClient.h
Normal file
111
Libraries/NetworkClient.h
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
/*
|
||||||
|
Client.h - Base class that provides Client
|
||||||
|
Copyright (c) 2011 Adrian McEwen. All right reserved.
|
||||||
|
|
||||||
|
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 "Client.h"
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
class NetworkClientSocketHandle;
|
||||||
|
class NetworkClientRxBuffer;
|
||||||
|
|
||||||
|
class ESPLwIPClient : public Client {
|
||||||
|
public:
|
||||||
|
virtual int connect(IPAddress ip, uint16_t port, int32_t timeout) = 0;
|
||||||
|
virtual int connect(const char *host, uint16_t port, int32_t timeout) = 0;
|
||||||
|
virtual void setConnectionTimeout(uint32_t milliseconds) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class NetworkClient : public ESPLwIPClient {
|
||||||
|
protected:
|
||||||
|
std::shared_ptr<NetworkClientSocketHandle> clientSocketHandle;
|
||||||
|
std::shared_ptr<NetworkClientRxBuffer> _rxBuffer;
|
||||||
|
bool _connected;
|
||||||
|
bool _sse;
|
||||||
|
int _timeout;
|
||||||
|
int _lastWriteTimeout;
|
||||||
|
int _lastReadTimeout;
|
||||||
|
|
||||||
|
public:
|
||||||
|
NetworkClient *next;
|
||||||
|
NetworkClient();
|
||||||
|
NetworkClient(int fd);
|
||||||
|
~NetworkClient();
|
||||||
|
int connect(IPAddress ip, uint16_t port);
|
||||||
|
int connect(IPAddress ip, uint16_t port, int32_t timeout_ms);
|
||||||
|
int connect(const char *host, uint16_t port);
|
||||||
|
int connect(const char *host, uint16_t port, int32_t timeout_ms);
|
||||||
|
size_t write(uint8_t data);
|
||||||
|
size_t write(const uint8_t *buf, size_t size);
|
||||||
|
size_t write_P(PGM_P buf, size_t size);
|
||||||
|
size_t write(Stream &stream);
|
||||||
|
[[deprecated("Use clear() instead.")]]
|
||||||
|
void flush(); // Print::flush tx
|
||||||
|
int available();
|
||||||
|
int read();
|
||||||
|
int read(uint8_t *buf, size_t size);
|
||||||
|
size_t readBytes(char *buffer, size_t length);
|
||||||
|
size_t readBytes(uint8_t *buffer, size_t length) {
|
||||||
|
return readBytes((char *)buffer, length);
|
||||||
|
}
|
||||||
|
int peek();
|
||||||
|
void clear(); // clear rx
|
||||||
|
void stop();
|
||||||
|
uint8_t connected();
|
||||||
|
void setSSE(bool sse);
|
||||||
|
bool isSSE();
|
||||||
|
|
||||||
|
operator bool() {
|
||||||
|
return connected();
|
||||||
|
}
|
||||||
|
bool operator==(const bool value) {
|
||||||
|
return bool() == value;
|
||||||
|
}
|
||||||
|
bool operator!=(const bool value) {
|
||||||
|
return bool() != value;
|
||||||
|
}
|
||||||
|
bool operator==(const NetworkClient &);
|
||||||
|
bool operator!=(const NetworkClient &rhs) {
|
||||||
|
return !this->operator==(rhs);
|
||||||
|
};
|
||||||
|
|
||||||
|
virtual int fd() const;
|
||||||
|
|
||||||
|
int setSocketOption(int option, char *value, size_t len);
|
||||||
|
int setSocketOption(int level, int option, const void *value, size_t len);
|
||||||
|
int getSocketOption(int level, int option, const void *value, size_t size);
|
||||||
|
int setOption(int option, int *value);
|
||||||
|
int getOption(int option, int *value);
|
||||||
|
void setConnectionTimeout(uint32_t milliseconds);
|
||||||
|
int setNoDelay(bool nodelay);
|
||||||
|
bool getNoDelay();
|
||||||
|
|
||||||
|
IPAddress remoteIP() const;
|
||||||
|
IPAddress remoteIP(int fd) const;
|
||||||
|
uint16_t remotePort() const;
|
||||||
|
uint16_t remotePort(int fd) const;
|
||||||
|
IPAddress localIP() const;
|
||||||
|
IPAddress localIP(int fd) const;
|
||||||
|
uint16_t localPort() const;
|
||||||
|
uint16_t localPort(int fd) const;
|
||||||
|
|
||||||
|
//friend class NetworkServer;
|
||||||
|
using Print::write;
|
||||||
|
};
|
||||||
828
Libraries/NimBLE-Arduino_modified/NimBLEAdvertisedDevice.cpp
Normal file
828
Libraries/NimBLE-Arduino_modified/NimBLEAdvertisedDevice.cpp
Normal file
|
|
@ -0,0 +1,828 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||||
|
* esp-nimble-cpp, NimBLE-Arduino contributors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "nimconfig.h"
|
||||||
|
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER)
|
||||||
|
|
||||||
|
# include "NimBLEDevice.h"
|
||||||
|
# include "NimBLEAdvertisedDevice.h"
|
||||||
|
# include "NimBLEUtils.h"
|
||||||
|
# include "NimBLELog.h"
|
||||||
|
|
||||||
|
# include <climits>
|
||||||
|
|
||||||
|
static const char* LOG_TAG = "NimBLEAdvertisedDevice";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Constructor
|
||||||
|
* @param [in] event The advertisement event data.
|
||||||
|
*/
|
||||||
|
NimBLEAdvertisedDevice::NimBLEAdvertisedDevice(const ble_gap_event* event, uint8_t eventType)
|
||||||
|
# if CONFIG_BT_NIMBLE_EXT_ADV
|
||||||
|
: m_address{event->ext_disc.addr},
|
||||||
|
m_advType{eventType},
|
||||||
|
m_rssi{event->ext_disc.rssi},
|
||||||
|
m_callbackSent{0},
|
||||||
|
m_advLength{event->ext_disc.length_data},
|
||||||
|
m_isLegacyAdv{!!(event->ext_disc.props & BLE_HCI_ADV_LEGACY_MASK)},
|
||||||
|
m_sid{event->ext_disc.sid},
|
||||||
|
m_primPhy{event->ext_disc.prim_phy},
|
||||||
|
m_secPhy{event->ext_disc.sec_phy},
|
||||||
|
m_periodicItvl{event->ext_disc.periodic_adv_itvl},
|
||||||
|
m_payload(event->ext_disc.data, event->ext_disc.data + event->ext_disc.length_data) {
|
||||||
|
# else
|
||||||
|
: m_address{event->disc.addr},
|
||||||
|
m_advType{eventType},
|
||||||
|
m_rssi{event->disc.rssi},
|
||||||
|
m_callbackSent{0},
|
||||||
|
m_advLength{event->disc.length_data},
|
||||||
|
m_payload(event->disc.data, event->disc.data + event->disc.length_data) {
|
||||||
|
# endif
|
||||||
|
m_payload.reserve(256);
|
||||||
|
} // NimBLEAdvertisedDevice
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Update the advertisement data.
|
||||||
|
* @param [in] event The advertisement event data.
|
||||||
|
*/
|
||||||
|
void NimBLEAdvertisedDevice::update(const ble_gap_event* event, uint8_t eventType) {
|
||||||
|
# if CONFIG_BT_NIMBLE_EXT_ADV
|
||||||
|
const auto& disc = event->ext_disc;
|
||||||
|
m_isLegacyAdv = disc.props & BLE_HCI_ADV_LEGACY_MASK;
|
||||||
|
# else
|
||||||
|
const auto& disc = event->disc;
|
||||||
|
# endif
|
||||||
|
|
||||||
|
m_rssi = disc.rssi;
|
||||||
|
if (eventType == BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP && isLegacyAdvertisement()) {
|
||||||
|
m_payload.insert(m_payload.end(), disc.data, disc.data + disc.length_data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_advLength = disc.length_data;
|
||||||
|
m_payload.assign(disc.data, disc.data + disc.length_data);
|
||||||
|
m_callbackSent = 0; // new data, reset callback sent flag
|
||||||
|
} // update
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the address of the advertising device.
|
||||||
|
* @return The address of the advertised device.
|
||||||
|
*/
|
||||||
|
const NimBLEAddress& NimBLEAdvertisedDevice::getAddress() const {
|
||||||
|
return m_address;
|
||||||
|
} // getAddress
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the advertisement type.
|
||||||
|
* @return The advertising type the device is reporting:
|
||||||
|
* * BLE_HCI_ADV_TYPE_ADV_IND (0) - indirect advertising
|
||||||
|
* * BLE_HCI_ADV_TYPE_ADV_DIRECT_IND_HD (1) - direct advertising - high duty cycle
|
||||||
|
* * BLE_HCI_ADV_TYPE_ADV_SCAN_IND (2) - indirect scan response
|
||||||
|
* * BLE_HCI_ADV_TYPE_ADV_NONCONN_IND (3) - indirect advertising - not connectable
|
||||||
|
* * BLE_HCI_ADV_TYPE_ADV_DIRECT_IND_LD (4) - direct advertising - low duty cycle
|
||||||
|
*/
|
||||||
|
uint8_t NimBLEAdvertisedDevice::getAdvType() const {
|
||||||
|
return m_advType;
|
||||||
|
} // getAdvType
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the advertisement flags.
|
||||||
|
* @return The advertisement flags, a bitmask of:
|
||||||
|
* BLE_HS_ADV_F_DISC_LTD (0x01) - limited discoverability
|
||||||
|
* BLE_HS_ADV_F_DISC_GEN (0x02) - general discoverability
|
||||||
|
* BLE_HS_ADV_F_BREDR_UNSUP - BR/EDR not supported
|
||||||
|
*/
|
||||||
|
uint8_t NimBLEAdvertisedDevice::getAdvFlags() const {
|
||||||
|
size_t data_loc;
|
||||||
|
if (findAdvField(BLE_HS_ADV_TYPE_FLAGS, 0, &data_loc) > 0) {
|
||||||
|
const ble_hs_adv_field* field = reinterpret_cast<const ble_hs_adv_field*>(&m_payload[data_loc]);
|
||||||
|
if (field->length == BLE_HS_ADV_FLAGS_LEN + 1) {
|
||||||
|
return *field->value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
} // getAdvFlags
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the appearance.
|
||||||
|
*
|
||||||
|
* A %BLE device can declare its own appearance. The appearance is how it would like to be shown to an end user
|
||||||
|
* typically in the form of an icon.
|
||||||
|
*
|
||||||
|
* @return The appearance of the advertised device.
|
||||||
|
*/
|
||||||
|
uint16_t NimBLEAdvertisedDevice::getAppearance() const {
|
||||||
|
size_t data_loc;
|
||||||
|
if (findAdvField(BLE_HS_ADV_TYPE_APPEARANCE, 0, &data_loc) > 0) {
|
||||||
|
const ble_hs_adv_field* field = reinterpret_cast<const ble_hs_adv_field*>(&m_payload[data_loc]);
|
||||||
|
if (field->length == BLE_HS_ADV_APPEARANCE_LEN + 1) {
|
||||||
|
return *field->value | *(field->value + 1) << 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
} // getAppearance
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the advertisement interval.
|
||||||
|
* @return The advertisement interval in 0.625ms units.
|
||||||
|
*/
|
||||||
|
uint16_t NimBLEAdvertisedDevice::getAdvInterval() const {
|
||||||
|
size_t data_loc;
|
||||||
|
if (findAdvField(BLE_HS_ADV_TYPE_ADV_ITVL, 0, &data_loc) > 0) {
|
||||||
|
const ble_hs_adv_field* field = reinterpret_cast<const ble_hs_adv_field*>(&m_payload[data_loc]);
|
||||||
|
if (field->length == BLE_HS_ADV_ADV_ITVL_LEN + 1) {
|
||||||
|
return *field->value | *(field->value + 1) << 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
} // getAdvInterval
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the preferred min connection interval.
|
||||||
|
* @return The preferred min connection interval in 1.25ms units.
|
||||||
|
*/
|
||||||
|
uint16_t NimBLEAdvertisedDevice::getMinInterval() const {
|
||||||
|
size_t data_loc;
|
||||||
|
if (findAdvField(BLE_HS_ADV_TYPE_SLAVE_ITVL_RANGE, 0, &data_loc) > 0) {
|
||||||
|
const ble_hs_adv_field* field = reinterpret_cast<const ble_hs_adv_field*>(&m_payload[data_loc]);
|
||||||
|
if (field->length == BLE_HS_ADV_SLAVE_ITVL_RANGE_LEN + 1) {
|
||||||
|
return *field->value | *(field->value + 1) << 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
} // getMinInterval
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the preferred max connection interval.
|
||||||
|
* @return The preferred max connection interval in 1.25ms units.
|
||||||
|
*/
|
||||||
|
uint16_t NimBLEAdvertisedDevice::getMaxInterval() const {
|
||||||
|
size_t data_loc;
|
||||||
|
if (findAdvField(BLE_HS_ADV_TYPE_SLAVE_ITVL_RANGE, 0, &data_loc) > 0) {
|
||||||
|
const ble_hs_adv_field* field = reinterpret_cast<const ble_hs_adv_field*>(&m_payload[data_loc]);
|
||||||
|
if (field->length == BLE_HS_ADV_SLAVE_ITVL_RANGE_LEN + 1) {
|
||||||
|
return *(field->value + 2) | *(field->value + 3) << 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
} // getMaxInterval
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the manufacturer data.
|
||||||
|
* @param [in] index The index of the of the manufacturer data set to get.
|
||||||
|
* @return The manufacturer data.
|
||||||
|
*/
|
||||||
|
std::string NimBLEAdvertisedDevice::getManufacturerData(uint8_t index) const {
|
||||||
|
return getPayloadByType(BLE_HS_ADV_TYPE_MFG_DATA, index);
|
||||||
|
} // getManufacturerData
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the count of manufacturer data sets.
|
||||||
|
* @return The number of manufacturer data sets.
|
||||||
|
*/
|
||||||
|
uint8_t NimBLEAdvertisedDevice::getManufacturerDataCount() const {
|
||||||
|
return findAdvField(BLE_HS_ADV_TYPE_MFG_DATA);
|
||||||
|
} // getManufacturerDataCount
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the URI from the advertisement.
|
||||||
|
* @return The URI data.
|
||||||
|
*/
|
||||||
|
std::string NimBLEAdvertisedDevice::getURI() const {
|
||||||
|
return getPayloadByType(BLE_HS_ADV_TYPE_URI);
|
||||||
|
} // getURI
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the data from any type available in the advertisement.
|
||||||
|
* @param [in] type The advertised data type BLE_HS_ADV_TYPE.
|
||||||
|
* @param [in] index The index of the data type.
|
||||||
|
* @return The data available under the type `type`.
|
||||||
|
*/
|
||||||
|
std::string NimBLEAdvertisedDevice::getPayloadByType(uint16_t type, uint8_t index) const {
|
||||||
|
size_t data_loc;
|
||||||
|
if (findAdvField(type, index, &data_loc) > 0) {
|
||||||
|
const ble_hs_adv_field* field = reinterpret_cast<const ble_hs_adv_field*>(&m_payload[data_loc]);
|
||||||
|
if (field->length > 1) {
|
||||||
|
return std::string((char*)field->value, field->length - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
} // getPayloadByType
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the advertised name.
|
||||||
|
* @return The name of the advertised device.
|
||||||
|
*/
|
||||||
|
std::string NimBLEAdvertisedDevice::getName() const {
|
||||||
|
return getPayloadByType(BLE_HS_ADV_TYPE_COMP_NAME);
|
||||||
|
} // getName
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the RSSI.
|
||||||
|
* @return The RSSI of the advertised device.
|
||||||
|
*/
|
||||||
|
int8_t NimBLEAdvertisedDevice::getRSSI() const {
|
||||||
|
return m_rssi;
|
||||||
|
} // getRSSI
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the scan object that created this advertised device.
|
||||||
|
* @return The scan object.
|
||||||
|
*/
|
||||||
|
NimBLEScan* NimBLEAdvertisedDevice::getScan() const {
|
||||||
|
return NimBLEDevice::getScan();
|
||||||
|
} // getScan
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the number of target addresses.
|
||||||
|
* @return The number of addresses.
|
||||||
|
*/
|
||||||
|
uint8_t NimBLEAdvertisedDevice::getTargetAddressCount() const {
|
||||||
|
uint8_t count = findAdvField(BLE_HS_ADV_TYPE_PUBLIC_TGT_ADDR);
|
||||||
|
count += findAdvField(BLE_HS_ADV_TYPE_RANDOM_TGT_ADDR);
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the target address at the index.
|
||||||
|
* @param [in] index The index of the target address.
|
||||||
|
* @return The target address.
|
||||||
|
*/
|
||||||
|
NimBLEAddress NimBLEAdvertisedDevice::getTargetAddress(uint8_t index) const {
|
||||||
|
size_t data_loc = ULONG_MAX;
|
||||||
|
uint8_t count = findAdvField(BLE_HS_ADV_TYPE_PUBLIC_TGT_ADDR, index, &data_loc);
|
||||||
|
if (count < index + 1) {
|
||||||
|
index -= count;
|
||||||
|
count = findAdvField(BLE_HS_ADV_TYPE_RANDOM_TGT_ADDR, index, &data_loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count > 0 && data_loc != ULONG_MAX) {
|
||||||
|
index++;
|
||||||
|
const ble_hs_adv_field* field = reinterpret_cast<const ble_hs_adv_field*>(&m_payload[data_loc]);
|
||||||
|
if (field->length < index * BLE_HS_ADV_PUBLIC_TGT_ADDR_ENTRY_LEN) {
|
||||||
|
// In the case of more than one field of target addresses we need to adjust the index
|
||||||
|
index -= count - field->length / BLE_HS_ADV_PUBLIC_TGT_ADDR_ENTRY_LEN;
|
||||||
|
}
|
||||||
|
if (field->length > index * BLE_HS_ADV_PUBLIC_TGT_ADDR_ENTRY_LEN) {
|
||||||
|
return NimBLEAddress{field->value + (index - 1) * BLE_HS_ADV_PUBLIC_TGT_ADDR_ENTRY_LEN, field->type};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NimBLEAddress{};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the service data.
|
||||||
|
* @param [in] index The index of the service data requested.
|
||||||
|
* @return The advertised service data or empty string if no data.
|
||||||
|
*/
|
||||||
|
std::string NimBLEAdvertisedDevice::getServiceData(uint8_t index) const {
|
||||||
|
uint8_t bytes;
|
||||||
|
size_t data_loc = findServiceData(index, &bytes);
|
||||||
|
if (data_loc != ULONG_MAX) {
|
||||||
|
const ble_hs_adv_field* field = reinterpret_cast<const ble_hs_adv_field*>(&m_payload[data_loc]);
|
||||||
|
if (field->length > bytes) {
|
||||||
|
const char* field_data = reinterpret_cast<const char*>(field->value + bytes);
|
||||||
|
return std::string(field_data, field->length - bytes - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
} // getServiceData
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the service data.
|
||||||
|
* @param [out] len The length of the service data requested.
|
||||||
|
* @return The advertised service data or empty string if no data.
|
||||||
|
*/
|
||||||
|
uint8_t* NimBLEAdvertisedDevice::getServiceData(uint16_t* len) const {
|
||||||
|
uint8_t bytes;
|
||||||
|
size_t data_loc = findServiceData(0, &bytes); // Assuming index 0 for the first service data
|
||||||
|
if (data_loc != ULONG_MAX) {
|
||||||
|
const ble_hs_adv_field* field = reinterpret_cast<const ble_hs_adv_field*>(&m_payload[data_loc]);
|
||||||
|
if (field->length > bytes) {
|
||||||
|
*len = field->length - bytes - 1;
|
||||||
|
return const_cast<uint8_t*>(field->value + bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*len = 0;
|
||||||
|
return nullptr;
|
||||||
|
} // getServiceData
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the service data.
|
||||||
|
* @param [in] uuid The uuid of the service data requested.
|
||||||
|
* @return The advertised service data or empty string if no data.
|
||||||
|
*/
|
||||||
|
std::string NimBLEAdvertisedDevice::getServiceData(const NimBLEUUID& uuid) const {
|
||||||
|
uint8_t bytes;
|
||||||
|
uint8_t index = 0;
|
||||||
|
size_t data_loc = findServiceData(index, &bytes);
|
||||||
|
size_t pl_size = m_payload.size() - 2;
|
||||||
|
uint8_t uuid_bytes = uuid.bitSize() / 8;
|
||||||
|
|
||||||
|
while (data_loc < pl_size) {
|
||||||
|
const ble_hs_adv_field* field = reinterpret_cast<const ble_hs_adv_field*>(&m_payload[data_loc]);
|
||||||
|
if (bytes == uuid_bytes && NimBLEUUID(field->value, bytes) == uuid) {
|
||||||
|
const char* field_data = reinterpret_cast<const char*>(field->value + bytes);
|
||||||
|
return std::string(field_data, field->length - bytes - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
index++;
|
||||||
|
data_loc = findServiceData(index, &bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
NIMBLE_LOGI(LOG_TAG, "No service data found");
|
||||||
|
return "";
|
||||||
|
} // getServiceData
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the UUID of the service data at the index.
|
||||||
|
* @param [in] index The index of the service data UUID requested.
|
||||||
|
* @return The advertised service data UUID or an empty UUID if not found.
|
||||||
|
*/
|
||||||
|
NimBLEUUID NimBLEAdvertisedDevice::getServiceDataUUID(uint8_t index) const {
|
||||||
|
uint8_t bytes;
|
||||||
|
size_t data_loc = findServiceData(index, &bytes);
|
||||||
|
if (data_loc != ULONG_MAX) {
|
||||||
|
const ble_hs_adv_field* field = reinterpret_cast<const ble_hs_adv_field*>(&m_payload[data_loc]);
|
||||||
|
if (field->length >= bytes) {
|
||||||
|
return NimBLEUUID(field->value, bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NimBLEUUID("");
|
||||||
|
} // getServiceDataUUID
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Find the service data at the index.
|
||||||
|
* @param [in] index The index of the service data to find.
|
||||||
|
* @param [in] bytes A pointer to storage for the number of the bytes in the UUID.
|
||||||
|
* @return The index in the vector where the data is located, ULONG_MAX if not found.
|
||||||
|
*/
|
||||||
|
size_t NimBLEAdvertisedDevice::findServiceData(uint8_t index, uint8_t* bytes) const {
|
||||||
|
*bytes = 0;
|
||||||
|
|
||||||
|
size_t data_loc = 0;
|
||||||
|
uint8_t found = findAdvField(BLE_HS_ADV_TYPE_SVC_DATA_UUID16, index, &data_loc);
|
||||||
|
if (found > index) {
|
||||||
|
*bytes = 2;
|
||||||
|
return data_loc;
|
||||||
|
}
|
||||||
|
|
||||||
|
index -= found;
|
||||||
|
found = findAdvField(BLE_HS_ADV_TYPE_SVC_DATA_UUID32, index, &data_loc);
|
||||||
|
if (found > index) {
|
||||||
|
*bytes = 4;
|
||||||
|
return data_loc;
|
||||||
|
}
|
||||||
|
|
||||||
|
index -= found;
|
||||||
|
found = findAdvField(BLE_HS_ADV_TYPE_SVC_DATA_UUID128, index, &data_loc);
|
||||||
|
if (found > index) {
|
||||||
|
*bytes = 16;
|
||||||
|
return data_loc;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ULONG_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the count of advertised service data UUIDS
|
||||||
|
* @return The number of service data UUIDS in the vector.
|
||||||
|
*/
|
||||||
|
uint8_t NimBLEAdvertisedDevice::getServiceDataCount() const {
|
||||||
|
uint8_t count = findAdvField(BLE_HS_ADV_TYPE_SVC_DATA_UUID16);
|
||||||
|
count += findAdvField(BLE_HS_ADV_TYPE_SVC_DATA_UUID32);
|
||||||
|
count += findAdvField(BLE_HS_ADV_TYPE_SVC_DATA_UUID128);
|
||||||
|
|
||||||
|
return count;
|
||||||
|
} // getServiceDataCount
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the Service UUID.
|
||||||
|
* @param [in] index The index of the service UUID requested.
|
||||||
|
* @return The Service UUID of the advertised service, or an empty UUID if not found.
|
||||||
|
*/
|
||||||
|
NimBLEUUID NimBLEAdvertisedDevice::getServiceUUID(uint8_t index) const {
|
||||||
|
uint8_t type = BLE_HS_ADV_TYPE_INCOMP_UUIDS16;
|
||||||
|
size_t data_loc = 0;
|
||||||
|
uint8_t uuid_bytes = 0;
|
||||||
|
uint8_t count = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
count = findAdvField(type, index, &data_loc);
|
||||||
|
if (count > index) {
|
||||||
|
if (type < BLE_HS_ADV_TYPE_INCOMP_UUIDS32) {
|
||||||
|
uuid_bytes = 2;
|
||||||
|
} else if (type < BLE_HS_ADV_TYPE_INCOMP_UUIDS128) {
|
||||||
|
uuid_bytes = 4;
|
||||||
|
} else {
|
||||||
|
uuid_bytes = 16;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
type++;
|
||||||
|
index -= count;
|
||||||
|
}
|
||||||
|
|
||||||
|
} while (type <= BLE_HS_ADV_TYPE_COMP_UUIDS128);
|
||||||
|
|
||||||
|
if (uuid_bytes > 0) {
|
||||||
|
index++;
|
||||||
|
const ble_hs_adv_field* field = reinterpret_cast<const ble_hs_adv_field*>(&m_payload[data_loc]);
|
||||||
|
// In the case of more than one field of service uuid's we need to adjust
|
||||||
|
// the index to account for the uuids of the previous fields.
|
||||||
|
if (field->length < index * uuid_bytes) {
|
||||||
|
index -= count - field->length / uuid_bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (field->length > uuid_bytes * index) {
|
||||||
|
return NimBLEUUID(field->value + uuid_bytes * (index - 1), uuid_bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NimBLEUUID("");
|
||||||
|
} // getServiceUUID
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the number of services advertised
|
||||||
|
* @return The count of services in the advertising packet.
|
||||||
|
*/
|
||||||
|
uint8_t NimBLEAdvertisedDevice::getServiceUUIDCount() const {
|
||||||
|
uint8_t count = findAdvField(BLE_HS_ADV_TYPE_INCOMP_UUIDS16);
|
||||||
|
count += findAdvField(BLE_HS_ADV_TYPE_COMP_UUIDS16);
|
||||||
|
count += findAdvField(BLE_HS_ADV_TYPE_INCOMP_UUIDS32);
|
||||||
|
count += findAdvField(BLE_HS_ADV_TYPE_COMP_UUIDS32);
|
||||||
|
count += findAdvField(BLE_HS_ADV_TYPE_INCOMP_UUIDS128);
|
||||||
|
count += findAdvField(BLE_HS_ADV_TYPE_COMP_UUIDS128);
|
||||||
|
|
||||||
|
return count;
|
||||||
|
} // getServiceUUIDCount
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check advertised services for existence of the required UUID
|
||||||
|
* @param [in] uuid The service uuid to look for in the advertisement.
|
||||||
|
* @return Return true if service is advertised
|
||||||
|
*/
|
||||||
|
bool NimBLEAdvertisedDevice::isAdvertisingService(const NimBLEUUID& uuid) const {
|
||||||
|
size_t count = getServiceUUIDCount();
|
||||||
|
for (size_t i = 0; i < count; i++) {
|
||||||
|
if (uuid == getServiceUUID(i)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} // isAdvertisingService
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the TX Power.
|
||||||
|
* @return The TX Power of the advertised device.
|
||||||
|
*/
|
||||||
|
int8_t NimBLEAdvertisedDevice::getTXPower() const {
|
||||||
|
size_t data_loc = 0;
|
||||||
|
if (findAdvField(BLE_HS_ADV_TYPE_TX_PWR_LVL, 0, &data_loc) > 0) {
|
||||||
|
const ble_hs_adv_field* field = reinterpret_cast<const ble_hs_adv_field*>(&m_payload[data_loc]);
|
||||||
|
if (field->length == BLE_HS_ADV_TX_PWR_LVL_LEN + 1) {
|
||||||
|
return *(int8_t*)field->value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -99;
|
||||||
|
} // getTXPower
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Does this advertisement have preferred connection parameters?
|
||||||
|
* @return True if connection parameters are present.
|
||||||
|
*/
|
||||||
|
bool NimBLEAdvertisedDevice::haveConnParams() const {
|
||||||
|
return findAdvField(BLE_HS_ADV_TYPE_SLAVE_ITVL_RANGE) > 0;
|
||||||
|
} // haveConnParams
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Does this advertisement have have the advertising interval?
|
||||||
|
* @return True if the advertisement interval is present.
|
||||||
|
*/
|
||||||
|
bool NimBLEAdvertisedDevice::haveAdvInterval() const {
|
||||||
|
return findAdvField(BLE_HS_ADV_TYPE_ADV_ITVL) > 0;
|
||||||
|
} // haveAdvInterval
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Does this advertisement have an appearance value?
|
||||||
|
* @return True if there is an appearance value present.
|
||||||
|
*/
|
||||||
|
bool NimBLEAdvertisedDevice::haveAppearance() const {
|
||||||
|
return findAdvField(BLE_HS_ADV_TYPE_APPEARANCE) > 0;
|
||||||
|
} // haveAppearance
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Does this advertisement have manufacturer data?
|
||||||
|
* @return True if there is manufacturer data present.
|
||||||
|
*/
|
||||||
|
bool NimBLEAdvertisedDevice::haveManufacturerData() const {
|
||||||
|
return findAdvField(BLE_HS_ADV_TYPE_MFG_DATA) > 0;
|
||||||
|
} // haveManufacturerData
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Does this advertisement have a URI?
|
||||||
|
* @return True if there is a URI present.
|
||||||
|
*/
|
||||||
|
bool NimBLEAdvertisedDevice::haveURI() const {
|
||||||
|
return findAdvField(BLE_HS_ADV_TYPE_URI) > 0;
|
||||||
|
} // haveURI
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Does this advertisement have a adv type `type`?
|
||||||
|
* @return True if there is a `type` present.
|
||||||
|
*/
|
||||||
|
bool NimBLEAdvertisedDevice::haveType(uint16_t type) const {
|
||||||
|
return findAdvField(type) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Does the advertisement contain a target address?
|
||||||
|
* @return True if an address is present.
|
||||||
|
*/
|
||||||
|
bool NimBLEAdvertisedDevice::haveTargetAddress() const {
|
||||||
|
return findAdvField(BLE_HS_ADV_TYPE_PUBLIC_TGT_ADDR) > 0 || findAdvField(BLE_HS_ADV_TYPE_RANDOM_TGT_ADDR) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Does this advertisement have a name value?
|
||||||
|
* @return True if there is a name value present.
|
||||||
|
*/
|
||||||
|
bool NimBLEAdvertisedDevice::haveName() const {
|
||||||
|
return findAdvField(BLE_HS_ADV_TYPE_COMP_NAME) > 0;
|
||||||
|
} // haveName
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Does this advertisement have a service data value?
|
||||||
|
* @return True if there is a service data value present.
|
||||||
|
*/
|
||||||
|
bool NimBLEAdvertisedDevice::haveServiceData() const {
|
||||||
|
return getServiceDataCount() > 0;
|
||||||
|
} // haveServiceData
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Does this advertisement have a service UUID value?
|
||||||
|
* @return True if there is a service UUID value present.
|
||||||
|
*/
|
||||||
|
bool NimBLEAdvertisedDevice::haveServiceUUID() const {
|
||||||
|
return getServiceUUIDCount() > 0;
|
||||||
|
} // haveServiceUUID
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Does this advertisement have a transmission power value?
|
||||||
|
* @return True if there is a transmission power value present.
|
||||||
|
*/
|
||||||
|
bool NimBLEAdvertisedDevice::haveTXPower() const {
|
||||||
|
return findAdvField(BLE_HS_ADV_TYPE_TX_PWR_LVL) > 0;
|
||||||
|
} // haveTXPower
|
||||||
|
|
||||||
|
# if CONFIG_BT_NIMBLE_EXT_ADV
|
||||||
|
/**
|
||||||
|
* @brief Get the set ID of the extended advertisement.
|
||||||
|
* @return The set ID.
|
||||||
|
*/
|
||||||
|
uint8_t NimBLEAdvertisedDevice::getSetId() const {
|
||||||
|
return m_sid;
|
||||||
|
} // getSetId
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the primary PHY used by this advertisement.
|
||||||
|
* @return The PHY type, one of:
|
||||||
|
* * BLE_HCI_LE_PHY_1M
|
||||||
|
* * BLE_HCI_LE_PHY_CODED
|
||||||
|
*/
|
||||||
|
uint8_t NimBLEAdvertisedDevice::getPrimaryPhy() const {
|
||||||
|
return m_primPhy;
|
||||||
|
} // getPrimaryPhy
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the primary PHY used by this advertisement.
|
||||||
|
* @return The PHY type, one of:
|
||||||
|
* * BLE_HCI_LE_PHY_1M
|
||||||
|
* * BLE_HCI_LE_PHY_2M
|
||||||
|
* * BLE_HCI_LE_PHY_CODED
|
||||||
|
*/
|
||||||
|
uint8_t NimBLEAdvertisedDevice::getSecondaryPhy() const {
|
||||||
|
return m_secPhy;
|
||||||
|
} // getSecondaryPhy
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the periodic interval of the advertisement.
|
||||||
|
* @return The periodic advertising interval, 0 if not periodic advertising.
|
||||||
|
*/
|
||||||
|
uint16_t NimBLEAdvertisedDevice::getPeriodicInterval() const {
|
||||||
|
return m_periodicItvl;
|
||||||
|
} // getPeriodicInterval
|
||||||
|
# endif
|
||||||
|
|
||||||
|
uint8_t NimBLEAdvertisedDevice::findAdvField(uint8_t type, uint8_t index, size_t* data_loc) const {
|
||||||
|
size_t length = m_payload.size();
|
||||||
|
size_t data = 0;
|
||||||
|
uint8_t count = 0;
|
||||||
|
|
||||||
|
while (length > 2) {
|
||||||
|
const ble_hs_adv_field* field = reinterpret_cast<const ble_hs_adv_field*>(&m_payload[data]);
|
||||||
|
if (field->length >= length) {
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (field->type == type || (type == BLE_HS_ADV_TYPE_COMP_NAME && field->type == BLE_HS_ADV_TYPE_INCOMP_NAME)) {
|
||||||
|
switch (type) {
|
||||||
|
case BLE_HS_ADV_TYPE_INCOMP_UUIDS16:
|
||||||
|
case BLE_HS_ADV_TYPE_COMP_UUIDS16:
|
||||||
|
count += field->length / 2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BLE_HS_ADV_TYPE_INCOMP_UUIDS32:
|
||||||
|
case BLE_HS_ADV_TYPE_COMP_UUIDS32:
|
||||||
|
count += field->length / 4;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BLE_HS_ADV_TYPE_INCOMP_UUIDS128:
|
||||||
|
case BLE_HS_ADV_TYPE_COMP_UUIDS128:
|
||||||
|
count += field->length / 16;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BLE_HS_ADV_TYPE_PUBLIC_TGT_ADDR:
|
||||||
|
case BLE_HS_ADV_TYPE_RANDOM_TGT_ADDR:
|
||||||
|
count += field->length / 6;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BLE_HS_ADV_TYPE_COMP_NAME:
|
||||||
|
// keep looking for complete name, else use this
|
||||||
|
if (data_loc != nullptr && field->type == BLE_HS_ADV_TYPE_INCOMP_NAME) {
|
||||||
|
*data_loc = data;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
// fall through
|
||||||
|
default:
|
||||||
|
count++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data_loc != nullptr) {
|
||||||
|
if (count > index) { // assumes index values default to 0
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
length -= 1 + field->length;
|
||||||
|
data += 1 + field->length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data_loc != nullptr && count > index) {
|
||||||
|
*data_loc = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
} // findAdvField
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create a string representation of this device.
|
||||||
|
* @return A string representation of this device.
|
||||||
|
*/
|
||||||
|
std::string NimBLEAdvertisedDevice::toString() const {
|
||||||
|
std::string res = "Name: " + getName() + ", Address: " + getAddress().toString();
|
||||||
|
|
||||||
|
if (haveAppearance()) {
|
||||||
|
char val[6];
|
||||||
|
snprintf(val, sizeof(val), "%d", getAppearance());
|
||||||
|
res += ", appearance: ";
|
||||||
|
res += val;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (haveManufacturerData()) {
|
||||||
|
auto mfgData = getManufacturerData();
|
||||||
|
res += ", manufacturer data: ";
|
||||||
|
res += NimBLEUtils::dataToHexString(reinterpret_cast<const uint8_t*>(mfgData.data()), mfgData.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (haveServiceUUID()) {
|
||||||
|
res += ", serviceUUID: " + getServiceUUID().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (haveTXPower()) {
|
||||||
|
char val[5];
|
||||||
|
snprintf(val, sizeof(val), "%d", getTXPower());
|
||||||
|
res += ", txPower: ";
|
||||||
|
res += val;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (haveServiceData()) {
|
||||||
|
uint8_t count = getServiceDataCount();
|
||||||
|
res += "\nService Data:";
|
||||||
|
for (uint8_t i = 0; i < count; i++) {
|
||||||
|
res += "\nUUID: " + std::string(getServiceDataUUID(i));
|
||||||
|
res += ", Data: " + getServiceData(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
|
||||||
|
} // toString
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the length of the advertisement data in the payload.
|
||||||
|
* @return The number of bytes in the payload that is from the advertisement.
|
||||||
|
*/
|
||||||
|
uint8_t NimBLEAdvertisedDevice::getAdvLength() const {
|
||||||
|
return m_advLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the advertised device address type.
|
||||||
|
* @return The device address type:
|
||||||
|
* * BLE_ADDR_PUBLIC (0x00)
|
||||||
|
* * BLE_ADDR_RANDOM (0x01)
|
||||||
|
* * BLE_ADDR_PUBLIC_ID (0x02)
|
||||||
|
* * BLE_ADDR_RANDOM_ID (0x03)
|
||||||
|
*/
|
||||||
|
uint8_t NimBLEAdvertisedDevice::getAddressType() const {
|
||||||
|
return m_address.getType();
|
||||||
|
} // getAddressType
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if this device is advertising as connectable.
|
||||||
|
* @return True if the device is connectable.
|
||||||
|
*/
|
||||||
|
bool NimBLEAdvertisedDevice::isConnectable() const {
|
||||||
|
# if CONFIG_BT_NIMBLE_EXT_ADV
|
||||||
|
if (m_isLegacyAdv) {
|
||||||
|
return m_advType == BLE_HCI_ADV_RPT_EVTYPE_ADV_IND || m_advType == BLE_HCI_ADV_RPT_EVTYPE_DIR_IND;
|
||||||
|
}
|
||||||
|
# endif
|
||||||
|
return (m_advType & BLE_HCI_ADV_CONN_MASK) || (m_advType & BLE_HCI_ADV_DIRECT_MASK);
|
||||||
|
} // isConnectable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if this device is advertising as scannable.
|
||||||
|
* @return True if the device is scannable.
|
||||||
|
*/
|
||||||
|
bool NimBLEAdvertisedDevice::isScannable() const {
|
||||||
|
return isLegacyAdvertisement() && (m_advType == BLE_HCI_ADV_TYPE_ADV_IND || m_advType == BLE_HCI_ADV_TYPE_ADV_SCAN_IND);
|
||||||
|
} // isScannable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if this advertisement is a legacy or extended type
|
||||||
|
* @return True if legacy (Bluetooth 4.x), false if extended (bluetooth 5.x).
|
||||||
|
*/
|
||||||
|
bool NimBLEAdvertisedDevice::isLegacyAdvertisement() const {
|
||||||
|
# if CONFIG_BT_NIMBLE_EXT_ADV
|
||||||
|
return m_isLegacyAdv;
|
||||||
|
# else
|
||||||
|
return true;
|
||||||
|
# endif
|
||||||
|
} // isLegacyAdvertisement
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the payload advertised by the device.
|
||||||
|
* @return The advertisement payload.
|
||||||
|
*/
|
||||||
|
const std::vector<uint8_t>& NimBLEAdvertisedDevice::getPayload() const {
|
||||||
|
return m_payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the begin iterator for the payload.
|
||||||
|
* @return A read only iterator pointing to the first byte in the payload.
|
||||||
|
*/
|
||||||
|
const std::vector<uint8_t>::const_iterator NimBLEAdvertisedDevice::begin() const {
|
||||||
|
return m_payload.cbegin();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the end iterator for the payload.
|
||||||
|
* @return A read only iterator pointing to one past the last byte of the payload.
|
||||||
|
*/
|
||||||
|
const std::vector<uint8_t>::const_iterator NimBLEAdvertisedDevice::end() const {
|
||||||
|
return m_payload.cend();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */
|
||||||
197
Libraries/NimBLE-Arduino_modified/NimBLEAdvertisedDevice.h
Normal file
197
Libraries/NimBLE-Arduino_modified/NimBLEAdvertisedDevice.h
Normal file
|
|
@ -0,0 +1,197 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||||
|
* esp-nimble-cpp, NimBLE-Arduino contributors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef NIMBLE_CPP_ADVERTISED_DEVICE_H_
|
||||||
|
#define NIMBLE_CPP_ADVERTISED_DEVICE_H_
|
||||||
|
|
||||||
|
#include "nimconfig.h"
|
||||||
|
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER)
|
||||||
|
|
||||||
|
# include "NimBLEAddress.h"
|
||||||
|
# include "NimBLEScan.h"
|
||||||
|
# include "NimBLEUUID.h"
|
||||||
|
|
||||||
|
# if defined(CONFIG_NIMBLE_CPP_IDF)
|
||||||
|
# include "host/ble_hs_adv.h"
|
||||||
|
# include "host/ble_gap.h"
|
||||||
|
# else
|
||||||
|
# include "nimble/nimble/host/include/host/ble_hs_adv.h"
|
||||||
|
# include "nimble/nimble/host/include/host/ble_gap.h"
|
||||||
|
# endif
|
||||||
|
|
||||||
|
# include <vector>
|
||||||
|
|
||||||
|
class NimBLEScan;
|
||||||
|
/**
|
||||||
|
* @brief A representation of a %BLE advertised device found by a scan.
|
||||||
|
*
|
||||||
|
* When we perform a %BLE scan, the result will be a set of devices that are advertising. This
|
||||||
|
* class provides a model of a detected device.
|
||||||
|
*/
|
||||||
|
class NimBLEAdvertisedDevice {
|
||||||
|
public:
|
||||||
|
NimBLEAdvertisedDevice() = default;
|
||||||
|
|
||||||
|
uint8_t getAdvType() const;
|
||||||
|
uint8_t getAdvFlags() const;
|
||||||
|
uint16_t getAppearance() const;
|
||||||
|
uint16_t getAdvInterval() const;
|
||||||
|
uint16_t getMinInterval() const;
|
||||||
|
uint16_t getMaxInterval() const;
|
||||||
|
uint8_t getManufacturerDataCount() const;
|
||||||
|
const NimBLEAddress& getAddress() const;
|
||||||
|
std::string getManufacturerData(uint8_t index = 0) const;
|
||||||
|
std::string getURI() const;
|
||||||
|
std::string getPayloadByType(uint16_t type, uint8_t index = 0) const;
|
||||||
|
std::string getName() const;
|
||||||
|
int8_t getRSSI() const;
|
||||||
|
NimBLEScan* getScan() const;
|
||||||
|
uint8_t getServiceDataCount() const;
|
||||||
|
std::string getServiceData(uint8_t index = 0) const;
|
||||||
|
uint8_t * getServiceData(uint16_t *len) const;
|
||||||
|
std::string getServiceData(const NimBLEUUID& uuid) const;
|
||||||
|
NimBLEUUID getServiceDataUUID(uint8_t index = 0) const;
|
||||||
|
NimBLEUUID getServiceUUID(uint8_t index = 0) const;
|
||||||
|
uint8_t getServiceUUIDCount() const;
|
||||||
|
NimBLEAddress getTargetAddress(uint8_t index = 0) const;
|
||||||
|
uint8_t getTargetAddressCount() const;
|
||||||
|
int8_t getTXPower() const;
|
||||||
|
uint8_t getAdvLength() const;
|
||||||
|
uint8_t getAddressType() const;
|
||||||
|
bool isAdvertisingService(const NimBLEUUID& uuid) const;
|
||||||
|
bool haveAppearance() const;
|
||||||
|
bool haveManufacturerData() const;
|
||||||
|
bool haveName() const;
|
||||||
|
bool haveServiceData() const;
|
||||||
|
bool haveServiceUUID() const;
|
||||||
|
bool haveTXPower() const;
|
||||||
|
bool haveConnParams() const;
|
||||||
|
bool haveAdvInterval() const;
|
||||||
|
bool haveTargetAddress() const;
|
||||||
|
bool haveURI() const;
|
||||||
|
bool haveType(uint16_t type) const;
|
||||||
|
std::string toString() const;
|
||||||
|
bool isConnectable() const;
|
||||||
|
bool isScannable() const;
|
||||||
|
bool isLegacyAdvertisement() const;
|
||||||
|
# if CONFIG_BT_NIMBLE_EXT_ADV
|
||||||
|
uint8_t getSetId() const;
|
||||||
|
uint8_t getPrimaryPhy() const;
|
||||||
|
uint8_t getSecondaryPhy() const;
|
||||||
|
uint16_t getPeriodicInterval() const;
|
||||||
|
# endif
|
||||||
|
|
||||||
|
const std::vector<uint8_t>& getPayload() const;
|
||||||
|
const std::vector<uint8_t>::const_iterator begin() const;
|
||||||
|
const std::vector<uint8_t>::const_iterator end() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A template to convert the service data to <type\>.
|
||||||
|
* @tparam T The type to convert the data to.
|
||||||
|
* @param [in] skipSizeCheck If true it will skip checking if the data size is less than <tt>sizeof(<type\>)</tt>.
|
||||||
|
* @return The data converted to <type\> or NULL if skipSizeCheck is false and the data is
|
||||||
|
* less than <tt>sizeof(<type\>)</tt>.
|
||||||
|
* @details <b>Use:</b> <tt>getManufacturerData<type>(skipSizeCheck);</tt>
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
T getManufacturerData(bool skipSizeCheck = false) const {
|
||||||
|
std::string data = getManufacturerData();
|
||||||
|
if (!skipSizeCheck && data.size() < sizeof(T)) return T();
|
||||||
|
const char* pData = data.data();
|
||||||
|
return *((T*)pData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A template to convert the service data to <tt><type\></tt>.
|
||||||
|
* @tparam T The type to convert the data to.
|
||||||
|
* @param [in] index The vector index of the service data requested.
|
||||||
|
* @param [in] skipSizeCheck If true it will skip checking if the data size is less than <tt>sizeof(<type\>)</tt>.
|
||||||
|
* @return The data converted to <type\> or NULL if skipSizeCheck is false and the data is
|
||||||
|
* less than <tt>sizeof(<type\>)</tt>.
|
||||||
|
* @details <b>Use:</b> <tt>getServiceData<type>(skipSizeCheck);</tt>
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
T getServiceData(uint8_t index = 0, bool skipSizeCheck = false) const {
|
||||||
|
std::string data = getServiceData(index);
|
||||||
|
if (!skipSizeCheck && data.size() < sizeof(T)) return T();
|
||||||
|
const char* pData = data.data();
|
||||||
|
return *((T*)pData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A template to convert the service data to <tt><type\></tt>.
|
||||||
|
* @tparam T The type to convert the data to.
|
||||||
|
* @param [in] uuid The uuid of the service data requested.
|
||||||
|
* @param [in] skipSizeCheck If true it will skip checking if the data size is less than <tt>sizeof(<type\>)</tt>.
|
||||||
|
* @return The data converted to <type\> or NULL if skipSizeCheck is false and the data is
|
||||||
|
* less than <tt>sizeof(<type\>)</tt>.
|
||||||
|
* @details <b>Use:</b> <tt>getServiceData<type>(skipSizeCheck);</tt>
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
T getServiceData(const NimBLEUUID& uuid, bool skipSizeCheck = false) const {
|
||||||
|
std::string data = getServiceData(uuid);
|
||||||
|
if (!skipSizeCheck && data.size() < sizeof(T)) return T();
|
||||||
|
const char* pData = data.data();
|
||||||
|
return *((T*)pData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A template to convert the service data to <tt><type></tt>.
|
||||||
|
* @tparam T The type to convert the data to.
|
||||||
|
* @param [in] uuid The UUID of the service data requested.
|
||||||
|
* @param [in] skipSizeCheck If true, skips checking if the data size is less than <tt>sizeof(<type>)</tt>.
|
||||||
|
* @return The data converted to <type> or a default-constructed <type> if size check fails.
|
||||||
|
* @details <b>Usage:</b> <tt>getServiceData<Type>(uuid, skipSizeCheck);</tt>
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
T getServiceData(uint16_t *len) const {
|
||||||
|
uint8_t* data = getServiceData(len);
|
||||||
|
|
||||||
|
if (data == nullptr) {
|
||||||
|
len = 0;
|
||||||
|
return T();
|
||||||
|
}
|
||||||
|
return *reinterpret_cast<T*>(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class NimBLEScan;
|
||||||
|
|
||||||
|
NimBLEAdvertisedDevice(const ble_gap_event* event, uint8_t eventType);
|
||||||
|
void update(const ble_gap_event* event, uint8_t eventType);
|
||||||
|
uint8_t findAdvField(uint8_t type, uint8_t index = 0, size_t* data_loc = nullptr) const;
|
||||||
|
size_t findServiceData(uint8_t index, uint8_t* bytes) const;
|
||||||
|
|
||||||
|
NimBLEAddress m_address{};
|
||||||
|
uint8_t m_advType{};
|
||||||
|
int8_t m_rssi{};
|
||||||
|
uint8_t m_callbackSent{};
|
||||||
|
uint8_t m_advLength{};
|
||||||
|
|
||||||
|
# if CONFIG_BT_NIMBLE_EXT_ADV
|
||||||
|
bool m_isLegacyAdv{};
|
||||||
|
uint8_t m_sid{};
|
||||||
|
uint8_t m_primPhy{};
|
||||||
|
uint8_t m_secPhy{};
|
||||||
|
uint16_t m_periodicItvl{};
|
||||||
|
# endif
|
||||||
|
|
||||||
|
std::vector<uint8_t> m_payload;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_OBSERVER */
|
||||||
|
#endif /* NIMBLE_CPP_ADVERTISED_DEVICE_H_ */
|
||||||
625
Libraries/NimBLE-Arduino_modified/NimBLEScan.cpp
Normal file
625
Libraries/NimBLE-Arduino_modified/NimBLEScan.cpp
Normal file
|
|
@ -0,0 +1,625 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||||
|
* esp-nimble-cpp, NimBLE-Arduino contributors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "nimconfig.h"
|
||||||
|
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER)
|
||||||
|
|
||||||
|
# include "NimBLEScan.h"
|
||||||
|
# include "NimBLEDevice.h"
|
||||||
|
# include "NimBLELog.h"
|
||||||
|
|
||||||
|
# include <string>
|
||||||
|
# include <climits>
|
||||||
|
|
||||||
|
static const char* LOG_TAG = "NimBLEScan";
|
||||||
|
static NimBLEScanCallbacks defaultScanCallbacks;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Scan constructor.
|
||||||
|
*/
|
||||||
|
NimBLEScan::NimBLEScan()
|
||||||
|
: m_pScanCallbacks{&defaultScanCallbacks},
|
||||||
|
// default interval + window, no whitelist scan filter,not limited scan, no scan response, filter_duplicates
|
||||||
|
m_scanParams{0, 0, BLE_HCI_SCAN_FILT_NO_WL, 0, 1, 1},
|
||||||
|
m_pTaskData{nullptr},
|
||||||
|
m_maxResults{0xFF},
|
||||||
|
m_bDiscovered(false)
|
||||||
|
{}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Scan destructor, release any allocated resources.
|
||||||
|
*/
|
||||||
|
NimBLEScan::~NimBLEScan() {
|
||||||
|
clearResults();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Handle GAP events related to scans.
|
||||||
|
* @param [in] event The event type for this event.
|
||||||
|
* @param [in] param Parameter data for this event.
|
||||||
|
*/
|
||||||
|
int NimBLEScan::handleGapEvent(ble_gap_event* event, void* arg) {
|
||||||
|
(void)arg;
|
||||||
|
NimBLEScan* pScan = NimBLEDevice::getScan();
|
||||||
|
|
||||||
|
switch (event->type) {
|
||||||
|
case BLE_GAP_EVENT_EXT_DISC:
|
||||||
|
case BLE_GAP_EVENT_DISC: {
|
||||||
|
//const auto& disc = event->disc;
|
||||||
|
//const bool isLegacyAdv = true;
|
||||||
|
//const auto event_type = event->disc.event_type;
|
||||||
|
NimBLEAddress advertisedAddress(event->disc.addr);
|
||||||
|
NimBLEAdvertisedDevice tempDevice(event, event->disc.event_type);
|
||||||
|
|
||||||
|
// Process if the address does match the target
|
||||||
|
if (advertisedAddress == pScan->m_targetAddress) {
|
||||||
|
pScan->m_pScanCallbacks->onResult(&tempDevice);
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
// SSID & PW Broadcast
|
||||||
|
// Get manufacturer data as a string
|
||||||
|
const std::string& manufacturerData = tempDevice.getManufacturerData();
|
||||||
|
if (!manufacturerData.empty() && manufacturerData.length() >= 2) {
|
||||||
|
// Check if the manufacturer data is valid and the first 2 bytes matches the manufacturer ID
|
||||||
|
uint16_t manufacturerID = * (uint16_t *) &manufacturerData[0];
|
||||||
|
if (manufacturerID == 0x4E80 || manufacturerID == 0x4E81) {
|
||||||
|
pScan->m_pScanCallbacks->onDiscovered(&tempDevice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Call onDiscovered only once
|
||||||
|
// if (!pScan->m_bDiscovered) {
|
||||||
|
// pScan->m_device = NimBLEAdvertisedDevice(event, event->disc.event_type);
|
||||||
|
// //pScan->m_pScanCallbacks->onDiscovered(&pScan->m_device);
|
||||||
|
// pScan->m_bDiscovered = true;
|
||||||
|
// } else {
|
||||||
|
// // Update the single device instance with new data
|
||||||
|
// pScan->m_device.update(event, event->disc.event_type);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Always call onResult
|
||||||
|
// pScan->m_pScanCallbacks->onResult(&pScan->m_device);
|
||||||
|
// //pScan->erase(&pScan->m_device);
|
||||||
|
// return 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case BLE_GAP_EVENT_DISC_COMPLETE: {
|
||||||
|
NIMBLE_LOGD(LOG_TAG, "Discovery complete; reason=%d", event->disc_complete.reason);
|
||||||
|
|
||||||
|
//pScan->m_pScanCallbacks->onScanEnd(pScan->m_scanResults, event->disc_complete.reason);
|
||||||
|
|
||||||
|
if (pScan->m_pTaskData != nullptr) {
|
||||||
|
NimBLEUtils::taskRelease(*pScan->m_pTaskData, event->disc_complete.reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
int NimBLEScan::handleGapEvent(ble_gap_event* event, void* arg) {
|
||||||
|
(void)arg;
|
||||||
|
NimBLEScan* pScan = NimBLEDevice::getScan();
|
||||||
|
|
||||||
|
switch (event->type) {
|
||||||
|
case BLE_GAP_EVENT_EXT_DISC:
|
||||||
|
case BLE_GAP_EVENT_DISC: {
|
||||||
|
# if CONFIG_BT_NIMBLE_EXT_ADV
|
||||||
|
const auto& disc = event->ext_disc;
|
||||||
|
const bool isLegacyAdv = disc.props & BLE_HCI_ADV_LEGACY_MASK;
|
||||||
|
const auto event_type = isLegacyAdv ? disc.legacy_event_type : disc.props;
|
||||||
|
# else
|
||||||
|
const auto& disc = event->disc;
|
||||||
|
const bool isLegacyAdv = true;
|
||||||
|
const auto event_type = disc.event_type;
|
||||||
|
# endif
|
||||||
|
NimBLEAddress advertisedAddress(disc.addr);
|
||||||
|
|
||||||
|
# ifdef CONFIG_BT_NIMBLE_ROLE_CENTRAL
|
||||||
|
// stop processing if already connected
|
||||||
|
NimBLEClient* pClient = NimBLEDevice::getClientByPeerAddress(advertisedAddress);
|
||||||
|
if (pClient != nullptr && pClient->isConnected()) {
|
||||||
|
NIMBLE_LOGI(LOG_TAG, "Ignoring device: address: %s, already connected", advertisedAddress.toString().c_str());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
# endif
|
||||||
|
NimBLEAdvertisedDevice* advertisedDevice = nullptr;
|
||||||
|
|
||||||
|
// If we've seen this device before get a pointer to it from the vector
|
||||||
|
for (const auto& dev : pScan->m_scanResults.m_deviceVec) {
|
||||||
|
# if CONFIG_BT_NIMBLE_EXT_ADV
|
||||||
|
// Same address but different set ID should create a new advertised device.
|
||||||
|
if (dev->getAddress() == advertisedAddress && dev->getSetId() == disc.sid)
|
||||||
|
# else
|
||||||
|
if (dev->getAddress() == advertisedAddress)
|
||||||
|
# endif
|
||||||
|
{
|
||||||
|
advertisedDevice = dev;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we haven't seen this device before; create a new instance and insert it in the vector.
|
||||||
|
// Otherwise just update the relevant parameters of the already known device.
|
||||||
|
if (advertisedDevice == nullptr) {
|
||||||
|
// Check if we have reach the scan results limit, ignore this one if so.
|
||||||
|
// We still need to store each device when maxResults is 0 to be able to append the scan results
|
||||||
|
if (pScan->m_maxResults > 0 && pScan->m_maxResults < 0xFF &&
|
||||||
|
(pScan->m_scanResults.m_deviceVec.size() >= pScan->m_maxResults)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLegacyAdv && event_type == BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP) {
|
||||||
|
NIMBLE_LOGI(LOG_TAG, "Scan response without advertisement: %s", advertisedAddress.toString().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
advertisedDevice = new NimBLEAdvertisedDevice(event, event_type);
|
||||||
|
pScan->m_scanResults.m_deviceVec.push_back(advertisedDevice);
|
||||||
|
NIMBLE_LOGI(LOG_TAG, "New advertiser: %s", advertisedAddress.toString().c_str());
|
||||||
|
} else {
|
||||||
|
advertisedDevice->update(event, event_type);
|
||||||
|
if (isLegacyAdv && event_type == BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP) {
|
||||||
|
NIMBLE_LOGI(LOG_TAG, "Scan response from: %s", advertisedAddress.toString().c_str());
|
||||||
|
} else {
|
||||||
|
NIMBLE_LOGI(LOG_TAG, "Duplicate; updated: %s", advertisedAddress.toString().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!advertisedDevice->m_callbackSent) {
|
||||||
|
advertisedDevice->m_callbackSent++;
|
||||||
|
pScan->m_pScanCallbacks->onDiscovered(advertisedDevice);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not active scanning or scan response is not available
|
||||||
|
// or extended advertisement scanning, report the result to the callback now.
|
||||||
|
if (pScan->m_scanParams.passive || !isLegacyAdv || !advertisedDevice->isScannable()) {
|
||||||
|
advertisedDevice->m_callbackSent++;
|
||||||
|
pScan->m_pScanCallbacks->onResult(advertisedDevice);
|
||||||
|
} else if (isLegacyAdv && event_type == BLE_HCI_ADV_RPT_EVTYPE_SCAN_RSP) {
|
||||||
|
advertisedDevice->m_callbackSent++;
|
||||||
|
// got the scan response report the full data.
|
||||||
|
pScan->m_pScanCallbacks->onResult(advertisedDevice);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not storing results and we have invoked the callback, delete the device.
|
||||||
|
if (pScan->m_maxResults == 0 && advertisedDevice->m_callbackSent >= 2) {
|
||||||
|
pScan->erase(advertisedDevice);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
case BLE_GAP_EVENT_DISC_COMPLETE: {
|
||||||
|
NIMBLE_LOGD(LOG_TAG, "discovery complete; reason=%d", event->disc_complete.reason);
|
||||||
|
|
||||||
|
if (pScan->m_maxResults == 0) {
|
||||||
|
pScan->clearResults();
|
||||||
|
}
|
||||||
|
|
||||||
|
pScan->m_pScanCallbacks->onScanEnd(pScan->m_scanResults, event->disc_complete.reason);
|
||||||
|
|
||||||
|
if (pScan->m_pTaskData != nullptr) {
|
||||||
|
NimBLEUtils::taskRelease(*pScan->m_pTaskData, event->disc_complete.reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} // handleGapEvent
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Should we perform an active or passive scan?
|
||||||
|
* The default is a passive scan. An active scan means that we will request a scan response.
|
||||||
|
* @param [in] active If true, we perform an active scan otherwise a passive scan.
|
||||||
|
*/
|
||||||
|
void NimBLEScan::setActiveScan(bool active) {
|
||||||
|
m_scanParams.passive = !active;
|
||||||
|
} // setActiveScan
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set whether or not the BLE controller should only report results
|
||||||
|
* from devices it has not already seen.
|
||||||
|
* @param [in] enabled If set to 1 (true), scanned devices will only be reported once.
|
||||||
|
* If set to 0 duplicates will be reported each time they are seen.
|
||||||
|
* If using extended scanning this can be set to 2 which will reset the duplicate filter
|
||||||
|
* at the end of each scan period if the scan period is set.
|
||||||
|
* @note The controller has a limited buffer and will start reporting
|
||||||
|
duplicate devices once the limit is reached.
|
||||||
|
*/
|
||||||
|
void NimBLEScan::setDuplicateFilter(uint8_t enabled) {
|
||||||
|
m_scanParams.filter_duplicates = enabled;
|
||||||
|
} // setDuplicateFilter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set whether or not the BLE controller only reports scan results
|
||||||
|
* from devices advertising in limited discovery mode.
|
||||||
|
* @param [in] enabled If true, only limited discovery devices will be in scan results.
|
||||||
|
*/
|
||||||
|
void NimBLEScan::setLimitedOnly(bool enabled) {
|
||||||
|
m_scanParams.limited = enabled;
|
||||||
|
} // setLimited
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the scan filter policy.
|
||||||
|
* @param [in] filter Can be one of:
|
||||||
|
* * BLE_HCI_SCAN_FILT_NO_WL (0)
|
||||||
|
* Scanner processes all advertising packets (white list not used) except\n
|
||||||
|
* directed, connectable advertising packets not sent to the scanner.
|
||||||
|
* * BLE_HCI_SCAN_FILT_USE_WL (1)
|
||||||
|
* Scanner processes advertisements from white list only. A connectable,\n
|
||||||
|
* directed advertisement is ignored unless it contains scanners address.
|
||||||
|
* * BLE_HCI_SCAN_FILT_NO_WL_INITA (2)
|
||||||
|
* Scanner process all advertising packets (white list not used). A\n
|
||||||
|
* connectable, directed advertisement shall not be ignored if the InitA
|
||||||
|
* is a resolvable private address.
|
||||||
|
* * BLE_HCI_SCAN_FILT_USE_WL_INITA (3)
|
||||||
|
* Scanner process advertisements from white list only. A connectable,\n
|
||||||
|
* directed advertisement shall not be ignored if the InitA is a
|
||||||
|
* resolvable private address.
|
||||||
|
*/
|
||||||
|
void NimBLEScan::setFilterPolicy(uint8_t filter) {
|
||||||
|
m_scanParams.filter_policy = filter;
|
||||||
|
} // setFilterPolicy
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the max number of results to store.
|
||||||
|
* @param [in] maxResults The number of results to limit storage to\n
|
||||||
|
* 0 == none (callbacks only) 0xFF == unlimited, any other value is the limit.
|
||||||
|
*/
|
||||||
|
void NimBLEScan::setMaxResults(uint8_t maxResults) {
|
||||||
|
m_maxResults = maxResults;
|
||||||
|
} // setMaxResults
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the call backs to be invoked.
|
||||||
|
* @param [in] pScanCallbacks Call backs to be invoked.
|
||||||
|
* @param [in] wantDuplicates True if we wish to be called back with duplicates, default: false.
|
||||||
|
*/
|
||||||
|
void NimBLEScan::setScanCallbacks(NimBLEScanCallbacks* pScanCallbacks, bool wantDuplicates) {
|
||||||
|
setDuplicateFilter(!wantDuplicates);
|
||||||
|
if (pScanCallbacks == nullptr) {
|
||||||
|
m_pScanCallbacks = &defaultScanCallbacks;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_pScanCallbacks = pScanCallbacks;
|
||||||
|
} // setScanCallbacks
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the interval to scan.
|
||||||
|
* @param [in] intervalMs The scan interval in milliseconds.
|
||||||
|
* @details The interval is the time between the start of two consecutive scan windows.
|
||||||
|
* When a new interval starts the controller changes the channel it's scanning on.
|
||||||
|
*/
|
||||||
|
void NimBLEScan::setInterval(uint16_t intervalMs) {
|
||||||
|
m_scanParams.itvl = (intervalMs * 16) / 10;
|
||||||
|
} // setInterval
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the window to actively scan.
|
||||||
|
* @param [in] windowMs How long during the interval to actively scan in milliseconds.
|
||||||
|
*/
|
||||||
|
void NimBLEScan::setWindow(uint16_t windowMs) {
|
||||||
|
m_scanParams.window = (windowMs * 16) / 10;
|
||||||
|
} // setWindow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the status of the scanner.
|
||||||
|
* @return true if scanning or scan starting.
|
||||||
|
*/
|
||||||
|
bool NimBLEScan::isScanning() {
|
||||||
|
return ble_gap_disc_active();
|
||||||
|
}
|
||||||
|
|
||||||
|
# if CONFIG_BT_NIMBLE_EXT_ADV
|
||||||
|
/**
|
||||||
|
* @brief Set the PHYs to scan.
|
||||||
|
* @param [in] phyMask The PHYs to scan, a bit mask of:
|
||||||
|
* * NIMBLE_CPP_SCAN_1M
|
||||||
|
* * NIMBLE_CPP_SCAN_CODED
|
||||||
|
*/
|
||||||
|
void NimBLEScan::setPhy(Phy phyMask) {
|
||||||
|
m_phy = phyMask;
|
||||||
|
} // setScanPhy
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the extended scanning period.
|
||||||
|
* @param [in] periodMs The scan period in milliseconds
|
||||||
|
* @details The period is the time between the start of two consecutive scan periods.
|
||||||
|
* This works as a timer to restart scanning at the specified amount of time in periodMs.
|
||||||
|
* @note The duration used when this is set must be less than period.
|
||||||
|
*/
|
||||||
|
void NimBLEScan::setPeriod(uint32_t periodMs) {
|
||||||
|
m_period = (periodMs + 500) / 1280; // round up 1.28 second units
|
||||||
|
} // setScanPeriod
|
||||||
|
# endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Start scanning.
|
||||||
|
* @param [in] duration The duration in milliseconds for which to scan. 0 == scan forever.
|
||||||
|
* @param [in] isContinue Set to true to save previous scan results, false to clear them.
|
||||||
|
* @param [in] restart Set to true to restart the scan if already in progress.
|
||||||
|
* this is useful to clear the duplicate filter so all devices are reported again.
|
||||||
|
* @return True if scan started or false if there was an error.
|
||||||
|
*/
|
||||||
|
bool NimBLEScan::start(uint32_t duration, bool isContinue, bool restart) {
|
||||||
|
NIMBLE_LOGD(LOG_TAG, ">> start: duration=%" PRIu32, duration);
|
||||||
|
if (isScanning()) {
|
||||||
|
if (restart) {
|
||||||
|
NIMBLE_LOGI(LOG_TAG, "Scan already in progress, restarting it");
|
||||||
|
if (!stop()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isContinue) {
|
||||||
|
clearResults();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else { // Don't clear results while scanning is active
|
||||||
|
if (!isContinue) {
|
||||||
|
clearResults();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If scanning is already active, call the functions anyway as the parameters can be changed.
|
||||||
|
|
||||||
|
# if CONFIG_BT_NIMBLE_EXT_ADV
|
||||||
|
ble_gap_ext_disc_params scan_params;
|
||||||
|
scan_params.passive = m_scanParams.passive;
|
||||||
|
scan_params.itvl = m_scanParams.itvl;
|
||||||
|
scan_params.window = m_scanParams.window;
|
||||||
|
int rc = ble_gap_ext_disc(NimBLEDevice::m_ownAddrType,
|
||||||
|
duration / 10, // 10ms units
|
||||||
|
m_period,
|
||||||
|
m_scanParams.filter_duplicates,
|
||||||
|
m_scanParams.filter_policy,
|
||||||
|
m_scanParams.limited,
|
||||||
|
m_phy & SCAN_1M ? &scan_params : NULL,
|
||||||
|
m_phy & SCAN_CODED ? &scan_params : NULL,
|
||||||
|
NimBLEScan::handleGapEvent,
|
||||||
|
NULL);
|
||||||
|
# else
|
||||||
|
int rc = ble_gap_disc(NimBLEDevice::m_ownAddrType,
|
||||||
|
duration ? duration : BLE_HS_FOREVER,
|
||||||
|
&m_scanParams,
|
||||||
|
NimBLEScan::handleGapEvent,
|
||||||
|
NULL);
|
||||||
|
# endif
|
||||||
|
switch (rc) {
|
||||||
|
case 0:
|
||||||
|
case BLE_HS_EALREADY:
|
||||||
|
NIMBLE_LOGD(LOG_TAG, "Scan started");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BLE_HS_EBUSY:
|
||||||
|
NIMBLE_LOGE(LOG_TAG, "Unable to scan - connection in progress.");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BLE_HS_ETIMEOUT_HCI:
|
||||||
|
case BLE_HS_EOS:
|
||||||
|
case BLE_HS_ECONTROLLER:
|
||||||
|
case BLE_HS_ENOTSYNCED:
|
||||||
|
NIMBLE_LOGE(LOG_TAG, "Unable to scan - Host Reset");
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
NIMBLE_LOGE(LOG_TAG, "Error starting scan; rc=%d, %s", rc, NimBLEUtils::returnCodeToString(rc));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
NIMBLE_LOGD(LOG_TAG, "<< start()");
|
||||||
|
return rc == 0 || rc == BLE_HS_EALREADY;
|
||||||
|
} // start
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Stop an in progress scan.
|
||||||
|
* @return True if successful.
|
||||||
|
*/
|
||||||
|
bool NimBLEScan::stop() {
|
||||||
|
NIMBLE_LOGD(LOG_TAG, ">> stop()");
|
||||||
|
|
||||||
|
int rc = ble_gap_disc_cancel();
|
||||||
|
if (rc != 0 && rc != BLE_HS_EALREADY) {
|
||||||
|
NIMBLE_LOGE(LOG_TAG, "Failed to cancel scan; rc=%d", rc);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_maxResults == 0) {
|
||||||
|
clearResults();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_pTaskData != nullptr) {
|
||||||
|
NimBLEUtils::taskRelease(*m_pTaskData);
|
||||||
|
}
|
||||||
|
|
||||||
|
NIMBLE_LOGD(LOG_TAG, "<< stop()");
|
||||||
|
return true;
|
||||||
|
} // stop
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Delete peer device from the scan results vector.
|
||||||
|
* @param [in] address The address of the device to delete from the results.
|
||||||
|
*/
|
||||||
|
void NimBLEScan::erase(const NimBLEAddress& address) {
|
||||||
|
NIMBLE_LOGD(LOG_TAG, "erase device: %s", address.toString().c_str());
|
||||||
|
for (auto it = m_scanResults.m_deviceVec.begin(); it != m_scanResults.m_deviceVec.end(); ++it) {
|
||||||
|
if ((*it)->getAddress() == address) {
|
||||||
|
delete *it;
|
||||||
|
m_scanResults.m_deviceVec.erase(it);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Delete peer device from the scan results vector.
|
||||||
|
* @param [in] device The device to delete from the results.
|
||||||
|
*/
|
||||||
|
void NimBLEScan::erase(const NimBLEAdvertisedDevice* device) {
|
||||||
|
NIMBLE_LOGD(LOG_TAG, "erase device: %s", device->getAddress().toString().c_str());
|
||||||
|
for (auto it = m_scanResults.m_deviceVec.begin(); it != m_scanResults.m_deviceVec.end(); ++it) {
|
||||||
|
if ((*it) == device) {
|
||||||
|
delete *it;
|
||||||
|
m_scanResults.m_deviceVec.erase(it);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief If the host reset and re-synced this is called.
|
||||||
|
* If the application was scanning indefinitely with a callback, restart it.
|
||||||
|
*/
|
||||||
|
void NimBLEScan::onHostSync() {
|
||||||
|
m_pScanCallbacks->onScanEnd(m_scanResults, BLE_HS_ENOTSYNCED);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Start scanning and block until scanning has been completed.
|
||||||
|
* @param [in] duration The duration in milliseconds for which to scan.
|
||||||
|
* @param [in] is_continue Set to true to save previous scan results, false to clear them.
|
||||||
|
* @return The scan results.
|
||||||
|
*/
|
||||||
|
NimBLEScanResults NimBLEScan::getResults(uint32_t duration, bool is_continue) {
|
||||||
|
if (duration == 0) {
|
||||||
|
NIMBLE_LOGW(LOG_TAG, "Blocking scan called with duration = forever");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_pTaskData != nullptr) {
|
||||||
|
NIMBLE_LOGE(LOG_TAG, "Scan already in progress");
|
||||||
|
return m_scanResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
NimBLETaskData taskData;
|
||||||
|
m_pTaskData = &taskData;
|
||||||
|
|
||||||
|
if (start(duration, is_continue)) {
|
||||||
|
NimBLEUtils::taskWait(taskData, BLE_NPL_TIME_FOREVER);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pTaskData = nullptr;
|
||||||
|
return m_scanResults;
|
||||||
|
} // getResults
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the results of the scan.
|
||||||
|
* @return NimBLEScanResults object.
|
||||||
|
*/
|
||||||
|
NimBLEScanResults NimBLEScan::getResults() {
|
||||||
|
return m_scanResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Clear the stored results of the scan.
|
||||||
|
*/
|
||||||
|
void NimBLEScan::clearResults() {
|
||||||
|
if (m_scanResults.m_deviceVec.size()) {
|
||||||
|
std::vector<NimBLEAdvertisedDevice*> vSwap{};
|
||||||
|
ble_npl_hw_enter_critical();
|
||||||
|
vSwap.swap(m_scanResults.m_deviceVec);
|
||||||
|
ble_npl_hw_exit_critical(0);
|
||||||
|
for (const auto& dev : vSwap) {
|
||||||
|
delete dev;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // clearResults
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Dump the scan results to the log.
|
||||||
|
*/
|
||||||
|
void NimBLEScanResults::dump() const {
|
||||||
|
#if CONFIG_NIMBLE_CPP_LOG_LEVEL >=3
|
||||||
|
for (const auto& dev : m_deviceVec) {
|
||||||
|
NIMBLE_LOGI(LOG_TAG, "- %s", dev->toString().c_str());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
} // dump
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the count of devices found in the last scan.
|
||||||
|
* @return The number of devices found in the last scan.
|
||||||
|
*/
|
||||||
|
int NimBLEScanResults::getCount() const {
|
||||||
|
return m_deviceVec.size();
|
||||||
|
} // getCount
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return the specified device at the given index.
|
||||||
|
* The index should be between 0 and getCount()-1.
|
||||||
|
* @param [in] idx The index of the device.
|
||||||
|
* @return The device at the specified index.
|
||||||
|
*/
|
||||||
|
const NimBLEAdvertisedDevice* NimBLEScanResults::getDevice(uint32_t idx) const {
|
||||||
|
return m_deviceVec[idx];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get iterator to the beginning of the vector of advertised device pointers.
|
||||||
|
* @return An iterator to the beginning of the vector of advertised device pointers.
|
||||||
|
*/
|
||||||
|
std::vector<NimBLEAdvertisedDevice*>::const_iterator NimBLEScanResults::begin() const {
|
||||||
|
return m_deviceVec.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get iterator to the end of the vector of advertised device pointers.
|
||||||
|
* @return An iterator to the end of the vector of advertised device pointers.
|
||||||
|
*/
|
||||||
|
std::vector<NimBLEAdvertisedDevice*>::const_iterator NimBLEScanResults::end() const {
|
||||||
|
return m_deviceVec.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get a pointer to the specified device at the given address.
|
||||||
|
* If the address is not found a nullptr is returned.
|
||||||
|
* @param [in] address The address of the device.
|
||||||
|
* @return A pointer to the device at the specified address.
|
||||||
|
*/
|
||||||
|
const NimBLEAdvertisedDevice* NimBLEScanResults::getDevice(const NimBLEAddress& address) const {
|
||||||
|
for (const auto& dev : m_deviceVec) {
|
||||||
|
if (dev->getAddress() == address) {
|
||||||
|
return dev;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* CB_TAG = "NimBLEScanCallbacks";
|
||||||
|
|
||||||
|
void NimBLEScanCallbacks::onDiscovered(const NimBLEAdvertisedDevice* pAdvertisedDevice) {
|
||||||
|
NIMBLE_LOGD(CB_TAG, "Discovered: %s", pAdvertisedDevice->toString().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void NimBLEScanCallbacks::onResult(const NimBLEAdvertisedDevice* pAdvertisedDevice) {
|
||||||
|
NIMBLE_LOGD(CB_TAG, "Result: %s", pAdvertisedDevice->toString().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void NimBLEScanCallbacks::onScanEnd(const NimBLEScanResults& results, int reason) {
|
||||||
|
NIMBLE_LOGD(CB_TAG, "Scan ended; reason %d, num results: %d", reason, results.getCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_OBSERVER */
|
||||||
146
Libraries/NimBLE-Arduino_modified/NimBLEScan.h
Normal file
146
Libraries/NimBLE-Arduino_modified/NimBLEScan.h
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020-2024 Ryan Powell <ryan@nable-embedded.io> and
|
||||||
|
* esp-nimble-cpp, NimBLE-Arduino contributors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef NIMBLE_CPP_SCAN_H_
|
||||||
|
#define NIMBLE_CPP_SCAN_H_
|
||||||
|
|
||||||
|
#include "nimconfig.h"
|
||||||
|
#if defined(CONFIG_BT_ENABLED) && defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER)
|
||||||
|
|
||||||
|
# include "NimBLEAdvertisedDevice.h"
|
||||||
|
# include "NimBLEUtils.h"
|
||||||
|
|
||||||
|
# if defined(CONFIG_NIMBLE_CPP_IDF)
|
||||||
|
# include "host/ble_gap.h"
|
||||||
|
# else
|
||||||
|
# include "nimble/nimble/host/include/host/ble_gap.h"
|
||||||
|
# endif
|
||||||
|
|
||||||
|
# include <vector>
|
||||||
|
|
||||||
|
class NimBLEDevice;
|
||||||
|
class NimBLEScan;
|
||||||
|
class NimBLEAdvertisedDevice;
|
||||||
|
class NimBLEScanCallbacks;
|
||||||
|
class NimBLEAddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A class that contains and operates on the results of a BLE scan.
|
||||||
|
* @details When a scan completes, we have a set of found devices. Each device is described
|
||||||
|
* by a NimBLEAdvertisedDevice object. The number of items in the set is given by
|
||||||
|
* getCount(). We can retrieve a device by calling getDevice() passing in the
|
||||||
|
* index (starting at 0) of the desired device.
|
||||||
|
*/
|
||||||
|
class NimBLEScanResults {
|
||||||
|
public:
|
||||||
|
void dump() const;
|
||||||
|
int getCount() const;
|
||||||
|
const NimBLEAdvertisedDevice* getDevice(uint32_t idx) const;
|
||||||
|
const NimBLEAdvertisedDevice* getDevice(const NimBLEAddress& address) const;
|
||||||
|
std::vector<NimBLEAdvertisedDevice*>::const_iterator begin() const;
|
||||||
|
std::vector<NimBLEAdvertisedDevice*>::const_iterator end() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend NimBLEScan;
|
||||||
|
std::vector<NimBLEAdvertisedDevice*> m_deviceVec;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Perform and manage %BLE scans.
|
||||||
|
*
|
||||||
|
* Scanning is associated with a %BLE client that is attempting to locate BLE servers.
|
||||||
|
*/
|
||||||
|
class NimBLEScan {
|
||||||
|
public:
|
||||||
|
bool m_bDiscovered;
|
||||||
|
NimBLEAdvertisedDevice m_device;
|
||||||
|
NimBLEAddress m_targetAddress;
|
||||||
|
void setTargetAddress(NimBLEAddress& addr) { m_targetAddress = addr; };
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool start(uint32_t duration, bool isContinue = false, bool restart = true);
|
||||||
|
bool isScanning();
|
||||||
|
void setScanCallbacks(NimBLEScanCallbacks* pScanCallbacks, bool wantDuplicates = false);
|
||||||
|
void setActiveScan(bool active);
|
||||||
|
void setInterval(uint16_t intervalMs);
|
||||||
|
void setWindow(uint16_t windowMs);
|
||||||
|
void setDuplicateFilter(uint8_t enabled);
|
||||||
|
void setLimitedOnly(bool enabled);
|
||||||
|
void setFilterPolicy(uint8_t filter);
|
||||||
|
bool stop();
|
||||||
|
void clearResults();
|
||||||
|
NimBLEScanResults getResults();
|
||||||
|
NimBLEScanResults getResults(uint32_t duration, bool is_continue = false);
|
||||||
|
void setMaxResults(uint8_t maxResults);
|
||||||
|
void erase(const NimBLEAddress& address);
|
||||||
|
void erase(const NimBLEAdvertisedDevice* device);
|
||||||
|
|
||||||
|
# if CONFIG_BT_NIMBLE_EXT_ADV
|
||||||
|
enum Phy { SCAN_1M = 0x01, SCAN_CODED = 0x02, SCAN_ALL = 0x03 };
|
||||||
|
void setPhy(Phy phyMask);
|
||||||
|
void setPeriod(uint32_t periodMs);
|
||||||
|
# endif
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class NimBLEDevice;
|
||||||
|
|
||||||
|
NimBLEScan();
|
||||||
|
~NimBLEScan();
|
||||||
|
static int handleGapEvent(ble_gap_event* event, void* arg);
|
||||||
|
void onHostSync();
|
||||||
|
|
||||||
|
NimBLEScanCallbacks* m_pScanCallbacks;
|
||||||
|
ble_gap_disc_params m_scanParams;
|
||||||
|
NimBLEScanResults m_scanResults;
|
||||||
|
NimBLETaskData* m_pTaskData;
|
||||||
|
uint8_t m_maxResults;
|
||||||
|
|
||||||
|
# if CONFIG_BT_NIMBLE_EXT_ADV
|
||||||
|
uint8_t m_phy{SCAN_ALL};
|
||||||
|
uint16_t m_period{0};
|
||||||
|
# endif
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A callback handler for callbacks associated device scanning.
|
||||||
|
*/
|
||||||
|
class NimBLEScanCallbacks {
|
||||||
|
public:
|
||||||
|
virtual ~NimBLEScanCallbacks() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Called when a new device is discovered, before the scan result is received (if applicable).
|
||||||
|
* @param [in] advertisedDevice The device which was discovered.
|
||||||
|
*/
|
||||||
|
virtual void onDiscovered(const NimBLEAdvertisedDevice* advertisedDevice);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Called when a new scan result is complete, including scan response data (if applicable).
|
||||||
|
* @param [in] advertisedDevice The device for which the complete result is available.
|
||||||
|
*/
|
||||||
|
virtual void onResult(const NimBLEAdvertisedDevice* advertisedDevice);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Called when a scan operation ends.
|
||||||
|
* @param [in] scanResults The results of the scan that ended.
|
||||||
|
* @param [in] reason The reason code for why the scan ended.
|
||||||
|
*/
|
||||||
|
virtual void onScanEnd(const NimBLEScanResults& scanResults, int reason);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CONFIG_BT_ENABLED CONFIG_BT_NIMBLE_ROLE_OBSERVER
|
||||||
|
#endif // NIMBLE_CPP_SCAN_H_
|
||||||
2
OTA.cpp
2
OTA.cpp
|
|
@ -17,7 +17,7 @@
|
||||||
// OTA
|
// OTA
|
||||||
//
|
//
|
||||||
// ==============================================================
|
// ==============================================================
|
||||||
const char *HC__VERSION = "20250405001";
|
const char *HC__VERSION = "20260413001";
|
||||||
#define UPDATE_PORT ((uint16_t) 443)
|
#define UPDATE_PORT ((uint16_t) 443)
|
||||||
String url = "visionsoft.kr";
|
String url = "visionsoft.kr";
|
||||||
String uri = "/hc/hc_firmware_update.php";
|
String uri = "/hc/hc_firmware_update.php";
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
|
|
||||||
dependancy: NimBLE-Arduino library, maintained by h2zero (Ryan Powell)
|
dependancy: NimBLE-Arduino library, maintained by h2zero (Ryan Powell)
|
||||||
|
requires Arduino core 3.0.2
|
||||||
83
Setup.cpp
83
Setup.cpp
|
|
@ -9,7 +9,6 @@
|
||||||
#include "TimeManager.h"
|
#include "TimeManager.h"
|
||||||
#include "OTA.h"
|
#include "OTA.h"
|
||||||
#include "UI.h"
|
#include "UI.h"
|
||||||
#include "LED0.h"
|
|
||||||
#include "BLEScan.h"
|
#include "BLEScan.h"
|
||||||
#include <esp_wifi.h>
|
#include <esp_wifi.h>
|
||||||
#include "esp_coexist.h"
|
#include "esp_coexist.h"
|
||||||
|
|
@ -57,19 +56,24 @@ void setup() {
|
||||||
g_nSecond = 0;
|
g_nSecond = 0;
|
||||||
bShowSensor = false;
|
bShowSensor = false;
|
||||||
|
|
||||||
led0.setup(PIN_LED_WIFI, PWM_AP_FREQ, PWM_AP_CHANNEL);
|
|
||||||
led0.setDuty(20);
|
|
||||||
|
|
||||||
|
DPRINTLN("1");
|
||||||
setupConfig();
|
setupConfig();
|
||||||
|
DPRINTLN("2");
|
||||||
setupStatus();
|
setupStatus();
|
||||||
|
DPRINTLN("3");
|
||||||
setupPins();
|
setupPins();
|
||||||
|
DPRINTLN("4");
|
||||||
scanI2C();
|
scanI2C();
|
||||||
|
DPRINTLN("5");
|
||||||
setupSensor();
|
setupSensor();
|
||||||
|
DPRINTLN("6");
|
||||||
ui.setup();
|
ui.setup();
|
||||||
|
DPRINTLN("7");
|
||||||
ui.message(0, (char *) "WiFi...");
|
ui.message(0, (char *) "WiFi...");
|
||||||
|
DPRINTLN("8");
|
||||||
setupWiFi();
|
setupWiFi();
|
||||||
|
DPRINTLN("9");
|
||||||
|
|
||||||
if (aht25.sensor() || aht10_0x39.sensor()) {
|
if (aht25.sensor() || aht10_0x39.sensor()) {
|
||||||
ui.message(4, (char *) "Sensor... OK!");
|
ui.message(4, (char *) "Sensor... OK!");
|
||||||
|
|
@ -88,13 +92,14 @@ void setup() {
|
||||||
// Restore Status
|
// Restore Status
|
||||||
restoreStatus();
|
restoreStatus();
|
||||||
|
|
||||||
|
|
||||||
// Create a task pinned to core 0
|
// Create a task pinned to core 0
|
||||||
xTaskCreatePinnedToCore(
|
xTaskCreatePinnedToCore(
|
||||||
core0Task, // Function to run as a task
|
core0Task, // Function to run as a task
|
||||||
"Task0", // Task name
|
"Task0", // Task name
|
||||||
10240, // Stack size in words
|
10240, // Stack size in words
|
||||||
NULL, // Task input parameter
|
NULL, // Task input parameter
|
||||||
0, // Priority of the task
|
1, // Priority of the task
|
||||||
&TaskHandle_0, // Task handle
|
&TaskHandle_0, // Task handle
|
||||||
0 // Core 0
|
0 // Core 0
|
||||||
);
|
);
|
||||||
|
|
@ -193,30 +198,70 @@ void setupPostWiFi(bool bBoot = false) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#include "driver/ledc.h"
|
||||||
|
|
||||||
void setupPins() {
|
void setupPins() {
|
||||||
|
// --- Basic Digital Pin Initialization ---
|
||||||
pinMode(PIN_HEATER1, OUTPUT);
|
pinMode(PIN_HEATER1, OUTPUT);
|
||||||
pinMode(PIN_HEATER2, OUTPUT);
|
pinMode(PIN_HEATER2, OUTPUT);
|
||||||
digitalWrite(PIN_HEATER1, HEATER_OFF);
|
digitalWrite(PIN_HEATER1, HEATER_OFF);
|
||||||
digitalWrite(PIN_HEATER2, HEATER_OFF);
|
digitalWrite(PIN_HEATER2, HEATER_OFF);
|
||||||
|
|
||||||
//ledcAttachChannel(PIN_LED_WIFI, PWM_AP_FREQ, PWM_RESOLUTION, PWM_AP_CHANNEL);
|
// --- 1. High Speed Group: Motor and Fan (~26kHz) ---
|
||||||
//ledcWrite(PIN_LED_WIFI, PWM_FULL * 4 / 5); // Light Blink
|
// Core 2.0.17 uses APB (80MHz). 80MHz / (3 * 1024) ≈ 26kHz.
|
||||||
|
ledcAttachChannel(PIN_MOTOR, PWM_26KHZ_FREQ, PWM_RESOLUTION, PWM_MOTOR_CHANNEL);
|
||||||
ledcAttachChannel(PIN_MIST, PWM_MIST_FREQ, PWM_RESOLUTION, PWM_MIST_CHANNEL);
|
ledcAttachChannel(PIN_FAN, PWM_26KHZ_FREQ, PWM_RESOLUTION, PWM_FAN_CHANNEL);
|
||||||
ledcWrite(PIN_MIST, PWM_OFF);
|
|
||||||
|
|
||||||
|
// --- 2. Medium Speed Group: LED & Heaters (1221Hz) ---
|
||||||
|
// 80MHz / (64 * 1024) = 1220.7Hz.
|
||||||
|
ledcAttachChannel(PIN_LIGHT, PWM_1KHZ_FREQ, PWM_RESOLUTION, PWM_LIGHT_CHANNEL);
|
||||||
ledcAttachChannel(PIN_LED_HEATER1, PWM_1KHZ_FREQ, PWM_RESOLUTION, PWM_HEATER1_CHANNEL);
|
ledcAttachChannel(PIN_LED_HEATER1, PWM_1KHZ_FREQ, PWM_RESOLUTION, PWM_HEATER1_CHANNEL);
|
||||||
ledcAttachChannel(PIN_LED_HEATER2, PWM_1KHZ_FREQ, PWM_RESOLUTION, PWM_HEATER2_CHANNEL);
|
ledcAttachChannel(PIN_LED_HEATER2, PWM_1KHZ_FREQ, PWM_RESOLUTION, PWM_HEATER2_CHANNEL);
|
||||||
ledcAttachChannel(PIN_LIGHT, PWM_1KHZ_FREQ, PWM_RESOLUTION, PWM_LIGHT_CHANNEL);
|
|
||||||
|
|
||||||
ledcAttachChannel(PIN_MOTOR, PWM_25KHZ_FREQ, PWM_RESOLUTION, PWM_MOTOR_CHANNEL);
|
// --- 3. Low Speed Group: Mist & WiFi LED (1.19Hz) ---
|
||||||
ledcAttachChannel(PIN_FAN, PWM_25KHZ_FREQ, PWM_RESOLUTION, PWM_FAN_CHANNEL);
|
// Initialized at 32Hz to bypass the slow divider calculation/WDT panic.
|
||||||
|
ledcAttachChannel(PIN_MIST, PWM_1HZ_FREQ, PWM_RESOLUTION, PWM_MIST_CHANNEL);
|
||||||
|
ledcAttachChannel(PIN_LED_WIFI, PWM_1HZ_FREQ, PWM_RESOLUTION, PWM_WIFI_LED_CHANNEL);
|
||||||
|
|
||||||
ledcWrite(PIN_LED_HEATER1, PWM_FULL - PWM_OFF);
|
// MANUALLY RECONFIGURE TIMER FOR 1.19Hz
|
||||||
ledcWrite(PIN_LED_HEATER2, PWM_FULL - PWM_OFF);
|
// Source: LEDC_REF_TICK (1MHz)
|
||||||
ledcWrite(PIN_LIGHT, PWM_OFF);
|
// Divider: 819 (Integer part)
|
||||||
|
// Result: 1,000,000 / (819 * 1024) ≈ 1.192 Hz
|
||||||
|
// Note: On Core 2.0.17, LEDC_USE_REF_TICK is the correct constant.
|
||||||
|
/*
|
||||||
|
ledc_timer_set(
|
||||||
|
LEDC_LOW_SPEED_MODE,
|
||||||
|
LEDC_TIMER_0,
|
||||||
|
(uint32_t)(819 << 8), // 18.8 bit register: 819 integer, 0 fractional
|
||||||
|
(uint32_t)PWM_RESOLUTION,
|
||||||
|
LEDC_REF_TICK
|
||||||
|
);
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
ledc_timer_config_t mist_timer_conf = {
|
||||||
|
.speed_mode = LEDC_LOW_SPEED_MODE,
|
||||||
|
.duty_resolution = LEDC_TIMER_8_BIT,
|
||||||
|
.timer_num = LEDC_TIMER_0,
|
||||||
|
.freq_hz = 2, // The driver will target 1Hz
|
||||||
|
.clk_cfg = LEDC_USE_REF_TICK, // Forces the 1MHz source
|
||||||
|
.deconfigure = false
|
||||||
|
};
|
||||||
|
esp_err_t err = ledc_timer_config(&mist_timer_conf);
|
||||||
|
if (err == ESP_OK) {
|
||||||
|
// Apply the change and reset the counter
|
||||||
|
ledc_timer_rst(LEDC_LOW_SPEED_MODE, LEDC_TIMER_0);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
// --- Initial State Writes ---
|
||||||
ledcWrite(PIN_MOTOR, PWM_OFF);
|
ledcWrite(PIN_MOTOR, PWM_OFF);
|
||||||
ledcWrite(PIN_FAN, PWM_OFF);
|
ledcWrite(PIN_FAN, PWM_OFF);
|
||||||
|
ledcWrite(PIN_LIGHT, PWM_OFF);
|
||||||
|
ledcWrite(PIN_MIST, PWM_OFF);
|
||||||
|
ledcWrite(PIN_LED_WIFI, (PWM_FULL * 4) / 5); // 80% Initial Blink
|
||||||
|
|
||||||
|
// Heaters (Inverted logic for MOSFET safety)
|
||||||
|
ledcWrite(PIN_LED_HEATER1, PWM_FULL - PWM_OFF);
|
||||||
|
ledcWrite(PIN_LED_HEATER2, PWM_FULL - PWM_OFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setupStatus() {
|
void setupStatus() {
|
||||||
|
|
@ -243,13 +288,15 @@ void restoreStatus() {
|
||||||
time_t now;
|
time_t now;
|
||||||
time(&now);
|
time(&now);
|
||||||
uint32_t gap = (uint32_t)now - config.statusSave.now;
|
uint32_t gap = (uint32_t)now - config.statusSave.now;
|
||||||
DPRINTF("Reboot in %.1f seconds\n", gap / 1000.0f);
|
DPRINTF("Reboot within %.1f seconds after last reset... \n", gap / 1000.0f);
|
||||||
if (gap < 60000 && config.bStatusSaved) {
|
if (gap < 60000 && config.bStatusSaved) {
|
||||||
status = config.statusSave;
|
status = config.statusSave;
|
||||||
status.nFlags |= (uint16_t)(config.statusSave.nFlags & 0xFF);
|
status.nFlags |= (uint16_t)(config.statusSave.nFlags & 0xFF);
|
||||||
status.nLightDuty = 0;
|
status.nLightDuty = 0;
|
||||||
config.bStatusSaved = false;
|
config.bStatusSaved = false;
|
||||||
DPRINTLN(" Status Restored!");
|
DPRINTLN(" Status Restored!");
|
||||||
|
} else {
|
||||||
|
DPRINTLN(" Status NOT Restored!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ void setup_BLE();
|
||||||
// ==================================================================================
|
// ==================================================================================
|
||||||
MY_IRAM_ATTR void core0Task(void *pvParameters) {
|
MY_IRAM_ATTR void core0Task(void *pvParameters) {
|
||||||
ESP_LOGI(TAG_TASK0,"Core 0 Task Started");
|
ESP_LOGI(TAG_TASK0,"Core 0 Task Started");
|
||||||
|
DPRINTLN("Core 0 Task Started");
|
||||||
wl_status_t lastWiFiStatus = WL_DISCONNECTED;
|
wl_status_t lastWiFiStatus = WL_DISCONNECTED;
|
||||||
unsigned long tickMillis = millis();
|
unsigned long tickMillis = millis();
|
||||||
unsigned long tickSecond;
|
unsigned long tickSecond;
|
||||||
|
|
|
||||||
10
UI.cpp
10
UI.cpp
|
|
@ -160,7 +160,7 @@ enum UI_ITEM {
|
||||||
ITEM_COUNT
|
ITEM_COUNT
|
||||||
};
|
};
|
||||||
|
|
||||||
char *title[] = {
|
const char *title[] = {
|
||||||
"Time:",
|
"Time:",
|
||||||
|
|
||||||
"Temp1", "Temp2", "Temp3",
|
"Temp1", "Temp2", "Temp3",
|
||||||
|
|
@ -691,7 +691,7 @@ MY_IRAM_ATTR bool CUI::displayBottom() {
|
||||||
int lastC = 0;
|
int lastC = 0;
|
||||||
char sz[32];
|
char sz[32];
|
||||||
static uint8_t bLastNTP = 0xFF;
|
static uint8_t bLastNTP = 0xFF;
|
||||||
static uint8_t bLastBLE = ~FLAG_BLE_NODATA;
|
static uint8_t bLastBLE = (uint8_t)(~FLAG_BLE_NODATA);
|
||||||
static uint8_t bLastConn = 0xFF;
|
static uint8_t bLastConn = 0xFF;
|
||||||
static uint8_t bLastWiFi = 0xFF;
|
static uint8_t bLastWiFi = 0xFF;
|
||||||
|
|
||||||
|
|
@ -1133,20 +1133,20 @@ void CUI::checkButtonStates(unsigned long currentMillis) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ISR for the Set button handling
|
// ISR for the Set button handling
|
||||||
IRAM_ATTR void buttonSetISR() {
|
ARDUINO_ISR_ATTR void buttonSetISR() {
|
||||||
// Record the time of the button interrupt and set a flag
|
// Record the time of the button interrupt and set a flag
|
||||||
ui.buttonSetChangeTime = millis();
|
ui.buttonSetChangeTime = millis();
|
||||||
ui.bButtonSetChanged = true; // Flag for main loop to process
|
ui.bButtonSetChanged = true; // Flag for main loop to process
|
||||||
}
|
}
|
||||||
|
|
||||||
// ISR for the Up button handling
|
// ISR for the Up button handling
|
||||||
IRAM_ATTR void buttonUpISR() {
|
ARDUINO_ISR_ATTR void buttonUpISR() {
|
||||||
ui.buttonUpChangeTime = millis();
|
ui.buttonUpChangeTime = millis();
|
||||||
ui.bButtonUpChanged = true; // Flag for main loop to process
|
ui.bButtonUpChanged = true; // Flag for main loop to process
|
||||||
}
|
}
|
||||||
|
|
||||||
// ISR for the Down button handling
|
// ISR for the Down button handling
|
||||||
IRAM_ATTR void buttonDownISR() {
|
ARDUINO_ISR_ATTR void buttonDownISR() {
|
||||||
ui.buttonDownChangeTime = millis();
|
ui.buttonDownChangeTime = millis();
|
||||||
ui.bButtonDownChanged = true; // Flag for main loop to process
|
ui.bButtonDownChanged = true; // Flag for main loop to process
|
||||||
}
|
}
|
||||||
48
WiFiHost.cpp
48
WiFiHost.cpp
|
|
@ -157,14 +157,16 @@ MY_IRAM_ATTR void CWiFiHost::Stop() {
|
||||||
udpExternal.stop(); // or udpExternal.end(); depending on your preference
|
udpExternal.stop(); // or udpExternal.end(); depending on your preference
|
||||||
}
|
}
|
||||||
|
|
||||||
MY_IRAM_ATTR void CWiFiHost::CloseConnection()
|
MY_IRAM_ATTR
|
||||||
|
void CWiFiHost::CloseConnection()
|
||||||
{
|
{
|
||||||
if (wifiClient && wifiClient.connected()) wifiClient.stop();
|
if (wifiClient && wifiClient.connected()) wifiClient.stop();
|
||||||
m_bClientConnected = false;
|
m_bClientConnected = false;
|
||||||
m_nMode = MODE_WAITING;
|
m_nMode = MODE_WAITING;
|
||||||
}
|
}
|
||||||
|
|
||||||
IRAM_ATTR void CWiFiHost::Loop(unsigned long clock)
|
IRAM_ATTR
|
||||||
|
void CWiFiHost::Loop(unsigned long clock)
|
||||||
{
|
{
|
||||||
if (!isWiFiConnected()) return;
|
if (!isWiFiConnected()) return;
|
||||||
|
|
||||||
|
|
@ -250,7 +252,8 @@ IRAM_ATTR void CWiFiHost::Loop(unsigned long clock)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MY_IRAM_ATTR void CWiFiHost::SendHeartBeat(unsigned long clock) {
|
MY_IRAM_ATTR
|
||||||
|
void CWiFiHost::SendHeartBeat(unsigned long clock) {
|
||||||
if (!isWiFiConnected()) {
|
if (!isWiFiConnected()) {
|
||||||
//ESP_LOGI(TAG_WIFI_HOST,"WiFiHost - SendHeartBeat() called while not connected!");
|
//ESP_LOGI(TAG_WIFI_HOST,"WiFiHost - SendHeartBeat() called while not connected!");
|
||||||
return;
|
return;
|
||||||
|
|
@ -302,7 +305,8 @@ MY_IRAM_ATTR void CWiFiHost::SendHeartBeat(unsigned long clock) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MY_IRAM_ATTR void CWiFiHost::MonitorUDP() {
|
MY_IRAM_ATTR
|
||||||
|
void CWiFiHost::MonitorUDP() {
|
||||||
// Listen for UDP External port
|
// Listen for UDP External port
|
||||||
IPAddress ip;
|
IPAddress ip;
|
||||||
int size = udpExternal.parsePacket();
|
int size = udpExternal.parsePacket();
|
||||||
|
|
@ -355,7 +359,8 @@ MY_IRAM_ATTR void CWiFiHost::MonitorUDP() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IRAM_ATTR void CWiFiHost::CheckClient(unsigned long clock)
|
IRAM_ATTR
|
||||||
|
void CWiFiHost::CheckClient(unsigned long clock)
|
||||||
{
|
{
|
||||||
// 1. Quick exit if no data is ready to be read
|
// 1. Quick exit if no data is ready to be read
|
||||||
int available = wifiClient.available();
|
int available = wifiClient.available();
|
||||||
|
|
@ -386,7 +391,8 @@ IRAM_ATTR void CWiFiHost::CheckClient(unsigned long clock)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IRAM_ATTR void CWiFiHost::ProcessPacket(TCP_PACKET& pkt)
|
IRAM_ATTR
|
||||||
|
void CWiFiHost::ProcessPacket(TCP_PACKET& pkt)
|
||||||
{
|
{
|
||||||
switch (pkt.cmd)
|
switch (pkt.cmd)
|
||||||
{
|
{
|
||||||
|
|
@ -463,10 +469,6 @@ IRAM_ATTR void CWiFiHost::ProcessPacket(TCP_PACKET& pkt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case CMD_PAUSE:
|
|
||||||
break;
|
|
||||||
case CMD_RESUME:
|
|
||||||
break;
|
|
||||||
case CMD_RESET_SENSOR:
|
case CMD_RESET_SENSOR:
|
||||||
aht25.setScanFlag(true);
|
aht25.setScanFlag(true);
|
||||||
aht10_0x39.setScanFlag(true);
|
aht10_0x39.setScanFlag(true);
|
||||||
|
|
@ -515,11 +517,6 @@ IRAM_ATTR void CWiFiHost::ProcessPacket(TCP_PACKET& pkt)
|
||||||
ESP_LOGI(TAG_WIFI_HOST,"WiFi - Send Config initiated...");
|
ESP_LOGI(TAG_WIFI_HOST,"WiFi - Send Config initiated...");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case CMD_SEND_CONFIG_SERVER:
|
|
||||||
|
|
||||||
case CMD_LOAD_CONFIG_SERVER:
|
|
||||||
break;
|
|
||||||
|
|
||||||
// PID
|
// PID
|
||||||
case CMD_INIT_PID_PARAM:
|
case CMD_INIT_PID_PARAM:
|
||||||
history.savePID();
|
history.savePID();
|
||||||
|
|
@ -713,7 +710,8 @@ IRAM_ATTR void CWiFiHost::ProcessPacket(TCP_PACKET& pkt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IRAM_ATTR int CWiFiHost::SendPacket(TCP_PACKET& pkt)
|
IRAM_ATTR
|
||||||
|
int CWiFiHost::SendPacket(TCP_PACKET& pkt)
|
||||||
{
|
{
|
||||||
size_t sent = 0;
|
size_t sent = 0;
|
||||||
if (m_bClientConnected && wifiClient && wifiClient.connected())
|
if (m_bClientConnected && wifiClient && wifiClient.connected())
|
||||||
|
|
@ -723,7 +721,8 @@ IRAM_ATTR int CWiFiHost::SendPacket(TCP_PACKET& pkt)
|
||||||
return sent;
|
return sent;
|
||||||
}
|
}
|
||||||
|
|
||||||
IRAM_ATTR size_t CWiFiHost::SendData(const uint8_t* data, size_t size) {
|
IRAM_ATTR
|
||||||
|
size_t CWiFiHost::SendData(const uint8_t* data, size_t size) {
|
||||||
if (data != nullptr) {
|
if (data != nullptr) {
|
||||||
m_nMode = MODE_SEND;
|
m_nMode = MODE_SEND;
|
||||||
m_pDataSend_data = (char *) data;
|
m_pDataSend_data = (char *) data;
|
||||||
|
|
@ -735,7 +734,8 @@ IRAM_ATTR size_t CWiFiHost::SendData(const uint8_t* data, size_t size) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
IRAM_ATTR // This function sends a small "chunk" then returns false.
|
IRAM_ATTR
|
||||||
|
// This function sends a small "chunk" then returns false.
|
||||||
// It returns true ONLY when the entire buffer is finished.
|
// It returns true ONLY when the entire buffer is finished.
|
||||||
bool CWiFiHost::SendData(unsigned long clock)
|
bool CWiFiHost::SendData(unsigned long clock)
|
||||||
{
|
{
|
||||||
|
|
@ -767,7 +767,8 @@ bool CWiFiHost::SendData(unsigned long clock)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
IRAM_ATTR size_t CWiFiHost::ReceiveData(uint8_t* data, size_t size)
|
IRAM_ATTR
|
||||||
|
size_t CWiFiHost::ReceiveData(uint8_t* data, size_t size)
|
||||||
{
|
{
|
||||||
m_nMode = MODE_RECV;
|
m_nMode = MODE_RECV;
|
||||||
m_pDataReceive_data = (char *) data;
|
m_pDataReceive_data = (char *) data;
|
||||||
|
|
@ -779,7 +780,8 @@ IRAM_ATTR size_t CWiFiHost::ReceiveData(uint8_t* data, size_t size)
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
IRAM_ATTR bool CWiFiHost::ReceiveData(unsigned long clock)
|
IRAM_ATTR
|
||||||
|
bool CWiFiHost::ReceiveData(unsigned long clock)
|
||||||
{
|
{
|
||||||
// Receive Data
|
// Receive Data
|
||||||
size_t nSize = 0;
|
size_t nSize = 0;
|
||||||
|
|
@ -822,7 +824,8 @@ IRAM_ATTR bool CWiFiHost::ReceiveData(unsigned long clock)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
IRAM_ATTR void CWiFiHost::SendHeartBeat() {
|
IRAM_ATTR
|
||||||
|
void CWiFiHost::SendHeartBeat() {
|
||||||
if (m_bHelloSent) {
|
if (m_bHelloSent) {
|
||||||
hostPacket.cmd = CMD_HEARTBEAT;
|
hostPacket.cmd = CMD_HEARTBEAT;
|
||||||
time_t now = time(NULL); // Get current time in seconds
|
time_t now = time(NULL); // Get current time in seconds
|
||||||
|
|
@ -833,7 +836,8 @@ IRAM_ATTR void CWiFiHost::SendHeartBeat() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MY_IRAM_ATTR void CWiFiHost::Restart() {
|
MY_IRAM_ATTR
|
||||||
|
void CWiFiHost::Restart() {
|
||||||
if (isWiFiConnected()) {
|
if (isWiFiConnected()) {
|
||||||
if (m_bClientConnected && m_bClientConnected && wifiClient && wifiClient.connected()) {
|
if (m_bClientConnected && m_bClientConnected && wifiClient && wifiClient.connected()) {
|
||||||
hostPacket.cmd = CMD_DROP_CONNECTION;
|
hostPacket.cmd = CMD_DROP_CONNECTION;
|
||||||
|
|
|
||||||
|
|
@ -271,7 +271,10 @@ private:
|
||||||
volatile bool m_bClientConnected;
|
volatile bool m_bClientConnected;
|
||||||
//uint32_t m_dwPublicIP;
|
//uint32_t m_dwPublicIP;
|
||||||
//uint16_t m_nPublicPort;
|
//uint16_t m_nPublicPort;
|
||||||
|
|
||||||
|
// Send Status to VisionSoft HeartBeat service
|
||||||
IPAddress m_cExternalServerIPAddress;
|
IPAddress m_cExternalServerIPAddress;
|
||||||
|
|
||||||
WiFiUDP udpLocal, udpExternal;
|
WiFiUDP udpLocal, udpExternal;
|
||||||
WiFiServer wifiServer; //, wifiExternal;
|
WiFiServer wifiServer; //, wifiExternal;
|
||||||
WiFiClient wifiClient;
|
WiFiClient wifiClient;
|
||||||
|
|
|
||||||
4
zcd.cpp
4
zcd.cpp
|
|
@ -40,7 +40,7 @@ short getHeater2Duty() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to set the duty based on percentage (0 to 10000)
|
// Function to set the duty based on percentage (0 to 10000)
|
||||||
IRAM_ATTR void setHeater1Duty(short duty) {
|
ARDUINO_ISR_ATTR void setHeater1Duty(short duty) {
|
||||||
if (duty <= 0) {
|
if (duty <= 0) {
|
||||||
dutyHeater1 = 0; // If 0% duty, no pulse (turn off TRIAC)
|
dutyHeater1 = 0; // If 0% duty, no pulse (turn off TRIAC)
|
||||||
} else if (duty >= 10000) {
|
} else if (duty >= 10000) {
|
||||||
|
|
@ -66,7 +66,7 @@ IRAM_ATTR void setHeater1Duty(short duty) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to set the duty based on percentage (0 to 10000)
|
// Function to set the duty based on percentage (0 to 10000)
|
||||||
IRAM_ATTR void setHeater2Duty(short duty) {
|
ARDUINO_ISR_ATTR void setHeater2Duty(short duty) {
|
||||||
if (config.bAC2_OnOff) {
|
if (config.bAC2_OnOff) {
|
||||||
if (duty >= 10000) {
|
if (duty >= 10000) {
|
||||||
digitalWrite(PIN_HEATER2, HEATER_ON);
|
digitalWrite(PIN_HEATER2, HEATER_ON);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user