Compare commits

..

1 commit

Author SHA1 Message Date
e987e9afd5
Price notify refactor 2025-01-05 23:50:26 +01:00
34 changed files with 912 additions and 1438 deletions

2
data

@ -1 +1 @@
Subproject commit 8389ed8e36a9a1a7a39ca31f53324a0949aedb1d
Subproject commit 732dd260ea708841f0e15ee1ee64a3d5115cd475

View file

@ -4,6 +4,6 @@ dependencies:
source:
type: idf
version: 4.4.7
manifest_hash: 1d4ef353a86901733b106a1897b186dbf9fc091a4981f0560ea2f6899b7a3d44
manifest_hash: cd2f3ee15e776d949eb4ea4eddc8f39b30c2a7905050850eed01ab4928143cff
target: esp32s3
version: 1.0.0

View file

@ -1,721 +0,0 @@
#pragma once
#include <string_view>
#include <array>
#include <utility>
namespace timezone_data {
// Enum for unique timezone strings
enum class TimezoneValue : uint8_t {
plus000plus02_2M350_1M1050_3,
plus01_1,
plus02_2,
plus0330_330,
plus03_3,
plus0430_430,
plus04_4,
plus0530_530,
plus0545_545,
plus05_5,
plus0630_630,
plus06_6,
plus07_7,
plus0845_845,
plus08_8,
plus09_9,
plus1030_1030plus11_11M1010M410,
plus10_10,
plus11_11,
plus11_11plus12M1010M410_3,
plus1245_1245plus1345M950_245M410_345,
plus12_12,
plus13_13,
plus14_14,
_011,
_011plus00M350_0M1050_1,
_022,
_022_01M350__1M1050_0,
_033,
_033_02M320M1110,
_044,
_044_03M1010_0M340_0,
_044_03M916_24M416_24,
_055,
_066,
_066_05M916_22M416_22,
_077,
_088,
_0930930,
_099,
_1010,
_1111,
_1212,
ACST_930,
ACST_930ACDTM1010M410_3,
AEST_10,
AEST_10AEDTM1010M410_3,
AKST9AKDTM320M1110,
AST4,
AST4ADTM320M1110,
AWST_8,
CAT_2,
CET_1,
CET_1CESTM350M1050_3,
CST_8,
CST5CDTM320_0M1110_1,
CST6,
CST6CDTM320M1110,
ChST_10,
EAT_3,
EET_2,
EET_2EESTM344_50M1044_50,
EET_2EESTM350M1050_3,
EET_2EESTM350_0M1050_0,
EET_2EESTM350_3M1050_4,
EET_2EESTM455_0M1054_24,
EST5,
EST5EDTM320M1110,
GMT0,
GMT0BSTM350_1M1050,
HKT_8,
HST10,
HST10HDTM320M1110,
IST_1GMT0M1050M350_1,
IST_2IDTM344_26M1050,
IST_530,
JST_9,
KST_9,
MSK_3,
MST7,
MST7MDTM320M1110,
NST330NDTM320M1110,
NZST_12NZDTM950M410_3,
PKT_5,
PST_8,
PST8PDTM320M1110,
SAST_2,
SST11,
UTC0,
WAT_1,
WET0WESTM350_1M1050,
WIB_7,
WIT_9,
WITA_8,
};
// Key-value pair type
using TimezoneEntry = std::pair<std::string_view, TimezoneValue>;
// Lookup table
constexpr std::array<TimezoneEntry, 461> TIMEZONE_DATA = {{
{"Africa/Abidjan", TimezoneValue::GMT0},
{"Africa/Accra", TimezoneValue::GMT0},
{"Africa/Addis_Ababa", TimezoneValue::EAT_3},
{"Africa/Algiers", TimezoneValue::CET_1},
{"Africa/Asmara", TimezoneValue::EAT_3},
{"Africa/Bamako", TimezoneValue::GMT0},
{"Africa/Bangui", TimezoneValue::WAT_1},
{"Africa/Banjul", TimezoneValue::GMT0},
{"Africa/Bissau", TimezoneValue::GMT0},
{"Africa/Blantyre", TimezoneValue::CAT_2},
{"Africa/Brazzaville", TimezoneValue::WAT_1},
{"Africa/Bujumbura", TimezoneValue::CAT_2},
{"Africa/Cairo", TimezoneValue::EET_2EESTM455_0M1054_24},
{"Africa/Casablanca", TimezoneValue::plus01_1},
{"Africa/Ceuta", TimezoneValue::CET_1CESTM350M1050_3},
{"Africa/Conakry", TimezoneValue::GMT0},
{"Africa/Dakar", TimezoneValue::GMT0},
{"Africa/Dar_es_Salaam", TimezoneValue::EAT_3},
{"Africa/Djibouti", TimezoneValue::EAT_3},
{"Africa/Douala", TimezoneValue::WAT_1},
{"Africa/El_Aaiun", TimezoneValue::plus01_1},
{"Africa/Freetown", TimezoneValue::GMT0},
{"Africa/Gaborone", TimezoneValue::CAT_2},
{"Africa/Harare", TimezoneValue::CAT_2},
{"Africa/Johannesburg", TimezoneValue::SAST_2},
{"Africa/Juba", TimezoneValue::CAT_2},
{"Africa/Kampala", TimezoneValue::EAT_3},
{"Africa/Khartoum", TimezoneValue::CAT_2},
{"Africa/Kigali", TimezoneValue::CAT_2},
{"Africa/Kinshasa", TimezoneValue::WAT_1},
{"Africa/Lagos", TimezoneValue::WAT_1},
{"Africa/Libreville", TimezoneValue::WAT_1},
{"Africa/Lome", TimezoneValue::GMT0},
{"Africa/Luanda", TimezoneValue::WAT_1},
{"Africa/Lubumbashi", TimezoneValue::CAT_2},
{"Africa/Lusaka", TimezoneValue::CAT_2},
{"Africa/Malabo", TimezoneValue::WAT_1},
{"Africa/Maputo", TimezoneValue::CAT_2},
{"Africa/Maseru", TimezoneValue::SAST_2},
{"Africa/Mbabane", TimezoneValue::SAST_2},
{"Africa/Mogadishu", TimezoneValue::EAT_3},
{"Africa/Monrovia", TimezoneValue::GMT0},
{"Africa/Nairobi", TimezoneValue::EAT_3},
{"Africa/Ndjamena", TimezoneValue::WAT_1},
{"Africa/Niamey", TimezoneValue::WAT_1},
{"Africa/Nouakchott", TimezoneValue::GMT0},
{"Africa/Ouagadougou", TimezoneValue::GMT0},
{"Africa/Porto-Novo", TimezoneValue::WAT_1},
{"Africa/Sao_Tome", TimezoneValue::GMT0},
{"Africa/Tripoli", TimezoneValue::EET_2},
{"Africa/Tunis", TimezoneValue::CET_1},
{"Africa/Windhoek", TimezoneValue::CAT_2},
{"America/Adak", TimezoneValue::HST10HDTM320M1110},
{"America/Anchorage", TimezoneValue::AKST9AKDTM320M1110},
{"America/Anguilla", TimezoneValue::AST4},
{"America/Antigua", TimezoneValue::AST4},
{"America/Araguaina", TimezoneValue::_033},
{"America/Argentina/Buenos_Aires", TimezoneValue::_033},
{"America/Argentina/Catamarca", TimezoneValue::_033},
{"America/Argentina/Cordoba", TimezoneValue::_033},
{"America/Argentina/Jujuy", TimezoneValue::_033},
{"America/Argentina/La_Rioja", TimezoneValue::_033},
{"America/Argentina/Mendoza", TimezoneValue::_033},
{"America/Argentina/Rio_Gallegos", TimezoneValue::_033},
{"America/Argentina/Salta", TimezoneValue::_033},
{"America/Argentina/San_Juan", TimezoneValue::_033},
{"America/Argentina/San_Luis", TimezoneValue::_033},
{"America/Argentina/Tucuman", TimezoneValue::_033},
{"America/Argentina/Ushuaia", TimezoneValue::_033},
{"America/Aruba", TimezoneValue::AST4},
{"America/Asuncion", TimezoneValue::_044_03M1010_0M340_0},
{"America/Atikokan", TimezoneValue::EST5},
{"America/Bahia", TimezoneValue::_033},
{"America/Bahia_Banderas", TimezoneValue::CST6},
{"America/Barbados", TimezoneValue::AST4},
{"America/Belem", TimezoneValue::_033},
{"America/Belize", TimezoneValue::CST6},
{"America/Blanc-Sablon", TimezoneValue::AST4},
{"America/Boa_Vista", TimezoneValue::_044},
{"America/Bogota", TimezoneValue::_055},
{"America/Boise", TimezoneValue::MST7MDTM320M1110},
{"America/Cambridge_Bay", TimezoneValue::MST7MDTM320M1110},
{"America/Campo_Grande", TimezoneValue::_044},
{"America/Cancun", TimezoneValue::EST5},
{"America/Caracas", TimezoneValue::_044},
{"America/Cayenne", TimezoneValue::_033},
{"America/Cayman", TimezoneValue::EST5},
{"America/Chicago", TimezoneValue::CST6CDTM320M1110},
{"America/Chihuahua", TimezoneValue::CST6},
{"America/Costa_Rica", TimezoneValue::CST6},
{"America/Creston", TimezoneValue::MST7},
{"America/Cuiaba", TimezoneValue::_044},
{"America/Curacao", TimezoneValue::AST4},
{"America/Danmarkshavn", TimezoneValue::GMT0},
{"America/Dawson", TimezoneValue::MST7},
{"America/Dawson_Creek", TimezoneValue::MST7},
{"America/Denver", TimezoneValue::MST7MDTM320M1110},
{"America/Detroit", TimezoneValue::EST5EDTM320M1110},
{"America/Dominica", TimezoneValue::AST4},
{"America/Edmonton", TimezoneValue::MST7MDTM320M1110},
{"America/Eirunepe", TimezoneValue::_055},
{"America/El_Salvador", TimezoneValue::CST6},
{"America/Fort_Nelson", TimezoneValue::MST7},
{"America/Fortaleza", TimezoneValue::_033},
{"America/Glace_Bay", TimezoneValue::AST4ADTM320M1110},
{"America/Godthab", TimezoneValue::_022_01M350__1M1050_0},
{"America/Goose_Bay", TimezoneValue::AST4ADTM320M1110},
{"America/Grand_Turk", TimezoneValue::EST5EDTM320M1110},
{"America/Grenada", TimezoneValue::AST4},
{"America/Guadeloupe", TimezoneValue::AST4},
{"America/Guatemala", TimezoneValue::CST6},
{"America/Guayaquil", TimezoneValue::_055},
{"America/Guyana", TimezoneValue::_044},
{"America/Halifax", TimezoneValue::AST4ADTM320M1110},
{"America/Havana", TimezoneValue::CST5CDTM320_0M1110_1},
{"America/Hermosillo", TimezoneValue::MST7},
{"America/Indiana/Indianapolis", TimezoneValue::EST5EDTM320M1110},
{"America/Indiana/Knox", TimezoneValue::CST6CDTM320M1110},
{"America/Indiana/Marengo", TimezoneValue::EST5EDTM320M1110},
{"America/Indiana/Petersburg", TimezoneValue::EST5EDTM320M1110},
{"America/Indiana/Tell_City", TimezoneValue::CST6CDTM320M1110},
{"America/Indiana/Vevay", TimezoneValue::EST5EDTM320M1110},
{"America/Indiana/Vincennes", TimezoneValue::EST5EDTM320M1110},
{"America/Indiana/Winamac", TimezoneValue::EST5EDTM320M1110},
{"America/Inuvik", TimezoneValue::MST7MDTM320M1110},
{"America/Iqaluit", TimezoneValue::EST5EDTM320M1110},
{"America/Jamaica", TimezoneValue::EST5},
{"America/Juneau", TimezoneValue::AKST9AKDTM320M1110},
{"America/Kentucky/Louisville", TimezoneValue::EST5EDTM320M1110},
{"America/Kentucky/Monticello", TimezoneValue::EST5EDTM320M1110},
{"America/Kralendijk", TimezoneValue::AST4},
{"America/La_Paz", TimezoneValue::_044},
{"America/Lima", TimezoneValue::_055},
{"America/Los_Angeles", TimezoneValue::PST8PDTM320M1110},
{"America/Lower_Princes", TimezoneValue::AST4},
{"America/Maceio", TimezoneValue::_033},
{"America/Managua", TimezoneValue::CST6},
{"America/Manaus", TimezoneValue::_044},
{"America/Marigot", TimezoneValue::AST4},
{"America/Martinique", TimezoneValue::AST4},
{"America/Matamoros", TimezoneValue::CST6CDTM320M1110},
{"America/Mazatlan", TimezoneValue::MST7},
{"America/Menominee", TimezoneValue::CST6CDTM320M1110},
{"America/Merida", TimezoneValue::CST6},
{"America/Metlakatla", TimezoneValue::AKST9AKDTM320M1110},
{"America/Mexico_City", TimezoneValue::CST6},
{"America/Miquelon", TimezoneValue::_033_02M320M1110},
{"America/Moncton", TimezoneValue::AST4ADTM320M1110},
{"America/Monterrey", TimezoneValue::CST6},
{"America/Montevideo", TimezoneValue::_033},
{"America/Montreal", TimezoneValue::EST5EDTM320M1110},
{"America/Montserrat", TimezoneValue::AST4},
{"America/Nassau", TimezoneValue::EST5EDTM320M1110},
{"America/New_York", TimezoneValue::EST5EDTM320M1110},
{"America/Nipigon", TimezoneValue::EST5EDTM320M1110},
{"America/Nome", TimezoneValue::AKST9AKDTM320M1110},
{"America/Noronha", TimezoneValue::_022},
{"America/North_Dakota/Beulah", TimezoneValue::CST6CDTM320M1110},
{"America/North_Dakota/Center", TimezoneValue::CST6CDTM320M1110},
{"America/North_Dakota/New_Salem", TimezoneValue::CST6CDTM320M1110},
{"America/Nuuk", TimezoneValue::_022_01M350__1M1050_0},
{"America/Ojinaga", TimezoneValue::CST6CDTM320M1110},
{"America/Panama", TimezoneValue::EST5},
{"America/Pangnirtung", TimezoneValue::EST5EDTM320M1110},
{"America/Paramaribo", TimezoneValue::_033},
{"America/Phoenix", TimezoneValue::MST7},
{"America/Port-au-Prince", TimezoneValue::EST5EDTM320M1110},
{"America/Port_of_Spain", TimezoneValue::AST4},
{"America/Porto_Velho", TimezoneValue::_044},
{"America/Puerto_Rico", TimezoneValue::AST4},
{"America/Punta_Arenas", TimezoneValue::_033},
{"America/Rainy_River", TimezoneValue::CST6CDTM320M1110},
{"America/Rankin_Inlet", TimezoneValue::CST6CDTM320M1110},
{"America/Recife", TimezoneValue::_033},
{"America/Regina", TimezoneValue::CST6},
{"America/Resolute", TimezoneValue::CST6CDTM320M1110},
{"America/Rio_Branco", TimezoneValue::_055},
{"America/Santarem", TimezoneValue::_033},
{"America/Santiago", TimezoneValue::_044_03M916_24M416_24},
{"America/Santo_Domingo", TimezoneValue::AST4},
{"America/Sao_Paulo", TimezoneValue::_033},
{"America/Scoresbysund", TimezoneValue::_022_01M350__1M1050_0},
{"America/Sitka", TimezoneValue::AKST9AKDTM320M1110},
{"America/St_Barthelemy", TimezoneValue::AST4},
{"America/St_Johns", TimezoneValue::NST330NDTM320M1110},
{"America/St_Kitts", TimezoneValue::AST4},
{"America/St_Lucia", TimezoneValue::AST4},
{"America/St_Thomas", TimezoneValue::AST4},
{"America/St_Vincent", TimezoneValue::AST4},
{"America/Swift_Current", TimezoneValue::CST6},
{"America/Tegucigalpa", TimezoneValue::CST6},
{"America/Thule", TimezoneValue::AST4ADTM320M1110},
{"America/Thunder_Bay", TimezoneValue::EST5EDTM320M1110},
{"America/Tijuana", TimezoneValue::PST8PDTM320M1110},
{"America/Toronto", TimezoneValue::EST5EDTM320M1110},
{"America/Tortola", TimezoneValue::AST4},
{"America/Vancouver", TimezoneValue::PST8PDTM320M1110},
{"America/Whitehorse", TimezoneValue::MST7},
{"America/Winnipeg", TimezoneValue::CST6CDTM320M1110},
{"America/Yakutat", TimezoneValue::AKST9AKDTM320M1110},
{"America/Yellowknife", TimezoneValue::MST7MDTM320M1110},
{"Antarctica/Casey", TimezoneValue::plus08_8},
{"Antarctica/Davis", TimezoneValue::plus07_7},
{"Antarctica/DumontDUrville", TimezoneValue::plus10_10},
{"Antarctica/Macquarie", TimezoneValue::AEST_10AEDTM1010M410_3},
{"Antarctica/Mawson", TimezoneValue::plus05_5},
{"Antarctica/McMurdo", TimezoneValue::NZST_12NZDTM950M410_3},
{"Antarctica/Palmer", TimezoneValue::_033},
{"Antarctica/Rothera", TimezoneValue::_033},
{"Antarctica/Syowa", TimezoneValue::plus03_3},
{"Antarctica/Troll", TimezoneValue::plus000plus02_2M350_1M1050_3},
{"Antarctica/Vostok", TimezoneValue::plus05_5},
{"Arctic/Longyearbyen", TimezoneValue::CET_1CESTM350M1050_3},
{"Asia/Aden", TimezoneValue::plus03_3},
{"Asia/Almaty", TimezoneValue::plus05_5},
{"Asia/Amman", TimezoneValue::plus03_3},
{"Asia/Anadyr", TimezoneValue::plus12_12},
{"Asia/Aqtau", TimezoneValue::plus05_5},
{"Asia/Aqtobe", TimezoneValue::plus05_5},
{"Asia/Ashgabat", TimezoneValue::plus05_5},
{"Asia/Atyrau", TimezoneValue::plus05_5},
{"Asia/Baghdad", TimezoneValue::plus03_3},
{"Asia/Bahrain", TimezoneValue::plus03_3},
{"Asia/Baku", TimezoneValue::plus04_4},
{"Asia/Bangkok", TimezoneValue::plus07_7},
{"Asia/Barnaul", TimezoneValue::plus07_7},
{"Asia/Beirut", TimezoneValue::EET_2EESTM350_0M1050_0},
{"Asia/Bishkek", TimezoneValue::plus06_6},
{"Asia/Brunei", TimezoneValue::plus08_8},
{"Asia/Chita", TimezoneValue::plus09_9},
{"Asia/Choibalsan", TimezoneValue::plus08_8},
{"Asia/Colombo", TimezoneValue::plus0530_530},
{"Asia/Damascus", TimezoneValue::plus03_3},
{"Asia/Dhaka", TimezoneValue::plus06_6},
{"Asia/Dili", TimezoneValue::plus09_9},
{"Asia/Dubai", TimezoneValue::plus04_4},
{"Asia/Dushanbe", TimezoneValue::plus05_5},
{"Asia/Famagusta", TimezoneValue::EET_2EESTM350_3M1050_4},
{"Asia/Gaza", TimezoneValue::EET_2EESTM344_50M1044_50},
{"Asia/Hebron", TimezoneValue::EET_2EESTM344_50M1044_50},
{"Asia/Ho_Chi_Minh", TimezoneValue::plus07_7},
{"Asia/Hong_Kong", TimezoneValue::HKT_8},
{"Asia/Hovd", TimezoneValue::plus07_7},
{"Asia/Irkutsk", TimezoneValue::plus08_8},
{"Asia/Jakarta", TimezoneValue::WIB_7},
{"Asia/Jayapura", TimezoneValue::WIT_9},
{"Asia/Jerusalem", TimezoneValue::IST_2IDTM344_26M1050},
{"Asia/Kabul", TimezoneValue::plus0430_430},
{"Asia/Kamchatka", TimezoneValue::plus12_12},
{"Asia/Karachi", TimezoneValue::PKT_5},
{"Asia/Kathmandu", TimezoneValue::plus0545_545},
{"Asia/Khandyga", TimezoneValue::plus09_9},
{"Asia/Kolkata", TimezoneValue::IST_530},
{"Asia/Krasnoyarsk", TimezoneValue::plus07_7},
{"Asia/Kuala_Lumpur", TimezoneValue::plus08_8},
{"Asia/Kuching", TimezoneValue::plus08_8},
{"Asia/Kuwait", TimezoneValue::plus03_3},
{"Asia/Macau", TimezoneValue::CST_8},
{"Asia/Magadan", TimezoneValue::plus11_11},
{"Asia/Makassar", TimezoneValue::WITA_8},
{"Asia/Manila", TimezoneValue::PST_8},
{"Asia/Muscat", TimezoneValue::plus04_4},
{"Asia/Nicosia", TimezoneValue::EET_2EESTM350_3M1050_4},
{"Asia/Novokuznetsk", TimezoneValue::plus07_7},
{"Asia/Novosibirsk", TimezoneValue::plus07_7},
{"Asia/Omsk", TimezoneValue::plus06_6},
{"Asia/Oral", TimezoneValue::plus05_5},
{"Asia/Phnom_Penh", TimezoneValue::plus07_7},
{"Asia/Pontianak", TimezoneValue::WIB_7},
{"Asia/Pyongyang", TimezoneValue::KST_9},
{"Asia/Qatar", TimezoneValue::plus03_3},
{"Asia/Qyzylorda", TimezoneValue::plus05_5},
{"Asia/Riyadh", TimezoneValue::plus03_3},
{"Asia/Sakhalin", TimezoneValue::plus11_11},
{"Asia/Samarkand", TimezoneValue::plus05_5},
{"Asia/Seoul", TimezoneValue::KST_9},
{"Asia/Shanghai", TimezoneValue::CST_8},
{"Asia/Singapore", TimezoneValue::plus08_8},
{"Asia/Srednekolymsk", TimezoneValue::plus11_11},
{"Asia/Taipei", TimezoneValue::CST_8},
{"Asia/Tashkent", TimezoneValue::plus05_5},
{"Asia/Tbilisi", TimezoneValue::plus04_4},
{"Asia/Tehran", TimezoneValue::plus0330_330},
{"Asia/Thimphu", TimezoneValue::plus06_6},
{"Asia/Tokyo", TimezoneValue::JST_9},
{"Asia/Tomsk", TimezoneValue::plus07_7},
{"Asia/Ulaanbaatar", TimezoneValue::plus08_8},
{"Asia/Urumqi", TimezoneValue::plus06_6},
{"Asia/Ust-Nera", TimezoneValue::plus10_10},
{"Asia/Vientiane", TimezoneValue::plus07_7},
{"Asia/Vladivostok", TimezoneValue::plus10_10},
{"Asia/Yakutsk", TimezoneValue::plus09_9},
{"Asia/Yangon", TimezoneValue::plus0630_630},
{"Asia/Yekaterinburg", TimezoneValue::plus05_5},
{"Asia/Yerevan", TimezoneValue::plus04_4},
{"Atlantic/Azores", TimezoneValue::_011plus00M350_0M1050_1},
{"Atlantic/Bermuda", TimezoneValue::AST4ADTM320M1110},
{"Atlantic/Canary", TimezoneValue::WET0WESTM350_1M1050},
{"Atlantic/Cape_Verde", TimezoneValue::_011},
{"Atlantic/Faroe", TimezoneValue::WET0WESTM350_1M1050},
{"Atlantic/Madeira", TimezoneValue::WET0WESTM350_1M1050},
{"Atlantic/Reykjavik", TimezoneValue::GMT0},
{"Atlantic/South_Georgia", TimezoneValue::_022},
{"Atlantic/St_Helena", TimezoneValue::GMT0},
{"Atlantic/Stanley", TimezoneValue::_033},
{"Australia/Adelaide", TimezoneValue::ACST_930ACDTM1010M410_3},
{"Australia/Brisbane", TimezoneValue::AEST_10},
{"Australia/Broken_Hill", TimezoneValue::ACST_930ACDTM1010M410_3},
{"Australia/Currie", TimezoneValue::AEST_10AEDTM1010M410_3},
{"Australia/Darwin", TimezoneValue::ACST_930},
{"Australia/Eucla", TimezoneValue::plus0845_845},
{"Australia/Hobart", TimezoneValue::AEST_10AEDTM1010M410_3},
{"Australia/Lindeman", TimezoneValue::AEST_10},
{"Australia/Lord_Howe", TimezoneValue::plus1030_1030plus11_11M1010M410},
{"Australia/Melbourne", TimezoneValue::AEST_10AEDTM1010M410_3},
{"Australia/Perth", TimezoneValue::AWST_8},
{"Australia/Sydney", TimezoneValue::AEST_10AEDTM1010M410_3},
{"Etc/GMT", TimezoneValue::GMT0},
{"Etc/GMT+0", TimezoneValue::GMT0},
{"Etc/GMT+1", TimezoneValue::_011},
{"Etc/GMT+10", TimezoneValue::_1010},
{"Etc/GMT+11", TimezoneValue::_1111},
{"Etc/GMT+12", TimezoneValue::_1212},
{"Etc/GMT+2", TimezoneValue::_022},
{"Etc/GMT+3", TimezoneValue::_033},
{"Etc/GMT+4", TimezoneValue::_044},
{"Etc/GMT+5", TimezoneValue::_055},
{"Etc/GMT+6", TimezoneValue::_066},
{"Etc/GMT+7", TimezoneValue::_077},
{"Etc/GMT+8", TimezoneValue::_088},
{"Etc/GMT+9", TimezoneValue::_099},
{"Etc/GMT-0", TimezoneValue::GMT0},
{"Etc/GMT-1", TimezoneValue::plus01_1},
{"Etc/GMT-10", TimezoneValue::plus10_10},
{"Etc/GMT-11", TimezoneValue::plus11_11},
{"Etc/GMT-12", TimezoneValue::plus12_12},
{"Etc/GMT-13", TimezoneValue::plus13_13},
{"Etc/GMT-14", TimezoneValue::plus14_14},
{"Etc/GMT-2", TimezoneValue::plus02_2},
{"Etc/GMT-3", TimezoneValue::plus03_3},
{"Etc/GMT-4", TimezoneValue::plus04_4},
{"Etc/GMT-5", TimezoneValue::plus05_5},
{"Etc/GMT-6", TimezoneValue::plus06_6},
{"Etc/GMT-7", TimezoneValue::plus07_7},
{"Etc/GMT-8", TimezoneValue::plus08_8},
{"Etc/GMT-9", TimezoneValue::plus09_9},
{"Etc/GMT0", TimezoneValue::GMT0},
{"Etc/Greenwich", TimezoneValue::GMT0},
{"Etc/UCT", TimezoneValue::UTC0},
{"Etc/UTC", TimezoneValue::UTC0},
{"Etc/Universal", TimezoneValue::UTC0},
{"Etc/Zulu", TimezoneValue::UTC0},
{"Europe/Amsterdam", TimezoneValue::CET_1CESTM350M1050_3},
{"Europe/Andorra", TimezoneValue::CET_1CESTM350M1050_3},
{"Europe/Astrakhan", TimezoneValue::plus04_4},
{"Europe/Athens", TimezoneValue::EET_2EESTM350_3M1050_4},
{"Europe/Belgrade", TimezoneValue::CET_1CESTM350M1050_3},
{"Europe/Berlin", TimezoneValue::CET_1CESTM350M1050_3},
{"Europe/Bratislava", TimezoneValue::CET_1CESTM350M1050_3},
{"Europe/Brussels", TimezoneValue::CET_1CESTM350M1050_3},
{"Europe/Bucharest", TimezoneValue::EET_2EESTM350_3M1050_4},
{"Europe/Budapest", TimezoneValue::CET_1CESTM350M1050_3},
{"Europe/Busingen", TimezoneValue::CET_1CESTM350M1050_3},
{"Europe/Chisinau", TimezoneValue::EET_2EESTM350M1050_3},
{"Europe/Copenhagen", TimezoneValue::CET_1CESTM350M1050_3},
{"Europe/Dublin", TimezoneValue::IST_1GMT0M1050M350_1},
{"Europe/Gibraltar", TimezoneValue::CET_1CESTM350M1050_3},
{"Europe/Guernsey", TimezoneValue::GMT0BSTM350_1M1050},
{"Europe/Helsinki", TimezoneValue::EET_2EESTM350_3M1050_4},
{"Europe/Isle_of_Man", TimezoneValue::GMT0BSTM350_1M1050},
{"Europe/Istanbul", TimezoneValue::plus03_3},
{"Europe/Jersey", TimezoneValue::GMT0BSTM350_1M1050},
{"Europe/Kaliningrad", TimezoneValue::EET_2},
{"Europe/Kiev", TimezoneValue::EET_2EESTM350_3M1050_4},
{"Europe/Kirov", TimezoneValue::MSK_3},
{"Europe/Lisbon", TimezoneValue::WET0WESTM350_1M1050},
{"Europe/Ljubljana", TimezoneValue::CET_1CESTM350M1050_3},
{"Europe/London", TimezoneValue::GMT0BSTM350_1M1050},
{"Europe/Luxembourg", TimezoneValue::CET_1CESTM350M1050_3},
{"Europe/Madrid", TimezoneValue::CET_1CESTM350M1050_3},
{"Europe/Malta", TimezoneValue::CET_1CESTM350M1050_3},
{"Europe/Mariehamn", TimezoneValue::EET_2EESTM350_3M1050_4},
{"Europe/Minsk", TimezoneValue::plus03_3},
{"Europe/Monaco", TimezoneValue::CET_1CESTM350M1050_3},
{"Europe/Moscow", TimezoneValue::MSK_3},
{"Europe/Oslo", TimezoneValue::CET_1CESTM350M1050_3},
{"Europe/Paris", TimezoneValue::CET_1CESTM350M1050_3},
{"Europe/Podgorica", TimezoneValue::CET_1CESTM350M1050_3},
{"Europe/Prague", TimezoneValue::CET_1CESTM350M1050_3},
{"Europe/Riga", TimezoneValue::EET_2EESTM350_3M1050_4},
{"Europe/Rome", TimezoneValue::CET_1CESTM350M1050_3},
{"Europe/Samara", TimezoneValue::plus04_4},
{"Europe/San_Marino", TimezoneValue::CET_1CESTM350M1050_3},
{"Europe/Sarajevo", TimezoneValue::CET_1CESTM350M1050_3},
{"Europe/Saratov", TimezoneValue::plus04_4},
{"Europe/Simferopol", TimezoneValue::MSK_3},
{"Europe/Skopje", TimezoneValue::CET_1CESTM350M1050_3},
{"Europe/Sofia", TimezoneValue::EET_2EESTM350_3M1050_4},
{"Europe/Stockholm", TimezoneValue::CET_1CESTM350M1050_3},
{"Europe/Tallinn", TimezoneValue::EET_2EESTM350_3M1050_4},
{"Europe/Tirane", TimezoneValue::CET_1CESTM350M1050_3},
{"Europe/Ulyanovsk", TimezoneValue::plus04_4},
{"Europe/Uzhgorod", TimezoneValue::EET_2EESTM350_3M1050_4},
{"Europe/Vaduz", TimezoneValue::CET_1CESTM350M1050_3},
{"Europe/Vatican", TimezoneValue::CET_1CESTM350M1050_3},
{"Europe/Vienna", TimezoneValue::CET_1CESTM350M1050_3},
{"Europe/Vilnius", TimezoneValue::EET_2EESTM350_3M1050_4},
{"Europe/Volgograd", TimezoneValue::MSK_3},
{"Europe/Warsaw", TimezoneValue::CET_1CESTM350M1050_3},
{"Europe/Zagreb", TimezoneValue::CET_1CESTM350M1050_3},
{"Europe/Zaporozhye", TimezoneValue::EET_2EESTM350_3M1050_4},
{"Europe/Zurich", TimezoneValue::CET_1CESTM350M1050_3},
{"Indian/Antananarivo", TimezoneValue::EAT_3},
{"Indian/Chagos", TimezoneValue::plus06_6},
{"Indian/Christmas", TimezoneValue::plus07_7},
{"Indian/Cocos", TimezoneValue::plus0630_630},
{"Indian/Comoro", TimezoneValue::EAT_3},
{"Indian/Kerguelen", TimezoneValue::plus05_5},
{"Indian/Mahe", TimezoneValue::plus04_4},
{"Indian/Maldives", TimezoneValue::plus05_5},
{"Indian/Mauritius", TimezoneValue::plus04_4},
{"Indian/Mayotte", TimezoneValue::EAT_3},
{"Indian/Reunion", TimezoneValue::plus04_4},
{"Pacific/Apia", TimezoneValue::plus13_13},
{"Pacific/Auckland", TimezoneValue::NZST_12NZDTM950M410_3},
{"Pacific/Bougainville", TimezoneValue::plus11_11},
{"Pacific/Chatham", TimezoneValue::plus1245_1245plus1345M950_245M410_345},
{"Pacific/Chuuk", TimezoneValue::plus10_10},
{"Pacific/Easter", TimezoneValue::_066_05M916_22M416_22},
{"Pacific/Efate", TimezoneValue::plus11_11},
{"Pacific/Enderbury", TimezoneValue::plus13_13},
{"Pacific/Fakaofo", TimezoneValue::plus13_13},
{"Pacific/Fiji", TimezoneValue::plus12_12},
{"Pacific/Funafuti", TimezoneValue::plus12_12},
{"Pacific/Galapagos", TimezoneValue::_066},
{"Pacific/Gambier", TimezoneValue::_099},
{"Pacific/Guadalcanal", TimezoneValue::plus11_11},
{"Pacific/Guam", TimezoneValue::ChST_10},
{"Pacific/Honolulu", TimezoneValue::HST10},
{"Pacific/Kiritimati", TimezoneValue::plus14_14},
{"Pacific/Kosrae", TimezoneValue::plus11_11},
{"Pacific/Kwajalein", TimezoneValue::plus12_12},
{"Pacific/Majuro", TimezoneValue::plus12_12},
{"Pacific/Marquesas", TimezoneValue::_0930930},
{"Pacific/Midway", TimezoneValue::SST11},
{"Pacific/Nauru", TimezoneValue::plus12_12},
{"Pacific/Niue", TimezoneValue::_1111},
{"Pacific/Norfolk", TimezoneValue::plus11_11plus12M1010M410_3},
{"Pacific/Noumea", TimezoneValue::plus11_11},
{"Pacific/Pago_Pago", TimezoneValue::SST11},
{"Pacific/Palau", TimezoneValue::plus09_9},
{"Pacific/Pitcairn", TimezoneValue::_088},
{"Pacific/Pohnpei", TimezoneValue::plus11_11},
{"Pacific/Port_Moresby", TimezoneValue::plus10_10},
{"Pacific/Rarotonga", TimezoneValue::_1010},
{"Pacific/Saipan", TimezoneValue::ChST_10},
{"Pacific/Tahiti", TimezoneValue::_1010},
{"Pacific/Tarawa", TimezoneValue::plus12_12},
{"Pacific/Tongatapu", TimezoneValue::plus13_13},
{"Pacific/Wake", TimezoneValue::plus12_12},
{"Pacific/Wallis", TimezoneValue::plus12_12},
}};
// Helper function to find timezone value
inline TimezoneValue find_timezone_value(std::string_view key) {
for (const auto& entry : TIMEZONE_DATA) {
if (entry.first == key) {
return entry.second;
}
}
return TimezoneValue::plus000plus02_2M350_1M1050_3; // Default fallback
}
// Overload for String
inline TimezoneValue find_timezone_value(const String& key) {
return find_timezone_value(std::string_view(key.c_str()));
}
// Overload for std::string
inline TimezoneValue find_timezone_value(const std::string& key) {
return find_timezone_value(std::string_view(key));
}
// Helper function to convert TimezoneValue to string representation
inline String get_timezone_string(TimezoneValue value) {
for (const auto& entry : TIMEZONE_DATA) {
if (entry.second == value) {
return String(entry.first.data(), entry.first.length());
}
}
return String("GMT0"); // Default fallback
}
// Overload for std::string
inline std::string get_timezone_string_std(TimezoneValue value) {
for (const auto& entry : TIMEZONE_DATA) {
if (entry.second == value) {
return std::string(entry.first.data(), entry.first.length());
}
}
return "GMT0"; // Default fallback
}
// Helper function to convert TimezoneValue enum to its string representation
inline String get_timezone_value_string(TimezoneValue value) {
switch (value) {
case TimezoneValue::plus000plus02_2M350_1M1050_3: return String("+00:00+02:00,M3.5.0/1,M10.5.0/3");
case TimezoneValue::plus01_1: return String("+01:00");
case TimezoneValue::plus02_2: return String("+02:00");
case TimezoneValue::plus0330_330: return String("+03:30");
case TimezoneValue::plus03_3: return String("+03:00");
case TimezoneValue::plus0430_430: return String("+04:30");
case TimezoneValue::plus04_4: return String("+04:00");
case TimezoneValue::plus0530_530: return String("+05:30");
case TimezoneValue::plus0545_545: return String("+05:45");
case TimezoneValue::plus05_5: return String("+05:00");
case TimezoneValue::plus0630_630: return String("+06:30");
case TimezoneValue::plus06_6: return String("+06:00");
case TimezoneValue::plus07_7: return String("+07:00");
case TimezoneValue::plus0845_845: return String("+08:45");
case TimezoneValue::plus08_8: return String("+08:00");
case TimezoneValue::plus09_9: return String("+09:00");
case TimezoneValue::plus1030_1030plus11_11M1010M410: return String("+10:30+11:00,M10.1.0,M4.1.0");
case TimezoneValue::plus10_10: return String("+10:00");
case TimezoneValue::plus11_11: return String("+11:00");
case TimezoneValue::plus11_11plus12M1010M410_3: return String("+11:00+12:00,M10.1.0,M4.1.0/3");
case TimezoneValue::plus1245_1245plus1345M950_245M410_345: return String("+12:45+13:45,M9.5.0/2:45,M4.1.0/3:45");
case TimezoneValue::plus12_12: return String("+12:00");
case TimezoneValue::plus13_13: return String("+13:00");
case TimezoneValue::plus14_14: return String("+14:00");
case TimezoneValue::_011: return String("-01:00");
case TimezoneValue::_011plus00M350_0M1050_1: return String("-01:00+00:00,M3.5.0/0,M10.5.0/1");
case TimezoneValue::_022: return String("-02:00");
case TimezoneValue::_022_01M350__1M1050_0: return String("-02:00-01:00,M3.5.0/-1,M10.5.0/0");
case TimezoneValue::_033: return String("-03:00");
case TimezoneValue::_033_02M320M1110: return String("-03:00-02:00,M3.2.0,M11.1.0");
case TimezoneValue::_044: return String("-04:00");
case TimezoneValue::_044_03M1010_0M340_0: return String("-04:00-03:00,M10.1.0/0,M3.4.0/0");
case TimezoneValue::_044_03M916_24M416_24: return String("-04:00-03:00,M9.1.6/24,M4.1.6/24");
case TimezoneValue::_055: return String("-05:00");
case TimezoneValue::_066: return String("-06:00");
case TimezoneValue::_066_05M916_22M416_22: return String("-06:00-05:00,M9.1.6/22,M4.1.6/22");
case TimezoneValue::_077: return String("-07:00");
case TimezoneValue::_088: return String("-08:00");
case TimezoneValue::_0930930: return String("-09:30");
case TimezoneValue::_099: return String("-09:00");
case TimezoneValue::_1010: return String("-10:00");
case TimezoneValue::_1111: return String("-11:00");
case TimezoneValue::_1212: return String("-12:00");
case TimezoneValue::ACST_930: return String("ACST-9:30");
case TimezoneValue::ACST_930ACDTM1010M410_3: return String("ACST-9:30ACDT,M10.1.0,M4.1.0/3");
case TimezoneValue::AEST_10: return String("AEST-10");
case TimezoneValue::AEST_10AEDTM1010M410_3: return String("AEST-10AEDT,M10.1.0,M4.1.0/3");
case TimezoneValue::AKST9AKDTM320M1110: return String("AKST9AKDT,M3.2.0,M11.1.0");
case TimezoneValue::AST4: return String("AST4");
case TimezoneValue::AST4ADTM320M1110: return String("AST4ADT,M3.2.0,M11.1.0");
case TimezoneValue::AWST_8: return String("AWST-8");
case TimezoneValue::CAT_2: return String("CAT-2");
case TimezoneValue::CET_1: return String("CET-1");
case TimezoneValue::CET_1CESTM350M1050_3: return String("CET-1CEST,M3.5.0,M10.5.0/3");
case TimezoneValue::CST_8: return String("CST-8");
case TimezoneValue::CST5CDTM320_0M1110_1: return String("CST5CDT,M3.2.0/0,M11.1.0/1");
case TimezoneValue::CST6: return String("CST6");
case TimezoneValue::CST6CDTM320M1110: return String("CST6CDT,M3.2.0,M11.1.0");
case TimezoneValue::ChST_10: return String("ChST-10");
case TimezoneValue::EAT_3: return String("EAT-3");
case TimezoneValue::EET_2: return String("EET-2");
case TimezoneValue::EET_2EESTM344_50M1044_50: return String("EET-2EEST,M3.4.4/50,M10.4.4/50");
case TimezoneValue::EET_2EESTM350M1050_3: return String("EET-2EEST,M3.5.0,M10.5.0/3");
case TimezoneValue::EET_2EESTM350_0M1050_0: return String("EET-2EEST,M3.5.0/0,M10.5.0/0");
case TimezoneValue::EET_2EESTM350_3M1050_4: return String("EET-2EEST,M3.5.0/3,M10.5.0/4");
case TimezoneValue::EET_2EESTM455_0M1054_24: return String("EET-2EEST,M4.5.5/0,M10.5.4/24");
case TimezoneValue::EST5: return String("EST5");
case TimezoneValue::EST5EDTM320M1110: return String("EST5EDT,M3.2.0,M11.1.0");
case TimezoneValue::GMT0: return String("GMT0");
case TimezoneValue::GMT0BSTM350_1M1050: return String("GMT0BST,M3.5.0/1,M10.5.0");
case TimezoneValue::HKT_8: return String("HKT-8");
case TimezoneValue::HST10: return String("HST10");
case TimezoneValue::HST10HDTM320M1110: return String("HST10HDT,M3.2.0,M11.1.0");
case TimezoneValue::IST_1GMT0M1050M350_1: return String("IST-1GMT0,M10.5.0,M3.5.0/1");
case TimezoneValue::IST_2IDTM344_26M1050: return String("IST-2IDT,M3.4.4/26,M10.5.0");
case TimezoneValue::IST_530: return String("IST-5:30");
case TimezoneValue::JST_9: return String("JST-9");
case TimezoneValue::KST_9: return String("KST-9");
case TimezoneValue::MSK_3: return String("MSK-3");
case TimezoneValue::MST7: return String("MST7");
case TimezoneValue::MST7MDTM320M1110: return String("MST7MDT,M3.2.0,M11.1.0");
case TimezoneValue::NST330NDTM320M1110: return String("NST3:30NDT,M3.2.0,M11.1.0");
case TimezoneValue::NZST_12NZDTM950M410_3: return String("NZST-12NZDT,M9.5.0,M4.1.0/3");
case TimezoneValue::PKT_5: return String("PKT-5");
case TimezoneValue::PST_8: return String("PST-8");
case TimezoneValue::PST8PDTM320M1110: return String("PST8PDT,M3.2.0,M11.1.0");
case TimezoneValue::SAST_2: return String("SAST-2");
case TimezoneValue::SST11: return String("SST11");
case TimezoneValue::UTC0: return String("UTC0");
case TimezoneValue::WAT_1: return String("WAT-1");
case TimezoneValue::WET0WESTM350_1M1050: return String("WET0WEST,M3.5.0/1,M10.5.0");
case TimezoneValue::WIB_7: return String("WIB-7");
case TimezoneValue::WIT_9: return String("WIT-9");
case TimezoneValue::WITA_8: return String("WITA-8");
default: return String("GMT0"); // Default fallback
}
}
// Overload for std::string
inline std::string get_timezone_value_string_std(TimezoneValue value) {
return std::string(get_timezone_value_string(value).c_str());
}
} // namespace timezone_data

