From 50698ad10e7bdd476fbbf787b64c1da89c59da67 Mon Sep 17 00:00:00 2001
From: tonyt321 <34804458+tonyt321@users.noreply.github.com>
Date: Tue, 6 Sep 2022 19:38:10 -0400
Subject: [PATCH 01/11] Update README.md
---
README.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/README.md b/README.md
index 31838f1..be9a427 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,8 @@
# OwieWatcher
+
+todo
+- use the u8g2 library better display support
+
A simple ESP8266 based sketch to connect to an [owie](https://github.com/lolwheel/Owie) device and pull the battery info from the status webpage for output to an OLED display. The current code configuration outputs the Voltage, BMS SOC & OVERRIDEN SOC.
## Demo
From 253d8a0a7b391f92fd60a5bfd1c6636aa55b6bef Mon Sep 17 00:00:00 2001
From: tonyt321 <34804458+tonyt321@users.noreply.github.com>
Date: Tue, 6 Sep 2022 19:42:11 -0400
Subject: [PATCH 02/11] Update README.md
---
README.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index be9a427..4263974 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,10 @@
# OwieWatcher
+[](https://gitpod.io/#https://github.com/lolwheel/Owie)
todo
- use the u8g2 library better display support
-A simple ESP8266 based sketch to connect to an [owie](https://github.com/lolwheel/Owie) device and pull the battery info from the status webpage for output to an OLED display. The current code configuration outputs the Voltage, BMS SOC & OVERRIDEN SOC.
+A simple ESP8266 based sketch to connect to an [owie](https://github.com/tonyt321/OWIE-OLED) device and pull the battery info from the status webpage for output to an OLED display. The current code configuration outputs the Voltage, BMS SOC & OVERRIDEN SOC.
## Demo
OwieWatcher bootup
From abcddd014ecd7df4423fc6125e03299b8b56973a Mon Sep 17 00:00:00 2001
From: tonyt321 <34804458+tonyt321@users.noreply.github.com>
Date: Tue, 6 Sep 2022 19:46:14 -0400
Subject: [PATCH 03/11] Add files via upload
---
LICENSE | 21 ++++
pio_tools/gen_data.py | 74 +++++++++++++
pio_tools/platformio_upload.py | 53 +++++++++
platformio.ini | 53 +++++++++
proto/settings.options | 14 +++
proto/settings.proto | 26 +++++
src/owie-watcher.ino | 192 +++++++++++++++++++++++++++++++++
7 files changed, 433 insertions(+)
create mode 100644 LICENSE
create mode 100644 pio_tools/gen_data.py
create mode 100644 pio_tools/platformio_upload.py
create mode 100644 platformio.ini
create mode 100644 proto/settings.options
create mode 100644 proto/settings.proto
create mode 100644 src/owie-watcher.ino
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..bb53b93
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 lolwheel
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/pio_tools/gen_data.py b/pio_tools/gen_data.py
new file mode 100644
index 0000000..5e55c4b
--- /dev/null
+++ b/pio_tools/gen_data.py
@@ -0,0 +1,74 @@
+Import("env")
+import os
+import subprocess
+
+from SCons.Script import COMMAND_LINE_TARGETS
+
+if "idedata" in COMMAND_LINE_TARGETS:
+ env.Exit(0)
+
+
+def ReadAndMaybeMinifyFiles(fullPath):
+ _, extension = os.path.splitext(fullPath)
+ if not extension in ['.html', '.js', '.css']:
+ with open(fullPath, "rb") as f:
+ return f.read()
+ originalSize = os.stat(fullPath).st_size
+ result = subprocess.run(['html-minifier-terser',
+ '--collapse-whitespace',
+ '--remove-comments',
+ '--minify-js',
+ 'true',
+ '--minify-css',
+ 'true',
+ fullPath], stdout=subprocess.PIPE)
+ minifiedContent = result.stdout
+ print("Minified '%s' with from %d to %d bytes" % (fullPath, originalSize, len(minifiedContent)))
+ return minifiedContent
+
+
+def GenData():
+ dataDir = os.path.join(env["PROJECT_DIR"], "data")
+ print("dataDir = %s" % dataDir)
+ genDir = os.path.join(env.subst("$BUILD_DIR"), 'inline_data')
+ print("genDir = %s" % genDir)
+ if not os.path.exists(dataDir):
+ return
+ if not os.path.exists(genDir):
+ os.mkdir(genDir)
+ env.Append(CPPPATH=[genDir])
+
+ files = sorted(file for file in os.listdir(dataDir)
+ if os.path.isfile(os.path.join(dataDir, file)))
+
+ out = "// WARNING: Autogenerated by pio_tools/gen_data.py, don't edit manually.\n"
+ out += "#ifndef OWIE_GENERATED_DATA_H\n"
+ out +="#define OWIE_GENERATED_DATA_H\n\n"
+ for name in files:
+ varName = name.upper().replace(".", "_")
+ sizeName = varName + "_SIZE"
+ storageArrayName = varName + "_PROGMEM_ARRAY"
+ out += (
+ "static const unsigned char %s[] PROGMEM = {\n " % storageArrayName)
+ firstByte = True
+ fileContent = ReadAndMaybeMinifyFiles(os.path.join(dataDir, name))
+ column = 0
+ for b in fileContent:
+ if not firstByte:
+ out += ","
+ else:
+ firstByte = False
+ column = column + 1
+ if column > 20:
+ column = 0
+ out += "\n "
+ out += str(b)
+ out += "};\n"
+ out += "#define %s FPSTR(%s)\n" % (varName, storageArrayName)
+ out += "#define %s sizeof(%s)\n\n" % (sizeName, storageArrayName)
+ out += "\n#endif // OWIE_GENERATED_DATA_H\n"
+ with open(os.path.join(genDir, "data.h"), 'w') as f:
+ f.write(out)
+ print("Wrote data.h\n")
+
+GenData()
diff --git a/pio_tools/platformio_upload.py b/pio_tools/platformio_upload.py
new file mode 100644
index 0000000..02e735d
--- /dev/null
+++ b/pio_tools/platformio_upload.py
@@ -0,0 +1,53 @@
+# Allows PlatformIO to upload directly to AsyncElegantOTA
+#
+# To use:
+# - copy this script into the same folder as your platformio.ini
+# - set the following for your project in platformio.ini:
+#
+# extra_scripts = platformio_upload.py
+# upload_protocol = custom
+# upload_url =
+#
+# An example of an upload URL:
+# upload_URL = http://192.168.1.123/update
+
+import requests
+import hashlib
+Import('env')
+
+try:
+ from requests_toolbelt import MultipartEncoder, MultipartEncoderMonitor
+ from tqdm import tqdm
+except ImportError:
+ env.Execute("$PYTHONEXE -m pip install requests_toolbelt")
+ env.Execute("$PYTHONEXE -m pip install tqdm")
+ from requests_toolbelt import MultipartEncoder, MultipartEncoderMonitor
+ from tqdm import tqdm
+
+def on_upload(source, target, env):
+ firmware_path = str(source[0])
+ upload_url = env.GetProjectOption('upload_url')
+
+ with open(firmware_path, 'rb') as firmware:
+ md5 = hashlib.md5(firmware.read()).hexdigest()
+ firmware.seek(0)
+ encoder = MultipartEncoder(fields={
+ 'MD5': md5,
+ 'firmware': ('firmware', firmware, 'application/octet-stream')}
+ )
+
+ bar = tqdm(desc='Upload Progress',
+ total=encoder.len,
+ dynamic_ncols=True,
+ unit='B',
+ unit_scale=True,
+ unit_divisor=1024
+ )
+
+ monitor = MultipartEncoderMonitor(encoder, lambda monitor: bar.update(monitor.bytes_read - bar.n))
+
+ response = requests.post(upload_url, data=monitor, headers={'Content-Type': monitor.content_type})
+ bar.close()
+ print(response,response.text)
+
+env.Replace(UPLOADCMD=on_upload)
\ No newline at end of file
diff --git a/platformio.ini b/platformio.ini
new file mode 100644
index 0000000..d4f84ad
--- /dev/null
+++ b/platformio.ini
@@ -0,0 +1,53 @@
+[platformio]
+; point data_dir to nonexistent directory so that PIO doesn't bother building SPIFFS
+data_dir = nonexistent
+
+[env:d1_mini_lite_clone]
+platform = espressif8266
+upload_speed = 524288
+monitor_speed = 115200
+board = d1_mini
+# Following is necessary for cheap Wemos D1 Lite clones.
+# Without this line, flashing succeeds but programs simply don't run on the chip.
+board_build.flash_mode = dout
+framework = arduino
+board_build.ldscript = eagle.flash.1m.ld
+
+custom_nanopb_protos =
+ +
+custom_nanopb_options =
+ --error-on-unmatched
+
+extra_scripts =
+ pre:pio_tools/gen_data.py
+
+build_flags =
+ ; Disable global instances to save space
+ -DNO_GLOBAL_INSTANCES
+ ;-DDEBUG_ESP_PORT=Serial
+ ;-DDEBUG_EEPROM_ROTATE_PORT=Serial
+ ;-DDEBUG_ESP_CORE
+ ;-DDEBUG_ESP_WIFI
+ ;-DDEBUG_ESP_UPDATER
+ ;-DDEBUG_ESP_PORT=Serial
+ ;-DDEBUG_UPDATER=Serial
+
+
+lib_deps =
+ xoseperez/EEPROM_Rotate@^0.9.2
+ ottowinter/ESPAsyncWebServer-esphome@^2.1.0
+ nanopb/Nanopb@^0.4.6
+ bblanchon/ArduinoJson@^6.19.4
+
+[env:ota]
+extends = env:d1_mini_lite_clone
+extra_scripts =
+ pre:pio_tools/gen_data.py
+ pio_tools/platformio_upload.py
+upload_url = http://owie-c024.lan/update
+upload_protocol = custom
+;board_build.gzip_fw = true
+
+[env:native]
+platform = native
+debug_test = test_bms_relay
diff --git a/proto/settings.options b/proto/settings.options
new file mode 100644
index 0000000..f847d47
--- /dev/null
+++ b/proto/settings.options
@@ -0,0 +1,14 @@
+SettingsMsg.ap_name max_size:31
+SettingsMsg.ap_password max_size:31
+SettingsMsg.ap_self_password max_size:31
+SettingsMsg.ap_self_name max_size:31
+SettingsMsg.redf max_size:3
+SettingsMsg.greenf max_size:3
+SettingsMsg.bluef max_size:3
+SettingsMsg.whitef max_size:3
+SettingsMsg.ledf max_size:3
+SettingsMsg.ap_self_redt max_size:3
+SettingsMsg.ap_self_greent max_size:3
+SettingsMsg.ap_self_bluet max_size:3
+SettingsMsg.ap_self_whitet max_size:3
+SettingsMsg.ap_self_ledt max_size:3
\ No newline at end of file
diff --git a/proto/settings.proto b/proto/settings.proto
new file mode 100644
index 0000000..e505ffc
--- /dev/null
+++ b/proto/settings.proto
@@ -0,0 +1,26 @@
+syntax = "proto3";
+
+
+message SettingsMsg {
+ uint32 quick_power_cycle_count = 1;
+ string ap_name = 2;
+ string ap_password = 3;
+ int32 graceful_shutdown_count = 4;
+ reserved 5; // used to be BMS serial override
+ string ap_self_password = 6;
+ string ap_self_name = 7;
+ int32 wifi_power = 8;
+ bool is_locked = 9; // if the board is actively locked/parked
+ bool locking_enabled = 10; // if the board locking functionality is enabled ('armed')
+ reserved 11;
+ int32 redf = 12;
+ int32 greenf = 13;
+ int32 bluef = 14;
+ int32 whitef = 15;
+ int32 ledf = 16;
+ int32 ap_self_redt = 17;
+ int32 ap_self_greent = 18;
+ int32 ap_self_bluet = 19;
+ int32 ap_self_whitet = 20;
+ int32 ap_self_ledt = 21;
+}
\ No newline at end of file
diff --git a/src/owie-watcher.ino b/src/owie-watcher.ino
new file mode 100644
index 0000000..b8b2bb7
--- /dev/null
+++ b/src/owie-watcher.ino
@@ -0,0 +1,192 @@
+#include
+#include
+#include
+#include
+#include
+#include
+
+ESP8266WiFiMulti WiFiMulti;
+
+// REPLACE WITH YOUR NETWORK CREDENTIALS
+char* ssid = "owie-ssid";
+char* password = "owie-wifi-password";
+#define OLED_RESET 0 // GPIO0
+Adafruit_SSD1306 display(OLED_RESET);
+
+//Boot Logo, change if you dont like it
+static const unsigned char PROGMEM logo16_glcd_bmp[] =
+{ 0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11111111,
+ 0b11000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000011,
+ 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
+ 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
+ 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
+ 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
+ 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
+ 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
+ 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
+ 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
+ 0b10000000, 0b00000000, 0b01000000, 0b00000000, 0b00000000, 0b10000001, 0b00000000, 0b00000001,
+ 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b10000001, 0b00000000, 0b00000001,
+ 0b10000110, 0b01001001, 0b01001100, 0b00010010, 0b01011101, 0b11011001, 0b01000110, 0b01010001,
+ 0b10001001, 0b01010101, 0b01010010, 0b00010101, 0b01000010, 0b10100101, 0b10101001, 0b01100001,
+ 0b10001001, 0b01010101, 0b01011110, 0b00010101, 0b01001110, 0b10100001, 0b00101111, 0b01000001,
+ 0b10001001, 0b01010101, 0b01010000, 0b00010101, 0b01010010, 0b10100001, 0b00101000, 0b01000001,
+ 0b10001001, 0b01010101, 0b01010010, 0b00010101, 0b01010010, 0b10100101, 0b00101001, 0b01000001,
+ 0b10000110, 0b00100010, 0b01001100, 0b00001000, 0b10001110, 0b11011001, 0b00100110, 0b01000001,
+ 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
+ 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
+ 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
+ 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
+ 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
+ 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
+ 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
+ 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
+ 0b10000000, 0b00000000, 0b00000000, 0b00000011, 0b11000000, 0b00000000, 0b00000000, 0b00000001,
+ 0b10000000, 0b00000000, 0b00000000, 0b00001111, 0b11110000, 0b00000000, 0b00000000, 0b00000001,
+ 0b10000000, 0b00000000, 0b00000000, 0b00011100, 0b00111000, 0b00000000, 0b00000000, 0b00000001,
+ 0b10000000, 0b00000000, 0b00000000, 0b00111000, 0b00011100, 0b00000000, 0b00000000, 0b00000001,
+ 0b10000000, 0b00000000, 0b00000000, 0b00110000, 0b00001100, 0b00000000, 0b00000000, 0b00000001,
+ 0b10000000, 0b00011111, 0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11111000, 0b00000001,
+ 0b10000000, 0b00001000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00010000, 0b00000001,
+ 0b10000000, 0b00000100, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00100000, 0b00000001,
+ 0b10000000, 0b00000011, 0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11000000, 0b00000001,
+ 0b10000000, 0b00000000, 0b00000000, 0b00110000, 0b00001100, 0b00000000, 0b00000000, 0b00000001,
+ 0b10000000, 0b00000000, 0b00000000, 0b00111000, 0b00011100, 0b00000000, 0b00000000, 0b00000001,
+ 0b10000000, 0b00000000, 0b00000000, 0b00011100, 0b00111000, 0b00000000, 0b00000000, 0b00000001,
+ 0b10000000, 0b00000000, 0b00000000, 0b00001111, 0b11110000, 0b00000000, 0b00000000, 0b00000001,
+ 0b10000000, 0b00000000, 0b00000000, 0b00000011, 0b11000000, 0b00000000, 0b00000000, 0b00000001,
+ 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
+ 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
+ 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
+ 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
+ 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
+ 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
+ 0b11000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000011,
+ 0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11111111 };
+
+void setup() {
+
+ Serial.begin(115200);
+ display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
+ display.clearDisplay();
+ display.drawBitmap(0, 0, logo16_glcd_bmp, 64, 48, 1);
+ display.display();
+ delay(2000);
+ display.clearDisplay();
+
+ Serial.println();
+ Serial.println();
+ Serial.println();
+
+ for (uint8_t t = 4; t > 0; t--) {
+ Serial.printf("[SETUP] WAIT %d...\n", t);
+ Serial.flush();
+ delay(1000);
+ }
+
+ WiFi.mode(WIFI_STA);
+ WiFiMulti.addAP(ssid,password);
+}
+
+void loop() {
+ // wait for WiFi connection
+ if ((WiFiMulti.run() == WL_CONNECTED)) {
+
+ WiFiClient client;
+ HTTPClient http;
+
+ display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
+ display.setTextSize(1);
+ display.setTextColor(WHITE,BLACK);
+
+ display.setCursor(0, 0);
+ Serial.print("[HTTP] begin...\n");
+
+ http.begin(client, "http://192.168.4.1");
+
+ Serial.print("[HTTP] GET...\n");
+ int httpCode = http.GET();
+ if (httpCode > 0) {
+ Serial.printf("[HTTP] GET... code: %d\n", httpCode);
+
+ if (httpCode == HTTP_CODE_OK) {
+ int len = http.getSize();
+
+#if 0
+ // with API
+ Serial.println(http.getString());
+#else
+ WiFiClient* stream = &client;
+
+ while (http.connected() && (len > 0 || len == -1)) {
+ char vbuff[5] = {0};
+ char socbuff[4] = {0};
+ char ovrbuff[4] = {0};
+ display.setCursor(0, 0);
+ stream->find("Voltage");
+ while(stream->find("TOTAL_VOLTAGE\">")){
+ stream->readBytesUntil('<',vbuff,5);
+ Serial.print(vbuff);
+ display.print("VOL:");
+ display.print(vbuff);
+ display.print("v");
+ display.println();
+ display.display();
+ stream->find("BMS_SOC\">");
+ stream->readBytesUntil('<',socbuff,4);
+ display.println();
+ display.print("BMS:");
+ Serial.println();
+ Serial.print(socbuff);
+ display.print(socbuff);
+ display.println();
+ display.display();
+ stream->find("OVERRIDDEN_SOC\">");
+ stream->readBytesUntil('<',ovrbuff,4);
+ Serial.println();
+ Serial.print(ovrbuff);
+ display.println();
+ display.print("OVR:");
+ display.print(ovrbuff);
+ display.println();
+ display.display();
+ display.print(" ");
+ display.display();
+ display.setCursor(0, 41);
+ delay(5 * 1000);
+ display.print("REFRESH");
+ display.display();
+ Serial.print("REFRESH");
+ delay(1 * 1000);
+ display.print(".");
+ Serial.print(".");
+ display.display();
+ delay(1 * 1000);
+ display.print(".");
+ Serial.print(".");
+ display.display();
+ delay(1 * 1000);
+ display.print(".");
+ Serial.print(".");
+ display.display();
+ delay(1 * 1000);
+ return;
+ }
+ }
+#endif
+ Serial.println();
+ Serial.print("[HTTP] connection closed or file end.\n");
+ }
+ } else {
+ Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
+ }
+
+ http.end();
+ }
+
+ Serial.print("RETRYING...");
+ display.setCursor(0, 41);
+ display.print("RETRYING ");
+ display.display();
+ delay(30 * 1000);
+}
From 05b67536fb33229122d30033278b146f35a2872f Mon Sep 17 00:00:00 2001
From: tonyt321 <34804458+tonyt321@users.noreply.github.com>
Date: Tue, 6 Sep 2022 19:48:47 -0400
Subject: [PATCH 04/11] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 4263974..b4e65ec 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
# OwieWatcher
-[](https://gitpod.io/#https://github.com/lolwheel/Owie)
+[](https://gitpod.io/#https://github.com/tonyt321/OWIE-OLED)
todo
- use the u8g2 library better display support
From ae9cc62e61db4fb8fc4bddd56ea3d4949a866e2d Mon Sep 17 00:00:00 2001
From: tonyt321 <34804458+tonyt321@users.noreply.github.com>
Date: Tue, 6 Sep 2022 19:57:46 -0400
Subject: [PATCH 05/11] Add files via upload
---
lib/bms/Adafruit_NeoPixel.cpp | 3440 +++++++++++++++++++++++++++++++++
lib/bms/Adafruit_NeoPixel.h | 410 ++++
lib/bms/ESP8266HTTPClient.cpp | 1105 +++++++++++
lib/bms/ESP8266HTTPClient.h | 277 +++
lib/bms/ESP8266WiFiMulti.cpp | 529 +++++
lib/bms/ESP8266WiFiMulti.h | 86 +
lib/bms/WiFiClient.cpp | 454 +++++
lib/bms/WiFiClient.h | 164 ++
lib/bms/desktop.ini | 3 +
lib/bms/esp.c | 178 ++
lib/bms/esp8266.c | 86 +
11 files changed, 6732 insertions(+)
create mode 100644 lib/bms/Adafruit_NeoPixel.cpp
create mode 100644 lib/bms/Adafruit_NeoPixel.h
create mode 100644 lib/bms/ESP8266HTTPClient.cpp
create mode 100644 lib/bms/ESP8266HTTPClient.h
create mode 100644 lib/bms/ESP8266WiFiMulti.cpp
create mode 100644 lib/bms/ESP8266WiFiMulti.h
create mode 100644 lib/bms/WiFiClient.cpp
create mode 100644 lib/bms/WiFiClient.h
create mode 100644 lib/bms/desktop.ini
create mode 100644 lib/bms/esp.c
create mode 100644 lib/bms/esp8266.c
diff --git a/lib/bms/Adafruit_NeoPixel.cpp b/lib/bms/Adafruit_NeoPixel.cpp
new file mode 100644
index 0000000..a1216d9
--- /dev/null
+++ b/lib/bms/Adafruit_NeoPixel.cpp
@@ -0,0 +1,3440 @@
+/*!
+ * @file Adafruit_NeoPixel.cpp
+ *
+ * @mainpage Arduino Library for driving Adafruit NeoPixel addressable LEDs,
+ * FLORA RGB Smart Pixels and compatible devicess -- WS2811, WS2812, WS2812B,
+ * SK6812, etc.
+ *
+ * @section intro_sec Introduction
+ *
+ * This is the documentation for Adafruit's NeoPixel library for the
+ * Arduino platform, allowing a broad range of microcontroller boards
+ * (most AVR boards, many ARM devices, ESP8266 and ESP32, among others)
+ * to control Adafruit NeoPixels, FLORA RGB Smart Pixels and compatible
+ * devices -- WS2811, WS2812, WS2812B, SK6812, etc.
+ *
+ * Adafruit invests time and resources providing this open source code,
+ * please support Adafruit and open-source hardware by purchasing products
+ * from Adafruit!
+ *
+ * @section author Author
+ *
+ * Written by Phil "Paint Your Dragon" Burgess for Adafruit Industries,
+ * with contributions by PJRC, Michael Miller and other members of the
+ * open source community.
+ *
+ * @section license License
+ *
+ * This file is part of the Adafruit_NeoPixel library.
+ *
+ * Adafruit_NeoPixel 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 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Adafruit_NeoPixel 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 NeoPixel. If not, see
+ * .
+ *
+ */
+
+#include "Adafruit_NeoPixel.h"
+
+#if defined(TARGET_LPC1768)
+#include
+#endif
+
+#if defined(NRF52) || defined(NRF52_SERIES)
+#include "nrf.h"
+
+// Interrupt is only disabled if there is no PWM device available
+// Note: Adafruit Bluefruit nrf52 does not use this option
+//#define NRF52_DISABLE_INT
+#endif
+
+#if defined(ARDUINO_ARCH_NRF52840)
+#if defined __has_include
+#if __has_include()
+#include
+#endif
+#endif
+#endif
+
+/*!
+ @brief NeoPixel constructor when length, pin and pixel type are known
+ at compile-time.
+ @param n Number of NeoPixels in strand.
+ @param p Arduino pin number which will drive the NeoPixel data in.
+ @param t Pixel type -- add together NEO_* constants defined in
+ Adafruit_NeoPixel.h, for example NEO_GRB+NEO_KHZ800 for
+ NeoPixels expecting an 800 KHz (vs 400 KHz) data stream
+ with color bytes expressed in green, red, blue order per
+ pixel.
+ @return Adafruit_NeoPixel object. Call the begin() function before use.
+*/
+Adafruit_NeoPixel::Adafruit_NeoPixel(uint16_t n, int16_t p, neoPixelType t)
+ : begun(false), brightness(0), pixels(NULL), endTime(0) {
+ updateType(t);
+ updateLength(n);
+ setPin(p);
+#if defined(ARDUINO_ARCH_RP2040)
+ // Find a free SM on one of the PIO's
+ sm = pio_claim_unused_sm(pio, false); // don't panic
+ // Try pio1 if SM not found
+ if (sm < 0) {
+ pio = pio1;
+ sm = pio_claim_unused_sm(pio, true); // panic if no SM is free
+ }
+ init = true;
+#endif
+}
+
+/*!
+ @brief "Empty" NeoPixel constructor when length, pin and/or pixel type
+ are not known at compile-time, and must be initialized later with
+ updateType(), updateLength() and setPin().
+ @return Adafruit_NeoPixel object. Call the begin() function before use.
+ @note This function is deprecated, here only for old projects that
+ may still be calling it. New projects should instead use the
+ 'new' keyword with the first constructor syntax (length, pin,
+ type).
+*/
+Adafruit_NeoPixel::Adafruit_NeoPixel()
+ :
+#if defined(NEO_KHZ400)
+ is800KHz(true),
+#endif
+ begun(false), numLEDs(0), numBytes(0), pin(-1), brightness(0),
+ pixels(NULL), rOffset(1), gOffset(0), bOffset(2), wOffset(1), endTime(0) {
+}
+
+/*!
+ @brief Deallocate Adafruit_NeoPixel object, set data pin back to INPUT.
+*/
+Adafruit_NeoPixel::~Adafruit_NeoPixel() {
+ free(pixels);
+ if (pin >= 0)
+ pinMode(pin, INPUT);
+}
+
+/*!
+ @brief Configure NeoPixel pin for output.
+*/
+void Adafruit_NeoPixel::begin(void) {
+ if (pin >= 0) {
+ pinMode(pin, OUTPUT);
+ digitalWrite(pin, LOW);
+ }
+ begun = true;
+}
+
+/*!
+ @brief Change the length of a previously-declared Adafruit_NeoPixel
+ strip object. Old data is deallocated and new data is cleared.
+ Pin number and pixel format are unchanged.
+ @param n New length of strip, in pixels.
+ @note This function is deprecated, here only for old projects that
+ may still be calling it. New projects should instead use the
+ 'new' keyword with the first constructor syntax (length, pin,
+ type).
+*/
+void Adafruit_NeoPixel::updateLength(uint16_t n) {
+ free(pixels); // Free existing data (if any)
+
+ // Allocate new data -- note: ALL PIXELS ARE CLEARED
+ numBytes = n * ((wOffset == rOffset) ? 3 : 4);
+ if ((pixels = (uint8_t *)malloc(numBytes))) {
+ memset(pixels, 0, numBytes);
+ numLEDs = n;
+ } else {
+ numLEDs = numBytes = 0;
+ }
+}
+
+/*!
+ @brief Change the pixel format of a previously-declared
+ Adafruit_NeoPixel strip object. If format changes from one of
+ the RGB variants to an RGBW variant (or RGBW to RGB), the old
+ data will be deallocated and new data is cleared. Otherwise,
+ the old data will remain in RAM and is not reordered to the
+ new format, so it's advisable to follow up with clear().
+ @param t Pixel type -- add together NEO_* constants defined in
+ Adafruit_NeoPixel.h, for example NEO_GRB+NEO_KHZ800 for
+ NeoPixels expecting an 800 KHz (vs 400 KHz) data stream
+ with color bytes expressed in green, red, blue order per
+ pixel.
+ @note This function is deprecated, here only for old projects that
+ may still be calling it. New projects should instead use the
+ 'new' keyword with the first constructor syntax
+ (length, pin, type).
+*/
+void Adafruit_NeoPixel::updateType(neoPixelType t) {
+ bool oldThreeBytesPerPixel = (wOffset == rOffset); // false if RGBW
+
+ wOffset = (t >> 6) & 0b11; // See notes in header file
+ rOffset = (t >> 4) & 0b11; // regarding R/G/B/W offsets
+ gOffset = (t >> 2) & 0b11;
+ bOffset = t & 0b11;
+#if defined(NEO_KHZ400)
+ is800KHz = (t < 256); // 400 KHz flag is 1<<8
+#endif
+
+ // If bytes-per-pixel has changed (and pixel data was previously
+ // allocated), re-allocate to new size. Will clear any data.
+ if (pixels) {
+ bool newThreeBytesPerPixel = (wOffset == rOffset);
+ if (newThreeBytesPerPixel != oldThreeBytesPerPixel)
+ updateLength(numLEDs);
+ }
+}
+
+// RP2040 specific driver
+#if defined(ARDUINO_ARCH_RP2040)
+void Adafruit_NeoPixel::rp2040Init(uint8_t pin, bool is800KHz)
+{
+ uint offset = pio_add_program(pio, &ws2812_program);
+
+ if (is800KHz)
+ {
+ // 800kHz, 8 bit transfers
+ ws2812_program_init(pio, sm, offset, pin, 800000, 8);
+ }
+ else
+ {
+ // 400kHz, 8 bit transfers
+ ws2812_program_init(pio, sm, offset, pin, 400000, 8);
+ }
+}
+// Not a user API
+void Adafruit_NeoPixel::rp2040Show(uint8_t pin, uint8_t *pixels, uint32_t numBytes, bool is800KHz)
+{
+ if (this->init)
+ {
+ // On first pass through initialise the PIO
+ rp2040Init(pin, is800KHz);
+ this->init = false;
+ }
+
+ while(numBytes--)
+ // Bits for transmission must be shifted to top 8 bits
+ pio_sm_put_blocking(pio, sm, ((uint32_t)*pixels++)<< 24);
+}
+
+#endif
+
+#if defined(ESP8266)
+// ESP8266 show() is external to enforce ICACHE_RAM_ATTR execution
+extern "C" IRAM_ATTR void espShow(uint16_t pin, uint8_t *pixels,
+ uint32_t numBytes, uint8_t type);
+#elif defined(ESP32)
+extern "C" void espShow(uint16_t pin, uint8_t *pixels, uint32_t numBytes,
+ uint8_t type);
+#endif // ESP8266
+
+#if defined(K210)
+#define KENDRYTE_K210 1
+#endif
+
+#if defined(KENDRYTE_K210)
+extern "C" void k210Show(uint8_t pin, uint8_t *pixels, uint32_t numBytes,
+ boolean is800KHz);
+#endif // KENDRYTE_K210
+/*!
+ @brief Transmit pixel data in RAM to NeoPixels.
+ @note On most architectures, interrupts are temporarily disabled in
+ order to achieve the correct NeoPixel signal timing. This means
+ that the Arduino millis() and micros() functions, which require
+ interrupts, will lose small intervals of time whenever this
+ function is called (about 30 microseconds per RGB pixel, 40 for
+ RGBW pixels). There's no easy fix for this, but a few
+ specialized alternative or companion libraries exist that use
+ very device-specific peripherals to work around it.
+*/
+void Adafruit_NeoPixel::show(void) {
+
+ if (!pixels)
+ return;
+
+ // Data latch = 300+ microsecond pause in the output stream. Rather than
+ // put a delay at the end of the function, the ending time is noted and
+ // the function will simply hold off (if needed) on issuing the
+ // subsequent round of data until the latch time has elapsed. This
+ // allows the mainline code to start generating the next frame of data
+ // rather than stalling for the latch.
+ while (!canShow())
+ ;
+ // endTime is a private member (rather than global var) so that multiple
+ // instances on different pins can be quickly issued in succession (each
+ // instance doesn't delay the next).
+
+ // In order to make this code runtime-configurable to work with any pin,
+ // SBI/CBI instructions are eschewed in favor of full PORT writes via the
+ // OUT or ST instructions. It relies on two facts: that peripheral
+ // functions (such as PWM) take precedence on output pins, so our PORT-
+ // wide writes won't interfere, and that interrupts are globally disabled
+ // while data is being issued to the LEDs, so no other code will be
+ // accessing the PORT. The code takes an initial 'snapshot' of the PORT
+ // state, computes 'pin high' and 'pin low' values, and writes these back
+ // to the PORT register as needed.
+
+ // NRF52 may use PWM + DMA (if available), may not need to disable interrupt
+#if !(defined(NRF52) || defined(NRF52_SERIES))
+ noInterrupts(); // Need 100% focus on instruction timing
+#endif
+
+#if defined(__AVR__)
+ // AVR MCUs -- ATmega & ATtiny (no XMEGA) ---------------------------------
+
+ volatile uint16_t i = numBytes; // Loop counter
+ volatile uint8_t *ptr = pixels, // Pointer to next byte
+ b = *ptr++, // Current byte value
+ hi, // PORT w/output bit set high
+ lo; // PORT w/output bit set low
+
+ // Hand-tuned assembly code issues data to the LED drivers at a specific
+ // rate. There's separate code for different CPU speeds (8, 12, 16 MHz)
+ // for both the WS2811 (400 KHz) and WS2812 (800 KHz) drivers. The
+ // datastream timing for the LED drivers allows a little wiggle room each
+ // way (listed in the datasheets), so the conditions for compiling each
+ // case are set up for a range of frequencies rather than just the exact
+ // 8, 12 or 16 MHz values, permitting use with some close-but-not-spot-on
+ // devices (e.g. 16.5 MHz DigiSpark). The ranges were arrived at based
+ // on the datasheet figures and have not been extensively tested outside
+ // the canonical 8/12/16 MHz speeds; there's no guarantee these will work
+ // close to the extremes (or possibly they could be pushed further).
+ // Keep in mind only one CPU speed case actually gets compiled; the
+ // resulting program isn't as massive as it might look from source here.
+
+// 8 MHz(ish) AVR ---------------------------------------------------------
+#if (F_CPU >= 7400000UL) && (F_CPU <= 9500000UL)
+
+#if defined(NEO_KHZ400) // 800 KHz check needed only if 400 KHz support enabled
+ if (is800KHz) {
+#endif
+
+ volatile uint8_t n1, n2 = 0; // First, next bits out
+
+ // Squeezing an 800 KHz stream out of an 8 MHz chip requires code
+ // specific to each PORT register.
+
+ // 10 instruction clocks per bit: HHxxxxxLLL
+ // OUT instructions: ^ ^ ^ (T=0,2,7)
+
+ // PORTD OUTPUT ----------------------------------------------------
+
+#if defined(PORTD)
+#if defined(PORTB) || defined(PORTC) || defined(PORTF)
+ if (port == &PORTD) {
+#endif // defined(PORTB/C/F)
+
+ hi = PORTD | pinMask;
+ lo = PORTD & ~pinMask;
+ n1 = lo;
+ if (b & 0x80)
+ n1 = hi;
+
+ // Dirty trick: RJMPs proceeding to the next instruction are used
+ // to delay two clock cycles in one instruction word (rather than
+ // using two NOPs). This was necessary in order to squeeze the
+ // loop down to exactly 64 words -- the maximum possible for a
+ // relative branch.
+
+ asm volatile(
+ "headD:"
+ "\n\t" // Clk Pseudocode
+ // Bit 7:
+ "out %[port] , %[hi]"
+ "\n\t" // 1 PORT = hi
+ "mov %[n2] , %[lo]"
+ "\n\t" // 1 n2 = lo
+ "out %[port] , %[n1]"
+ "\n\t" // 1 PORT = n1
+ "rjmp .+0"
+ "\n\t" // 2 nop nop
+ "sbrc %[byte] , 6"
+ "\n\t" // 1-2 if(b & 0x40)
+ "mov %[n2] , %[hi]"
+ "\n\t" // 0-1 n2 = hi
+ "out %[port] , %[lo]"
+ "\n\t" // 1 PORT = lo
+ "rjmp .+0"
+ "\n\t" // 2 nop nop
+ // Bit 6:
+ "out %[port] , %[hi]"
+ "\n\t" // 1 PORT = hi
+ "mov %[n1] , %[lo]"
+ "\n\t" // 1 n1 = lo
+ "out %[port] , %[n2]"
+ "\n\t" // 1 PORT = n2
+ "rjmp .+0"
+ "\n\t" // 2 nop nop
+ "sbrc %[byte] , 5"
+ "\n\t" // 1-2 if(b & 0x20)
+ "mov %[n1] , %[hi]"
+ "\n\t" // 0-1 n1 = hi
+ "out %[port] , %[lo]"
+ "\n\t" // 1 PORT = lo
+ "rjmp .+0"
+ "\n\t" // 2 nop nop
+ // Bit 5:
+ "out %[port] , %[hi]"
+ "\n\t" // 1 PORT = hi
+ "mov %[n2] , %[lo]"
+ "\n\t" // 1 n2 = lo
+ "out %[port] , %[n1]"
+ "\n\t" // 1 PORT = n1
+ "rjmp .+0"
+ "\n\t" // 2 nop nop
+ "sbrc %[byte] , 4"
+ "\n\t" // 1-2 if(b & 0x10)
+ "mov %[n2] , %[hi]"
+ "\n\t" // 0-1 n2 = hi
+ "out %[port] , %[lo]"
+ "\n\t" // 1 PORT = lo
+ "rjmp .+0"
+ "\n\t" // 2 nop nop
+ // Bit 4:
+ "out %[port] , %[hi]"
+ "\n\t" // 1 PORT = hi
+ "mov %[n1] , %[lo]"
+ "\n\t" // 1 n1 = lo
+ "out %[port] , %[n2]"
+ "\n\t" // 1 PORT = n2
+ "rjmp .+0"
+ "\n\t" // 2 nop nop
+ "sbrc %[byte] , 3"
+ "\n\t" // 1-2 if(b & 0x08)
+ "mov %[n1] , %[hi]"
+ "\n\t" // 0-1 n1 = hi
+ "out %[port] , %[lo]"
+ "\n\t" // 1 PORT = lo
+ "rjmp .+0"
+ "\n\t" // 2 nop nop
+ // Bit 3:
+ "out %[port] , %[hi]"
+ "\n\t" // 1 PORT = hi
+ "mov %[n2] , %[lo]"
+ "\n\t" // 1 n2 = lo
+ "out %[port] , %[n1]"
+ "\n\t" // 1 PORT = n1
+ "rjmp .+0"
+ "\n\t" // 2 nop nop
+ "sbrc %[byte] , 2"
+ "\n\t" // 1-2 if(b & 0x04)
+ "mov %[n2] , %[hi]"
+ "\n\t" // 0-1 n2 = hi
+ "out %[port] , %[lo]"
+ "\n\t" // 1 PORT = lo
+ "rjmp .+0"
+ "\n\t" // 2 nop nop
+ // Bit 2:
+ "out %[port] , %[hi]"
+ "\n\t" // 1 PORT = hi
+ "mov %[n1] , %[lo]"
+ "\n\t" // 1 n1 = lo
+ "out %[port] , %[n2]"
+ "\n\t" // 1 PORT = n2
+ "rjmp .+0"
+ "\n\t" // 2 nop nop
+ "sbrc %[byte] , 1"
+ "\n\t" // 1-2 if(b & 0x02)
+ "mov %[n1] , %[hi]"
+ "\n\t" // 0-1 n1 = hi
+ "out %[port] , %[lo]"
+ "\n\t" // 1 PORT = lo
+ "rjmp .+0"
+ "\n\t" // 2 nop nop
+ // Bit 1:
+ "out %[port] , %[hi]"
+ "\n\t" // 1 PORT = hi
+ "mov %[n2] , %[lo]"
+ "\n\t" // 1 n2 = lo
+ "out %[port] , %[n1]"
+ "\n\t" // 1 PORT = n1
+ "rjmp .+0"
+ "\n\t" // 2 nop nop
+ "sbrc %[byte] , 0"
+ "\n\t" // 1-2 if(b & 0x01)
+ "mov %[n2] , %[hi]"
+ "\n\t" // 0-1 n2 = hi
+ "out %[port] , %[lo]"
+ "\n\t" // 1 PORT = lo
+ "sbiw %[count], 1"
+ "\n\t" // 2 i-- (don't act on Z flag yet)
+ // Bit 0:
+ "out %[port] , %[hi]"
+ "\n\t" // 1 PORT = hi
+ "mov %[n1] , %[lo]"
+ "\n\t" // 1 n1 = lo
+ "out %[port] , %[n2]"
+ "\n\t" // 1 PORT = n2
+ "ld %[byte] , %a[ptr]+"
+ "\n\t" // 2 b = *ptr++
+ "sbrc %[byte] , 7"
+ "\n\t" // 1-2 if(b & 0x80)
+ "mov %[n1] , %[hi]"
+ "\n\t" // 0-1 n1 = hi
+ "out %[port] , %[lo]"
+ "\n\t" // 1 PORT = lo
+ "brne headD"
+ "\n" // 2 while(i) (Z flag set above)
+ : [byte] "+r"(b), [n1] "+r"(n1), [n2] "+r"(n2), [count] "+w"(i)
+ : [port] "I"(_SFR_IO_ADDR(PORTD)), [ptr] "e"(ptr), [hi] "r"(hi),
+ [lo] "r"(lo));
+
+#if defined(PORTB) || defined(PORTC) || defined(PORTF)
+ } else // other PORT(s)
+#endif // defined(PORTB/C/F)
+#endif // defined(PORTD)
+
+ // PORTB OUTPUT ----------------------------------------------------
+
+#if defined(PORTB)
+#if defined(PORTD) || defined(PORTC) || defined(PORTF)
+ if (port == &PORTB) {
+#endif // defined(PORTD/C/F)
+
+ // Same as above, just switched to PORTB and stripped of comments.
+ hi = PORTB | pinMask;
+ lo = PORTB & ~pinMask;
+ n1 = lo;
+ if (b & 0x80)
+ n1 = hi;
+
+ asm volatile(
+ "headB:"
+ "\n\t"
+ "out %[port] , %[hi]"
+ "\n\t"
+ "mov %[n2] , %[lo]"
+ "\n\t"
+ "out %[port] , %[n1]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "sbrc %[byte] , 6"
+ "\n\t"
+ "mov %[n2] , %[hi]"
+ "\n\t"
+ "out %[port] , %[lo]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "out %[port] , %[hi]"
+ "\n\t"
+ "mov %[n1] , %[lo]"
+ "\n\t"
+ "out %[port] , %[n2]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "sbrc %[byte] , 5"
+ "\n\t"
+ "mov %[n1] , %[hi]"
+ "\n\t"
+ "out %[port] , %[lo]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "out %[port] , %[hi]"
+ "\n\t"
+ "mov %[n2] , %[lo]"
+ "\n\t"
+ "out %[port] , %[n1]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "sbrc %[byte] , 4"
+ "\n\t"
+ "mov %[n2] , %[hi]"
+ "\n\t"
+ "out %[port] , %[lo]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "out %[port] , %[hi]"
+ "\n\t"
+ "mov %[n1] , %[lo]"
+ "\n\t"
+ "out %[port] , %[n2]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "sbrc %[byte] , 3"
+ "\n\t"
+ "mov %[n1] , %[hi]"
+ "\n\t"
+ "out %[port] , %[lo]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "out %[port] , %[hi]"
+ "\n\t"
+ "mov %[n2] , %[lo]"
+ "\n\t"
+ "out %[port] , %[n1]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "sbrc %[byte] , 2"
+ "\n\t"
+ "mov %[n2] , %[hi]"
+ "\n\t"
+ "out %[port] , %[lo]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "out %[port] , %[hi]"
+ "\n\t"
+ "mov %[n1] , %[lo]"
+ "\n\t"
+ "out %[port] , %[n2]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "sbrc %[byte] , 1"
+ "\n\t"
+ "mov %[n1] , %[hi]"
+ "\n\t"
+ "out %[port] , %[lo]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "out %[port] , %[hi]"
+ "\n\t"
+ "mov %[n2] , %[lo]"
+ "\n\t"
+ "out %[port] , %[n1]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "sbrc %[byte] , 0"
+ "\n\t"
+ "mov %[n2] , %[hi]"
+ "\n\t"
+ "out %[port] , %[lo]"
+ "\n\t"
+ "sbiw %[count], 1"
+ "\n\t"
+ "out %[port] , %[hi]"
+ "\n\t"
+ "mov %[n1] , %[lo]"
+ "\n\t"
+ "out %[port] , %[n2]"
+ "\n\t"
+ "ld %[byte] , %a[ptr]+"
+ "\n\t"
+ "sbrc %[byte] , 7"
+ "\n\t"
+ "mov %[n1] , %[hi]"
+ "\n\t"
+ "out %[port] , %[lo]"
+ "\n\t"
+ "brne headB"
+ "\n"
+ : [byte] "+r"(b), [n1] "+r"(n1), [n2] "+r"(n2), [count] "+w"(i)
+ : [port] "I"(_SFR_IO_ADDR(PORTB)), [ptr] "e"(ptr), [hi] "r"(hi),
+ [lo] "r"(lo));
+
+#if defined(PORTD) || defined(PORTC) || defined(PORTF)
+ }
+#endif
+#if defined(PORTC) || defined(PORTF)
+ else
+#endif // defined(PORTC/F)
+#endif // defined(PORTB)
+
+ // PORTC OUTPUT ----------------------------------------------------
+
+#if defined(PORTC)
+#if defined(PORTD) || defined(PORTB) || defined(PORTF)
+ if (port == &PORTC) {
+#endif // defined(PORTD/B/F)
+
+ // Same as above, just switched to PORTC and stripped of comments.
+ hi = PORTC | pinMask;
+ lo = PORTC & ~pinMask;
+ n1 = lo;
+ if (b & 0x80)
+ n1 = hi;
+
+ asm volatile(
+ "headC:"
+ "\n\t"
+ "out %[port] , %[hi]"
+ "\n\t"
+ "mov %[n2] , %[lo]"
+ "\n\t"
+ "out %[port] , %[n1]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "sbrc %[byte] , 6"
+ "\n\t"
+ "mov %[n2] , %[hi]"
+ "\n\t"
+ "out %[port] , %[lo]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "out %[port] , %[hi]"
+ "\n\t"
+ "mov %[n1] , %[lo]"
+ "\n\t"
+ "out %[port] , %[n2]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "sbrc %[byte] , 5"
+ "\n\t"
+ "mov %[n1] , %[hi]"
+ "\n\t"
+ "out %[port] , %[lo]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "out %[port] , %[hi]"
+ "\n\t"
+ "mov %[n2] , %[lo]"
+ "\n\t"
+ "out %[port] , %[n1]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "sbrc %[byte] , 4"
+ "\n\t"
+ "mov %[n2] , %[hi]"
+ "\n\t"
+ "out %[port] , %[lo]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "out %[port] , %[hi]"
+ "\n\t"
+ "mov %[n1] , %[lo]"
+ "\n\t"
+ "out %[port] , %[n2]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "sbrc %[byte] , 3"
+ "\n\t"
+ "mov %[n1] , %[hi]"
+ "\n\t"
+ "out %[port] , %[lo]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "out %[port] , %[hi]"
+ "\n\t"
+ "mov %[n2] , %[lo]"
+ "\n\t"
+ "out %[port] , %[n1]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "sbrc %[byte] , 2"
+ "\n\t"
+ "mov %[n2] , %[hi]"
+ "\n\t"
+ "out %[port] , %[lo]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "out %[port] , %[hi]"
+ "\n\t"
+ "mov %[n1] , %[lo]"
+ "\n\t"
+ "out %[port] , %[n2]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "sbrc %[byte] , 1"
+ "\n\t"
+ "mov %[n1] , %[hi]"
+ "\n\t"
+ "out %[port] , %[lo]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "out %[port] , %[hi]"
+ "\n\t"
+ "mov %[n2] , %[lo]"
+ "\n\t"
+ "out %[port] , %[n1]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "sbrc %[byte] , 0"
+ "\n\t"
+ "mov %[n2] , %[hi]"
+ "\n\t"
+ "out %[port] , %[lo]"
+ "\n\t"
+ "sbiw %[count], 1"
+ "\n\t"
+ "out %[port] , %[hi]"
+ "\n\t"
+ "mov %[n1] , %[lo]"
+ "\n\t"
+ "out %[port] , %[n2]"
+ "\n\t"
+ "ld %[byte] , %a[ptr]+"
+ "\n\t"
+ "sbrc %[byte] , 7"
+ "\n\t"
+ "mov %[n1] , %[hi]"
+ "\n\t"
+ "out %[port] , %[lo]"
+ "\n\t"
+ "brne headC"
+ "\n"
+ : [byte] "+r"(b), [n1] "+r"(n1), [n2] "+r"(n2), [count] "+w"(i)
+ : [port] "I"(_SFR_IO_ADDR(PORTC)), [ptr] "e"(ptr), [hi] "r"(hi),
+ [lo] "r"(lo));
+
+#if defined(PORTD) || defined(PORTB) || defined(PORTF)
+ }
+#endif // defined(PORTD/B/F)
+#if defined(PORTF)
+ else
+#endif
+#endif // defined(PORTC)
+
+ // PORTF OUTPUT ----------------------------------------------------
+
+#if defined(PORTF)
+#if defined(PORTD) || defined(PORTB) || defined(PORTC)
+ if (port == &PORTF) {
+#endif // defined(PORTD/B/C)
+
+ hi = PORTF | pinMask;
+ lo = PORTF & ~pinMask;
+ n1 = lo;
+ if (b & 0x80)
+ n1 = hi;
+
+ asm volatile(
+ "headF:"
+ "\n\t"
+ "out %[port] , %[hi]"
+ "\n\t"
+ "mov %[n2] , %[lo]"
+ "\n\t"
+ "out %[port] , %[n1]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "sbrc %[byte] , 6"
+ "\n\t"
+ "mov %[n2] , %[hi]"
+ "\n\t"
+ "out %[port] , %[lo]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "out %[port] , %[hi]"
+ "\n\t"
+ "mov %[n1] , %[lo]"
+ "\n\t"
+ "out %[port] , %[n2]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "sbrc %[byte] , 5"
+ "\n\t"
+ "mov %[n1] , %[hi]"
+ "\n\t"
+ "out %[port] , %[lo]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "out %[port] , %[hi]"
+ "\n\t"
+ "mov %[n2] , %[lo]"
+ "\n\t"
+ "out %[port] , %[n1]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "sbrc %[byte] , 4"
+ "\n\t"
+ "mov %[n2] , %[hi]"
+ "\n\t"
+ "out %[port] , %[lo]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "out %[port] , %[hi]"
+ "\n\t"
+ "mov %[n1] , %[lo]"
+ "\n\t"
+ "out %[port] , %[n2]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "sbrc %[byte] , 3"
+ "\n\t"
+ "mov %[n1] , %[hi]"
+ "\n\t"
+ "out %[port] , %[lo]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "out %[port] , %[hi]"
+ "\n\t"
+ "mov %[n2] , %[lo]"
+ "\n\t"
+ "out %[port] , %[n1]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "sbrc %[byte] , 2"
+ "\n\t"
+ "mov %[n2] , %[hi]"
+ "\n\t"
+ "out %[port] , %[lo]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "out %[port] , %[hi]"
+ "\n\t"
+ "mov %[n1] , %[lo]"
+ "\n\t"
+ "out %[port] , %[n2]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "sbrc %[byte] , 1"
+ "\n\t"
+ "mov %[n1] , %[hi]"
+ "\n\t"
+ "out %[port] , %[lo]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "out %[port] , %[hi]"
+ "\n\t"
+ "mov %[n2] , %[lo]"
+ "\n\t"
+ "out %[port] , %[n1]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "sbrc %[byte] , 0"
+ "\n\t"
+ "mov %[n2] , %[hi]"
+ "\n\t"
+ "out %[port] , %[lo]"
+ "\n\t"
+ "sbiw %[count], 1"
+ "\n\t"
+ "out %[port] , %[hi]"
+ "\n\t"
+ "mov %[n1] , %[lo]"
+ "\n\t"
+ "out %[port] , %[n2]"
+ "\n\t"
+ "ld %[byte] , %a[ptr]+"
+ "\n\t"
+ "sbrc %[byte] , 7"
+ "\n\t"
+ "mov %[n1] , %[hi]"
+ "\n\t"
+ "out %[port] , %[lo]"
+ "\n\t"
+ "brne headF"
+ "\n"
+ : [byte] "+r"(b), [n1] "+r"(n1), [n2] "+r"(n2), [count] "+w"(i)
+ : [port] "I"(_SFR_IO_ADDR(PORTF)), [ptr] "e"(ptr), [hi] "r"(hi),
+ [lo] "r"(lo));
+
+#if defined(PORTD) || defined(PORTB) || defined(PORTC)
+ }
+#endif // defined(PORTD/B/C)
+#endif // defined(PORTF)
+
+#if defined(NEO_KHZ400)
+ } else { // end 800 KHz, do 400 KHz
+
+ // Timing is more relaxed; unrolling the inner loop for each bit is
+ // not necessary. Still using the peculiar RJMPs as 2X NOPs, not out
+ // of need but just to trim the code size down a little.
+ // This 400-KHz-datastream-on-8-MHz-CPU code is not quite identical
+ // to the 800-on-16 code later -- the hi/lo timing between WS2811 and
+ // WS2812 is not simply a 2:1 scale!
+
+ // 20 inst. clocks per bit: HHHHxxxxxxLLLLLLLLLL
+ // ST instructions: ^ ^ ^ (T=0,4,10)
+
+ volatile uint8_t next, bit;
+
+ hi = *port | pinMask;
+ lo = *port & ~pinMask;
+ next = lo;
+ bit = 8;
+
+ asm volatile("head20:"
+ "\n\t" // Clk Pseudocode (T = 0)
+ "st %a[port], %[hi]"
+ "\n\t" // 2 PORT = hi (T = 2)
+ "sbrc %[byte] , 7"
+ "\n\t" // 1-2 if(b & 128)
+ "mov %[next], %[hi]"
+ "\n\t" // 0-1 next = hi (T = 4)
+ "st %a[port], %[next]"
+ "\n\t" // 2 PORT = next (T = 6)
+ "mov %[next] , %[lo]"
+ "\n\t" // 1 next = lo (T = 7)
+ "dec %[bit]"
+ "\n\t" // 1 bit-- (T = 8)
+ "breq nextbyte20"
+ "\n\t" // 1-2 if(bit == 0)
+ "rol %[byte]"
+ "\n\t" // 1 b <<= 1 (T = 10)
+ "st %a[port], %[lo]"
+ "\n\t" // 2 PORT = lo (T = 12)
+ "rjmp .+0"
+ "\n\t" // 2 nop nop (T = 14)
+ "rjmp .+0"
+ "\n\t" // 2 nop nop (T = 16)
+ "rjmp .+0"
+ "\n\t" // 2 nop nop (T = 18)
+ "rjmp head20"
+ "\n\t" // 2 -> head20 (next bit out)
+ "nextbyte20:"
+ "\n\t" // (T = 10)
+ "st %a[port], %[lo]"
+ "\n\t" // 2 PORT = lo (T = 12)
+ "nop"
+ "\n\t" // 1 nop (T = 13)
+ "ldi %[bit] , 8"
+ "\n\t" // 1 bit = 8 (T = 14)
+ "ld %[byte] , %a[ptr]+"
+ "\n\t" // 2 b = *ptr++ (T = 16)
+ "sbiw %[count], 1"
+ "\n\t" // 2 i-- (T = 18)
+ "brne head20"
+ "\n" // 2 if(i != 0) -> (next byte)
+ : [port] "+e"(port), [byte] "+r"(b), [bit] "+r"(bit),
+ [next] "+r"(next), [count] "+w"(i)
+ : [hi] "r"(hi), [lo] "r"(lo), [ptr] "e"(ptr));
+ }
+#endif // NEO_KHZ400
+
+// 12 MHz(ish) AVR --------------------------------------------------------
+#elif (F_CPU >= 11100000UL) && (F_CPU <= 14300000UL)
+
+#if defined(NEO_KHZ400) // 800 KHz check needed only if 400 KHz support enabled
+ if (is800KHz) {
+#endif
+
+ // In the 12 MHz case, an optimized 800 KHz datastream (no dead time
+ // between bytes) requires a PORT-specific loop similar to the 8 MHz
+ // code (but a little more relaxed in this case).
+
+ // 15 instruction clocks per bit: HHHHxxxxxxLLLLL
+ // OUT instructions: ^ ^ ^ (T=0,4,10)
+
+ volatile uint8_t next;
+
+ // PORTD OUTPUT ----------------------------------------------------
+
+#if defined(PORTD)
+#if defined(PORTB) || defined(PORTC) || defined(PORTF)
+ if (port == &PORTD) {
+#endif // defined(PORTB/C/F)
+
+ hi = PORTD | pinMask;
+ lo = PORTD & ~pinMask;
+ next = lo;
+ if (b & 0x80)
+ next = hi;
+
+ // Don't "optimize" the OUT calls into the bitTime subroutine;
+ // we're exploiting the RCALL and RET as 3- and 4-cycle NOPs!
+ asm volatile("headD:"
+ "\n\t" // (T = 0)
+ "out %[port], %[hi]"
+ "\n\t" // (T = 1)
+ "rcall bitTimeD"
+ "\n\t" // Bit 7 (T = 15)
+ "out %[port], %[hi]"
+ "\n\t"
+ "rcall bitTimeD"
+ "\n\t" // Bit 6
+ "out %[port], %[hi]"
+ "\n\t"
+ "rcall bitTimeD"
+ "\n\t" // Bit 5
+ "out %[port], %[hi]"
+ "\n\t"
+ "rcall bitTimeD"
+ "\n\t" // Bit 4
+ "out %[port], %[hi]"
+ "\n\t"
+ "rcall bitTimeD"
+ "\n\t" // Bit 3
+ "out %[port], %[hi]"
+ "\n\t"
+ "rcall bitTimeD"
+ "\n\t" // Bit 2
+ "out %[port], %[hi]"
+ "\n\t"
+ "rcall bitTimeD"
+ "\n\t" // Bit 1
+ // Bit 0:
+ "out %[port] , %[hi]"
+ "\n\t" // 1 PORT = hi (T = 1)
+ "rjmp .+0"
+ "\n\t" // 2 nop nop (T = 3)
+ "ld %[byte] , %a[ptr]+"
+ "\n\t" // 2 b = *ptr++ (T = 5)
+ "out %[port] , %[next]"
+ "\n\t" // 1 PORT = next (T = 6)
+ "mov %[next] , %[lo]"
+ "\n\t" // 1 next = lo (T = 7)
+ "sbrc %[byte] , 7"
+ "\n\t" // 1-2 if(b & 0x80) (T = 8)
+ "mov %[next] , %[hi]"
+ "\n\t" // 0-1 next = hi (T = 9)
+ "nop"
+ "\n\t" // 1 (T = 10)
+ "out %[port] , %[lo]"
+ "\n\t" // 1 PORT = lo (T = 11)
+ "sbiw %[count], 1"
+ "\n\t" // 2 i-- (T = 13)
+ "brne headD"
+ "\n\t" // 2 if(i != 0) -> (next byte)
+ "rjmp doneD"
+ "\n\t"
+ "bitTimeD:"
+ "\n\t" // nop nop nop (T = 4)
+ "out %[port], %[next]"
+ "\n\t" // 1 PORT = next (T = 5)
+ "mov %[next], %[lo]"
+ "\n\t" // 1 next = lo (T = 6)
+ "rol %[byte]"
+ "\n\t" // 1 b <<= 1 (T = 7)
+ "sbrc %[byte], 7"
+ "\n\t" // 1-2 if(b & 0x80) (T = 8)
+ "mov %[next], %[hi]"
+ "\n\t" // 0-1 next = hi (T = 9)
+ "nop"
+ "\n\t" // 1 (T = 10)
+ "out %[port], %[lo]"
+ "\n\t" // 1 PORT = lo (T = 11)
+ "ret"
+ "\n\t" // 4 nop nop nop nop (T = 15)
+ "doneD:"
+ "\n"
+ : [byte] "+r"(b), [next] "+r"(next), [count] "+w"(i)
+ : [port] "I"(_SFR_IO_ADDR(PORTD)), [ptr] "e"(ptr),
+ [hi] "r"(hi), [lo] "r"(lo));
+
+#if defined(PORTB) || defined(PORTC) || defined(PORTF)
+ } else // other PORT(s)
+#endif // defined(PORTB/C/F)
+#endif // defined(PORTD)
+
+ // PORTB OUTPUT ----------------------------------------------------
+
+#if defined(PORTB)
+#if defined(PORTD) || defined(PORTC) || defined(PORTF)
+ if (port == &PORTB) {
+#endif // defined(PORTD/C/F)
+
+ hi = PORTB | pinMask;
+ lo = PORTB & ~pinMask;
+ next = lo;
+ if (b & 0x80)
+ next = hi;
+
+ // Same as above, just set for PORTB & stripped of comments
+ asm volatile("headB:"
+ "\n\t"
+ "out %[port], %[hi]"
+ "\n\t"
+ "rcall bitTimeB"
+ "\n\t"
+ "out %[port], %[hi]"
+ "\n\t"
+ "rcall bitTimeB"
+ "\n\t"
+ "out %[port], %[hi]"
+ "\n\t"
+ "rcall bitTimeB"
+ "\n\t"
+ "out %[port], %[hi]"
+ "\n\t"
+ "rcall bitTimeB"
+ "\n\t"
+ "out %[port], %[hi]"
+ "\n\t"
+ "rcall bitTimeB"
+ "\n\t"
+ "out %[port], %[hi]"
+ "\n\t"
+ "rcall bitTimeB"
+ "\n\t"
+ "out %[port], %[hi]"
+ "\n\t"
+ "rcall bitTimeB"
+ "\n\t"
+ "out %[port] , %[hi]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "ld %[byte] , %a[ptr]+"
+ "\n\t"
+ "out %[port] , %[next]"
+ "\n\t"
+ "mov %[next] , %[lo]"
+ "\n\t"
+ "sbrc %[byte] , 7"
+ "\n\t"
+ "mov %[next] , %[hi]"
+ "\n\t"
+ "nop"
+ "\n\t"
+ "out %[port] , %[lo]"
+ "\n\t"
+ "sbiw %[count], 1"
+ "\n\t"
+ "brne headB"
+ "\n\t"
+ "rjmp doneB"
+ "\n\t"
+ "bitTimeB:"
+ "\n\t"
+ "out %[port], %[next]"
+ "\n\t"
+ "mov %[next], %[lo]"
+ "\n\t"
+ "rol %[byte]"
+ "\n\t"
+ "sbrc %[byte], 7"
+ "\n\t"
+ "mov %[next], %[hi]"
+ "\n\t"
+ "nop"
+ "\n\t"
+ "out %[port], %[lo]"
+ "\n\t"
+ "ret"
+ "\n\t"
+ "doneB:"
+ "\n"
+ : [byte] "+r"(b), [next] "+r"(next), [count] "+w"(i)
+ : [port] "I"(_SFR_IO_ADDR(PORTB)), [ptr] "e"(ptr),
+ [hi] "r"(hi), [lo] "r"(lo));
+
+#if defined(PORTD) || defined(PORTC) || defined(PORTF)
+ }
+#endif
+#if defined(PORTC) || defined(PORTF)
+ else
+#endif // defined(PORTC/F)
+#endif // defined(PORTB)
+
+ // PORTC OUTPUT ----------------------------------------------------
+
+#if defined(PORTC)
+#if defined(PORTD) || defined(PORTB) || defined(PORTF)
+ if (port == &PORTC) {
+#endif // defined(PORTD/B/F)
+
+ hi = PORTC | pinMask;
+ lo = PORTC & ~pinMask;
+ next = lo;
+ if (b & 0x80)
+ next = hi;
+
+ // Same as above, just set for PORTC & stripped of comments
+ asm volatile("headC:"
+ "\n\t"
+ "out %[port], %[hi]"
+ "\n\t"
+ "rcall bitTimeC"
+ "\n\t"
+ "out %[port], %[hi]"
+ "\n\t"
+ "rcall bitTimeC"
+ "\n\t"
+ "out %[port], %[hi]"
+ "\n\t"
+ "rcall bitTimeC"
+ "\n\t"
+ "out %[port], %[hi]"
+ "\n\t"
+ "rcall bitTimeC"
+ "\n\t"
+ "out %[port], %[hi]"
+ "\n\t"
+ "rcall bitTimeC"
+ "\n\t"
+ "out %[port], %[hi]"
+ "\n\t"
+ "rcall bitTimeC"
+ "\n\t"
+ "out %[port], %[hi]"
+ "\n\t"
+ "rcall bitTimeC"
+ "\n\t"
+ "out %[port] , %[hi]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "ld %[byte] , %a[ptr]+"
+ "\n\t"
+ "out %[port] , %[next]"
+ "\n\t"
+ "mov %[next] , %[lo]"
+ "\n\t"
+ "sbrc %[byte] , 7"
+ "\n\t"
+ "mov %[next] , %[hi]"
+ "\n\t"
+ "nop"
+ "\n\t"
+ "out %[port] , %[lo]"
+ "\n\t"
+ "sbiw %[count], 1"
+ "\n\t"
+ "brne headC"
+ "\n\t"
+ "rjmp doneC"
+ "\n\t"
+ "bitTimeC:"
+ "\n\t"
+ "out %[port], %[next]"
+ "\n\t"
+ "mov %[next], %[lo]"
+ "\n\t"
+ "rol %[byte]"
+ "\n\t"
+ "sbrc %[byte], 7"
+ "\n\t"
+ "mov %[next], %[hi]"
+ "\n\t"
+ "nop"
+ "\n\t"
+ "out %[port], %[lo]"
+ "\n\t"
+ "ret"
+ "\n\t"
+ "doneC:"
+ "\n"
+ : [byte] "+r"(b), [next] "+r"(next), [count] "+w"(i)
+ : [port] "I"(_SFR_IO_ADDR(PORTC)), [ptr] "e"(ptr),
+ [hi] "r"(hi), [lo] "r"(lo));
+
+#if defined(PORTD) || defined(PORTB) || defined(PORTF)
+ }
+#endif // defined(PORTD/B/F)
+#if defined(PORTF)
+ else
+#endif
+#endif // defined(PORTC)
+
+ // PORTF OUTPUT ----------------------------------------------------
+
+#if defined(PORTF)
+#if defined(PORTD) || defined(PORTB) || defined(PORTC)
+ if (port == &PORTF) {
+#endif // defined(PORTD/B/C)
+
+ hi = PORTF | pinMask;
+ lo = PORTF & ~pinMask;
+ next = lo;
+ if (b & 0x80)
+ next = hi;
+
+ // Same as above, just set for PORTF & stripped of comments
+ asm volatile("headF:"
+ "\n\t"
+ "out %[port], %[hi]"
+ "\n\t"
+ "rcall bitTimeC"
+ "\n\t"
+ "out %[port], %[hi]"
+ "\n\t"
+ "rcall bitTimeC"
+ "\n\t"
+ "out %[port], %[hi]"
+ "\n\t"
+ "rcall bitTimeC"
+ "\n\t"
+ "out %[port], %[hi]"
+ "\n\t"
+ "rcall bitTimeC"
+ "\n\t"
+ "out %[port], %[hi]"
+ "\n\t"
+ "rcall bitTimeC"
+ "\n\t"
+ "out %[port], %[hi]"
+ "\n\t"
+ "rcall bitTimeC"
+ "\n\t"
+ "out %[port], %[hi]"
+ "\n\t"
+ "rcall bitTimeC"
+ "\n\t"
+ "out %[port] , %[hi]"
+ "\n\t"
+ "rjmp .+0"
+ "\n\t"
+ "ld %[byte] , %a[ptr]+"
+ "\n\t"
+ "out %[port] , %[next]"
+ "\n\t"
+ "mov %[next] , %[lo]"
+ "\n\t"
+ "sbrc %[byte] , 7"
+ "\n\t"
+ "mov %[next] , %[hi]"
+ "\n\t"
+ "nop"
+ "\n\t"
+ "out %[port] , %[lo]"
+ "\n\t"
+ "sbiw %[count], 1"
+ "\n\t"
+ "brne headF"
+ "\n\t"
+ "rjmp doneC"
+ "\n\t"
+ "bitTimeC:"
+ "\n\t"
+ "out %[port], %[next]"
+ "\n\t"
+ "mov %[next], %[lo]"
+ "\n\t"
+ "rol %[byte]"
+ "\n\t"
+ "sbrc %[byte], 7"
+ "\n\t"
+ "mov %[next], %[hi]"
+ "\n\t"
+ "nop"
+ "\n\t"
+ "out %[port], %[lo]"
+ "\n\t"
+ "ret"
+ "\n\t"
+ "doneC:"
+ "\n"
+ : [byte] "+r"(b), [next] "+r"(next), [count] "+w"(i)
+ : [port] "I"(_SFR_IO_ADDR(PORTF)), [ptr] "e"(ptr),
+ [hi] "r"(hi), [lo] "r"(lo));
+
+#if defined(PORTD) || defined(PORTB) || defined(PORTC)
+ }
+#endif // defined(PORTD/B/C)
+#endif // defined(PORTF)
+
+#if defined(NEO_KHZ400)
+ } else { // 400 KHz
+
+ // 30 instruction clocks per bit: HHHHHHxxxxxxxxxLLLLLLLLLLLLLLL
+ // ST instructions: ^ ^ ^ (T=0,6,15)
+
+ volatile uint8_t next, bit;
+
+ hi = *port | pinMask;
+ lo = *port & ~pinMask;
+ next = lo;
+ bit = 8;
+
+ asm volatile("head30:"
+ "\n\t" // Clk Pseudocode (T = 0)
+ "st %a[port], %[hi]"
+ "\n\t" // 2 PORT = hi (T = 2)
+ "sbrc %[byte] , 7"
+ "\n\t" // 1-2 if(b & 128)
+ "mov %[next], %[hi]"
+ "\n\t" // 0-1 next = hi (T = 4)
+ "rjmp .+0"
+ "\n\t" // 2 nop nop (T = 6)
+ "st %a[port], %[next]"
+ "\n\t" // 2 PORT = next (T = 8)
+ "rjmp .+0"
+ "\n\t" // 2 nop nop (T = 10)
+ "rjmp .+0"
+ "\n\t" // 2 nop nop (T = 12)
+ "rjmp .+0"
+ "\n\t" // 2 nop nop (T = 14)
+ "nop"
+ "\n\t" // 1 nop (T = 15)
+ "st %a[port], %[lo]"
+ "\n\t" // 2 PORT = lo (T = 17)
+ "rjmp .+0"
+ "\n\t" // 2 nop nop (T = 19)
+ "dec %[bit]"
+ "\n\t" // 1 bit-- (T = 20)
+ "breq nextbyte30"
+ "\n\t" // 1-2 if(bit == 0)
+ "rol %[byte]"
+ "\n\t" // 1 b <<= 1 (T = 22)
+ "rjmp .+0"
+ "\n\t" // 2 nop nop (T = 24)
+ "rjmp .+0"
+ "\n\t" // 2 nop nop (T = 26)
+ "rjmp .+0"
+ "\n\t" // 2 nop nop (T = 28)
+ "rjmp head30"
+ "\n\t" // 2 -> head30 (next bit out)
+ "nextbyte30:"
+ "\n\t" // (T = 22)
+ "nop"
+ "\n\t" // 1 nop (T = 23)
+ "ldi %[bit] , 8"
+ "\n\t" // 1 bit = 8 (T = 24)
+ "ld %[byte] , %a[ptr]+"
+ "\n\t" // 2 b = *ptr++ (T = 26)
+ "sbiw %[count], 1"
+ "\n\t" // 2 i-- (T = 28)
+ "brne head30"
+ "\n" // 1-2 if(i != 0) -> (next byte)
+ : [port] "+e"(port), [byte] "+r"(b), [bit] "+r"(bit),
+ [next] "+r"(next), [count] "+w"(i)
+ : [hi] "r"(hi), [lo] "r"(lo), [ptr] "e"(ptr));
+ }
+#endif // NEO_KHZ400
+
+// 16 MHz(ish) AVR --------------------------------------------------------
+#elif (F_CPU >= 15400000UL) && (F_CPU <= 19000000L)
+
+#if defined(NEO_KHZ400) // 800 KHz check needed only if 400 KHz support enabled
+ if (is800KHz) {
+#endif
+
+ // WS2811 and WS2812 have different hi/lo duty cycles; this is
+ // similar but NOT an exact copy of the prior 400-on-8 code.
+
+ // 20 inst. clocks per bit: HHHHHxxxxxxxxLLLLLLL
+ // ST instructions: ^ ^ ^ (T=0,5,13)
+
+ volatile uint8_t next, bit;
+
+ hi = *port | pinMask;
+ lo = *port & ~pinMask;
+ next = lo;
+ bit = 8;
+
+ asm volatile("head20:"
+ "\n\t" // Clk Pseudocode (T = 0)
+ "st %a[port], %[hi]"
+ "\n\t" // 2 PORT = hi (T = 2)
+ "sbrc %[byte], 7"
+ "\n\t" // 1-2 if(b & 128)
+ "mov %[next], %[hi]"
+ "\n\t" // 0-1 next = hi (T = 4)
+ "dec %[bit]"
+ "\n\t" // 1 bit-- (T = 5)
+ "st %a[port], %[next]"
+ "\n\t" // 2 PORT = next (T = 7)
+ "mov %[next] , %[lo]"
+ "\n\t" // 1 next = lo (T = 8)
+ "breq nextbyte20"
+ "\n\t" // 1-2 if(bit == 0) (from dec above)
+ "rol %[byte]"
+ "\n\t" // 1 b <<= 1 (T = 10)
+ "rjmp .+0"
+ "\n\t" // 2 nop nop (T = 12)
+ "nop"
+ "\n\t" // 1 nop (T = 13)
+ "st %a[port], %[lo]"
+ "\n\t" // 2 PORT = lo (T = 15)
+ "nop"
+ "\n\t" // 1 nop (T = 16)
+ "rjmp .+0"
+ "\n\t" // 2 nop nop (T = 18)
+ "rjmp head20"
+ "\n\t" // 2 -> head20 (next bit out)
+ "nextbyte20:"
+ "\n\t" // (T = 10)
+ "ldi %[bit] , 8"
+ "\n\t" // 1 bit = 8 (T = 11)
+ "ld %[byte] , %a[ptr]+"
+ "\n\t" // 2 b = *ptr++ (T = 13)
+ "st %a[port], %[lo]"
+ "\n\t" // 2 PORT = lo (T = 15)
+ "nop"
+ "\n\t" // 1 nop (T = 16)
+ "sbiw %[count], 1"
+ "\n\t" // 2 i-- (T = 18)
+ "brne head20"
+ "\n" // 2 if(i != 0) -> (next byte)
+ : [port] "+e"(port), [byte] "+r"(b), [bit] "+r"(bit),
+ [next] "+r"(next), [count] "+w"(i)
+ : [ptr] "e"(ptr), [hi] "r"(hi), [lo] "r"(lo));
+
+#if defined(NEO_KHZ400)
+ } else { // 400 KHz
+
+ // The 400 KHz clock on 16 MHz MCU is the most 'relaxed' version.
+
+ // 40 inst. clocks per bit: HHHHHHHHxxxxxxxxxxxxLLLLLLLLLLLLLLLLLLLL
+ // ST instructions: ^ ^ ^ (T=0,8,20)
+
+ volatile uint8_t next, bit;
+
+ hi = *port | pinMask;
+ lo = *port & ~pinMask;
+ next = lo;
+ bit = 8;
+
+ asm volatile("head40:"
+ "\n\t" // Clk Pseudocode (T = 0)
+ "st %a[port], %[hi]"
+ "\n\t" // 2 PORT = hi (T = 2)
+ "sbrc %[byte] , 7"
+ "\n\t" // 1-2 if(b & 128)
+ "mov %[next] , %[hi]"
+ "\n\t" // 0-1 next = hi (T = 4)
+ "rjmp .+0"
+ "\n\t" // 2 nop nop (T = 6)
+ "rjmp .+0"
+ "\n\t" // 2 nop nop (T = 8)
+ "st %a[port], %[next]"
+ "\n\t" // 2 PORT = next (T = 10)
+ "rjmp .+0"
+ "\n\t" // 2 nop nop (T = 12)
+ "rjmp .+0"
+ "\n\t" // 2 nop nop (T = 14)
+ "rjmp .+0"
+ "\n\t" // 2 nop nop (T = 16)
+ "rjmp .+0"
+ "\n\t" // 2 nop nop (T = 18)
+ "rjmp .+0"
+ "\n\t" // 2 nop nop (T = 20)
+ "st %a[port], %[lo]"
+ "\n\t" // 2 PORT = lo (T = 22)
+ "nop"
+ "\n\t" // 1 nop (T = 23)
+ "mov %[next] , %[lo]"
+ "\n\t" // 1 next = lo (T = 24)
+ "dec %[bit]"
+ "\n\t" // 1 bit-- (T = 25)
+ "breq nextbyte40"
+ "\n\t" // 1-2 if(bit == 0)
+ "rol %[byte]"
+ "\n\t" // 1 b <<= 1 (T = 27)
+ "nop"
+ "\n\t" // 1 nop (T = 28)
+ "rjmp .+0"
+ "\n\t" // 2 nop nop (T = 30)
+ "rjmp .+0"
+ "\n\t" // 2 nop nop (T = 32)
+ "rjmp .+0"
+ "\n\t" // 2 nop nop (T = 34)
+ "rjmp .+0"
+ "\n\t" // 2 nop nop (T = 36)
+ "rjmp .+0"
+ "\n\t" // 2 nop nop (T = 38)
+ "rjmp head40"
+ "\n\t" // 2 -> head40 (next bit out)
+ "nextbyte40:"
+ "\n\t" // (T = 27)
+ "ldi %[bit] , 8"
+ "\n\t" // 1 bit = 8 (T = 28)
+ "ld %[byte] , %a[ptr]+"
+ "\n\t" // 2 b = *ptr++ (T = 30)
+ "rjmp .+0"
+ "\n\t" // 2 nop nop (T = 32)
+ "st %a[port], %[lo]"
+ "\n\t" // 2 PORT = lo (T = 34)
+ "rjmp .+0"
+ "\n\t" // 2 nop nop (T = 36)
+ "sbiw %[count], 1"
+ "\n\t" // 2 i-- (T = 38)
+ "brne head40"
+ "\n" // 1-2 if(i != 0) -> (next byte)
+ : [port] "+e"(port), [byte] "+r"(b), [bit] "+r"(bit),
+ [next] "+r"(next), [count] "+w"(i)
+ : [ptr] "e"(ptr), [hi] "r"(hi), [lo] "r"(lo));
+ }
+#endif // NEO_KHZ400
+
+#else
+#error "CPU SPEED NOT SUPPORTED"
+#endif // end F_CPU ifdefs on __AVR__
+
+ // END AVR ----------------------------------------------------------------
+
+#elif defined(__arm__)
+
+ // ARM MCUs -- Teensy 3.0, 3.1, LC, Arduino Due, RP2040 -------------------
+
+#if defined(ARDUINO_ARCH_RP2040)
+ // Use PIO
+ rp2040Show(pin, pixels, numBytes, is800KHz);
+
+#elif defined(TEENSYDUINO) && \
+ defined(KINETISK) // Teensy 3.0, 3.1, 3.2, 3.5, 3.6
+#define CYCLES_800_T0H (F_CPU / 4000000)
+#define CYCLES_800_T1H (F_CPU / 1250000)
+#define CYCLES_800 (F_CPU / 800000)
+#define CYCLES_400_T0H (F_CPU / 2000000)
+#define CYCLES_400_T1H (F_CPU / 833333)
+#define CYCLES_400 (F_CPU / 400000)
+
+ uint8_t *p = pixels, *end = p + numBytes, pix, mask;
+ volatile uint8_t *set = portSetRegister(pin), *clr = portClearRegister(pin);
+ uint32_t cyc;
+
+ ARM_DEMCR |= ARM_DEMCR_TRCENA;
+ ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;
+
+#if defined(NEO_KHZ400) // 800 KHz check needed only if 400 KHz support enabled
+ if (is800KHz) {
+#endif
+ cyc = ARM_DWT_CYCCNT + CYCLES_800;
+ while (p < end) {
+ pix = *p++;
+ for (mask = 0x80; mask; mask >>= 1) {
+ while (ARM_DWT_CYCCNT - cyc < CYCLES_800)
+ ;
+ cyc = ARM_DWT_CYCCNT;
+ *set = 1;
+ if (pix & mask) {
+ while (ARM_DWT_CYCCNT - cyc < CYCLES_800_T1H)
+ ;
+ } else {
+ while (ARM_DWT_CYCCNT - cyc < CYCLES_800_T0H)
+ ;
+ }
+ *clr = 1;
+ }
+ }
+ while (ARM_DWT_CYCCNT - cyc < CYCLES_800)
+ ;
+#if defined(NEO_KHZ400)
+ } else { // 400 kHz bitstream
+ cyc = ARM_DWT_CYCCNT + CYCLES_400;
+ while (p < end) {
+ pix = *p++;
+ for (mask = 0x80; mask; mask >>= 1) {
+ while (ARM_DWT_CYCCNT - cyc < CYCLES_400)
+ ;
+ cyc = ARM_DWT_CYCCNT;
+ *set = 1;
+ if (pix & mask) {
+ while (ARM_DWT_CYCCNT - cyc < CYCLES_400_T1H)
+ ;
+ } else {
+ while (ARM_DWT_CYCCNT - cyc < CYCLES_400_T0H)
+ ;
+ }
+ *clr = 1;
+ }
+ }
+ while (ARM_DWT_CYCCNT - cyc < CYCLES_400)
+ ;
+ }
+#endif // NEO_KHZ400
+
+#elif defined(TEENSYDUINO) && (defined(__IMXRT1052__) || defined(__IMXRT1062__))
+#define CYCLES_800_T0H (F_CPU_ACTUAL / 4000000)
+#define CYCLES_800_T1H (F_CPU_ACTUAL / 1250000)
+#define CYCLES_800 (F_CPU_ACTUAL / 800000)
+#define CYCLES_400_T0H (F_CPU_ACTUAL / 2000000)
+#define CYCLES_400_T1H (F_CPU_ACTUAL / 833333)
+#define CYCLES_400 (F_CPU_ACTUAL / 400000)
+
+ uint8_t *p = pixels, *end = p + numBytes, pix, mask;
+ volatile uint32_t *set = portSetRegister(pin), *clr = portClearRegister(pin);
+ uint32_t cyc, msk = digitalPinToBitMask(pin);
+
+ ARM_DEMCR |= ARM_DEMCR_TRCENA;
+ ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;
+
+#if defined(NEO_KHZ400) // 800 KHz check needed only if 400 KHz support enabled
+ if (is800KHz) {
+#endif
+ cyc = ARM_DWT_CYCCNT + CYCLES_800;
+ while (p < end) {
+ pix = *p++;
+ for (mask = 0x80; mask; mask >>= 1) {
+ while (ARM_DWT_CYCCNT - cyc < CYCLES_800)
+ ;
+ cyc = ARM_DWT_CYCCNT;
+ *set = msk;
+ if (pix & mask) {
+ while (ARM_DWT_CYCCNT - cyc < CYCLES_800_T1H)
+ ;
+ } else {
+ while (ARM_DWT_CYCCNT - cyc < CYCLES_800_T0H)
+ ;
+ }
+ *clr = msk;
+ }
+ }
+ while (ARM_DWT_CYCCNT - cyc < CYCLES_800)
+ ;
+#if defined(NEO_KHZ400)
+ } else { // 400 kHz bitstream
+ cyc = ARM_DWT_CYCCNT + CYCLES_400;
+ while (p < end) {
+ pix = *p++;
+ for (mask = 0x80; mask; mask >>= 1) {
+ while (ARM_DWT_CYCCNT - cyc < CYCLES_400)
+ ;
+ cyc = ARM_DWT_CYCCNT;
+ *set = msk;
+ if (pix & mask) {
+ while (ARM_DWT_CYCCNT - cyc < CYCLES_400_T1H)
+ ;
+ } else {
+ while (ARM_DWT_CYCCNT - cyc < CYCLES_400_T0H)
+ ;
+ }
+ *clr = msk;
+ }
+ }
+ while (ARM_DWT_CYCCNT - cyc < CYCLES_400)
+ ;
+ }
+#endif // NEO_KHZ400
+
+#elif defined(TEENSYDUINO) && defined(__MKL26Z64__) // Teensy-LC
+
+#if F_CPU == 48000000
+ uint8_t *p = pixels, pix, count, dly, bitmask = digitalPinToBitMask(pin);
+ volatile uint8_t *reg = portSetRegister(pin);
+ uint32_t num = numBytes;
+ asm volatile("L%=_begin:"
+ "\n\t"
+ "ldrb %[pix], [%[p], #0]"
+ "\n\t"
+ "lsl %[pix], #24"
+ "\n\t"
+ "movs %[count], #7"
+ "\n\t"
+ "L%=_loop:"
+ "\n\t"
+ "lsl %[pix], #1"
+ "\n\t"
+ "bcs L%=_loop_one"
+ "\n\t"
+ "L%=_loop_zero:"
+ "\n\t"
+ "strb %[bitmask], [%[reg], #0]"
+ "\n\t"
+ "movs %[dly], #4"
+ "\n\t"
+ "L%=_loop_delay_T0H:"
+ "\n\t"
+ "sub %[dly], #1"
+ "\n\t"
+ "bne L%=_loop_delay_T0H"
+ "\n\t"
+ "strb %[bitmask], [%[reg], #4]"
+ "\n\t"
+ "movs %[dly], #13"
+ "\n\t"
+ "L%=_loop_delay_T0L:"
+ "\n\t"
+ "sub %[dly], #1"
+ "\n\t"
+ "bne L%=_loop_delay_T0L"
+ "\n\t"
+ "b L%=_next"
+ "\n\t"
+ "L%=_loop_one:"
+ "\n\t"
+ "strb %[bitmask], [%[reg], #0]"
+ "\n\t"
+ "movs %[dly], #13"
+ "\n\t"
+ "L%=_loop_delay_T1H:"
+ "\n\t"
+ "sub %[dly], #1"
+ "\n\t"
+ "bne L%=_loop_delay_T1H"
+ "\n\t"
+ "strb %[bitmask], [%[reg], #4]"
+ "\n\t"
+ "movs %[dly], #4"
+ "\n\t"
+ "L%=_loop_delay_T1L:"
+ "\n\t"
+ "sub %[dly], #1"
+ "\n\t"
+ "bne L%=_loop_delay_T1L"
+ "\n\t"
+ "nop"
+ "\n\t"
+ "L%=_next:"
+ "\n\t"
+ "sub %[count], #1"
+ "\n\t"
+ "bne L%=_loop"
+ "\n\t"
+ "lsl %[pix], #1"
+ "\n\t"
+ "bcs L%=_last_one"
+ "\n\t"
+ "L%=_last_zero:"
+ "\n\t"
+ "strb %[bitmask], [%[reg], #0]"
+ "\n\t"
+ "movs %[dly], #4"
+ "\n\t"
+ "L%=_last_delay_T0H:"
+ "\n\t"
+ "sub %[dly], #1"
+ "\n\t"
+ "bne L%=_last_delay_T0H"
+ "\n\t"
+ "strb %[bitmask], [%[reg], #4]"
+ "\n\t"
+ "movs %[dly], #10"
+ "\n\t"
+ "L%=_last_delay_T0L:"
+ "\n\t"
+ "sub %[dly], #1"
+ "\n\t"
+ "bne L%=_last_delay_T0L"
+ "\n\t"
+ "b L%=_repeat"
+ "\n\t"
+ "L%=_last_one:"
+ "\n\t"
+ "strb %[bitmask], [%[reg], #0]"
+ "\n\t"
+ "movs %[dly], #13"
+ "\n\t"
+ "L%=_last_delay_T1H:"
+ "\n\t"
+ "sub %[dly], #1"
+ "\n\t"
+ "bne L%=_last_delay_T1H"
+ "\n\t"
+ "strb %[bitmask], [%[reg], #4]"
+ "\n\t"
+ "movs %[dly], #1"
+ "\n\t"
+ "L%=_last_delay_T1L:"
+ "\n\t"
+ "sub %[dly], #1"
+ "\n\t"
+ "bne L%=_last_delay_T1L"
+ "\n\t"
+ "nop"
+ "\n\t"
+ "L%=_repeat:"
+ "\n\t"
+ "add %[p], #1"
+ "\n\t"
+ "sub %[num], #1"
+ "\n\t"
+ "bne L%=_begin"
+ "\n\t"
+ "L%=_done:"
+ "\n\t"
+ : [p] "+r"(p), [pix] "=&r"(pix), [count] "=&r"(count),
+ [dly] "=&r"(dly), [num] "+r"(num)
+ : [bitmask] "r"(bitmask), [reg] "r"(reg));
+#else
+#error "Sorry, only 48 MHz is supported, please set Tools > CPU Speed to 48 MHz"
+#endif // F_CPU == 48000000
+
+ // Begin of support for nRF52 based boards -------------------------
+
+#elif defined(NRF52) || defined(NRF52_SERIES)
+// [[[Begin of the Neopixel NRF52 EasyDMA implementation
+// by the Hackerspace San Salvador]]]
+// This technique uses the PWM peripheral on the NRF52. The PWM uses the
+// EasyDMA feature included on the chip. This technique loads the duty
+// cycle configuration for each cycle when the PWM is enabled. For this
+// to work we need to store a 16 bit configuration for each bit of the
+// RGB(W) values in the pixel buffer.
+// Comparator values for the PWM were hand picked and are guaranteed to
+// be 100% organic to preserve freshness and high accuracy. Current
+// parameters are:
+// * PWM Clock: 16Mhz
+// * Minimum step time: 62.5ns
+// * Time for zero in high (T0H): 0.31ms
+// * Time for one in high (T1H): 0.75ms
+// * Cycle time: 1.25us
+// * Frequency: 800Khz
+// For 400Khz we just double the calculated times.
+// ---------- BEGIN Constants for the EasyDMA implementation -----------
+// The PWM starts the duty cycle in LOW. To start with HIGH we
+// need to set the 15th bit on each register.
+
+// WS2812 (rev A) timing is 0.35 and 0.7us
+//#define MAGIC_T0H 5UL | (0x8000) // 0.3125us
+//#define MAGIC_T1H 12UL | (0x8000) // 0.75us
+
+// WS2812B (rev B) timing is 0.4 and 0.8 us
+#define MAGIC_T0H 6UL | (0x8000) // 0.375us
+#define MAGIC_T1H 13UL | (0x8000) // 0.8125us
+
+// WS2811 (400 khz) timing is 0.5 and 1.2
+#define MAGIC_T0H_400KHz 8UL | (0x8000) // 0.5us
+#define MAGIC_T1H_400KHz 19UL | (0x8000) // 1.1875us
+
+// For 400Khz, we double value of CTOPVAL
+#define CTOPVAL 20UL // 1.25us
+#define CTOPVAL_400KHz 40UL // 2.5us
+
+// ---------- END Constants for the EasyDMA implementation -------------
+//
+// If there is no device available an alternative cycle-counter
+// implementation is tried.
+// The nRF52 runs with a fixed clock of 64Mhz. The alternative
+// implementation is the same as the one used for the Teensy 3.0/1/2 but
+// with the Nordic SDK HAL & registers syntax.
+// The number of cycles was hand picked and is guaranteed to be 100%
+// organic to preserve freshness and high accuracy.
+// ---------- BEGIN Constants for cycle counter implementation ---------
+#define CYCLES_800_T0H 18 // ~0.36 uS
+#define CYCLES_800_T1H 41 // ~0.76 uS
+#define CYCLES_800 71 // ~1.25 uS
+
+#define CYCLES_400_T0H 26 // ~0.50 uS
+#define CYCLES_400_T1H 70 // ~1.26 uS
+#define CYCLES_400 156 // ~2.50 uS
+ // ---------- END of Constants for cycle counter implementation --------
+
+ // To support both the SoftDevice + Neopixels we use the EasyDMA
+ // feature from the NRF25. However this technique implies to
+ // generate a pattern and store it on the memory. The actual
+ // memory used in bytes corresponds to the following formula:
+ // totalMem = numBytes*8*2+(2*2)
+ // The two additional bytes at the end are needed to reset the
+ // sequence.
+ //
+ // If there is not enough memory, we will fall back to cycle counter
+ // using DWT
+ uint32_t pattern_size =
+ numBytes * 8 * sizeof(uint16_t) + 2 * sizeof(uint16_t);
+ uint16_t *pixels_pattern = NULL;
+
+ NRF_PWM_Type *pwm = NULL;
+
+ // Try to find a free PWM device, which is not enabled
+ // and has no connected pins
+ NRF_PWM_Type *PWM[] = {
+ NRF_PWM0,
+ NRF_PWM1,
+ NRF_PWM2
+#if defined(NRF_PWM3)
+ ,
+ NRF_PWM3
+#endif
+ };
+
+ for (unsigned int device = 0; device < (sizeof(PWM) / sizeof(PWM[0]));
+ device++) {
+ if ((PWM[device]->ENABLE == 0) &&
+ (PWM[device]->PSEL.OUT[0] & PWM_PSEL_OUT_CONNECT_Msk) &&
+ (PWM[device]->PSEL.OUT[1] & PWM_PSEL_OUT_CONNECT_Msk) &&
+ (PWM[device]->PSEL.OUT[2] & PWM_PSEL_OUT_CONNECT_Msk) &&
+ (PWM[device]->PSEL.OUT[3] & PWM_PSEL_OUT_CONNECT_Msk)) {
+ pwm = PWM[device];
+ break;
+ }
+ }
+
+ // only malloc if there is PWM device available
+ if (pwm != NULL) {
+#if defined(ARDUINO_NRF52_ADAFRUIT) // use thread-safe malloc
+ pixels_pattern = (uint16_t *)rtos_malloc(pattern_size);
+#else
+ pixels_pattern = (uint16_t *)malloc(pattern_size);
+#endif
+ }
+
+ // Use the identified device to choose the implementation
+ // If a PWM device is available use DMA
+ if ((pixels_pattern != NULL) && (pwm != NULL)) {
+ uint16_t pos = 0; // bit position
+
+ for (uint16_t n = 0; n < numBytes; n++) {
+ uint8_t pix = pixels[n];
+
+ for (uint8_t mask = 0x80; mask > 0; mask >>= 1) {
+#if defined(NEO_KHZ400)
+ if (!is800KHz) {
+ pixels_pattern[pos] =
+ (pix & mask) ? MAGIC_T1H_400KHz : MAGIC_T0H_400KHz;
+ } else
+#endif
+ {
+ pixels_pattern[pos] = (pix & mask) ? MAGIC_T1H : MAGIC_T0H;
+ }
+
+ pos++;
+ }
+ }
+
+ // Zero padding to indicate the end of que sequence
+ pixels_pattern[pos++] = 0 | (0x8000); // Seq end
+ pixels_pattern[pos++] = 0 | (0x8000); // Seq end
+
+ // Set the wave mode to count UP
+ pwm->MODE = (PWM_MODE_UPDOWN_Up << PWM_MODE_UPDOWN_Pos);
+
+ // Set the PWM to use the 16MHz clock
+ pwm->PRESCALER =
+ (PWM_PRESCALER_PRESCALER_DIV_1 << PWM_PRESCALER_PRESCALER_Pos);
+
+ // Setting of the maximum count
+ // but keeping it on 16Mhz allows for more granularity just
+ // in case someone wants to do more fine-tuning of the timing.
+#if defined(NEO_KHZ400)
+ if (!is800KHz) {
+ pwm->COUNTERTOP = (CTOPVAL_400KHz << PWM_COUNTERTOP_COUNTERTOP_Pos);
+ } else
+#endif
+ {
+ pwm->COUNTERTOP = (CTOPVAL << PWM_COUNTERTOP_COUNTERTOP_Pos);
+ }
+
+ // Disable loops, we want the sequence to repeat only once
+ pwm->LOOP = (PWM_LOOP_CNT_Disabled << PWM_LOOP_CNT_Pos);
+
+ // On the "Common" setting the PWM uses the same pattern for the
+ // for supported sequences. The pattern is stored on half-word
+ // of 16bits
+ pwm->DECODER = (PWM_DECODER_LOAD_Common << PWM_DECODER_LOAD_Pos) |
+ (PWM_DECODER_MODE_RefreshCount << PWM_DECODER_MODE_Pos);
+
+ // Pointer to the memory storing the patter
+ pwm->SEQ[0].PTR = (uint32_t)(pixels_pattern) << PWM_SEQ_PTR_PTR_Pos;
+
+ // Calculation of the number of steps loaded from memory.
+ pwm->SEQ[0].CNT = (pattern_size / sizeof(uint16_t)) << PWM_SEQ_CNT_CNT_Pos;
+
+ // The following settings are ignored with the current config.
+ pwm->SEQ[0].REFRESH = 0;
+ pwm->SEQ[0].ENDDELAY = 0;
+
+ // The Neopixel implementation is a blocking algorithm. DMA
+ // allows for non-blocking operation. To "simulate" a blocking
+ // operation we enable the interruption for the end of sequence
+ // and block the execution thread until the event flag is set by
+ // the peripheral.
+ // pwm->INTEN |= (PWM_INTEN_SEQEND0_Enabled<PSEL.OUT[0] = g_APinDescription[pin].name;
+#else
+ pwm->PSEL.OUT[0] = g_ADigitalPinMap[pin];
+#endif
+
+ // Enable the PWM
+ pwm->ENABLE = 1;
+
+ // After all of this and many hours of reading the documentation
+ // we are ready to start the sequence...
+ pwm->EVENTS_SEQEND[0] = 0;
+ pwm->TASKS_SEQSTART[0] = 1;
+
+ // But we have to wait for the flag to be set.
+ while (!pwm->EVENTS_SEQEND[0]) {
+#if defined(ARDUINO_NRF52_ADAFRUIT) || defined(ARDUINO_ARCH_NRF52840)
+ yield();
+#endif
+ }
+
+ // Before leave we clear the flag for the event.
+ pwm->EVENTS_SEQEND[0] = 0;
+
+ // We need to disable the device and disconnect
+ // all the outputs before leave or the device will not
+ // be selected on the next call.
+ // TODO: Check if disabling the device causes performance issues.
+ pwm->ENABLE = 0;
+
+ pwm->PSEL.OUT[0] = 0xFFFFFFFFUL;
+
+#if defined(ARDUINO_NRF52_ADAFRUIT) // use thread-safe free
+ rtos_free(pixels_pattern);
+#else
+ free(pixels_pattern);
+#endif
+ } // End of DMA implementation
+ // ---------------------------------------------------------------------
+ else {
+#ifndef ARDUINO_ARCH_NRF52840
+// Fall back to DWT
+#if defined(ARDUINO_NRF52_ADAFRUIT)
+ // Bluefruit Feather 52 uses freeRTOS
+ // Critical Section is used since it does not block SoftDevice execution
+ taskENTER_CRITICAL();
+#elif defined(NRF52_DISABLE_INT)
+ // If you are using the Bluetooth SoftDevice we advise you to not disable
+ // the interrupts. Disabling the interrupts even for short periods of time
+ // causes the SoftDevice to stop working.
+ // Disable the interrupts only in cases where you need high performance for
+ // the LEDs and if you are not using the EasyDMA feature.
+ __disable_irq();
+#endif
+
+ NRF_GPIO_Type *nrf_port = (NRF_GPIO_Type *)digitalPinToPort(pin);
+ uint32_t pinMask = digitalPinToBitMask(pin);
+
+ uint32_t CYCLES_X00 = CYCLES_800;
+ uint32_t CYCLES_X00_T1H = CYCLES_800_T1H;
+ uint32_t CYCLES_X00_T0H = CYCLES_800_T0H;
+
+#if defined(NEO_KHZ400)
+ if (!is800KHz) {
+ CYCLES_X00 = CYCLES_400;
+ CYCLES_X00_T1H = CYCLES_400_T1H;
+ CYCLES_X00_T0H = CYCLES_400_T0H;
+ }
+#endif
+
+ // Enable DWT in debug core
+ CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
+ DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
+
+ // Tries to re-send the frame if is interrupted by the SoftDevice.
+ while (1) {
+ uint8_t *p = pixels;
+
+ uint32_t cycStart = DWT->CYCCNT;
+ uint32_t cyc = 0;
+
+ for (uint16_t n = 0; n < numBytes; n++) {
+ uint8_t pix = *p++;
+
+ for (uint8_t mask = 0x80; mask; mask >>= 1) {
+ while (DWT->CYCCNT - cyc < CYCLES_X00)
+ ;
+ cyc = DWT->CYCCNT;
+
+ nrf_port->OUTSET |= pinMask;
+
+ if (pix & mask) {
+ while (DWT->CYCCNT - cyc < CYCLES_X00_T1H)
+ ;
+ } else {
+ while (DWT->CYCCNT - cyc < CYCLES_X00_T0H)
+ ;
+ }
+
+ nrf_port->OUTCLR |= pinMask;
+ }
+ }
+ while (DWT->CYCCNT - cyc < CYCLES_X00)
+ ;
+
+ // If total time longer than 25%, resend the whole data.
+ // Since we are likely to be interrupted by SoftDevice
+ if ((DWT->CYCCNT - cycStart) < (8 * numBytes * ((CYCLES_X00 * 5) / 4))) {
+ break;
+ }
+
+ // re-send need 300us delay
+ delayMicroseconds(300);
+ }
+
+// Enable interrupts again
+#if defined(ARDUINO_NRF52_ADAFRUIT)
+ taskEXIT_CRITICAL();
+#elif defined(NRF52_DISABLE_INT)
+ __enable_irq();
+#endif
+#endif
+ }
+ // END of NRF52 implementation
+
+#elif defined(__SAMD21E17A__) || defined(__SAMD21G18A__) || \
+ defined(__SAMD21E18A__) || defined(__SAMD21J18A__) || \
+ defined (__SAMD11C14A__)
+ // Arduino Zero, Gemma/Trinket M0, SODAQ Autonomo
+ // and others
+ // Tried this with a timer/counter, couldn't quite get adequate
+ // resolution. So yay, you get a load of goofball NOPs...
+
+ uint8_t *ptr, *end, p, bitMask, portNum;
+ uint32_t pinMask;
+
+ portNum = g_APinDescription[pin].ulPort;
+ pinMask = 1ul << g_APinDescription[pin].ulPin;
+ ptr = pixels;
+ end = ptr + numBytes;
+ p = *ptr++;
+ bitMask = 0x80;
+
+ volatile uint32_t *set = &(PORT->Group[portNum].OUTSET.reg),
+ *clr = &(PORT->Group[portNum].OUTCLR.reg);
+
+#if defined(NEO_KHZ400) // 800 KHz check needed only if 400 KHz support enabled
+ if (is800KHz) {
+#endif
+ for (;;) {
+ *set = pinMask;
+ asm("nop; nop; nop; nop; nop; nop; nop; nop;");
+ if (p & bitMask) {
+ asm("nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop;");
+ *clr = pinMask;
+ } else {
+ *clr = pinMask;
+ asm("nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop;");
+ }
+ if (bitMask >>= 1) {
+ asm("nop; nop; nop; nop; nop; nop; nop; nop; nop;");
+ } else {
+ if (ptr >= end)
+ break;
+ p = *ptr++;
+ bitMask = 0x80;
+ }
+ }
+#if defined(NEO_KHZ400)
+ } else { // 400 KHz bitstream
+ for (;;) {
+ *set = pinMask;
+ asm("nop; nop; nop; nop; nop; nop; nop; nop; nop; nop; nop;");
+ if (p & bitMask) {
+ asm("nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop;");
+ *clr = pinMask;
+ } else {
+ *clr = pinMask;
+ asm("nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop;");
+ }
+ asm("nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;");
+ if (bitMask >>= 1) {
+ asm("nop; nop; nop; nop; nop; nop; nop;");
+ } else {
+ if (ptr >= end)
+ break;
+ p = *ptr++;
+ bitMask = 0x80;
+ }
+ }
+ }
+#endif
+
+//----
+#elif defined(XMC1100_XMC2GO) || defined(XMC1100_H_BRIDGE2GO) || defined(XMC1100_Boot_Kit) || defined(XMC1300_Boot_Kit)
+
+ // XMC1100/1200/1300 with ARM Cortex M0 are running with 32MHz, XMC1400 runs with 48MHz so may not work
+ // Tried this with a timer/counter, couldn't quite get adequate
+ // resolution. So yay, you get a load of goofball NOPs...
+
+ uint8_t *ptr, *end, p, bitMask, portNum;
+ uint32_t pinMask;
+
+ ptr = pixels;
+ end = ptr + numBytes;
+ p = *ptr++;
+ bitMask = 0x80;
+
+ XMC_GPIO_PORT_t* XMC_port = mapping_port_pin[ pin ].port;
+ uint8_t XMC_pin = mapping_port_pin[ pin ].pin;
+
+ uint32_t omrhigh = (uint32_t)XMC_GPIO_OUTPUT_LEVEL_HIGH << XMC_pin;
+ uint32_t omrlow = (uint32_t)XMC_GPIO_OUTPUT_LEVEL_LOW << XMC_pin;
+
+#ifdef NEO_KHZ400 // 800 KHz check needed only if 400 KHz support enabled
+ if(is800KHz) {
+#endif
+ for(;;) {
+ XMC_port->OMR = omrhigh;
+ asm("nop; nop; nop; nop;");
+ if(p & bitMask) {
+ asm("nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop;");
+ XMC_port->OMR = omrlow;
+ } else {
+ XMC_port->OMR = omrlow;
+ asm("nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop;");
+ }
+ if(bitMask >>= 1) {
+ asm("nop; nop; nop; nop; nop;");
+ } else {
+ if(ptr >= end) break;
+ p = *ptr++;
+ bitMask = 0x80;
+ }
+ }
+#ifdef NEO_KHZ400 // untested code
+ } else { // 400 KHz bitstream
+ for(;;) {
+ XMC_port->OMR = omrhigh;
+ asm("nop; nop; nop; nop; nop;");
+ if(p & bitMask) {
+ asm("nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop;");
+ XMC_port->OMR = omrlow;
+ } else {
+ XMC_port->OMR = omrlow;
+ asm("nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop;");
+ }
+ asm("nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;");
+ if(bitMask >>= 1) {
+ asm("nop; nop; nop;");
+ } else {
+ if(ptr >= end) break;
+ p = *ptr++;
+ bitMask = 0x80;
+ }
+ }
+ }
+
+#endif
+//----
+
+//----
+#elif defined(XMC4700_Relax_Kit) || defined(XMC4800_Relax_Kit)
+
+// XMC4700 and XMC4800 with ARM Cortex M4 are running with 144MHz
+// Tried this with a timer/counter, couldn't quite get adequate
+// resolution. So yay, you get a load of goofball NOPs...
+
+uint8_t *ptr, *end, p, bitMask, portNum;
+uint32_t pinMask;
+
+ptr = pixels;
+end = ptr + numBytes;
+p = *ptr++;
+bitMask = 0x80;
+
+XMC_GPIO_PORT_t* XMC_port = mapping_port_pin[ pin ].port;
+uint8_t XMC_pin = mapping_port_pin[ pin ].pin;
+
+uint32_t omrhigh = (uint32_t)XMC_GPIO_OUTPUT_LEVEL_HIGH << XMC_pin;
+uint32_t omrlow = (uint32_t)XMC_GPIO_OUTPUT_LEVEL_LOW << XMC_pin;
+
+#ifdef NEO_KHZ400 // 800 KHz check needed only if 400 KHz support enabled
+if(is800KHz) {
+#endif
+
+ for(;;) {
+ XMC_port->OMR = omrhigh;
+ asm("nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop;");
+ if(p & bitMask) {
+ asm("nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;");
+ XMC_port->OMR = omrlow;
+ } else {
+ XMC_port->OMR = omrlow;
+ asm("nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;");
+ }
+ if(bitMask >>= 1) {
+ asm("nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;");
+ } else {
+ if(ptr >= end) break;
+ p = *ptr++;
+ bitMask = 0x80;
+ }
+ }
+
+
+#ifdef NEO_KHZ400
+ } else { // 400 KHz bitstream
+ // ToDo!
+ }
+#endif
+//----
+
+#elif defined(__SAMD51__) // M4
+
+ uint8_t *ptr, *end, p, bitMask, portNum, bit;
+ uint32_t pinMask;
+
+ portNum = g_APinDescription[pin].ulPort;
+ pinMask = 1ul << g_APinDescription[pin].ulPin;
+ ptr = pixels;
+ end = ptr + numBytes;
+ p = *ptr++;
+ bitMask = 0x80;
+
+ volatile uint32_t *set = &(PORT->Group[portNum].OUTSET.reg),
+ *clr = &(PORT->Group[portNum].OUTCLR.reg);
+
+ // SAMD51 overclock-compatible timing is only a mild abomination.
+ // It uses SysTick for a consistent clock reference regardless of
+ // optimization / cache settings. That's the good news. The bad news,
+ // since SysTick->VAL is a volatile type it's slow to access...and then,
+ // with the SysTick interval that Arduino sets up (1 ms), this would
+ // require a subtract and MOD operation for gauging elapsed time, and
+ // all taken in combination that lacks adequate temporal resolution
+ // for NeoPixel timing. So a kind of horrible thing is done here...
+ // since interrupts are turned off anyway and it's generally accepted
+ // by now that we're gonna lose track of time in the NeoPixel lib,
+ // the SysTick timer is reconfigured for a period matching the NeoPixel
+ // bit timing (either 800 or 400 KHz) and we watch SysTick->VAL very
+ // closely (just a threshold, no subtract or MOD or anything) and that
+ // seems to work just well enough. When finished, the SysTick
+ // peripheral is set back to its original state.
+
+ uint32_t t0, t1, top, ticks, saveLoad = SysTick->LOAD, saveVal = SysTick->VAL;
+
+#if defined(NEO_KHZ400) // 800 KHz check needed only if 400 KHz support enabled
+ if (is800KHz) {
+#endif
+ top = (uint32_t)(F_CPU * 0.00000125); // Bit hi + lo = 1.25 uS
+ t0 = top - (uint32_t)(F_CPU * 0.00000040); // 0 = 0.4 uS hi
+ t1 = top - (uint32_t)(F_CPU * 0.00000080); // 1 = 0.8 uS hi
+#if defined(NEO_KHZ400)
+ } else { // 400 KHz bitstream
+ top = (uint32_t)(F_CPU * 0.00000250); // Bit hi + lo = 2.5 uS
+ t0 = top - (uint32_t)(F_CPU * 0.00000050); // 0 = 0.5 uS hi
+ t1 = top - (uint32_t)(F_CPU * 0.00000120); // 1 = 1.2 uS hi
+ }
+#endif
+
+ SysTick->LOAD = top; // Config SysTick for NeoPixel bit freq
+ SysTick->VAL = top; // Set to start value (counts down)
+ (void)SysTick->VAL; // Dummy read helps sync up 1st bit
+
+ for (;;) {
+ *set = pinMask; // Set output high
+ ticks = (p & bitMask) ? t1 : t0; // SysTick threshold,
+ while (SysTick->VAL > ticks)
+ ; // wait for it
+ *clr = pinMask; // Set output low
+ if (!(bitMask >>= 1)) { // Next bit for this byte...done?
+ if (ptr >= end)
+ break; // If last byte sent, exit loop
+ p = *ptr++; // Fetch next byte
+ bitMask = 0x80; // Reset bitmask
+ }
+ while (SysTick->VAL <= ticks)
+ ; // Wait for rollover to 'top'
+ }
+
+ SysTick->LOAD = saveLoad; // Restore SysTick rollover to 1 ms
+ SysTick->VAL = saveVal; // Restore SysTick value
+
+#elif defined(ARDUINO_STM32_FEATHER) // FEATHER WICED (120MHz)
+
+ // Tried this with a timer/counter, couldn't quite get adequate
+ // resolution. So yay, you get a load of goofball NOPs...
+
+ uint8_t *ptr, *end, p, bitMask;
+ uint32_t pinMask;
+
+ pinMask = BIT(PIN_MAP[pin].gpio_bit);
+ ptr = pixels;
+ end = ptr + numBytes;
+ p = *ptr++;
+ bitMask = 0x80;
+
+ volatile uint16_t *set = &(PIN_MAP[pin].gpio_device->regs->BSRRL);
+ volatile uint16_t *clr = &(PIN_MAP[pin].gpio_device->regs->BSRRH);
+
+#if defined(NEO_KHZ400) // 800 KHz check needed only if 400 KHz support enabled
+ if (is800KHz) {
+#endif
+ for (;;) {
+ if (p & bitMask) { // ONE
+ // High 800ns
+ *set = pinMask;
+ asm("nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop;");
+ // Low 450ns
+ *clr = pinMask;
+ asm("nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop;");
+ } else { // ZERO
+ // High 400ns
+ *set = pinMask;
+ asm("nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop;");
+ // Low 850ns
+ *clr = pinMask;
+ asm("nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop; nop; nop; nop; nop;"
+ "nop; nop; nop; nop;");
+ }
+ if (bitMask >>= 1) {
+ // Move on to the next pixel
+ asm("nop;");
+ } else {
+ if (ptr >= end)
+ break;
+ p = *ptr++;
+ bitMask = 0x80;
+ }
+ }
+#if defined(NEO_KHZ400)
+ } else { // 400 KHz bitstream
+ // ToDo!
+ }
+#endif
+
+#elif defined(TARGET_LPC1768)
+ uint8_t *ptr, *end, p, bitMask;
+ ptr = pixels;
+ end = ptr + numBytes;
+ p = *ptr++;
+ bitMask = 0x80;
+
+#if defined(NEO_KHZ400) // 800 KHz check needed only if 400 KHz support enabled
+ if (is800KHz) {
+#endif
+ for (;;) {
+ if (p & bitMask) {
+ // data ONE high
+ // min: 550 typ: 700 max: 5,500
+ gpio_set(pin);
+ time::delay_ns(550);
+ // min: 450 typ: 600 max: 5,000
+ gpio_clear(pin);
+ time::delay_ns(450);
+ } else {
+ // data ZERO high
+ // min: 200 typ: 350 max: 500
+ gpio_set(pin);
+ time::delay_ns(200);
+ // data low
+ // min: 450 typ: 600 max: 5,000
+ gpio_clear(pin);
+ time::delay_ns(450);
+ }
+ if (bitMask >>= 1) {
+ // Move on to the next pixel
+ asm("nop;");
+ } else {
+ if (ptr >= end)
+ break;
+ p = *ptr++;
+ bitMask = 0x80;
+ }
+ }
+#if defined(NEO_KHZ400)
+ } else { // 400 KHz bitstream
+ // ToDo!
+ }
+#endif
+#elif defined(ARDUINO_ARCH_STM32) || defined(ARDUINO_ARCH_ARDUINO_CORE_STM32)
+ uint8_t *p = pixels, *end = p + numBytes, pix = *p++, mask = 0x80;
+ uint32_t cyc;
+ uint32_t saveLoad = SysTick->LOAD, saveVal = SysTick->VAL;
+#if defined(NEO_KHZ400) // 800 KHz check needed only if 400 KHz support enabled
+ if (is800KHz) {
+#endif
+ uint32_t top = (F_CPU / 800000); // 1.25µs
+ uint32_t t0 = top - (F_CPU / 2500000); // 0.4µs
+ uint32_t t1 = top - (F_CPU / 1250000); // 0.8µs
+ SysTick->LOAD = top - 1; // Config SysTick for NeoPixel bit freq
+ SysTick->VAL = 0; // Set to start value
+ for (;;) {
+ LL_GPIO_SetOutputPin(gpioPort, gpioPin);
+ cyc = (pix & mask) ? t1 : t0;
+ while (SysTick->VAL > cyc)
+ ;
+ LL_GPIO_ResetOutputPin(gpioPort, gpioPin);
+ if (!(mask >>= 1)) {
+ if (p >= end)
+ break;
+ pix = *p++;
+ mask = 0x80;
+ }
+ while (SysTick->VAL <= cyc)
+ ;
+ }
+#if defined(NEO_KHZ400)
+ } else { // 400 kHz bitstream
+ uint32_t top = (F_CPU / 400000); // 2.5µs
+ uint32_t t0 = top - (F_CPU / 2000000); // 0.5µs
+ uint32_t t1 = top - (F_CPU / 833333); // 1.2µs
+ SysTick->LOAD = top - 1; // Config SysTick for NeoPixel bit freq
+ SysTick->VAL = 0; // Set to start value
+ for (;;) {
+ LL_GPIO_SetOutputPin(gpioPort, gpioPin);
+ cyc = (pix & mask) ? t1 : t0;
+ while (SysTick->VAL > cyc)
+ ;
+ LL_GPIO_ResetOutputPin(gpioPort, gpioPin);
+ if (!(mask >>= 1)) {
+ if (p >= end)
+ break;
+ pix = *p++;
+ mask = 0x80;
+ }
+ while (SysTick->VAL <= cyc)
+ ;
+ }
+ }
+#endif // NEO_KHZ400
+ SysTick->LOAD = saveLoad; // Restore SysTick rollover to 1 ms
+ SysTick->VAL = saveVal; // Restore SysTick value
+#elif defined(NRF51)
+ uint8_t *p = pixels, pix, count, mask;
+ int32_t num = numBytes;
+ unsigned int bitmask = (1 << g_ADigitalPinMap[pin]);
+ // https://github.com/sandeepmistry/arduino-nRF5/blob/dc53980c8bac27898fca90d8ecb268e11111edc1/variants/BBCmicrobit/variant.cpp
+
+ volatile unsigned int *reg = (unsigned int *)(0x50000000UL + 0x508);
+
+ // https://github.com/sandeepmistry/arduino-nRF5/blob/dc53980c8bac27898fca90d8ecb268e11111edc1/cores/nRF5/SDK/components/device/nrf51.h
+ // http://www.iot-programmer.com/index.php/books/27-micro-bit-iot-in-c/chapters-micro-bit-iot-in-c/47-micro-bit-iot-in-c-fast-memory-mapped-gpio?showall=1
+ // https://github.com/Microsoft/pxt-neopixel/blob/master/sendbuffer.asm
+
+ asm volatile(
+ // "cpsid i" ; disable irq
+
+ // b .start
+ "b L%=_start"
+ "\n\t"
+ // .nextbit: ; C0
+ "L%=_nextbit:"
+ "\n\t" //; C0
+ // str r1, [r3, #0] ; pin := hi C2
+ "strb %[bitmask], [%[reg], #0]"
+ "\n\t" //; pin := hi C2
+ // tst r6, r0 ; C3
+ "tst %[mask], %[pix]"
+ "\n\t" // ; C3
+ // bne .islate ; C4
+ "bne L%=_islate"
+ "\n\t" //; C4
+ // str r1, [r2, #0] ; pin := lo C6
+ "strb %[bitmask], [%[reg], #4]"
+ "\n\t" //; pin := lo C6
+ // .islate:
+ "L%=_islate:"
+ "\n\t"
+ // lsrs r6, r6, #1 ; r6 >>= 1 C7
+ "lsr %[mask], %[mask], #1"
+ "\n\t" //; r6 >>= 1 C7
+ // bne .justbit ; C8
+ "bne L%=_justbit"
+ "\n\t" //; C8
+
+ // ; not just a bit - need new byte
+ // adds r4, #1 ; r4++ C9
+ "add %[p], #1"
+ "\n\t" //; r4++ C9
+ // subs r5, #1 ; r5-- C10
+ "sub %[num], #1"
+ "\n\t" //; r5-- C10
+ // bcc .stop ; if (r5<0) goto .stop C11
+ "bcc L%=_stop"
+ "\n\t" //; if (r5<0) goto .stop C11
+ // .start:
+ "L%=_start:"
+ // movs r6, #0x80 ; reset mask C12
+ "movs %[mask], #0x80"
+ "\n\t" //; reset mask C12
+ // nop ; C13
+ "nop"
+ "\n\t" //; C13
+
+ // .common: ; C13
+ "L%=_common:"
+ "\n\t" //; C13
+ // str r1, [r2, #0] ; pin := lo C15
+ "strb %[bitmask], [%[reg], #4]"
+ "\n\t" //; pin := lo C15
+ // ; always re-load byte - it just fits with the cycles better this way
+ // ldrb r0, [r4, #0] ; r0 := *r4 C17
+ "ldrb %[pix], [%[p], #0]"
+ "\n\t" //; r0 := *r4 C17
+ // b .nextbit ; C20
+ "b L%=_nextbit"
+ "\n\t" //; C20
+
+ // .justbit: ; C10
+ "L%=_justbit:"
+ "\n\t" //; C10
+ // ; no nops, branch taken is already 3 cycles
+ // b .common ; C13
+ "b L%=_common"
+ "\n\t" //; C13
+
+ // .stop:
+ "L%=_stop:"
+ "\n\t"
+ // str r1, [r2, #0] ; pin := lo
+ "strb %[bitmask], [%[reg], #4]"
+ "\n\t" //; pin := lo
+ // cpsie i ; enable irq
+
+ : [p] "+r"(p), [pix] "=&r"(pix), [count] "=&r"(count), [mask] "=&r"(mask),
+ [num] "+r"(num)
+ : [bitmask] "r"(bitmask), [reg] "r"(reg));
+
+#elif defined(__SAM3X8E__) // Arduino Due
+
+#define SCALE VARIANT_MCK / 2UL / 1000000UL
+#define INST (2UL * F_CPU / VARIANT_MCK)
+#define TIME_800_0 ((int)(0.40 * SCALE + 0.5) - (5 * INST))
+#define TIME_800_1 ((int)(0.80 * SCALE + 0.5) - (5 * INST))
+#define PERIOD_800 ((int)(1.25 * SCALE + 0.5) - (5 * INST))
+#define TIME_400_0 ((int)(0.50 * SCALE + 0.5) - (5 * INST))
+#define TIME_400_1 ((int)(1.20 * SCALE + 0.5) - (5 * INST))
+#define PERIOD_400 ((int)(2.50 * SCALE + 0.5) - (5 * INST))
+
+ int pinMask, time0, time1, period, t;
+ Pio *port;
+ volatile WoReg *portSet, *portClear, *timeValue, *timeReset;
+ uint8_t *p, *end, pix, mask;
+
+ pmc_set_writeprotect(false);
+ pmc_enable_periph_clk((uint32_t)TC3_IRQn);
+ TC_Configure(TC1, 0,
+ TC_CMR_WAVE | TC_CMR_WAVSEL_UP | TC_CMR_TCCLKS_TIMER_CLOCK1);
+ TC_Start(TC1, 0);
+
+ pinMask = g_APinDescription[pin].ulPin; // Don't 'optimize' these into
+ port = g_APinDescription[pin].pPort; // declarations above. Want to
+ portSet = &(port->PIO_SODR); // burn a few cycles after
+ portClear = &(port->PIO_CODR); // starting timer to minimize
+ timeValue = &(TC1->TC_CHANNEL[0].TC_CV); // the initial 'while'.
+ timeReset = &(TC1->TC_CHANNEL[0].TC_CCR);
+ p = pixels;
+ end = p + numBytes;
+ pix = *p++;
+ mask = 0x80;
+
+#if defined(NEO_KHZ400) // 800 KHz check needed only if 400 KHz support enabled
+ if (is800KHz) {
+#endif
+ time0 = TIME_800_0;
+ time1 = TIME_800_1;
+ period = PERIOD_800;
+#if defined(NEO_KHZ400)
+ } else { // 400 KHz bitstream
+ time0 = TIME_400_0;
+ time1 = TIME_400_1;
+ period = PERIOD_400;
+ }
+#endif
+
+ for (t = time0;; t = time0) {
+ if (pix & mask)
+ t = time1;
+ while (*timeValue < (unsigned)period)
+ ;
+ *portSet = pinMask;
+ *timeReset = TC_CCR_CLKEN | TC_CCR_SWTRG;
+ while (*timeValue < (unsigned)t)
+ ;
+ *portClear = pinMask;
+ if (!(mask >>= 1)) { // This 'inside-out' loop logic utilizes
+ if (p >= end)
+ break; // idle time to minimize inter-byte delays.
+ pix = *p++;
+ mask = 0x80;
+ }
+ }
+ while (*timeValue < (unsigned)period)
+ ; // Wait for last bit
+ TC_Stop(TC1, 0);
+
+#endif // end Due
+
+ // END ARM ----------------------------------------------------------------
+
+#elif defined(ESP8266) || defined(ESP32)
+
+ // ESP8266 ----------------------------------------------------------------
+
+ // ESP8266 show() is external to enforce ICACHE_RAM_ATTR execution
+ espShow(pin, pixels, numBytes, is800KHz);
+
+#elif defined(KENDRYTE_K210)
+
+ k210Show(pin, pixels, numBytes, is800KHz);
+
+#elif defined(__ARDUINO_ARC__)
+
+ // Arduino 101 -----------------------------------------------------------
+
+#define NOPx7 \
+ { \
+ __builtin_arc_nop(); \
+ __builtin_arc_nop(); \
+ __builtin_arc_nop(); \
+ __builtin_arc_nop(); \
+ __builtin_arc_nop(); \
+ __builtin_arc_nop(); \
+ __builtin_arc_nop(); \
+ }
+
+ PinDescription *pindesc = &g_APinDescription[pin];
+ register uint32_t loop =
+ 8 * numBytes; // one loop to handle all bytes and all bits
+ register uint8_t *p = pixels;
+ register uint32_t currByte = (uint32_t)(*p);
+ register uint32_t currBit = 0x80 & currByte;
+ register uint32_t bitCounter = 0;
+ register uint32_t first = 1;
+
+ // The loop is unusual. Very first iteration puts all the way LOW to the wire
+ // - constant LOW does not affect NEOPIXEL, so there is no visible effect
+ // displayed. During that very first iteration CPU caches instructions in the
+ // loop. Because of the caching process, "CPU slows down". NEOPIXEL pulse is
+ // very time sensitive that's why we let the CPU cache first and we start
+ // regular pulse from 2nd iteration
+ if (pindesc->ulGPIOType == SS_GPIO) {
+ register uint32_t reg = pindesc->ulGPIOBase + SS_GPIO_SWPORTA_DR;
+ uint32_t reg_val = __builtin_arc_lr((volatile uint32_t)reg);
+ register uint32_t reg_bit_high = reg_val | (1 << pindesc->ulGPIOId);
+ register uint32_t reg_bit_low = reg_val & ~(1 << pindesc->ulGPIOId);
+
+ loop += 1; // include first, special iteration
+ while (loop--) {
+ if (!first) {
+ currByte <<= 1;
+ bitCounter++;
+ }
+
+ // 1 is >550ns high and >450ns low; 0 is 200..500ns high and >450ns low
+ __builtin_arc_sr(first ? reg_bit_low : reg_bit_high,
+ (volatile uint32_t)reg);
+ if (currBit) { // ~400ns HIGH (740ns overall)
+ NOPx7 NOPx7
+ }
+ // ~340ns HIGH
+ NOPx7 __builtin_arc_nop();
+
+ // 820ns LOW; per spec, max allowed low here is 5000ns */
+ __builtin_arc_sr(reg_bit_low, (volatile uint32_t)reg);
+ NOPx7 NOPx7
+
+ if (bitCounter >= 8) {
+ bitCounter = 0;
+ currByte = (uint32_t)(*++p);
+ }
+
+ currBit = 0x80 & currByte;
+ first = 0;
+ }
+ } else if (pindesc->ulGPIOType == SOC_GPIO) {
+ register uint32_t reg = pindesc->ulGPIOBase + SOC_GPIO_SWPORTA_DR;
+ uint32_t reg_val = MMIO_REG_VAL(reg);
+ register uint32_t reg_bit_high = reg_val | (1 << pindesc->ulGPIOId);
+ register uint32_t reg_bit_low = reg_val & ~(1 << pindesc->ulGPIOId);
+
+ loop += 1; // include first, special iteration
+ while (loop--) {
+ if (!first) {
+ currByte <<= 1;
+ bitCounter++;
+ }
+ MMIO_REG_VAL(reg) = first ? reg_bit_low : reg_bit_high;
+ if (currBit) { // ~430ns HIGH (740ns overall)
+ NOPx7 NOPx7 __builtin_arc_nop();
+ }
+ // ~310ns HIGH
+ NOPx7
+
+ // 850ns LOW; per spec, max allowed low here is 5000ns */
+ MMIO_REG_VAL(reg) = reg_bit_low;
+ NOPx7 NOPx7
+
+ if (bitCounter >= 8) {
+ bitCounter = 0;
+ currByte = (uint32_t)(*++p);
+ }
+
+ currBit = 0x80 & currByte;
+ first = 0;
+ }
+ }
+
+#else
+#error Architecture not supported
+#endif
+
+ // END ARCHITECTURE SELECT ------------------------------------------------
+
+#if !(defined(NRF52) || defined(NRF52_SERIES))
+ interrupts();
+#endif
+
+ endTime = micros(); // Save EOD time for latch on next call
+}
+
+/*!
+ @brief Set/change the NeoPixel output pin number. Previous pin,
+ if any, is set to INPUT and the new pin is set to OUTPUT.
+ @param p Arduino pin number (-1 = no pin).
+*/
+void Adafruit_NeoPixel::setPin(int16_t p) {
+ if (begun && (pin >= 0))
+ pinMode(pin, INPUT); // Disable existing out pin
+ pin = p;
+ if (begun) {
+ pinMode(p, OUTPUT);
+ digitalWrite(p, LOW);
+ }
+#if defined(__AVR__)
+ port = portOutputRegister(digitalPinToPort(p));
+ pinMask = digitalPinToBitMask(p);
+#endif
+#if defined(ARDUINO_ARCH_STM32) || defined(ARDUINO_ARCH_ARDUINO_CORE_STM32)
+ gpioPort = digitalPinToPort(p);
+ gpioPin = STM_LL_GPIO_PIN(digitalPinToPinName(p));
+#endif
+}
+
+/*!
+ @brief Set a pixel's color using separate red, green and blue
+ components. If using RGBW pixels, white will be set to 0.
+ @param n Pixel index, starting from 0.
+ @param r Red brightness, 0 = minimum (off), 255 = maximum.
+ @param g Green brightness, 0 = minimum (off), 255 = maximum.
+ @param b Blue brightness, 0 = minimum (off), 255 = maximum.
+*/
+void Adafruit_NeoPixel::setPixelColor(uint16_t n, uint8_t r, uint8_t g,
+ uint8_t b) {
+
+ if (n < numLEDs) {
+ if (brightness) { // See notes in setBrightness()
+ r = (r * brightness) >> 8;
+ g = (g * brightness) >> 8;
+ b = (b * brightness) >> 8;
+ }
+ uint8_t *p;
+ if (wOffset == rOffset) { // Is an RGB-type strip
+ p = &pixels[n * 3]; // 3 bytes per pixel
+ } else { // Is a WRGB-type strip
+ p = &pixels[n * 4]; // 4 bytes per pixel
+ p[wOffset] = 0; // But only R,G,B passed -- set W to 0
+ }
+ p[rOffset] = r; // R,G,B always stored
+ p[gOffset] = g;
+ p[bOffset] = b;
+ }
+}
+
+/*!
+ @brief Set a pixel's color using separate red, green, blue and white
+ components (for RGBW NeoPixels only).
+ @param n Pixel index, starting from 0.
+ @param r Red brightness, 0 = minimum (off), 255 = maximum.
+ @param g Green brightness, 0 = minimum (off), 255 = maximum.
+ @param b Blue brightness, 0 = minimum (off), 255 = maximum.
+ @param w White brightness, 0 = minimum (off), 255 = maximum, ignored
+ if using RGB pixels.
+*/
+void Adafruit_NeoPixel::setPixelColor(uint16_t n, uint8_t r, uint8_t g,
+ uint8_t b, uint8_t w) {
+
+ if (n < numLEDs) {
+ if (brightness) { // See notes in setBrightness()
+ r = (r * brightness) >> 8;
+ g = (g * brightness) >> 8;
+ b = (b * brightness) >> 8;
+ w = (w * brightness) >> 8;
+ }
+ uint8_t *p;
+ if (wOffset == rOffset) { // Is an RGB-type strip
+ p = &pixels[n * 3]; // 3 bytes per pixel (ignore W)
+ } else { // Is a WRGB-type strip
+ p = &pixels[n * 4]; // 4 bytes per pixel
+ p[wOffset] = w; // Store W
+ }
+ p[rOffset] = r; // Store R,G,B
+ p[gOffset] = g;
+ p[bOffset] = b;
+ }
+}
+
+/*!
+ @brief Set a pixel's color using a 32-bit 'packed' RGB or RGBW value.
+ @param n Pixel index, starting from 0.
+ @param c 32-bit color value. Most significant byte is white (for RGBW
+ pixels) or ignored (for RGB pixels), next is red, then green,
+ and least significant byte is blue.
+*/
+void Adafruit_NeoPixel::setPixelColor(uint16_t n, uint32_t c) {
+ if (n < numLEDs) {
+ uint8_t *p, r = (uint8_t)(c >> 16), g = (uint8_t)(c >> 8), b = (uint8_t)c;
+ if (brightness) { // See notes in setBrightness()
+ r = (r * brightness) >> 8;
+ g = (g * brightness) >> 8;
+ b = (b * brightness) >> 8;
+ }
+ if (wOffset == rOffset) {
+ p = &pixels[n * 3];
+ } else {
+ p = &pixels[n * 4];
+ uint8_t w = (uint8_t)(c >> 24);
+ p[wOffset] = brightness ? ((w * brightness) >> 8) : w;
+ }
+ p[rOffset] = r;
+ p[gOffset] = g;
+ p[bOffset] = b;
+ }
+}
+
+/*!
+ @brief Fill all or part of the NeoPixel strip with a color.
+ @param c 32-bit color value. Most significant byte is white (for
+ RGBW pixels) or ignored (for RGB pixels), next is red,
+ then green, and least significant byte is blue. If all
+ arguments are unspecified, this will be 0 (off).
+ @param first Index of first pixel to fill, starting from 0. Must be
+ in-bounds, no clipping is performed. 0 if unspecified.
+ @param count Number of pixels to fill, as a positive value. Passing
+ 0 or leaving unspecified will fill to end of strip.
+*/
+void Adafruit_NeoPixel::fill(uint32_t c, uint16_t first, uint16_t count) {
+ uint16_t i, end;
+
+ if (first >= numLEDs) {
+ return; // If first LED is past end of strip, nothing to do
+ }
+
+ // Calculate the index ONE AFTER the last pixel to fill
+ if (count == 0) {
+ // Fill to end of strip
+ end = numLEDs;
+ } else {
+ // Ensure that the loop won't go past the last pixel
+ end = first + count;
+ if (end > numLEDs)
+ end = numLEDs;
+ }
+
+ for (i = first; i < end; i++) {
+ this->setPixelColor(i, c);
+ }
+}
+
+/*!
+ @brief Convert hue, saturation and value into a packed 32-bit RGB color
+ that can be passed to setPixelColor() or other RGB-compatible
+ functions.
+ @param hue An unsigned 16-bit value, 0 to 65535, representing one full
+ loop of the color wheel, which allows 16-bit hues to "roll
+ over" while still doing the expected thing (and allowing
+ more precision than the wheel() function that was common to
+ prior NeoPixel examples).
+ @param sat Saturation, 8-bit value, 0 (min or pure grayscale) to 255
+ (max or pure hue). Default of 255 if unspecified.
+ @param val Value (brightness), 8-bit value, 0 (min / black / off) to
+ 255 (max or full brightness). Default of 255 if unspecified.
+ @return Packed 32-bit RGB with the most significant byte set to 0 -- the
+ white element of WRGB pixels is NOT utilized. Result is linearly
+ but not perceptually correct, so you may want to pass the result
+ through the gamma32() function (or your own gamma-correction
+ operation) else colors may appear washed out. This is not done
+ automatically by this function because coders may desire a more
+ refined gamma-correction function than the simplified
+ one-size-fits-all operation of gamma32(). Diffusing the LEDs also
+ really seems to help when using low-saturation colors.
+*/
+uint32_t Adafruit_NeoPixel::ColorHSV(uint16_t hue, uint8_t sat, uint8_t val) {
+
+ uint8_t r, g, b;
+
+ // Remap 0-65535 to 0-1529. Pure red is CENTERED on the 64K rollover;
+ // 0 is not the start of pure red, but the midpoint...a few values above
+ // zero and a few below 65536 all yield pure red (similarly, 32768 is the
+ // midpoint, not start, of pure cyan). The 8-bit RGB hexcone (256 values
+ // each for red, green, blue) really only allows for 1530 distinct hues
+ // (not 1536, more on that below), but the full unsigned 16-bit type was
+ // chosen for hue so that one's code can easily handle a contiguous color
+ // wheel by allowing hue to roll over in either direction.
+ hue = (hue * 1530L + 32768) / 65536;
+ // Because red is centered on the rollover point (the +32768 above,
+ // essentially a fixed-point +0.5), the above actually yields 0 to 1530,
+ // where 0 and 1530 would yield the same thing. Rather than apply a
+ // costly modulo operator, 1530 is handled as a special case below.
+
+ // So you'd think that the color "hexcone" (the thing that ramps from
+ // pure red, to pure yellow, to pure green and so forth back to red,
+ // yielding six slices), and with each color component having 256
+ // possible values (0-255), might have 1536 possible items (6*256),
+ // but in reality there's 1530. This is because the last element in
+ // each 256-element slice is equal to the first element of the next
+ // slice, and keeping those in there this would create small
+ // discontinuities in the color wheel. So the last element of each
+ // slice is dropped...we regard only elements 0-254, with item 255
+ // being picked up as element 0 of the next slice. Like this:
+ // Red to not-quite-pure-yellow is: 255, 0, 0 to 255, 254, 0
+ // Pure yellow to not-quite-pure-green is: 255, 255, 0 to 1, 255, 0
+ // Pure green to not-quite-pure-cyan is: 0, 255, 0 to 0, 255, 254
+ // and so forth. Hence, 1530 distinct hues (0 to 1529), and hence why
+ // the constants below are not the multiples of 256 you might expect.
+
+ // Convert hue to R,G,B (nested ifs faster than divide+mod+switch):
+ if (hue < 510) { // Red to Green-1
+ b = 0;
+ if (hue < 255) { // Red to Yellow-1
+ r = 255;
+ g = hue; // g = 0 to 254
+ } else { // Yellow to Green-1
+ r = 510 - hue; // r = 255 to 1
+ g = 255;
+ }
+ } else if (hue < 1020) { // Green to Blue-1
+ r = 0;
+ if (hue < 765) { // Green to Cyan-1
+ g = 255;
+ b = hue - 510; // b = 0 to 254
+ } else { // Cyan to Blue-1
+ g = 1020 - hue; // g = 255 to 1
+ b = 255;
+ }
+ } else if (hue < 1530) { // Blue to Red-1
+ g = 0;
+ if (hue < 1275) { // Blue to Magenta-1
+ r = hue - 1020; // r = 0 to 254
+ b = 255;
+ } else { // Magenta to Red-1
+ r = 255;
+ b = 1530 - hue; // b = 255 to 1
+ }
+ } else { // Last 0.5 Red (quicker than % operator)
+ r = 255;
+ g = b = 0;
+ }
+
+ // Apply saturation and value to R,G,B, pack into 32-bit result:
+ uint32_t v1 = 1 + val; // 1 to 256; allows >>8 instead of /255
+ uint16_t s1 = 1 + sat; // 1 to 256; same reason
+ uint8_t s2 = 255 - sat; // 255 to 0
+ return ((((((r * s1) >> 8) + s2) * v1) & 0xff00) << 8) |
+ (((((g * s1) >> 8) + s2) * v1) & 0xff00) |
+ (((((b * s1) >> 8) + s2) * v1) >> 8);
+}
+
+/*!
+ @brief Query the color of a previously-set pixel.
+ @param n Index of pixel to read (0 = first).
+ @return 'Packed' 32-bit RGB or WRGB value. Most significant byte is white
+ (for RGBW pixels) or 0 (for RGB pixels), next is red, then green,
+ and least significant byte is blue.
+ @note If the strip brightness has been changed from the default value
+ of 255, the color read from a pixel may not exactly match what
+ was previously written with one of the setPixelColor() functions.
+ This gets more pronounced at lower brightness levels.
+*/
+uint32_t Adafruit_NeoPixel::getPixelColor(uint16_t n) const {
+ if (n >= numLEDs)
+ return 0; // Out of bounds, return no color.
+
+ uint8_t *p;
+
+ if (wOffset == rOffset) { // Is RGB-type device
+ p = &pixels[n * 3];
+ if (brightness) {
+ // Stored color was decimated by setBrightness(). Returned value
+ // attempts to scale back to an approximation of the original 24-bit
+ // value used when setting the pixel color, but there will always be
+ // some error -- those bits are simply gone. Issue is most
+ // pronounced at low brightness levels.
+ return (((uint32_t)(p[rOffset] << 8) / brightness) << 16) |
+ (((uint32_t)(p[gOffset] << 8) / brightness) << 8) |
+ ((uint32_t)(p[bOffset] << 8) / brightness);
+ } else {
+ // No brightness adjustment has been made -- return 'raw' color
+ return ((uint32_t)p[rOffset] << 16) | ((uint32_t)p[gOffset] << 8) |
+ (uint32_t)p[bOffset];
+ }
+ } else { // Is RGBW-type device
+ p = &pixels[n * 4];
+ if (brightness) { // Return scaled color
+ return (((uint32_t)(p[wOffset] << 8) / brightness) << 24) |
+ (((uint32_t)(p[rOffset] << 8) / brightness) << 16) |
+ (((uint32_t)(p[gOffset] << 8) / brightness) << 8) |
+ ((uint32_t)(p[bOffset] << 8) / brightness);
+ } else { // Return raw color
+ return ((uint32_t)p[wOffset] << 24) | ((uint32_t)p[rOffset] << 16) |
+ ((uint32_t)p[gOffset] << 8) | (uint32_t)p[bOffset];
+ }
+ }
+}
+
+/*!
+ @brief Adjust output brightness. Does not immediately affect what's
+ currently displayed on the LEDs. The next call to show() will
+ refresh the LEDs at this level.
+ @param b Brightness setting, 0=minimum (off), 255=brightest.
+ @note This was intended for one-time use in one's setup() function,
+ not as an animation effect in itself. Because of the way this
+ library "pre-multiplies" LED colors in RAM, changing the
+ brightness is often a "lossy" operation -- what you write to
+ pixels isn't necessary the same as what you'll read back.
+ Repeated brightness changes using this function exacerbate the
+ problem. Smart programs therefore treat the strip as a
+ write-only resource, maintaining their own state to render each
+ frame of an animation, not relying on read-modify-write.
+*/
+void Adafruit_NeoPixel::setBrightness(uint8_t b) {
+ // Stored brightness value is different than what's passed.
+ // This simplifies the actual scaling math later, allowing a fast
+ // 8x8-bit multiply and taking the MSB. 'brightness' is a uint8_t,
+ // adding 1 here may (intentionally) roll over...so 0 = max brightness
+ // (color values are interpreted literally; no scaling), 1 = min
+ // brightness (off), 255 = just below max brightness.
+ uint8_t newBrightness = b + 1;
+ if (newBrightness != brightness) { // Compare against prior value
+ // Brightness has changed -- re-scale existing data in RAM,
+ // This process is potentially "lossy," especially when increasing
+ // brightness. The tight timing in the WS2811/WS2812 code means there
+ // aren't enough free cycles to perform this scaling on the fly as data
+ // is issued. So we make a pass through the existing color data in RAM
+ // and scale it (subsequent graphics commands also work at this
+ // brightness level). If there's a significant step up in brightness,
+ // the limited number of steps (quantization) in the old data will be
+ // quite visible in the re-scaled version. For a non-destructive
+ // change, you'll need to re-render the full strip data. C'est la vie.
+ uint8_t c, *ptr = pixels,
+ oldBrightness = brightness - 1; // De-wrap old brightness value
+ uint16_t scale;
+ if (oldBrightness == 0)
+ scale = 0; // Avoid /0
+ else if (b == 255)
+ scale = 65535 / oldBrightness;
+ else
+ scale = (((uint16_t)newBrightness << 8) - 1) / oldBrightness;
+ for (uint16_t i = 0; i < numBytes; i++) {
+ c = *ptr;
+ *ptr++ = (c * scale) >> 8;
+ }
+ brightness = newBrightness;
+ }
+}
+
+/*!
+ @brief Retrieve the last-set brightness value for the strip.
+ @return Brightness value: 0 = minimum (off), 255 = maximum.
+*/
+uint8_t Adafruit_NeoPixel::getBrightness(void) const { return brightness - 1; }
+
+/*!
+ @brief Fill the whole NeoPixel strip with 0 / black / off.
+*/
+void Adafruit_NeoPixel::clear(void) { memset(pixels, 0, numBytes); }
+
+// A 32-bit variant of gamma8() that applies the same function
+// to all components of a packed RGB or WRGB value.
+uint32_t Adafruit_NeoPixel::gamma32(uint32_t x) {
+ uint8_t *y = (uint8_t *)&x;
+ // All four bytes of a 32-bit value are filtered even if RGB (not WRGB),
+ // to avoid a bunch of shifting and masking that would be necessary for
+ // properly handling different endianisms (and each byte is a fairly
+ // trivial operation, so it might not even be wasting cycles vs a check
+ // and branch for the RGB case). In theory this might cause trouble *if*
+ // someone's storing information in the unused most significant byte
+ // of an RGB value, but this seems exceedingly rare and if it's
+ // encountered in reality they can mask values going in or coming out.
+ for (uint8_t i = 0; i < 4; i++)
+ y[i] = gamma8(y[i]);
+ return x; // Packed 32-bit return
+}
+
+/*!
+ @brief Fill NeoPixel strip with one or more cycles of hues.
+ Everyone loves the rainbow swirl so much, now it's canon!
+ @param first_hue Hue of first pixel, 0-65535, representing one full
+ cycle of the color wheel. Each subsequent pixel will
+ be offset to complete one or more cycles over the
+ length of the strip.
+ @param reps Number of cycles of the color wheel over the length
+ of the strip. Default is 1. Negative values can be
+ used to reverse the hue order.
+ @param saturation Saturation (optional), 0-255 = gray to pure hue,
+ default = 255.
+ @param brightness Brightness/value (optional), 0-255 = off to max,
+ default = 255. This is distinct and in combination
+ with any configured global strip brightness.
+ @param gammify If true (default), apply gamma correction to colors
+ for better appearance.
+*/
+void Adafruit_NeoPixel::rainbow(uint16_t first_hue, int8_t reps,
+ uint8_t saturation, uint8_t brightness, bool gammify) {
+ for (uint16_t i=0; i.
+ *
+ */
+
+#ifndef ADAFRUIT_NEOPIXEL_H
+#define ADAFRUIT_NEOPIXEL_H
+
+#ifdef ARDUINO
+#if (ARDUINO >= 100)
+#include
+#else
+#include
+#include
+#endif
+
+#ifdef USE_TINYUSB // For Serial when selecting TinyUSB
+#include
+#endif
+
+#endif
+
+#ifdef TARGET_LPC1768
+#include
+#endif
+
+#if defined(ARDUINO_ARCH_RP2040)
+#include
+#include "hardware/pio.h"
+#include "hardware/clocks.h"
+#include "rp2040_pio.h"
+#endif
+
+// The order of primary colors in the NeoPixel data stream can vary among
+// device types, manufacturers and even different revisions of the same
+// item. The third parameter to the Adafruit_NeoPixel constructor encodes
+// the per-pixel byte offsets of the red, green and blue primaries (plus
+// white, if present) in the data stream -- the following #defines provide
+// an easier-to-use named version for each permutation. e.g. NEO_GRB
+// indicates a NeoPixel-compatible device expecting three bytes per pixel,
+// with the first byte transmitted containing the green value, second
+// containing red and third containing blue. The in-memory representation
+// of a chain of NeoPixels is the same as the data-stream order; no
+// re-ordering of bytes is required when issuing data to the chain.
+// Most of these values won't exist in real-world devices, but it's done
+// this way so we're ready for it (also, if using the WS2811 driver IC,
+// one might have their pixels set up in any weird permutation).
+
+// Bits 5,4 of this value are the offset (0-3) from the first byte of a
+// pixel to the location of the red color byte. Bits 3,2 are the green
+// offset and 1,0 are the blue offset. If it is an RGBW-type device
+// (supporting a white primary in addition to R,G,B), bits 7,6 are the
+// offset to the white byte...otherwise, bits 7,6 are set to the same value
+// as 5,4 (red) to indicate an RGB (not RGBW) device.
+// i.e. binary representation:
+// 0bWWRRGGBB for RGBW devices
+// 0bRRRRGGBB for RGB
+
+// RGB NeoPixel permutations; white and red offsets are always same
+// Offset: W R G B
+#define NEO_RGB ((0 << 6) | (0 << 4) | (1 << 2) | (2)) ///< Transmit as R,G,B
+#define NEO_RBG ((0 << 6) | (0 << 4) | (2 << 2) | (1)) ///< Transmit as R,B,G
+#define NEO_GRB ((1 << 6) | (1 << 4) | (0 << 2) | (2)) ///< Transmit as G,R,B
+#define NEO_GBR ((2 << 6) | (2 << 4) | (0 << 2) | (1)) ///< Transmit as G,B,R
+#define NEO_BRG ((1 << 6) | (1 << 4) | (2 << 2) | (0)) ///< Transmit as B,R,G
+#define NEO_BGR ((2 << 6) | (2 << 4) | (1 << 2) | (0)) ///< Transmit as B,G,R
+
+// RGBW NeoPixel permutations; all 4 offsets are distinct
+// Offset: W R G B
+#define NEO_WRGB ((0 << 6) | (1 << 4) | (2 << 2) | (3)) ///< Transmit as W,R,G,B
+#define NEO_WRBG ((0 << 6) | (1 << 4) | (3 << 2) | (2)) ///< Transmit as W,R,B,G
+#define NEO_WGRB ((0 << 6) | (2 << 4) | (1 << 2) | (3)) ///< Transmit as W,G,R,B
+#define NEO_WGBR ((0 << 6) | (3 << 4) | (1 << 2) | (2)) ///< Transmit as W,G,B,R
+#define NEO_WBRG ((0 << 6) | (2 << 4) | (3 << 2) | (1)) ///< Transmit as W,B,R,G
+#define NEO_WBGR ((0 << 6) | (3 << 4) | (2 << 2) | (1)) ///< Transmit as W,B,G,R
+
+#define NEO_RWGB ((1 << 6) | (0 << 4) | (2 << 2) | (3)) ///< Transmit as R,W,G,B
+#define NEO_RWBG ((1 << 6) | (0 << 4) | (3 << 2) | (2)) ///< Transmit as R,W,B,G
+#define NEO_RGWB ((2 << 6) | (0 << 4) | (1 << 2) | (3)) ///< Transmit as R,G,W,B
+#define NEO_RGBW ((3 << 6) | (0 << 4) | (1 << 2) | (2)) ///< Transmit as R,G,B,W
+#define NEO_RBWG ((2 << 6) | (0 << 4) | (3 << 2) | (1)) ///< Transmit as R,B,W,G
+#define NEO_RBGW ((3 << 6) | (0 << 4) | (2 << 2) | (1)) ///< Transmit as R,B,G,W
+
+#define NEO_GWRB ((1 << 6) | (2 << 4) | (0 << 2) | (3)) ///< Transmit as G,W,R,B
+#define NEO_GWBR ((1 << 6) | (3 << 4) | (0 << 2) | (2)) ///< Transmit as G,W,B,R
+#define NEO_GRWB ((2 << 6) | (1 << 4) | (0 << 2) | (3)) ///< Transmit as G,R,W,B
+#define NEO_GRBW ((3 << 6) | (1 << 4) | (0 << 2) | (2)) ///< Transmit as G,R,B,W
+#define NEO_GBWR ((2 << 6) | (3 << 4) | (0 << 2) | (1)) ///< Transmit as G,B,W,R
+#define NEO_GBRW ((3 << 6) | (2 << 4) | (0 << 2) | (1)) ///< Transmit as G,B,R,W
+
+#define NEO_BWRG ((1 << 6) | (2 << 4) | (3 << 2) | (0)) ///< Transmit as B,W,R,G
+#define NEO_BWGR ((1 << 6) | (3 << 4) | (2 << 2) | (0)) ///< Transmit as B,W,G,R
+#define NEO_BRWG ((2 << 6) | (1 << 4) | (3 << 2) | (0)) ///< Transmit as B,R,W,G
+#define NEO_BRGW ((3 << 6) | (1 << 4) | (2 << 2) | (0)) ///< Transmit as B,R,G,W
+#define NEO_BGWR ((2 << 6) | (3 << 4) | (1 << 2) | (0)) ///< Transmit as B,G,W,R
+#define NEO_BGRW ((3 << 6) | (2 << 4) | (1 << 2) | (0)) ///< Transmit as B,G,R,W
+
+// Add NEO_KHZ400 to the color order value to indicate a 400 KHz device.
+// All but the earliest v1 NeoPixels expect an 800 KHz data stream, this is
+// the default if unspecified. Because flash space is very limited on ATtiny
+// devices (e.g. Trinket, Gemma), v1 NeoPixels aren't handled by default on
+// those chips, though it can be enabled by removing the ifndef/endif below,
+// but code will be bigger. Conversely, can disable the NEO_KHZ400 line on
+// other MCUs to remove v1 support and save a little space.
+
+#define NEO_KHZ800 0x0000 ///< 800 KHz data transmission
+#ifndef __AVR_ATtiny85__
+#define NEO_KHZ400 0x0100 ///< 400 KHz data transmission
+#endif
+
+// If 400 KHz support is enabled, the third parameter to the constructor
+// requires a 16-bit value (in order to select 400 vs 800 KHz speed).
+// If only 800 KHz is enabled (as is default on ATtiny), an 8-bit value
+// is sufficient to encode pixel color order, saving some space.
+
+#ifdef NEO_KHZ400
+typedef uint16_t neoPixelType; ///< 3rd arg to Adafruit_NeoPixel constructor
+#else
+typedef uint8_t neoPixelType; ///< 3rd arg to Adafruit_NeoPixel constructor
+#endif
+
+// These two tables are declared outside the Adafruit_NeoPixel class
+// because some boards may require oldschool compilers that don't
+// handle the C++11 constexpr keyword.
+
+/* A PROGMEM (flash mem) table containing 8-bit unsigned sine wave (0-255).
+ Copy & paste this snippet into a Python REPL to regenerate:
+import math
+for x in range(256):
+ print("{:3},".format(int((math.sin(x/128.0*math.pi)+1.0)*127.5+0.5))),
+ if x&15 == 15: print
+*/
+static const uint8_t PROGMEM _NeoPixelSineTable[256] = {
+ 128, 131, 134, 137, 140, 143, 146, 149, 152, 155, 158, 162, 165, 167, 170,
+ 173, 176, 179, 182, 185, 188, 190, 193, 196, 198, 201, 203, 206, 208, 211,
+ 213, 215, 218, 220, 222, 224, 226, 228, 230, 232, 234, 235, 237, 238, 240,
+ 241, 243, 244, 245, 246, 248, 249, 250, 250, 251, 252, 253, 253, 254, 254,
+ 254, 255, 255, 255, 255, 255, 255, 255, 254, 254, 254, 253, 253, 252, 251,
+ 250, 250, 249, 248, 246, 245, 244, 243, 241, 240, 238, 237, 235, 234, 232,
+ 230, 228, 226, 224, 222, 220, 218, 215, 213, 211, 208, 206, 203, 201, 198,
+ 196, 193, 190, 188, 185, 182, 179, 176, 173, 170, 167, 165, 162, 158, 155,
+ 152, 149, 146, 143, 140, 137, 134, 131, 128, 124, 121, 118, 115, 112, 109,
+ 106, 103, 100, 97, 93, 90, 88, 85, 82, 79, 76, 73, 70, 67, 65,
+ 62, 59, 57, 54, 52, 49, 47, 44, 42, 40, 37, 35, 33, 31, 29,
+ 27, 25, 23, 21, 20, 18, 17, 15, 14, 12, 11, 10, 9, 7, 6,
+ 5, 5, 4, 3, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0,
+ 0, 1, 1, 1, 2, 2, 3, 4, 5, 5, 6, 7, 9, 10, 11,
+ 12, 14, 15, 17, 18, 20, 21, 23, 25, 27, 29, 31, 33, 35, 37,
+ 40, 42, 44, 47, 49, 52, 54, 57, 59, 62, 65, 67, 70, 73, 76,
+ 79, 82, 85, 88, 90, 93, 97, 100, 103, 106, 109, 112, 115, 118, 121,
+ 124};
+
+/* Similar to above, but for an 8-bit gamma-correction table.
+ Copy & paste this snippet into a Python REPL to regenerate:
+import math
+gamma=2.6
+for x in range(256):
+ print("{:3},".format(int(math.pow((x)/255.0,gamma)*255.0+0.5))),
+ if x&15 == 15: print
+*/
+static const uint8_t PROGMEM _NeoPixelGammaTable[256] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3,
+ 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6,
+ 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10,
+ 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, 17,
+ 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25,
+ 25, 26, 27, 27, 28, 29, 29, 30, 31, 31, 32, 33, 34, 34, 35,
+ 36, 37, 38, 38, 39, 40, 41, 42, 42, 43, 44, 45, 46, 47, 48,
+ 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
+ 64, 65, 66, 68, 69, 70, 71, 72, 73, 75, 76, 77, 78, 80, 81,
+ 82, 84, 85, 86, 88, 89, 90, 92, 93, 94, 96, 97, 99, 100, 102,
+ 103, 105, 106, 108, 109, 111, 112, 114, 115, 117, 119, 120, 122, 124, 125,
+ 127, 129, 130, 132, 134, 136, 137, 139, 141, 143, 145, 146, 148, 150, 152,
+ 154, 156, 158, 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182,
+ 184, 186, 188, 191, 193, 195, 197, 199, 202, 204, 206, 209, 211, 213, 215,
+ 218, 220, 223, 225, 227, 230, 232, 235, 237, 240, 242, 245, 247, 250, 252,
+ 255};
+
+/*!
+ @brief Class that stores state and functions for interacting with
+ Adafruit NeoPixels and compatible devices.
+*/
+class Adafruit_NeoPixel {
+
+public:
+ // Constructor: number of LEDs, pin number, LED type
+ Adafruit_NeoPixel(uint16_t n, int16_t pin = 6,
+ neoPixelType type = NEO_GRB + NEO_KHZ800);
+ Adafruit_NeoPixel(void);
+ ~Adafruit_NeoPixel();
+
+ void begin(void);
+ void show(void);
+ void setPin(int16_t p);
+ void setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b);
+ void setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b, uint8_t w);
+ void setPixelColor(uint16_t n, uint32_t c);
+ void fill(uint32_t c = 0, uint16_t first = 0, uint16_t count = 0);
+ void setBrightness(uint8_t);
+ void clear(void);
+ void updateLength(uint16_t n);
+ void updateType(neoPixelType t);
+ /*!
+ @brief Check whether a call to show() will start sending data
+ immediately or will 'block' for a required interval. NeoPixels
+ require a short quiet time (about 300 microseconds) after the
+ last bit is received before the data 'latches' and new data can
+ start being received. Usually one's sketch is implicitly using
+ this time to generate a new frame of animation...but if it
+ finishes very quickly, this function could be used to see if
+ there's some idle time available for some low-priority
+ concurrent task.
+ @return 1 or true if show() will start sending immediately, 0 or false
+ if show() would block (meaning some idle time is available).
+ */
+ bool canShow(void) {
+ // It's normal and possible for endTime to exceed micros() if the
+ // 32-bit clock counter has rolled over (about every 70 minutes).
+ // Since both are uint32_t, a negative delta correctly maps back to
+ // positive space, and it would seem like the subtraction below would
+ // suffice. But a problem arises if code invokes show() very
+ // infrequently...the micros() counter may roll over MULTIPLE times in
+ // that interval, the delta calculation is no longer correct and the
+ // next update may stall for a very long time. The check below resets
+ // the latch counter if a rollover has occurred. This can cause an
+ // extra delay of up to 300 microseconds in the rare case where a
+ // show() call happens precisely around the rollover, but that's
+ // neither likely nor especially harmful, vs. other code that might
+ // stall for 30+ minutes, or having to document and frequently remind
+ // and/or provide tech support explaining an unintuitive need for
+ // show() calls at least once an hour.
+ uint32_t now = micros();
+ if (endTime > now) {
+ endTime = now;
+ }
+ return (now - endTime) >= 300L;
+ }
+ /*!
+ @brief Get a pointer directly to the NeoPixel data buffer in RAM.
+ Pixel data is stored in a device-native format (a la the NEO_*
+ constants) and is not translated here. Applications that access
+ this buffer will need to be aware of the specific data format
+ and handle colors appropriately.
+ @return Pointer to NeoPixel buffer (uint8_t* array).
+ @note This is for high-performance applications where calling
+ setPixelColor() on every single pixel would be too slow (e.g.
+ POV or light-painting projects). There is no bounds checking
+ on the array, creating tremendous potential for mayhem if one
+ writes past the ends of the buffer. Great power, great
+ responsibility and all that.
+ */
+ uint8_t *getPixels(void) const { return pixels; };
+ uint8_t getBrightness(void) const;
+ /*!
+ @brief Retrieve the pin number used for NeoPixel data output.
+ @return Arduino pin number (-1 if not set).
+ */
+ int16_t getPin(void) const { return pin; };
+ /*!
+ @brief Return the number of pixels in an Adafruit_NeoPixel strip object.
+ @return Pixel count (0 if not set).
+ */
+ uint16_t numPixels(void) const { return numLEDs; }
+ uint32_t getPixelColor(uint16_t n) const;
+ /*!
+ @brief An 8-bit integer sine wave function, not directly compatible
+ with standard trigonometric units like radians or degrees.
+ @param x Input angle, 0-255; 256 would loop back to zero, completing
+ the circle (equivalent to 360 degrees or 2 pi radians).
+ One can therefore use an unsigned 8-bit variable and simply
+ add or subtract, allowing it to overflow/underflow and it
+ still does the expected contiguous thing.
+ @return Sine result, 0 to 255, or -128 to +127 if type-converted to
+ a signed int8_t, but you'll most likely want unsigned as this
+ output is often used for pixel brightness in animation effects.
+ */
+ static uint8_t sine8(uint8_t x) {
+ return pgm_read_byte(&_NeoPixelSineTable[x]); // 0-255 in, 0-255 out
+ }
+ /*!
+ @brief An 8-bit gamma-correction function for basic pixel brightness
+ adjustment. Makes color transitions appear more perceptially
+ correct.
+ @param x Input brightness, 0 (minimum or off/black) to 255 (maximum).
+ @return Gamma-adjusted brightness, can then be passed to one of the
+ setPixelColor() functions. This uses a fixed gamma correction
+ exponent of 2.6, which seems reasonably okay for average
+ NeoPixels in average tasks. If you need finer control you'll
+ need to provide your own gamma-correction function instead.
+ */
+ static uint8_t gamma8(uint8_t x) {
+ return pgm_read_byte(&_NeoPixelGammaTable[x]); // 0-255 in, 0-255 out
+ }
+ /*!
+ @brief Convert separate red, green and blue values into a single
+ "packed" 32-bit RGB color.
+ @param r Red brightness, 0 to 255.
+ @param g Green brightness, 0 to 255.
+ @param b Blue brightness, 0 to 255.
+ @return 32-bit packed RGB value, which can then be assigned to a
+ variable for later use or passed to the setPixelColor()
+ function. Packed RGB format is predictable, regardless of
+ LED strand color order.
+ */
+ static uint32_t Color(uint8_t r, uint8_t g, uint8_t b) {
+ return ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
+ }
+ /*!
+ @brief Convert separate red, green, blue and white values into a
+ single "packed" 32-bit WRGB color.
+ @param r Red brightness, 0 to 255.
+ @param g Green brightness, 0 to 255.
+ @param b Blue brightness, 0 to 255.
+ @param w White brightness, 0 to 255.
+ @return 32-bit packed WRGB value, which can then be assigned to a
+ variable for later use or passed to the setPixelColor()
+ function. Packed WRGB format is predictable, regardless of
+ LED strand color order.
+ */
+ static uint32_t Color(uint8_t r, uint8_t g, uint8_t b, uint8_t w) {
+ return ((uint32_t)w << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
+ }
+ static uint32_t ColorHSV(uint16_t hue, uint8_t sat = 255, uint8_t val = 255);
+ /*!
+ @brief A gamma-correction function for 32-bit packed RGB or WRGB
+ colors. Makes color transitions appear more perceptially
+ correct.
+ @param x 32-bit packed RGB or WRGB color.
+ @return Gamma-adjusted packed color, can then be passed in one of the
+ setPixelColor() functions. Like gamma8(), this uses a fixed
+ gamma correction exponent of 2.6, which seems reasonably okay
+ for average NeoPixels in average tasks. If you need finer
+ control you'll need to provide your own gamma-correction
+ function instead.
+ */
+ static uint32_t gamma32(uint32_t x);
+
+ void rainbow(uint16_t first_hue = 0, int8_t reps = 1,
+ uint8_t saturation = 255, uint8_t brightness = 255,
+ bool gammify = true);
+
+private:
+#if defined(ARDUINO_ARCH_RP2040)
+ void rp2040Init(uint8_t pin, bool is800KHz);
+ void rp2040Show(uint8_t pin, uint8_t *pixels, uint32_t numBytes, bool is800KHz);
+#endif
+
+protected:
+#ifdef NEO_KHZ400 // If 400 KHz NeoPixel support enabled...
+ bool is800KHz; ///< true if 800 KHz pixels
+#endif
+ bool begun; ///< true if begin() previously called
+ uint16_t numLEDs; ///< Number of RGB LEDs in strip
+ uint16_t numBytes; ///< Size of 'pixels' buffer below
+ int16_t pin; ///< Output pin number (-1 if not yet set)
+ uint8_t brightness; ///< Strip brightness 0-255 (stored as +1)
+ uint8_t *pixels; ///< Holds LED color values (3 or 4 bytes each)
+ uint8_t rOffset; ///< Red index within each 3- or 4-byte pixel
+ uint8_t gOffset; ///< Index of green byte
+ uint8_t bOffset; ///< Index of blue byte
+ uint8_t wOffset; ///< Index of white (==rOffset if no white)
+ uint32_t endTime; ///< Latch timing reference
+#ifdef __AVR__
+ volatile uint8_t *port; ///< Output PORT register
+ uint8_t pinMask; ///< Output PORT bitmask
+#endif
+#if defined(ARDUINO_ARCH_STM32) || defined(ARDUINO_ARCH_ARDUINO_CORE_STM32)
+ GPIO_TypeDef *gpioPort; ///< Output GPIO PORT
+ uint32_t gpioPin; ///< Output GPIO PIN
+#endif
+#if defined(ARDUINO_ARCH_RP2040)
+ PIO pio = pio0;
+ int sm = 0;
+ bool init = true;
+#endif
+};
+
+#endif // ADAFRUIT_NEOPIXEL_H
diff --git a/lib/bms/ESP8266HTTPClient.cpp b/lib/bms/ESP8266HTTPClient.cpp
new file mode 100644
index 0000000..89da711
--- /dev/null
+++ b/lib/bms/ESP8266HTTPClient.cpp
@@ -0,0 +1,1105 @@
+/**
+ * ESP8266HTTPClient.cpp
+ *
+ * Created on: 02.11.2015
+ *
+ * Copyright (c) 2015 Markus Sattler. All rights reserved.
+ * This file is part of the ESP8266HTTPClient for Arduino.
+ *
+ * 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
+#include
+
+#include "ESP8266HTTPClient.h"
+#include
+#include
+#include
+
+// per https://github.com/esp8266/Arduino/issues/8231
+// make sure HTTPClient can be utilized as a movable class member
+static_assert(std::is_default_constructible_v, "");
+static_assert(!std::is_copy_constructible_v, "");
+static_assert(std::is_move_constructible_v, "");
+static_assert(std::is_move_assignable_v, "");
+
+static const char defaultUserAgentPstr[] PROGMEM = "ESP8266HTTPClient";
+const String HTTPClient::defaultUserAgent = defaultUserAgentPstr;
+
+static int StreamReportToHttpClientReport (Stream::Report streamSendError)
+{
+ switch (streamSendError)
+ {
+ case Stream::Report::TimedOut: return HTTPC_ERROR_READ_TIMEOUT;
+ case Stream::Report::ReadError: return HTTPC_ERROR_NO_STREAM;
+ case Stream::Report::WriteError: return HTTPC_ERROR_STREAM_WRITE;
+ case Stream::Report::ShortOperation: return HTTPC_ERROR_STREAM_WRITE;
+ case Stream::Report::Success: return 0;
+ }
+ return 0; // never reached, keep gcc quiet
+}
+
+void HTTPClient::clear()
+{
+ _returnCode = 0;
+ _size = -1;
+ _headers.clear();
+ _location.clear();
+ _payload.reset();
+}
+
+
+/**
+ * parsing the url for all needed parameters
+ * @param client Client&
+ * @param url String
+ * @param https bool
+ * @return success bool
+ */
+bool HTTPClient::begin(WiFiClient &client, const String& url) {
+ // check for : (http: or https:)
+ int index = url.indexOf(':');
+ if(index < 0) {
+ DEBUG_HTTPCLIENT("[HTTP-Client][begin] failed to parse protocol\n");
+ return false;
+ }
+
+ String protocol = url.substring(0, index);
+ protocol.toLowerCase();
+ if(protocol != "http" && protocol != "https") {
+ DEBUG_HTTPCLIENT("[HTTP-Client][begin] unknown protocol '%s'\n", protocol.c_str());
+ return false;
+ }
+
+ _port = (protocol == "https" ? 443 : 80);
+ _client = client.clone();
+
+ return beginInternal(url, protocol.c_str());
+}
+
+
+/**
+ * directly supply all needed parameters
+ * @param client Client&
+ * @param host String
+ * @param port uint16_t
+ * @param uri String
+ * @param https bool
+ * @return success bool
+ */
+bool HTTPClient::begin(WiFiClient &client, const String& host, uint16_t port, const String& uri, bool https)
+{
+ // Disconnect when reusing HTTPClient to talk to a different host
+ if (!_host.isEmpty() && _host != host) {
+ _canReuse = false;
+ disconnect(true);
+ }
+
+ _client = client.clone();
+
+ clear();
+
+ _host = host;
+ _port = port;
+ _uri = uri;
+ _protocol = (https ? "https" : "http");
+ return true;
+}
+
+
+bool HTTPClient::beginInternal(const String& __url, const char* expectedProtocol)
+{
+ String url(__url);
+
+ DEBUG_HTTPCLIENT("[HTTP-Client][begin] url: %s\n", url.c_str());
+ clear();
+
+ // check for : (http: or https:
+ int index = url.indexOf(':');
+ if(index < 0) {
+ DEBUG_HTTPCLIENT("[HTTP-Client][begin] failed to parse protocol\n");
+ return false;
+ }
+
+ _protocol = url.substring(0, index);
+ _protocol.toLowerCase();
+ url.remove(0, (index + 3)); // remove http:// or https://
+
+ if (_protocol == "http") {
+ // set default port for 'http'
+ _port = 80;
+ } else if (_protocol == "https") {
+ // set default port for 'https'
+ _port = 443;
+ } else {
+ DEBUG_HTTPCLIENT("[HTTP-Client][begin] unsupported protocol: %s\n", _protocol.c_str());
+ return false;
+ }
+
+ index = url.indexOf('/');
+ String host = url.substring(0, index);
+ url.remove(0, index); // remove host part
+
+ // get Authorization
+ index = host.indexOf('@');
+ if(index >= 0) {
+ // auth info
+ String auth = host.substring(0, index);
+ host.remove(0, index + 1); // remove auth part including @
+ _base64Authorization = base64::encode(auth, false /* doNewLines */);
+ }
+
+ const String oldHost = _host;
+
+ // get port
+ index = host.indexOf(':');
+ if(index >= 0) {
+ _host = host.substring(0, index); // hostname
+ host.remove(0, (index + 1)); // remove hostname + :
+ _port = host.toInt(); // get port
+ } else {
+ _host = host;
+ }
+
+ // Disconnect when reusing HTTPClient to talk to a different host
+ if (!oldHost.isEmpty() && _host != oldHost) {
+ _canReuse = false;
+ disconnect(true);
+ }
+
+ _uri = url;
+
+ if ( expectedProtocol != nullptr && _protocol != expectedProtocol) {
+ DEBUG_HTTPCLIENT("[HTTP-Client][begin] unexpected protocol: %s, expected %s\n", _protocol.c_str(), expectedProtocol);
+ return false;
+ }
+ DEBUG_HTTPCLIENT("[HTTP-Client][begin] host: %s port: %d url: %s\n", _host.c_str(), _port, _uri.c_str());
+ return true;
+}
+
+
+/**
+ * end
+ * called after the payload is handled
+ */
+void HTTPClient::end(void)
+{
+ disconnect(false);
+ clear();
+}
+
+/**
+ * disconnect
+ * close the TCP socket
+ */
+void HTTPClient::disconnect(bool preserveClient)
+{
+ if(connected()) {
+ if(_client->available() > 0) {
+ DEBUG_HTTPCLIENT("[HTTP-Client][end] still data in buffer (%d), clean up.\n", _client->available());
+ while(_client->available() > 0) {
+ _client->read();
+ }
+ }
+
+ if(_reuse && _canReuse) {
+ DEBUG_HTTPCLIENT("[HTTP-Client][end] tcp keep open for reuse\n");
+ } else {
+ DEBUG_HTTPCLIENT("[HTTP-Client][end] tcp stop\n");
+ if(_client) {
+ _client->stop();
+ if (!preserveClient) {
+ _client = nullptr;
+ }
+ }
+ }
+ } else {
+ if (!preserveClient && _client) { // Also destroy _client if not connected()
+ _client = nullptr;
+ }
+
+ DEBUG_HTTPCLIENT("[HTTP-Client][end] tcp is closed\n");
+ }
+}
+
+/**
+ * connected
+ * @return connected status
+ */
+bool HTTPClient::connected()
+{
+ if(_client) {
+ return (_client->connected() || (_client->available() > 0));
+ }
+ return false;
+}
+
+/**
+ * try to reuse the connection to the server
+ * keep-alive
+ * @param reuse bool
+ */
+void HTTPClient::setReuse(bool reuse)
+{
+ _reuse = reuse;
+}
+
+/**
+ * set User Agent
+ * @param userAgent const char *
+ */
+void HTTPClient::setUserAgent(const String& userAgent)
+{
+ _userAgent = userAgent;
+}
+
+/**
+ * set the Authorizatio for the http request
+ * @param user const char *
+ * @param password const char *
+ */
+void HTTPClient::setAuthorization(const char * user, const char * password)
+{
+ if(user && password) {
+ String auth = user;
+ auth += ':';
+ auth += password;
+ _base64Authorization = base64::encode(auth, false /* doNewLines */);
+ }
+}
+
+/**
+ * set the Authorization for the http request
+ * @param auth const char * base64
+ */
+void HTTPClient::setAuthorization(const char * auth)
+{
+ if (auth) {
+ setAuthorization(String(auth));
+ }
+}
+
+/**
+ * set the Authorization for the http request
+ * @param auth String base64
+ */
+void HTTPClient::setAuthorization(String auth)
+{
+ _base64Authorization = std::move(auth);
+ _base64Authorization.replace(String('\n'), emptyString);
+}
+
+/**
+ * set the timeout for the TCP connection
+ * @param timeout unsigned int
+ */
+void HTTPClient::setTimeout(uint16_t timeout)
+{
+ _tcpTimeout = timeout;
+ if(connected()) {
+ _client->setTimeout(timeout);
+ }
+}
+
+/**
+ * set the URL to a new value. Handy for following redirects.
+ * @param url
+ */
+bool HTTPClient::setURL(const String& url)
+{
+ // if the new location is only a path then only update the URI
+ if (url && url[0] == '/') {
+ _uri = url;
+ clear();
+ return true;
+ }
+
+ if (!url.startsWith(_protocol + ':')) {
+ DEBUG_HTTPCLIENT("[HTTP-Client][setURL] new URL not the same protocol, expected '%s', URL: '%s'\n", _protocol.c_str(), url.c_str());
+ return false;
+ }
+ // disconnect but preserve _client (clear _canReuse so disconnect will close the connection)
+ _canReuse = false;
+ disconnect(true);
+ return beginInternal(url, nullptr);
+}
+
+/**
+ * set redirect follow mode. See `followRedirects_t` enum for available modes.
+ * @param follow
+ */
+void HTTPClient::setFollowRedirects(followRedirects_t follow)
+{
+ _followRedirects = follow;
+}
+
+void HTTPClient::setRedirectLimit(uint16_t limit)
+{
+ _redirectLimit = limit;
+}
+
+/**
+ * use HTTP1.0
+ * @param useHTTP10 bool
+ */
+void HTTPClient::useHTTP10(bool useHTTP10)
+{
+ _useHTTP10 = useHTTP10;
+ _reuse = !useHTTP10;
+}
+
+/**
+ * send a GET request
+ * @return http code
+ */
+int HTTPClient::GET()
+{
+ return sendRequest("GET");
+}
+/**
+ * send a DELETE request
+ * @return http code
+ */
+int HTTPClient::DELETE()
+{
+ return sendRequest("DELETE");
+}
+
+/**
+ * sends a post request to the server
+ * @param payload const uint8_t *
+ * @param size size_t
+ * @return http code
+ */
+int HTTPClient::POST(const uint8_t* payload, size_t size)
+{
+ return sendRequest("POST", payload, size);
+}
+
+int HTTPClient::POST(const String& payload)
+{
+ return POST((uint8_t *) payload.c_str(), payload.length());
+}
+
+/**
+ * sends a put request to the server
+ * @param payload uint8_t *
+ * @param size size_t
+ * @return http code
+ */
+int HTTPClient::PUT(const uint8_t* payload, size_t size) {
+ return sendRequest("PUT", payload, size);
+}
+
+int HTTPClient::PUT(const String& payload) {
+ return PUT((const uint8_t *) payload.c_str(), payload.length());
+}
+
+/**
+ * sends a patch request to the server
+ * @param payload const uint8_t *
+ * @param size size_t
+ * @return http code
+ */
+int HTTPClient::PATCH(const uint8_t * payload, size_t size) {
+ return sendRequest("PATCH", payload, size);
+}
+
+int HTTPClient::PATCH(const String& payload) {
+ return PATCH((const uint8_t *) payload.c_str(), payload.length());
+}
+
+/**
+ * sendRequest
+ * @param type const char * "GET", "POST", ....
+ * @param payload String data for the message body
+ * @return
+ */
+int HTTPClient::sendRequest(const char * type, const String& payload)
+{
+ return sendRequest(type, (const uint8_t *) payload.c_str(), payload.length());
+}
+
+/**
+ * sendRequest
+ * @param type const char * "GET", "POST", ....
+ * @param payload const uint8_t * data for the message body if null not send
+ * @param size size_t size for the message body if 0 not send
+ * @return -1 if no info or > 0 when Content-Length is set by server
+ */
+int HTTPClient::sendRequest(const char * type, const uint8_t * payload, size_t size)
+{
+ int code;
+ bool redirect = false;
+ uint16_t redirectCount = 0;
+ do {
+ // wipe out any existing headers from previous request
+ for(size_t i = 0; i < _headerKeysCount; i++) {
+ if (_currentHeaders[i].value.length() > 0) {
+ _currentHeaders[i].value.clear();
+ }
+ }
+
+ DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] type: '%s' redirCount: %d\n", type, redirectCount);
+
+ // connect to server
+ if(!connect()) {
+ return returnError(HTTPC_ERROR_CONNECTION_FAILED);
+ }
+
+ addHeader(F("Content-Length"), String(payload && size > 0 ? size : 0));
+
+ // send Header
+ if(!sendHeader(type)) {
+ return returnError(HTTPC_ERROR_SEND_HEADER_FAILED);
+ }
+
+ // transfer all of it, with send-timeout
+ if (size && StreamConstPtr(payload, size).sendAll(_client.get()) != size)
+ return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED);
+
+ // handle Server Response (Header)
+ code = handleHeaderResponse();
+
+ //
+ // Handle redirections as stated in RFC document:
+ // https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
+ //
+ // Implementing HTTP_CODE_FOUND as redirection with GET method,
+ // to follow most of existing user agent implementations.
+ //
+ redirect = false;
+ if (
+ _followRedirects != HTTPC_DISABLE_FOLLOW_REDIRECTS &&
+ redirectCount < _redirectLimit &&
+ _location.length() > 0
+ ) {
+ switch (code) {
+ // redirecting using the same method
+ case HTTP_CODE_MOVED_PERMANENTLY:
+ case HTTP_CODE_TEMPORARY_REDIRECT: {
+ if (
+ // allow to force redirections on other methods
+ // (the RFC require user to accept the redirection)
+ _followRedirects == HTTPC_FORCE_FOLLOW_REDIRECTS ||
+ // allow GET and HEAD methods without force
+ !strcmp(type, "GET") ||
+ !strcmp(type, "HEAD")
+ ) {
+ redirectCount += 1;
+ DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] following redirect (the same method): '%s' redirCount: %d\n", _location.c_str(), redirectCount);
+ if (!setURL(_location)) {
+ DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] failed setting URL for redirection\n");
+ // no redirection
+ break;
+ }
+ // redirect using the same request method and payload, different URL
+ redirect = true;
+ }
+ break;
+ }
+ // redirecting with method dropped to GET or HEAD
+ // note: it does not need `HTTPC_FORCE_FOLLOW_REDIRECTS` for any method
+ case HTTP_CODE_FOUND:
+ case HTTP_CODE_SEE_OTHER: {
+ redirectCount += 1;
+ DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] following redirect (dropped to GET/HEAD): '%s' redirCount: %d\n", _location.c_str(), redirectCount);
+ if (!setURL(_location)) {
+ DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] failed setting URL for redirection\n");
+ // no redirection
+ break;
+ }
+ // redirect after changing method to GET/HEAD and dropping payload
+ type = "GET";
+ payload = nullptr;
+ size = 0;
+ redirect = true;
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+ } while (redirect);
+
+ // handle Server Response (Header)
+ return returnError(code);
+}
+
+/**
+ * sendRequest
+ * @param type const char * "GET", "POST", ....
+ * @param stream Stream * data stream for the message body
+ * @param size size_t size for the message body if 0 not Content-Length is send
+ * @return -1 if no info or > 0 when Content-Length is set by server
+ */
+int HTTPClient::sendRequest(const char * type, Stream * stream, size_t size)
+{
+
+ if(!stream) {
+ return returnError(HTTPC_ERROR_NO_STREAM);
+ }
+
+ // connect to server
+ if(!connect()) {
+ return returnError(HTTPC_ERROR_CONNECTION_FAILED);
+ }
+
+ if(size > 0) {
+ addHeader(F("Content-Length"), String(size));
+ }
+
+ // send Header
+ if(!sendHeader(type)) {
+ return returnError(HTTPC_ERROR_SEND_HEADER_FAILED);
+ }
+
+ // transfer all of it, with timeout
+ size_t transferred = stream->sendSize(_client.get(), size);
+ if (transferred != size)
+ {
+ DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] short write, asked for %zu but got %zu failed.\n", size, transferred);
+ esp_yield();
+ return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED);
+ }
+
+ // handle Server Response (Header)
+ return returnError(handleHeaderResponse());
+}
+
+/**
+ * size of message body / payload
+ * @return -1 if no info or > 0 when Content-Length is set by server
+ */
+int HTTPClient::getSize(void)
+{
+ return _size;
+}
+
+/**
+ * Location if redirect
+ */
+const String& HTTPClient::getLocation(void)
+{
+ return _location;
+}
+
+/**
+ * returns the stream of the tcp connection
+ * @return WiFiClient
+ */
+WiFiClient& HTTPClient::getStream(void)
+{
+ if(connected()) {
+ return *_client;
+ }
+
+ DEBUG_HTTPCLIENT("[HTTP-Client] getStream: not connected\n");
+ static WiFiClient empty;
+ return empty;
+}
+
+/**
+ * returns the stream of the tcp connection
+ * @return WiFiClient *
+ */
+WiFiClient* HTTPClient::getStreamPtr(void)
+{
+ if(connected()) {
+ return _client.get();
+ }
+
+ DEBUG_HTTPCLIENT("[HTTP-Client] getStreamPtr: not connected\n");
+ return nullptr;
+}
+
+/**
+ * write all message body / payload to Stream
+ * @param stream Stream *
+ * @return bytes written ( negative values are error codes )
+ */
+int HTTPClient::writeToStream(Stream * stream)
+{
+ return writeToPrint(stream);
+}
+
+/**
+ * write all message body / payload to Print
+ * @param print Print *
+ * @return bytes written ( negative values are error codes )
+ */
+int HTTPClient::writeToPrint(Print * print)
+{
+
+ if(!print) {
+ return returnError(HTTPC_ERROR_NO_STREAM);
+ }
+
+ // Only return error if not connected and no data available, because otherwise ::getString() will return an error instead of an empty
+ // string when the server returned a http code 204 (no content)
+ if(!connected() && _transferEncoding != HTTPC_TE_IDENTITY && _size > 0) {
+ return returnError(HTTPC_ERROR_NOT_CONNECTED);
+ }
+
+ // get length of document (is -1 when Server sends no Content-Length header)
+ int len = _size;
+ int ret = 0;
+
+ if(_transferEncoding == HTTPC_TE_IDENTITY) {
+ // len < 0: transfer all of it, with timeout
+ // len >= 0: max:len, with timeout
+ ret = _client->sendSize(print, len);
+
+ // do we have an error?
+ if(_client->getLastSendReport() != Stream::Report::Success) {
+ return returnError(StreamReportToHttpClientReport(_client->getLastSendReport()));
+ }
+ } else if(_transferEncoding == HTTPC_TE_CHUNKED) {
+ int size = 0;
+ while(1) {
+ if(!connected()) {
+ return returnError(HTTPC_ERROR_CONNECTION_LOST);
+ }
+ String chunkHeader = _client->readStringUntil('\n');
+
+ if(chunkHeader.length() <= 0) {
+ return returnError(HTTPC_ERROR_READ_TIMEOUT);
+ }
+
+ chunkHeader.trim(); // remove \r
+
+ // read size of chunk
+ len = (uint32_t) strtol((const char *) chunkHeader.c_str(), NULL, 16);
+ size += len;
+ DEBUG_HTTPCLIENT("[HTTP-Client] read chunk len: %d\n", len);
+
+ // data left?
+ if(len > 0) {
+ // read len bytes with timeout
+ int r = _client->sendSize(print, len);
+ if (_client->getLastSendReport() != Stream::Report::Success)
+ // not all data transferred
+ return returnError(StreamReportToHttpClientReport(_client->getLastSendReport()));
+ ret += r;
+ } else {
+
+ // if no length Header use global chunk size
+ if(_size <= 0) {
+ _size = size;
+ }
+
+ // check if we have write all data out
+ if(ret != _size) {
+ return returnError(HTTPC_ERROR_STREAM_WRITE);
+ }
+ break;
+ }
+
+ // read trailing \r\n at the end of the chunk
+ char buf[2];
+ auto trailing_seq_len = _client->readBytes((uint8_t*)buf, 2);
+ if (trailing_seq_len != 2 || buf[0] != '\r' || buf[1] != '\n') {
+ return returnError(HTTPC_ERROR_READ_TIMEOUT);
+ }
+
+ esp_yield();
+ }
+ } else {
+ return returnError(HTTPC_ERROR_ENCODING);
+ }
+
+ disconnect(true);
+ return ret;
+}
+
+/**
+ * return all payload as String (may need lot of ram or trigger out of memory!)
+ * @return String
+ */
+const String& HTTPClient::getString(void)
+{
+ if (_payload) {
+ return *_payload;
+ }
+
+ _payload.reset(new StreamString());
+
+ if(_size > 0) {
+ // try to reserve needed memory
+ if(!_payload->reserve((_size + 1))) {
+ DEBUG_HTTPCLIENT("[HTTP-Client][getString] not enough memory to reserve a string! need: %d\n", (_size + 1));
+ return *_payload;
+ }
+ }
+
+ writeToStream(_payload.get());
+ return *_payload;
+}
+
+/**
+ * converts error code to String
+ * @param error int
+ * @return String
+ */
+String HTTPClient::errorToString(int error)
+{
+ switch(error) {
+ case HTTPC_ERROR_CONNECTION_FAILED:
+ return F("connection failed");
+ case HTTPC_ERROR_SEND_HEADER_FAILED:
+ return F("send header failed");
+ case HTTPC_ERROR_SEND_PAYLOAD_FAILED:
+ return F("send payload failed");
+ case HTTPC_ERROR_NOT_CONNECTED:
+ return F("not connected");
+ case HTTPC_ERROR_CONNECTION_LOST:
+ return F("connection lost");
+ case HTTPC_ERROR_NO_STREAM:
+ return F("no stream");
+ case HTTPC_ERROR_NO_HTTP_SERVER:
+ return F("no HTTP server");
+ case HTTPC_ERROR_TOO_LESS_RAM:
+ return F("not enough ram");
+ case HTTPC_ERROR_ENCODING:
+ return F("Transfer-Encoding not supported");
+ case HTTPC_ERROR_STREAM_WRITE:
+ return F("Stream write error");
+ case HTTPC_ERROR_READ_TIMEOUT:
+ return F("read Timeout");
+ default:
+ return String();
+ }
+}
+
+/**
+ * adds Header to the request
+ * @param name
+ * @param value
+ * @param first
+ */
+void HTTPClient::addHeader(const String& name, const String& value, bool first, bool replace)
+{
+ // not allow set of Header handled by code
+ if (!name.equalsIgnoreCase(F("Connection")) &&
+ !name.equalsIgnoreCase(F("User-Agent")) &&
+ !name.equalsIgnoreCase(F("Host")) &&
+ !(name.equalsIgnoreCase(F("Authorization")) && _base64Authorization.length())) {
+
+ String headerLine;
+ headerLine.reserve(name.length() + value.length() + 4);
+ headerLine += name;
+ headerLine += ": ";
+
+ if (replace) {
+ int headerStart = _headers.indexOf(headerLine);
+ if (headerStart != -1) {
+ int headerEnd = _headers.indexOf('\n', headerStart);
+ _headers = _headers.substring(0, headerStart) + _headers.substring(headerEnd + 1);
+ }
+ }
+
+ headerLine += value;
+ headerLine += "\r\n";
+ if (first) {
+ _headers = headerLine + _headers;
+ } else {
+ _headers += headerLine;
+ }
+ }
+}
+
+void HTTPClient::collectHeaders(const char* headerKeys[], const size_t headerKeysCount)
+{
+ _headerKeysCount = headerKeysCount;
+ _currentHeaders = std::make_unique(_headerKeysCount);
+ for(size_t i = 0; i < _headerKeysCount; i++) {
+ _currentHeaders[i].key = headerKeys[i];
+ }
+}
+
+String HTTPClient::header(const char* name)
+{
+ for(size_t i = 0; i < _headerKeysCount; ++i) {
+ if(_currentHeaders[i].key == name) {
+ return _currentHeaders[i].value;
+ }
+ }
+ return String();
+}
+
+String HTTPClient::header(size_t i)
+{
+ if(i < _headerKeysCount) {
+ return _currentHeaders[i].value;
+ }
+ return String();
+}
+
+String HTTPClient::headerName(size_t i)
+{
+ if(i < _headerKeysCount) {
+ return _currentHeaders[i].key;
+ }
+ return String();
+}
+
+int HTTPClient::headers()
+{
+ return _headerKeysCount;
+}
+
+bool HTTPClient::hasHeader(const char* name)
+{
+ for(size_t i = 0; i < _headerKeysCount; ++i) {
+ if((_currentHeaders[i].key == name) && (_currentHeaders[i].value.length() > 0)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * init TCP connection and handle ssl verify if needed
+ * @return true if connection is ok
+ */
+bool HTTPClient::connect(void)
+{
+ if(_reuse && _canReuse && connected()) {
+ DEBUG_HTTPCLIENT("[HTTP-Client] connect: already connected, reusing connection\n");
+
+#if defined(NO_GLOBAL_INSTANCES) || defined(NO_GLOBAL_STREAMDEV)
+ StreamNull devnull;
+#endif
+ _client->sendAvailable(devnull); // clear _client's output (all of it, no timeout)
+ return true;
+ }
+
+ if(!_client) {
+ DEBUG_HTTPCLIENT("[HTTP-Client] connect: HTTPClient::begin was not called or returned error\n");
+ return false;
+ }
+
+ _client->setTimeout(_tcpTimeout);
+
+ if(!_client->connect(_host.c_str(), _port)) {
+ DEBUG_HTTPCLIENT("[HTTP-Client] failed connect to %s:%u\n", _host.c_str(), _port);
+ return false;
+ }
+
+ DEBUG_HTTPCLIENT("[HTTP-Client] connected to %s:%u\n", _host.c_str(), _port);
+
+#ifdef ESP8266
+ _client->setNoDelay(true);
+#endif
+ return connected();
+}
+
+/**
+ * sends HTTP request header
+ * @param type (GET, POST, ...)
+ * @return status
+ */
+bool HTTPClient::sendHeader(const char * type)
+{
+ if(!connected()) {
+ return false;
+ }
+
+ String header;
+ // 128: Arbitrarily chosen to have enough buffer space for avoiding internal reallocations
+ header.reserve(_headers.length() + _uri.length() +
+ _base64Authorization.length() + _host.length() + _userAgent.length() + 128);
+ header += type;
+ header += ' ';
+ if (_uri.length()) {
+ header += _uri;
+ } else {
+ header += '/';
+ }
+ header += F(" HTTP/1.");
+
+ if(_useHTTP10) {
+ header += '0';
+ } else {
+ header += '1';
+ }
+
+ header += F("\r\nHost: ");
+ header += _host;
+ if (_port != 80 && _port != 443)
+ {
+ header += ':';
+ header += String(_port);
+ }
+ if (_userAgent.length()) {
+ header += F("\r\nUser-Agent: ");
+ header += _userAgent;
+ }
+
+ if (!_useHTTP10) {
+ header += F("\r\nAccept-Encoding: identity;q=1,chunked;q=0.1,*;q=0");
+ }
+
+ if (_base64Authorization.length()) {
+ header += F("\r\nAuthorization: Basic ");
+ header += _base64Authorization;
+ }
+
+ header += F("\r\nConnection: ");
+ header += _reuse ? F("keep-alive") : F("close");
+ header += "\r\n";
+
+ header += _headers;
+ header += "\r\n";
+
+ DEBUG_HTTPCLIENT("[HTTP-Client] sending request header\n-----\n%s-----\n", header.c_str());
+
+ // transfer all of it, with timeout
+ return StreamConstPtr(header).sendAll(_client.get()) == header.length();
+}
+
+/**
+ * reads the response from the server
+ * @return int http code
+ */
+int HTTPClient::handleHeaderResponse()
+{
+
+ if(!connected()) {
+ return HTTPC_ERROR_NOT_CONNECTED;
+ }
+
+ clear();
+
+ _canReuse = _reuse;
+
+ String transferEncoding;
+
+ _transferEncoding = HTTPC_TE_IDENTITY;
+ unsigned long lastDataTime = millis();
+
+ while(connected()) {
+ size_t len = _client->available();
+ if(len > 0) {
+ int headerSeparator = -1;
+ String headerLine = _client->readStringUntil('\n');
+
+ lastDataTime = millis();
+
+ DEBUG_HTTPCLIENT("[HTTP-Client][handleHeaderResponse] RX: '%s'\n", headerLine.c_str());
+
+ if (headerLine.startsWith(F("HTTP/1."))) {
+
+ constexpr auto httpVersionIdx = sizeof "HTTP/1." - 1;
+ _canReuse = _canReuse && (headerLine[httpVersionIdx] != '0');
+ _returnCode = headerLine.substring(httpVersionIdx + 2, headerLine.indexOf(' ', httpVersionIdx + 2)).toInt();
+ _canReuse = _canReuse && (_returnCode > 0) && (_returnCode < 500);
+
+ } else if ((headerSeparator = headerLine.indexOf(':')) > 0) {
+ String headerName = headerLine.substring(0, headerSeparator);
+ String headerValue = headerLine.substring(headerSeparator + 1);
+ headerValue.trim();
+
+ if(headerName.equalsIgnoreCase(F("Content-Length"))) {
+ _size = headerValue.toInt();
+ }
+
+ if(_canReuse && headerName.equalsIgnoreCase(F("Connection"))) {
+ if (headerValue.indexOf(F("close")) >= 0 &&
+ headerValue.indexOf(F("keep-alive")) < 0) {
+ _canReuse = false;
+ }
+ }
+
+ if(headerName.equalsIgnoreCase(F("Transfer-Encoding"))) {
+ transferEncoding = headerValue;
+ }
+
+ if(headerName.equalsIgnoreCase(F("Location"))) {
+ _location = headerValue;
+ }
+
+ for (size_t i = 0; i < _headerKeysCount; i++) {
+ if (_currentHeaders[i].key.equalsIgnoreCase(headerName)) {
+ if (!_currentHeaders[i].value.isEmpty()) {
+ // Existing value, append this one with a comma
+ _currentHeaders[i].value += ',';
+ _currentHeaders[i].value += headerValue;
+ } else {
+ _currentHeaders[i].value = headerValue;
+ }
+ break; // We found a match, stop looking
+ }
+ }
+ continue;
+ }
+
+ headerLine.trim(); // remove \r
+
+ if (headerLine.isEmpty()) {
+ DEBUG_HTTPCLIENT("[HTTP-Client][handleHeaderResponse] code: %d\n", _returnCode);
+
+ if(_size > 0) {
+ DEBUG_HTTPCLIENT("[HTTP-Client][handleHeaderResponse] size: %d\n", _size);
+ }
+
+ if(transferEncoding.length() > 0) {
+ DEBUG_HTTPCLIENT("[HTTP-Client][handleHeaderResponse] Transfer-Encoding: %s\n", transferEncoding.c_str());
+ if(transferEncoding.equalsIgnoreCase(F("chunked"))) {
+ _transferEncoding = HTTPC_TE_CHUNKED;
+ } else {
+ _returnCode = HTTPC_ERROR_ENCODING;
+ return _returnCode;
+ }
+ } else {
+ _transferEncoding = HTTPC_TE_IDENTITY;
+ }
+
+ if(_returnCode <= 0) {
+ DEBUG_HTTPCLIENT("[HTTP-Client][handleHeaderResponse] Remote host is not an HTTP Server!");
+ _returnCode = HTTPC_ERROR_NO_HTTP_SERVER;
+ }
+ return _returnCode;
+ }
+
+ } else {
+ if((millis() - lastDataTime) > _tcpTimeout) {
+ return HTTPC_ERROR_READ_TIMEOUT;
+ }
+ esp_yield();
+ }
+ }
+
+ return HTTPC_ERROR_CONNECTION_LOST;
+}
+
+/**
+ * called to handle error return, may disconnect the connection if still exists
+ * @param error
+ * @return error
+ */
+int HTTPClient::returnError(int error)
+{
+ if(error < 0) {
+ DEBUG_HTTPCLIENT("[HTTP-Client][returnError] error(%d): %s\n", error, errorToString(error).c_str());
+ if(connected()) {
+ DEBUG_HTTPCLIENT("[HTTP-Client][returnError] tcp stop\n");
+ _client->stop();
+ }
+ }
+ return error;
+}
diff --git a/lib/bms/ESP8266HTTPClient.h b/lib/bms/ESP8266HTTPClient.h
new file mode 100644
index 0000000..9e3aef9
--- /dev/null
+++ b/lib/bms/ESP8266HTTPClient.h
@@ -0,0 +1,277 @@
+/**
+ * ESP8266HTTPClient.h
+ *
+ * Created on: 02.11.2015
+ *
+ * Copyright (c) 2015 Markus Sattler. All rights reserved.
+ * This file is part of the ESP8266HTTPClient for Arduino.
+ *
+ * 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
+ *
+ * Modified by Jeroen Döll, June 2018
+ */
+
+#ifndef ESP8266HTTPClient_H_
+#define ESP8266HTTPClient_H_
+
+#include
+#include
+#include
+
+#include
+
+#ifdef DEBUG_ESP_HTTP_CLIENT
+#ifdef DEBUG_ESP_PORT
+#define DEBUG_HTTPCLIENT(fmt, ...) DEBUG_ESP_PORT.printf_P( (PGM_P)PSTR(fmt), ## __VA_ARGS__ )
+#endif
+#endif
+
+#ifndef DEBUG_HTTPCLIENT
+#define DEBUG_HTTPCLIENT(...) do { (void)0; } while (0)
+#endif
+
+#define HTTPCLIENT_DEFAULT_TCP_TIMEOUT (5000)
+
+/// HTTP client errors
+#define HTTPC_ERROR_CONNECTION_FAILED (-1)
+#define HTTPC_ERROR_SEND_HEADER_FAILED (-2)
+#define HTTPC_ERROR_SEND_PAYLOAD_FAILED (-3)
+#define HTTPC_ERROR_NOT_CONNECTED (-4)
+#define HTTPC_ERROR_CONNECTION_LOST (-5)
+#define HTTPC_ERROR_NO_STREAM (-6)
+#define HTTPC_ERROR_NO_HTTP_SERVER (-7)
+#define HTTPC_ERROR_TOO_LESS_RAM (-8)
+#define HTTPC_ERROR_ENCODING (-9)
+#define HTTPC_ERROR_STREAM_WRITE (-10)
+#define HTTPC_ERROR_READ_TIMEOUT (-11)
+
+constexpr int HTTPC_ERROR_CONNECTION_REFUSED __attribute__((deprecated)) = HTTPC_ERROR_CONNECTION_FAILED;
+
+/// size for the stream handling
+#define HTTP_TCP_BUFFER_SIZE (1460)
+
+/// HTTP codes see RFC7231
+typedef enum {
+ HTTP_CODE_CONTINUE = 100,
+ HTTP_CODE_SWITCHING_PROTOCOLS = 101,
+ HTTP_CODE_PROCESSING = 102,
+ HTTP_CODE_OK = 200,
+ HTTP_CODE_CREATED = 201,
+ HTTP_CODE_ACCEPTED = 202,
+ HTTP_CODE_NON_AUTHORITATIVE_INFORMATION = 203,
+ HTTP_CODE_NO_CONTENT = 204,
+ HTTP_CODE_RESET_CONTENT = 205,
+ HTTP_CODE_PARTIAL_CONTENT = 206,
+ HTTP_CODE_MULTI_STATUS = 207,
+ HTTP_CODE_ALREADY_REPORTED = 208,
+ HTTP_CODE_IM_USED = 226,
+ HTTP_CODE_MULTIPLE_CHOICES = 300,
+ HTTP_CODE_MOVED_PERMANENTLY = 301,
+ HTTP_CODE_FOUND = 302,
+ HTTP_CODE_SEE_OTHER = 303,
+ HTTP_CODE_NOT_MODIFIED = 304,
+ HTTP_CODE_USE_PROXY = 305,
+ HTTP_CODE_TEMPORARY_REDIRECT = 307,
+ HTTP_CODE_PERMANENT_REDIRECT = 308,
+ HTTP_CODE_BAD_REQUEST = 400,
+ HTTP_CODE_UNAUTHORIZED = 401,
+ HTTP_CODE_PAYMENT_REQUIRED = 402,
+ HTTP_CODE_FORBIDDEN = 403,
+ HTTP_CODE_NOT_FOUND = 404,
+ HTTP_CODE_METHOD_NOT_ALLOWED = 405,
+ HTTP_CODE_NOT_ACCEPTABLE = 406,
+ HTTP_CODE_PROXY_AUTHENTICATION_REQUIRED = 407,
+ HTTP_CODE_REQUEST_TIMEOUT = 408,
+ HTTP_CODE_CONFLICT = 409,
+ HTTP_CODE_GONE = 410,
+ HTTP_CODE_LENGTH_REQUIRED = 411,
+ HTTP_CODE_PRECONDITION_FAILED = 412,
+ HTTP_CODE_PAYLOAD_TOO_LARGE = 413,
+ HTTP_CODE_URI_TOO_LONG = 414,
+ HTTP_CODE_UNSUPPORTED_MEDIA_TYPE = 415,
+ HTTP_CODE_RANGE_NOT_SATISFIABLE = 416,
+ HTTP_CODE_EXPECTATION_FAILED = 417,
+ HTTP_CODE_MISDIRECTED_REQUEST = 421,
+ HTTP_CODE_UNPROCESSABLE_ENTITY = 422,
+ HTTP_CODE_LOCKED = 423,
+ HTTP_CODE_FAILED_DEPENDENCY = 424,
+ HTTP_CODE_UPGRADE_REQUIRED = 426,
+ HTTP_CODE_PRECONDITION_REQUIRED = 428,
+ HTTP_CODE_TOO_MANY_REQUESTS = 429,
+ HTTP_CODE_REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
+ HTTP_CODE_INTERNAL_SERVER_ERROR = 500,
+ HTTP_CODE_NOT_IMPLEMENTED = 501,
+ HTTP_CODE_BAD_GATEWAY = 502,
+ HTTP_CODE_SERVICE_UNAVAILABLE = 503,
+ HTTP_CODE_GATEWAY_TIMEOUT = 504,
+ HTTP_CODE_HTTP_VERSION_NOT_SUPPORTED = 505,
+ HTTP_CODE_VARIANT_ALSO_NEGOTIATES = 506,
+ HTTP_CODE_INSUFFICIENT_STORAGE = 507,
+ HTTP_CODE_LOOP_DETECTED = 508,
+ HTTP_CODE_NOT_EXTENDED = 510,
+ HTTP_CODE_NETWORK_AUTHENTICATION_REQUIRED = 511
+} t_http_codes;
+
+typedef enum {
+ HTTPC_TE_IDENTITY,
+ HTTPC_TE_CHUNKED
+} transferEncoding_t;
+
+/**
+ * redirection follow mode.
+ * + `HTTPC_DISABLE_FOLLOW_REDIRECTS` - no redirection will be followed.
+ * + `HTTPC_STRICT_FOLLOW_REDIRECTS` - strict RFC2616, only requests using
+ * GET or HEAD methods will be redirected (using the same method),
+ * since the RFC requires end-user confirmation in other cases.
+ * + `HTTPC_FORCE_FOLLOW_REDIRECTS` - all redirections will be followed,
+ * regardless of a used method. New request will use the same method,
+ * and they will include the same body data and the same headers.
+ * In the sense of the RFC, it's just like every redirection is confirmed.
+ */
+typedef enum {
+ HTTPC_DISABLE_FOLLOW_REDIRECTS,
+ HTTPC_STRICT_FOLLOW_REDIRECTS,
+ HTTPC_FORCE_FOLLOW_REDIRECTS
+} followRedirects_t;
+
+class TransportTraits;
+typedef std::unique_ptr TransportTraitsPtr;
+
+class HTTPClient
+{
+public:
+ HTTPClient() = default;
+ ~HTTPClient() = default;
+ HTTPClient(HTTPClient&&) = default;
+ HTTPClient& operator=(HTTPClient&&) = default;
+
+ // Note that WiFiClient's underlying connection *will* be captured
+ bool begin(WiFiClient &client, const String& url);
+ bool begin(WiFiClient &client, const String& host, uint16_t port, const String& uri = "/", bool https = false);
+
+ // old API is now explicitly forbidden
+ bool begin(String url) __attribute__ ((error("obsolete API, use ::begin(WiFiClient, url)")));
+ bool begin(String host, uint16_t port, String uri = "/") __attribute__ ((error("obsolete API, use ::begin(WiFiClient, host, port, uri)")));
+ bool begin(String url, const uint8_t httpsFingerprint[20]) __attribute__ ((error("obsolete API, use ::begin(WiFiClientSecure, ...)")));
+ bool begin(String host, uint16_t port, String uri, const uint8_t httpsFingerprint[20]) __attribute__ ((error("obsolete API, use ::begin(WiFiClientSecure, ...)")));
+ bool begin(String host, uint16_t port, String uri, bool https, String httpsFingerprint) __attribute__ ((error("obsolete API, use ::begin(WiFiClientSecure, ...)")));
+
+ void end(void);
+
+ bool connected(void);
+
+ void setReuse(bool reuse); /// keep-alive
+ void setUserAgent(const String& userAgent);
+ void setAuthorization(const char * user, const char * password);
+ void setAuthorization(const char * auth);
+ void setAuthorization(String auth);
+ void setTimeout(uint16_t timeout);
+
+ // Redirections
+ void setFollowRedirects(followRedirects_t follow);
+ void setRedirectLimit(uint16_t limit); // max redirects to follow for a single request
+
+ bool setURL(const String& url); // handy for handling redirects
+ void useHTTP10(bool usehttp10 = true);
+
+ /// request handling
+ int GET();
+ int DELETE();
+ int POST(const uint8_t* payload, size_t size);
+ int POST(const String& payload);
+ int PUT(const uint8_t* payload, size_t size);
+ int PUT(const String& payload);
+ int PATCH(const uint8_t* payload, size_t size);
+ int PATCH(const String& payload);
+ int sendRequest(const char* type, const String& payload);
+ int sendRequest(const char* type, const uint8_t* payload = NULL, size_t size = 0);
+ int sendRequest(const char* type, Stream * stream, size_t size = 0);
+
+ void addHeader(const String& name, const String& value, bool first = false, bool replace = true);
+
+ /// Response handling
+ void collectHeaders(const char* headerKeys[], const size_t headerKeysCount);
+ String header(const char* name); // get request header value by name
+ String header(size_t i); // get request header value by number
+ String headerName(size_t i); // get request header name by number
+ int headers(); // get header count
+ bool hasHeader(const char* name); // check if header exists
+
+
+ int getSize(void);
+ const String& getLocation(void); // Location header from redirect if 3XX
+
+ WiFiClient& getStream(void);
+ WiFiClient* getStreamPtr(void);
+ int writeToPrint(Print* print);
+ int writeToStream(Stream* stream);
+ const String& getString(void);
+ static String errorToString(int error);
+
+protected:
+ struct RequestArgument {
+ String key;
+ String value;
+ };
+
+ bool beginInternal(const String& url, const char* expectedProtocol);
+ void disconnect(bool preserveClient = false);
+ void clear();
+ int returnError(int error);
+ bool connect(void);
+ bool sendHeader(const char * type);
+ int handleHeaderResponse();
+ int writeToStreamDataBlock(Stream * stream, int len);
+
+ // The common pattern to use the class is to
+ // {
+ // WiFiClient socket;
+ // HTTPClient http;
+ // http.begin(socket, "http://blahblah");
+ // }
+ // Make sure it's not possible to break things in an opposite direction
+
+ std::unique_ptr _client;
+
+ /// request handling
+ String _host;
+ uint16_t _port = 0;
+ bool _reuse = true;
+ uint16_t _tcpTimeout = HTTPCLIENT_DEFAULT_TCP_TIMEOUT;
+ bool _useHTTP10 = false;
+
+ String _uri;
+ String _protocol;
+ String _headers;
+ String _base64Authorization;
+
+ static const String defaultUserAgent;
+ String _userAgent = defaultUserAgent;
+
+ /// Response handling
+ std::unique_ptr _currentHeaders;
+ size_t _headerKeysCount = 0;
+
+ int _returnCode = 0;
+ int _size = -1;
+ bool _canReuse = false;
+ followRedirects_t _followRedirects = HTTPC_DISABLE_FOLLOW_REDIRECTS;
+ uint16_t _redirectLimit = 10;
+ String _location;
+ transferEncoding_t _transferEncoding = HTTPC_TE_IDENTITY;
+ std::unique_ptr _payload;
+};
+
+#endif /* ESP8266HTTPClient_H_ */
diff --git a/lib/bms/ESP8266WiFiMulti.cpp b/lib/bms/ESP8266WiFiMulti.cpp
new file mode 100644
index 0000000..dada704
--- /dev/null
+++ b/lib/bms/ESP8266WiFiMulti.cpp
@@ -0,0 +1,529 @@
+/**
+ *
+ * @file ESP8266WiFiMulti.cpp
+ * @date 30.09.2020
+ * @author Markus Sattler, Erriez
+ *
+ * Copyright (c) 2015-2020 Markus Sattler. All rights reserved.
+ * This file is part of the esp8266 core for Arduino environment.
+ *
+ * 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 "PolledTimeout.h"
+#include "ESP8266WiFiMulti.h"
+#include
+#include
+#include
+
+/**
+ * @brief Print WiFi status
+ * @details
+ * Macro DEBUG_ESP_WIFI and DEBUG_ESP_PORT must be configured
+ * @param status
+ * WiFi status
+ */
+static void printWiFiStatus(wl_status_t status)
+{
+#ifdef DEBUG_ESP_WIFI
+ IPAddress ip;
+ uint8_t *mac;
+
+ switch (status) {
+ case WL_CONNECTED:
+ ip = WiFi.localIP();
+ mac = WiFi.BSSID();
+
+ DEBUG_WIFI_MULTI("[WIFIM] Connected:\n");
+ DEBUG_WIFI_MULTI("[WIFIM] SSID: %s\n", WiFi.SSID().c_str());
+ DEBUG_WIFI_MULTI("[WIFIM] IP: %d.%d.%d.%d\n", ip[0], ip[1], ip[2], ip[3]);
+ DEBUG_WIFI_MULTI("[WIFIM] MAC: %02X:%02X:%02X:%02X:%02X:%02X\n",
+ mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+ DEBUG_WIFI_MULTI("[WIFIM] CH: %d\n", WiFi.channel());
+ DEBUG_WIFI_MULTI("[WIFIM] RSSI: %d\n", WiFi.RSSI());
+ break;
+ case WL_NO_SSID_AVAIL:
+ DEBUG_WIFI_MULTI("[WIFIM] Connecting failed AP not found.\n");
+ break;
+ case WL_CONNECT_FAILED:
+ DEBUG_WIFI_MULTI("[WIFIM] Connecting failed.\n");
+ break;
+ case WL_WRONG_PASSWORD:
+ DEBUG_WIFI_MULTI("[WIFIM] Wrong password.\n");
+ break;
+ default:
+ DEBUG_WIFI_MULTI("[WIFIM] Connecting failed (%d).\n", status);
+ break;
+ }
+#else
+ // Suppress warning unused variable
+ (void)(status);
+#endif
+}
+
+/**
+ * @brief Wait for WiFi connect status change, protected with timeout
+ * @param connectTimeoutMs
+ * WiFi connection timeout in ms
+ * @return
+ * WiFi connection status
+ */
+static wl_status_t waitWiFiConnect(uint32_t connectTimeoutMs)
+{
+ wl_status_t status = WL_CONNECT_FAILED;
+ // The final argument, intvl_ms, to esp_delay influences how frequently
+ // the scheduled recurrent functions (Schedule.h) are probed.
+ esp_delay(connectTimeoutMs,
+ [&status]() {
+ status = WiFi.status();
+ return status != WL_CONNECTED && status != WL_CONNECT_FAILED;
+ }, 0);
+
+ // Check status
+ if (status == WL_CONNECTED) {
+ // Connected, print WiFi status
+ printWiFiStatus(status);
+
+ // Return WiFi status
+ return status;
+ } else if (status == WL_CONNECT_FAILED) {
+ DEBUG_WIFI_MULTI("[WIFIM] Connect failed\n");
+ } else {
+ DEBUG_WIFI_MULTI("[WIFIM] Connect timeout\n");
+ }
+
+ // Return WiFi connect failed
+ return WL_CONNECT_FAILED;
+}
+
+/**
+ * @brief Constructor
+ */
+ESP8266WiFiMulti::ESP8266WiFiMulti() : _firstRun(true)
+{
+}
+
+/**
+ * @brief Destructor
+ */
+ESP8266WiFiMulti::~ESP8266WiFiMulti()
+{
+ // Cleanup memory
+ APlistClean();
+}
+
+/**
+ * @brief Add Access Point
+ * @param ssid
+ * WiFi SSID char array, max 32 characters + NULL character
+ * @param passphrase
+ * WiFi password char array, max 63 characters + NULL character
+ * @retval true
+ * Success
+ * @retval false
+ * Failure
+ */
+bool ESP8266WiFiMulti::addAP(const char *ssid, const char *passphrase)
+{
+ return APlistAdd(ssid, passphrase);
+}
+
+/**
+ * @brief Remove all Access Points from list
+ */
+void ESP8266WiFiMulti::cleanAPlist(void)
+{
+ APlistClean();
+}
+
+/**
+ * @brief Check if Access Point exists in list
+ * @param ssid
+ * WiFi SSID
+ * @param passphrase
+ * WiFi Password
+ * @retval true
+ * Success
+ * @retval false
+ * Failure
+ */
+bool ESP8266WiFiMulti::existsAP(const char *ssid, const char *passphrase)
+{
+ return APlistExists(ssid, passphrase);
+}
+
+/**
+ * @brief Keep WiFi connected to Access Point with strongest WiFi signal (RSSI)
+ * @param connectTimeoutMs
+ * Timeout in ms per WiFi connection (excluding fixed 5 seconds scan timeout)
+ * @return
+ * WiFi status
+ */
+wl_status_t ESP8266WiFiMulti::run(uint32_t connectTimeoutMs)
+{
+ int8_t scanResult;
+ wl_status_t status;
+
+ // Fast connect to previous WiFi on startup
+ if (_firstRun) {
+ _firstRun = false;
+
+ // Check if previous WiFi connection saved
+ if (strlen(WiFi.SSID().c_str())) {
+ DEBUG_WIFI_MULTI("[WIFIM] Connecting saved WiFi\n");
+
+ // Connect to previous saved WiFi
+ WiFi.begin();
+
+ // Wait for status change
+ status = waitWiFiConnect(connectTimeoutMs);
+ }
+ }
+
+ // Check connection state
+ status = WiFi.status();
+ if (status == WL_CONNECTED) {
+ // Already connected
+ return status;
+ }
+
+ // Start WiFi scan
+ scanResult = startScan();
+ if (scanResult < 0) {
+ // No WiFi scan results
+ return WL_NO_SSID_AVAIL;
+ }
+
+ // Try to connect to multiple WiFi's with strongest signal (RSSI)
+ return connectWiFiMulti(connectTimeoutMs);
+}
+
+/**
+ * @brief Start WiFi scan
+ * @retval >0
+ * Number of detected WiFi SSID's
+ * @retval 0
+ * No WiFi connections found
+ * @retval -2
+ * WiFi scan failed
+ */
+int8_t ESP8266WiFiMulti::startScan()
+{
+ int8_t scanResult;
+
+ DEBUG_WIFI_MULTI("[WIFIM] Start scan\n");
+
+ // Clean previous scan
+ WiFi.scanDelete();
+
+ // Remove previous WiFi SSID/password
+ WiFi.disconnect();
+
+ // Start wifi scan in async mode
+ WiFi.scanNetworks(true);
+
+ // Wait for WiFi scan change or timeout
+ // The final argument, intvl_ms, to esp_delay influences how frequently
+ // the scheduled recurrent functions (Schedule.h) are probed.
+ esp_delay(WIFI_SCAN_TIMEOUT_MS,
+ [&scanResult]() {
+ scanResult = WiFi.scanComplete();
+ return scanResult < 0;
+ }, 0);
+ // Check for scan timeout which may occur when scan does not report completion
+ if (scanResult < 0) {
+ DEBUG_WIFI_MULTI("[WIFIM] Scan timeout\n");
+ return WIFI_SCAN_FAILED;
+ }
+
+ // Print WiFi scan result
+ printWiFiScan();
+
+ // Return (positive) number of detected WiFi networks
+ return scanResult;
+}
+
+/**
+ * @brief Connect to multiple WiFi's
+ * @param connectTimeoutMs
+ * WiFi connect timeout in ms
+ * @return
+ * WiFi connection status
+ */
+wl_status_t ESP8266WiFiMulti::connectWiFiMulti(uint32_t connectTimeoutMs)
+{
+ int8_t scanResult;
+ String ssid;
+ int32_t rssi;
+ uint8_t encType;
+ uint8_t *bssid;
+ int32_t channel;
+ bool hidden;
+
+ // Get scan results
+ scanResult = WiFi.scanComplete();
+
+ // Find known WiFi networks
+ uint8_t known[_APlist.size()];
+ uint8_t numNetworks = 0;
+ for (int8_t i = 0; i < scanResult; i++) {
+ // Get network information
+ WiFi.getNetworkInfo(i, ssid, encType, rssi, bssid, channel, hidden);
+
+ // Check if the WiFi network contains an entry in AP list
+ for (auto entry : _APlist) {
+ // Check SSID
+ if (ssid == entry.ssid) {
+ // Known network
+ known[numNetworks++] = i;
+ }
+ }
+ }
+
+ // Sort WiFi networks by RSSI
+ for (int i = 0; i < numNetworks; i++) {
+ for (int j = i + 1; j < numNetworks; j++) {
+ if (WiFi.RSSI(known[j]) > WiFi.RSSI(known[i])) {
+ int8_t tmp;
+
+ // Swap indices
+ tmp = known[i];
+ known[i] = known[j];
+ known[j] = tmp;
+ }
+ }
+ }
+
+ // Print sorted indices
+ DEBUG_WIFI_MULTI("[WIFIM] Sorted indices: ");
+ for (int8_t i = 0; i < numNetworks; i++) {
+ DEBUG_WIFI_MULTI("%d ", known[i]);
+ }
+ DEBUG_WIFI_MULTI("\n");
+
+ // Create indices for AP connection failures
+ uint8_t connectSkipIndex[_APlist.size()];
+ memset(connectSkipIndex, 0, sizeof(connectSkipIndex));
+
+ // Connect to known WiFi AP's sorted by RSSI
+ for (int8_t i = 0; i < numNetworks; i++) {
+ // Get network information
+ WiFi.getNetworkInfo(known[i], ssid, encType, rssi, bssid, channel, hidden);
+
+ for (uint8_t j = 0; j < _APlist.size(); j++) {
+ auto &entry = _APlist[j];
+
+ // Check SSID
+ if (ssid == entry.ssid) {
+ DEBUG_WIFI_MULTI("[WIFIM] Connecting %s\n", ssid.c_str());
+
+ // Connect to WiFi
+ WiFi.begin(ssid, entry.passphrase, channel, bssid);
+
+ // Wait for status change
+ if (waitWiFiConnect(connectTimeoutMs) == WL_CONNECTED) {
+ return WL_CONNECTED;
+ }
+
+ // Failed to connect, skip for hidden SSID connects
+ connectSkipIndex[j] = true;
+ }
+ }
+ }
+
+ // Try to connect to hidden AP's which are not reported by WiFi scan
+ for (uint8_t i = 0; i < _APlist.size(); i++) {
+ auto &entry = _APlist[i];
+
+ if (!connectSkipIndex[i]) {
+ DEBUG_WIFI_MULTI("[WIFIM] Try hidden connect %s\n", entry.ssid);
+
+ // Connect to WiFi
+ WiFi.begin(entry.ssid, entry.passphrase);
+
+ // Wait for status change
+ if (waitWiFiConnect(connectTimeoutMs) == WL_CONNECTED) {
+ return WL_CONNECTED;
+ }
+ }
+ }
+
+ DEBUG_WIFI_MULTI("[WIFIM] Could not connect\n");
+
+ // Could not connect to any WiFi network
+ return WL_CONNECT_FAILED;
+}
+
+// ##################################################################################
+
+/**
+ * @brief Add WiFi connection to internal AP list
+ * @param ssid
+ * WiFi SSID
+ * @param passphrase
+ * WiFi Password
+ * @retval true
+ * Success
+ * @retval false
+ * Failure
+ */
+bool ESP8266WiFiMulti::APlistAdd(const char *ssid, const char *passphrase)
+{
+ WifiAPEntry newAP;
+
+ if (!ssid || (*ssid == 0x00) || (strlen(ssid) > 32)) {
+ // Fail SSID too long or missing!
+ DEBUG_WIFI_MULTI("[WIFIM][APlistAdd] No ssid or ssid too long\n");
+ return false;
+ }
+
+ // For passphrase, max is 63 ascii + null. For psk, 64hex + null.
+ if (passphrase && (strlen(passphrase) > 64)) {
+ // fail passphrase too long!
+ DEBUG_WIFI_MULTI("[WIFIM][APlistAdd] Passphrase too long\n");
+ return false;
+ }
+
+ if (APlistExists(ssid, passphrase)) {
+ DEBUG_WIFI_MULTI("[WIFIM][APlistAdd] SSID: %s already exists\n", ssid);
+ return true;
+ }
+
+ newAP.ssid = strdup(ssid);
+
+ if (!newAP.ssid) {
+ DEBUG_WIFI_MULTI("[WIFIM][APlistAdd] Fail newAP.ssid == 0\n");
+ return false;
+ }
+
+ if (passphrase) {
+ newAP.passphrase = strdup(passphrase);
+ } else {
+ newAP.passphrase = strdup("");
+ }
+
+ if (!newAP.passphrase) {
+ DEBUG_WIFI_MULTI("[WIFIM][APlistAdd] Fail newAP.passphrase == 0\n");
+ free(newAP.ssid);
+ return false;
+ }
+
+ _APlist.push_back(newAP);
+ DEBUG_WIFI_MULTI("[WIFIM][APlistAdd] Add SSID: %s\n", newAP.ssid);
+ return true;
+}
+
+/**
+ * @brief Check if AP exists in list
+ * @param ssid
+ * WiFi SSID
+ * @param passphrase
+ * WiFi Password
+ * @return
+ */
+bool ESP8266WiFiMulti::APlistExists(const char *ssid, const char *passphrase)
+{
+ if (!ssid || (*ssid == 0x00) || (strlen(ssid) > 32)) {
+ // Fail SSID too long or missing
+ DEBUG_WIFI_MULTI("[WIFIM][APlistExists] No ssid or ssid too long\n");
+ return false;
+ }
+
+ for (auto entry : _APlist) {
+ if (!strcmp(entry.ssid, ssid)) {
+ if (!passphrase) {
+ if (!strcmp(entry.passphrase, "")) {
+ return true;
+ }
+ } else {
+ if (!strcmp(entry.passphrase, passphrase)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+/**
+ * @brief Remove all AP's from list
+ */
+void ESP8266WiFiMulti::APlistClean(void)
+{
+ // Remove all entries from APlist
+ for (auto entry : _APlist) {
+ if (entry.ssid) {
+ free(entry.ssid);
+ }
+ if (entry.passphrase) {
+ free(entry.passphrase);
+ }
+ }
+
+ _APlist.clear();
+}
+
+/**
+ * @brief Print WiFi scan results
+ * @details
+ * Macro DEBUG_ESP_WIFI and DEBUG_ESP_PORT must be configured
+ */
+void ESP8266WiFiMulti::printWiFiScan()
+{
+#ifdef DEBUG_ESP_WIFI
+ String ssid;
+ int32_t rssi;
+ uint8_t encryptionType;
+ uint8_t* bssid;
+ int32_t channel;
+ bool hidden;
+ int8_t scanResult;
+
+ scanResult = WiFi.scanComplete();
+
+ DEBUG_WIFI_MULTI("[WIFIM] %d networks found:\n", scanResult);
+
+ // Print unsorted scan results
+ for (int8_t i = 0; i < scanResult; i++) {
+ bool known = false;
+
+ WiFi.getNetworkInfo(i, ssid, encryptionType, rssi, bssid, channel, hidden);
+
+ for(auto entry : _APlist) {
+ if(ssid == entry.ssid) {
+ // SSID match
+ known = true;
+ }
+ }
+
+ if (known) {
+ DEBUG_WIFI_MULTI(" --->");
+ } else {
+ DEBUG_WIFI_MULTI(" ");
+ }
+
+ DEBUG_WIFI_MULTI(" %d: [CH %02d] [%02X:%02X:%02X:%02X:%02X:%02X] %ddBm %c %s\n",
+ i,
+ channel,
+ bssid[0], bssid[1], bssid[2],
+ bssid[3], bssid[4], bssid[5],
+ rssi,
+ (encryptionType == ENC_TYPE_NONE) ? ' ' : '*',
+ ssid.c_str());
+ esp_yield();
+ }
+#endif
+}
diff --git a/lib/bms/ESP8266WiFiMulti.h b/lib/bms/ESP8266WiFiMulti.h
new file mode 100644
index 0000000..fab5829
--- /dev/null
+++ b/lib/bms/ESP8266WiFiMulti.h
@@ -0,0 +1,86 @@
+/**
+ *
+ * @file ESP8266WiFiMulti.h
+ * @date 30.09.2020
+ * @author Markus Sattler, Erriez
+ *
+ * Copyright (c) 2015-2020 Markus Sattler. All rights reserved.
+ * This file is part of the esp8266 core for Arduino environment.
+ *
+ * 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
+ *
+ */
+
+
+#ifndef WIFI_CLIENT_MULTI_H_
+#define WIFI_CLIENT_MULTI_H_
+
+#include "ESP8266WiFi.h"
+#include
+
+#ifdef DEBUG_ESP_WIFI
+#ifdef DEBUG_ESP_PORT
+#define DEBUG_WIFI_MULTI(fmt, ...) DEBUG_ESP_PORT.printf_P( (PGM_P)PSTR(fmt), ##__VA_ARGS__ )
+#endif
+#endif
+
+#ifndef DEBUG_WIFI_MULTI
+#define DEBUG_WIFI_MULTI(...) do { (void)0; } while (0)
+#endif
+
+//! Default WiFi connection timeout in ms
+#ifndef WIFI_CONNECT_TIMEOUT_MS
+#define WIFI_CONNECT_TIMEOUT_MS 5000
+#endif
+
+//! Default WiFi scan timeout in ms
+#ifndef WIFI_SCAN_TIMEOUT_MS
+#define WIFI_SCAN_TIMEOUT_MS 5000
+#endif
+
+struct WifiAPEntry {
+ char *ssid;
+ char *passphrase;
+};
+
+typedef std::vector WifiAPlist;
+
+class ESP8266WiFiMulti
+{
+public:
+ ESP8266WiFiMulti();
+ ~ESP8266WiFiMulti();
+
+ bool addAP(const char *ssid, const char *passphrase = NULL);
+ bool existsAP(const char *ssid, const char *passphrase = NULL);
+
+ wl_status_t run(uint32_t connectTimeoutMs=WIFI_CONNECT_TIMEOUT_MS);
+
+ void cleanAPlist();
+
+private:
+ WifiAPlist _APlist;
+ bool _firstRun;
+
+ bool APlistAdd(const char *ssid, const char *passphrase = NULL);
+ bool APlistExists(const char *ssid, const char *passphrase = NULL);
+ void APlistClean();
+
+ wl_status_t connectWiFiMulti(uint32_t connectTimeoutMs);
+ int8_t startScan();
+ void printWiFiScan();
+};
+
+#endif // WIFI_CLIENT_MULTI_H_
diff --git a/lib/bms/WiFiClient.cpp b/lib/bms/WiFiClient.cpp
new file mode 100644
index 0000000..6cdd5d1
--- /dev/null
+++ b/lib/bms/WiFiClient.cpp
@@ -0,0 +1,454 @@
+/*
+ WiFiClient.cpp - TCP/IP client for esp8266, mostly compatible
+ with Arduino WiFi shield library
+
+ Copyright (c) 2014 Ivan Grokhotkov. All rights reserved.
+ This file is part of the esp8266 core for Arduino environment.
+
+ 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
+*/
+
+extern "C"
+{
+ #include "wl_definitions.h"
+ #include "osapi.h"
+ #include "ets_sys.h"
+}
+
+#include "debug.h"
+#include "ESP8266WiFi.h"
+#include "WiFiClient.h"
+#include "WiFiServer.h"
+#include "lwip/opt.h"
+#include "lwip/ip.h"
+#include "lwip/tcp.h"
+#include "lwip/inet.h"
+#include "lwip/netif.h"
+#include
+#include "c_types.h"
+#include
+
+uint16_t WiFiClient::_localPort = 0;
+
+static bool defaultNoDelay = false; // false == Nagle enabled by default
+static bool defaultSync = false;
+
+bool getDefaultPrivateGlobalSyncValue ()
+{
+ return defaultSync;
+}
+
+void WiFiClient::setDefaultNoDelay (bool noDelay)
+{
+ defaultNoDelay = noDelay;
+}
+
+void WiFiClient::setDefaultSync (bool sync)
+{
+ defaultSync = sync;
+}
+
+bool WiFiClient::getDefaultNoDelay ()
+{
+ return defaultNoDelay;
+}
+
+bool WiFiClient::getDefaultSync ()
+{
+ return defaultSync;
+}
+
+template<>
+WiFiClient* SList::_s_first = 0;
+
+
+WiFiClient::WiFiClient()
+: _client(0), _owned(0)
+{
+ _timeout = 5000;
+ WiFiClient::_add(this);
+}
+
+WiFiClient::WiFiClient(ClientContext* client)
+: _client(client), _owned(0)
+{
+ _timeout = 5000;
+ _client->ref();
+ WiFiClient::_add(this);
+
+ setSync(defaultSync);
+ setNoDelay(defaultNoDelay);
+}
+
+WiFiClient::~WiFiClient()
+{
+ WiFiClient::_remove(this);
+ if (_client)
+ _client->unref();
+}
+
+std::unique_ptr WiFiClient::clone() const {
+ return std::make_unique(*this);
+}
+
+WiFiClient::WiFiClient(const WiFiClient& other)
+{
+ _client = other._client;
+ _timeout = other._timeout;
+ _localPort = other._localPort;
+ _owned = other._owned;
+ if (_client)
+ _client->ref();
+ WiFiClient::_add(this);
+}
+
+WiFiClient& WiFiClient::operator=(const WiFiClient& other)
+{
+ if (_client)
+ _client->unref();
+ _client = other._client;
+ _timeout = other._timeout;
+ _localPort = other._localPort;
+ _owned = other._owned;
+ if (_client)
+ _client->ref();
+ return *this;
+}
+
+int WiFiClient::connect(const char* host, uint16_t port)
+{
+ IPAddress remote_addr;
+ if (WiFi.hostByName(host, remote_addr, _timeout))
+ {
+ return connect(remote_addr, port);
+ }
+ return 0;
+}
+
+int WiFiClient::connect(const String& host, uint16_t port)
+{
+ return connect(host.c_str(), port);
+}
+
+int WiFiClient::connect(IPAddress ip, uint16_t port)
+{
+ if (_client) {
+ stop();
+ _client->unref();
+ _client = nullptr;
+ }
+
+ tcp_pcb* pcb = tcp_new();
+ if (!pcb)
+ return 0;
+
+ if (_localPort > 0) {
+ pcb->local_port = _localPort++;
+ }
+
+ _client = new ClientContext(pcb, nullptr, nullptr);
+ _client->ref();
+ _client->setTimeout(_timeout);
+ int res = _client->connect(ip, port);
+ if (res == 0) {
+ _client->unref();
+ _client = nullptr;
+ return 0;
+ }
+
+ setSync(defaultSync);
+ setNoDelay(defaultNoDelay);
+
+ return 1;
+}
+
+void WiFiClient::setNoDelay(bool nodelay) {
+ if (!_client)
+ return;
+ _client->setNoDelay(nodelay);
+}
+
+bool WiFiClient::getNoDelay() const {
+ if (!_client)
+ return false;
+ return _client->getNoDelay();
+}
+
+void WiFiClient::setSync(bool sync)
+{
+ if (!_client)
+ return;
+ _client->setSync(sync);
+}
+
+bool WiFiClient::getSync() const
+{
+ if (!_client)
+ return false;
+ return _client->getSync();
+}
+
+int WiFiClient::availableForWrite ()
+{
+ return _client? _client->availableForWrite(): 0;
+}
+
+size_t WiFiClient::write(uint8_t b)
+{
+ return write(&b, 1);
+}
+
+size_t WiFiClient::write(const uint8_t *buf, size_t size)
+{
+ if (!_client || !size)
+ {
+ return 0;
+ }
+ _client->setTimeout(_timeout);
+ return _client->write((const char*)buf, size);
+}
+
+size_t WiFiClient::write(Stream& stream)
+{
+ // (this method is deprecated)
+
+ if (!_client || !stream.available())
+ {
+ return 0;
+ }
+ // core up to 2.7.4 was equivalent to this
+ return stream.sendAll(this);
+}
+
+size_t WiFiClient::write_P(PGM_P buf, size_t size)
+{
+ if (!_client || !size)
+ {
+ return 0;
+ }
+ _client->setTimeout(_timeout);
+ StreamConstPtr nopeek(buf, size);
+ return nopeek.sendAll(this);
+}
+
+int WiFiClient::available()
+{
+ if (!_client)
+ return 0;
+
+ int result = _client->getSize();
+
+ if (!result) {
+ optimistic_yield(100);
+ }
+ return result;
+}
+
+int WiFiClient::read()
+{
+ if (!available())
+ return -1;
+
+ return _client->read();
+}
+
+int WiFiClient::read(uint8_t* buf, size_t size)
+{
+ return (int)_client->read((char*)buf, size);
+}
+
+int WiFiClient::read(char* buf, size_t size)
+{
+ return (int)_client->read(buf, size);
+}
+
+int WiFiClient::peek()
+{
+ if (!available())
+ return -1;
+
+ return _client->peek();
+}
+
+size_t WiFiClient::peekBytes(uint8_t *buffer, size_t length) {
+ size_t count = 0;
+
+ if(!_client) {
+ return 0;
+ }
+
+ _startMillis = millis();
+ while((available() < (int) length) && ((millis() - _startMillis) < _timeout)) {
+ yield();
+ }
+
+ if(available() < (int) length) {
+ count = available();
+ } else {
+ count = length;
+ }
+
+ return _client->peekBytes((char *)buffer, count);
+}
+
+bool WiFiClient::flush(unsigned int maxWaitMs)
+{
+ if (!_client)
+ return true;
+
+ if (maxWaitMs == 0)
+ maxWaitMs = WIFICLIENT_MAX_FLUSH_WAIT_MS;
+ return _client->wait_until_acked(maxWaitMs);
+}
+
+bool WiFiClient::stop(unsigned int maxWaitMs)
+{
+ if (!_client)
+ return true;
+
+ bool ret = flush(maxWaitMs); // virtual, may be ssl's
+ if (_client->close() != ERR_OK)
+ ret = false;
+ return ret;
+}
+
+uint8_t WiFiClient::connected()
+{
+ if (!_client || _client->state() == CLOSED)
+ return 0;
+
+ return _client->state() == ESTABLISHED || available();
+}
+
+uint8_t WiFiClient::status()
+{
+ if (!_client)
+ return CLOSED;
+ return _client->state();
+}
+
+WiFiClient::operator bool()
+{
+ return available() || connected();
+}
+
+IPAddress WiFiClient::remoteIP()
+{
+ if (!_client || !_client->getRemoteAddress())
+ return IPAddress(0U);
+
+ return _client->getRemoteAddress();
+}
+
+uint16_t WiFiClient::remotePort()
+{
+ if (!_client)
+ return 0;
+
+ return _client->getRemotePort();
+}
+
+IPAddress WiFiClient::localIP()
+{
+ if (!_client || !_client->getLocalAddress())
+ return IPAddress(0U);
+
+ return IPAddress(_client->getLocalAddress());
+}
+
+uint16_t WiFiClient::localPort()
+{
+ if (!_client)
+ return 0;
+
+ return _client->getLocalPort();
+}
+
+void WiFiClient::stopAll()
+{
+ for (WiFiClient* it = _s_first; it; it = it->_next) {
+ it->stop();
+ }
+}
+
+
+void WiFiClient::stopAllExcept(WiFiClient* except)
+{
+ // Stop all will look at the lowest-level wrapper connections only
+ while (except->_owned) {
+ except = except->_owned;
+ }
+ for (WiFiClient* it = _s_first; it; it = it->_next) {
+ WiFiClient* conn = it;
+ // Find the lowest-level owner of the current list entry
+ while (conn->_owned) {
+ conn = conn->_owned;
+ }
+ if (conn != except) {
+ conn->stop();
+ }
+ }
+}
+
+
+void WiFiClient::keepAlive (uint16_t idle_sec, uint16_t intv_sec, uint8_t count)
+{
+ _client->keepAlive(idle_sec, intv_sec, count);
+}
+
+bool WiFiClient::isKeepAliveEnabled () const
+{
+ return _client->isKeepAliveEnabled();
+}
+
+uint16_t WiFiClient::getKeepAliveIdle () const
+{
+ return _client->getKeepAliveIdle();
+}
+
+uint16_t WiFiClient::getKeepAliveInterval () const
+{
+ return _client->getKeepAliveInterval();
+}
+
+uint8_t WiFiClient::getKeepAliveCount () const
+{
+ return _client->getKeepAliveCount();
+}
+
+bool WiFiClient::hasPeekBufferAPI () const
+{
+ return true;
+}
+
+// return a pointer to available data buffer (size = peekAvailable())
+// semantic forbids any kind of read() before calling peekConsume()
+const char* WiFiClient::peekBuffer ()
+{
+ return _client? _client->peekBuffer(): nullptr;
+}
+
+// return number of byte accessible by peekBuffer()
+size_t WiFiClient::peekAvailable ()
+{
+ return _client? _client->peekAvailable(): 0;
+}
+
+// consume bytes after use (see peekBuffer)
+void WiFiClient::peekConsume (size_t consume)
+{
+ if (_client)
+ _client->peekConsume(consume);
+}
diff --git a/lib/bms/WiFiClient.h b/lib/bms/WiFiClient.h
new file mode 100644
index 0000000..5065622
--- /dev/null
+++ b/lib/bms/WiFiClient.h
@@ -0,0 +1,164 @@
+/*
+ WiFiClient.h - Library for Arduino Wifi shield.
+ Copyright (c) 2011-2014 Arduino. 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
+
+ Modified by Ivan Grokhotkov, December 2014 - esp8266 support
+*/
+
+#ifndef wificlient_h
+#define wificlient_h
+#include
+#include "Arduino.h"
+#include "Print.h"
+#include "Client.h"
+#include "IPAddress.h"
+#include "include/slist.h"
+
+#ifndef TCP_MSS
+#define TCP_MSS 1460 // lwip1.4
+#endif
+
+#define WIFICLIENT_MAX_PACKET_SIZE TCP_MSS
+#define WIFICLIENT_MAX_FLUSH_WAIT_MS 300
+
+#define TCP_DEFAULT_KEEPALIVE_IDLE_SEC 7200 // 2 hours
+#define TCP_DEFAULT_KEEPALIVE_INTERVAL_SEC 75 // 75 sec
+#define TCP_DEFAULT_KEEPALIVE_COUNT 9 // fault after 9 failures
+
+class ClientContext;
+class WiFiServer;
+
+class WiFiClient : public Client, public SList {
+protected:
+ WiFiClient(ClientContext* client);
+
+public:
+ WiFiClient();
+ virtual ~WiFiClient();
+ WiFiClient(const WiFiClient&);
+ WiFiClient& operator=(const WiFiClient&);
+
+ // b/c this is both a real class and a virtual parent of the secure client, make sure
+ // there's a safe way to copy from the pointer without 'slicing' it; i.e. only the base
+ // portion of a derived object will be copied, and the polymorphic behavior will be corrupted.
+ //
+ // this class still implements the copy and assignment though, so this is not yet enforced
+ // (but, *should* be inside the Core itself, see httpclient & server)
+ //
+ // ref.
+ // - https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-copy-virtual
+ // - https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rh-copy
+ virtual std::unique_ptr clone() const;
+
+ virtual uint8_t status();
+ virtual int connect(IPAddress ip, uint16_t port) override;
+ virtual int connect(const char *host, uint16_t port) override;
+ virtual int connect(const String& host, uint16_t port);
+ virtual size_t write(uint8_t) override;
+ virtual size_t write(const uint8_t *buf, size_t size) override;
+ virtual size_t write_P(PGM_P buf, size_t size);
+ [[ deprecated("use stream.sendHow(client...)") ]]
+ size_t write(Stream& stream);
+
+ virtual int available() override;
+ virtual int read() override;
+ virtual int read(uint8_t* buf, size_t size) override;
+ int read(char* buf, size_t size);
+
+ virtual int peek() override;
+ virtual size_t peekBytes(uint8_t *buffer, size_t length);
+ size_t peekBytes(char *buffer, size_t length) {
+ return peekBytes((uint8_t *) buffer, length);
+ }
+ virtual void flush() override { (void)flush(0); } // wait for all outgoing characters to be sent, output buffer should be empty after this call
+ virtual void stop() override { (void)stop(0); }
+ bool flush(unsigned int maxWaitMs);
+ bool stop(unsigned int maxWaitMs);
+ virtual uint8_t connected() override;
+ virtual operator bool() override;
+
+ IPAddress remoteIP();
+ uint16_t remotePort();
+ IPAddress localIP();
+ uint16_t localPort();
+
+ static void setLocalPortStart(uint16_t port) { _localPort = port; }
+
+ int availableForWrite() override;
+
+ friend class WiFiServer;
+
+ using Print::write;
+
+ static void stopAll();
+ static void stopAllExcept(WiFiClient * c);
+
+ void keepAlive (uint16_t idle_sec = TCP_DEFAULT_KEEPALIVE_IDLE_SEC, uint16_t intv_sec = TCP_DEFAULT_KEEPALIVE_INTERVAL_SEC, uint8_t count = TCP_DEFAULT_KEEPALIVE_COUNT);
+ bool isKeepAliveEnabled () const;
+ uint16_t getKeepAliveIdle () const;
+ uint16_t getKeepAliveInterval () const;
+ uint8_t getKeepAliveCount () const;
+ void disableKeepAlive () { keepAlive(0, 0, 0); }
+
+ // default NoDelay=False (Nagle=True=!NoDelay)
+ // Nagle is for shortly delaying outgoing data, to send less/bigger packets
+ // Nagle should be disabled for telnet-like/interactive streams
+ // Nagle is meaningless/ignored when Sync=true
+ static void setDefaultNoDelay (bool noDelay);
+ static bool getDefaultNoDelay ();
+ bool getNoDelay() const;
+ void setNoDelay(bool nodelay);
+
+ // default Sync=false
+ // When sync is true, all writes are automatically flushed.
+ // This is slower but also does not allocate
+ // temporary memory for sending data
+ static void setDefaultSync (bool sync);
+ static bool getDefaultSync ();
+ bool getSync() const;
+ void setSync(bool sync);
+
+ // peek buffer API is present
+ virtual bool hasPeekBufferAPI () const override;
+
+ // return number of byte accessible by peekBuffer()
+ virtual size_t peekAvailable () override;
+
+ // return a pointer to available data buffer (size = peekAvailable())
+ // semantic forbids any kind of read() before calling peekConsume()
+ virtual const char* peekBuffer () override;
+
+ // consume bytes after use (see peekBuffer)
+ virtual void peekConsume (size_t consume) override;
+
+ virtual bool outputCanTimeout () override { return connected(); }
+ virtual bool inputCanTimeout () override { return connected(); }
+
+protected:
+
+ static int8_t _s_connected(void* arg, void* tpcb, int8_t err);
+ static void _s_err(void* arg, int8_t err);
+
+ int8_t _connected(void* tpcb, int8_t err);
+ void _err(int8_t err);
+
+ ClientContext* _client;
+ WiFiClient* _owned;
+ static uint16_t _localPort;
+};
+
+#endif
diff --git a/lib/bms/desktop.ini b/lib/bms/desktop.ini
new file mode 100644
index 0000000..a7a9d99
--- /dev/null
+++ b/lib/bms/desktop.ini
@@ -0,0 +1,3 @@
+[LocalizedFileNames]
+ESP8266WiFiMulti.h=@ESP8266WiFiMulti.h,0
+ESP8266WiFiMulti.cpp=@ESP8266WiFiMulti.cpp,0
diff --git a/lib/bms/esp.c b/lib/bms/esp.c
new file mode 100644
index 0000000..c480a20
--- /dev/null
+++ b/lib/bms/esp.c
@@ -0,0 +1,178 @@
+// Implements the RMT peripheral on Espressif SoCs
+// Copyright (c) 2020 Lucian Copeland for Adafruit Industries
+
+/* Uses code from Espressif RGB LED Strip demo and drivers
+ * Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD
+ *
+ * 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.
+ */
+
+#if defined(ESP32)
+
+#include
+#include "driver/rmt.h"
+
+#if defined(ESP_IDF_VERSION)
+#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0)
+#define HAS_ESP_IDF_4
+#endif
+#endif
+
+// This code is adapted from the ESP-IDF v3.4 RMT "led_strip" example, altered
+// to work with the Arduino version of the ESP-IDF (3.2)
+
+#define WS2812_T0H_NS (400)
+#define WS2812_T0L_NS (850)
+#define WS2812_T1H_NS (800)
+#define WS2812_T1L_NS (450)
+
+#define WS2811_T0H_NS (500)
+#define WS2811_T0L_NS (2000)
+#define WS2811_T1H_NS (1200)
+#define WS2811_T1L_NS (1300)
+
+static uint32_t t0h_ticks = 0;
+static uint32_t t1h_ticks = 0;
+static uint32_t t0l_ticks = 0;
+static uint32_t t1l_ticks = 0;
+
+// Limit the number of RMT channels available for the Neopixels. Defaults to all
+// channels (8 on ESP32, 4 on ESP32-S2 and S3). Redefining this value will free
+// any channels with a higher number for other uses, such as IR send-and-recieve
+// libraries. Redefine as 1 to restrict Neopixels to only a single channel.
+#define ADAFRUIT_RMT_CHANNEL_MAX RMT_CHANNEL_MAX
+
+#define RMT_LL_HW_BASE (&RMT)
+
+bool rmt_reserved_channels[ADAFRUIT_RMT_CHANNEL_MAX];
+
+static void IRAM_ATTR ws2812_rmt_adapter(const void *src, rmt_item32_t *dest, size_t src_size,
+ size_t wanted_num, size_t *translated_size, size_t *item_num)
+{
+ if (src == NULL || dest == NULL) {
+ *translated_size = 0;
+ *item_num = 0;
+ return;
+ }
+ const rmt_item32_t bit0 = {{{ t0h_ticks, 1, t0l_ticks, 0 }}}; //Logical 0
+ const rmt_item32_t bit1 = {{{ t1h_ticks, 1, t1l_ticks, 0 }}}; //Logical 1
+ size_t size = 0;
+ size_t num = 0;
+ uint8_t *psrc = (uint8_t *)src;
+ rmt_item32_t *pdest = dest;
+ while (size < src_size && num < wanted_num) {
+ for (int i = 0; i < 8; i++) {
+ // MSB first
+ if (*psrc & (1 << (7 - i))) {
+ pdest->val = bit1.val;
+ } else {
+ pdest->val = bit0.val;
+ }
+ num++;
+ pdest++;
+ }
+ size++;
+ psrc++;
+ }
+ *translated_size = size;
+ *item_num = num;
+}
+
+void espShow(uint8_t pin, uint8_t *pixels, uint32_t numBytes, boolean is800KHz) {
+ // Reserve channel
+ rmt_channel_t channel = ADAFRUIT_RMT_CHANNEL_MAX;
+ for (size_t i = 0; i < ADAFRUIT_RMT_CHANNEL_MAX; i++) {
+ if (!rmt_reserved_channels[i]) {
+ rmt_reserved_channels[i] = true;
+ channel = i;
+ break;
+ }
+ }
+ if (channel == ADAFRUIT_RMT_CHANNEL_MAX) {
+ // Ran out of channels!
+ return;
+ }
+
+#if defined(HAS_ESP_IDF_4)
+ rmt_config_t config = RMT_DEFAULT_CONFIG_TX(pin, channel);
+ config.clk_div = 2;
+#else
+ // Match default TX config from ESP-IDF version 3.4
+ rmt_config_t config = {
+ .rmt_mode = RMT_MODE_TX,
+ .channel = channel,
+ .gpio_num = pin,
+ .clk_div = 2,
+ .mem_block_num = 1,
+ .tx_config = {
+ .carrier_freq_hz = 38000,
+ .carrier_level = RMT_CARRIER_LEVEL_HIGH,
+ .idle_level = RMT_IDLE_LEVEL_LOW,
+ .carrier_duty_percent = 33,
+ .carrier_en = false,
+ .loop_en = false,
+ .idle_output_en = true,
+ }
+ };
+#endif
+ rmt_config(&config);
+ rmt_driver_install(config.channel, 0, 0);
+
+ // Convert NS timings to ticks
+ uint32_t counter_clk_hz = 0;
+
+#if defined(HAS_ESP_IDF_4)
+ rmt_get_counter_clock(channel, &counter_clk_hz);
+#else
+ // this emulates the rmt_get_counter_clock() function from ESP-IDF 3.4
+ if (RMT_LL_HW_BASE->conf_ch[config.channel].conf1.ref_always_on == RMT_BASECLK_REF) {
+ uint32_t div_cnt = RMT_LL_HW_BASE->conf_ch[config.channel].conf0.div_cnt;
+ uint32_t div = div_cnt == 0 ? 256 : div_cnt;
+ counter_clk_hz = REF_CLK_FREQ / (div);
+ } else {
+ uint32_t div_cnt = RMT_LL_HW_BASE->conf_ch[config.channel].conf0.div_cnt;
+ uint32_t div = div_cnt == 0 ? 256 : div_cnt;
+ counter_clk_hz = APB_CLK_FREQ / (div);
+ }
+#endif
+
+ // NS to tick converter
+ float ratio = (float)counter_clk_hz / 1e9;
+
+ if (is800KHz) {
+ t0h_ticks = (uint32_t)(ratio * WS2812_T0H_NS);
+ t0l_ticks = (uint32_t)(ratio * WS2812_T0L_NS);
+ t1h_ticks = (uint32_t)(ratio * WS2812_T1H_NS);
+ t1l_ticks = (uint32_t)(ratio * WS2812_T1L_NS);
+ } else {
+ t0h_ticks = (uint32_t)(ratio * WS2811_T0H_NS);
+ t0l_ticks = (uint32_t)(ratio * WS2811_T0L_NS);
+ t1h_ticks = (uint32_t)(ratio * WS2811_T1H_NS);
+ t1l_ticks = (uint32_t)(ratio * WS2811_T1L_NS);
+ }
+
+ // Initialize automatic timing translator
+ rmt_translator_init(config.channel, ws2812_rmt_adapter);
+
+ // Write and wait to finish
+ rmt_write_sample(config.channel, pixels, (size_t)numBytes, true);
+ rmt_wait_tx_done(config.channel, pdMS_TO_TICKS(100));
+
+ // Free channel again
+ rmt_driver_uninstall(config.channel);
+ rmt_reserved_channels[channel] = false;
+
+ gpio_set_direction(pin, GPIO_MODE_OUTPUT);
+}
+
+#endif
diff --git a/lib/bms/esp8266.c b/lib/bms/esp8266.c
new file mode 100644
index 0000000..51c3f3c
--- /dev/null
+++ b/lib/bms/esp8266.c
@@ -0,0 +1,86 @@
+// This is a mash-up of the Due show() code + insights from Michael Miller's
+// ESP8266 work for the NeoPixelBus library: github.com/Makuna/NeoPixelBus
+// Needs to be a separate .c file to enforce ICACHE_RAM_ATTR execution.
+
+#if defined(ESP8266)
+
+#include
+#ifdef ESP8266
+#include
+#endif
+
+static uint32_t _getCycleCount(void) __attribute__((always_inline));
+static inline uint32_t _getCycleCount(void) {
+ uint32_t ccount;
+ __asm__ __volatile__("rsr %0,ccount":"=a" (ccount));
+ return ccount;
+}
+
+#ifdef ESP8266
+IRAM_ATTR void espShow(
+ uint8_t pin, uint8_t *pixels, uint32_t numBytes, __attribute__((unused)) boolean is800KHz) {
+#else
+void espShow(
+ uint8_t pin, uint8_t *pixels, uint32_t numBytes, boolean is800KHz) {
+#endif
+
+#define CYCLES_800_T0H (F_CPU / 2500001) // 0.4us
+#define CYCLES_800_T1H (F_CPU / 1250001) // 0.8us
+#define CYCLES_800 (F_CPU / 800001) // 1.25us per bit
+#define CYCLES_400_T0H (F_CPU / 2000000) // 0.5uS
+#define CYCLES_400_T1H (F_CPU / 833333) // 1.2us
+#define CYCLES_400 (F_CPU / 400000) // 2.5us per bit
+
+ uint8_t *p, *end, pix, mask;
+ uint32_t t, time0, time1, period, c, startTime;
+
+#ifdef ESP8266
+ uint32_t pinMask;
+ pinMask = _BV(pin);
+#endif
+
+ p = pixels;
+ end = p + numBytes;
+ pix = *p++;
+ mask = 0x80;
+ startTime = 0;
+
+#ifdef NEO_KHZ400
+ if(is800KHz) {
+#endif
+ time0 = CYCLES_800_T0H;
+ time1 = CYCLES_800_T1H;
+ period = CYCLES_800;
+#ifdef NEO_KHZ400
+ } else { // 400 KHz bitstream
+ time0 = CYCLES_400_T0H;
+ time1 = CYCLES_400_T1H;
+ period = CYCLES_400;
+ }
+#endif
+
+ for(t = time0;; t = time0) {
+ if(pix & mask) t = time1; // Bit high duration
+ while(((c = _getCycleCount()) - startTime) < period); // Wait for bit start
+#ifdef ESP8266
+ GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, pinMask); // Set high
+#else
+ gpio_set_level(pin, HIGH);
+#endif
+ startTime = c; // Save start time
+ while(((c = _getCycleCount()) - startTime) < t); // Wait high duration
+#ifdef ESP8266
+ GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, pinMask); // Set low
+#else
+ gpio_set_level(pin, LOW);
+#endif
+ if(!(mask >>= 1)) { // Next bit/byte
+ if(p >= end) break;
+ pix = *p++;
+ mask = 0x80;
+ }
+ }
+ while((_getCycleCount() - startTime) < period); // Wait for last bit
+}
+
+#endif // ESP8266
From c6e1c3097b9cb535da03a01aad09027cd519906d Mon Sep 17 00:00:00 2001
From: tonyt321 <34804458+tonyt321@users.noreply.github.com>
Date: Tue, 6 Sep 2022 20:00:18 -0400
Subject: [PATCH 06/11] Add files via upload
---
.gitignore | 5 +++++
.gitpod.Dockerfile | 5 +++++
.gitpod.yml | 11 +++++++++++
3 files changed, 21 insertions(+)
create mode 100644 .gitignore
create mode 100644 .gitpod.Dockerfile
create mode 100644 .gitpod.yml
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..89cc49c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+.pio
+.vscode/.browse.c_cpp.db*
+.vscode/c_cpp_properties.json
+.vscode/launch.json
+.vscode/ipch
diff --git a/.gitpod.Dockerfile b/.gitpod.Dockerfile
new file mode 100644
index 0000000..7fc22ba
--- /dev/null
+++ b/.gitpod.Dockerfile
@@ -0,0 +1,5 @@
+FROM gitpod/workspace-full
+
+USER gitpod
+
+RUN pip3 install -U platformio && npm install html-minifier-terser -g
\ No newline at end of file
diff --git a/.gitpod.yml b/.gitpod.yml
new file mode 100644
index 0000000..92c7b82
--- /dev/null
+++ b/.gitpod.yml
@@ -0,0 +1,11 @@
+vscode:
+ extensions:
+ - vscode.cpp
+
+tasks:
+ - before: platformio upgrade
+ - init: platformio test -e native
+ - command: platformio run -e d1_mini_lite_clone
+
+image:
+ file: .gitpod.Dockerfile
From ca0f5d6f6c421b6bf07862134f67e653e2e88858 Mon Sep 17 00:00:00 2001
From: tonyt321 <34804458+tonyt321@users.noreply.github.com>
Date: Tue, 6 Sep 2022 21:20:15 -0400
Subject: [PATCH 07/11] Update README.md
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index b4e65ec..27a331f 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,7 @@
[](https://gitpod.io/#https://github.com/tonyt321/OWIE-OLED)
todo
+- get gitpod working idk how to save from gitpod to here
- use the u8g2 library better display support
A simple ESP8266 based sketch to connect to an [owie](https://github.com/tonyt321/OWIE-OLED) device and pull the battery info from the status webpage for output to an OLED display. The current code configuration outputs the Voltage, BMS SOC & OVERRIDEN SOC.
From dd6ffe749ebe15c73995374d4fc5bb09be6c4435 Mon Sep 17 00:00:00 2001
From: tonyt321
Date: Wed, 7 Sep 2022 02:11:02 +0000
Subject: [PATCH 08/11] deleted: lib/bms/ESP8266HTTPClient.cpp
deleted: lib/bms/ESP8266HTTPClient.h deleted:
lib/bms/ESP8266WiFiMulti.cpp deleted: lib/bms/ESP8266WiFiMulti.h
deleted: lib/bms/WiFiClient.cpp deleted: lib/bms/WiFiClient.h
new file: src/EEPROM_Rotate.cpp new file: src/EEPROM_Rotate.h
modified: src/owie-watcher.ino
---
lib/bms/ESP8266HTTPClient.cpp | 1105 ---------------------------------
lib/bms/ESP8266HTTPClient.h | 277 ---------
lib/bms/ESP8266WiFiMulti.cpp | 529 ----------------
lib/bms/ESP8266WiFiMulti.h | 86 ---
lib/bms/WiFiClient.cpp | 454 --------------
lib/bms/WiFiClient.h | 164 -----
src/EEPROM_Rotate.cpp | 397 ++++++++++++
src/EEPROM_Rotate.h | 84 +++
src/owie-watcher.ino | 95 ++-
9 files changed, 528 insertions(+), 2663 deletions(-)
delete mode 100644 lib/bms/ESP8266HTTPClient.cpp
delete mode 100644 lib/bms/ESP8266HTTPClient.h
delete mode 100644 lib/bms/ESP8266WiFiMulti.cpp
delete mode 100644 lib/bms/ESP8266WiFiMulti.h
delete mode 100644 lib/bms/WiFiClient.cpp
delete mode 100644 lib/bms/WiFiClient.h
create mode 100644 src/EEPROM_Rotate.cpp
create mode 100644 src/EEPROM_Rotate.h
diff --git a/lib/bms/ESP8266HTTPClient.cpp b/lib/bms/ESP8266HTTPClient.cpp
deleted file mode 100644
index 89da711..0000000
--- a/lib/bms/ESP8266HTTPClient.cpp
+++ /dev/null
@@ -1,1105 +0,0 @@
-/**
- * ESP8266HTTPClient.cpp
- *
- * Created on: 02.11.2015
- *
- * Copyright (c) 2015 Markus Sattler. All rights reserved.
- * This file is part of the ESP8266HTTPClient for Arduino.
- *
- * 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
-#include
-
-#include "ESP8266HTTPClient.h"
-#include
-#include
-#include
-
-// per https://github.com/esp8266/Arduino/issues/8231
-// make sure HTTPClient can be utilized as a movable class member
-static_assert(std::is_default_constructible_v, "");
-static_assert(!std::is_copy_constructible_v, "");
-static_assert(std::is_move_constructible_v, "");
-static_assert(std::is_move_assignable_v, "");
-
-static const char defaultUserAgentPstr[] PROGMEM = "ESP8266HTTPClient";
-const String HTTPClient::defaultUserAgent = defaultUserAgentPstr;
-
-static int StreamReportToHttpClientReport (Stream::Report streamSendError)
-{
- switch (streamSendError)
- {
- case Stream::Report::TimedOut: return HTTPC_ERROR_READ_TIMEOUT;
- case Stream::Report::ReadError: return HTTPC_ERROR_NO_STREAM;
- case Stream::Report::WriteError: return HTTPC_ERROR_STREAM_WRITE;
- case Stream::Report::ShortOperation: return HTTPC_ERROR_STREAM_WRITE;
- case Stream::Report::Success: return 0;
- }
- return 0; // never reached, keep gcc quiet
-}
-
-void HTTPClient::clear()
-{
- _returnCode = 0;
- _size = -1;
- _headers.clear();
- _location.clear();
- _payload.reset();
-}
-
-
-/**
- * parsing the url for all needed parameters
- * @param client Client&
- * @param url String
- * @param https bool
- * @return success bool
- */
-bool HTTPClient::begin(WiFiClient &client, const String& url) {
- // check for : (http: or https:)
- int index = url.indexOf(':');
- if(index < 0) {
- DEBUG_HTTPCLIENT("[HTTP-Client][begin] failed to parse protocol\n");
- return false;
- }
-
- String protocol = url.substring(0, index);
- protocol.toLowerCase();
- if(protocol != "http" && protocol != "https") {
- DEBUG_HTTPCLIENT("[HTTP-Client][begin] unknown protocol '%s'\n", protocol.c_str());
- return false;
- }
-
- _port = (protocol == "https" ? 443 : 80);
- _client = client.clone();
-
- return beginInternal(url, protocol.c_str());
-}
-
-
-/**
- * directly supply all needed parameters
- * @param client Client&
- * @param host String
- * @param port uint16_t
- * @param uri String
- * @param https bool
- * @return success bool
- */
-bool HTTPClient::begin(WiFiClient &client, const String& host, uint16_t port, const String& uri, bool https)
-{
- // Disconnect when reusing HTTPClient to talk to a different host
- if (!_host.isEmpty() && _host != host) {
- _canReuse = false;
- disconnect(true);
- }
-
- _client = client.clone();
-
- clear();
-
- _host = host;
- _port = port;
- _uri = uri;
- _protocol = (https ? "https" : "http");
- return true;
-}
-
-
-bool HTTPClient::beginInternal(const String& __url, const char* expectedProtocol)
-{
- String url(__url);
-
- DEBUG_HTTPCLIENT("[HTTP-Client][begin] url: %s\n", url.c_str());
- clear();
-
- // check for : (http: or https:
- int index = url.indexOf(':');
- if(index < 0) {
- DEBUG_HTTPCLIENT("[HTTP-Client][begin] failed to parse protocol\n");
- return false;
- }
-
- _protocol = url.substring(0, index);
- _protocol.toLowerCase();
- url.remove(0, (index + 3)); // remove http:// or https://
-
- if (_protocol == "http") {
- // set default port for 'http'
- _port = 80;
- } else if (_protocol == "https") {
- // set default port for 'https'
- _port = 443;
- } else {
- DEBUG_HTTPCLIENT("[HTTP-Client][begin] unsupported protocol: %s\n", _protocol.c_str());
- return false;
- }
-
- index = url.indexOf('/');
- String host = url.substring(0, index);
- url.remove(0, index); // remove host part
-
- // get Authorization
- index = host.indexOf('@');
- if(index >= 0) {
- // auth info
- String auth = host.substring(0, index);
- host.remove(0, index + 1); // remove auth part including @
- _base64Authorization = base64::encode(auth, false /* doNewLines */);
- }
-
- const String oldHost = _host;
-
- // get port
- index = host.indexOf(':');
- if(index >= 0) {
- _host = host.substring(0, index); // hostname
- host.remove(0, (index + 1)); // remove hostname + :
- _port = host.toInt(); // get port
- } else {
- _host = host;
- }
-
- // Disconnect when reusing HTTPClient to talk to a different host
- if (!oldHost.isEmpty() && _host != oldHost) {
- _canReuse = false;
- disconnect(true);
- }
-
- _uri = url;
-
- if ( expectedProtocol != nullptr && _protocol != expectedProtocol) {
- DEBUG_HTTPCLIENT("[HTTP-Client][begin] unexpected protocol: %s, expected %s\n", _protocol.c_str(), expectedProtocol);
- return false;
- }
- DEBUG_HTTPCLIENT("[HTTP-Client][begin] host: %s port: %d url: %s\n", _host.c_str(), _port, _uri.c_str());
- return true;
-}
-
-
-/**
- * end
- * called after the payload is handled
- */
-void HTTPClient::end(void)
-{
- disconnect(false);
- clear();
-}
-
-/**
- * disconnect
- * close the TCP socket
- */
-void HTTPClient::disconnect(bool preserveClient)
-{
- if(connected()) {
- if(_client->available() > 0) {
- DEBUG_HTTPCLIENT("[HTTP-Client][end] still data in buffer (%d), clean up.\n", _client->available());
- while(_client->available() > 0) {
- _client->read();
- }
- }
-
- if(_reuse && _canReuse) {
- DEBUG_HTTPCLIENT("[HTTP-Client][end] tcp keep open for reuse\n");
- } else {
- DEBUG_HTTPCLIENT("[HTTP-Client][end] tcp stop\n");
- if(_client) {
- _client->stop();
- if (!preserveClient) {
- _client = nullptr;
- }
- }
- }
- } else {
- if (!preserveClient && _client) { // Also destroy _client if not connected()
- _client = nullptr;
- }
-
- DEBUG_HTTPCLIENT("[HTTP-Client][end] tcp is closed\n");
- }
-}
-
-/**
- * connected
- * @return connected status
- */
-bool HTTPClient::connected()
-{
- if(_client) {
- return (_client->connected() || (_client->available() > 0));
- }
- return false;
-}
-
-/**
- * try to reuse the connection to the server
- * keep-alive
- * @param reuse bool
- */
-void HTTPClient::setReuse(bool reuse)
-{
- _reuse = reuse;
-}
-
-/**
- * set User Agent
- * @param userAgent const char *
- */
-void HTTPClient::setUserAgent(const String& userAgent)
-{
- _userAgent = userAgent;
-}
-
-/**
- * set the Authorizatio for the http request
- * @param user const char *
- * @param password const char *
- */
-void HTTPClient::setAuthorization(const char * user, const char * password)
-{
- if(user && password) {
- String auth = user;
- auth += ':';
- auth += password;
- _base64Authorization = base64::encode(auth, false /* doNewLines */);
- }
-}
-
-/**
- * set the Authorization for the http request
- * @param auth const char * base64
- */
-void HTTPClient::setAuthorization(const char * auth)
-{
- if (auth) {
- setAuthorization(String(auth));
- }
-}
-
-/**
- * set the Authorization for the http request
- * @param auth String base64
- */
-void HTTPClient::setAuthorization(String auth)
-{
- _base64Authorization = std::move(auth);
- _base64Authorization.replace(String('\n'), emptyString);
-}
-
-/**
- * set the timeout for the TCP connection
- * @param timeout unsigned int
- */
-void HTTPClient::setTimeout(uint16_t timeout)
-{
- _tcpTimeout = timeout;
- if(connected()) {
- _client->setTimeout(timeout);
- }
-}
-
-/**
- * set the URL to a new value. Handy for following redirects.
- * @param url
- */
-bool HTTPClient::setURL(const String& url)
-{
- // if the new location is only a path then only update the URI
- if (url && url[0] == '/') {
- _uri = url;
- clear();
- return true;
- }
-
- if (!url.startsWith(_protocol + ':')) {
- DEBUG_HTTPCLIENT("[HTTP-Client][setURL] new URL not the same protocol, expected '%s', URL: '%s'\n", _protocol.c_str(), url.c_str());
- return false;
- }
- // disconnect but preserve _client (clear _canReuse so disconnect will close the connection)
- _canReuse = false;
- disconnect(true);
- return beginInternal(url, nullptr);
-}
-
-/**
- * set redirect follow mode. See `followRedirects_t` enum for available modes.
- * @param follow
- */
-void HTTPClient::setFollowRedirects(followRedirects_t follow)
-{
- _followRedirects = follow;
-}
-
-void HTTPClient::setRedirectLimit(uint16_t limit)
-{
- _redirectLimit = limit;
-}
-
-/**
- * use HTTP1.0
- * @param useHTTP10 bool
- */
-void HTTPClient::useHTTP10(bool useHTTP10)
-{
- _useHTTP10 = useHTTP10;
- _reuse = !useHTTP10;
-}
-
-/**
- * send a GET request
- * @return http code
- */
-int HTTPClient::GET()
-{
- return sendRequest("GET");
-}
-/**
- * send a DELETE request
- * @return http code
- */
-int HTTPClient::DELETE()
-{
- return sendRequest("DELETE");
-}
-
-/**
- * sends a post request to the server
- * @param payload const uint8_t *
- * @param size size_t
- * @return http code
- */
-int HTTPClient::POST(const uint8_t* payload, size_t size)
-{
- return sendRequest("POST", payload, size);
-}
-
-int HTTPClient::POST(const String& payload)
-{
- return POST((uint8_t *) payload.c_str(), payload.length());
-}
-
-/**
- * sends a put request to the server
- * @param payload uint8_t *
- * @param size size_t
- * @return http code
- */
-int HTTPClient::PUT(const uint8_t* payload, size_t size) {
- return sendRequest("PUT", payload, size);
-}
-
-int HTTPClient::PUT(const String& payload) {
- return PUT((const uint8_t *) payload.c_str(), payload.length());
-}
-
-/**
- * sends a patch request to the server
- * @param payload const uint8_t *
- * @param size size_t
- * @return http code
- */
-int HTTPClient::PATCH(const uint8_t * payload, size_t size) {
- return sendRequest("PATCH", payload, size);
-}
-
-int HTTPClient::PATCH(const String& payload) {
- return PATCH((const uint8_t *) payload.c_str(), payload.length());
-}
-
-/**
- * sendRequest
- * @param type const char * "GET", "POST", ....
- * @param payload String data for the message body
- * @return
- */
-int HTTPClient::sendRequest(const char * type, const String& payload)
-{
- return sendRequest(type, (const uint8_t *) payload.c_str(), payload.length());
-}
-
-/**
- * sendRequest
- * @param type const char * "GET", "POST", ....
- * @param payload const uint8_t * data for the message body if null not send
- * @param size size_t size for the message body if 0 not send
- * @return -1 if no info or > 0 when Content-Length is set by server
- */
-int HTTPClient::sendRequest(const char * type, const uint8_t * payload, size_t size)
-{
- int code;
- bool redirect = false;
- uint16_t redirectCount = 0;
- do {
- // wipe out any existing headers from previous request
- for(size_t i = 0; i < _headerKeysCount; i++) {
- if (_currentHeaders[i].value.length() > 0) {
- _currentHeaders[i].value.clear();
- }
- }
-
- DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] type: '%s' redirCount: %d\n", type, redirectCount);
-
- // connect to server
- if(!connect()) {
- return returnError(HTTPC_ERROR_CONNECTION_FAILED);
- }
-
- addHeader(F("Content-Length"), String(payload && size > 0 ? size : 0));
-
- // send Header
- if(!sendHeader(type)) {
- return returnError(HTTPC_ERROR_SEND_HEADER_FAILED);
- }
-
- // transfer all of it, with send-timeout
- if (size && StreamConstPtr(payload, size).sendAll(_client.get()) != size)
- return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED);
-
- // handle Server Response (Header)
- code = handleHeaderResponse();
-
- //
- // Handle redirections as stated in RFC document:
- // https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
- //
- // Implementing HTTP_CODE_FOUND as redirection with GET method,
- // to follow most of existing user agent implementations.
- //
- redirect = false;
- if (
- _followRedirects != HTTPC_DISABLE_FOLLOW_REDIRECTS &&
- redirectCount < _redirectLimit &&
- _location.length() > 0
- ) {
- switch (code) {
- // redirecting using the same method
- case HTTP_CODE_MOVED_PERMANENTLY:
- case HTTP_CODE_TEMPORARY_REDIRECT: {
- if (
- // allow to force redirections on other methods
- // (the RFC require user to accept the redirection)
- _followRedirects == HTTPC_FORCE_FOLLOW_REDIRECTS ||
- // allow GET and HEAD methods without force
- !strcmp(type, "GET") ||
- !strcmp(type, "HEAD")
- ) {
- redirectCount += 1;
- DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] following redirect (the same method): '%s' redirCount: %d\n", _location.c_str(), redirectCount);
- if (!setURL(_location)) {
- DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] failed setting URL for redirection\n");
- // no redirection
- break;
- }
- // redirect using the same request method and payload, different URL
- redirect = true;
- }
- break;
- }
- // redirecting with method dropped to GET or HEAD
- // note: it does not need `HTTPC_FORCE_FOLLOW_REDIRECTS` for any method
- case HTTP_CODE_FOUND:
- case HTTP_CODE_SEE_OTHER: {
- redirectCount += 1;
- DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] following redirect (dropped to GET/HEAD): '%s' redirCount: %d\n", _location.c_str(), redirectCount);
- if (!setURL(_location)) {
- DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] failed setting URL for redirection\n");
- // no redirection
- break;
- }
- // redirect after changing method to GET/HEAD and dropping payload
- type = "GET";
- payload = nullptr;
- size = 0;
- redirect = true;
- break;
- }
-
- default:
- break;
- }
- }
- } while (redirect);
-
- // handle Server Response (Header)
- return returnError(code);
-}
-
-/**
- * sendRequest
- * @param type const char * "GET", "POST", ....
- * @param stream Stream * data stream for the message body
- * @param size size_t size for the message body if 0 not Content-Length is send
- * @return -1 if no info or > 0 when Content-Length is set by server
- */
-int HTTPClient::sendRequest(const char * type, Stream * stream, size_t size)
-{
-
- if(!stream) {
- return returnError(HTTPC_ERROR_NO_STREAM);
- }
-
- // connect to server
- if(!connect()) {
- return returnError(HTTPC_ERROR_CONNECTION_FAILED);
- }
-
- if(size > 0) {
- addHeader(F("Content-Length"), String(size));
- }
-
- // send Header
- if(!sendHeader(type)) {
- return returnError(HTTPC_ERROR_SEND_HEADER_FAILED);
- }
-
- // transfer all of it, with timeout
- size_t transferred = stream->sendSize(_client.get(), size);
- if (transferred != size)
- {
- DEBUG_HTTPCLIENT("[HTTP-Client][sendRequest] short write, asked for %zu but got %zu failed.\n", size, transferred);
- esp_yield();
- return returnError(HTTPC_ERROR_SEND_PAYLOAD_FAILED);
- }
-
- // handle Server Response (Header)
- return returnError(handleHeaderResponse());
-}
-
-/**
- * size of message body / payload
- * @return -1 if no info or > 0 when Content-Length is set by server
- */
-int HTTPClient::getSize(void)
-{
- return _size;
-}
-
-/**
- * Location if redirect
- */
-const String& HTTPClient::getLocation(void)
-{
- return _location;
-}
-
-/**
- * returns the stream of the tcp connection
- * @return WiFiClient
- */
-WiFiClient& HTTPClient::getStream(void)
-{
- if(connected()) {
- return *_client;
- }
-
- DEBUG_HTTPCLIENT("[HTTP-Client] getStream: not connected\n");
- static WiFiClient empty;
- return empty;
-}
-
-/**
- * returns the stream of the tcp connection
- * @return WiFiClient *
- */
-WiFiClient* HTTPClient::getStreamPtr(void)
-{
- if(connected()) {
- return _client.get();
- }
-
- DEBUG_HTTPCLIENT("[HTTP-Client] getStreamPtr: not connected\n");
- return nullptr;
-}
-
-/**
- * write all message body / payload to Stream
- * @param stream Stream *
- * @return bytes written ( negative values are error codes )
- */
-int HTTPClient::writeToStream(Stream * stream)
-{
- return writeToPrint(stream);
-}
-
-/**
- * write all message body / payload to Print
- * @param print Print *
- * @return bytes written ( negative values are error codes )
- */
-int HTTPClient::writeToPrint(Print * print)
-{
-
- if(!print) {
- return returnError(HTTPC_ERROR_NO_STREAM);
- }
-
- // Only return error if not connected and no data available, because otherwise ::getString() will return an error instead of an empty
- // string when the server returned a http code 204 (no content)
- if(!connected() && _transferEncoding != HTTPC_TE_IDENTITY && _size > 0) {
- return returnError(HTTPC_ERROR_NOT_CONNECTED);
- }
-
- // get length of document (is -1 when Server sends no Content-Length header)
- int len = _size;
- int ret = 0;
-
- if(_transferEncoding == HTTPC_TE_IDENTITY) {
- // len < 0: transfer all of it, with timeout
- // len >= 0: max:len, with timeout
- ret = _client->sendSize(print, len);
-
- // do we have an error?
- if(_client->getLastSendReport() != Stream::Report::Success) {
- return returnError(StreamReportToHttpClientReport(_client->getLastSendReport()));
- }
- } else if(_transferEncoding == HTTPC_TE_CHUNKED) {
- int size = 0;
- while(1) {
- if(!connected()) {
- return returnError(HTTPC_ERROR_CONNECTION_LOST);
- }
- String chunkHeader = _client->readStringUntil('\n');
-
- if(chunkHeader.length() <= 0) {
- return returnError(HTTPC_ERROR_READ_TIMEOUT);
- }
-
- chunkHeader.trim(); // remove \r
-
- // read size of chunk
- len = (uint32_t) strtol((const char *) chunkHeader.c_str(), NULL, 16);
- size += len;
- DEBUG_HTTPCLIENT("[HTTP-Client] read chunk len: %d\n", len);
-
- // data left?
- if(len > 0) {
- // read len bytes with timeout
- int r = _client->sendSize(print, len);
- if (_client->getLastSendReport() != Stream::Report::Success)
- // not all data transferred
- return returnError(StreamReportToHttpClientReport(_client->getLastSendReport()));
- ret += r;
- } else {
-
- // if no length Header use global chunk size
- if(_size <= 0) {
- _size = size;
- }
-
- // check if we have write all data out
- if(ret != _size) {
- return returnError(HTTPC_ERROR_STREAM_WRITE);
- }
- break;
- }
-
- // read trailing \r\n at the end of the chunk
- char buf[2];
- auto trailing_seq_len = _client->readBytes((uint8_t*)buf, 2);
- if (trailing_seq_len != 2 || buf[0] != '\r' || buf[1] != '\n') {
- return returnError(HTTPC_ERROR_READ_TIMEOUT);
- }
-
- esp_yield();
- }
- } else {
- return returnError(HTTPC_ERROR_ENCODING);
- }
-
- disconnect(true);
- return ret;
-}
-
-/**
- * return all payload as String (may need lot of ram or trigger out of memory!)
- * @return String
- */
-const String& HTTPClient::getString(void)
-{
- if (_payload) {
- return *_payload;
- }
-
- _payload.reset(new StreamString());
-
- if(_size > 0) {
- // try to reserve needed memory
- if(!_payload->reserve((_size + 1))) {
- DEBUG_HTTPCLIENT("[HTTP-Client][getString] not enough memory to reserve a string! need: %d\n", (_size + 1));
- return *_payload;
- }
- }
-
- writeToStream(_payload.get());
- return *_payload;
-}
-
-/**
- * converts error code to String
- * @param error int
- * @return String
- */
-String HTTPClient::errorToString(int error)
-{
- switch(error) {
- case HTTPC_ERROR_CONNECTION_FAILED:
- return F("connection failed");
- case HTTPC_ERROR_SEND_HEADER_FAILED:
- return F("send header failed");
- case HTTPC_ERROR_SEND_PAYLOAD_FAILED:
- return F("send payload failed");
- case HTTPC_ERROR_NOT_CONNECTED:
- return F("not connected");
- case HTTPC_ERROR_CONNECTION_LOST:
- return F("connection lost");
- case HTTPC_ERROR_NO_STREAM:
- return F("no stream");
- case HTTPC_ERROR_NO_HTTP_SERVER:
- return F("no HTTP server");
- case HTTPC_ERROR_TOO_LESS_RAM:
- return F("not enough ram");
- case HTTPC_ERROR_ENCODING:
- return F("Transfer-Encoding not supported");
- case HTTPC_ERROR_STREAM_WRITE:
- return F("Stream write error");
- case HTTPC_ERROR_READ_TIMEOUT:
- return F("read Timeout");
- default:
- return String();
- }
-}
-
-/**
- * adds Header to the request
- * @param name
- * @param value
- * @param first
- */
-void HTTPClient::addHeader(const String& name, const String& value, bool first, bool replace)
-{
- // not allow set of Header handled by code
- if (!name.equalsIgnoreCase(F("Connection")) &&
- !name.equalsIgnoreCase(F("User-Agent")) &&
- !name.equalsIgnoreCase(F("Host")) &&
- !(name.equalsIgnoreCase(F("Authorization")) && _base64Authorization.length())) {
-
- String headerLine;
- headerLine.reserve(name.length() + value.length() + 4);
- headerLine += name;
- headerLine += ": ";
-
- if (replace) {
- int headerStart = _headers.indexOf(headerLine);
- if (headerStart != -1) {
- int headerEnd = _headers.indexOf('\n', headerStart);
- _headers = _headers.substring(0, headerStart) + _headers.substring(headerEnd + 1);
- }
- }
-
- headerLine += value;
- headerLine += "\r\n";
- if (first) {
- _headers = headerLine + _headers;
- } else {
- _headers += headerLine;
- }
- }
-}
-
-void HTTPClient::collectHeaders(const char* headerKeys[], const size_t headerKeysCount)
-{
- _headerKeysCount = headerKeysCount;
- _currentHeaders = std::make_unique(_headerKeysCount);
- for(size_t i = 0; i < _headerKeysCount; i++) {
- _currentHeaders[i].key = headerKeys[i];
- }
-}
-
-String HTTPClient::header(const char* name)
-{
- for(size_t i = 0; i < _headerKeysCount; ++i) {
- if(_currentHeaders[i].key == name) {
- return _currentHeaders[i].value;
- }
- }
- return String();
-}
-
-String HTTPClient::header(size_t i)
-{
- if(i < _headerKeysCount) {
- return _currentHeaders[i].value;
- }
- return String();
-}
-
-String HTTPClient::headerName(size_t i)
-{
- if(i < _headerKeysCount) {
- return _currentHeaders[i].key;
- }
- return String();
-}
-
-int HTTPClient::headers()
-{
- return _headerKeysCount;
-}
-
-bool HTTPClient::hasHeader(const char* name)
-{
- for(size_t i = 0; i < _headerKeysCount; ++i) {
- if((_currentHeaders[i].key == name) && (_currentHeaders[i].value.length() > 0)) {
- return true;
- }
- }
- return false;
-}
-
-/**
- * init TCP connection and handle ssl verify if needed
- * @return true if connection is ok
- */
-bool HTTPClient::connect(void)
-{
- if(_reuse && _canReuse && connected()) {
- DEBUG_HTTPCLIENT("[HTTP-Client] connect: already connected, reusing connection\n");
-
-#if defined(NO_GLOBAL_INSTANCES) || defined(NO_GLOBAL_STREAMDEV)
- StreamNull devnull;
-#endif
- _client->sendAvailable(devnull); // clear _client's output (all of it, no timeout)
- return true;
- }
-
- if(!_client) {
- DEBUG_HTTPCLIENT("[HTTP-Client] connect: HTTPClient::begin was not called or returned error\n");
- return false;
- }
-
- _client->setTimeout(_tcpTimeout);
-
- if(!_client->connect(_host.c_str(), _port)) {
- DEBUG_HTTPCLIENT("[HTTP-Client] failed connect to %s:%u\n", _host.c_str(), _port);
- return false;
- }
-
- DEBUG_HTTPCLIENT("[HTTP-Client] connected to %s:%u\n", _host.c_str(), _port);
-
-#ifdef ESP8266
- _client->setNoDelay(true);
-#endif
- return connected();
-}
-
-/**
- * sends HTTP request header
- * @param type (GET, POST, ...)
- * @return status
- */
-bool HTTPClient::sendHeader(const char * type)
-{
- if(!connected()) {
- return false;
- }
-
- String header;
- // 128: Arbitrarily chosen to have enough buffer space for avoiding internal reallocations
- header.reserve(_headers.length() + _uri.length() +
- _base64Authorization.length() + _host.length() + _userAgent.length() + 128);
- header += type;
- header += ' ';
- if (_uri.length()) {
- header += _uri;
- } else {
- header += '/';
- }
- header += F(" HTTP/1.");
-
- if(_useHTTP10) {
- header += '0';
- } else {
- header += '1';
- }
-
- header += F("\r\nHost: ");
- header += _host;
- if (_port != 80 && _port != 443)
- {
- header += ':';
- header += String(_port);
- }
- if (_userAgent.length()) {
- header += F("\r\nUser-Agent: ");
- header += _userAgent;
- }
-
- if (!_useHTTP10) {
- header += F("\r\nAccept-Encoding: identity;q=1,chunked;q=0.1,*;q=0");
- }
-
- if (_base64Authorization.length()) {
- header += F("\r\nAuthorization: Basic ");
- header += _base64Authorization;
- }
-
- header += F("\r\nConnection: ");
- header += _reuse ? F("keep-alive") : F("close");
- header += "\r\n";
-
- header += _headers;
- header += "\r\n";
-
- DEBUG_HTTPCLIENT("[HTTP-Client] sending request header\n-----\n%s-----\n", header.c_str());
-
- // transfer all of it, with timeout
- return StreamConstPtr(header).sendAll(_client.get()) == header.length();
-}
-
-/**
- * reads the response from the server
- * @return int http code
- */
-int HTTPClient::handleHeaderResponse()
-{
-
- if(!connected()) {
- return HTTPC_ERROR_NOT_CONNECTED;
- }
-
- clear();
-
- _canReuse = _reuse;
-
- String transferEncoding;
-
- _transferEncoding = HTTPC_TE_IDENTITY;
- unsigned long lastDataTime = millis();
-
- while(connected()) {
- size_t len = _client->available();
- if(len > 0) {
- int headerSeparator = -1;
- String headerLine = _client->readStringUntil('\n');
-
- lastDataTime = millis();
-
- DEBUG_HTTPCLIENT("[HTTP-Client][handleHeaderResponse] RX: '%s'\n", headerLine.c_str());
-
- if (headerLine.startsWith(F("HTTP/1."))) {
-
- constexpr auto httpVersionIdx = sizeof "HTTP/1." - 1;
- _canReuse = _canReuse && (headerLine[httpVersionIdx] != '0');
- _returnCode = headerLine.substring(httpVersionIdx + 2, headerLine.indexOf(' ', httpVersionIdx + 2)).toInt();
- _canReuse = _canReuse && (_returnCode > 0) && (_returnCode < 500);
-
- } else if ((headerSeparator = headerLine.indexOf(':')) > 0) {
- String headerName = headerLine.substring(0, headerSeparator);
- String headerValue = headerLine.substring(headerSeparator + 1);
- headerValue.trim();
-
- if(headerName.equalsIgnoreCase(F("Content-Length"))) {
- _size = headerValue.toInt();
- }
-
- if(_canReuse && headerName.equalsIgnoreCase(F("Connection"))) {
- if (headerValue.indexOf(F("close")) >= 0 &&
- headerValue.indexOf(F("keep-alive")) < 0) {
- _canReuse = false;
- }
- }
-
- if(headerName.equalsIgnoreCase(F("Transfer-Encoding"))) {
- transferEncoding = headerValue;
- }
-
- if(headerName.equalsIgnoreCase(F("Location"))) {
- _location = headerValue;
- }
-
- for (size_t i = 0; i < _headerKeysCount; i++) {
- if (_currentHeaders[i].key.equalsIgnoreCase(headerName)) {
- if (!_currentHeaders[i].value.isEmpty()) {
- // Existing value, append this one with a comma
- _currentHeaders[i].value += ',';
- _currentHeaders[i].value += headerValue;
- } else {
- _currentHeaders[i].value = headerValue;
- }
- break; // We found a match, stop looking
- }
- }
- continue;
- }
-
- headerLine.trim(); // remove \r
-
- if (headerLine.isEmpty()) {
- DEBUG_HTTPCLIENT("[HTTP-Client][handleHeaderResponse] code: %d\n", _returnCode);
-
- if(_size > 0) {
- DEBUG_HTTPCLIENT("[HTTP-Client][handleHeaderResponse] size: %d\n", _size);
- }
-
- if(transferEncoding.length() > 0) {
- DEBUG_HTTPCLIENT("[HTTP-Client][handleHeaderResponse] Transfer-Encoding: %s\n", transferEncoding.c_str());
- if(transferEncoding.equalsIgnoreCase(F("chunked"))) {
- _transferEncoding = HTTPC_TE_CHUNKED;
- } else {
- _returnCode = HTTPC_ERROR_ENCODING;
- return _returnCode;
- }
- } else {
- _transferEncoding = HTTPC_TE_IDENTITY;
- }
-
- if(_returnCode <= 0) {
- DEBUG_HTTPCLIENT("[HTTP-Client][handleHeaderResponse] Remote host is not an HTTP Server!");
- _returnCode = HTTPC_ERROR_NO_HTTP_SERVER;
- }
- return _returnCode;
- }
-
- } else {
- if((millis() - lastDataTime) > _tcpTimeout) {
- return HTTPC_ERROR_READ_TIMEOUT;
- }
- esp_yield();
- }
- }
-
- return HTTPC_ERROR_CONNECTION_LOST;
-}
-
-/**
- * called to handle error return, may disconnect the connection if still exists
- * @param error
- * @return error
- */
-int HTTPClient::returnError(int error)
-{
- if(error < 0) {
- DEBUG_HTTPCLIENT("[HTTP-Client][returnError] error(%d): %s\n", error, errorToString(error).c_str());
- if(connected()) {
- DEBUG_HTTPCLIENT("[HTTP-Client][returnError] tcp stop\n");
- _client->stop();
- }
- }
- return error;
-}
diff --git a/lib/bms/ESP8266HTTPClient.h b/lib/bms/ESP8266HTTPClient.h
deleted file mode 100644
index 9e3aef9..0000000
--- a/lib/bms/ESP8266HTTPClient.h
+++ /dev/null
@@ -1,277 +0,0 @@
-/**
- * ESP8266HTTPClient.h
- *
- * Created on: 02.11.2015
- *
- * Copyright (c) 2015 Markus Sattler. All rights reserved.
- * This file is part of the ESP8266HTTPClient for Arduino.
- *
- * 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
- *
- * Modified by Jeroen Döll, June 2018
- */
-
-#ifndef ESP8266HTTPClient_H_
-#define ESP8266HTTPClient_H_
-
-#include
-#include
-#include
-
-#include
-
-#ifdef DEBUG_ESP_HTTP_CLIENT
-#ifdef DEBUG_ESP_PORT
-#define DEBUG_HTTPCLIENT(fmt, ...) DEBUG_ESP_PORT.printf_P( (PGM_P)PSTR(fmt), ## __VA_ARGS__ )
-#endif
-#endif
-
-#ifndef DEBUG_HTTPCLIENT
-#define DEBUG_HTTPCLIENT(...) do { (void)0; } while (0)
-#endif
-
-#define HTTPCLIENT_DEFAULT_TCP_TIMEOUT (5000)
-
-/// HTTP client errors
-#define HTTPC_ERROR_CONNECTION_FAILED (-1)
-#define HTTPC_ERROR_SEND_HEADER_FAILED (-2)
-#define HTTPC_ERROR_SEND_PAYLOAD_FAILED (-3)
-#define HTTPC_ERROR_NOT_CONNECTED (-4)
-#define HTTPC_ERROR_CONNECTION_LOST (-5)
-#define HTTPC_ERROR_NO_STREAM (-6)
-#define HTTPC_ERROR_NO_HTTP_SERVER (-7)
-#define HTTPC_ERROR_TOO_LESS_RAM (-8)
-#define HTTPC_ERROR_ENCODING (-9)
-#define HTTPC_ERROR_STREAM_WRITE (-10)
-#define HTTPC_ERROR_READ_TIMEOUT (-11)
-
-constexpr int HTTPC_ERROR_CONNECTION_REFUSED __attribute__((deprecated)) = HTTPC_ERROR_CONNECTION_FAILED;
-
-/// size for the stream handling
-#define HTTP_TCP_BUFFER_SIZE (1460)
-
-/// HTTP codes see RFC7231
-typedef enum {
- HTTP_CODE_CONTINUE = 100,
- HTTP_CODE_SWITCHING_PROTOCOLS = 101,
- HTTP_CODE_PROCESSING = 102,
- HTTP_CODE_OK = 200,
- HTTP_CODE_CREATED = 201,
- HTTP_CODE_ACCEPTED = 202,
- HTTP_CODE_NON_AUTHORITATIVE_INFORMATION = 203,
- HTTP_CODE_NO_CONTENT = 204,
- HTTP_CODE_RESET_CONTENT = 205,
- HTTP_CODE_PARTIAL_CONTENT = 206,
- HTTP_CODE_MULTI_STATUS = 207,
- HTTP_CODE_ALREADY_REPORTED = 208,
- HTTP_CODE_IM_USED = 226,
- HTTP_CODE_MULTIPLE_CHOICES = 300,
- HTTP_CODE_MOVED_PERMANENTLY = 301,
- HTTP_CODE_FOUND = 302,
- HTTP_CODE_SEE_OTHER = 303,
- HTTP_CODE_NOT_MODIFIED = 304,
- HTTP_CODE_USE_PROXY = 305,
- HTTP_CODE_TEMPORARY_REDIRECT = 307,
- HTTP_CODE_PERMANENT_REDIRECT = 308,
- HTTP_CODE_BAD_REQUEST = 400,
- HTTP_CODE_UNAUTHORIZED = 401,
- HTTP_CODE_PAYMENT_REQUIRED = 402,
- HTTP_CODE_FORBIDDEN = 403,
- HTTP_CODE_NOT_FOUND = 404,
- HTTP_CODE_METHOD_NOT_ALLOWED = 405,
- HTTP_CODE_NOT_ACCEPTABLE = 406,
- HTTP_CODE_PROXY_AUTHENTICATION_REQUIRED = 407,
- HTTP_CODE_REQUEST_TIMEOUT = 408,
- HTTP_CODE_CONFLICT = 409,
- HTTP_CODE_GONE = 410,
- HTTP_CODE_LENGTH_REQUIRED = 411,
- HTTP_CODE_PRECONDITION_FAILED = 412,
- HTTP_CODE_PAYLOAD_TOO_LARGE = 413,
- HTTP_CODE_URI_TOO_LONG = 414,
- HTTP_CODE_UNSUPPORTED_MEDIA_TYPE = 415,
- HTTP_CODE_RANGE_NOT_SATISFIABLE = 416,
- HTTP_CODE_EXPECTATION_FAILED = 417,
- HTTP_CODE_MISDIRECTED_REQUEST = 421,
- HTTP_CODE_UNPROCESSABLE_ENTITY = 422,
- HTTP_CODE_LOCKED = 423,
- HTTP_CODE_FAILED_DEPENDENCY = 424,
- HTTP_CODE_UPGRADE_REQUIRED = 426,
- HTTP_CODE_PRECONDITION_REQUIRED = 428,
- HTTP_CODE_TOO_MANY_REQUESTS = 429,
- HTTP_CODE_REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
- HTTP_CODE_INTERNAL_SERVER_ERROR = 500,
- HTTP_CODE_NOT_IMPLEMENTED = 501,
- HTTP_CODE_BAD_GATEWAY = 502,
- HTTP_CODE_SERVICE_UNAVAILABLE = 503,
- HTTP_CODE_GATEWAY_TIMEOUT = 504,
- HTTP_CODE_HTTP_VERSION_NOT_SUPPORTED = 505,
- HTTP_CODE_VARIANT_ALSO_NEGOTIATES = 506,
- HTTP_CODE_INSUFFICIENT_STORAGE = 507,
- HTTP_CODE_LOOP_DETECTED = 508,
- HTTP_CODE_NOT_EXTENDED = 510,
- HTTP_CODE_NETWORK_AUTHENTICATION_REQUIRED = 511
-} t_http_codes;
-
-typedef enum {
- HTTPC_TE_IDENTITY,
- HTTPC_TE_CHUNKED
-} transferEncoding_t;
-
-/**
- * redirection follow mode.
- * + `HTTPC_DISABLE_FOLLOW_REDIRECTS` - no redirection will be followed.
- * + `HTTPC_STRICT_FOLLOW_REDIRECTS` - strict RFC2616, only requests using
- * GET or HEAD methods will be redirected (using the same method),
- * since the RFC requires end-user confirmation in other cases.
- * + `HTTPC_FORCE_FOLLOW_REDIRECTS` - all redirections will be followed,
- * regardless of a used method. New request will use the same method,
- * and they will include the same body data and the same headers.
- * In the sense of the RFC, it's just like every redirection is confirmed.
- */
-typedef enum {
- HTTPC_DISABLE_FOLLOW_REDIRECTS,
- HTTPC_STRICT_FOLLOW_REDIRECTS,
- HTTPC_FORCE_FOLLOW_REDIRECTS
-} followRedirects_t;
-
-class TransportTraits;
-typedef std::unique_ptr TransportTraitsPtr;
-
-class HTTPClient
-{
-public:
- HTTPClient() = default;
- ~HTTPClient() = default;
- HTTPClient(HTTPClient&&) = default;
- HTTPClient& operator=(HTTPClient&&) = default;
-
- // Note that WiFiClient's underlying connection *will* be captured
- bool begin(WiFiClient &client, const String& url);
- bool begin(WiFiClient &client, const String& host, uint16_t port, const String& uri = "/", bool https = false);
-
- // old API is now explicitly forbidden
- bool begin(String url) __attribute__ ((error("obsolete API, use ::begin(WiFiClient, url)")));
- bool begin(String host, uint16_t port, String uri = "/") __attribute__ ((error("obsolete API, use ::begin(WiFiClient, host, port, uri)")));
- bool begin(String url, const uint8_t httpsFingerprint[20]) __attribute__ ((error("obsolete API, use ::begin(WiFiClientSecure, ...)")));
- bool begin(String host, uint16_t port, String uri, const uint8_t httpsFingerprint[20]) __attribute__ ((error("obsolete API, use ::begin(WiFiClientSecure, ...)")));
- bool begin(String host, uint16_t port, String uri, bool https, String httpsFingerprint) __attribute__ ((error("obsolete API, use ::begin(WiFiClientSecure, ...)")));
-
- void end(void);
-
- bool connected(void);
-
- void setReuse(bool reuse); /// keep-alive
- void setUserAgent(const String& userAgent);
- void setAuthorization(const char * user, const char * password);
- void setAuthorization(const char * auth);
- void setAuthorization(String auth);
- void setTimeout(uint16_t timeout);
-
- // Redirections
- void setFollowRedirects(followRedirects_t follow);
- void setRedirectLimit(uint16_t limit); // max redirects to follow for a single request
-
- bool setURL(const String& url); // handy for handling redirects
- void useHTTP10(bool usehttp10 = true);
-
- /// request handling
- int GET();
- int DELETE();
- int POST(const uint8_t* payload, size_t size);
- int POST(const String& payload);
- int PUT(const uint8_t* payload, size_t size);
- int PUT(const String& payload);
- int PATCH(const uint8_t* payload, size_t size);
- int PATCH(const String& payload);
- int sendRequest(const char* type, const String& payload);
- int sendRequest(const char* type, const uint8_t* payload = NULL, size_t size = 0);
- int sendRequest(const char* type, Stream * stream, size_t size = 0);
-
- void addHeader(const String& name, const String& value, bool first = false, bool replace = true);
-
- /// Response handling
- void collectHeaders(const char* headerKeys[], const size_t headerKeysCount);
- String header(const char* name); // get request header value by name
- String header(size_t i); // get request header value by number
- String headerName(size_t i); // get request header name by number
- int headers(); // get header count
- bool hasHeader(const char* name); // check if header exists
-
-
- int getSize(void);
- const String& getLocation(void); // Location header from redirect if 3XX
-
- WiFiClient& getStream(void);
- WiFiClient* getStreamPtr(void);
- int writeToPrint(Print* print);
- int writeToStream(Stream* stream);
- const String& getString(void);
- static String errorToString(int error);
-
-protected:
- struct RequestArgument {
- String key;
- String value;
- };
-
- bool beginInternal(const String& url, const char* expectedProtocol);
- void disconnect(bool preserveClient = false);
- void clear();
- int returnError(int error);
- bool connect(void);
- bool sendHeader(const char * type);
- int handleHeaderResponse();
- int writeToStreamDataBlock(Stream * stream, int len);
-
- // The common pattern to use the class is to
- // {
- // WiFiClient socket;
- // HTTPClient http;
- // http.begin(socket, "http://blahblah");
- // }
- // Make sure it's not possible to break things in an opposite direction
-
- std::unique_ptr _client;
-
- /// request handling
- String _host;
- uint16_t _port = 0;
- bool _reuse = true;
- uint16_t _tcpTimeout = HTTPCLIENT_DEFAULT_TCP_TIMEOUT;
- bool _useHTTP10 = false;
-
- String _uri;
- String _protocol;
- String _headers;
- String _base64Authorization;
-
- static const String defaultUserAgent;
- String _userAgent = defaultUserAgent;
-
- /// Response handling
- std::unique_ptr _currentHeaders;
- size_t _headerKeysCount = 0;
-
- int _returnCode = 0;
- int _size = -1;
- bool _canReuse = false;
- followRedirects_t _followRedirects = HTTPC_DISABLE_FOLLOW_REDIRECTS;
- uint16_t _redirectLimit = 10;
- String _location;
- transferEncoding_t _transferEncoding = HTTPC_TE_IDENTITY;
- std::unique_ptr _payload;
-};
-
-#endif /* ESP8266HTTPClient_H_ */
diff --git a/lib/bms/ESP8266WiFiMulti.cpp b/lib/bms/ESP8266WiFiMulti.cpp
deleted file mode 100644
index dada704..0000000
--- a/lib/bms/ESP8266WiFiMulti.cpp
+++ /dev/null
@@ -1,529 +0,0 @@
-/**
- *
- * @file ESP8266WiFiMulti.cpp
- * @date 30.09.2020
- * @author Markus Sattler, Erriez
- *
- * Copyright (c) 2015-2020 Markus Sattler. All rights reserved.
- * This file is part of the esp8266 core for Arduino environment.
- *
- * 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 "PolledTimeout.h"
-#include "ESP8266WiFiMulti.h"
-#include
-#include
-#include
-
-/**
- * @brief Print WiFi status
- * @details
- * Macro DEBUG_ESP_WIFI and DEBUG_ESP_PORT must be configured
- * @param status
- * WiFi status
- */
-static void printWiFiStatus(wl_status_t status)
-{
-#ifdef DEBUG_ESP_WIFI
- IPAddress ip;
- uint8_t *mac;
-
- switch (status) {
- case WL_CONNECTED:
- ip = WiFi.localIP();
- mac = WiFi.BSSID();
-
- DEBUG_WIFI_MULTI("[WIFIM] Connected:\n");
- DEBUG_WIFI_MULTI("[WIFIM] SSID: %s\n", WiFi.SSID().c_str());
- DEBUG_WIFI_MULTI("[WIFIM] IP: %d.%d.%d.%d\n", ip[0], ip[1], ip[2], ip[3]);
- DEBUG_WIFI_MULTI("[WIFIM] MAC: %02X:%02X:%02X:%02X:%02X:%02X\n",
- mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
- DEBUG_WIFI_MULTI("[WIFIM] CH: %d\n", WiFi.channel());
- DEBUG_WIFI_MULTI("[WIFIM] RSSI: %d\n", WiFi.RSSI());
- break;
- case WL_NO_SSID_AVAIL:
- DEBUG_WIFI_MULTI("[WIFIM] Connecting failed AP not found.\n");
- break;
- case WL_CONNECT_FAILED:
- DEBUG_WIFI_MULTI("[WIFIM] Connecting failed.\n");
- break;
- case WL_WRONG_PASSWORD:
- DEBUG_WIFI_MULTI("[WIFIM] Wrong password.\n");
- break;
- default:
- DEBUG_WIFI_MULTI("[WIFIM] Connecting failed (%d).\n", status);
- break;
- }
-#else
- // Suppress warning unused variable
- (void)(status);
-#endif
-}
-
-/**
- * @brief Wait for WiFi connect status change, protected with timeout
- * @param connectTimeoutMs
- * WiFi connection timeout in ms
- * @return
- * WiFi connection status
- */
-static wl_status_t waitWiFiConnect(uint32_t connectTimeoutMs)
-{
- wl_status_t status = WL_CONNECT_FAILED;
- // The final argument, intvl_ms, to esp_delay influences how frequently
- // the scheduled recurrent functions (Schedule.h) are probed.
- esp_delay(connectTimeoutMs,
- [&status]() {
- status = WiFi.status();
- return status != WL_CONNECTED && status != WL_CONNECT_FAILED;
- }, 0);
-
- // Check status
- if (status == WL_CONNECTED) {
- // Connected, print WiFi status
- printWiFiStatus(status);
-
- // Return WiFi status
- return status;
- } else if (status == WL_CONNECT_FAILED) {
- DEBUG_WIFI_MULTI("[WIFIM] Connect failed\n");
- } else {
- DEBUG_WIFI_MULTI("[WIFIM] Connect timeout\n");
- }
-
- // Return WiFi connect failed
- return WL_CONNECT_FAILED;
-}
-
-/**
- * @brief Constructor
- */
-ESP8266WiFiMulti::ESP8266WiFiMulti() : _firstRun(true)
-{
-}
-
-/**
- * @brief Destructor
- */
-ESP8266WiFiMulti::~ESP8266WiFiMulti()
-{
- // Cleanup memory
- APlistClean();
-}
-
-/**
- * @brief Add Access Point
- * @param ssid
- * WiFi SSID char array, max 32 characters + NULL character
- * @param passphrase
- * WiFi password char array, max 63 characters + NULL character
- * @retval true
- * Success
- * @retval false
- * Failure
- */
-bool ESP8266WiFiMulti::addAP(const char *ssid, const char *passphrase)
-{
- return APlistAdd(ssid, passphrase);
-}
-
-/**
- * @brief Remove all Access Points from list
- */
-void ESP8266WiFiMulti::cleanAPlist(void)
-{
- APlistClean();
-}
-
-/**
- * @brief Check if Access Point exists in list
- * @param ssid
- * WiFi SSID
- * @param passphrase
- * WiFi Password
- * @retval true
- * Success
- * @retval false
- * Failure
- */
-bool ESP8266WiFiMulti::existsAP(const char *ssid, const char *passphrase)
-{
- return APlistExists(ssid, passphrase);
-}
-
-/**
- * @brief Keep WiFi connected to Access Point with strongest WiFi signal (RSSI)
- * @param connectTimeoutMs
- * Timeout in ms per WiFi connection (excluding fixed 5 seconds scan timeout)
- * @return
- * WiFi status
- */
-wl_status_t ESP8266WiFiMulti::run(uint32_t connectTimeoutMs)
-{
- int8_t scanResult;
- wl_status_t status;
-
- // Fast connect to previous WiFi on startup
- if (_firstRun) {
- _firstRun = false;
-
- // Check if previous WiFi connection saved
- if (strlen(WiFi.SSID().c_str())) {
- DEBUG_WIFI_MULTI("[WIFIM] Connecting saved WiFi\n");
-
- // Connect to previous saved WiFi
- WiFi.begin();
-
- // Wait for status change
- status = waitWiFiConnect(connectTimeoutMs);
- }
- }
-
- // Check connection state
- status = WiFi.status();
- if (status == WL_CONNECTED) {
- // Already connected
- return status;
- }
-
- // Start WiFi scan
- scanResult = startScan();
- if (scanResult < 0) {
- // No WiFi scan results
- return WL_NO_SSID_AVAIL;
- }
-
- // Try to connect to multiple WiFi's with strongest signal (RSSI)
- return connectWiFiMulti(connectTimeoutMs);
-}
-
-/**
- * @brief Start WiFi scan
- * @retval >0
- * Number of detected WiFi SSID's
- * @retval 0
- * No WiFi connections found
- * @retval -2
- * WiFi scan failed
- */
-int8_t ESP8266WiFiMulti::startScan()
-{
- int8_t scanResult;
-
- DEBUG_WIFI_MULTI("[WIFIM] Start scan\n");
-
- // Clean previous scan
- WiFi.scanDelete();
-
- // Remove previous WiFi SSID/password
- WiFi.disconnect();
-
- // Start wifi scan in async mode
- WiFi.scanNetworks(true);
-
- // Wait for WiFi scan change or timeout
- // The final argument, intvl_ms, to esp_delay influences how frequently
- // the scheduled recurrent functions (Schedule.h) are probed.
- esp_delay(WIFI_SCAN_TIMEOUT_MS,
- [&scanResult]() {
- scanResult = WiFi.scanComplete();
- return scanResult < 0;
- }, 0);
- // Check for scan timeout which may occur when scan does not report completion
- if (scanResult < 0) {
- DEBUG_WIFI_MULTI("[WIFIM] Scan timeout\n");
- return WIFI_SCAN_FAILED;
- }
-
- // Print WiFi scan result
- printWiFiScan();
-
- // Return (positive) number of detected WiFi networks
- return scanResult;
-}
-
-/**
- * @brief Connect to multiple WiFi's
- * @param connectTimeoutMs
- * WiFi connect timeout in ms
- * @return
- * WiFi connection status
- */
-wl_status_t ESP8266WiFiMulti::connectWiFiMulti(uint32_t connectTimeoutMs)
-{
- int8_t scanResult;
- String ssid;
- int32_t rssi;
- uint8_t encType;
- uint8_t *bssid;
- int32_t channel;
- bool hidden;
-
- // Get scan results
- scanResult = WiFi.scanComplete();
-
- // Find known WiFi networks
- uint8_t known[_APlist.size()];
- uint8_t numNetworks = 0;
- for (int8_t i = 0; i < scanResult; i++) {
- // Get network information
- WiFi.getNetworkInfo(i, ssid, encType, rssi, bssid, channel, hidden);
-
- // Check if the WiFi network contains an entry in AP list
- for (auto entry : _APlist) {
- // Check SSID
- if (ssid == entry.ssid) {
- // Known network
- known[numNetworks++] = i;
- }
- }
- }
-
- // Sort WiFi networks by RSSI
- for (int i = 0; i < numNetworks; i++) {
- for (int j = i + 1; j < numNetworks; j++) {
- if (WiFi.RSSI(known[j]) > WiFi.RSSI(known[i])) {
- int8_t tmp;
-
- // Swap indices
- tmp = known[i];
- known[i] = known[j];
- known[j] = tmp;
- }
- }
- }
-
- // Print sorted indices
- DEBUG_WIFI_MULTI("[WIFIM] Sorted indices: ");
- for (int8_t i = 0; i < numNetworks; i++) {
- DEBUG_WIFI_MULTI("%d ", known[i]);
- }
- DEBUG_WIFI_MULTI("\n");
-
- // Create indices for AP connection failures
- uint8_t connectSkipIndex[_APlist.size()];
- memset(connectSkipIndex, 0, sizeof(connectSkipIndex));
-
- // Connect to known WiFi AP's sorted by RSSI
- for (int8_t i = 0; i < numNetworks; i++) {
- // Get network information
- WiFi.getNetworkInfo(known[i], ssid, encType, rssi, bssid, channel, hidden);
-
- for (uint8_t j = 0; j < _APlist.size(); j++) {
- auto &entry = _APlist[j];
-
- // Check SSID
- if (ssid == entry.ssid) {
- DEBUG_WIFI_MULTI("[WIFIM] Connecting %s\n", ssid.c_str());
-
- // Connect to WiFi
- WiFi.begin(ssid, entry.passphrase, channel, bssid);
-
- // Wait for status change
- if (waitWiFiConnect(connectTimeoutMs) == WL_CONNECTED) {
- return WL_CONNECTED;
- }
-
- // Failed to connect, skip for hidden SSID connects
- connectSkipIndex[j] = true;
- }
- }
- }
-
- // Try to connect to hidden AP's which are not reported by WiFi scan
- for (uint8_t i = 0; i < _APlist.size(); i++) {
- auto &entry = _APlist[i];
-
- if (!connectSkipIndex[i]) {
- DEBUG_WIFI_MULTI("[WIFIM] Try hidden connect %s\n", entry.ssid);
-
- // Connect to WiFi
- WiFi.begin(entry.ssid, entry.passphrase);
-
- // Wait for status change
- if (waitWiFiConnect(connectTimeoutMs) == WL_CONNECTED) {
- return WL_CONNECTED;
- }
- }
- }
-
- DEBUG_WIFI_MULTI("[WIFIM] Could not connect\n");
-
- // Could not connect to any WiFi network
- return WL_CONNECT_FAILED;
-}
-
-// ##################################################################################
-
-/**
- * @brief Add WiFi connection to internal AP list
- * @param ssid
- * WiFi SSID
- * @param passphrase
- * WiFi Password
- * @retval true
- * Success
- * @retval false
- * Failure
- */
-bool ESP8266WiFiMulti::APlistAdd(const char *ssid, const char *passphrase)
-{
- WifiAPEntry newAP;
-
- if (!ssid || (*ssid == 0x00) || (strlen(ssid) > 32)) {
- // Fail SSID too long or missing!
- DEBUG_WIFI_MULTI("[WIFIM][APlistAdd] No ssid or ssid too long\n");
- return false;
- }
-
- // For passphrase, max is 63 ascii + null. For psk, 64hex + null.
- if (passphrase && (strlen(passphrase) > 64)) {
- // fail passphrase too long!
- DEBUG_WIFI_MULTI("[WIFIM][APlistAdd] Passphrase too long\n");
- return false;
- }
-
- if (APlistExists(ssid, passphrase)) {
- DEBUG_WIFI_MULTI("[WIFIM][APlistAdd] SSID: %s already exists\n", ssid);
- return true;
- }
-
- newAP.ssid = strdup(ssid);
-
- if (!newAP.ssid) {
- DEBUG_WIFI_MULTI("[WIFIM][APlistAdd] Fail newAP.ssid == 0\n");
- return false;
- }
-
- if (passphrase) {
- newAP.passphrase = strdup(passphrase);
- } else {
- newAP.passphrase = strdup("");
- }
-
- if (!newAP.passphrase) {
- DEBUG_WIFI_MULTI("[WIFIM][APlistAdd] Fail newAP.passphrase == 0\n");
- free(newAP.ssid);
- return false;
- }
-
- _APlist.push_back(newAP);
- DEBUG_WIFI_MULTI("[WIFIM][APlistAdd] Add SSID: %s\n", newAP.ssid);
- return true;
-}
-
-/**
- * @brief Check if AP exists in list
- * @param ssid
- * WiFi SSID
- * @param passphrase
- * WiFi Password
- * @return
- */
-bool ESP8266WiFiMulti::APlistExists(const char *ssid, const char *passphrase)
-{
- if (!ssid || (*ssid == 0x00) || (strlen(ssid) > 32)) {
- // Fail SSID too long or missing
- DEBUG_WIFI_MULTI("[WIFIM][APlistExists] No ssid or ssid too long\n");
- return false;
- }
-
- for (auto entry : _APlist) {
- if (!strcmp(entry.ssid, ssid)) {
- if (!passphrase) {
- if (!strcmp(entry.passphrase, "")) {
- return true;
- }
- } else {
- if (!strcmp(entry.passphrase, passphrase)) {
- return true;
- }
- }
- }
- }
- return false;
-}
-
-/**
- * @brief Remove all AP's from list
- */
-void ESP8266WiFiMulti::APlistClean(void)
-{
- // Remove all entries from APlist
- for (auto entry : _APlist) {
- if (entry.ssid) {
- free(entry.ssid);
- }
- if (entry.passphrase) {
- free(entry.passphrase);
- }
- }
-
- _APlist.clear();
-}
-
-/**
- * @brief Print WiFi scan results
- * @details
- * Macro DEBUG_ESP_WIFI and DEBUG_ESP_PORT must be configured
- */
-void ESP8266WiFiMulti::printWiFiScan()
-{
-#ifdef DEBUG_ESP_WIFI
- String ssid;
- int32_t rssi;
- uint8_t encryptionType;
- uint8_t* bssid;
- int32_t channel;
- bool hidden;
- int8_t scanResult;
-
- scanResult = WiFi.scanComplete();
-
- DEBUG_WIFI_MULTI("[WIFIM] %d networks found:\n", scanResult);
-
- // Print unsorted scan results
- for (int8_t i = 0; i < scanResult; i++) {
- bool known = false;
-
- WiFi.getNetworkInfo(i, ssid, encryptionType, rssi, bssid, channel, hidden);
-
- for(auto entry : _APlist) {
- if(ssid == entry.ssid) {
- // SSID match
- known = true;
- }
- }
-
- if (known) {
- DEBUG_WIFI_MULTI(" --->");
- } else {
- DEBUG_WIFI_MULTI(" ");
- }
-
- DEBUG_WIFI_MULTI(" %d: [CH %02d] [%02X:%02X:%02X:%02X:%02X:%02X] %ddBm %c %s\n",
- i,
- channel,
- bssid[0], bssid[1], bssid[2],
- bssid[3], bssid[4], bssid[5],
- rssi,
- (encryptionType == ENC_TYPE_NONE) ? ' ' : '*',
- ssid.c_str());
- esp_yield();
- }
-#endif
-}
diff --git a/lib/bms/ESP8266WiFiMulti.h b/lib/bms/ESP8266WiFiMulti.h
deleted file mode 100644
index fab5829..0000000
--- a/lib/bms/ESP8266WiFiMulti.h
+++ /dev/null
@@ -1,86 +0,0 @@
-/**
- *
- * @file ESP8266WiFiMulti.h
- * @date 30.09.2020
- * @author Markus Sattler, Erriez
- *
- * Copyright (c) 2015-2020 Markus Sattler. All rights reserved.
- * This file is part of the esp8266 core for Arduino environment.
- *
- * 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
- *
- */
-
-
-#ifndef WIFI_CLIENT_MULTI_H_
-#define WIFI_CLIENT_MULTI_H_
-
-#include "ESP8266WiFi.h"
-#include
-
-#ifdef DEBUG_ESP_WIFI
-#ifdef DEBUG_ESP_PORT
-#define DEBUG_WIFI_MULTI(fmt, ...) DEBUG_ESP_PORT.printf_P( (PGM_P)PSTR(fmt), ##__VA_ARGS__ )
-#endif
-#endif
-
-#ifndef DEBUG_WIFI_MULTI
-#define DEBUG_WIFI_MULTI(...) do { (void)0; } while (0)
-#endif
-
-//! Default WiFi connection timeout in ms
-#ifndef WIFI_CONNECT_TIMEOUT_MS
-#define WIFI_CONNECT_TIMEOUT_MS 5000
-#endif
-
-//! Default WiFi scan timeout in ms
-#ifndef WIFI_SCAN_TIMEOUT_MS
-#define WIFI_SCAN_TIMEOUT_MS 5000
-#endif
-
-struct WifiAPEntry {
- char *ssid;
- char *passphrase;
-};
-
-typedef std::vector WifiAPlist;
-
-class ESP8266WiFiMulti
-{
-public:
- ESP8266WiFiMulti();
- ~ESP8266WiFiMulti();
-
- bool addAP(const char *ssid, const char *passphrase = NULL);
- bool existsAP(const char *ssid, const char *passphrase = NULL);
-
- wl_status_t run(uint32_t connectTimeoutMs=WIFI_CONNECT_TIMEOUT_MS);
-
- void cleanAPlist();
-
-private:
- WifiAPlist _APlist;
- bool _firstRun;
-
- bool APlistAdd(const char *ssid, const char *passphrase = NULL);
- bool APlistExists(const char *ssid, const char *passphrase = NULL);
- void APlistClean();
-
- wl_status_t connectWiFiMulti(uint32_t connectTimeoutMs);
- int8_t startScan();
- void printWiFiScan();
-};
-
-#endif // WIFI_CLIENT_MULTI_H_
diff --git a/lib/bms/WiFiClient.cpp b/lib/bms/WiFiClient.cpp
deleted file mode 100644
index 6cdd5d1..0000000
--- a/lib/bms/WiFiClient.cpp
+++ /dev/null
@@ -1,454 +0,0 @@
-/*
- WiFiClient.cpp - TCP/IP client for esp8266, mostly compatible
- with Arduino WiFi shield library
-
- Copyright (c) 2014 Ivan Grokhotkov. All rights reserved.
- This file is part of the esp8266 core for Arduino environment.
-
- 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
-*/
-
-extern "C"
-{
- #include "wl_definitions.h"
- #include "osapi.h"
- #include "ets_sys.h"
-}
-
-#include "debug.h"
-#include "ESP8266WiFi.h"
-#include "WiFiClient.h"
-#include "WiFiServer.h"
-#include "lwip/opt.h"
-#include "lwip/ip.h"
-#include "lwip/tcp.h"
-#include "lwip/inet.h"
-#include "lwip/netif.h"
-#include
-#include "c_types.h"
-#include
-
-uint16_t WiFiClient::_localPort = 0;
-
-static bool defaultNoDelay = false; // false == Nagle enabled by default
-static bool defaultSync = false;
-
-bool getDefaultPrivateGlobalSyncValue ()
-{
- return defaultSync;
-}
-
-void WiFiClient::setDefaultNoDelay (bool noDelay)
-{
- defaultNoDelay = noDelay;
-}
-
-void WiFiClient::setDefaultSync (bool sync)
-{
- defaultSync = sync;
-}
-
-bool WiFiClient::getDefaultNoDelay ()
-{
- return defaultNoDelay;
-}
-
-bool WiFiClient::getDefaultSync ()
-{
- return defaultSync;
-}
-
-template<>
-WiFiClient* SList::_s_first = 0;
-
-
-WiFiClient::WiFiClient()
-: _client(0), _owned(0)
-{
- _timeout = 5000;
- WiFiClient::_add(this);
-}
-
-WiFiClient::WiFiClient(ClientContext* client)
-: _client(client), _owned(0)
-{
- _timeout = 5000;
- _client->ref();
- WiFiClient::_add(this);
-
- setSync(defaultSync);
- setNoDelay(defaultNoDelay);
-}
-
-WiFiClient::~WiFiClient()
-{
- WiFiClient::_remove(this);
- if (_client)
- _client->unref();
-}
-
-std::unique_ptr WiFiClient::clone() const {
- return std::make_unique(*this);
-}
-
-WiFiClient::WiFiClient(const WiFiClient& other)
-{
- _client = other._client;
- _timeout = other._timeout;
- _localPort = other._localPort;
- _owned = other._owned;
- if (_client)
- _client->ref();
- WiFiClient::_add(this);
-}
-
-WiFiClient& WiFiClient::operator=(const WiFiClient& other)
-{
- if (_client)
- _client->unref();
- _client = other._client;
- _timeout = other._timeout;
- _localPort = other._localPort;
- _owned = other._owned;
- if (_client)
- _client->ref();
- return *this;
-}
-
-int WiFiClient::connect(const char* host, uint16_t port)
-{
- IPAddress remote_addr;
- if (WiFi.hostByName(host, remote_addr, _timeout))
- {
- return connect(remote_addr, port);
- }
- return 0;
-}
-
-int WiFiClient::connect(const String& host, uint16_t port)
-{
- return connect(host.c_str(), port);
-}
-
-int WiFiClient::connect(IPAddress ip, uint16_t port)
-{
- if (_client) {
- stop();
- _client->unref();
- _client = nullptr;
- }
-
- tcp_pcb* pcb = tcp_new();
- if (!pcb)
- return 0;
-
- if (_localPort > 0) {
- pcb->local_port = _localPort++;
- }
-
- _client = new ClientContext(pcb, nullptr, nullptr);
- _client->ref();
- _client->setTimeout(_timeout);
- int res = _client->connect(ip, port);
- if (res == 0) {
- _client->unref();
- _client = nullptr;
- return 0;
- }
-
- setSync(defaultSync);
- setNoDelay(defaultNoDelay);
-
- return 1;
-}
-
-void WiFiClient::setNoDelay(bool nodelay) {
- if (!_client)
- return;
- _client->setNoDelay(nodelay);
-}
-
-bool WiFiClient::getNoDelay() const {
- if (!_client)
- return false;
- return _client->getNoDelay();
-}
-
-void WiFiClient::setSync(bool sync)
-{
- if (!_client)
- return;
- _client->setSync(sync);
-}
-
-bool WiFiClient::getSync() const
-{
- if (!_client)
- return false;
- return _client->getSync();
-}
-
-int WiFiClient::availableForWrite ()
-{
- return _client? _client->availableForWrite(): 0;
-}
-
-size_t WiFiClient::write(uint8_t b)
-{
- return write(&b, 1);
-}
-
-size_t WiFiClient::write(const uint8_t *buf, size_t size)
-{
- if (!_client || !size)
- {
- return 0;
- }
- _client->setTimeout(_timeout);
- return _client->write((const char*)buf, size);
-}
-
-size_t WiFiClient::write(Stream& stream)
-{
- // (this method is deprecated)
-
- if (!_client || !stream.available())
- {
- return 0;
- }
- // core up to 2.7.4 was equivalent to this
- return stream.sendAll(this);
-}
-
-size_t WiFiClient::write_P(PGM_P buf, size_t size)
-{
- if (!_client || !size)
- {
- return 0;
- }
- _client->setTimeout(_timeout);
- StreamConstPtr nopeek(buf, size);
- return nopeek.sendAll(this);
-}
-
-int WiFiClient::available()
-{
- if (!_client)
- return 0;
-
- int result = _client->getSize();
-
- if (!result) {
- optimistic_yield(100);
- }
- return result;
-}
-
-int WiFiClient::read()
-{
- if (!available())
- return -1;
-
- return _client->read();
-}
-
-int WiFiClient::read(uint8_t* buf, size_t size)
-{
- return (int)_client->read((char*)buf, size);
-}
-
-int WiFiClient::read(char* buf, size_t size)
-{
- return (int)_client->read(buf, size);
-}
-
-int WiFiClient::peek()
-{
- if (!available())
- return -1;
-
- return _client->peek();
-}
-
-size_t WiFiClient::peekBytes(uint8_t *buffer, size_t length) {
- size_t count = 0;
-
- if(!_client) {
- return 0;
- }
-
- _startMillis = millis();
- while((available() < (int) length) && ((millis() - _startMillis) < _timeout)) {
- yield();
- }
-
- if(available() < (int) length) {
- count = available();
- } else {
- count = length;
- }
-
- return _client->peekBytes((char *)buffer, count);
-}
-
-bool WiFiClient::flush(unsigned int maxWaitMs)
-{
- if (!_client)
- return true;
-
- if (maxWaitMs == 0)
- maxWaitMs = WIFICLIENT_MAX_FLUSH_WAIT_MS;
- return _client->wait_until_acked(maxWaitMs);
-}
-
-bool WiFiClient::stop(unsigned int maxWaitMs)
-{
- if (!_client)
- return true;
-
- bool ret = flush(maxWaitMs); // virtual, may be ssl's
- if (_client->close() != ERR_OK)
- ret = false;
- return ret;
-}
-
-uint8_t WiFiClient::connected()
-{
- if (!_client || _client->state() == CLOSED)
- return 0;
-
- return _client->state() == ESTABLISHED || available();
-}
-
-uint8_t WiFiClient::status()
-{
- if (!_client)
- return CLOSED;
- return _client->state();
-}
-
-WiFiClient::operator bool()
-{
- return available() || connected();
-}
-
-IPAddress WiFiClient::remoteIP()
-{
- if (!_client || !_client->getRemoteAddress())
- return IPAddress(0U);
-
- return _client->getRemoteAddress();
-}
-
-uint16_t WiFiClient::remotePort()
-{
- if (!_client)
- return 0;
-
- return _client->getRemotePort();
-}
-
-IPAddress WiFiClient::localIP()
-{
- if (!_client || !_client->getLocalAddress())
- return IPAddress(0U);
-
- return IPAddress(_client->getLocalAddress());
-}
-
-uint16_t WiFiClient::localPort()
-{
- if (!_client)
- return 0;
-
- return _client->getLocalPort();
-}
-
-void WiFiClient::stopAll()
-{
- for (WiFiClient* it = _s_first; it; it = it->_next) {
- it->stop();
- }
-}
-
-
-void WiFiClient::stopAllExcept(WiFiClient* except)
-{
- // Stop all will look at the lowest-level wrapper connections only
- while (except->_owned) {
- except = except->_owned;
- }
- for (WiFiClient* it = _s_first; it; it = it->_next) {
- WiFiClient* conn = it;
- // Find the lowest-level owner of the current list entry
- while (conn->_owned) {
- conn = conn->_owned;
- }
- if (conn != except) {
- conn->stop();
- }
- }
-}
-
-
-void WiFiClient::keepAlive (uint16_t idle_sec, uint16_t intv_sec, uint8_t count)
-{
- _client->keepAlive(idle_sec, intv_sec, count);
-}
-
-bool WiFiClient::isKeepAliveEnabled () const
-{
- return _client->isKeepAliveEnabled();
-}
-
-uint16_t WiFiClient::getKeepAliveIdle () const
-{
- return _client->getKeepAliveIdle();
-}
-
-uint16_t WiFiClient::getKeepAliveInterval () const
-{
- return _client->getKeepAliveInterval();
-}
-
-uint8_t WiFiClient::getKeepAliveCount () const
-{
- return _client->getKeepAliveCount();
-}
-
-bool WiFiClient::hasPeekBufferAPI () const
-{
- return true;
-}
-
-// return a pointer to available data buffer (size = peekAvailable())
-// semantic forbids any kind of read() before calling peekConsume()
-const char* WiFiClient::peekBuffer ()
-{
- return _client? _client->peekBuffer(): nullptr;
-}
-
-// return number of byte accessible by peekBuffer()
-size_t WiFiClient::peekAvailable ()
-{
- return _client? _client->peekAvailable(): 0;
-}
-
-// consume bytes after use (see peekBuffer)
-void WiFiClient::peekConsume (size_t consume)
-{
- if (_client)
- _client->peekConsume(consume);
-}
diff --git a/lib/bms/WiFiClient.h b/lib/bms/WiFiClient.h
deleted file mode 100644
index 5065622..0000000
--- a/lib/bms/WiFiClient.h
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- WiFiClient.h - Library for Arduino Wifi shield.
- Copyright (c) 2011-2014 Arduino. 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
-
- Modified by Ivan Grokhotkov, December 2014 - esp8266 support
-*/
-
-#ifndef wificlient_h
-#define wificlient_h
-#include
-#include "Arduino.h"
-#include "Print.h"
-#include "Client.h"
-#include "IPAddress.h"
-#include "include/slist.h"
-
-#ifndef TCP_MSS
-#define TCP_MSS 1460 // lwip1.4
-#endif
-
-#define WIFICLIENT_MAX_PACKET_SIZE TCP_MSS
-#define WIFICLIENT_MAX_FLUSH_WAIT_MS 300
-
-#define TCP_DEFAULT_KEEPALIVE_IDLE_SEC 7200 // 2 hours
-#define TCP_DEFAULT_KEEPALIVE_INTERVAL_SEC 75 // 75 sec
-#define TCP_DEFAULT_KEEPALIVE_COUNT 9 // fault after 9 failures
-
-class ClientContext;
-class WiFiServer;
-
-class WiFiClient : public Client, public SList {
-protected:
- WiFiClient(ClientContext* client);
-
-public:
- WiFiClient();
- virtual ~WiFiClient();
- WiFiClient(const WiFiClient&);
- WiFiClient& operator=(const WiFiClient&);
-
- // b/c this is both a real class and a virtual parent of the secure client, make sure
- // there's a safe way to copy from the pointer without 'slicing' it; i.e. only the base
- // portion of a derived object will be copied, and the polymorphic behavior will be corrupted.
- //
- // this class still implements the copy and assignment though, so this is not yet enforced
- // (but, *should* be inside the Core itself, see httpclient & server)
- //
- // ref.
- // - https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-copy-virtual
- // - https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rh-copy
- virtual std::unique_ptr clone() const;
-
- virtual uint8_t status();
- virtual int connect(IPAddress ip, uint16_t port) override;
- virtual int connect(const char *host, uint16_t port) override;
- virtual int connect(const String& host, uint16_t port);
- virtual size_t write(uint8_t) override;
- virtual size_t write(const uint8_t *buf, size_t size) override;
- virtual size_t write_P(PGM_P buf, size_t size);
- [[ deprecated("use stream.sendHow(client...)") ]]
- size_t write(Stream& stream);
-
- virtual int available() override;
- virtual int read() override;
- virtual int read(uint8_t* buf, size_t size) override;
- int read(char* buf, size_t size);
-
- virtual int peek() override;
- virtual size_t peekBytes(uint8_t *buffer, size_t length);
- size_t peekBytes(char *buffer, size_t length) {
- return peekBytes((uint8_t *) buffer, length);
- }
- virtual void flush() override { (void)flush(0); } // wait for all outgoing characters to be sent, output buffer should be empty after this call
- virtual void stop() override { (void)stop(0); }
- bool flush(unsigned int maxWaitMs);
- bool stop(unsigned int maxWaitMs);
- virtual uint8_t connected() override;
- virtual operator bool() override;
-
- IPAddress remoteIP();
- uint16_t remotePort();
- IPAddress localIP();
- uint16_t localPort();
-
- static void setLocalPortStart(uint16_t port) { _localPort = port; }
-
- int availableForWrite() override;
-
- friend class WiFiServer;
-
- using Print::write;
-
- static void stopAll();
- static void stopAllExcept(WiFiClient * c);
-
- void keepAlive (uint16_t idle_sec = TCP_DEFAULT_KEEPALIVE_IDLE_SEC, uint16_t intv_sec = TCP_DEFAULT_KEEPALIVE_INTERVAL_SEC, uint8_t count = TCP_DEFAULT_KEEPALIVE_COUNT);
- bool isKeepAliveEnabled () const;
- uint16_t getKeepAliveIdle () const;
- uint16_t getKeepAliveInterval () const;
- uint8_t getKeepAliveCount () const;
- void disableKeepAlive () { keepAlive(0, 0, 0); }
-
- // default NoDelay=False (Nagle=True=!NoDelay)
- // Nagle is for shortly delaying outgoing data, to send less/bigger packets
- // Nagle should be disabled for telnet-like/interactive streams
- // Nagle is meaningless/ignored when Sync=true
- static void setDefaultNoDelay (bool noDelay);
- static bool getDefaultNoDelay ();
- bool getNoDelay() const;
- void setNoDelay(bool nodelay);
-
- // default Sync=false
- // When sync is true, all writes are automatically flushed.
- // This is slower but also does not allocate
- // temporary memory for sending data
- static void setDefaultSync (bool sync);
- static bool getDefaultSync ();
- bool getSync() const;
- void setSync(bool sync);
-
- // peek buffer API is present
- virtual bool hasPeekBufferAPI () const override;
-
- // return number of byte accessible by peekBuffer()
- virtual size_t peekAvailable () override;
-
- // return a pointer to available data buffer (size = peekAvailable())
- // semantic forbids any kind of read() before calling peekConsume()
- virtual const char* peekBuffer () override;
-
- // consume bytes after use (see peekBuffer)
- virtual void peekConsume (size_t consume) override;
-
- virtual bool outputCanTimeout () override { return connected(); }
- virtual bool inputCanTimeout () override { return connected(); }
-
-protected:
-
- static int8_t _s_connected(void* arg, void* tpcb, int8_t err);
- static void _s_err(void* arg, int8_t err);
-
- int8_t _connected(void* tpcb, int8_t err);
- void _err(int8_t err);
-
- ClientContext* _client;
- WiFiClient* _owned;
- static uint16_t _localPort;
-};
-
-#endif
diff --git a/src/EEPROM_Rotate.cpp b/src/EEPROM_Rotate.cpp
new file mode 100644
index 0000000..bcf438c
--- /dev/null
+++ b/src/EEPROM_Rotate.cpp
@@ -0,0 +1,397 @@
+/*
+
+EEPROM Rotate 0.9.2
+
+EEPROM wrapper for ESP8266
+
+Copyright (C) 2018 by Xose Pérez
+
+The EEPROM_Rotate 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 3 of the License, or
+(at your option) any later version.
+
+The EEPROM_Rotate 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 the EEPROM_Rotate library. If not, see .
+
+*/
+
+#include "Arduino.h"
+#include "EEPROM_Rotate.h"
+
+extern "C" {
+ #include "spi_flash.h"
+}
+
+extern "C" uint32_t _SPIFFS_end;
+
+// -----------------------------------------------------------------------------
+// PUBLIC *NEW* METHODS
+// -----------------------------------------------------------------------------
+
+/**
+ * @brief Defines the sector pool size that will be used for EEPROM rotation.
+ * Must be called before the begin method.
+ * @param {uint8_t} sectors Number of sectors (from 1 to 10)
+ * @returns {bool} True if seccessfully set
+ */
+bool EEPROM_Rotate::size(uint8_t size) {
+ if (size < 1 || 10 < size) return false;
+ _pool_size = size;
+ return true;
+}
+
+/**
+ * @brief Defines the offset inside the sector memory where the magic bytes will be stored.
+ * The library uses 3 bytes to track last valid sector, so there must be at least 3
+ * bytes available in the memory buffer from the offset onwards.
+ * Must be called before the begin method.
+ * @param {uint16_t} offset Offset
+ * @returns {bool} True if seccessfully set
+ */
+bool EEPROM_Rotate::offset(uint16_t offset) {
+ if (offset + 3 > SPI_FLASH_SEC_SIZE) return false;
+ _offset = offset;
+ return true;
+}
+
+/**
+ * @brief Returns the number of the last available sector for EEPROM.
+ * This is also the sector that the default EEPROM library uses.
+ * @returns {uint32_t} Last available sector for EEPROM storing
+ */
+uint32_t EEPROM_Rotate::last() {
+ return ESP.getFlashChipSize() / SPI_FLASH_SEC_SIZE - 5;
+}
+
+/**
+ * @brief Returns the base sector for current rotating configuration.
+ * @returns {uint32_t} Base sector
+ */
+uint32_t EEPROM_Rotate::base() {
+ return _base;
+}
+
+/**
+ * @brief Returns the sector index whose contents match those of the EEPROM data buffer.
+ * @returns {uint32_t} Current sector
+ */
+uint32_t EEPROM_Rotate::current() {
+ return _sector;
+}
+
+/**
+ * @brief Returns the number of sectors used for rotating EEPROM.
+ * @returns {uint8_t} Sector pool size
+ */
+uint8_t EEPROM_Rotate::size() {
+ return _pool_size;
+}
+
+/**
+ * @brief Returns the number of sectors reserved to EEPROM in the memory layout
+ * @returns {uint8_t} Number of sectors
+ */
+uint8_t EEPROM_Rotate::reserved() {
+ uint32_t fs_end = (uint32_t)&_SPIFFS_end - 0x40200000;
+ uint8_t sectors = (ESP.getFlashChipSize() - fs_end) / SPI_FLASH_SEC_SIZE - 4;
+ return sectors;
+}
+
+/**
+ * @brief Backups the current data to the given sector.
+ * @param {uint32_t} target Target sector (defaults to base sector)
+ * @returns {bool} True if seccessfully copied
+ */
+bool EEPROM_Rotate::backup(uint32_t target) {
+
+ // Backup to the latest sector by default
+ if (0 == target) target = base();
+
+ // Do not backup if sector is already current
+ if (_sector == target) return true;
+
+ // Backup current index
+ uint32_t backup_index = _sector_index;
+
+ // Calculate new index (must be previous to target)
+ _sector_index = _getIndex(target);
+ if (0 == _sector_index) {
+ _sector_index = _pool_size - 1;
+ } else {
+ --_sector_index;
+ }
+
+ // Flag as dirty to force write
+ _dirty = true;
+
+ // Do commit
+ bool ret = commit();
+
+ // If commit failed restore index
+ if (!ret) {
+ _sector_index = backup_index;
+ _sector = _getSector(_sector_index);
+ }
+
+ return ret;
+
+}
+
+/**
+ * @brief Enable or disable sector rotation.
+ * If disabled it will always write to the last sector.
+ * This is useful if you are doing something that might use the other sectors
+ * (like an OTA upgrade) and you don't want the library to mess with them.
+ * Disabling rotation also sets the _dirty flag to True,
+ * so it forces next commit to persist the data to the last sector.
+ * @param {bool} value True to enable (and let them rotate), False to disable
+ * @returns {bool} Current rotating status
+ */
+bool EEPROM_Rotate::rotate(bool value) {
+ if (false == value) _dirty = true;
+ return (_enabled = value);
+}
+
+/**
+ * @brief Dumps the EEPROM data to the given stream in a human-friendly way.
+ * @param {Stream &} debug Stream to dump the data to
+ * @param {uint32_t} sector Sector to dump (default to current sector)
+ */
+void EEPROM_Rotate::dump(Stream & debug, uint32_t sector) {
+
+ if (0 == sector) sector = _sector;
+ if (sector > last() + 4) return;
+
+ char ascii[17];
+ memset(ascii, ' ', 16);
+ ascii[16] = 0;
+
+ debug.printf("\n ");
+ for (uint16_t i = 0; i <= 0x0F; i++) {
+ debug.printf("%02X ", i);
+ }
+ debug.printf("\n------------------------------------------------------");
+
+ uint8_t * data = new uint8_t[16];
+
+ for (uint16_t address = 0; address < SPI_FLASH_SEC_SIZE; address++) {
+
+ if ((address % 16) == 0) {
+ noInterrupts();
+ spi_flash_read(sector * SPI_FLASH_SEC_SIZE + address, reinterpret_cast(data), 16);
+ interrupts();
+ if (address > 0) {
+ debug.print(ascii);
+ memset(ascii, ' ', 16);
+ }
+ debug.printf("\n0x%04X: ", address);
+ }
+
+ uint8_t b = data[address % 16];
+ if (31 < b && b < 127) ascii[address % 16] = (char) b;
+ debug.printf("%02X ", b);
+
+ yield();
+
+ }
+
+ delete[] data;
+
+ debug.print(ascii);
+ debug.printf("\n\n");
+
+}
+
+// -----------------------------------------------------------------------------
+// OVERWRITTEN METHODS
+// -----------------------------------------------------------------------------
+
+/**
+ * @brief Loads 'size' bytes of data into memory for EEPROM emulation from the
+ * latest valid sector in the sector pool
+ * @param {size_t} size Data size to read
+ */
+void EEPROM_Rotate::begin(size_t size) {
+
+ uint32_t best_index = 0;
+ uint8_t best_value = 0xFF;
+ bool first = true;
+
+ for (uint32_t index = 0; index < _pool_size; index++) {
+
+ // load the sector data
+ // Sector count goes downward,
+ // so default EEPROM sector is always index 0
+ _sector = _getSector(index);
+ EEPROMClass::begin(size);
+
+ // get sector value
+ uint8_t value = read(_offset + EEPROM_ROTATE_COUNTER_OFFSET);
+ DEBUG_EEPROM_ROTATE("Magic value for sector #%u is %u\n", _sector, value);
+
+ // validate content
+ if (!_checkCRC()) {
+ DEBUG_EEPROM_ROTATE("Sector #%u has not passed the CRC check\n", _sector);
+ continue;
+ }
+
+ // if this is the first valid sector we are reading
+ if (first) {
+
+ first = false;
+ best_index = index;
+ best_value = value;
+
+ // else compare values
+ } else {
+
+ // This new sector is newer if...
+ bool newer = ((value < best_value) and (best_value - value) > 128) or \
+ ((value > best_value) and (value - best_value) < 128);
+
+ if (newer) {
+ best_index = index;
+ best_value = value;
+ }
+
+ }
+
+ }
+
+ // Re-read the data from the best index sector
+ _sector_index = best_index;
+ _sector = _getSector(_sector_index);
+ EEPROMClass::begin(size);
+ _sector_value = read(_offset + EEPROM_ROTATE_COUNTER_OFFSET);
+
+ DEBUG_EEPROM_ROTATE("Current sector is #%u\n", _sector);
+ DEBUG_EEPROM_ROTATE("Current magic value is #%u\n", _sector_value);
+
+}
+
+/**
+ * @brief Writes data from memory to the next sector in the sector pool and flags it as current sector
+ * @returns {bool} True if successfully written
+ */
+bool EEPROM_Rotate::commit() {
+
+ // Check if we are really going to write
+ if (!_size) return false;
+ if (!_dirty) return true;
+ if (!_data) return false;
+
+ // Backup current values
+ uint8_t index_backup = _sector_index;
+ uint8_t value_backup = _sector_value;
+
+ // Calculate the sector to write to
+ if (_enabled) {
+ // Get the sector for the next write
+ _sector_index = (_sector_index + 1) % _pool_size;
+ } else {
+ // If rotation is disabled write always to the last sector (index 0)
+ _sector_index = 0;
+ }
+
+ // Update sector for next write
+ _sector = _getSector(_sector_index);
+ _sector_value++;
+
+ DEBUG_EEPROM_ROTATE("Writing to sector #%u\n", _sector);
+ DEBUG_EEPROM_ROTATE("Writing magic value #%u\n", _sector_value);
+
+ // Update the counter & crc bytes
+ uint16_t crc = _calculateCRC();
+ write(_offset + EEPROM_ROTATE_CRC_OFFSET, (crc >> 8) & 0xFF);
+ write(_offset + EEPROM_ROTATE_CRC_OFFSET + 1, crc & 0xFF);
+ write(_offset + EEPROM_ROTATE_COUNTER_OFFSET, _sector_value);
+
+ // Perform the commit
+ bool ret = EEPROMClass::commit();
+
+ // If commit failed restore values
+ if (!ret) {
+
+ DEBUG_EEPROM_ROTATE("Commit to sector #%u failed, restoring\n", _sector);
+
+ // Restore values
+ _sector_index = index_backup;
+ _sector_value = value_backup;
+ _sector = _getSector(_sector_index);
+
+ }
+
+ return ret;
+
+}
+
+// -----------------------------------------------------------------------------
+// PRIVATE METHODS
+// -----------------------------------------------------------------------------
+
+/**
+ * @brief Calculates and automatically sets
+ * the pool size based on the memory layout
+ * @protected
+ */
+void EEPROM_Rotate::_auto() {
+ uint8_t size = reserved();
+ if (size > 10) size = 10;
+ if (size < 1) size = 1;
+ _pool_size = size;
+}
+
+/**
+ * @brief Returns sector from index
+ * @param {uint8_t} index Sector index
+ * @returns {uint32_t} Sector number
+ * @protected
+ */
+uint32_t EEPROM_Rotate::_getSector(uint8_t index) {
+ return _base - index;
+}
+
+/**
+ * @brief Returns index for sector
+ * @param {uint32_t} sector Sector number
+ * @returns {uint8_t} Sector index
+ * @protected
+ */
+uint8_t EEPROM_Rotate::_getIndex(uint32_t sector) {
+ return _base - sector;
+}
+
+/**
+ * @brief Calculates the CRC of the data in memory (except for the magic bytes)
+ * @returns {uint16_t} CRC
+ * @protected
+ */
+uint16_t EEPROM_Rotate::_calculateCRC() {
+ uint16_t crc = 0;
+ for (uint16_t address = 0; address < _size; address++) {
+ if (_offset <= address && address <= _offset + 2) continue;
+ crc = crc + read(address);
+ }
+ return crc;
+}
+
+/**
+ * @brief Compares the CRC of the data in memory against the stored one
+ * @returns {bool} True if they match, so data is OK
+ * @protected
+ */
+bool EEPROM_Rotate::_checkCRC() {
+ uint16_t calculated = _calculateCRC();
+ uint16_t stored =
+ (read(_offset + EEPROM_ROTATE_CRC_OFFSET) << 8) +
+ read(_offset + EEPROM_ROTATE_CRC_OFFSET + 1);
+ DEBUG_EEPROM_ROTATE("Calculated CRC: 0x%04X\n", calculated);
+ DEBUG_EEPROM_ROTATE("Stored CRC : 0x%04X\n", stored);
+ return (calculated == stored);
+}
diff --git a/src/EEPROM_Rotate.h b/src/EEPROM_Rotate.h
new file mode 100644
index 0000000..997cbfa
--- /dev/null
+++ b/src/EEPROM_Rotate.h
@@ -0,0 +1,84 @@
+/*
+
+EEPROM Rotate 0.9.2
+
+EEPROM wrapper for ESP8266
+
+Copyright (C) 2018 by Xose Pérez
+
+The EEPROM_Rotate 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 3 of the License, or
+(at your option) any later version.
+
+The EEPROM_Rotate 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 the EEPROM_Rotate library. If not, see .
+
+*/
+
+#ifndef EEPROM_ROTATE_H
+#define EEPROM_ROTATE_H
+
+#include
+#include
+
+#ifdef DEBUG_EEPROM_ROTATE_PORT
+#define DEBUG_EEPROM_ROTATE(...) DEBUG_EEPROM_ROTATE_PORT.printf( __VA_ARGS__ )
+#else
+#define DEBUG_EEPROM_ROTATE(...)
+#endif
+
+#define EEPROM_ROTATE_CRC_OFFSET 0 // 2 bytes long
+#define EEPROM_ROTATE_COUNTER_OFFSET 2 // 1 byte long
+
+class EEPROM_Rotate: public EEPROMClass {
+
+ public:
+
+ EEPROM_Rotate(void): EEPROMClass(last()), _base(last()) {
+ _auto();
+ };
+ EEPROM_Rotate(uint32_t sector): EEPROMClass(sector), _base(sector) {
+ _auto();
+ };
+
+ bool size(uint8_t size);
+ uint8_t size();
+ bool offset(uint16_t offset);
+
+ void begin(size_t size);
+ bool commit();
+
+ uint32_t base();
+ uint32_t last();
+ uint32_t current();
+ uint8_t reserved();
+ bool rotate(bool value);
+ bool backup(uint32_t target = 0);
+ void dump(Stream & debug, uint32_t sector = 0);
+
+ protected:
+
+ uint32_t _base = 0;
+ uint8_t _pool_size = 1;
+
+ uint16_t _offset = 0;
+ uint8_t _sector_index = 0;
+ uint8_t _sector_value = 0;
+
+ bool _enabled = true;
+
+ void _auto();
+ uint32_t _getSector(uint8_t index);
+ uint8_t _getIndex(uint32_t sector);
+ uint16_t _calculateCRC();
+ bool _checkCRC();
+
+};
+
+#endif // EEPROM_ROTATE_H
diff --git a/src/owie-watcher.ino b/src/owie-watcher.ino
index b8b2bb7..7785c15 100644
--- a/src/owie-watcher.ino
+++ b/src/owie-watcher.ino
@@ -1,6 +1,4 @@
#include
-#include
-#include
#include
#include
#include
@@ -11,7 +9,7 @@ ESP8266WiFiMulti WiFiMulti;
char* ssid = "owie-ssid";
char* password = "owie-wifi-password";
#define OLED_RESET 0 // GPIO0
-Adafruit_SSD1306 display(OLED_RESET);
+//Adafruit_SSD1306 display(OLED_RESET);
//Boot Logo, change if you dont like it
static const unsigned char PROGMEM logo16_glcd_bmp[] =
@@ -66,21 +64,21 @@ static const unsigned char PROGMEM logo16_glcd_bmp[] =
void setup() {
- Serial.begin(115200);
- display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
- display.clearDisplay();
- display.drawBitmap(0, 0, logo16_glcd_bmp, 64, 48, 1);
- display.display();
+ //Serial.begin(115200);
+ //display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
+ //display.clearDisplay();
+ //display.drawBitmap(0, 0, logo16_glcd_bmp, 64, 48, 1);
+ //display.display();
delay(2000);
- display.clearDisplay();
+ //display.clearDisplay();
- Serial.println();
- Serial.println();
- Serial.println();
+ //Serial.println();
+ //Serial.println();
+ //Serial.println();
for (uint8_t t = 4; t > 0; t--) {
- Serial.printf("[SETUP] WAIT %d...\n", t);
- Serial.flush();
+ // Serial.printf("[SETUP] WAIT %d...\n", t);
+ // Serial.flush();
delay(1000);
}
@@ -95,26 +93,26 @@ void loop() {
WiFiClient client;
HTTPClient http;
- display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
- display.setTextSize(1);
- display.setTextColor(WHITE,BLACK);
+ //display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
+ //display.setTextSize(1);
+ //display.setTextColor(WHITE,BLACK);
- display.setCursor(0, 0);
- Serial.print("[HTTP] begin...\n");
+ //display.setCursor(0, 0);
+ //Serial.print("[HTTP] begin...\n");
http.begin(client, "http://192.168.4.1");
- Serial.print("[HTTP] GET...\n");
+ // Serial.print("[HTTP] GET...\n");
int httpCode = http.GET();
if (httpCode > 0) {
- Serial.printf("[HTTP] GET... code: %d\n", httpCode);
+ // Serial.printf("[HTTP] GET... code: %d\n", httpCode);
if (httpCode == HTTP_CODE_OK) {
int len = http.getSize();
#if 0
// with API
- Serial.println(http.getString());
+ // Serial.println(http.getString());
#else
WiFiClient* stream = &client;
@@ -122,31 +120,31 @@ void loop() {
char vbuff[5] = {0};
char socbuff[4] = {0};
char ovrbuff[4] = {0};
- display.setCursor(0, 0);
+ //display.setCursor(0, 0);
stream->find("Voltage");
while(stream->find("TOTAL_VOLTAGE\">")){
stream->readBytesUntil('<',vbuff,5);
- Serial.print(vbuff);
- display.print("VOL:");
- display.print(vbuff);
- display.print("v");
- display.println();
- display.display();
+ // Serial.print(vbuff);
+ //display.print("VOL:");
+ //display.print(vbuff);
+ //display.print("v");
+ // display.println();
+ //display.display();
stream->find("BMS_SOC\">");
stream->readBytesUntil('<',socbuff,4);
- display.println();
- display.print("BMS:");
- Serial.println();
- Serial.print(socbuff);
- display.print(socbuff);
- display.println();
- display.display();
+ //display.println();
+ //display.print("BMS:");
+ // Serial.println();
+ // Serial.print(socbuff);
+ //display.print(socbuff);
+ //display.println();
+ //display.display();
stream->find("OVERRIDDEN_SOC\">");
stream->readBytesUntil('<',ovrbuff,4);
- Serial.println();
- Serial.print(ovrbuff);
- display.println();
- display.print("OVR:");
+ // Serial.println();
+ // Serial.print(ovrbuff);
+ // display.println();
+ /* display.print("OVR:");
display.print(ovrbuff);
display.println();
display.display();
@@ -169,24 +167,25 @@ void loop() {
display.print(".");
Serial.print(".");
display.display();
- delay(1 * 1000);
+ delay(1 * 1000);
+ */
return;
}
}
#endif
- Serial.println();
- Serial.print("[HTTP] connection closed or file end.\n");
+ // Serial.println();
+ // Serial.print("[HTTP] connection closed or file end.\n");
}
} else {
- Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
+ // Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
}
http.end();
}
- Serial.print("RETRYING...");
- display.setCursor(0, 41);
- display.print("RETRYING ");
- display.display();
+ // Serial.print("RETRYING..."); //testbb
+ //display.setCursor(0, 41);
+ //display.print("RETRYING ");
+ //display.display();
delay(30 * 1000);
}
From fa4337c57699cfb6924d89731008cf62c5836b25 Mon Sep 17 00:00:00 2001
From: tonyt321 <34804458+tonyt321@users.noreply.github.com>
Date: Thu, 8 Sep 2022 10:26:10 -0400
Subject: [PATCH 09/11] Update owie-watcher.ino
---
owie-watcher.ino | 160 +++++++++++++++++++++++++++++++++--------------
1 file changed, 114 insertions(+), 46 deletions(-)
diff --git a/owie-watcher.ino b/owie-watcher.ino
index b8b2bb7..461270a 100644
--- a/owie-watcher.ino
+++ b/owie-watcher.ino
@@ -1,17 +1,77 @@
#include
-#include
-#include
+#include // https://github.com/olikraus/u8g2/wiki/u8g2setupcpp
+#include
#include
#include
#include
+
ESP8266WiFiMulti WiFiMulti;
// REPLACE WITH YOUR NETWORK CREDENTIALS
-char* ssid = "owie-ssid";
-char* password = "owie-wifi-password";
-#define OLED_RESET 0 // GPIO0
-Adafruit_SSD1306 display(OLED_RESET);
+char* ssid = "Owie-8282";
+char* password = "";
+
+// uncomment the one that works for your OLED if none check the U8G2 example codes
+// R is the rotation change to pick direction R0,R1,R2,R3
+//U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R2, /* reset=*/ U8X8_PIN_NONE); //1.3 inch 128x64 generic
+U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ D4); //2.42 inch 128x64 generic
+
+
+
+////////////////////////////////////////////////////////////////////////////////////////////
+void wifiscan()
+{ // WiFi.scanNetworks will return the number of networks found
+ int n = WiFi.scanNetworks();
+ Serial.println("scan done");
+ if (n == 0) {
+ Serial.println("no networks found");
+ } else {
+ Serial.print(n);
+ Serial.println(" networks found");
+ for (int i = 0; i < n; ++i) {
+ // Print SSID and RSSI for each network found
+ Serial.print(i + 1);
+ Serial.print(": ");
+ Serial.print(WiFi.SSID(i));
+ Serial.print(" strength: ");
+ Serial.print(WiFi.RSSI(i));
+ Serial.print(" encrypt = ");
+ Serial.println(WiFi.encryptionType(i));
+ delay(5000);
+
+ u8g2.firstPage();
+ do {
+
+ u8g2.setFont(u8g2_font_lubI12_te); // 15 high font
+ //u8g2.setFont(u8g2_font_maniac_tf);dual outline font //u8g2_font_inr33_t_cyrillic);//u8g2_font_maniac_tf//u8g2_font_maniac_tf//u8g2_font_freedoomr25_tn//u8g2_font_7Segments_26x42_mn
+ u8g2.setCursor(0,24);
+ u8g2.print (WiFi.SSID(i));
+ } while ( u8g2.nextPage() );
+}
+ }
+ }
+
+
+
+//////////////////////////////
+void displayip()
+{
+ u8g2.firstPage();
+ do {
+
+ u8g2.setFont(u8g2_font_lubI12_te); // 15 high font
+ //u8g2.setFont(u8g2_font_maniac_tf);dual outline font //u8g2_font_inr33_t_cyrillic);//u8g2_font_maniac_tf//u8g2_font_maniac_tf//u8g2_font_freedoomr25_tn//u8g2_font_7Segments_26x42_mn
+ u8g2.setCursor(0,24);
+ String IPaddress = WiFi.localIP().toString();
+ u8g2.print (IPaddress);
+ } while ( u8g2.nextPage() );
+}
+///////////////////////////////////////////////////
+
+
+
+
//Boot Logo, change if you dont like it
static const unsigned char PROGMEM logo16_glcd_bmp[] =
@@ -65,14 +125,20 @@ static const unsigned char PROGMEM logo16_glcd_bmp[] =
0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11111111 };
void setup() {
+Serial.begin(115200);
+u8g2.begin();
+u8g2.setFontMode(0); // enable transparent mode, which is faster
+
+displayip();
+wifiscan();
+
- Serial.begin(115200);
- display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
- display.clearDisplay();
- display.drawBitmap(0, 0, logo16_glcd_bmp, 64, 48, 1);
- display.display();
+ //display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
+ // display.clearDisplay();
+ //display.drawBitmap(0, 0, logo16_glcd_bmp, 64, 48, 1);
+ //display.display();
delay(2000);
- display.clearDisplay();
+ //display.clearDisplay();
Serial.println();
Serial.println();
@@ -95,11 +161,11 @@ void loop() {
WiFiClient client;
HTTPClient http;
- display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
- display.setTextSize(1);
- display.setTextColor(WHITE,BLACK);
+ // display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
+ // display.setTextSize(1);
+ // display.setTextColor(WHITE,BLACK);
- display.setCursor(0, 0);
+ // display.setCursor(0, 0);
Serial.print("[HTTP] begin...\n");
http.begin(client, "http://192.168.4.1");
@@ -122,53 +188,53 @@ void loop() {
char vbuff[5] = {0};
char socbuff[4] = {0};
char ovrbuff[4] = {0};
- display.setCursor(0, 0);
+// display.setCursor(0, 0);
stream->find("Voltage");
while(stream->find("TOTAL_VOLTAGE\">")){
stream->readBytesUntil('<',vbuff,5);
Serial.print(vbuff);
- display.print("VOL:");
- display.print(vbuff);
- display.print("v");
- display.println();
- display.display();
+ // display.print("VOL:");
+ // display.print(vbuff);
+ // display.print("v");
+ // display.println();
+ // display.display();
stream->find("BMS_SOC\">");
stream->readBytesUntil('<',socbuff,4);
- display.println();
- display.print("BMS:");
+ // display.println();
+ // display.print("BMS:");
Serial.println();
Serial.print(socbuff);
- display.print(socbuff);
- display.println();
- display.display();
+ // display.print(socbuff);
+ // display.println();
+ // display.display();
stream->find("OVERRIDDEN_SOC\">");
stream->readBytesUntil('<',ovrbuff,4);
Serial.println();
Serial.print(ovrbuff);
- display.println();
- display.print("OVR:");
- display.print(ovrbuff);
- display.println();
- display.display();
- display.print(" ");
- display.display();
- display.setCursor(0, 41);
+ // display.println();
+ // display.print("OVR:");
+ // display.print(ovrbuff);
+ // display.println();
+ // display.display();
+ // display.print(" ");
+ // display.display();
+ // display.setCursor(0, 41);
delay(5 * 1000);
- display.print("REFRESH");
- display.display();
+ // display.print("REFRESH");
+ // display.display();
Serial.print("REFRESH");
delay(1 * 1000);
- display.print(".");
+ // display.print(".");
Serial.print(".");
- display.display();
+ // display.display();
delay(1 * 1000);
- display.print(".");
+ // display.print(".");
Serial.print(".");
- display.display();
+ // display.display();
delay(1 * 1000);
- display.print(".");
+ // display.print(".");
Serial.print(".");
- display.display();
+ // display.display();
delay(1 * 1000);
return;
}
@@ -185,8 +251,10 @@ void loop() {
}
Serial.print("RETRYING...");
- display.setCursor(0, 41);
- display.print("RETRYING ");
- display.display();
+ // display.setCursor(0, 41);
+ // display.print("RETRYING ");
+ // display.display();
delay(30 * 1000);
}
+
+
From 435c6516b197b24775ebce34d6677e6d979cf4a8 Mon Sep 17 00:00:00 2001
From: tonyt321
Date: Thu, 8 Sep 2022 19:25:04 +0000
Subject: [PATCH 10/11] changed stuff
---
owie-watcher.ino | 108 +++++++++++++++++++++--------------------------
1 file changed, 47 insertions(+), 61 deletions(-)
diff --git a/owie-watcher.ino b/owie-watcher.ino
index 461270a..7d5cc1e 100644
--- a/owie-watcher.ino
+++ b/owie-watcher.ino
@@ -12,10 +12,20 @@ ESP8266WiFiMulti WiFiMulti;
char* ssid = "Owie-8282";
char* password = "";
+String ssid1 = "null";
+String ssid2 = "null";
+String ssid3 = "null";
+String ssid4 = "null";
+
// uncomment the one that works for your OLED if none check the U8G2 example codes
// R is the rotation change to pick direction R0,R1,R2,R3
//U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R2, /* reset=*/ U8X8_PIN_NONE); //1.3 inch 128x64 generic
-U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ D4); //2.42 inch 128x64 generic
+U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R2, /* reset=*/ D4); //2.42 inch 128x64 generic
+
+
+// searches for the string sfind in the string str
+// returns 1 if string found
+// returns 0 if string not found
@@ -26,6 +36,13 @@ void wifiscan()
Serial.println("scan done");
if (n == 0) {
Serial.println("no networks found");
+ u8g2.firstPage();
+ do {
+ u8g2.setFont(u8g2_font_lubI12_te); // 15 high font
+ u8g2.setCursor(0,24);
+ u8g2.print ("no networks found");
+ } while ( u8g2.nextPage() );
+
} else {
Serial.print(n);
Serial.println(" networks found");
@@ -38,19 +55,42 @@ void wifiscan()
Serial.print(WiFi.RSSI(i));
Serial.print(" encrypt = ");
Serial.println(WiFi.encryptionType(i));
- delay(5000);
+
- u8g2.firstPage();
- do {
+
+if (WiFi.encryptionType(i) == 7) {
+
+ u8g2.firstPage();do {
u8g2.setFont(u8g2_font_lubI12_te); // 15 high font
//u8g2.setFont(u8g2_font_maniac_tf);dual outline font //u8g2_font_inr33_t_cyrillic);//u8g2_font_maniac_tf//u8g2_font_maniac_tf//u8g2_font_freedoomr25_tn//u8g2_font_7Segments_26x42_mn
u8g2.setCursor(0,24);
u8g2.print (WiFi.SSID(i));
} while ( u8g2.nextPage() );
+
+
+ if (ssid1 == "null"){ssid1 = WiFi.SSID(i);
+ Serial.print(" ssid1: "); Serial.println(WiFi.SSID(i)); goto wifilist;
}
- }
+ if (ssid2 == "null"){ssid2 = WiFi.SSID(i);
+ Serial.print(" ssid2: "); Serial.println(WiFi.SSID(i)); goto wifilist;
+}
+ if (ssid3 == "null"){ssid3 = WiFi.SSID(i);
+ Serial.print(" ssid3: "); Serial.println(WiFi.SSID(i)); goto wifilist;
+}
+ if (ssid4 == "null"){ssid4 = WiFi.SSID(i);
+ Serial.print(" ssid4: "); Serial.println(WiFi.SSID(i)); goto wifilist;
+}
+
+ delay(2000);
+
+} // end of wifi list that has no password
+ wifilist: {}
+
}
+}
+}//end of wifi scan
+
@@ -72,58 +112,6 @@ void displayip()
-
-//Boot Logo, change if you dont like it
-static const unsigned char PROGMEM logo16_glcd_bmp[] =
-{ 0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11111111,
- 0b11000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000011,
- 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
- 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
- 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
- 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
- 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
- 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
- 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
- 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
- 0b10000000, 0b00000000, 0b01000000, 0b00000000, 0b00000000, 0b10000001, 0b00000000, 0b00000001,
- 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b10000001, 0b00000000, 0b00000001,
- 0b10000110, 0b01001001, 0b01001100, 0b00010010, 0b01011101, 0b11011001, 0b01000110, 0b01010001,
- 0b10001001, 0b01010101, 0b01010010, 0b00010101, 0b01000010, 0b10100101, 0b10101001, 0b01100001,
- 0b10001001, 0b01010101, 0b01011110, 0b00010101, 0b01001110, 0b10100001, 0b00101111, 0b01000001,
- 0b10001001, 0b01010101, 0b01010000, 0b00010101, 0b01010010, 0b10100001, 0b00101000, 0b01000001,
- 0b10001001, 0b01010101, 0b01010010, 0b00010101, 0b01010010, 0b10100101, 0b00101001, 0b01000001,
- 0b10000110, 0b00100010, 0b01001100, 0b00001000, 0b10001110, 0b11011001, 0b00100110, 0b01000001,
- 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
- 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
- 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
- 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
- 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
- 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
- 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
- 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
- 0b10000000, 0b00000000, 0b00000000, 0b00000011, 0b11000000, 0b00000000, 0b00000000, 0b00000001,
- 0b10000000, 0b00000000, 0b00000000, 0b00001111, 0b11110000, 0b00000000, 0b00000000, 0b00000001,
- 0b10000000, 0b00000000, 0b00000000, 0b00011100, 0b00111000, 0b00000000, 0b00000000, 0b00000001,
- 0b10000000, 0b00000000, 0b00000000, 0b00111000, 0b00011100, 0b00000000, 0b00000000, 0b00000001,
- 0b10000000, 0b00000000, 0b00000000, 0b00110000, 0b00001100, 0b00000000, 0b00000000, 0b00000001,
- 0b10000000, 0b00011111, 0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11111000, 0b00000001,
- 0b10000000, 0b00001000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00010000, 0b00000001,
- 0b10000000, 0b00000100, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00100000, 0b00000001,
- 0b10000000, 0b00000011, 0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11000000, 0b00000001,
- 0b10000000, 0b00000000, 0b00000000, 0b00110000, 0b00001100, 0b00000000, 0b00000000, 0b00000001,
- 0b10000000, 0b00000000, 0b00000000, 0b00111000, 0b00011100, 0b00000000, 0b00000000, 0b00000001,
- 0b10000000, 0b00000000, 0b00000000, 0b00011100, 0b00111000, 0b00000000, 0b00000000, 0b00000001,
- 0b10000000, 0b00000000, 0b00000000, 0b00001111, 0b11110000, 0b00000000, 0b00000000, 0b00000001,
- 0b10000000, 0b00000000, 0b00000000, 0b00000011, 0b11000000, 0b00000000, 0b00000000, 0b00000001,
- 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
- 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
- 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
- 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
- 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
- 0b10000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000001,
- 0b11000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000011,
- 0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11111111, 0b11111111 };
-
void setup() {
Serial.begin(115200);
u8g2.begin();
@@ -151,7 +139,7 @@ wifiscan();
}
WiFi.mode(WIFI_STA);
- WiFiMulti.addAP(ssid,password);
+ WiFiMulti.addAP(ssid1,password);
}
void loop() {
@@ -255,6 +243,4 @@ void loop() {
// display.print("RETRYING ");
// display.display();
delay(30 * 1000);
-}
-
-
+}
\ No newline at end of file
From 3a546c007e370b0e60202f1712eedb3e1cd48607 Mon Sep 17 00:00:00 2001
From: tonyt321
Date: Fri, 30 Sep 2022 01:40:38 +0000
Subject: [PATCH 11/11] https://github.com/tonyt321/esp32-base/tree/main
---
.gitpod.yml | 2 +-
Boot.gif | Bin 7894612 -> 0 bytes
lib/bms/Adafruit_NeoPixel.cpp | 3440 ---------------------------------
lib/bms/Adafruit_NeoPixel.h | 410 ----
lib/bms/desktop.ini | 2 -
lib/bms/esp.c | 178 --
lib/bms/esp8266.c | 86 -
owie-watcher-logo_EXAMPLE.bmp | Bin 448 -> 0 bytes
owie-watcher.ino | 246 ---
platformio.ini | 39 +
proto/settings.options | 14 -
proto/settings.proto | 26 -
src/EEPROM_Rotate.cpp | 397 ----
src/EEPROM_Rotate.h | 84 -
src/owie-watcher.ino | 221 +--
15 files changed, 79 insertions(+), 5066 deletions(-)
delete mode 100644 Boot.gif
delete mode 100644 lib/bms/Adafruit_NeoPixel.cpp
delete mode 100644 lib/bms/Adafruit_NeoPixel.h
delete mode 100644 lib/bms/esp.c
delete mode 100644 lib/bms/esp8266.c
delete mode 100644 owie-watcher-logo_EXAMPLE.bmp
delete mode 100644 owie-watcher.ino
delete mode 100644 proto/settings.options
delete mode 100644 proto/settings.proto
delete mode 100644 src/EEPROM_Rotate.cpp
delete mode 100644 src/EEPROM_Rotate.h
diff --git a/.gitpod.yml b/.gitpod.yml
index 92c7b82..dd4cc5d 100644
--- a/.gitpod.yml
+++ b/.gitpod.yml
@@ -5,7 +5,7 @@ vscode:
tasks:
- before: platformio upgrade
- init: platformio test -e native
- - command: platformio run -e d1_mini_lite_clone
+ - command: platformio run -e lolin32
image:
file: .gitpod.Dockerfile
diff --git a/Boot.gif b/Boot.gif
deleted file mode 100644
index 9fac7e4907ee1a4fb1b09bd6367627db22b0a3de..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 7894612
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