Working with QR code config, very memory consuming

This commit is contained in:
Djuri Baars 2023-11-10 13:02:05 +01:00
parent 8bad5ebafa
commit f611d2f5f8
13 changed files with 1659 additions and 70 deletions

1028
lib/qrcode/qrcodegen.c Normal file

File diff suppressed because it is too large Load diff

385
lib/qrcode/qrcodegen.h Normal file
View file

@ -0,0 +1,385 @@
/*
* QR Code generator library (C)
*
* Copyright (c) Project Nayuki. (MIT License)
* https://www.nayuki.io/page/qr-code-generator-library
*
* 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.
*/
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/*
* This library creates QR Code symbols, which is a type of two-dimension barcode.
* Invented by Denso Wave and described in the ISO/IEC 18004 standard.
* A QR Code structure is an immutable square grid of dark and light cells.
* The library provides functions to create a QR Code from text or binary data.
* The library covers the QR Code Model 2 specification, supporting all versions (sizes)
* from 1 to 40, all 4 error correction levels, and 4 character encoding modes.
*
* Ways to create a QR Code object:
* - High level: Take the payload data and call qrcodegen_encodeText() or qrcodegen_encodeBinary().
* - Low level: Custom-make the list of segments and call
* qrcodegen_encodeSegments() or qrcodegen_encodeSegmentsAdvanced().
* (Note that all ways require supplying the desired error correction level and various byte buffers.)
*/
/*---- Enum and struct types----*/
/*
* The error correction level in a QR Code symbol.
*/
enum qrcodegen_Ecc {
// Must be declared in ascending order of error protection
// so that an internal qrcodegen function works properly
qrcodegen_Ecc_LOW = 0 , // The QR Code can tolerate about 7% erroneous codewords
qrcodegen_Ecc_MEDIUM , // The QR Code can tolerate about 15% erroneous codewords
qrcodegen_Ecc_QUARTILE, // The QR Code can tolerate about 25% erroneous codewords
qrcodegen_Ecc_HIGH , // The QR Code can tolerate about 30% erroneous codewords
};
/*
* The mask pattern used in a QR Code symbol.
*/
enum qrcodegen_Mask {
// A special value to tell the QR Code encoder to
// automatically select an appropriate mask pattern
qrcodegen_Mask_AUTO = -1,
// The eight actual mask patterns
qrcodegen_Mask_0 = 0,
qrcodegen_Mask_1,
qrcodegen_Mask_2,
qrcodegen_Mask_3,
qrcodegen_Mask_4,
qrcodegen_Mask_5,
qrcodegen_Mask_6,
qrcodegen_Mask_7,
};
/*
* Describes how a segment's data bits are interpreted.
*/
enum qrcodegen_Mode {
qrcodegen_Mode_NUMERIC = 0x1,
qrcodegen_Mode_ALPHANUMERIC = 0x2,
qrcodegen_Mode_BYTE = 0x4,
qrcodegen_Mode_KANJI = 0x8,
qrcodegen_Mode_ECI = 0x7,
};
/*
* A segment of character/binary/control data in a QR Code symbol.
* The mid-level way to create a segment is to take the payload data
* and call a factory function such as qrcodegen_makeNumeric().
* The low-level way to create a segment is to custom-make the bit buffer
* and initialize a qrcodegen_Segment struct with appropriate values.
* Even in the most favorable conditions, a QR Code can only hold 7089 characters of data.
* Any segment longer than this is meaningless for the purpose of generating QR Codes.
* Moreover, the maximum allowed bit length is 32767 because
* the largest QR Code (version 40) has 31329 modules.
*/
struct qrcodegen_Segment {
// The mode indicator of this segment.
enum qrcodegen_Mode mode;
// The length of this segment's unencoded data. Measured in characters for
// numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode.
// Always zero or positive. Not the same as the data's bit length.
int numChars;
// The data bits of this segment, packed in bitwise big endian.
// Can be null if the bit length is zero.
uint8_t *data;
// The number of valid data bits used in the buffer. Requires
// 0 <= bitLength <= 32767, and bitLength <= (capacity of data array) * 8.
// The character count (numChars) must agree with the mode and the bit buffer length.
int bitLength;
};
/*---- Macro constants and functions ----*/
#define qrcodegen_VERSION_MIN 1 // The minimum version number supported in the QR Code Model 2 standard
#define qrcodegen_VERSION_MAX 40 // The maximum version number supported in the QR Code Model 2 standard
// Calculates the number of bytes needed to store any QR Code up to and including the given version number,
// as a compile-time constant. For example, 'uint8_t buffer[qrcodegen_BUFFER_LEN_FOR_VERSION(25)];'
// can store any single QR Code from version 1 to 25 (inclusive). The result fits in an int (or int16).
// Requires qrcodegen_VERSION_MIN <= n <= qrcodegen_VERSION_MAX.
#define qrcodegen_BUFFER_LEN_FOR_VERSION(n) ((((n) * 4 + 17) * ((n) * 4 + 17) + 7) / 8 + 1)
// The worst-case number of bytes needed to store one QR Code, up to and including
// version 40. This value equals 3918, which is just under 4 kilobytes.
// Use this more convenient value to avoid calculating tighter memory bounds for buffers.
#define qrcodegen_BUFFER_LEN_MAX qrcodegen_BUFFER_LEN_FOR_VERSION(qrcodegen_VERSION_MAX)
/*---- Functions (high level) to generate QR Codes ----*/
/*
* Encodes the given text string to a QR Code, returning true if successful.
* If the data is too long to fit in any version in the given range
* at the given ECC level, then false is returned.
*
* The input text must be encoded in UTF-8 and contain no NULs.
* Requires 1 <= minVersion <= maxVersion <= 40.
*
* The smallest possible QR Code version within the given range is automatically
* chosen for the output. Iff boostEcl is true, then the ECC level of the result
* may be higher than the ecl argument if it can be done without increasing the
* version. The mask is either between qrcodegen_Mask_0 to 7 to force that mask, or
* qrcodegen_Mask_AUTO to automatically choose an appropriate mask (which may be slow).
*
* About the arrays, letting len = qrcodegen_BUFFER_LEN_FOR_VERSION(maxVersion):
* - Before calling the function:
* - The array ranges tempBuffer[0 : len] and qrcode[0 : len] must allow
* reading and writing; hence each array must have a length of at least len.
* - The two ranges must not overlap (aliasing).
* - The initial state of both ranges can be uninitialized
* because the function always writes before reading.
* - After the function returns:
* - Both ranges have no guarantee on which elements are initialized and what values are stored.
* - tempBuffer contains no useful data and should be treated as entirely uninitialized.
* - If successful, qrcode can be passed into qrcodegen_getSize() and qrcodegen_getModule().
*
* If successful, the resulting QR Code may use numeric,
* alphanumeric, or byte mode to encode the text.
*
* In the most optimistic case, a QR Code at version 40 with low ECC
* can hold any UTF-8 string up to 2953 bytes, or any alphanumeric string
* up to 4296 characters, or any digit string up to 7089 characters.
* These numbers represent the hard upper limit of the QR Code standard.
*
* Please consult the QR Code specification for information on
* data capacities per version, ECC level, and text encoding mode.
*/
bool qrcodegen_encodeText(const char *text, uint8_t tempBuffer[], uint8_t qrcode[],
enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl);
/*
* Encodes the given binary data to a QR Code, returning true if successful.
* If the data is too long to fit in any version in the given range
* at the given ECC level, then false is returned.
*
* Requires 1 <= minVersion <= maxVersion <= 40.
*
* The smallest possible QR Code version within the given range is automatically
* chosen for the output. Iff boostEcl is true, then the ECC level of the result
* may be higher than the ecl argument if it can be done without increasing the
* version. The mask is either between qrcodegen_Mask_0 to 7 to force that mask, or
* qrcodegen_Mask_AUTO to automatically choose an appropriate mask (which may be slow).
*
* About the arrays, letting len = qrcodegen_BUFFER_LEN_FOR_VERSION(maxVersion):
* - Before calling the function:
* - The array ranges dataAndTemp[0 : len] and qrcode[0 : len] must allow
* reading and writing; hence each array must have a length of at least len.
* - The two ranges must not overlap (aliasing).
* - The input array range dataAndTemp[0 : dataLen] should normally be
* valid UTF-8 text, but is not required by the QR Code standard.
* - The initial state of dataAndTemp[dataLen : len] and qrcode[0 : len]
* can be uninitialized because the function always writes before reading.
* - After the function returns:
* - Both ranges have no guarantee on which elements are initialized and what values are stored.
* - dataAndTemp contains no useful data and should be treated as entirely uninitialized.
* - If successful, qrcode can be passed into qrcodegen_getSize() and qrcodegen_getModule().
*
* If successful, the resulting QR Code will use byte mode to encode the data.
*
* In the most optimistic case, a QR Code at version 40 with low ECC can hold any byte
* sequence up to length 2953. This is the hard upper limit of the QR Code standard.
*
* Please consult the QR Code specification for information on
* data capacities per version, ECC level, and text encoding mode.
*/
bool qrcodegen_encodeBinary(uint8_t dataAndTemp[], size_t dataLen, uint8_t qrcode[],
enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl);
/*---- Functions (low level) to generate QR Codes ----*/
/*
* Encodes the given segments to a QR Code, returning true if successful.
* If the data is too long to fit in any version at the given ECC level,
* then false is returned.
*
* The smallest possible QR Code version is automatically chosen for
* the output. The ECC level of the result may be higher than the
* ecl argument if it can be done without increasing the version.
*
* About the byte arrays, letting len = qrcodegen_BUFFER_LEN_FOR_VERSION(qrcodegen_VERSION_MAX):
* - Before calling the function:
* - The array ranges tempBuffer[0 : len] and qrcode[0 : len] must allow
* reading and writing; hence each array must have a length of at least len.
* - The two ranges must not overlap (aliasing).
* - The initial state of both ranges can be uninitialized
* because the function always writes before reading.
* - The input array segs can contain segments whose data buffers overlap with tempBuffer.
* - After the function returns:
* - Both ranges have no guarantee on which elements are initialized and what values are stored.
* - tempBuffer contains no useful data and should be treated as entirely uninitialized.
* - Any segment whose data buffer overlaps with tempBuffer[0 : len]
* must be treated as having invalid values in that array.
* - If successful, qrcode can be passed into qrcodegen_getSize() and qrcodegen_getModule().
*
* Please consult the QR Code specification for information on
* data capacities per version, ECC level, and text encoding mode.
*
* This function allows the user to create a custom sequence of segments that switches
* between modes (such as alphanumeric and byte) to encode text in less space.
* This is a low-level API; the high-level API is qrcodegen_encodeText() and qrcodegen_encodeBinary().
*/
bool qrcodegen_encodeSegments(const struct qrcodegen_Segment segs[], size_t len,
enum qrcodegen_Ecc ecl, uint8_t tempBuffer[], uint8_t qrcode[]);
/*
* Encodes the given segments to a QR Code, returning true if successful.
* If the data is too long to fit in any version in the given range
* at the given ECC level, then false is returned.
*
* Requires 1 <= minVersion <= maxVersion <= 40.
*
* The smallest possible QR Code version within the given range is automatically
* chosen for the output. Iff boostEcl is true, then the ECC level of the result
* may be higher than the ecl argument if it can be done without increasing the
* version. The mask is either between qrcodegen_Mask_0 to 7 to force that mask, or
* qrcodegen_Mask_AUTO to automatically choose an appropriate mask (which may be slow).
*
* About the byte arrays, letting len = qrcodegen_BUFFER_LEN_FOR_VERSION(qrcodegen_VERSION_MAX):
* - Before calling the function:
* - The array ranges tempBuffer[0 : len] and qrcode[0 : len] must allow
* reading and writing; hence each array must have a length of at least len.
* - The two ranges must not overlap (aliasing).
* - The initial state of both ranges can be uninitialized
* because the function always writes before reading.
* - The input array segs can contain segments whose data buffers overlap with tempBuffer.
* - After the function returns:
* - Both ranges have no guarantee on which elements are initialized and what values are stored.
* - tempBuffer contains no useful data and should be treated as entirely uninitialized.
* - Any segment whose data buffer overlaps with tempBuffer[0 : len]
* must be treated as having invalid values in that array.
* - If successful, qrcode can be passed into qrcodegen_getSize() and qrcodegen_getModule().
*
* Please consult the QR Code specification for information on
* data capacities per version, ECC level, and text encoding mode.
*
* This function allows the user to create a custom sequence of segments that switches
* between modes (such as alphanumeric and byte) to encode text in less space.
* This is a low-level API; the high-level API is qrcodegen_encodeText() and qrcodegen_encodeBinary().
*/
bool qrcodegen_encodeSegmentsAdvanced(const struct qrcodegen_Segment segs[], size_t len, enum qrcodegen_Ecc ecl,
int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl, uint8_t tempBuffer[], uint8_t qrcode[]);
/*
* Tests whether the given string can be encoded as a segment in numeric mode.
* A string is encodable iff each character is in the range 0 to 9.
*/
bool qrcodegen_isNumeric(const char *text);
/*
* Tests whether the given string can be encoded as a segment in alphanumeric mode.
* A string is encodable iff each character is in the following set: 0 to 9, A to Z
* (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon.
*/
bool qrcodegen_isAlphanumeric(const char *text);
/*
* Returns the number of bytes (uint8_t) needed for the data buffer of a segment
* containing the given number of characters using the given mode. Notes:
* - Returns SIZE_MAX on failure, i.e. numChars > INT16_MAX or the internal
* calculation of the number of needed bits exceeds INT16_MAX (i.e. 32767).
* - Otherwise, all valid results are in the range [0, ceil(INT16_MAX / 8)], i.e. at most 4096.
* - It is okay for the user to allocate more bytes for the buffer than needed.
* - For byte mode, numChars measures the number of bytes, not Unicode code points.
* - For ECI mode, numChars must be 0, and the worst-case number of bytes is returned.
* An actual ECI segment can have shorter data. For non-ECI modes, the result is exact.
*/
size_t qrcodegen_calcSegmentBufferSize(enum qrcodegen_Mode mode, size_t numChars);
/*
* Returns a segment representing the given binary data encoded in
* byte mode. All input byte arrays are acceptable. Any text string
* can be converted to UTF-8 bytes and encoded as a byte mode segment.
*/
struct qrcodegen_Segment qrcodegen_makeBytes(const uint8_t data[], size_t len, uint8_t buf[]);
/*
* Returns a segment representing the given string of decimal digits encoded in numeric mode.
*/
struct qrcodegen_Segment qrcodegen_makeNumeric(const char *digits, uint8_t buf[]);
/*
* Returns a segment representing the given text string encoded in alphanumeric mode.
* The characters allowed are: 0 to 9, A to Z (uppercase only), space,
* dollar, percent, asterisk, plus, hyphen, period, slash, colon.
*/
struct qrcodegen_Segment qrcodegen_makeAlphanumeric(const char *text, uint8_t buf[]);
/*
* Returns a segment representing an Extended Channel Interpretation
* (ECI) designator with the given assignment value.
*/
struct qrcodegen_Segment qrcodegen_makeEci(long assignVal, uint8_t buf[]);
/*---- Functions to extract raw data from QR Codes ----*/
/*
* Returns the side length of the given QR Code, assuming that encoding succeeded.
* The result is in the range [21, 177]. Note that the length of the array buffer
* is related to the side length - every 'uint8_t qrcode[]' must have length at least
* qrcodegen_BUFFER_LEN_FOR_VERSION(version), which equals ceil(size^2 / 8 + 1).
*/
int qrcodegen_getSize(const uint8_t qrcode[]);
/*
* Returns the color of the module (pixel) at the given coordinates, which is false
* for light or true for dark. The top left corner has the coordinates (x=0, y=0).
* If the given coordinates are out of bounds, then false (light) is returned.
*/
bool qrcodegen_getModule(const uint8_t qrcode[], int x, int y);
#ifdef __cplusplus
}
#endif

