diff --git a/ConnectWiFi.cpp b/ConnectWiFi.cpp
index dbf5453..8480caa 100644
--- a/ConnectWiFi.cpp
+++ b/ConnectWiFi.cpp
@@ -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;
diff --git a/HermitCrab.h b/HermitCrab.h
index 8bfb40f..691e510 100644
--- a/HermitCrab.h
+++ b/HermitCrab.h
@@ -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 {
diff --git a/LED0.cpp b/LED0.cpp
index 5bddf47..44a568d 100644
--- a/LED0.cpp
+++ b/LED0.cpp
@@ -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
diff --git a/Libraries/Adafruit_SSD1306.cpp b/Libraries/Adafruit_SSD1306.cpp
new file mode 100644
index 0000000..93e17fc
--- /dev/null
+++ b/Libraries/Adafruit_SSD1306.cpp
@@ -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 Adafruit_GFX
+ * 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
+#include "Adafruit_SSD1306.h"
+#include
+
+// 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
+}
\ No newline at end of file
diff --git a/Libraries/Adafruit_SSD1306.h b/Libraries/Adafruit_SSD1306.h
new file mode 100644
index 0000000..78f4295
--- /dev/null
+++ b/Libraries/Adafruit_SSD1306.h
@@ -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
+#include
+
+/// 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_
\ No newline at end of file
diff --git a/Libraries/NetworkClient.cpp b/Libraries/NetworkClient.cpp
new file mode 100644
index 0000000..0782b74
--- /dev/null
+++ b/Libraries/NetworkClient.cpp
@@ -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
+#include
+#include
+
+#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;
+}
diff --git a/Libraries/NetworkClient.h b/Libraries/NetworkClient.h
new file mode 100644
index 0000000..bdf857e
--- /dev/null
+++ b/Libraries/NetworkClient.h
@@ -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
+
+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 clientSocketHandle;
+ std::shared_ptr _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;
+};
diff --git a/Libraries/NimBLE-Arduino_modified/NimBLEAdvertisedDevice.cpp b/Libraries/NimBLE-Arduino_modified/NimBLEAdvertisedDevice.cpp
new file mode 100644
index 0000000..17501c3
--- /dev/null
+++ b/Libraries/NimBLE-Arduino_modified/NimBLEAdvertisedDevice.cpp
@@ -0,0 +1,828 @@
+/*
+ * Copyright 2020-2024 Ryan Powell 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
+
+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(&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(&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(&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(&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(&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(&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(&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(&m_payload[data_loc]);
+ if (field->length > bytes) {
+ const char* field_data = reinterpret_cast(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(&m_payload[data_loc]);
+ if (field->length > bytes) {
+ *len = field->length - bytes - 1;
+ return const_cast(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(&m_payload[data_loc]);
+ if (bytes == uuid_bytes && NimBLEUUID(field->value, bytes) == uuid) {
+ const char* field_data = reinterpret_cast(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(&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(&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(&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(&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(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& 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::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::const_iterator NimBLEAdvertisedDevice::end() const {
+ return m_payload.cend();
+}
+
+#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_CENTRAL */
diff --git a/Libraries/NimBLE-Arduino_modified/NimBLEAdvertisedDevice.h b/Libraries/NimBLE-Arduino_modified/NimBLEAdvertisedDevice.h
new file mode 100644
index 0000000..79955ef
--- /dev/null
+++ b/Libraries/NimBLE-Arduino_modified/NimBLEAdvertisedDevice.h
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2020-2024 Ryan Powell 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
+
+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& getPayload() const;
+ const std::vector::const_iterator begin() const;
+ const std::vector::const_iterator end() const;
+
+ /**
+ * @brief A template to convert the service data to .
+ * @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 sizeof().
+ * @return The data converted to or NULL if skipSizeCheck is false and the data is
+ * less than sizeof().
+ * @details Use: getManufacturerData(skipSizeCheck);
+ */
+ template
+ 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 .
+ * @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 sizeof().
+ * @return The data converted to or NULL if skipSizeCheck is false and the data is
+ * less than sizeof().
+ * @details Use: getServiceData(skipSizeCheck);
+ */
+ template
+ 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 .
+ * @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 sizeof().
+ * @return The data converted to or NULL if skipSizeCheck is false and the data is
+ * less than sizeof().
+ * @details Use: getServiceData(skipSizeCheck);
+ */
+ template
+ 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 .
+ * @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 sizeof().
+ * @return The data converted to or a default-constructed if size check fails.
+ * @details Usage: getServiceData(uuid, skipSizeCheck);
+ */
+ template
+ T getServiceData(uint16_t *len) const {
+ uint8_t* data = getServiceData(len);
+
+ if (data == nullptr) {
+ len = 0;
+ return T();
+ }
+ return *reinterpret_cast(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 m_payload;
+};
+
+#endif /* CONFIG_BT_ENABLED && CONFIG_BT_NIMBLE_ROLE_OBSERVER */
+#endif /* NIMBLE_CPP_ADVERTISED_DEVICE_H_ */
diff --git a/Libraries/NimBLE-Arduino_modified/NimBLEScan.cpp b/Libraries/NimBLE-Arduino_modified/NimBLEScan.cpp
new file mode 100644
index 0000000..bda0936
--- /dev/null
+++ b/Libraries/NimBLE-Arduino_modified/NimBLEScan.cpp
@@ -0,0 +1,625 @@
+/*
+ * Copyright 2020-2024 Ryan Powell 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
+# include
+
+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 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::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::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 */
diff --git a/Libraries/NimBLE-Arduino_modified/NimBLEScan.h b/Libraries/NimBLE-Arduino_modified/NimBLEScan.h
new file mode 100644
index 0000000..81cf7b0
--- /dev/null
+++ b/Libraries/NimBLE-Arduino_modified/NimBLEScan.h
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2020-2024 Ryan Powell 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
+
+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::const_iterator begin() const;
+ std::vector::const_iterator end() const;
+
+ private:
+ friend NimBLEScan;
+ std::vector 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_
diff --git a/OTA.cpp b/OTA.cpp
index 3fe0b37..4ad6ff4 100644
--- a/OTA.cpp
+++ b/OTA.cpp
@@ -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";
diff --git a/README.md b/README.md
index fc1c95b..5a53371 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,3 @@
-dependancy: NimBLE-Arduino library, maintained by h2zero (Ryan Powell)
\ No newline at end of file
+dependancy: NimBLE-Arduino library, maintained by h2zero (Ryan Powell)
+requires Arduino core 3.0.2
\ No newline at end of file
diff --git a/Setup.cpp b/Setup.cpp
index 28e93a8..ea4f8c4 100644
--- a/Setup.cpp
+++ b/Setup.cpp
@@ -9,7 +9,6 @@
#include "TimeManager.h"
#include "OTA.h"
#include "UI.h"
-#include "LED0.h"
#include "BLEScan.h"
#include
#include "esp_coexist.h"
@@ -36,69 +35,75 @@ void setup_BLE();
void scanI2C();
void setup() {
- // put your setup code here, to run once:
- #ifdef DEBUG
- Serial.begin(115200);
- #endif
- //esp_log_level_set("*", ESP_LOG_INFO); // Global log level
- //esp_log_level_set("BLE_POLL", ESP_LOG_INFO); // Module-specific level
- //esp_coex_preference_set(ESP_COEX_PREFER_BT);
+ // put your setup code here, to run once:
+ #ifdef DEBUG
+ Serial.begin(115200);
+ #endif
+ //esp_log_level_set("*", ESP_LOG_INFO); // Global log level
+ //esp_log_level_set("BLE_POLL", ESP_LOG_INFO); // Module-specific level
+ //esp_coex_preference_set(ESP_COEX_PREFER_BT);
- DPRINTLN(" **********************");
- DPRINTF(" SETUP - Start - ver. %s type: %d\n", HC__VERSION, THIS_DEVICE_TYPE);
- DPRINTLN(" **********************");
- g_bWiFiHasBeenConnected = false;
- g_bWiFiHasBeenConnected = false;
- g_nYear = 2024;
- g_nMonth = 10;
- g_nDay = 15;
- g_nHour = 0;
- g_nMinute = 0;
- g_nSecond = 0;
- bShowSensor = false;
+ DPRINTLN(" **********************");
+ DPRINTF(" SETUP - Start - ver. %s type: %d\n", HC__VERSION, THIS_DEVICE_TYPE);
+ DPRINTLN(" **********************");
+ g_bWiFiHasBeenConnected = false;
+ g_bWiFiHasBeenConnected = false;
+ g_nYear = 2024;
+ g_nMonth = 10;
+ g_nDay = 15;
+ g_nHour = 0;
+ g_nMinute = 0;
+ g_nSecond = 0;
+ bShowSensor = false;
- led0.setup(PIN_LED_WIFI, PWM_AP_FREQ, PWM_AP_CHANNEL);
- led0.setDuty(20);
- setupConfig();
- setupStatus();
-
- setupPins();
- scanI2C();
- setupSensor();
+ 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");
- ui.setup();
- ui.message(0, (char *) "WiFi...");
- setupWiFi();
-
- if (aht25.sensor() || aht10_0x39.sensor()) {
+ if (aht25.sensor() || aht10_0x39.sensor()) {
ui.message(4, (char *) "Sensor... OK!");
- } else {
+ } else {
ui.message(4, (char *) "Sensor... None!");
- }
+ }
- ui.message(5, (char *) "ZCD...");
- setupZCD();
- ui.message(5, (char *) "ZCD... OK!");
+ ui.message(5, (char *) "ZCD...");
+ setupZCD();
+ ui.message(5, (char *) "ZCD... OK!");
- ui.message(6, (char *) "Setup OK!");
- //if (!isWiFiConnected) delay(3000);
- ble.setupConnect(config.nBLESensorAddr, config.nBLESensorAddr2);
-
- // Restore Status
- restoreStatus();
+ ui.message(6, (char *) "Setup OK!");
+ //if (!isWiFiConnected) delay(3000);
+ ble.setupConnect(config.nBLESensorAddr, config.nBLESensorAddr2);
- // Create a task pinned to core 0
- xTaskCreatePinnedToCore(
+ // 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
- );
- DPRINTLN("Setup Completed\n========================\n");
+ );
+ DPRINTLN("Setup Completed\n========================\n");
}
@@ -193,30 +198,70 @@ void setupPostWiFi(bool bBoot = false) {
}
}
+#include "driver/ledc.h"
+
void setupPins() {
- pinMode(PIN_HEATER1, OUTPUT);
- pinMode(PIN_HEATER2, OUTPUT);
- digitalWrite(PIN_HEATER1, HEATER_OFF);
- digitalWrite(PIN_HEATER2, HEATER_OFF);
+ // --- 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
+ // --- 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);
- ledcAttachChannel(PIN_MIST, PWM_MIST_FREQ, PWM_RESOLUTION, PWM_MIST_CHANNEL);
- ledcWrite(PIN_MIST, PWM_OFF);
+ // --- 2. Medium Speed Group: LED & Heaters (1221Hz) ---
+ // 80MHz / (64 * 1024) = 1220.7Hz.
+ ledcAttachChannel(PIN_LIGHT, PWM_1KHZ_FREQ, PWM_RESOLUTION, PWM_LIGHT_CHANNEL);
+ ledcAttachChannel(PIN_LED_HEATER1, PWM_1KHZ_FREQ, PWM_RESOLUTION, PWM_HEATER1_CHANNEL);
+ ledcAttachChannel(PIN_LED_HEATER2, PWM_1KHZ_FREQ, PWM_RESOLUTION, PWM_HEATER2_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);
+ // --- 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);
- ledcAttachChannel(PIN_MOTOR, PWM_25KHZ_FREQ, PWM_RESOLUTION, PWM_MOTOR_CHANNEL);
- ledcAttachChannel(PIN_FAN, PWM_25KHZ_FREQ, PWM_RESOLUTION, PWM_FAN_CHANNEL);
-
- ledcWrite(PIN_LED_HEATER1, PWM_FULL - PWM_OFF);
- ledcWrite(PIN_LED_HEATER2, PWM_FULL - PWM_OFF);
- ledcWrite(PIN_LIGHT, PWM_OFF);
- ledcWrite(PIN_MOTOR, PWM_OFF);
- ledcWrite(PIN_FAN, 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!");
}
}
}
diff --git a/Task0.ino b/Task0.ino
index a9f9d03..ea97810 100644
--- a/Task0.ino
+++ b/Task0.ino
@@ -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;
diff --git a/UI.cpp b/UI.cpp
index 9b790d3..601e008 100644
--- a/UI.cpp
+++ b/UI.cpp
@@ -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
}
\ No newline at end of file
diff --git a/WiFiHost.cpp b/WiFiHost.cpp
index fb186b4..1cf9ecf 100644
--- a/WiFiHost.cpp
+++ b/WiFiHost.cpp
@@ -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;
diff --git a/WiFiHost.h b/WiFiHost.h
index e651e70..0dd96e8 100644
--- a/WiFiHost.h
+++ b/WiFiHost.h
@@ -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;
diff --git a/zcd.cpp b/zcd.cpp
index e33bc89..ed3c17a 100644
--- a/zcd.cpp
+++ b/zcd.cpp
@@ -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);