Hi,
I’ve just noticed other posts, ‘thanks’ that I haven’t seen before sorry for missing them!
Also I tried to keep as few includes as possible, so with the one I posted only 1x, which I’ll look into later.
I had some Heltecs in the post, and asked the vendors if they are 3.2s or 3.1s like the photo. They replied 3.2s, we use old photos!
Anyway, I received them and tried them with my 3.1 code. The screens were dim, so like many times before I asked AI to modify it to work on both modules, and it did, here:
//REPEATER_TOGGLE_140626_V3.1 AND V3.2
#include <heltec_unofficial.h>
// ------------------ IDENTIFIERS ------------------
#define BASE_ID “BASE”
#define REMOTE_ID “REMOTE”
// ------------------ FREQUENCIES ------------------
#define FROM_BASE_FREQ 863.8
#define TO_REMOTE_FREQ 864.4
#define FROM_REMOTE_FREQ 865.0
#define TO_BASE_FREQ 865.8
// ------------------ RADIO SETTINGS ------------------
int currentBW = 125;
int currentSF = 9;
int currentTxPower = 0;
// ------------------ TIMING ------------------
#define LISTEN_WINDOW_MS 10000UL // 10s on each side
// ------------------ STATE ------------------
volatile bool rxFlag = false;
String rxData;
enum RxSource { RX_FROM_BASE, RX_FROM_REMOTE };
RxSource currentRxSource = RX_FROM_BASE;
uint32_t windowStartedAt = 0;
String lastBaseMsg = “”;
String lastRemoteMsg = “”;
// ------------------ ISR ------------------
void rxISR() { rxFlag = true; }
// ------------------ RADIO MODES ------------------
void setRadioRx(float freq, RxSource src) {
currentRxSource = src;
windowStartedAt = millis();
radio.standby();
RADIOLIB_OR_HALT(radio.setFrequency(freq));
delay(10);
RADIOLIB_OR_HALT(radio.startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF));
}
void setRadioTx(float freq) {
radio.standby();
RADIOLIB_OR_HALT(radio.setFrequency(freq));
}
// ------------------ APPLY REMOTE SETTINGS ------------------
void applyRemoteSettings(int bw, int sf, int pwr) {
if (bw != currentBW) {
RADIOLIB_OR_HALT(radio.setBandwidth(bw));
currentBW = bw;
}
if (sf != currentSF) {
RADIOLIB_OR_HALT(radio.setSpreadingFactor(sf));
currentSF = sf;
}
if (pwr != currentTxPower) {
RADIOLIB_OR_HALT(radio.setOutputPower((int8_t)pwr));
currentTxPower = pwr;
}
}
// ------------------ PARSERS ------------------
// Base packet: “BASE,lat,lon,time”
// Remote packet: “REMOTE,lat,lon,time,bw,sf,pwr”
bool parseBase(const String& msg, float& lat, float& lon, String& t) {
int c1 = msg.indexOf(’,’);
int c2 = msg.indexOf(’,’, c1+1);
int c3 = msg.indexOf(’,’, c2+1);
if (c1 < 0 || c2 < 0 || c3 < 0) return false;
lat = msg.substring(c1+1, c2).toFloat();
lon = msg.substring(c2+1, c3).toFloat();
int c4 = msg.indexOf(’,’, c3+1);
t = (c4 > c3) ? msg.substring(c3+1, c4) : msg.substring(c3+1);
return true;
}
bool parseRemote(const String& msg, float& lat, float& lon, String& t,
int& bw, int& sf, int& pwr) {
int c1 = msg.indexOf(’,’);
int c2 = msg.indexOf(’,’, c1+1);
int c3 = msg.indexOf(’,’, c2+1);
int c4 = msg.indexOf(’,’, c3+1);
int c5 = msg.indexOf(’,’, c4+1);
int c6 = msg.indexOf(’,’, c5+1);
if (c1<0||c2<0||c3<0||c4<0||c5<0||c6<0) return false;
lat = msg.substring(c1+1, c2).toFloat();
lon = msg.substring(c2+1, c3).toFloat();
t = msg.substring(c3+1, c4);
bw = msg.substring(c4+1, c5).toInt();
sf = msg.substring(c5+1, c6).toInt();
pwr = msg.substring(c6+1).toInt();
return true;
}
// ------------------ DISPLAY ------------------
void showBasePacket(const String& msg) {
float lat, lon; String t;
display.clear();
display.drawString(0, 0, “=== BASE DATA ===”);
if (parseBase(msg, lat, lon, t)) {
display.drawString(0, 10, "LAT: " + String(lat, 6));
display.drawString(0, 20, "LON: " + String(lon, 6));
display.drawString(0, 30, "TIME: " + t);
display.drawString(0, 40, "BW" + String(currentBW) +
" SF" + String(currentSF) +
" PWR" + String(currentTxPower));
} else {
display.drawString(0, 10, "PARSE ERR");
}
display.display();
display.displayOn();
}
void showRemotePacket(const String& msg) {
float lat, lon; String t; int bw, sf, pwr;
display.clear();
display.drawString(0, 0, “=== REMOTE DATA ===”);
if (parseRemote(msg, lat, lon, t, bw, sf, pwr)) {
display.drawString(0, 10, "LAT: " + String(lat, 6));
display.drawString(0, 20, "LON: " + String(lon, 6));
display.drawString(0, 30, "TIME: " + t);
display.drawString(0, 40, "BW" + String(currentBW) +
" SF" + String(currentSF) +
" PWR" + String(currentTxPower));
} else {
display.drawString(0, 10, "PARSE ERR");
}
display.display();
display.displayOn();
}
void showListening(const char* side) {
display.clear();
display.drawString(0, 0, “REPEATER”);
display.drawString(0, 12, String("Listening ") + side);
display.drawString(0, 24, “BW” + String(currentBW) +
" SF" + String(currentSF) +
" PWR" + String(currentTxPower));
display.display();
display.displayOn();
}
// ------------------ SETUP ------------------
void setup() {
heltec_setup();
// Enable OLED power rail (Vext) for both V3.1 and V3.2
// GPIO36 controls Vext on Heltec V3 - LOW enables power
pinMode(36, OUTPUT);
digitalWrite(36, LOW);
delay(50);
// Ensure display is powered and on
display.displayOn();
RADIOLIB_OR_HALT(radio.begin());
radio.setDio1Action(rxISR);
RADIOLIB_OR_HALT(radio.setBandwidth(currentBW));
RADIOLIB_OR_HALT(radio.setSpreadingFactor(currentSF));
RADIOLIB_OR_HALT(radio.setOutputPower((int8_t)currentTxPower));
setRadioRx(FROM_BASE_FREQ, RX_FROM_BASE);
showListening(“BASE”);
}
// ------------------ LOOP ------------------
void loop() {
heltec_loop();
// ---- 10s TIMER: alternate listen side ----
if (millis() - windowStartedAt > LISTEN_WINDOW_MS) {
if (currentRxSource == RX_FROM_BASE) {
setRadioRx(FROM_REMOTE_FREQ, RX_FROM_REMOTE);
showListening("REMOTE");
} else {
setRadioRx(FROM_BASE_FREQ, RX_FROM_BASE);
showListening("BASE");
}
rxFlag = false; // discard any stale ISR from previous window
}
if (!rxFlag) return;
rxFlag = false;
rxData = “”;
int err = radio.readData(rxData);
if (err != RADIOLIB_ERR_NONE || rxData.length() == 0) {
if (currentRxSource == RX_FROM_BASE) setRadioRx(FROM_BASE_FREQ, RX_FROM_BASE);
else setRadioRx(FROM_REMOTE_FREQ, RX_FROM_REMOTE);
return;
}
// ---------- BASE → REMOTE ----------
if (rxData.startsWith(BASE_ID) && currentRxSource == RX_FROM_BASE) {
lastBaseMsg = rxData;
showBasePacket(rxData);
setRadioTx(TO_REMOTE_FREQ);
radio.transmit(rxData.c_str());
radio.finishTransmit();
// re-arm on same side — timer will switch when 10s is up
setRadioRx(FROM_BASE_FREQ, RX_FROM_BASE);
return;
}
// ---------- REMOTE → BASE ----------
if (rxData.startsWith(REMOTE_ID) && currentRxSource == RX_FROM_REMOTE) {
float lat, lon; String t; int bw, sf, pwr;
if (parseRemote(rxData, lat, lon, t, bw, sf, pwr)) {
applyRemoteSettings(bw, sf, pwr);
}
lastRemoteMsg = rxData;
showRemotePacket(rxData);
setRadioTx(TO_BASE_FREQ);
radio.transmit(rxData.c_str());
radio.finishTransmit();
// re-arm on same side — timer will switch when 10s is up
setRadioRx(FROM_REMOTE_FREQ, RX_FROM_REMOTE);
return;
}
// ---------- UNKNOWN / MISMATCH — re-arm ----------
if (currentRxSource == RX_FROM_BASE) setRadioRx(FROM_BASE_FREQ, RX_FROM_BASE);
else setRadioRx(FROM_REMOTE_FREQ, RX_FROM_REMOTE);
}