View file

@ -38,6 +38,7 @@ lib_deps =
adafruit/Adafruit NeoPixel@^1.11.0 adafruit/Adafruit NeoPixel@^1.11.0
https://github.com/dsbaars/universal_pin https://github.com/dsbaars/universal_pin
https://github.com/dsbaars/GxEPD2#universal_pin https://github.com/dsbaars/GxEPD2#universal_pin
https://github.com/tzapu/WiFiManager.git#v2.0.16-rc.2
[env:lolin_s3_mini] [env:lolin_s3_mini]
extends = btclock_base extends = btclock_base

View file

@ -44,3 +44,4 @@ CONFIG_RTC_CLK_CAL_CYCLES=576
CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=y CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=y
CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=3120 CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=3120
CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=n
CONFIG_SPIRAM_CACHE_WORKAROUND=y

View file

@ -1,7 +1,5 @@
#include "config.hpp" #include "config.hpp"
#define MAX_ATTEMPTS_WIFI_CONNECTION 20 #define MAX_ATTEMPTS_WIFI_CONNECTION 20
Preferences preferences; Preferences preferences;
@ -14,6 +12,8 @@ void setup()
setupHardware(); setupHardware();
if (mcp.digitalRead(3) == LOW) if (mcp.digitalRead(3) == LOW)
{ {
preferences.putBool("wifiConfigured", false);
WiFi.eraseAP(); WiFi.eraseAP();
blinkDelay(100, 3); blinkDelay(100, 3);
} }
@ -29,43 +29,90 @@ void setup()
setupTasks(); setupTasks();
setupTimers(); setupTimers();
setupWebsocketClients();
xTaskCreate(setupWebsocketClients, "setupWebsocketClients", 4096, NULL, tskIDLE_PRIORITY, NULL);
setupButtonTask(); setupButtonTask();
} }
void tryImprovSetup() void tryImprovSetup()
{ {
// if (mcp.digitalRead(3) == LOW) if (!preferences.getBool("wifiConfigured", false))
// {
// WiFi.persistent(false);
// blinkDelay(100, 3);
// }
// else
{ {
WiFi.begin(); queueLedEffect(LED_EFFECT_WIFI_WAIT_FOR_CONFIG);
}
uint8_t x_buffer[16]; uint8_t x_buffer[16];
uint8_t x_position = 0; uint8_t x_position = 0;
while (WiFi.status() != WL_CONNECTED) // Hold second button to start QR code wifi config
{ if (!mcp.digitalRead(2) == LOW)
if (Serial.available() > 0)
{ {
uint8_t b = Serial.read(); WiFiManager wm;
if (parse_improv_serial_byte(x_position, b, x_buffer, onImprovCommandCallback, onImprovErrorCallback)) byte mac[6];
WiFi.macAddress(mac);
String softAP_SSID = String("BTClock" + String(mac[5], 16) + String(mac[1], 16));
WiFi.setHostname(softAP_SSID.c_str());
String softAP_password = base64::encode(String(mac[2], 16) + String(mac[4], 16) + String(mac[5], 16) + String(mac[1], 16)).substring(2, 10);
wm.setConfigPortalTimeout(preferences.getUInt("wpTimeout", 600));
wm.setWiFiAutoReconnect(true);
wm.setAPCallback([&](WiFiManager *wifiManager)
{
Serial.printf("Entered config mode:ip=%s, ssid='%s', pass='%s'\n",
WiFi.softAPIP().toString().c_str(),
wifiManager->getConfigPortalSSID().c_str(),
softAP_password.c_str());
// vTaskDelay(pdMS_TO_TICKS(1000));
delay(1000);
const String qrText = "qrWIFI:S:" + wifiManager->getConfigPortalSSID() + ";T:WPA;P:" + softAP_password.c_str() + ";;";
const String explainText = "*SSID: *\r\n" + wifiManager->getConfigPortalSSID() + "\r\n\r\n*Password:*\r\n" + softAP_password;
std::array<String, NUM_SCREENS> epdContent = {"Welcome!", "", "To setup\r\nscan QR or\r\nconnect\r\nmanually", "", explainText, "", qrText};
setEpdContent(epdContent);
});
wm.setSaveConfigCallback([]() {
preferences.putBool("wifiConfigured", true);
});
bool ac = wm.autoConnect(softAP_SSID.c_str(), softAP_password.c_str());
wm.server->stop();
}
else
{
while (WiFi.status() != WL_CONNECTED)
{ {
x_buffer[x_position++] = b; if (Serial.available() > 0)
} {
else uint8_t b = Serial.read();
{
x_position = 0; if (parse_improv_serial_byte(x_position, b, x_buffer, onImprovCommandCallback, onImprovErrorCallback))
{
x_buffer[x_position++] = b;
}
else
{
x_position = 0;
}
}
esp_task_wdt_reset();
} }
} }
esp_task_wdt_reset();
} }
else
{
WiFi.begin();
while (WiFi.status() != WL_CONNECTED)
{
vTaskDelay(pdMS_TO_TICKS(400));
}
}
queueLedEffect(LED_EFFECT_WIFI_CONNECT_SUCCESS);
} }
void setupTime() void setupTime()
@ -90,17 +137,19 @@ void setupPreferences()
setFgColor(preferences.getUInt("fgColor", DEFAULT_FG_COLOR)); setFgColor(preferences.getUInt("fgColor", DEFAULT_FG_COLOR));
setBgColor(preferences.getUInt("bgColor", DEFAULT_BG_COLOR)); setBgColor(preferences.getUInt("bgColor", DEFAULT_BG_COLOR));
screenNameMap[SCREEN_BLOCK_HEIGHT] = "Block Height"; screenNameMap[SCREEN_BLOCK_HEIGHT] = "Block Height";
screenNameMap[SCREEN_MSCW_TIME] = "Sats per dollar"; screenNameMap[SCREEN_MSCW_TIME] = "Sats per dollar";
screenNameMap[SCREEN_BTC_TICKER] = "Ticker"; screenNameMap[SCREEN_BTC_TICKER] = "Ticker";
screenNameMap[SCREEN_TIME] = "Time"; screenNameMap[SCREEN_TIME] = "Time";
screenNameMap[SCREEN_HALVING_COUNTDOWN] = "Halving countdown"; screenNameMap[SCREEN_HALVING_COUNTDOWN] = "Halving countdown";
} }
void setupWebsocketClients() void setupWebsocketClients(void *pvParameters)
{ {
setupBlockNotify(); setupBlockNotify();
setupPriceNotify(); setupPriceNotify();
vTaskDelete(NULL);
} }
void setupTimers() void setupTimers()
@ -112,22 +161,27 @@ void setupTimers()
void finishSetup() void finishSetup()
{ {
if (preferences.getBool("ledStatus", false)) { if (preferences.getBool("ledStatus", false))
{
setLights(preferences.getUInt("ledColor", 0xFFCC00)); setLights(preferences.getUInt("ledColor", 0xFFCC00));
} else { }
else
{
clearLeds(); clearLeds();
} }
} }
std::vector<std::string> getScreenNameMap() { std::vector<std::string> getScreenNameMap()
{
return screenNameMap; return screenNameMap;
} }
void setupHardware() void setupHardware()
{ {
setupLeds(); setupLeds();
WiFi.setHostname(getMyHostname().c_str());;
WiFi.setHostname(getMyHostname().c_str());
;
if (psramInit()) if (psramInit())
{ {
Serial.println(F("PSRAM is correctly initialized")); Serial.println(F("PSRAM is correctly initialized"));
@ -200,10 +254,9 @@ bool improv_connectWifi(std::string ssid, std::string password)
return true; return true;
} }
void onImprovErrorCallback(improv::Error err) void onImprovErrorCallback(improv::Error err)
{ {
blinkDelayColor(100, 1, 255,0,0); blinkDelayColor(100, 1, 255, 0, 0);
// pixels.setPixelColor(0, pixels.Color(255, 0, 0)); // pixels.setPixelColor(0, pixels.Color(255, 0, 0));
// pixels.setPixelColor(1, pixels.Color(255, 0, 0)); // pixels.setPixelColor(1, pixels.Color(255, 0, 0));
// pixels.setPixelColor(2, pixels.Color(255, 0, 0)); // pixels.setPixelColor(2, pixels.Color(255, 0, 0));
@ -254,14 +307,18 @@ bool onImprovCommandCallback(improv::ImprovCommand cmd)
} }
improv_set_state(improv::STATE_PROVISIONING); improv_set_state(improv::STATE_PROVISIONING);
queueLedEffect(LED_EFFECT_WIFI_CONNECTING);
if (improv_connectWifi(cmd.ssid, cmd.password)) if (improv_connectWifi(cmd.ssid, cmd.password))
{ {
blinkDelay(100, 3); queueLedEffect(LED_EFFECT_WIFI_CONNECT_SUCCESS);
// std::array<String, NUM_SCREENS> epdContent = {"S", "U", "C", "C", "E", "S", "S"}; // std::array<String, NUM_SCREENS> epdContent = {"S", "U", "C", "C", "E", "S", "S"};
// setEpdContent(epdContent); // setEpdContent(epdContent);
preferences.putBool("wifiConfigured", true);
improv_set_state(improv::STATE_PROVISIONED); improv_set_state(improv::STATE_PROVISIONED);
std::vector<uint8_t> data = improv::build_rpc_response(improv::WIFI_SETTINGS, getLocalUrl(), false); std::vector<uint8_t> data = improv::build_rpc_response(improv::WIFI_SETTINGS, getLocalUrl(), false);
improv_send_response(data); improv_send_response(data);
@ -269,6 +326,8 @@ bool onImprovCommandCallback(improv::ImprovCommand cmd)
} }
else else
{ {
queueLedEffect(LED_EFFECT_WIFI_CONNECT_ERROR);
improv_set_state(improv::STATE_STOPPED); improv_set_state(improv::STATE_STOPPED);
improv_set_error(improv::Error::ERROR_UNABLE_TO_CONNECT); improv_set_error(improv::Error::ERROR_UNABLE_TO_CONNECT);
} }

View file

@ -1,15 +1,14 @@
#pragma once; #pragma once;
#include <WiFiManager.h>
#include "base64.h"
#include <WiFiClientSecure.h> #include <WiFiClientSecure.h>
#include <Preferences.h> #include <Preferences.h>
#include <Adafruit_MCP23X17.h> #include <Adafruit_MCP23X17.h>
#include "shared.hpp" #include "shared.hpp"
#include <esp_system.h>
#include <esp_netif.h>
#include <esp_sntp.h>
#include "epd.hpp" #include "epd.hpp"
#include "improv.hpp" #include "improv.hpp"
#include "hal/wdt_hal.h"
#include "esp_task_wdt.h" #include "esp_task_wdt.h"
#include <map> #include <map>
@ -21,6 +20,7 @@
#include "lib/button_handler.hpp" #include "lib/button_handler.hpp"
#include "lib/led_handler.hpp" #include "lib/led_handler.hpp"
#define NTP_SERVER "pool.ntp.org" #define NTP_SERVER "pool.ntp.org"
#define DEFAULT_MEMPOOL_INSTANCE "mempool.space" #define DEFAULT_MEMPOOL_INSTANCE "mempool.space"
#define TIME_OFFSET_SECONDS 3600 #define TIME_OFFSET_SECONDS 3600
@ -39,7 +39,7 @@
void setup(); void setup();
void setupTime(); void setupTime();
void setupPreferences(); void setupPreferences();
void setupWebsocketClients(); void setupWebsocketClients(void *pvParameters);
void setupHardware(); void setupHardware();
void tryImprovSetup(); void tryImprovSetup();
void setupTimers(); void setupTimers();

View file

@ -63,6 +63,8 @@ int bgColor = GxEPD_BLACK;
#define FONT_SMALL Antonio_SemiBold20pt7b #define FONT_SMALL Antonio_SemiBold20pt7b
#define FONT_BIG Antonio_SemiBold90pt7b #define FONT_BIG Antonio_SemiBold90pt7b
uint8_t qrcode[800];
void setupDisplays() void setupDisplays()
{ {
for (uint i = 0; i < NUM_SCREENS; i++) for (uint i = 0; i < NUM_SCREENS; i++)
@ -78,7 +80,7 @@ void setupDisplays()
int *taskParam = new int; int *taskParam = new int;
*taskParam = i; *taskParam = i;
xTaskCreate(updateDisplay, ("EpdUpd" + String(i)).c_str(), 4096, taskParam, tskIDLE_PRIORITY, &tasks[i]); // create task xTaskCreate(updateDisplay, ("EpdUpd" + String(i)).c_str(), 10000, taskParam, tskIDLE_PRIORITY, &tasks[i]); // create task
} }
xTaskCreate(taskEpd, "epd_task", 2048, NULL, tskIDLE_PRIORITY, &epdTaskHandle); xTaskCreate(taskEpd, "epd_task", 2048, NULL, tskIDLE_PRIORITY, &epdTaskHandle);
@ -92,10 +94,10 @@ void setupDisplays()
"K"}; "K"};
setEpdContent(epdContent); setEpdContent(epdContent);
// for (uint i = 0; i < NUM_SCREENS; i++) for (uint i = 0; i < NUM_SCREENS; i++)
// { {
// xTaskNotifyGive(tasks[i]); xTaskNotifyGive(tasks[i]);
// } }
} }
void taskEpd(void *pvParameters) void taskEpd(void *pvParameters)
@ -186,6 +188,14 @@ extern "C" void updateDisplay(void *pvParameters) noexcept
splitText(epdIndex, top, bottom, updatePartial); splitText(epdIndex, top, bottom, updatePartial);
#endif #endif
} }
else if (epdContent[epdIndex].startsWith(F("qr")))
{
renderQr(epdIndex, epdContent[epdIndex], updatePartial);
}
else if (epdContent[epdIndex].length() > 5)
{
renderText(epdIndex, epdContent[epdIndex], updatePartial);
}
else else
{ {
@ -223,7 +233,7 @@ void updateDisplayAlt(int epdIndex)
{ {
} }
void splitText(const uint dispNum, String top, String bottom, bool partial) void splitText(const uint dispNum, const String &top, const String &bottom, bool partial)
{ {
displays[dispNum].setRotation(2); displays[dispNum].setRotation(2);
displays[dispNum].setFont(&FONT_SMALL); displays[dispNum].setFont(&FONT_SMALL);
@ -384,3 +394,63 @@ std::array<String, NUM_SCREENS> getCurrentEpdContent()
// } // }
return currentEpdContent; return currentEpdContent;
} }
void renderText(const uint dispNum, const String &text, bool partial)
{
displays[dispNum].setRotation(2);
displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(), displays[dispNum].height());
displays[dispNum].fillScreen(GxEPD_WHITE);
displays[dispNum].setTextColor(GxEPD_BLACK);
displays[dispNum].setCursor(0, 50);
// displays[dispNum].setFont(&FreeSans9pt7b);
// std::regex pattern("/\*(.*)\*/");
std::stringstream ss;
ss.str(text.c_str());
std::string line;
while (std::getline(ss, line, '\n'))
{
if (line.rfind("*", 0) == 0)
{
line.erase(std::remove(line.begin(), line.end(), '*'), line.end());
displays[dispNum].setFont(&FreeSansBold9pt7b);
displays[dispNum].println(line.c_str());
}
else
{
displays[dispNum].setFont(&FreeSans9pt7b);
displays[dispNum].println(line.c_str());
}
}
displays[dispNum].display(partial);
}
void renderQr(const uint dispNum, const String &text, bool partial)
{
uint8_t tempBuffer[800];
bool ok = qrcodegen_encodeText(text.substring(2).c_str(), tempBuffer, qrcode, qrcodegen_Ecc_LOW,
qrcodegen_VERSION_MIN, qrcodegen_VERSION_MAX, qrcodegen_Mask_AUTO, true);
const int size = qrcodegen_getSize(qrcode);
const int padding = floor(float(displays[dispNum].width() - (size * 4)) / 2);
const int paddingY = floor(float(displays[dispNum].height() - (size * 4)) / 2);
displays[dispNum].setRotation(2);
displays[dispNum].setPartialWindow(0, 0, displays[dispNum].width(), displays[dispNum].height());
displays[dispNum].fillScreen(GxEPD_WHITE);
const int border = 0;
for (int y = -border; y < size * 4 + border; y++)
{
for (int x = -border; x < size * 4 + border; x++)
{
displays[dispNum].drawPixel(padding + x, paddingY + y, qrcodegen_getModule(qrcode, floor(float(x) / 4), floor(float(y) / 4)) ? GxEPD_BLACK : GxEPD_WHITE);
}
}
displays[dispNum].display(partial);
}

View file

@ -6,11 +6,16 @@
#include "shared.hpp" #include "shared.hpp"
#include "config.hpp" #include "config.hpp"
#include "fonts/fonts.hpp" #include "fonts/fonts.hpp"
#include <Fonts/FreeSansBold9pt7b.h>
#include <Fonts/FreeSans9pt7b.h>
#include <regex>
#include "qrcodegen.h"
void setupDisplays(); void setupDisplays();
void taskEpd(void *pvParameters); void taskEpd(void *pvParameters);
void splitText(const uint dispNum, String top, String bottom, bool partial); void splitText(const uint dispNum, const String& top, const String& bottom, bool partial);
void splitTextPaged(const uint dispNum, String top, String bottom, bool partial); void splitTextPaged(const uint dispNum, String top, String bottom, bool partial);
void showDigit(const uint dispNum, char chr, bool partial, const GFXfont *font); void showDigit(const uint dispNum, char chr, bool partial, const GFXfont *font);
@ -24,5 +29,8 @@ int getFgColor();
void setBgColor(int color); void setBgColor(int color);
void setFgColor(int color); void setFgColor(int color);
void renderText(const uint dispNum, const String& text, bool partial);
void renderQr(const uint dispNum, const String& text, bool partial);
void setEpdContent(std::array<String, NUM_SCREENS> newEpdContent); void setEpdContent(std::array<String, NUM_SCREENS> newEpdContent);
std::array<String, NUM_SCREENS> getCurrentEpdContent(); std::array<String, NUM_SCREENS> getCurrentEpdContent();

View file

@ -23,17 +23,40 @@ void ledTask(void *parameter)
switch (ledTaskParams) switch (ledTaskParams)
{ {
case LED_EFFECT_WIFI_CONNECT_ERROR:
case LED_FLASH_ERROR: case LED_FLASH_ERROR:
blinkDelayColor(250, 3, 255, 0, 0); blinkDelayColor(250, 3, 255, 0, 0);
break; break;
case LED_EFFECT_WIFI_CONNECT_SUCCESS:
case LED_FLASH_SUCCESS: case LED_FLASH_SUCCESS:
blinkDelayColor(250, 3, 0, 255, 0); blinkDelayColor(150, 3, 0, 255, 0);
break; break;
case LED_FLASH_UPDATE: case LED_FLASH_UPDATE:
break; break;
case LED_FLASH_BLOCK_NOTIFY: case LED_FLASH_BLOCK_NOTIFY:
blinkDelayTwoColor(250, 3, pixels.Color(224, 67, 0), pixels.Color(8, 2, 0)); blinkDelayTwoColor(250, 3, pixels.Color(224, 67, 0), pixels.Color(8, 2, 0));
break; break;
case LED_EFFECT_WIFI_WAIT_FOR_CONFIG:
blinkDelayTwoColor(100, 1, pixels.Color(8, 161, 236), pixels.Color(156, 225, 240));
break;
case LED_EFFECT_WIFI_CONNECTING:
for (int i = NEOPIXEL_COUNT; i >= 0; i--)
{
for (int j = NEOPIXEL_COUNT; j >= 0; j--)
{
if (j == i)
{
pixels.setPixelColor(i, pixels.Color(16, 197, 236));
}
else
{
pixels.setPixelColor(j, pixels.Color(0, 0, 0));
}
}
pixels.show();
vTaskDelay(pdMS_TO_TICKS(100));
}
break;
case LED_EFFECT_PAUSE_TIMER: case LED_EFFECT_PAUSE_TIMER:
for (int i = NEOPIXEL_COUNT; i >= 0; i--) for (int i = NEOPIXEL_COUNT; i >= 0; i--)
{ {
@ -59,7 +82,7 @@ void ledTask(void *parameter)
break; break;
case LED_EFFECT_START_TIMER: case LED_EFFECT_START_TIMER:
pixels.clear(); pixels.clear();
pixels.setPixelColor((NEOPIXEL_COUNT-1), pixels.Color(255, 0, 0)); pixels.setPixelColor((NEOPIXEL_COUNT - 1), pixels.Color(255, 0, 0));
pixels.show(); pixels.show();
delay(900); delay(900);
@ -101,18 +124,25 @@ void ledTask(void *parameter)
void setupLeds() void setupLeds()
{ {
pixels.begin(); pixels.begin();
pixels.setBrightness(preferences.getUInt("ledBrightness", 128)); if (preferences.getBool("ledTestOnPower", true))
pixels.setPixelColor(0, pixels.Color(255, 0, 0)); {
pixels.setPixelColor(1, pixels.Color(0, 255, 0)); pixels.setBrightness(preferences.getUInt("ledBrightness", 128));
pixels.setPixelColor(2, pixels.Color(0, 0, 255)); pixels.setPixelColor(0, pixels.Color(255, 0, 0));
pixels.setPixelColor(3, pixels.Color(255, 255, 255)); pixels.setPixelColor(1, pixels.Color(0, 255, 0));
pixels.setPixelColor(2, pixels.Color(0, 0, 255));
pixels.setPixelColor(3, pixels.Color(255, 255, 255));
}
else
{
pixels.clear();
}
pixels.show(); pixels.show();
setupLedTask(); setupLedTask();
} }
void setupLedTask() void setupLedTask()
{ {
ledTaskQueue = xQueueCreate(5, sizeof(char)); ledTaskQueue = xQueueCreate(2, sizeof(char));
xTaskCreate(ledTask, "LedTask", 2048, NULL, tskIDLE_PRIORITY, &ledTaskHandle); xTaskCreate(ledTask, "LedTask", 2048, NULL, tskIDLE_PRIORITY, &ledTaskHandle);
} }
@ -207,7 +237,6 @@ void setLights(uint32_t color)
pixels.show(); pixels.show();
} }
QueueHandle_t getLedTaskQueue() QueueHandle_t getLedTaskQueue()
{ {
return ledTaskQueue; return ledTaskQueue;

View file

@ -19,6 +19,10 @@ const int LED_FLASH_UPDATE = 2;
const int LED_FLASH_BLOCK_NOTIFY = 3; const int LED_FLASH_BLOCK_NOTIFY = 3;
const int LED_EFFECT_START_TIMER = 4; const int LED_EFFECT_START_TIMER = 4;
const int LED_EFFECT_PAUSE_TIMER = 5; const int LED_EFFECT_PAUSE_TIMER = 5;
const int LED_EFFECT_WIFI_WAIT_FOR_CONFIG = 100;
const int LED_EFFECT_WIFI_CONNECTING = 101;
const int LED_EFFECT_WIFI_CONNECT_ERROR = 102;
const int LED_EFFECT_WIFI_CONNECT_SUCCESS = 103;
extern TaskHandle_t ledTaskHandle; extern TaskHandle_t ledTaskHandle;

View file

@ -20,7 +20,7 @@ void setupWebserver()
} }
// send event with message "hello!", id current millis // send event with message "hello!", id current millis
// and set reconnect delay to 1 second // and set reconnect delay to 1 second
eventSourceUpdate(); client->send("welcome",NULL,millis(),1000);
}); });
server.addHandler(&events); server.addHandler(&events);
@ -58,15 +58,15 @@ void setupWebserver()
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*");
server.begin(); server.begin();
if (!MDNS.begin(getMyHostname())) // if (!MDNS.begin(getMyHostname()))
{ // {
Serial.println(F("Error setting up MDNS responder!")); // Serial.println(F("Error setting up MDNS responder!"));
while (1) // while (1)
{ // {
delay(1000); // delay(1000);
} // }
} // }
MDNS.addService("http", "tcp", 80); // MDNS.addService("http", "tcp", 80);
xTaskCreate(eventSourceTask, "eventSourceTask", 4096, NULL, tskIDLE_PRIORITY, &eventSourceTaskHandle); xTaskCreate(eventSourceTask, "eventSourceTask", 4096, NULL, tskIDLE_PRIORITY, &eventSourceTaskHandle);
} }

View file

@ -1,9 +1,9 @@
#pragma once #pragma once
#include "WebServer.h"
#include "ESPAsyncWebServer.h" #include "ESPAsyncWebServer.h"
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <LittleFS.h> #include <LittleFS.h>
#include <ESPmDNS.h> // #include <ESPmDNS.h>
#include "lib/block_notify.hpp" #include "lib/block_notify.hpp"
#include "lib/price_notify.hpp" #include "lib/price_notify.hpp"

View file

@ -1,4 +1,8 @@
#include "Arduino.h" #include "Arduino.h"
#include <WiFiManager.h>
#define WEBSERVER_H
#include "ESPAsyncWebServer.h"
#include "lib/config.hpp" #include "lib/config.hpp"
//char ptrTaskList[400]; //char ptrTaskList[400];