Req. Arduino Core 3.0.2

This commit is contained in:
Heuideog Yi @ PC RnD1 2026-04-14 05:11:09 +09:00
parent 21f4d6c054
commit 825e48086b
19 changed files with 3899 additions and 124 deletions

View File

@ -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 bool bConnecting = false;

View File

@ -20,7 +20,7 @@
#ifndef DEBUG
#define DEBUG 1 // Set to 0 to disable debug output
#endif
#undef DEBUG
//#define BLE_DEBUG
#ifdef BLE_DEBUG
@ -96,28 +96,27 @@
// End of PIN Definition
// =======================
// Resolution for all Channels
#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_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"
enum EVENT_TYPE {

View File

@ -15,7 +15,7 @@ void CLED0::setup(uint8_t _pin, uint16_t _freq, uint16_t _channel) {
setDuty(duty);
};
MY_IRAM_ATTR void CLED0::setFreq(uint16_t _freq) {
void CLED0::setFreq(uint16_t _freq) {
if (freq != _freq) {
if (_freq == 0) {
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;
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
}
MY_IRAM_ATTR void CLED0::setDuty(uint16_t _duty) {
void CLED0::setDuty(uint16_t _duty) {
if (duty != _duty) {
if (bPWMMode)
ledcWrite(PIN_LED_WIFI, PWM_FULL * (100 - _duty) / 100); // Light Blink

View 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
}

View 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
View 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
View 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;
};

View 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 */

View 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_ */

View 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 */

View 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_

View File

@ -17,7 +17,7 @@
// OTA
//
// ==============================================================
const char *HC__VERSION = "20250405001";
const char *HC__VERSION = "20260413001";
#define UPDATE_PORT ((uint16_t) 443)
String url = "visionsoft.kr";
String uri = "/hc/hc_firmware_update.php";

View File

@ -1,2 +1,3 @@

dependancy: NimBLE-Arduino library, maintained by h2zero (Ryan Powell)
requires Arduino core 3.0.2

View File

@ -9,7 +9,6 @@
#include "TimeManager.h"
#include "OTA.h"
#include "UI.h"
#include "LED0.h"
#include "BLEScan.h"
#include <esp_wifi.h>
#include "esp_coexist.h"
@ -57,19 +56,24 @@ void setup() {
g_nSecond = 0;
bShowSensor = false;
led0.setup(PIN_LED_WIFI, PWM_AP_FREQ, PWM_AP_CHANNEL);
led0.setDuty(20);
DPRINTLN("1");
setupConfig();
DPRINTLN("2");
setupStatus();
DPRINTLN("3");
setupPins();
DPRINTLN("4");
scanI2C();
DPRINTLN("5");
setupSensor();
DPRINTLN("6");
ui.setup();
DPRINTLN("7");
ui.message(0, (char *) "WiFi...");
DPRINTLN("8");
setupWiFi();
DPRINTLN("9");
if (aht25.sensor() || aht10_0x39.sensor()) {
ui.message(4, (char *) "Sensor... OK!");
@ -88,13 +92,14 @@ void setup() {
// Restore Status
restoreStatus();
// Create a task pinned to core 0
xTaskCreatePinnedToCore(
core0Task, // Function to run as a task
"Task0", // Task name
10240, // Stack size in words
NULL, // Task input parameter
0, // Priority of the task
1, // Priority of the task
&TaskHandle_0, // Task handle
0 // Core 0
);
@ -193,30 +198,70 @@ void setupPostWiFi(bool bBoot = false) {
}
}
#include "driver/ledc.h"
void setupPins() {
// --- Basic Digital Pin Initialization ---
pinMode(PIN_HEATER1, OUTPUT);
pinMode(PIN_HEATER2, OUTPUT);
digitalWrite(PIN_HEATER1, HEATER_OFF);
digitalWrite(PIN_HEATER2, HEATER_OFF);
//ledcAttachChannel(PIN_LED_WIFI, PWM_AP_FREQ, PWM_RESOLUTION, PWM_AP_CHANNEL);
//ledcWrite(PIN_LED_WIFI, PWM_FULL * 4 / 5); // Light Blink
ledcAttachChannel(PIN_MIST, PWM_MIST_FREQ, PWM_RESOLUTION, PWM_MIST_CHANNEL);
ledcWrite(PIN_MIST, PWM_OFF);
// --- 1. High Speed Group: Motor and Fan (~26kHz) ---
// Core 2.0.17 uses APB (80MHz). 80MHz / (3 * 1024) ≈ 26kHz.
ledcAttachChannel(PIN_MOTOR, PWM_26KHZ_FREQ, PWM_RESOLUTION, PWM_MOTOR_CHANNEL);
ledcAttachChannel(PIN_FAN, PWM_26KHZ_FREQ, PWM_RESOLUTION, PWM_FAN_CHANNEL);
// --- 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_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);
ledcAttachChannel(PIN_FAN, PWM_25KHZ_FREQ, PWM_RESOLUTION, PWM_FAN_CHANNEL);
// --- 3. Low Speed Group: Mist & WiFi LED (1.19Hz) ---
// 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);
ledcWrite(PIN_LED_HEATER2, PWM_FULL - PWM_OFF);
ledcWrite(PIN_LIGHT, PWM_OFF);
// MANUALLY RECONFIGURE TIMER FOR 1.19Hz
// Source: LEDC_REF_TICK (1MHz)
// 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_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() {
@ -243,13 +288,15 @@ void restoreStatus() {
time_t now;
time(&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) {
status = config.statusSave;
status.nFlags |= (uint16_t)(config.statusSave.nFlags & 0xFF);
status.nLightDuty = 0;
config.bStatusSaved = false;
DPRINTLN(" Status Restored!");
} else {
DPRINTLN(" Status NOT Restored!");
}
}
}

View File

@ -23,6 +23,7 @@ void setup_BLE();
// ==================================================================================
MY_IRAM_ATTR void core0Task(void *pvParameters) {
ESP_LOGI(TAG_TASK0,"Core 0 Task Started");
DPRINTLN("Core 0 Task Started");
wl_status_t lastWiFiStatus = WL_DISCONNECTED;
unsigned long tickMillis = millis();
unsigned long tickSecond;

10
UI.cpp
View File

@ -160,7 +160,7 @@ enum UI_ITEM {
ITEM_COUNT
};
char *title[] = {
const char *title[] = {
"Time:",
"Temp1", "Temp2", "Temp3",
@ -691,7 +691,7 @@ MY_IRAM_ATTR bool CUI::displayBottom() {
int lastC = 0;
char sz[32];
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 bLastWiFi = 0xFF;
@ -1133,20 +1133,20 @@ void CUI::checkButtonStates(unsigned long currentMillis) {
}
// 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
ui.buttonSetChangeTime = millis();
ui.bButtonSetChanged = true; // Flag for main loop to process
}
// ISR for the Up button handling
IRAM_ATTR void buttonUpISR() {
ARDUINO_ISR_ATTR void buttonUpISR() {
ui.buttonUpChangeTime = millis();
ui.bButtonUpChanged = true; // Flag for main loop to process
}
// ISR for the Down button handling
IRAM_ATTR void buttonDownISR() {
ARDUINO_ISR_ATTR void buttonDownISR() {
ui.buttonDownChangeTime = millis();
ui.bButtonDownChanged = true; // Flag for main loop to process
}

View File

@ -157,14 +157,16 @@ MY_IRAM_ATTR void CWiFiHost::Stop() {
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();
m_bClientConnected = false;
m_nMode = MODE_WAITING;
}
IRAM_ATTR void CWiFiHost::Loop(unsigned long clock)
IRAM_ATTR
void CWiFiHost::Loop(unsigned long clock)
{
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()) {
//ESP_LOGI(TAG_WIFI_HOST,"WiFiHost - SendHeartBeat() called while not connected!");
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
IPAddress ip;
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
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)
{
@ -463,10 +469,6 @@ IRAM_ATTR void CWiFiHost::ProcessPacket(TCP_PACKET& pkt)
}
}
break;
case CMD_PAUSE:
break;
case CMD_RESUME:
break;
case CMD_RESET_SENSOR:
aht25.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...");
}
break;
case CMD_SEND_CONFIG_SERVER:
case CMD_LOAD_CONFIG_SERVER:
break;
// PID
case CMD_INIT_PID_PARAM:
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;
if (m_bClientConnected && wifiClient && wifiClient.connected())
@ -723,7 +721,8 @@ IRAM_ATTR int CWiFiHost::SendPacket(TCP_PACKET& pkt)
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) {
m_nMode = MODE_SEND;
m_pDataSend_data = (char *) data;
@ -735,7 +734,8 @@ IRAM_ATTR size_t CWiFiHost::SendData(const uint8_t* data, size_t size) {
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.
bool CWiFiHost::SendData(unsigned long clock)
{
@ -767,7 +767,8 @@ bool CWiFiHost::SendData(unsigned long clock)
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_pDataReceive_data = (char *) data;
@ -779,7 +780,8 @@ IRAM_ATTR size_t CWiFiHost::ReceiveData(uint8_t* data, size_t size)
return size;
}
IRAM_ATTR bool CWiFiHost::ReceiveData(unsigned long clock)
IRAM_ATTR
bool CWiFiHost::ReceiveData(unsigned long clock)
{
// Receive Data
size_t nSize = 0;
@ -822,7 +824,8 @@ IRAM_ATTR bool CWiFiHost::ReceiveData(unsigned long clock)
return false;
}
IRAM_ATTR void CWiFiHost::SendHeartBeat() {
IRAM_ATTR
void CWiFiHost::SendHeartBeat() {
if (m_bHelloSent) {
hostPacket.cmd = CMD_HEARTBEAT;
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 (m_bClientConnected && m_bClientConnected && wifiClient && wifiClient.connected()) {
hostPacket.cmd = CMD_DROP_CONNECTION;

View File

@ -271,7 +271,10 @@ private:
volatile bool m_bClientConnected;
//uint32_t m_dwPublicIP;
//uint16_t m_nPublicPort;
// Send Status to VisionSoft HeartBeat service
IPAddress m_cExternalServerIPAddress;
WiFiUDP udpLocal, udpExternal;
WiFiServer wifiServer; //, wifiExternal;
WiFiClient wifiClient;

View File

@ -40,7 +40,7 @@ short getHeater2Duty() {
}
// 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) {
dutyHeater1 = 0; // If 0% duty, no pulse (turn off TRIAC)
} 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)
IRAM_ATTR void setHeater2Duty(short duty) {
ARDUINO_ISR_ATTR void setHeater2Duty(short duty) {
if (config.bAC2_OnOff) {
if (duty >= 10000) {
digitalWrite(PIN_HEATER2, HEATER_ON);