View file

@ -15,7 +15,7 @@ default_envs = lolin_s3_mini_213epd, lolin_s3_mini_29epd, btclock_rev_b_213epd,
[env]
[btclock_base]
platform = espressif32 @ ^6.10.0
platform = espressif32 @ ^6.9.0
framework = arduino, espidf
monitor_speed = 115200
monitor_filters = esp32_exception_decoder, colorize
@ -30,17 +30,17 @@ build_flags =
-DLAST_BUILD_TIME=$UNIX_TIME
-DARDUINO_USB_CDC_ON_BOOT
-DCORE_DEBUG_LEVEL=0
-D CONFIG_ASYNC_TCP_STACK_SIZE=16384
-D DEFAULT_BOOT_TEXT=\"BTCLOCK\"
-fexceptions
build_unflags =
-Werror=all
-fno-exceptions
lib_deps =
https://github.com/joltwallet/esp_littlefs.git#v1.16.4
bblanchon/ArduinoJson@^7.4.1
esp32async/ESPAsyncWebServer @ 3.7.7
robtillaart/MCP23017@^0.9.1
adafruit/Adafruit NeoPixel@^1.12.5
https://github.com/joltwallet/esp_littlefs.git
bblanchon/ArduinoJson@^7.2.1
mathieucarbou/ESPAsyncWebServer @ 3.4.5
robtillaart/MCP23017@^0.8.0
adafruit/Adafruit NeoPixel@^1.12.3
https://github.com/dsbaars/universal_pin#feature/mcp23017_rt
https://github.com/dsbaars/GxEPD2#universal_pin
https://github.com/tzapu/WiFiManager.git#v2.0.17
@ -79,10 +79,9 @@ build_flags =
-D I2C_SDA_PIN=35
-D I2C_SCK_PIN=36
-D HAS_FRONTLIGHT
-D PCA_OE_PIN=48
-D PCA_OE_PIN=45
-D PCA_I2C_ADDR=0x42
-D IS_HW_REV_B
lib_deps =
${btclock_base.lib_deps}
robtillaart/PCA9685@^0.7.1
@ -101,7 +100,6 @@ build_flags =
-D USE_QR
-D VERSION_EPD_2_13
-D HW_REV=\"REV_A_EPD_2_13\"
-D CONFIG_ARDUINO_MAIN_TASK_STACK_SIZE=16384
platform_packages =
platformio/tool-mklittlefs@^1.203.210628
earlephilhower/tool-mklittlefs-rp2040-earlephilhower@^5.100300.230216
@ -114,7 +112,6 @@ build_flags =
-D USE_QR
-D VERSION_EPD_2_13
-D HW_REV=\"REV_B_EPD_2_13\"
-D CONFIG_ARDUINO_MAIN_TASK_STACK_SIZE=16384
platform_packages =
platformio/tool-mklittlefs@^1.203.210628
earlephilhower/tool-mklittlefs-rp2040-earlephilhower@^5.100300.230216

