Captures audio and publishes drone alerts directly over Ethernet to an MQTT broker. No gateway required — ideal for fixed installations with PoE.
-
+ Heltec WiFi LoRa 32 V3 / V4
I2S Mic + DSPW5500 Ethernet / PoEMQTT + HA Discovery
From 10f127b9f3245a594ac1e798852d7fd18458114d Mon Sep 17 00:00:00 2001
From: popshark1
Date: Wed, 3 Jun 2026 03:44:52 +0300
Subject: [PATCH 20/27] Update Heltec WiFi LoRa 32 version references
Update Detector role specs to indicate support for both Heltec WiFi LoRa 32 V3 and V4 boards. This aligns the UI text with the separate firmware builds now available for each board version.
---
docs/flasher/index.html | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/flasher/index.html b/docs/flasher/index.html
index dbdbb35..1cd1ce5 100644
--- a/docs/flasher/index.html
+++ b/docs/flasher/index.html
@@ -217,7 +217,7 @@
From 08f1a92506d78529bdd0e8fccfbc1c0643a1420f Mon Sep 17 00:00:00 2001
From: popshark1
Date: Wed, 3 Jun 2026 04:01:26 +0300
Subject: [PATCH 21/27] Add board version selection to firmware page
Adds a Board Version selector (V3/V4) to the Web Flasher UI, allowing users to choose between Heltec WiFi LoRa 32 V3 (8MB) and V4 (16MB) before flashing detector or gateway firmware.
- New boardSelect dropdown with V3/V4 options
- Updated fwUrl() to append _v3 or _v4 suffix to firmware filenames for detector/gateway roles
- Added event listener to trigger manifest update when board version changes
- Wired detector remains unchanged (no version suffix needed)
---
docs/flasher/index.html | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)
diff --git a/docs/flasher/index.html b/docs/flasher/index.html
index 1cd1ce5..7044988 100644
--- a/docs/flasher/index.html
+++ b/docs/flasher/index.html
@@ -191,6 +191,15 @@
cfgPanels.forEach(p => p.classList.toggle("active", p.dataset.role === role));
// Wired Detector does not transmit over LoRa — hide the AES key field.
networkKeyPanel.style.display = (role === "wired_detector") ? "none" : "";
+ // Hide board version selector for wired_detector (only uses one firmware)
+ const boardVersionPicker = boardSelect.closest('.version-picker');
+ if (boardVersionPicker) {
+ boardVersionPicker.style.display = (role === "wired_detector") ? "none" : "";
+ }
installLabel.textContent = `Install ${ROLE_LABELS[role]}`;
validateAndUpdate();
}
From 14c309011e5f668424f903026f8af18fda206115 Mon Sep 17 00:00:00 2001
From: popshark1
Date: Tue, 9 Jun 2026 22:10:54 +0300
Subject: [PATCH 23/27] fix(battery): handle V3/V4 ADC_Ctrl polarity difference
for VBAT read
V3 uses active-LOW GPIO37 to connect the VBAT divider; V4 uses
active-HIGH (per V4.2.0 datasheet). Introduce VBAT_CTRL_CONNECT /
VBAT_CTRL_DISCONNECT macros selected at compile time via
CONFIG_BATEAR_BOARD_HELTEC_V4, replacing the hardcoded 0/1 values
that caused incorrect readings on V4 boards.
---
main/battery.c | 96 +++++++++++++++++++++++---------------------------
1 file changed, 45 insertions(+), 51 deletions(-)
diff --git a/main/battery.c b/main/battery.c
index 1062da4..3e81355 100644
--- a/main/battery.c
+++ b/main/battery.c
@@ -1,9 +1,10 @@
/*
* battery.c — ADC-oneshot readback of a resistor-divided VBAT rail
*
- * Heltec V3/V4 wire VBAT through a ~4.9× divider into GPIO1 (ADC1_CH0) gated
- * by GPIO37 (ADC_Ctrl, active low). We drive the gate low for each sample to
- * avoid a constant ~100 µA drain through the divider between reads.
+ * Heltec V3/V4 wire VBAT through a ~4.9x divider into GPIO1 (ADC1_CH0) gated
+ * by GPIO37 (ADC_Ctrl). V3: active LOW (drive low to connect divider).
+ * V4: active HIGH (drive high to connect divider).
+ * We connect the divider only while sampling to avoid a constant ~100 uA drain.
*/
#include "battery.h"
@@ -35,11 +36,27 @@ static const char *TAG = "battery";
#define VBAT_ADC_ATTEN ADC_ATTEN_DB_12
#define VBAT_ADC_BITWIDTH ADC_BITWIDTH_DEFAULT
#define VBAT_SAMPLES 8
-#define VBAT_SETTLE_MS 2
-static adc_oneshot_unit_handle_t s_adc;
-static adc_cali_handle_t s_cali;
-static bool s_ready;
+/* ADC_Ctrl (GPIO37) polarity differs between board revisions:
+ * V3 — active LOW: drive LOW to connect the divider, HIGH to disconnect.
+ * V4 — active HIGH: drive HIGH to connect the divider, LOW to disconnect.
+ */
+#if defined(CONFIG_BATEAR_BOARD_HELTEC_V4)
+#define VBAT_CTRL_CONNECT 1
+#define VBAT_CTRL_DISCONNECT 0
+#else
+#define VBAT_CTRL_CONNECT 0
+#define VBAT_CTRL_DISCONNECT 1
+#endif
+
+/* Settle time after enabling the divider before sampling (ms) */
+#ifndef VBAT_SETTLE_MS
+#define VBAT_SETTLE_MS 5
+#endif
+
+static adc_oneshot_unit_handle_t s_adc = NULL;
+static adc_cali_handle_t s_cali = NULL;
+static bool s_ready = false;
static bool cali_init(void)
{
@@ -47,25 +64,20 @@ static bool cali_init(void)
adc_cali_curve_fitting_config_t cfg = {
.unit_id = VBAT_ADC_UNIT,
.chan = VBAT_ADC_CHANNEL,
- .atten = VBAT_ADC_ATTEN,
+ .atten = VBAT_ADC_ATTEN,
.bitwidth = VBAT_ADC_BITWIDTH,
};
- if (adc_cali_create_scheme_curve_fitting(&cfg, &s_cali) == ESP_OK) {
- return true;
- }
-#endif
-#if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED
- adc_cali_line_fitting_config_t cfg2 = {
+ return adc_cali_create_scheme_curve_fitting(&cfg, &s_cali) == ESP_OK;
+#elif ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED
+ adc_cali_line_fitting_config_t cfg = {
.unit_id = VBAT_ADC_UNIT,
- .atten = VBAT_ADC_ATTEN,
+ .atten = VBAT_ADC_ATTEN,
.bitwidth = VBAT_ADC_BITWIDTH,
};
- if (adc_cali_create_scheme_line_fitting(&cfg2, &s_cali) == ESP_OK) {
- return true;
- }
-#endif
- s_cali = NULL;
+ return adc_cali_create_scheme_line_fitting(&cfg, &s_cali) == ESP_OK;
+#else
return false;
+#endif
}
void battery_init(void)
@@ -73,16 +85,15 @@ void battery_init(void)
if (s_ready) {
return;
}
-
gpio_config_t ctrl_cfg = {
- .pin_bit_mask = (1ULL << PIN_VBAT_CTRL),
- .mode = GPIO_MODE_OUTPUT,
- .pull_up_en = GPIO_PULLUP_DISABLE,
- .pull_down_en = GPIO_PULLDOWN_DISABLE,
- .intr_type = GPIO_INTR_DISABLE,
+ .pin_bit_mask = (1ULL << PIN_VBAT_CTRL),
+ .mode = GPIO_MODE_OUTPUT,
+ .pull_up_en = GPIO_PULLUP_DISABLE,
+ .pull_down_en = GPIO_PULLDOWN_DISABLE,
+ .intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&ctrl_cfg);
- gpio_set_level((gpio_num_t)PIN_VBAT_CTRL, 1); /* divider disconnected */
+ gpio_set_level((gpio_num_t)PIN_VBAT_CTRL, VBAT_CTRL_DISCONNECT); /* divider disconnected */
adc_oneshot_unit_init_cfg_t unit_cfg = {
.unit_id = VBAT_ADC_UNIT,
@@ -93,7 +104,6 @@ void battery_init(void)
ESP_LOGE(TAG, "adc_oneshot_new_unit: %s", esp_err_to_name(err));
return;
}
-
adc_oneshot_chan_cfg_t chan_cfg = {
.atten = VBAT_ADC_ATTEN,
.bitwidth = VBAT_ADC_BITWIDTH,
@@ -103,17 +113,9 @@ void battery_init(void)
ESP_LOGE(TAG, "adc_oneshot_config_channel: %s", esp_err_to_name(err));
return;
}
-
- /* cppcheck cannot see ESP-IDF's ADC_CALI_SCHEME_*_SUPPORTED macros
- * (defined via soc_caps.h from sdkconfig), so under its preprocessor
- * view both #if branches in cali_init() compile out and it concludes
- * the function always returns false. On real builds at least one
- * scheme is available. */
- // cppcheck-suppress knownConditionTrueFalse
if (!cali_init()) {
ESP_LOGW(TAG, "ADC calibration unavailable — readings will be rough");
}
-
s_ready = true;
ESP_LOGI(TAG, "battery monitor ready (adc=GPIO%d ctrl=GPIO%d ratio=%.2f)",
PIN_VBAT_ADC, PIN_VBAT_CTRL, (double)BOARD_VBAT_DIVIDER_RATIO);
@@ -124,10 +126,8 @@ uint32_t battery_read_mv(void)
if (!s_ready) {
return 0;
}
-
- gpio_set_level((gpio_num_t)PIN_VBAT_CTRL, 0);
+ gpio_set_level((gpio_num_t)PIN_VBAT_CTRL, VBAT_CTRL_CONNECT);
vTaskDelay(pdMS_TO_TICKS(VBAT_SETTLE_MS));
-
uint32_t raw_sum = 0;
int raw = 0;
int taken = 0;
@@ -137,34 +137,28 @@ uint32_t battery_read_mv(void)
taken++;
}
}
-
- gpio_set_level((gpio_num_t)PIN_VBAT_CTRL, 1);
-
+ gpio_set_level((gpio_num_t)PIN_VBAT_CTRL, VBAT_CTRL_DISCONNECT);
if (taken == 0) {
return 0;
}
-
- int raw_avg = (int)(raw_sum / (uint32_t)taken);
-
+ int raw_avg = (int)(raw_sum / (uint32_t)taken);
int mv_at_pin = 0;
if (s_cali) {
if (adc_cali_raw_to_voltage(s_cali, raw_avg, &mv_at_pin) != ESP_OK) {
mv_at_pin = 0;
}
} else {
- /* Rough fallback: 12-bit ADC @ 12 dB atten → ~3100 mV full scale. */
mv_at_pin = (raw_avg * 3100) / 4095;
}
-
float vbat_mv = (float)mv_at_pin * BOARD_VBAT_DIVIDER_RATIO;
- if (vbat_mv < 0.0f) vbat_mv = 0.0f;
+ if (vbat_mv < 0.0f) vbat_mv = 0.0f;
if (vbat_mv > 65535.0f) vbat_mv = 65535.0f;
return (uint32_t)(vbat_mv + 0.5f);
}
-#else /* BOARD_HAS_VBAT */
+#else /* !BOARD_HAS_VBAT */
-void battery_init(void) {}
+void battery_init(void) { }
uint32_t battery_read_mv(void) { return 0; }
-#endif
+#endif /* BOARD_HAS_VBAT */
From 0c6106474219e9b92ce4e7564b8d36ffd66dfa8a Mon Sep 17 00:00:00 2001
From: tn666
Date: Tue, 16 Jun 2026 21:19:50 +0800
Subject: [PATCH 24/27] flasher: revert fork refs, restore role panels, drop
duplicate sdkconfig
Addresses review:
- Revert REPO const + footer/datasets links from popshark1/* to batear-io/*
- Restore the
descriptions and drop the stray floating on the
gateway and wired_detector role-info panels
- fwUrl(): fix indentation and use wired_detector-exclusion so future LoRa
roles also get the board-version suffix
- Drop unreferenced sdkconfig.detector.v3 / sdkconfig.gateway.v3 (CI uses
the underscore _v3 variants; the dot variants were duplicates)
Co-Authored-By: Claude Opus 4.8 (1M context)
---
docs/flasher/index.html | 25 ++++++++++++++-----------
sdkconfig.detector.v3 | 15 ---------------
sdkconfig.gateway.v3 | 14 --------------
3 files changed, 14 insertions(+), 40 deletions(-)
delete mode 100644 sdkconfig.detector.v3
delete mode 100644 sdkconfig.gateway.v3
diff --git a/docs/flasher/index.html b/docs/flasher/index.html
index ea8f85e..30c7d6e 100644
--- a/docs/flasher/index.html
+++ b/docs/flasher/index.html
@@ -230,7 +230,8 @@
Flash firmware in 60 seconds
- Heltec WiFi LoRa 32 V3 / V4
+
Receives encrypted LoRa alerts from detectors, displays status on OLED, and forwards events to Home Assistant via MQTT over Wi-Fi.