Skip to content

Commit a25724f

Browse files
author
realtag
committed
Fix repeater webhook retries on v1.14.1
1 parent 9678b0b commit a25724f

3 files changed

Lines changed: 82 additions & 18 deletions

File tree

examples/simple_repeater/MyMesh.cpp

Lines changed: 74 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ static const uint8_t TEST_GROUP_SECRET[16] = {
7979
#if defined(ESP32)
8080
#define DISCORD_WEBHOOK_MIN_INTERVAL_MS 200
8181
#define DISCORD_WEBHOOK_RETRY_DELAY_MS 2000
82+
#define DISCORD_WEBHOOK_MAX_RETRIES 2
83+
#define DISCORD_WEBHOOK_DEDUPE_TTL_MS 30000UL
8284

8385
static size_t jsonEscape(const char* src, char* dst, size_t dst_len) {
8486
if (dst_len == 0) return 0;
@@ -826,6 +828,29 @@ void MyMesh::initTestChannel() {
826828
}
827829

828830
#if defined(ESP32)
831+
bool MyMesh::shouldQueueDiscordWebhook(const char* sender, const char* body) {
832+
uint32_t msg_hash = 0;
833+
mesh::Utils::sha256((uint8_t*)&msg_hash, sizeof(msg_hash),
834+
(const uint8_t*)sender, strlen(sender),
835+
(const uint8_t*)body, strlen(body));
836+
837+
int slot = -1;
838+
for (uint8_t i = 0; i < DISCORD_WEBHOOK_DEDUPE_SIZE; i++) {
839+
if (webhook_dedupe[i].expires_at == 0 || millisHasNowPassed(webhook_dedupe[i].expires_at)) {
840+
if (slot < 0) slot = i;
841+
continue;
842+
}
843+
if (webhook_dedupe[i].hash == msg_hash) {
844+
return false;
845+
}
846+
}
847+
848+
if (slot < 0) slot = 0;
849+
webhook_dedupe[slot].hash = msg_hash;
850+
webhook_dedupe[slot].expires_at = futureMillis(DISCORD_WEBHOOK_DEDUPE_TTL_MS);
851+
return true;
852+
}
853+
829854
void MyMesh::initWifiClient() {
830855
if (_prefs.wifi_ssid[0] == 0 || _prefs.wifi_pwd[0] == 0) return;
831856
WiFi.mode(WIFI_STA);
@@ -837,6 +862,10 @@ void MyMesh::initWifiClient() {
837862

838863
void MyMesh::queueDiscordWebhook(const char* sender, const char* body) {
839864
if (_prefs.discord_webhook_url[0] == 0) return;
865+
if (!shouldQueueDiscordWebhook(sender, body)) {
866+
MESH_DEBUG_PRINTLN("Discord webhook dedupe hit, skipping duplicate");
867+
return;
868+
}
840869
if (webhook_count >= DISCORD_WEBHOOK_QUEUE_SIZE) {
841870
MESH_DEBUG_PRINTLN("Discord webhook queue full, dropping oldest");
842871
webhook_head = (uint8_t)((webhook_head + 1) % DISCORD_WEBHOOK_QUEUE_SIZE);
@@ -845,6 +874,7 @@ void MyMesh::queueDiscordWebhook(const char* sender, const char* body) {
845874
WebhookItem* item = &webhook_queue[webhook_tail];
846875
StrHelper::strncpy(item->sender, sender, sizeof(item->sender));
847876
StrHelper::strncpy(item->body, body, sizeof(item->body));
877+
item->retry_count = 0;
848878
webhook_tail = (uint8_t)((webhook_tail + 1) % DISCORD_WEBHOOK_QUEUE_SIZE);
849879
webhook_count++;
850880
next_webhook_attempt_at = 0;
@@ -866,6 +896,25 @@ void MyMesh::pumpDiscordWebhook() {
866896
if (next_webhook_attempt_at && !millisHasNowPassed(next_webhook_attempt_at)) return;
867897

868898
WebhookItem* item = &webhook_queue[webhook_head];
899+
auto dropWebhookItem = [&]() {
900+
webhook_head = (uint8_t)((webhook_head + 1) % DISCORD_WEBHOOK_QUEUE_SIZE);
901+
webhook_count--;
902+
};
903+
auto scheduleWebhookRetry = [&](const char* reason) {
904+
if (item->retry_count >= DISCORD_WEBHOOK_MAX_RETRIES) {
905+
MESH_DEBUG_PRINTLN("Discord webhook dropped after %u retries: %s",
906+
(unsigned)item->retry_count, reason);
907+
dropWebhookItem();
908+
next_webhook_attempt_at = futureMillis(DISCORD_WEBHOOK_MIN_INTERVAL_MS);
909+
return;
910+
}
911+
item->retry_count++;
912+
MESH_DEBUG_PRINTLN("Discord webhook retry %u/%u: %s",
913+
(unsigned)item->retry_count,
914+
(unsigned)DISCORD_WEBHOOK_MAX_RETRIES,
915+
reason);
916+
next_webhook_attempt_at = futureMillis(DISCORD_WEBHOOK_RETRY_DELAY_MS);
917+
};
869918

870919
char esc_sender[80];
871920
char esc_body[256];
@@ -907,7 +956,6 @@ void MyMesh::pumpDiscordWebhook() {
907956
} else if (starts_with_ci(p, http_scheme, 7)) {
908957
is_http = true;
909958
} else {
910-
// Search for a scheme inside the string in case of hidden prefix chars.
911959
const char* s = p;
912960
while (*s) {
913961
if (starts_with_ci(s, https_scheme, 8)) { p = s; is_https = true; break; }
@@ -921,12 +969,12 @@ void MyMesh::pumpDiscordWebhook() {
921969
}
922970
char lead_hex[24];
923971
int lh = 0;
924-
for (int i = 0; i < 8 && url[i]; i++) {
925-
lh += snprintf(lead_hex + lh, sizeof(lead_hex) - lh, "%02X", (unsigned char)url[i]);
972+
for (int i = 0; i < 8 && p[i]; i++) {
973+
lh += snprintf(lead_hex + lh, sizeof(lead_hex) - lh, "%02X", (unsigned char)p[i]);
926974
}
927975
MESH_DEBUG_PRINTLN("Discord webhook scheme: %s lead=%s", is_https ? "https" : "http", lead_hex);
928976

929-
const char* host = url;
977+
const char* host = p;
930978
if (is_https) host += 8;
931979
else if (is_http) host += 7;
932980

@@ -947,9 +995,10 @@ void MyMesh::pumpDiscordWebhook() {
947995
if (is_https) {
948996
WiFiClientSecure client;
949997
client.setInsecure();
998+
client.setTimeout(8000);
950999
if (!client.connect(host_buf, 443)) {
9511000
MESH_DEBUG_PRINTLN("Discord webhook TLS connect failed");
952-
next_webhook_attempt_at = futureMillis(DISCORD_WEBHOOK_RETRY_DELAY_MS);
1001+
scheduleWebhookRetry("TLS connect failed");
9531002
return;
9541003
}
9551004
client.printf("POST %s HTTP/1.1\r\n", path);
@@ -960,16 +1009,17 @@ void MyMesh::pumpDiscordWebhook() {
9601009
client.printf("Connection: close\r\n\r\n");
9611010
client.write((const uint8_t*)payload, strlen(payload));
9621011

963-
// Read status line
1012+
unsigned long wait_until = millis() + 8000;
1013+
while (client.connected() && !client.available() && (long)(millis() - wait_until) < 0) {
1014+
delay(10);
1015+
}
1016+
9641017
String status = client.readStringUntil('\n');
9651018
status.trim();
9661019
if (status.startsWith("HTTP/")) {
9671020
int sp = status.indexOf(' ');
968-
if (sp > 0) {
969-
code = status.substring(sp + 1).toInt();
970-
}
1021+
if (sp > 0) code = status.substring(sp + 1).toInt();
9711022
}
972-
// Read headers, then body
9731023
while (client.connected()) {
9741024
String line = client.readStringUntil('\n');
9751025
if (line == "\r" || line.length() == 0) break;
@@ -979,16 +1029,23 @@ void MyMesh::pumpDiscordWebhook() {
9791029
} else {
9801030
HTTPClient http;
9811031
WiFiClient client;
982-
http.begin(client, host_buf, 80, path, false);
1032+
client.setTimeout(8000);
1033+
if (!http.begin(client, host_buf, 80, path, false)) {
1034+
MESH_DEBUG_PRINTLN("Discord webhook HTTP begin failed");
1035+
scheduleWebhookRetry("HTTP begin failed");
1036+
return;
1037+
}
1038+
http.setConnectTimeout(8000);
1039+
http.setTimeout(8000);
9831040
http.addHeader("Content-Type", "application/json");
1041+
http.addHeader("User-Agent", "MeshCore");
9841042
code = http.POST((uint8_t*)payload, strlen(payload));
9851043
resp = http.getString();
9861044
http.end();
9871045
}
9881046

9891047
if (code >= 200 && code < 300) {
990-
webhook_head = (uint8_t)((webhook_head + 1) % DISCORD_WEBHOOK_QUEUE_SIZE);
991-
webhook_count--;
1048+
dropWebhookItem();
9921049
MESH_DEBUG_PRINTLN("Discord webhook OK: HTTP %d", code);
9931050
next_webhook_attempt_at = futureMillis(DISCORD_WEBHOOK_MIN_INTERVAL_MS);
9941051
} else {
@@ -1000,7 +1057,7 @@ void MyMesh::pumpDiscordWebhook() {
10001057
resp_buf[n] = 0;
10011058
}
10021059
MESH_DEBUG_PRINTLN("Discord webhook failed: HTTP %d body=%s", code, resp_buf[0] ? resp_buf : "<empty>");
1003-
next_webhook_attempt_at = futureMillis(DISCORD_WEBHOOK_RETRY_DELAY_MS);
1060+
scheduleWebhookRetry(resp_buf[0] ? resp_buf : "<empty>");
10041061
}
10051062
}
10061063
#endif
@@ -1318,10 +1375,10 @@ void MyMesh::onGroupDataRecv(mesh::Packet* packet, uint8_t type, const mesh::Gro
13181375

13191376
StrHelper::strncpy(pending_ping_sender, sender_name, sizeof(pending_ping_sender));
13201377
StrHelper::strncpy(pending_ping_path, path_str, sizeof(pending_ping_path));
1321-
pending_ping_is_5count = is_5count;
1378+
pending_ping_is_5count = false;
13221379
pending_ping_channel = channel;
13231380
pending_ping_channel_ready = true;
1324-
pending_ping_total = is_5count ? 5 : PING_REPLY_ATTEMPTS;
1381+
pending_ping_total = (uint8_t)min(3, (int)max_replies);
13251382
pending_ping_retries = pending_ping_total;
13261383
pending_ping_include_prefix = true;
13271384
next_ping_send_at = 0;
@@ -1538,6 +1595,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
15381595
webhook_head = 0;
15391596
webhook_tail = 0;
15401597
webhook_count = 0;
1598+
memset(webhook_dedupe, 0, sizeof(webhook_dedupe));
15411599
#endif
15421600

15431601
#if MAX_NEIGHBOURS

examples/simple_repeater/MyMesh.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,6 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
119119
unsigned long pending_discover_until;
120120
bool region_load_active;
121121
unsigned long dirty_contacts_expiry;
122-
unsigned long dirty_contacts_expiry;
123122
#if MAX_NEIGHBOURS
124123
NeighbourInfo neighbours[MAX_NEIGHBOURS];
125124
#endif
@@ -136,6 +135,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
136135
bool test_channel_ready;
137136
#if defined(ESP32)
138137
static const uint8_t DISCORD_WEBHOOK_QUEUE_SIZE = 128;
138+
static const uint8_t DISCORD_WEBHOOK_DEDUPE_SIZE = 24;
139139
unsigned long next_wifi_attempt_at;
140140
unsigned long next_webhook_attempt_at;
141141
uint8_t webhook_head;
@@ -144,9 +144,16 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
144144
struct WebhookItem {
145145
char sender[40];
146146
char body[200];
147+
uint8_t retry_count;
148+
};
149+
struct WebhookDedupeItem {
150+
uint32_t hash;
151+
unsigned long expires_at;
147152
};
148153
WebhookItem webhook_queue[DISCORD_WEBHOOK_QUEUE_SIZE];
154+
WebhookDedupeItem webhook_dedupe[DISCORD_WEBHOOK_DEDUPE_SIZE];
149155
void initWifiClient();
156+
bool shouldQueueDiscordWebhook(const char* sender, const char* body);
150157
void queueDiscordWebhook(const char* sender, const char* body);
151158
void pumpDiscordWebhook();
152159
#endif

src/helpers/CommonCLI.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,6 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
205205
file.write((uint8_t *)&_prefs->ping_test_max_replies, sizeof(_prefs->ping_test_max_replies)); // 585
206206
file.write((uint8_t *)&_prefs->ping_simple_enabled, sizeof(_prefs->ping_simple_enabled)); // 586
207207
// 587
208-
>>>>>>> bf6ae358 (Add busy-delay throttling for ! commands and bump version to v1.12.3)
209208

210209
file.close();
211210
}

0 commit comments

Comments
 (0)