View file

@ -1,14 +1,14 @@
#include "block_notify.hpp"
#include "led_handler.hpp"
// Initialize static members
esp_websocket_client_handle_t BlockNotify::wsClient = nullptr;
uint32_t BlockNotify::currentBlockHeight = 878000;
uint16_t BlockNotify::blockMedianFee = 1;
bool BlockNotify::notifyInit = false;
unsigned long int BlockNotify::lastBlockUpdate = 0;
TaskHandle_t BlockNotify::taskHandle = nullptr;
char *wsServer;
esp_websocket_client_handle_t blockNotifyClient = NULL;
uint32_t currentBlockHeight = 873400;
uint16_t blockMedianFee = 1;
bool blockNotifyInit = false;
unsigned long int lastBlockUpdate;
const char* BlockNotify::mempoolWsCert = R"EOF(
const char *mempoolWsCert = R"EOF(
-----BEGIN CERTIFICATE-----
MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB
iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl
@ -43,106 +43,22 @@ VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB
L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG
jjxDah2nGN59PRbxYvnKkKj9
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
-----END CERTIFICATE-----
)EOF";
void BlockNotify::onWebsocketEvent(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) {
esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data;
BlockNotify& instance = BlockNotify::getInstance();
switch (event_id) {
case WEBSOCKET_EVENT_CONNECTED:
{
notifyInit = true;
Serial.print(F("Connected to "));
Serial.println(preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE));
JsonDocument doc;
doc["action"] = "want";
JsonArray dataArray = doc.createNestedArray("data");
dataArray.add("blocks");
dataArray.add("mempool-blocks");
String sub;
serializeJson(doc, sub);
esp_websocket_client_send_text(wsClient, sub.c_str(), sub.length(), portMAX_DELAY);
break;
}
case WEBSOCKET_EVENT_DATA:
instance.onWebsocketMessage(data);
break;
case WEBSOCKET_EVENT_DISCONNECTED:
Serial.println(F("Mempool.space WS Connection Closed"));
break;
case WEBSOCKET_EVENT_ERROR:
Serial.println(F("Mempool.space WS Connection Error"));
break;
}
}
void BlockNotify::onWebsocketMessage(esp_websocket_event_data_t *data) {
JsonDocument doc;
JsonDocument filter;
filter["block"]["height"] = true;
filter["mempool-blocks"][0]["medianFee"] = true;
deserializeJson(doc, (char*)data->data_ptr, DeserializationOption::Filter(filter));
if (doc["block"].is<JsonObject>()) {
JsonObject block = doc["block"];
if (block["height"].as<uint>() != currentBlockHeight) {
processNewBlock(block["height"].as<uint>());
}
}
else if (doc["mempool-blocks"].is<JsonArray>()) {
JsonArray blockInfo = doc["mempool-blocks"].as<JsonArray>();
uint medianFee = (uint)round(blockInfo[0]["medianFee"].as<double>());
processNewBlockFee(medianFee);
}
}
void BlockNotify::setup() {
void setupBlockNotify()
{
IPAddress result;
int dnsErr = -1;
String mempoolInstance = preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE);
while (dnsErr != 1 && !strchr(mempoolInstance.c_str(), ':')) {
int dnsErr = -1;
String mempoolInstance =
preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE);
while (dnsErr != 1 && !strchr(mempoolInstance.c_str(), ':'))
{
dnsErr = WiFi.hostByName(mempoolInstance.c_str(), result);
if (dnsErr != 1) {
if (dnsErr != 1)
{
Serial.print(mempoolInstance);
Serial.println(F("mempool DNS could not be resolved"));
WiFi.reconnect();
@ -151,45 +67,120 @@ void BlockNotify::setup() {
}
// Get current block height through regular API
int blockFetch = fetchLatestBlock();
int blockFetch = getBlockFetch();
if (blockFetch > currentBlockHeight)
currentBlockHeight = blockFetch;
if (currentBlockHeight != -1) {
if (currentBlockHeight != -1)
{
lastBlockUpdate = esp_timer_get_time() / 1000000;
}
if (workQueue != nullptr) {
if (workQueue != nullptr)
{
WorkItem blockUpdate = {TASK_BLOCK_UPDATE, 0};
xQueueSend(workQueue, &blockUpdate, portMAX_DELAY);
}
const bool useSSL = preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE);
const String protocol = useSSL ? "wss" : "ws";
String wsUri = protocol + "://" + mempoolInstance + "/api/v1/ws";
// std::strcpy(wsServer, String("wss://" + mempoolInstance +
// "/api/v1/ws").c_str());
const String protocol = preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE) ? "wss" : "ws";
String mempoolUri = protocol + "://" + preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE) + "/api/v1/ws";
esp_websocket_client_config_t config = {
// .uri = "wss://mempool.space/api/v1/ws",
.task_stack = (6*1024),
.user_agent = USER_AGENT
};
if (useSSL) {
if (preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE)) {
config.cert_pem = mempoolWsCert;
}
config.uri = wsUri.c_str();
config.uri = mempoolUri.c_str();
Serial.printf("Connecting to %s\r\n", mempoolInstance.c_str());
Serial.printf("Connecting to %s\r\n", preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE));
wsClient = esp_websocket_client_init(&config);
esp_websocket_register_events(wsClient, WEBSOCKET_EVENT_ANY, onWebsocketEvent, wsClient);
esp_websocket_client_start(wsClient);
blockNotifyClient = esp_websocket_client_init(&config);
esp_websocket_register_events(blockNotifyClient, WEBSOCKET_EVENT_ANY,
onWebsocketBlockEvent, blockNotifyClient);
esp_websocket_client_start(blockNotifyClient);
}
void onWebsocketBlockEvent(void *handler_args, esp_event_base_t base,
int32_t event_id, void *event_data)
{
esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data;
const String sub = "{\"action\": \"want\", \"data\":[\"blocks\", \"mempool-blocks\"]}";
switch (event_id)
{
case WEBSOCKET_EVENT_CONNECTED:
blockNotifyInit = true;
Serial.println(F("Connected to Mempool.space WebSocket"));
void BlockNotify::processNewBlock(uint32_t newBlockHeight) {
Serial.println(sub);
if (esp_websocket_client_send_text(blockNotifyClient, sub.c_str(),
sub.length(), portMAX_DELAY) == -1)
{
Serial.println(F("Mempool.space WS Block Subscribe Error"));
}
break;
case WEBSOCKET_EVENT_DATA:
onWebsocketBlockMessage(data);
break;
case WEBSOCKET_EVENT_ERROR:
Serial.println(F("Mempool.space WS Connnection error"));
break;
case WEBSOCKET_EVENT_DISCONNECTED:
Serial.println(F("Mempool.space WS Connnection Closed"));
break;
}
}
void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data)
{
JsonDocument doc;
JsonDocument filter;
filter["block"]["height"] = true;
filter["mempool-blocks"][0]["medianFee"] = true;
deserializeJson(doc, (char *)event_data->data_ptr, DeserializationOption::Filter(filter));
// if (error) {
// Serial.print("deserializeJson() failed: ");
// Serial.println(error.c_str());
// return;
// }
if (doc["block"].is<JsonObject>())
{
JsonObject block = doc["block"];
if (block["height"].as<uint>() == currentBlockHeight) {
return;
}
processNewBlock(block["height"].as<uint>());
}
else if (doc["mempool-blocks"].is<JsonArray>())
{
JsonArray blockInfo = doc["mempool-blocks"].as<JsonArray>();
uint medianFee = (uint)round(blockInfo[0]["medianFee"].as<double>());
processNewBlockFee(medianFee);
}
doc.clear();
}
void processNewBlock(uint32_t newBlockHeight) {
if (newBlockHeight <= currentBlockHeight)
{
return;
@ -229,12 +220,13 @@ void BlockNotify::processNewBlock(uint32_t newBlockHeight) {
}
}
void BlockNotify::processNewBlockFee(uint16_t newBlockFee) {
void processNewBlockFee(uint16_t newBlockFee) {
if (blockMedianFee == newBlockFee)
{
return;
}
// Serial.printf("New median fee: %d\r\n", medianFee);
blockMedianFee = newBlockFee;
if (workQueue != nullptr)
@ -244,54 +236,60 @@ void BlockNotify::processNewBlockFee(uint16_t newBlockFee) {
}
}
uint32_t BlockNotify::getBlockHeight() const {
return currentBlockHeight;
}
uint32_t getBlockHeight() { return currentBlockHeight; }
void BlockNotify::setBlockHeight(uint32_t newBlockHeight)
void setBlockHeight(uint32_t newBlockHeight)
{
currentBlockHeight = newBlockHeight;
}
uint16_t BlockNotify::getBlockMedianFee() const {
return blockMedianFee;
}
uint16_t getBlockMedianFee() { return blockMedianFee; }
void BlockNotify::setBlockMedianFee(uint16_t newBlockMedianFee)
void setBlockMedianFee(uint16_t newBlockMedianFee)
{
blockMedianFee = newBlockMedianFee;
}
bool BlockNotify::isConnected() const
bool isBlockNotifyConnected()
{
if (wsClient == NULL)
if (blockNotifyClient == NULL)
return false;
return esp_websocket_client_is_connected(wsClient);
return esp_websocket_client_is_connected(blockNotifyClient);
}
bool BlockNotify::isInitialized() const
bool getBlockNotifyInit()
{
return notifyInit;
return blockNotifyInit;
}
void BlockNotify::stop()
void stopBlockNotify()
{
if (wsClient == NULL)
if (blockNotifyClient == NULL)
return;
esp_websocket_client_close(wsClient, portMAX_DELAY);
esp_websocket_client_stop(wsClient);
esp_websocket_client_destroy(wsClient);
wsClient = NULL;
esp_websocket_client_close(blockNotifyClient, pdMS_TO_TICKS(5000));
esp_websocket_client_stop(blockNotifyClient);
esp_websocket_client_destroy(blockNotifyClient);
blockNotifyClient = NULL;
}
void BlockNotify::restart()
void restartBlockNotify()
{
stop();
setup();
stopBlockNotify();
if (blockNotifyClient == NULL) {
setupBlockNotify();
return;
}
// esp_websocket_client_close(blockNotifyClient, pdMS_TO_TICKS(5000));
// esp_websocket_client_stop(blockNotifyClient);
// esp_websocket_client_start(blockNotifyClient);
}
int BlockNotify::fetchLatestBlock() {
int getBlockFetch() {
try {
String mempoolInstance = preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE);
const String protocol = preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE) ? "https" : "http";
@ -314,12 +312,12 @@ int BlockNotify::fetchLatestBlock() {
return 2203; // B-T-C
}
uint BlockNotify::getLastBlockUpdate() const
uint getLastBlockUpdate()
{
return lastBlockUpdate;
}
void BlockNotify::setLastBlockUpdate(uint lastUpdate)
void setLastBlockUpdate(uint lastUpdate)
{
lastBlockUpdate = lastUpdate;
}

View file

@ -5,6 +5,7 @@
#include <HTTPClient.h>
#include <esp_timer.h>
#include <esp_websocket_client.h>
#include <cstring>
#include <string>
@ -13,53 +14,28 @@
#include "lib/timers.hpp"
#include "lib/shared.hpp"
class BlockNotify {
public:
static BlockNotify& getInstance() {
static BlockNotify instance;
return instance;
}
// using namespace websockets;
// Delete copy constructor and assignment operator
BlockNotify(const BlockNotify&) = delete;
void operator=(const BlockNotify&) = delete;
void setupBlockNotify();
// Block notification setup and control
void setup();
void stop();
void restart();
bool isConnected() const;
bool isInitialized() const;
void onWebsocketBlockEvent(void *handler_args, esp_event_base_t base,
int32_t event_id, void *event_data);
void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data);
// Block height management
void setBlockHeight(uint32_t newBlockHeight);
uint32_t getBlockHeight() const;
void setBlockHeight(uint32_t newBlockHeight);
uint32_t getBlockHeight();
// Block fee management
void setBlockMedianFee(uint16_t blockMedianFee);
uint16_t getBlockMedianFee() const;
void setBlockMedianFee(uint16_t blockMedianFee);
uint16_t getBlockMedianFee();
// Block processing
void processNewBlock(uint32_t newBlockHeight);
void processNewBlockFee(uint16_t newBlockFee);
bool isBlockNotifyConnected();
void stopBlockNotify();
void restartBlockNotify();
// Block fetch and update tracking
int fetchLatestBlock();
uint getLastBlockUpdate() const;
void setLastBlockUpdate(uint lastUpdate);
void processNewBlock(uint32_t newBlockHeight);
void processNewBlockFee(uint16_t newBlockFee);
private:
BlockNotify() = default; // Private constructor for singleton
void setupTask();
static void onWebsocketEvent(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data);
void onWebsocketMessage(esp_websocket_event_data_t *data);
static const char* mempoolWsCert;
static esp_websocket_client_handle_t wsClient;
static uint32_t currentBlockHeight;
static uint16_t blockMedianFee;
static bool notifyInit;
static unsigned long int lastBlockUpdate;
static TaskHandle_t taskHandle;
};
bool getBlockNotifyInit();
uint32_t getLastBlockUpdate();
int getBlockFetch();
void setLastBlockUpdate(uint32_t lastUpdate);

View file

@ -1,7 +1,7 @@
#include "config.hpp"
#include "led_handler.hpp"
#define MAX_ATTEMPTS_WIFI_CONNECTION 20
// Global instance definitions
PriceNotify::PriceNotifyManager priceManager;
// zlib_turbo zt;
@ -132,25 +132,9 @@ void setup()
void setupWifi()
{
WiFi.onEvent(WiFiEvent);
// wifi_country_t country = {
// .cc = "NL",
// .schan = 1,
// .nchan = 13,
// .policy = WIFI_COUNTRY_POLICY_MANUAL
// };
// esp_err_t err = esp_wifi_set_country(&country);
// if (err != ESP_OK) {
// Serial.printf("Failed to set country: %d\n", err);
// }
WiFi.setAutoConnect(true);
WiFi.setAutoReconnect(true);
WiFi.begin();
if (preferences.getInt("txPower", DEFAULT_TX_POWER))
{
if (WiFi.setTxPower(
@ -188,7 +172,6 @@ void setupWifi()
wm.setConfigPortalTimeout(preferences.getUInt("wpTimeout", DEFAULT_WP_TIMEOUT));
wm.setWiFiAutoReconnect(false);
wm.setDebugOutput(false);
wm.setCountry("NL");
wm.setConfigPortalBlocking(true);
wm.setAPCallback([&](WiFiManager *wifiManager)
@ -259,7 +242,7 @@ void setupWifi()
void syncTime()
{
configTime(0, 0,
configTime(preferences.getInt("gmtOffset", DEFAULT_TIME_OFFSET_SECONDS), 0,
NTP_SERVER);
struct tm timeinfo;
@ -267,32 +250,23 @@ void syncTime()
{
auto& ledHandler = getLedHandler();
ledHandler.queueEffect(LED_EFFECT_CONFIGURING);
configTime(0, 0,
configTime(preferences.getInt("gmtOffset", DEFAULT_TIME_OFFSET_SECONDS), 0,
NTP_SERVER);
delay(500);
Serial.println(F("Retry set time"));
}
setTimezone(get_timezone_value_string(timezone_data::find_timezone_value(preferences.getString("tzString", DEFAULT_TZ_STRING))));
lastTimeSync = esp_timer_get_time() / 1000000;
}
void setTimezone(String timezone) {
Serial.printf(" Setting Timezone to %s\n",timezone.c_str());
setenv("TZ",timezone.c_str(),1); // Now adjust the TZ. Clock settings are adjusted to show the new local time
tzset();
}
void setupPreferences()
{
preferences.begin("btclock", false);
EPDManager::getInstance().setForegroundColor(preferences.getUInt("fgColor", DEFAULT_FG_COLOR));
EPDManager::getInstance().setBackgroundColor(preferences.getUInt("bgColor", DEFAULT_BG_COLOR));
BlockNotify::getInstance().setBlockHeight(preferences.getUInt("blockHeight", INITIAL_BLOCK_HEIGHT));
setPrice(preferences.getUInt("lastPrice", INITIAL_LAST_PRICE), CURRENCY_USD);
setBlockHeight(preferences.getUInt("blockHeight", INITIAL_BLOCK_HEIGHT));
priceManager.processNewPrice(preferences.getUInt("lastPrice", INITIAL_LAST_PRICE), CURRENCY_USD);
if (!preferences.isKey("enableDebugLog")) {
preferences.putBool("enableDebugLog", DEFAULT_ENABLE_DEBUG_LOG);
@ -399,8 +373,16 @@ void setupWebsocketClients(void *pvParameters)
}
else if (dataSource == THIRD_PARTY_SOURCE)
{
BlockNotify::getInstance().setup();
setupBlockNotify();
setupPriceNotify();
// Create task for price manager loop
xTaskCreate([](void* param) {
for (;;) {
priceManager.loop();
vTaskDelay(10 / portTICK_PERIOD_MS);
}
}, "priceManagerLoop", (6 * 1024), NULL, tskIDLE_PRIORITY, NULL);
}
vTaskDelete(NULL);
@ -779,3 +761,17 @@ DataSourceType getDataSource() {
void setDataSource(DataSourceType source) {
preferences.putUChar("dataSource", static_cast<uint8_t>(source));
}
void setupPriceNotify() {
priceManager.init(PriceNotify::PriceSource::COINCAP);
priceManager.onPriceUpdate([](PriceNotify::Currency currency, uint64_t price) {
if (workQueue != nullptr && (ScreenHandler::getCurrentScreen() == SCREEN_BTC_TICKER ||
ScreenHandler::getCurrentScreen() == SCREEN_SATS_PER_CURRENCY ||
ScreenHandler::getCurrentScreen() == SCREEN_MARKET_CAP))
{
WorkItem priceUpdate = {TASK_PRICE_UPDATE, static_cast<char>(currency)};
xQueueSend(workQueue, &priceUpdate, portMAX_DELAY);
}
});
priceManager.connect();
}

View file

@ -1,108 +1,88 @@
#pragma once
#include <MCP23017.h>
#include <Arduino.h>
#include <ArduinoJson.h>
#include <Preferences.h>
#include <WiFiClientSecure.h>
#include <WiFiManager.h>
#include <base64.h>
#include <WiFi.h>
#include <esp_task_wdt.h>
#include <WiFiClientSecure.h>
#include <nvs_flash.h>
#include <map>
#include "lib/block_notify.hpp"
#include "lib/button_handler.hpp"
#include "lib/epd.hpp"
// #include "lib/improv.hpp"
#include "lib/led_handler.hpp"
#include "lib/ota.hpp"
#include "lib/nostr_notify.hpp"
#include "lib/bitaxe_fetch.hpp"
#include "lib/mining_pool_stats_fetch.hpp"
#include "lib/v2_notify.hpp"
#include "lib/price_notify.hpp"
#include "lib/screen_handler.hpp"
#include "lib/shared.hpp"
#include "lib/webserver.hpp"
#ifdef HAS_FRONTLIGHT
#include "PCA9685.h"
#include "BH1750.h"
#endif
#include <WiFiManager.h>
#include <ESPmDNS.h>
#include "block_notify.hpp"
#include "led_handler.hpp"
#include "nostr_notify.hpp"
#include "price_notify/price_notify.hpp"
#include "screen_handler.hpp"
#include "shared.hpp"
#include "defaults.hpp"
#include "timezone_data.hpp"
#define NTP_SERVER "pool.ntp.org"
#define DEFAULT_TIME_OFFSET_SECONDS 3600
#ifndef MCP_DEV_ADDR
#define MCP_DEV_ADDR 0x20
#include "timers.hpp"
#include "v2_notify.hpp"
#include "webserver.hpp"
#include "button_handler.hpp"
#include "bitaxe_fetch.hpp"
#include "mining_pool_stats_fetch.hpp"
#include "epd.hpp"
#ifdef HAS_FRONTLIGHT
#include <BH1750.h>
#include <PCA9685.h>
#endif
extern Preferences preferences;
extern QueueHandle_t workQueue;
extern PriceNotify::PriceNotifyManager priceManager;
void setup();
void syncTime();
void setTimezone(String timezone);
uint getLastTimeSync();
void setupPreferences();
void setupWebsocketClients(void *pvParameters);
void setupHardware();
void setupConfig();
void setupWifi();
void setupOTA();
void setupMDNS();
void setupDataSources();
void stopDataSources();
void restartDataSources();
void setupTasks();
void setupTimers();
void setupLittleFS();
void setupPreferences();
void setupWDT();
void setupWorkQueue();
void setupLedHandler();
void setupScreenHandler();
void setupWebserver();
void setupNostrNotify();
void setupBlockNotify();
void setupV2Notify();
void setupPriceNotify();
void setupHardware();
void finishSetup();
void setupMcp();
void syncTime();
void handleOTA();
void handleWifi();
void handleWDT();
void handleWorkQueue();
void setWifiTxPower(int8_t power);
void onWifiEvent(WiFiEvent_t event);
void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info);
DataSourceType getDataSource();
void setDataSource(DataSourceType source);
String getMyHostname();
uint getLastTimeSync();
bool debugLogEnabled();
#ifdef HAS_FRONTLIGHT
extern BH1750 bh1750;
extern bool hasLuxSensor;
float getLightLevel();
bool hasLightLevel();
extern PCA9685 flArray;
#endif
String getMyHostname();
std::vector<ScreenMapping> getScreenNameMap();
std::vector<std::string> getLocalUrl();
// bool improv_connectWifi(std::string ssid, std::string password);
// void improvGetAvailableWifiNetworks();
// bool onImprovCommandCallback(improv::ImprovCommand cmd);
// void onImprovErrorCallback(improv::Error err);
// void improv_set_state(improv::State state);
// void improv_send_response(std::vector<uint8_t> &response);
// void improv_set_error(improv::Error error);
//void addCurrencyMappings(const std::vector<std::string>& currencies);
std::vector<std::string> getActiveCurrencies();
std::vector<std::string> getAvailableCurrencies();
bool isActiveCurrency(std::string &currency);
void WiFiEvent(WiFiEvent_t event, WiFiEventInfo_t info);
String getHwRev();
bool isWhiteVersion();
String getFsRev();
bool debugLogEnabled();
void addScreenMapping(int value, const char* name);
// void addScreenMapping(int value, const String& name);
// void addScreenMapping(int value, const std::string& name);
int findScreenIndexByValue(int value);
String replaceAmbiguousChars(String input);
const char* getFirmwareFilename();
const char* getWebUiFilename();
// void loadIcons();
extern Preferences preferences;
extern MCP23017 mcp1;
#ifdef IS_BTCLOCK_V8
extern MCP23017 mcp2;
#endif
#ifdef HAS_FRONTLIGHT
extern PCA9685 flArray;
#endif
// Expose DataSourceType enum
extern DataSourceType getDataSource();
extern void setDataSource(DataSourceType source);
#define NTP_SERVER "pool.ntp.org"
#define DEFAULT_TIME_OFFSET_SECONDS 3600

View file

@ -24,7 +24,6 @@
#define DEFAULT_V2_SOURCE_CURRENCY CURRENCY_USD
#define DEFAULT_TIME_OFFSET_SECONDS 3600
#define DEFAULT_TZ_STRING "Europe/Amsterdam"
#define DEFAULT_HOSTNAME_PREFIX "btclock"
#define DEFAULT_MEMPOOL_INSTANCE "mempool.space"
@ -47,8 +46,8 @@
#define DEFAULT_LUX_LIGHT_TOGGLE 128
#define DEFAULT_FL_OFF_WHEN_DARK true
#define DEFAULT_FL_ALWAYS_ON true
#define DEFAULT_FL_FLASH_ON_UPDATE true
#define DEFAULT_FL_ALWAYS_ON false
#define DEFAULT_FL_FLASH_ON_UPDATE false
#define DEFAULT_LED_STATUS false
#define DEFAULT_TIMER_ACTIVE true
@ -61,7 +60,6 @@
#define DEFAULT_MINING_POOL_STATS_ENABLED false
#define DEFAULT_MINING_POOL_NAME "ocean"
#define DEFAULT_MINING_POOL_USER "38Qkkei3SuF1Eo45BaYmRHUneRD54yyTFy" // Random actual Ocean hasher
#define DEFAULT_LOCAL_POOL_ENDPOINT "umbrel.local:2019"
#define DEFAULT_ZAP_NOTIFY_ENABLED false
#define DEFAULT_ZAP_NOTIFY_PUBKEY "b5127a08cf33616274800a4387881a9f98e04b9c37116e92de5250498635c422"

6
src/lib/globals.hpp Normal file
View file

@ -0,0 +1,6 @@
#pragma once
#include "price_notify/price_notify.hpp"
// Global instances
extern PriceNotify::PriceNotifyManager priceManager;

View file

@ -535,7 +535,7 @@ void LedHandler::frontlightSetBrightness(uint brightness) {
}
for (int ledPin = 0; ledPin <= NUM_SCREENS; ledPin++) {
flArray.setPWM(ledPin + 1, 0, brightness);
flArray.setPWM(ledPin, 0, brightness);
}
}
@ -543,7 +543,7 @@ std::vector<uint16_t> LedHandler::frontlightGetStatus() {
std::vector<uint16_t> statuses;
for (int ledPin = 1; ledPin <= NUM_SCREENS; ledPin++) {
uint16_t a = 0, b = 0;
flArray.getPWM(ledPin + 1, &a, &b);
flArray.getPWM(ledPin, &a, &b);
statuses.push_back(round(b - a / 4096));
}
return statuses;
@ -576,7 +576,7 @@ void LedHandler::frontlightFadeInAll(int flDelayTime, bool staggered) {
} else {
for (int dutyCycle = 0; dutyCycle <= maxBrightness; dutyCycle += FL_FADE_STEP) {
for (int ledPin = 0; ledPin <= NUM_SCREENS; ledPin++) {
flArray.setPWM(ledPin + 1, 0, dutyCycle);
flArray.setPWM(ledPin, 0, dutyCycle);
}
vTaskDelay(pdMS_TO_TICKS(flDelayTime));
}
@ -611,7 +611,7 @@ void LedHandler::frontlightFadeOutAll(int flDelayTime, bool staggered) {
} else {
for (int dutyCycle = preferences.getUInt("flMaxBrightness"); dutyCycle >= 0; dutyCycle -= FL_FADE_STEP) {
for (int ledPin = 0; ledPin <= NUM_SCREENS; ledPin++) {
flArray.setPWM(ledPin + 1, 0, dutyCycle);
flArray.setPWM(ledPin, 0, dutyCycle);
}
vTaskDelay(pdMS_TO_TICKS(flDelayTime));
}
@ -628,7 +628,7 @@ void LedHandler::frontlightFadeIn(uint num, int flDelayTime) {
}
for (int dutyCycle = 0; dutyCycle <= preferences.getUInt("flMaxBrightness"); dutyCycle += 5) {
flArray.setPWM(num + 1, 0, dutyCycle);
flArray.setPWM(num, 0, dutyCycle);
vTaskDelay(pdMS_TO_TICKS(flDelayTime));
}
}
@ -639,7 +639,7 @@ void LedHandler::frontlightFadeOut(uint num, int flDelayTime) {
}
for (int dutyCycle = preferences.getUInt("flMaxBrightness"); dutyCycle >= 0; dutyCycle -= 5) {
flArray.setPWM(num + 1, 0, dutyCycle);
flArray.setPWM(num, 0, dutyCycle);
vTaskDelay(pdMS_TO_TICKS(flDelayTime));
}
}

View file

@ -5,7 +5,6 @@ const char* PoolFactory::MINING_POOL_NAME_NODERUNNERS = "noderunners";
const char* PoolFactory::MINING_POOL_NAME_BRAIINS = "braiins";
const char* PoolFactory::MINING_POOL_NAME_SATOSHI_RADIO = "satoshi_radio";
const char* PoolFactory::MINING_POOL_NAME_PUBLIC_POOL = "public_pool";
const char* PoolFactory::MINING_POOL_NAME_LOCAL_PUBLIC_POOL = "local_public_pool";
const char* PoolFactory::MINING_POOL_NAME_GOBRRR_POOL = "gobrrr_pool";
const char* PoolFactory::MINING_POOL_NAME_CKPOOL = "ckpool";
const char* PoolFactory::MINING_POOL_NAME_EU_CKPOOL = "eu_ckpool";
@ -18,7 +17,6 @@ std::unique_ptr<MiningPoolInterface> PoolFactory::createPool(const std::string&
{MINING_POOL_NAME_BRAIINS, []() { return std::make_unique<BraiinsPool>(); }},
{MINING_POOL_NAME_SATOSHI_RADIO, []() { return std::make_unique<SatoshiRadioPool>(); }},
{MINING_POOL_NAME_PUBLIC_POOL, []() { return std::make_unique<PublicPool>(); }},
{MINING_POOL_NAME_LOCAL_PUBLIC_POOL, []() { return std::make_unique<LocalPublicPool>(); }},
{MINING_POOL_NAME_GOBRRR_POOL, []() { return std::make_unique<GoBrrrPool>(); }},
{MINING_POOL_NAME_CKPOOL, []() { return std::make_unique<CKPool>(); }},
{MINING_POOL_NAME_EU_CKPOOL, []() { return std::make_unique<EUCKPool>(); }}

View file

@ -10,7 +10,6 @@
#include "ocean/ocean_pool.hpp"
#include "satoshi_radio/satoshi_radio_pool.hpp"
#include "public_pool/public_pool.hpp"
#include "public_pool/local_public_pool.hpp"
#include "gobrrr_pool/gobrrr_pool.hpp"
#include "ckpool/ckpool.hpp"
#include "ckpool/eu_ckpool.hpp"
@ -29,7 +28,6 @@ class PoolFactory {
MINING_POOL_NAME_SATOSHI_RADIO,
MINING_POOL_NAME_BRAIINS,
MINING_POOL_NAME_PUBLIC_POOL,
MINING_POOL_NAME_LOCAL_PUBLIC_POOL,
MINING_POOL_NAME_GOBRRR_POOL,
MINING_POOL_NAME_CKPOOL,
MINING_POOL_NAME_EU_CKPOOL
@ -57,7 +55,6 @@ class PoolFactory {
static const char* MINING_POOL_NAME_BRAIINS;
static const char* MINING_POOL_NAME_SATOSHI_RADIO;
static const char* MINING_POOL_NAME_PUBLIC_POOL;
static const char* MINING_POOL_NAME_LOCAL_PUBLIC_POOL;
static const char* MINING_POOL_NAME_GOBRRR_POOL;
static const char* MINING_POOL_NAME_CKPOOL;
static const char* MINING_POOL_NAME_EU_CKPOOL;

View file

@ -1,11 +0,0 @@
#include "local_public_pool.hpp"
#include "lib/shared.hpp"
#include "lib/defaults.hpp"
std::string LocalPublicPool::getEndpoint() const {
return preferences.getString("localPoolEndpoint", DEFAULT_LOCAL_POOL_ENDPOINT).c_str();
}
std::string LocalPublicPool::getApiUrl() const {
return "http://" + getEndpoint() + "/api/client/" + poolUser;
}

View file

@ -1,11 +0,0 @@
#pragma once
#include "public_pool.hpp"
class LocalPublicPool : public PublicPool {
public:
std::string getApiUrl() const override;
std::string getDisplayLabel() const override { return "LOCAL/POOL"; }
private:
std::string getEndpoint() const;
};

View file

@ -1,5 +1,6 @@
#include "nostr_notify.hpp"
#include "led_handler.hpp"
#include "globals.hpp"
std::vector<nostr::NostrPool *> pools;
nostr::Transport *transport;
@ -41,7 +42,7 @@ void setupNostrNotify(bool asDatasource, bool zapNotify)
{relay},
{// First filter
{
{"kinds", {"12203"}},
{"kinds", {"1"}},
{"since", {String(getMinutesAgo(60))}},
{"authors", {pubKey}},
}},
@ -79,9 +80,8 @@ void nostrTask(void *pvParameters)
{
DataSourceType dataSource = getDataSource();
if(dataSource == NOSTR_SOURCE) {
auto& blockNotify = BlockNotify::getInstance();
int blockFetch = blockNotify.fetchLatestBlock();
blockNotify.processNewBlock(blockFetch);
int blockFetch = getBlockFetch();
processNewBlock(blockFetch);
}
while (1)
@ -146,7 +146,6 @@ void handleNostrEventCallback(const String &subId, nostr::SignedNostrEvent *even
// Use direct value access instead of multiple comparisons
String typeValue;
uint medianFee = 0;
uint blockHeight = 0;
for (JsonArray tag : tags) {
if (tag.size() != 2) continue;
@ -167,31 +166,20 @@ void handleNostrEventCallback(const String &subId, nostr::SignedNostrEvent *even
medianFee = tag[1].as<uint>();
}
break;
case 'b': // blockHeight
if (strcmp(key, "block") == 0) {
blockHeight = tag[1].as<uint>();
}
break;
}
}
// Process the data
if (!typeValue.isEmpty()) {
if (typeValue == "priceUsd") {
processNewPrice(obj["content"].as<uint>(), CURRENCY_USD);
if (blockHeight != 0) {
auto& blockNotify = BlockNotify::getInstance();
blockNotify.processNewBlock(blockHeight);
}
priceManager.processNewPrice(obj["content"].as<uint>(), static_cast<PriceNotify::Currency>(CURRENCY_USD));
}
else if (typeValue == "blockHeight") {
auto& blockNotify = BlockNotify::getInstance();
blockNotify.processNewBlock(obj["content"].as<uint>());
processNewBlock(obj["content"].as<uint>());
}
if (medianFee != 0) {
auto& blockNotify = BlockNotify::getInstance();
blockNotify.processNewBlockFee(medianFee);
processNewBlockFee(medianFee);
}
}
}

View file

@ -10,8 +10,8 @@
#include "NostrEvent.h"
#include "NostrPool.h"
#include "price_notify.hpp"
#include "block_notify.hpp"
#include "price_notify/price_notify.hpp"
#include "lib/timers.hpp"
void setupNostrNotify(bool asDatasource, bool zapNotify);

View file

@ -74,8 +74,8 @@ void onOTAStart()
ButtonHandler::suspendTask();
// stopWebServer();
auto& blockNotify = BlockNotify::getInstance();
blockNotify.stop();
stopBlockNotify();
stopPriceNotify();
}
void handleOTATask(void *parameter)

View file

@ -1,184 +0,0 @@
#include "price_notify.hpp"
const char *wsServerPrice = "wss://ws.kraken.com/v2";
WebSocketsClient webSocket;
uint currentPrice = 90000;
unsigned long int lastPriceUpdate;
bool priceNotifyInit = false;
std::map<char, std::uint64_t> currencyMap;
std::map<char, unsigned long int> lastUpdateMap;
TaskHandle_t priceNotifyTaskHandle;
void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length);
void setupPriceNotify()
{
webSocket.beginSSL("ws.kraken.com", 443, "/v2");
webSocket.onEvent([](WStype_t type, uint8_t * payload, size_t length) {
onWebsocketPriceEvent(type, payload, length);
});
webSocket.setReconnectInterval(5000);
webSocket.enableHeartbeat(15000, 3000, 2);
setupPriceNotifyTask();
}
void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length) {
switch(type) {
case WStype_DISCONNECTED:
Serial.println(F("Price WS Connection Closed"));
break;
case WStype_CONNECTED:
{
Serial.println("Connected to " + String(wsServerPrice));
JsonDocument doc;
doc["method"] = "subscribe";
JsonObject params = doc["params"].to<JsonObject>();
params["channel"] = "ticker";
params["symbol"][0] = "BTC/USD";
webSocket.sendTXT(doc.as<String>().c_str());
break;
}
case WStype_TEXT:
{
JsonDocument doc;
deserializeJson(doc, (char *)payload);
if (doc["data"][0].is<JsonObject>())
{
float price = doc["data"][0]["last"].as<float>();
uint roundedPrice = round(price);
if (currentPrice != roundedPrice)
{
processNewPrice(roundedPrice, CURRENCY_USD);
}
}
break;
}
case WStype_BIN:
break;
case WStype_ERROR:
case WStype_FRAGMENT_TEXT_START:
case WStype_FRAGMENT_BIN_START:
case WStype_FRAGMENT:
case WStype_PING:
case WStype_PONG:
case WStype_FRAGMENT_FIN:
break;
}
}
void processNewPrice(uint newPrice, char currency)
{
uint minSecPriceUpd = preferences.getUInt(
"minSecPriceUpd", DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE);
uint currentTime = esp_timer_get_time() / 1000000;
if (lastUpdateMap.find(currency) == lastUpdateMap.end() ||
(currentTime - lastUpdateMap[currency]) > minSecPriceUpd)
{
currencyMap[currency] = newPrice;
// Store price in preferences if enough time has passed
if (lastUpdateMap[currency] == 0 || (currentTime - lastUpdateMap[currency]) > 120)
{
String prefKey = String("lastPrice_") + getCurrencyCode(currency).c_str();
preferences.putUInt(prefKey.c_str(), newPrice);
}
lastUpdateMap[currency] = currentTime;
if (workQueue != nullptr && (ScreenHandler::getCurrentScreen() == SCREEN_BTC_TICKER ||
ScreenHandler::getCurrentScreen() == SCREEN_SATS_PER_CURRENCY ||
ScreenHandler::getCurrentScreen() == SCREEN_MARKET_CAP))
{
WorkItem priceUpdate = {TASK_PRICE_UPDATE, currency};
xQueueSend(workQueue, &priceUpdate, portMAX_DELAY);
}
}
}
void loadStoredPrices()
{
// Load prices for all supported currencies
std::vector<std::string> currencies = getAvailableCurrencies();
for (const std::string &currency : currencies) {
// Get first character as the currency identifier
String prefKey = String("lastPrice_") + currency.c_str();
uint storedPrice = preferences.getUInt(prefKey.c_str(), 0);
if (storedPrice > 0) {
currencyMap[getCurrencyChar(currency)] = storedPrice;
// Initialize lastUpdateMap to 0 so next update will store immediately
lastUpdateMap[getCurrencyChar(currency)] = 0;
}
}
}
uint getLastPriceUpdate(char currency)
{
if (lastUpdateMap.find(currency) == lastUpdateMap.end())
{
return 0;
}
return lastUpdateMap[currency];
}
uint getPrice(char currency)
{
if (currencyMap.find(currency) == currencyMap.end())
{
return 0;
}
return currencyMap[currency];
}
void setPrice(uint newPrice, char currency)
{
currencyMap[currency] = newPrice;
}
bool isPriceNotifyConnected()
{
return webSocket.isConnected();
}
bool getPriceNotifyInit()
{
return priceNotifyInit;
}
void stopPriceNotify()
{
webSocket.disconnect();
if (priceNotifyTaskHandle != NULL) {
vTaskDelete(priceNotifyTaskHandle);
priceNotifyTaskHandle = NULL;
}
}
void restartPriceNotify()
{
stopPriceNotify();
setupPriceNotify();
}
void taskPriceNotify(void *pvParameters)
{
for (;;)
{
webSocket.loop();
vTaskDelay(10 / portTICK_PERIOD_MS);
}
}
void setupPriceNotifyTask()
{
xTaskCreate(taskPriceNotify, "priceNotify", (6 * 1024), NULL, tskIDLE_PRIORITY,
&priceNotifyTaskHandle);
}

View file

@ -1,29 +0,0 @@
#pragma once
#include <Arduino.h>
#include <ArduinoJson.h>
#include <WebSocketsClient.h>
#include <string>
#include "lib/screen_handler.hpp"
extern TaskHandle_t priceNotifyTaskHandle;
void setupPriceNotify();
void setupPriceNotifyTask();
void taskPriceNotify(void *pvParameters);
void onWebsocketPriceEvent(WStype_t type, uint8_t * payload, size_t length);
uint getPrice(char currency);
void setPrice(uint newPrice, char currency);
void processNewPrice(uint newPrice, char currency);
bool isPriceNotifyConnected();
void stopPriceNotify();
void restartPriceNotify();
bool getPriceNotifyInit();
uint getLastPriceUpdate(char currency);
void loadStoredPrices();

View file

@ -0,0 +1,53 @@
#pragma once
#include <Arduino.h>
#include <WebSocketsClient.h>
#include <functional>
#include <map>
#include <string>
namespace PriceNotify {
using std::function;
using std::map;
using std::string;
// Convert char-based currency to enum for type safety
enum class Currency : char {
USD = '$',
EUR = '[',
GBP = ']',
JPY = '^',
AUD = '_',
CAD = '`'
};
class IPriceSource {
public:
virtual ~IPriceSource() = default;
// Initialize and connect to the websocket
virtual void connect() = 0;
// Disconnect and cleanup
virtual void disconnect() = 0;
// Check connection status
virtual bool isConnected() const = 0;
// Get the last known price for a currency
virtual uint64_t getPrice(Currency currency) const = 0;
// Get the last update timestamp for a currency
virtual uint32_t getLastUpdate(Currency currency) const = 0;
// Set callback for price updates
virtual void onPriceUpdate(function<void(Currency, uint64_t)> callback) = 0;
// Process websocket loop - should be called regularly
virtual void loop() = 0;
protected:
function<void(Currency, uint64_t)> priceUpdateCallback;
};
} // namespace PriceNotify

View file

@ -0,0 +1,115 @@
#include "price_notify.hpp"
#include <esp_timer.h>
using std::make_unique;
namespace PriceNotify {
PriceNotifyManager::PriceNotifyManager() : currentSource(PriceSource::NONE) {}
PriceNotifyManager::~PriceNotifyManager() {
disconnect();
}
void PriceNotifyManager::init(PriceSource source) {
currentSource = source;
setPriceSource(source);
}
void PriceNotifyManager::connect() {
if (priceSource) {
priceSource->connect();
}
}
void PriceNotifyManager::disconnect() {
if (priceSource) {
priceSource->disconnect();
}
}
bool PriceNotifyManager::isConnected() const {
return priceSource ? priceSource->isConnected() : false;
}
uint64_t PriceNotifyManager::getPrice(Currency currency) const {
if (priceSource) {
return priceSource->getPrice(currency);
}
// If no price source, return from internal storage
auto it = prices.find(currency);
return (it != prices.end()) ? it->second : 0;
}
uint32_t PriceNotifyManager::getLastUpdate(Currency currency) const {
if (priceSource) {
return priceSource->getLastUpdate(currency);
}
// If no price source, return from internal storage
auto it = lastUpdates.find(currency);
return (it != lastUpdates.end()) ? it->second : 0;
}
void PriceNotifyManager::onPriceUpdate(function<void(Currency, uint64_t)> callback) {
userCallback = callback;
if (priceSource) {
priceSource->onPriceUpdate([this](Currency currency, uint64_t price) {
if (userCallback) {
userCallback(currency, price);
}
});
}
}
void PriceNotifyManager::loop() {
if (priceSource) {
priceSource->loop();
}
}
void PriceNotifyManager::setPriceSource(PriceSource source) {
// Store the callback before destroying the old source
auto oldCallback = userCallback;
// Disconnect and destroy old source
if (priceSource) {
priceSource->disconnect();
}
// Create new source
switch (source) {
case PriceSource::COINCAP:
priceSource = make_unique<CoinCapSource>();
break;
case PriceSource::KRAKEN:
priceSource = make_unique<KrakenSource>();
break;
case PriceSource::NONE:
priceSource.reset();
break;
}
currentSource = source;
// Restore callback if it exists
if (oldCallback) {
onPriceUpdate(oldCallback);
}
}
PriceSource PriceNotifyManager::getCurrentSource() const {
return currentSource;
}
void PriceNotifyManager::processNewPrice(uint64_t price, Currency currency) {
// Store the price and timestamp
prices[currency] = price;
lastUpdates[currency] = esp_timer_get_time() / 1000000; // Current time in seconds
// If we have a callback, notify about the price update
if (userCallback) {
userCallback(currency, price);
}
}
} // namespace PriceNotify

View file

@ -0,0 +1,68 @@
#pragma once
#include "interfaces/price_source.hpp"
#include "sources/coincap_source.hpp"
#include "sources/kraken_source.hpp"
#include <memory>
#include <map>
namespace PriceNotify {
using std::unique_ptr;
using std::function;
using std::map;
enum class PriceSource {
NONE, // Added for when no explicit source is set
COINCAP,
KRAKEN
};
class PriceNotifyManager {
public:
PriceNotifyManager();
~PriceNotifyManager();
// Initialize with a specific price source
void init(PriceSource source);
// Connect to the price source
void connect();
// Disconnect from the price source
void disconnect();
// Check if connected to price source
bool isConnected() const;
// Get the last known price for a currency
uint64_t getPrice(Currency currency) const;
// Get the last update timestamp for a currency
uint32_t getLastUpdate(Currency currency) const;
// Set callback for price updates
void onPriceUpdate(function<void(Currency, uint64_t)> callback);
// Process websocket loop - should be called regularly
void loop();
// Change the price source
void setPriceSource(PriceSource source);
// Get current price source
PriceSource getCurrentSource() const;
// Process new price from external source
void processNewPrice(uint64_t price, Currency currency);
private:
unique_ptr<IPriceSource> priceSource;
PriceSource currentSource;
function<void(Currency, uint64_t)> userCallback;
// For storing prices when no explicit source is set
map<Currency, uint64_t> prices;
map<Currency, uint32_t> lastUpdates;
};
} // namespace PriceNotify

View file

@ -0,0 +1,96 @@
#include "coincap_source.hpp"
namespace PriceNotify {
CoinCapSource* CoinCapSource::instance = nullptr;
CoinCapSource::CoinCapSource() : connected(false) {
instance = this;
}
CoinCapSource::~CoinCapSource() {
disconnect();
instance = nullptr;
}
void CoinCapSource::connect() {
webSocket.beginSSL("ws.coincap.io", 443, "/prices?assets=bitcoin");
webSocket.onEvent([](WStype_t type, uint8_t* payload, size_t length) {
if (instance) {
instance->handleWebSocketEvent(type, payload, length);
}
});
webSocket.setReconnectInterval(5000);
webSocket.enableHeartbeat(15000, 3000, 2);
}
void CoinCapSource::disconnect() {
webSocket.disconnect();
connected = false;
}
bool CoinCapSource::isConnected() const {
return connected && webSocket.isConnected();
}
uint64_t CoinCapSource::getPrice(Currency currency) const {
auto it = prices.find(currency);
return (it != prices.end()) ? it->second : 0;
}
uint32_t CoinCapSource::getLastUpdate(Currency currency) const {
auto it = lastUpdates.find(currency);
return (it != lastUpdates.end()) ? it->second : 0;
}
void CoinCapSource::onPriceUpdate(function<void(Currency, uint64_t)> callback) {
priceUpdateCallback = callback;
}
void CoinCapSource::loop() {
webSocket.loop();
}
void CoinCapSource::handleWebSocketEvent(WStype_t type, uint8_t* payload, size_t length) {
switch(type) {
case WStype_DISCONNECTED:
Serial.println(F("CoinCap WS Disconnected"));
connected = false;
break;
case WStype_CONNECTED:
Serial.println(F("CoinCap WS Connected"));
connected = true;
break;
case WStype_TEXT:
processMessage((char*)payload);
break;
default:
break;
}
}
void CoinCapSource::processMessage(const char* payload) {
StaticJsonDocument<512> doc;
DeserializationError error = deserializeJson(doc, payload);
if (error) {
Serial.println(F("Failed to parse CoinCap message"));
return;
}
if (doc["bitcoin"].is<JsonObject>()) {
uint64_t price = doc["bitcoin"].as<uint64_t>();
uint32_t timestamp = esp_timer_get_time() / 1000000; // Current time in seconds
prices[Currency::USD] = price;
lastUpdates[Currency::USD] = timestamp;
if (priceUpdateCallback) {
priceUpdateCallback(Currency::USD, price);
}
}
}
} // namespace PriceNotify

View file

@ -0,0 +1,36 @@
#pragma once
#include "../interfaces/price_source.hpp"
#include <ArduinoJson.h>
#include <esp_timer.h>
namespace PriceNotify {
using std::map;
class CoinCapSource : public IPriceSource {
public:
CoinCapSource();
~CoinCapSource() override;
void connect() override;
void disconnect() override;
bool isConnected() const override;
uint64_t getPrice(Currency currency) const override;
uint32_t getLastUpdate(Currency currency) const override;
void onPriceUpdate(function<void(Currency, uint64_t)> callback) override;
void loop() override;
private:
static void onWebSocketEvent(WStype_t type, uint8_t* payload, size_t length);
void handleWebSocketEvent(WStype_t type, uint8_t* payload, size_t length);
void processMessage(const char* payload);
WebSocketsClient webSocket;
map<Currency, uint64_t> prices;
map<Currency, uint32_t> lastUpdates;
bool connected;
static CoinCapSource* instance; // For callback handling
};
} // namespace PriceNotify

View file

@ -0,0 +1,118 @@
#include "kraken_source.hpp"
namespace PriceNotify {
KrakenSource* KrakenSource::instance = nullptr;
KrakenSource::KrakenSource() : connected(false) {
instance = this;
}
KrakenSource::~KrakenSource() {
disconnect();
instance = nullptr;
}
void KrakenSource::connect() {
webSocket.beginSSL("ws.kraken.com", 443, "/ws");
webSocket.onEvent([](WStype_t type, uint8_t* payload, size_t length) {
if (instance) {
instance->handleWebSocketEvent(type, payload, length);
}
});
webSocket.setReconnectInterval(5000);
webSocket.enableHeartbeat(15000, 3000, 2);
}
void KrakenSource::disconnect() {
webSocket.disconnect();
connected = false;
}
bool KrakenSource::isConnected() const {
return connected && webSocket.isConnected();
}
uint64_t KrakenSource::getPrice(Currency currency) const {
auto it = prices.find(currency);
return (it != prices.end()) ? it->second : 0;
}
uint32_t KrakenSource::getLastUpdate(Currency currency) const {
auto it = lastUpdates.find(currency);
return (it != lastUpdates.end()) ? it->second : 0;
}
void KrakenSource::onPriceUpdate(function<void(Currency, uint64_t)> callback) {
priceUpdateCallback = callback;
}
void KrakenSource::loop() {
webSocket.loop();
}
void KrakenSource::handleWebSocketEvent(WStype_t type, uint8_t* payload, size_t length) {
switch(type) {
case WStype_DISCONNECTED:
Serial.println(F("Kraken WS Disconnected"));
connected = false;
break;
case WStype_CONNECTED:
Serial.println(F("Kraken WS Connected"));
connected = true;
subscribe();
break;
case WStype_TEXT:
processMessage((char*)payload);
break;
default:
break;
}
}
void KrakenSource::subscribe() {
// Subscribe to XBT/USD ticker
StaticJsonDocument<256> doc;
doc["event"] = "subscribe";
JsonArray pair = doc.createNestedArray("pair");
pair.add("XBT/USD");
doc["subscription"]["name"] = "ticker";
String message;
serializeJson(doc, message);
webSocket.sendTXT(message);
}
void KrakenSource::processMessage(const char* payload) {
StaticJsonDocument<512> doc;
DeserializationError error = deserializeJson(doc, payload);
if (error) {
Serial.println(F("Failed to parse Kraken message"));
return;
}
// Check if it's a ticker update (array format)
if (doc.is<JsonArray>() && doc.size() >= 4) {
JsonArray arr = doc.as<JsonArray>();
if (arr[2] == "ticker" && arr[3] == "XBT/USD") {
JsonObject tickerData = arr[1].as<JsonObject>();
if (tickerData.containsKey("c")) {
// Get the first value from the "c" array which is the last trade price
uint64_t price = tickerData["c"][0].as<float>() * 100; // Convert to cents
uint32_t timestamp = esp_timer_get_time() / 1000000; // Current time in seconds
prices[Currency::USD] = price;
lastUpdates[Currency::USD] = timestamp;
if (priceUpdateCallback) {
priceUpdateCallback(Currency::USD, price);
}
}
}
}
}
} // namespace PriceNotify

View file

@ -0,0 +1,37 @@
#pragma once
#include "../interfaces/price_source.hpp"
#include <ArduinoJson.h>
#include <esp_timer.h>
namespace PriceNotify {
using std::map;
class KrakenSource : public IPriceSource {
public:
KrakenSource();
~KrakenSource() override;
void connect() override;
void disconnect() override;
bool isConnected() const override;
uint64_t getPrice(Currency currency) const override;
uint32_t getLastUpdate(Currency currency) const override;
void onPriceUpdate(function<void(Currency, uint64_t)> callback) override;
void loop() override;
private:
static void onWebSocketEvent(WStype_t type, uint8_t* payload, size_t length);
void handleWebSocketEvent(WStype_t type, uint8_t* payload, size_t length);
void processMessage(const char* payload);
void subscribe();
WebSocketsClient webSocket;
map<Currency, uint64_t> prices;
map<Currency, uint32_t> lastUpdates;
bool connected;
static KrakenSource* instance; // For callback handling
};
} // namespace PriceNotify

View file

@ -241,7 +241,7 @@ void workerTask(void *pvParameters) {
case TASK_PRICE_UPDATE: {
uint currency = ScreenHandler::getCurrentCurrency();
uint price = getPrice(currency);
uint price = priceManager.getPrice(static_cast<PriceNotify::Currency>(currency));
if (currentScreenValue == SCREEN_BTC_TICKER) {
taskEpdContent = parsePriceData(price, currency, preferences.getBool("suffixPrice", DEFAULT_SUFFIX_PRICE),
@ -251,8 +251,9 @@ void workerTask(void *pvParameters) {
} else if (currentScreenValue == SCREEN_SATS_PER_CURRENCY) {
taskEpdContent = parseSatsPerCurrency(price, currency, preferences.getBool("useSatsSymbol", DEFAULT_USE_SATS_SYMBOL));
} else {
auto& blockNotify = BlockNotify::getInstance();
taskEpdContent = parseMarketCap(blockNotify.getBlockHeight(), price, currency, preferences.getBool("mcapBigChar", DEFAULT_MCAP_BIG_CHAR));
taskEpdContent =
parseMarketCap(getBlockHeight(), price, currency,
preferences.getBool("mcapBigChar", DEFAULT_MCAP_BIG_CHAR));
}
EPDManager::getInstance().setContent(taskEpdContent);
@ -260,19 +261,16 @@ void workerTask(void *pvParameters) {
}
case TASK_FEE_UPDATE: {
if (currentScreenValue == SCREEN_BLOCK_FEE_RATE) {
auto& blockNotify = BlockNotify::getInstance();
taskEpdContent = parseBlockFees(static_cast<std::uint16_t>(blockNotify.getBlockMedianFee()));
taskEpdContent = parseBlockFees(static_cast<std::uint16_t>(getBlockMedianFee()));
EPDManager::getInstance().setContent(taskEpdContent);
}
break;
}
case TASK_BLOCK_UPDATE: {
if (currentScreenValue != SCREEN_HALVING_COUNTDOWN) {
auto& blockNotify = BlockNotify::getInstance();
taskEpdContent = parseBlockHeight(blockNotify.getBlockHeight());
taskEpdContent = parseBlockHeight(getBlockHeight());
} else {
auto& blockNotify = BlockNotify::getInstance();
taskEpdContent = parseHalvingCountdown(blockNotify.getBlockHeight(), preferences.getBool("useBlkCountdown", DEFAULT_USE_BLOCK_COUNTDOWN));
taskEpdContent = parseHalvingCountdown(getBlockHeight(), preferences.getBool("useBlkCountdown", DEFAULT_USE_BLOCK_COUNTDOWN));
}
if (currentScreenValue == SCREEN_HALVING_COUNTDOWN ||

View file

@ -40,39 +40,39 @@
// "MrY=\n"
// "-----END CERTIFICATE-----\n";
// const char* isrg_root_x1cert = R"EOF(
// -----BEGIN CERTIFICATE-----
// MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
// TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
// cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
// WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
// ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
// MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
// h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
// 0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
// A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
// T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
// B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
// B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
// KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
// OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
// jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
// qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
// rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
// HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
// hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
// ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
// 3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
// NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
// ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
// TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
// jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
// oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
// 4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
// mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
// emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
// -----END CERTIFICATE-----
// )EOF";
const char* isrg_root_x1cert = R"EOF(
-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
-----END CERTIFICATE-----
)EOF";
#ifdef TEST_SCREENS

View file

@ -68,7 +68,7 @@ const int usPerSecond = 1000000;
const int usPerMinute = 60 * usPerSecond;
// extern const char *github_root_ca;
// extern const char *isrg_root_x1cert;
extern const char *isrg_root_x1cert;
extern const uint8_t rootca_crt_bundle_start[] asm("_binary_x509_crt_bundle_start");
// extern const uint8_t ocean_logo_comp[] asm("_binary_ocean_gz_start");

View file

@ -1,4 +1,5 @@
#include "v2_notify.hpp"
#include "globals.hpp"
using namespace V2Notify;
@ -127,42 +128,34 @@ namespace V2Notify
void handleV2Message(JsonDocument doc)
{
if (doc["blockheight"].is<uint>())
if (doc["blockheight"].is<JsonObject>())
{
uint newBlockHeight = doc["blockheight"].as<uint>();
if (newBlockHeight == BlockNotify::getInstance().getBlockHeight())
if (newBlockHeight == getBlockHeight())
{
return;
}
if (debugLogEnabled()) {
Serial.print(F("processNewBlock "));
Serial.println(newBlockHeight);
processNewBlock(newBlockHeight);
}
BlockNotify::getInstance().processNewBlock(newBlockHeight);
}
else if (doc["blockfee"].is<uint>())
else if (doc["blockfee"].is<JsonObject>())
{
uint medianFee = doc["blockfee"].as<uint>();
if (debugLogEnabled()) {
Serial.print(F("processNewBlockFee "));
Serial.println(medianFee);
}
BlockNotify::getInstance().processNewBlockFee(medianFee);
processNewBlockFee(medianFee);
}
else if (doc["price"].is<JsonObject>())
{
// Iterate through the key-value pairs of the "price" object
for (JsonPair kv : doc["price"].as<JsonObject>())
{
const char *currency = kv.key().c_str();
uint newPrice = kv.value().as<uint>();
processNewPrice(newPrice, getCurrencyChar(currency));
// Convert currency string to PriceNotify::Currency using data_handler's conversion
char currencyChar = getCurrencyChar(currency);
priceManager.processNewPrice(newPrice, static_cast<PriceNotify::Currency>(currencyChar));
}
}
}
@ -172,7 +165,7 @@ namespace V2Notify
for (;;)
{
webSocket.loop();
vTaskDelay(pdMS_TO_TICKS(10));
vTaskDelay(10 / portTICK_PERIOD_MS);
}
}

View file

@ -5,7 +5,7 @@
static const char* JSON_CONTENT = "application/json";
static const char *const PROGMEM strSettings[] = {
"hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay", "bitaxeHostname", "miningPoolName", "miningPoolUser", "nostrZapPubkey", "httpAuthUser", "httpAuthPass", "gitReleaseUrl", "poolLogosUrl", "ceEndpoint", "fontName", "localPoolEndpoint", "tzString"};
"hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay", "bitaxeHostname", "miningPoolName", "miningPoolUser", "nostrZapPubkey", "httpAuthUser", "httpAuthPass", "gitReleaseUrl", "poolLogosUrl", "ceEndpoint", "fontName"};
static const char *const PROGMEM uintSettings[] = {"minSecPriceUpd", "fullRefreshMin", "ledBrightness", "flMaxBrightness", "flEffectDelay", "luxLightToggle", "wpTimeout"};
@ -30,8 +30,7 @@ TaskHandle_t eventSourceTaskHandle;
void setupWebserver()
{
events.onConnect([](AsyncEventSourceClient *client)
{ client->send("welcome", NULL, millis(), 1000);
});
{ client->send("welcome", NULL, millis(), 1000); });
server.addHandler(&events);
AsyncStaticWebHandler &staticHandler = server.serveStatic("/", LittleFS, "/").setDefaultFile("index.html");
@ -247,9 +246,8 @@ JsonDocument getStatusObject()
JsonObject conStatus = root["connectionStatus"].to<JsonObject>();
conStatus["price"] = isPriceNotifyConnected();
auto& blockNotify = BlockNotify::getInstance();
conStatus["blocks"] = blockNotify.isConnected();
conStatus["price"] = priceManager.isConnected();
conStatus["blocks"] = isBlockNotifyConnected();
conStatus["V2"] = V2Notify::isV2NotifyConnected();
conStatus["nostr"] = nostrConnected();
@ -298,13 +296,7 @@ JsonDocument getLedStatusObject()
uint blue = pixColor & 0xFF;
char hexColor[8];
snprintf(hexColor, sizeof(hexColor), "#%02X%02X%02X", red, green, blue);
// colors.add(hexColor);
JsonObject object = colors.add<JsonObject>();
object["red"] = red;
object["green"] = green;
object["blue"] = blue;
object["hex"] = hexColor;
colors.add(hexColor);
}
return root;
@ -313,18 +305,14 @@ JsonDocument getLedStatusObject()
void eventSourceUpdate() {
if (!events.count()) return;
static JsonDocument doc;
doc.clear();
JsonDocument root = getStatusObject();
root["leds"] = getLedStatusObject()["data"];
JsonDocument doc = getStatusObject();
doc["leds"] = getLedStatusObject()["data"];
// Get current EPD content directly as array
std::array<String, NUM_SCREENS> epdContent = EPDManager::getInstance().getCurrentContent();
// Add EPD content arrays
JsonArray data = root["data"].to<JsonArray>();
JsonArray data = doc["data"].to<JsonArray>();
// Copy array elements directly
for(const auto& content : epdContent) {
@ -332,7 +320,7 @@ void eventSourceUpdate() {
}
String buffer;
serializeJson(root, buffer);
serializeJson(doc, buffer);
events.send(buffer.c_str(), "status");
}
@ -698,8 +686,7 @@ void onApiSettingsGet(AsyncWebServerRequest *request)
root["fullRefreshMin"] =
preferences.getUInt("fullRefreshMin", DEFAULT_MINUTES_FULL_REFRESH);
root["wpTimeout"] = preferences.getUInt("wpTimeout", DEFAULT_WP_TIMEOUT);
//root["tzOffset"] = preferences.getInt("gmtOffset", DEFAULT_TIME_OFFSET_SECONDS) / 60;
root["tzString"] = preferences.getString("tzString", DEFAULT_TZ_STRING);
root["tzOffset"] = preferences.getInt("gmtOffset", DEFAULT_TIME_OFFSET_SECONDS) / 60;
// Add data source settings
root["dataSource"] = preferences.getUChar("dataSource", DEFAULT_DATA_SOURCE);
@ -708,9 +695,6 @@ void onApiSettingsGet(AsyncWebServerRequest *request)
root["mempoolInstance"] = preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE);
root["mempoolSecure"] = preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE);
// Local pool settings
root["localPoolEndpoint"] = preferences.getString("localPoolEndpoint", DEFAULT_LOCAL_POOL_ENDPOINT);
// Nostr settings (used for NOSTR_SOURCE or when zapNotify is enabled)
root["nostrPubKey"] = preferences.getString("nostrPubKey", DEFAULT_NOSTR_NPUB);
root["nostrRelay"] = preferences.getString("nostrRelay", DEFAULT_NOSTR_RELAY);
@ -922,7 +906,7 @@ void onApiStopDataSources(AsyncWebServerRequest *request)
request->beginResponseStream(JSON_CONTENT);
stopPriceNotify();
BlockNotify::getInstance().stop();
stopBlockNotify();
request->send(response);
}
@ -933,7 +917,9 @@ void onApiRestartDataSources(AsyncWebServerRequest *request)
request->beginResponseStream(JSON_CONTENT);
restartPriceNotify();
BlockNotify::getInstance().restart();
restartBlockNotify();
// setupPriceNotify();
// setupBlockNotify();
request->send(response);
}

View file

@ -11,7 +11,7 @@
#include "lib/block_notify.hpp"
#include "lib/led_handler.hpp"
#include "lib/price_notify.hpp"
#include "lib/screen_handler.hpp"
#include "webserver/OneParamRewrite.hpp"
#include "lib/mining_pool/pool_factory.hpp"

View file

@ -19,7 +19,6 @@
#include "ESPAsyncWebServer.h"
#include "lib/config.hpp"
#include "lib/led_handler.hpp"
#include "lib/block_notify.hpp"
uint wifiLostConnection;
uint priceNotifyLostConnection = 0;
@ -50,8 +49,7 @@ void handleBlockNotifyDisconnection() {
if ((getUptime() - blockNotifyLostConnection) > 300) { // 5 minutes timeout
Serial.println(F("Block notification connection lost for 5 minutes, restarting handler..."));
auto& blockNotify = BlockNotify::getInstance();
blockNotify.restart();
restartBlockNotify();
blockNotifyLostConnection = 0;
}
}
@ -94,14 +92,13 @@ void checkWiFiConnection() {
void checkMissedBlocks() {
Serial.println(F("Long time (45 min) since last block, checking if I missed anything..."));
auto& blockNotify = BlockNotify::getInstance();
int currentBlock = blockNotify.fetchLatestBlock();
int currentBlock = getBlockFetch();
if (currentBlock != -1) {
if (currentBlock != blockNotify.getBlockHeight()) {
if (currentBlock != getBlockHeight()) {
Serial.println(F("Detected stuck block height... restarting block handler."));
blockNotify.restart();
restartBlockNotify();
}
blockNotify.setLastBlockUpdate(getUptime());
setLastBlockUpdate(getUptime());
}
}
@ -114,10 +111,9 @@ void monitorDataConnections() {
}
// Block notification monitoring
auto& blockNotify = BlockNotify::getInstance();
if (blockNotify.isInitialized() && !blockNotify.isConnected()) {
if (getBlockNotifyInit() && !isBlockNotifyConnected()) {
handleBlockNotifyDisconnection();
} else if (blockNotifyLostConnection > 0 && blockNotify.isConnected()) {
} else if (blockNotifyLostConnection > 0 && isBlockNotifyConnected()) {
blockNotifyLostConnection = 0;
}
@ -129,7 +125,7 @@ void monitorDataConnections() {
}
// Check for missed blocks
if ((blockNotify.getLastBlockUpdate() - getUptime()) > 45 * 60) {
if ((getLastBlockUpdate() - getUptime()) > 45 * 60) {
checkMissedBlocks();
}
}