Files
HaloKeymind/variants/t1000-e/target.cpp
T
mikecarper bc8fe0c66c Merge upstream dev through 5846fa19
Import Airoha GPS RTC backup sleep, 24 contact search results, and the T-Echo Lite USB companion target. Preserve existing GPS telemetry and power-saving controls, crypto alignment checks, ESP32 RTC recovery, and RAK11310 dynamic-preference compatibility.

Validate GPS sleep acknowledgments with their NMEA checksum and add six native regression tests covering retry, drain, corruption, grace timeout, and timer rollover.

Verified: 1648 native and KISS modem tests; targeted nRF52, companion routing, one-key DM, and clock checks; T1000-E BLE, MeshTracker X1 USB, and T-Echo Lite USB companion builds.
2026-09-30 02:13:25 -07:00

244 lines
7.1 KiB
C++

#include <Arduino.h>
#include "t1000e_sensors.h"
#include "target.h"
#include <helpers/UsbLogging.h>
#include <helpers/radiolib/Nrf52BufferedRadioHal.h>
#include <helpers/sensors/MicroNMEALocationProvider.h>
#include <helpers/sensors/AirohaSleep.h>
T1000eBoard board;
Nrf52BufferedRadioHal radioHal(SPI);
RADIO_CLASS radio = new Module(&radioHal, P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
WRAPPER_CLASS radio_driver(radio, board);
VolatileRTCClock rtc_clock;
MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock);
T1000SensorManager sensors = T1000SensorManager(nmea);
#ifdef DISPLAY_CLASS
NullDisplayDriver display;
#endif
#ifndef LORA_CR
#define LORA_CR 5
#endif
#ifdef RF_SWITCH_TABLE
static const uint32_t rfswitch_dios[Module::RFSWITCH_MAX_PINS] = {
RADIOLIB_LR11X0_DIO5,
RADIOLIB_LR11X0_DIO6,
RADIOLIB_LR11X0_DIO7,
RADIOLIB_LR11X0_DIO8,
RADIOLIB_NC
};
static const Module::RfSwitchMode_t rfswitch_table[] = {
// mode DIO5 DIO6 DIO7 DIO8
{ LR11x0::MODE_STBY, {LOW, LOW, LOW, LOW }},
{ LR11x0::MODE_RX, {HIGH, LOW, LOW, HIGH }},
{ LR11x0::MODE_TX, {HIGH, HIGH, LOW, HIGH }},
{ LR11x0::MODE_TX_HP, {LOW, HIGH, LOW, HIGH }},
{ LR11x0::MODE_TX_HF, {LOW, LOW, LOW, LOW }},
{ LR11x0::MODE_GNSS, {LOW, LOW, HIGH, LOW }},
{ LR11x0::MODE_WIFI, {LOW, LOW, LOW, LOW }},
END_OF_MODE_TABLE,
};
#endif
bool radio_init() {
//rtc_clock.begin(Wire);
#ifdef LR11X0_DIO3_TCXO_VOLTAGE
float tcxo = LR11X0_DIO3_TCXO_VOLTAGE;
#else
float tcxo = 1.6f;
#endif
SPI.setPins(P_LORA_MISO, P_LORA_SCLK, P_LORA_MOSI);
SPI.begin();
int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_LR11X0_LORA_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo);
if (status != RADIOLIB_ERR_NONE) {
mesh::usbLoggingPort().printf("ERROR: radio init failed: %d\r\n", status);
return false; // fail
}
radio.setCRC(2);
radio.explicitHeader();
#ifdef RF_SWITCH_TABLE
radio.setRfSwitchTable(rfswitch_dios, rfswitch_table);
#endif
#ifdef RX_BOOSTED_GAIN
radio.setRxBoostedGainMode(RX_BOOSTED_GAIN);
#endif
// Reuse this board-specific reset and configuration sequence if a runtime
// TX failure requires hard LR1110 recovery.
radio_driver.setDeepInitCallback(radio_init);
return true; // success
}
uint32_t radio_fallback_rng_seed() {
uint32_t seed = micros() ^ NRF_FICR->DEVICEID[0] ^ NRF_FICR->DEVICEID[1];
#ifdef USE_CC310_HW_CRYPTO
mesh::mixCC310Random(reinterpret_cast<uint8_t*>(&seed), sizeof(seed));
#endif
return seed;
}
mesh::LocalIdentity radio_new_identity() {
RadioNoiseListener rng(radio);
return mesh::LocalIdentity(&rng); // create new random identity
}
void T1000SensorManager::armGpsPowerSavingCycle() {
if (!powersaving_enabled || !_nmea->getGPSPowerSaving()) return;
_nmea->syncTime();
_nmea->setNextGPSOn(0);
_nmea->setNextSleep();
}
void T1000SensorManager::start_gps() {
if (gps_active) return;
gps_active = true;
digitalWrite(GPS_EN, HIGH);
delay(10);
// Resume from RTC backup without resetting retained ephemeris data.
digitalWrite(GPS_RTC_INT, HIGH);
delay(5);
digitalWrite(GPS_RTC_INT, LOW);
armGpsPowerSavingCycle();
}
void T1000SensorManager::sleep_gps() {
stop_gps();
}
void T1000SensorManager::stop_gps() {
if (!gps_active) return;
gps_active = false;
if (powersaving_enabled && _nmea->getGPSPowerSaving()) {
_nmea->stopTimeSync();
_nmea->setNextGPSOff(0);
_nmea->setNextWake();
}
digitalWrite(GPS_VRTC_EN, HIGH); // keep RTC powered for faster fix on wake
digitalWrite(GPS_RTC_INT, LOW);
airohaEnterSleep(_nmea); // save ephemeris before cutting GPS VCC
digitalWrite(GPS_EN, LOW);
}
bool T1000SensorManager::begin() {
// init GPS
Serial1.begin(115200);
pinMode(GPS_VRTC_EN, OUTPUT);
pinMode(GPS_EN, OUTPUT);
pinMode(GPS_RESET, OUTPUT);
pinMode(GPS_SLEEP_INT, OUTPUT);
pinMode(GPS_RTC_INT, OUTPUT);
pinMode(GPS_RESETB, INPUT_PULLUP);
digitalWrite(GPS_VRTC_EN, HIGH);
digitalWrite(GPS_EN, HIGH);
digitalWrite(GPS_SLEEP_INT, LOW);
digitalWrite(GPS_RTC_INT, LOW);
digitalWrite(GPS_RESET, HIGH);
delay(10);
digitalWrite(GPS_RESET, LOW);
// Preserve the existing default-off behavior after initial setup.
gps_active = true;
stop_gps();
return true;
}
bool T1000SensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) {
queryGpsTelemetry(requester_permissions, telemetry);
if (requester_permissions & TELEM_PERM_ENVIRONMENT) {
// Firmware reports light as a 0-100 % scale, but expose it via Luminosity so app labels it "Luminosity".
telemetry.addLuminosity(TELEM_CHANNEL_SELF, t1000e_get_light());
telemetry.addTemperature(TELEM_CHANNEL_SELF, t1000e_get_temperature());
}
return true;
}
void T1000SensorManager::loop() {
static unsigned long next_gps_update = 0;
unsigned long now = millis();
loopGpsTelemetry(now);
if (powersaving_enabled && _nmea->getGPSPowerSaving()) {
unsigned long next_off = _nmea->getNextGPSOff();
unsigned long next_on = _nmea->getNextGPSOn();
if (gps_active && !gpsTelemetryReceiverRequired(now)
&& ((next_off != 0 && (long)(now - next_off) >= 0)
|| !_nmea->waitingTimeSync())) {
POWERSAVING_DEBUG_PRINTLN("GPS entering sleep");
stop_gps();
} else if (!gps_active && ((next_on != 0 && (long)(now - next_on) >= 0)
|| _nmea->waitingTimeSync())) {
POWERSAVING_DEBUG_PRINTLN("GPS waking");
start_gps();
}
}
if (gps_active) _nmea->loop();
if ((long)(now - next_gps_update) >= 0) {
if (gps_active && _nmea->isValid()) {
node_lat = ((double)_nmea->getLatitude())/1000000.;
node_lon = ((double)_nmea->getLongitude())/1000000.;
node_altitude = ((double)_nmea->getAltitude()) / 1000.0;
processGpsTelemetryFix(node_lat, node_lon, node_altitude, now);
}
next_gps_update = now + getGpsUpdateIntervalMillis();
}
}
int T1000SensorManager::getNumSettings() const { return 1; } // just one supported: "gps" (power switch)
const char* T1000SensorManager::getSettingName(int i) const {
return i == 0 ? "gps" : NULL;
}
const char* T1000SensorManager::getSettingValue(int i) const {
if (i == 0) {
return isGpsTelemetryUserEnabled() ? "1" : "0";
}
return NULL;
}
bool T1000SensorManager::setSettingValue(const char* name, const char* value) {
if (strcmp(name, "gps") == 0) {
bool enabled = strcmp(value, "0") != 0;
bool was_active = gps_active;
_nmea->setGPSPowerSaving(enabled && powersaving_enabled);
setGpsTelemetryUserEnabled(enabled);
if (enabled && powersaving_enabled && was_active) {
armGpsPowerSavingCycle();
}
return true;
}
return SensorManager::setSettingValue(name, value);
}
void T1000SensorManager::setPowerSavingEnabled(bool enabled) {
if (powersaving_enabled == enabled) return;
powersaving_enabled = enabled;
bool gps_user_enabled = isGpsTelemetryUserEnabled();
_nmea->setGPSPowerSaving(enabled && gps_user_enabled);
if (!gps_user_enabled) return;
if (enabled) {
if (gps_active) {
armGpsPowerSavingCycle();
} else {
start_gps();
}
} else if (!gps_active) {
start_gps();
}
}