Control Wio Tracker L2 GPS power and reserve display runtime RAM

This commit is contained in:
mikecarper committed 2026-10-15 17:37:54 -07:00
1 parent 958bc7a632
commit aabbaf7eb7
9 files changed
+516 -4

No files matched your search

+40
View File
@@ -14,6 +14,7 @@ from __future__ import annotations
import argparse
import hashlib
import json
import math
from pathlib import Path
import re
import struct
@@ -97,6 +98,34 @@ def mqtt_requirements(defines):
}
def wio_tracker_l2_display_heap(defines):
# Both LGFXDisplay::begin()'s internal canvas and Wio's concrete begin()
# must fit. The latter requests PSRAM, but LovyanGFX 1.2.30 explicitly
# falls back to internal DMA memory when that allocation fails.
zoom_text = str(defines.get("UI_ZOOM", 1)).strip().strip("()")
zoom_text = re.sub(r"[fFlL]$", "", zoom_text)
try:
zoom = float(zoom_text)
except ValueError as error:
raise ValueError(f"memory budget needs a numeric UI_ZOOM, got {zoom_text!r}") from error
if not math.isfinite(zoom) or zoom <= 0:
raise ValueError("Wio Tracker L2 UI_ZOOM must be finite and positive")
if integer(defines, "UI_COORD_SCALE", 1) == 0:
raise ValueError("Wio Tracker L2 UI_COORD_SCALE must be positive")
depth = integer(defines, "UI_BUFFER_COLOR_DEPTH", 8)
if depth not in (1, 2, 4, 8, 16, 24, 32):
raise ValueError("unsupported Wio Tracker L2 UI_BUFFER_COLOR_DEPTH")
# The base constructor divides physical dimensions by zoom*coordinate
# scale; its render path multiplies by coordinate scale again. Rounding
# upward bounds both that canvas and Wio's fixed-8bpp logical sprite.
width = math.ceil(320 / zoom)
height = math.ceil(240 / zoom)
row_bytes = (width * max(8, depth) + 7) // 8
# Include the sprite's extra trailing pixel, up to a 256-entry RGB888
# palette, alignment and allocation metadata, even with working PSRAM.
return row_bytes * height + 1024
def requirements(platform, defines, target):
if platform not in SUPPORTED:
raise ValueError(f"no memory policy for {platform}")
@@ -111,11 +140,22 @@ def requirements(platform, defines, target):
depth = integer(defines, "UI_BUFFER_COLOR_DEPTH", 8)
pixels = 480 * 480 if "INDICATOR_TRANSPORT_RENDER_PROFILE" in defines else 320 * 320
display_heap = (pixels * depth + 7) // 8 + 1024
elif display == "WioTrackerL2Display":
if platform != "ESP32_PLATFORM":
raise ValueError("Wio Tracker L2 display budget requires ESP32")
display_heap = wio_tracker_l2_display_heap(defines)
elif display in DISPLAY_HEAP:
display_heap = DISPLAY_HEAP[display]
else:
raise ValueError(f"add a runtime allocation budget for display {display!r}")
parts = {}
if display == "WioTrackerL2Display":
# The pinned LGFX QSPI bus has two DMA flip buffers. RGB565 conversion
# requests at most 2048 bytes each (2304 for RGB888); shrinking can
# briefly retain a third. Reserve 16 KiB for those, queued descriptors
# (up to 320 rows, including overlap while growing) and SDK bus/touch
# driver metadata. These allocations coexist with the UI sprite.
parts["display_dma_and_bus"] = 16384
mqtt = None
combined_rak_ethernet = "RAK4631_COMBINED_ETHERNET" in defines
if combined_rak_ethernet and (platform != "NRF52_PLATFORM"
+128
View File
@@ -0,0 +1,128 @@
#include <Arduino.h>
#include <Wire.h>
#include <WioTrackerL2LocationProvider.h>
#include "GpsTap.h"
#include <helpers/SensorManager.h>
#define MESH_DEBUG_PRINTLN(...) ((void)0)
#define POWERSAVING_DEBUG_PRINTLN(...) ((void)0)
void LocationProvider::sendSentence(const char*) {}
@EXPANDER@
// Keep only ESP32 startup hardware out of this host harness. The expander
// initialization, output shadows and GNSS power/reset methods are production.
void WioTrackerL2Board::begin() { expander_ok = initExpander(); }
uint16_t WioTrackerL2Board::getBattMilliVolts() { return 0; }
bool WioTrackerL2Board::isExternalPowered() { return false; }
void WioTrackerL2Board::setLed(bool) {}
class EnvironmentSensorManager : public SensorManager {
public:
LocationProvider* _location;
bool gps_detected=false, gps_active=false, gps_serial_transport=false;
bool gps_serial_transport_blocked=false, gps_discovery_pending=false;
bool gps_discovery_preference_known=false;
uint32_t gps_discovery_started_at=0;
explicit EnvironmentSensorManager(LocationProvider& location) : _location(&location) {}
LocationProvider* getLocationProvider() override { return _location; }
bool telemetryGpsDetected() const override { return gps_detected; }
bool telemetryGpsActive() const override { return gps_active; }
void telemetryGpsStart() override { start_gps(); }
void telemetryGpsStop() override { stop_gps(); }
bool setSettingValue(const char*,const char*) override;
void setPowerSavingEnabled(bool) override;
void finishBasicGpsDiscovery(bool found);
void serviceBasicGpsDiscovery();
void initBasicGPS();
void armGpsPowerSavingCycle();
void start_gps();
void stop_gps();
void loop() override;
};
@LIFECYCLE@
WioTrackerL2Board board;
GpsTapStream gps_tap(Serial1);
mesh::RTCClock clock_source;
WioTrackerL2LocationProvider gps(board, gps_tap, &clock_source);
EnvironmentSensorManager sensors(gps);
struct RadioMock { void powerOff() { event("radio-off"); } } radio_driver;
@SHUTDOWN@
size_t first(const char* name) {
for (size_t i=0;i<events.size();++i) if(events[i].name==name)return i;
return events.size();
}
void assertOff() {
assert(!Wire.gnssOn()&&!gps.isEnabled()&&!Serial1.installed);
assert(modes[PIN_GPS_TX]==INPUT&&modes[PIN_GPS_RX]==INPUT);
}
void start() {
// Match EnvironmentSensorManager's UART setup before provider begin().
Serial1.setPins(PIN_GPS_TX,PIN_GPS_RX);Serial1.begin(9600);
events.clear();gps.begin();
assert(gps.isEnabled()&&Wire.gnssOn()&&Serial1.installed);
assert(first("uart-end")<first("gnss-on"));
assert(first("gnss-on")<first("uart-begin"));
}
void stop() {
events.clear();gps.stop();assertOff();
assert(first("uart-end")<first("gnss-off"));
}
int main(int argc,char** argv) {
assert(argc==2&&events.empty()&&Wire.transactions==0);
const std::string mode=argv[1];
if(mode=="failed") {
Wire.present=false;board.begin();assert(!board.expanderOK());
Serial1.setPins(PIN_GPS_TX,PIN_GPS_RX);Serial1.begin(9600);
unsigned probes=Wire.transactions;gps.begin();
assertOff();assert(Wire.transactions==probes);
gps.stop();assertOff();assert(Wire.transactions==probes);
return 0;
}
board.begin();
assert(board.expanderOK()&&!Wire.gnssOn()&&!gps.isEnabled());
if(mode=="boot")return 0;
if(mode=="cycles") {
for(unsigned cycle=0;cycle<4;++cycle) {
start();assert(!gps.isValid());
unsigned probes=Wire.transactions,begins=Serial1.begins,ends=Serial1.ends;
gps.begin();assert(Wire.transactions==probes);
assert(Serial1.begins==begins&&Serial1.ends==ends);
stop();probes=Wire.transactions;ends=Serial1.ends;
gps.stop();assert(Wire.transactions==probes&&Serial1.ends==ends);
}
return 0;
}
if(mode=="manager") {
sensors.initBasicGPS();
assert(sensors.gps_detected&&!sensors.gps_active);assertOff();
assert(sensors.setSettingValue("gps","1"));
assert(sensors.gps_active&&gps.isEnabled()&&Wire.gnssOn());
assert(sensors.setSettingValue("gps","0"));assertOff();
sensors.setPowerSavingEnabled(true);
assert(sensors.setSettingValue("gps","1"));
assert(sensors.gps_active&&gps.getGPSPowerSaving()&&Wire.gnssOn());
gps.setNextGPSOff(millis()+1);gps.stopTimeSync();now_ms+=2;
sensors.loop();assert(!sensors.gps_active);assertOff();
uint32_t wake=gps.getNextGPSOn();assert(wake!=0);
now_ms=wake;sensors.loop();
assert(sensors.gps_active&&gps.isEnabled()&&Wire.gnssOn());
assert(sensors.setSettingValue("gps","0"));assertOff();
return 0;
}
assert(mode=="tapshutdown");
start();
Serial1.feed("$GPGGA,123456,4760.000,N,12200.000,W,1,08,1.0,1.0,M,0.0,M,,*00\r\n");
gps.loop();assert(gps.isValid());
assert(gps_tap.ggaFix()==1&&gps_tap.ggaUsed()==8);
events.clear();board.shutdownPeripherals();assertOff();
assert(first("radio-off")<first("gnss-off"));
assert(first("uart-end")<first("gnss-off"));
start();assert(!gps.isValid());stop();
}
+52
View File
@@ -121,6 +121,58 @@ class FirmwareRamTest(unittest.TestCase):
}, "muzi_base_uno_superIO_companion_radio_ble")
self.assertEqual(policy["components"]["display_pixels_and_driver"], 4096)
def test_wio_l2_counts_internal_sprite_fallback_and_dma_beside_psram(self):
flags = {"DISPLAY_CLASS": "WioTrackerL2Display", "UI_ZOOM": 2}
for target in ("Wio_Tracker_L2_repeater", "Wio_Tracker_L2_companion_radio_ble"):
with self.subTest(target=target):
policy = ram.requirements("ESP32_PLATFORM", flags, target)
self.assertEqual(policy["components"]["display_pixels_and_driver"],
160 * 120 + 1024)
self.assertEqual(policy["components"]["display_dma_and_bus"], 16384)
self.assertGreaterEqual(policy["required_contiguous_bytes"], 160 * 120 + 1024)
psram = ram.requirements("ESP32_PLATFORM", {**flags, "BOARD_HAS_PSRAM": 1}, target)
self.assertEqual(psram, policy)
without = ram.requirements("ESP32_PLATFORM", {"UI_ZOOM": 2}, target)
display_components = (policy["components"]["display_pixels_and_driver"]
+ policy["components"]["display_dma_and_bus"]
+ policy["components"]["screen_objects_and_history"])
self.assertEqual(policy["required_heap_bytes"] - without["required_heap_bytes"],
display_components)
def test_wio_l2_zoom_and_base_color_depth_cannot_erase_actual_sprite_cost(self):
flags = {"DISPLAY_CLASS": "WioTrackerL2Display"}
cases = (({}, 320 * 240), ({"UI_ZOOM": 2, "UI_BUFFER_COLOR_DEPTH": 4}, 160 * 120),
({"UI_ZOOM": 2, "UI_COORD_SCALE": 2}, 160 * 120),
({"UI_ZOOM": 2, "UI_BUFFER_COLOR_DEPTH": 16}, 160 * 120 * 2),
({"UI_ZOOM": "1.5f", "UI_BUFFER_COLOR_DEPTH": 24}, 214 * 160 * 3),
({"UI_ZOOM": "0.5", "UI_BUFFER_COLOR_DEPTH": 32}, 640 * 480 * 4))
for overrides, pixels in cases:
with self.subTest(overrides=overrides):
policy = ram.requirements("ESP32_PLATFORM", {**flags, **overrides}, "Wio_Tracker_L2_repeater")
self.assertEqual(policy["components"]["display_pixels_and_driver"], pixels + 1024)
self.assertGreaterEqual(policy["required_contiguous_bytes"], pixels + 1024)
for override in ({"UI_ZOOM": 0}, {"UI_ZOOM": -1}, {"UI_ZOOM": "inf"},
{"UI_ZOOM": "nan"}, {"UI_ZOOM": "auto"},
{"UI_COORD_SCALE": 0}, {"UI_BUFFER_COLOR_DEPTH": 7}):
with self.subTest(invalid=override), self.assertRaises(ValueError):
ram.requirements("ESP32_PLATFORM", {**flags, **override}, "Wio_Tracker_L2_repeater")
with self.assertRaisesRegex(ValueError, "requires ESP32"):
ram.requirements("NRF52_PLATFORM", flags, "unsupported_display")
def test_wio_l2_budget_tracks_real_sprite_and_qspi_geometry(self):
display = (ROOT / "variants/wio-tracker-l2/WioTrackerL2Display.h").read_text()
base = (ROOT / "src/helpers/ui/LGFXDisplay.cpp").read_text()
self.assertIn("WioTrackerL2Display() : LGFXDisplay(320, 240, disp)", display)
self.assertIn("cfg.panel_width = 240;", display)
self.assertIn("cfg.panel_height = 320;", display)
self.assertIn("buffer.setColorDepth(8);", display)
self.assertIn("buffer.setPsram(true);", display)
self.assertIn("buffer.createSprite(width(), height());", display)
self.assertIn("lgfx::Bus_SPI _bus_instance;", display)
self.assertIn("buffer.setPsram(false);", base)
self.assertIn("buffer.setColorDepth(UI_BUFFER_COLOR_DEPTH);", base)
self.assertIn("buffer.createSprite(width() * _coordinateScale,", base)
def test_heap_tables_and_ota_remain_in_runtime_budget(self):
policy = ram.requirements("ESP32_PLATFORM", {
"ENABLE_OTA": 1, "OTA_HEAP_CONTEXT": 1,
+235
View File
@@ -0,0 +1,235 @@
#!/usr/bin/env python3
"""Host-test Wio L2's real GNSS provider, expander operations and GPS lifecycle.
Only the UART/I2C/ESP32 hardware boundaries and NMEA library are mocked. No
physical power/current claims are made by these tests.
"""
from pathlib import Path
import re
import shutil
import subprocess
import sys
import tempfile
import unittest
from test_gps_upstream_adaptations import CAYENNE, MESH, NMEA, RTC
from test_replay_reset_integration import extract_braced
ROOT = Path(__file__).resolve().parents[1]
VARIANT = ROOT / "variants/wio-tracker-l2"
ARDUINO = r'''
#pragma once
#include <cstdint>
#include <cstddef>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cassert>
#include <deque>
#include <string>
#include <vector>
#define HIGH 1
#define LOW 0
#define OUTPUT 1
#define INPUT 0
struct Event { std::string name; int a; int b; };
inline std::vector<Event> events;
inline int modes[64] = {};
inline uint32_t now_ms = 1;
inline void event(const char* name, int a=0, int b=0) { events.push_back({name,a,b}); }
inline uint32_t millis() { return now_ms; }
inline void delay(uint32_t ms) { now_ms += ms; }
inline void yield() { ++now_ms; }
inline void pinMode(int pin,int mode) { assert(pin>=0&&pin<64);modes[pin]=mode;event("pin",pin,mode); }
inline void digitalWrite(int,int) {}
inline int digitalRead(int) { return HIGH; }
class Stream {
public:
virtual ~Stream() = default;
virtual int available() { return 0; }
virtual int peek() { return -1; }
virtual int read() { return -1; }
virtual void flush() {}
virtual size_t write(uint8_t) { return 1; }
virtual size_t write(const uint8_t*,size_t size) { return size; }
};
class HardwareSerial : public Stream {
public:
bool installed=false;
unsigned begins=0, ends=0;
int rx=-1,tx=-1;
std::deque<char> incoming;
void setPins(int r,int t) { rx=r;tx=t;event("uart-pins",r,t); }
void begin(unsigned baud) {
assert(rx>=0&&tx>=0);installed=true;++begins;
pinMode(rx,INPUT);pinMode(tx,OUTPUT);event("uart-begin",baud);
}
void end() { installed=false;++ends;incoming.clear();event("uart-end"); }
int available() override { return installed ? int(incoming.size()) : 0; }
int peek() override { return available() ? incoming.front() : -1; }
int read() override { if(!available())return -1;int c=incoming.front();incoming.pop_front();return c; }
void feed(const char* text) { while(*text)incoming.push_back(*text++); }
};
inline HardwareSerial Serial1, Serial;
using std::isnan;
'''
WIRE = r'''
#pragma once
#include <Arduino.h>
class TwoWire {
std::vector<uint8_t> sent;
uint8_t address=0;
public:
bool present=true;
unsigned transactions=0;
uint8_t regs[256];
TwoWire() { memset(regs,0xff,sizeof(regs)); }
bool gnssOn() const { return !(regs[7]&(1<<5)) && (regs[3]&(1<<5)); }
void beginTransmission(uint8_t a) { address=a;sent.clear();++transactions; }
size_t write(uint8_t byte) { sent.push_back(byte);return 1; }
uint8_t endTransmission() {
if(!present)return 4;
if(address==0x21&&sent.size()==2) {
const bool before=gnssOn();regs[sent[0]]=sent[1];
if(before!=gnssOn()) {
// Both enabling and disabling the receiver happen with UART pads
// released, never an idle-high MCU TX into an unpowered GNSS RX.
assert(!Serial1.installed&&modes[17]==INPUT&&modes[18]==INPUT);
event(gnssOn()?"gnss-on":"gnss-off");
}
event("i2c-write",sent[0],sent[1]);
}
return 0;
}
int requestFrom(int,int size) { return size; }
int read() { return 0; }
};
inline TwoWire Wire;
'''
ESP_BOARD = r'''
#pragma once
#include <Arduino.h>
class ESP32Board {
public:
virtual void begin() {}
virtual uint16_t getBattMilliVolts() { return 0; }
virtual const char* getManufacturerName() const { return "host"; }
virtual void onBeforeTransmit() {}
virtual void onAfterTransmit() {}
virtual bool isExternalPowered() { return false; }
virtual void shutdownPeripherals();
};
'''
def harness(mutation=None):
board = (VARIANT / "WioTrackerL2Board.cpp").read_text()
if mutation == "boot-rail-on":
board = board.replace("expSetOutput(EXP_PIN_GNSS_EN, LOW);",
"expSetOutput(EXP_PIN_GNSS_EN, HIGH);", 1)
constants = "\n".join(line for line in board.splitlines()
if re.match(r"static const uint8_t TCA_REG_", line))
expander = "\n".join(extract_braced(board, signature) for signature in (
"bool WioTrackerL2Board::expWriteReg(",
"void WioTrackerL2Board::expSetOutput(",
"void WioTrackerL2Board::expSetInput(",
"void WioTrackerL2Board::expWritePin(",
"bool WioTrackerL2Board::initExpander()",
"int WioTrackerL2Board::expReadInputs()",
))
manager = (ROOT / "src/helpers/sensors/EnvironmentSensorManager.cpp").read_text()
lifecycle = "\n".join(extract_braced(manager, signature) for signature in (
"bool EnvironmentSensorManager::setSettingValue(",
"void EnvironmentSensorManager::setPowerSavingEnabled(",
"void EnvironmentSensorManager::finishBasicGpsDiscovery(",
"void EnvironmentSensorManager::serviceBasicGpsDiscovery()",
"void EnvironmentSensorManager::initBasicGPS()",
"void EnvironmentSensorManager::armGpsPowerSavingCycle()",
"void EnvironmentSensorManager::start_gps()",
"void EnvironmentSensorManager::stop_gps()",
"void EnvironmentSensorManager::loop()",
))
shutdown = extract_braced((ROOT / "src/helpers/ESP32Board.cpp").read_text(),
"void ESP32Board::shutdownPeripherals()")
fixture = (ROOT / "test/fixtures/wio_tracker_l2_gps_power.cpp").read_text()
return (fixture.replace("@EXPANDER@", constants + "\n" + expander)
.replace("@LIFECYCLE@", lifecycle).replace("@SHUTDOWN@", shutdown))
class WioTrackerL2GpsPowerTests(unittest.TestCase):
def compile_run(self, mode, mutation=None, fail=False):
compiler = shutil.which("g++") or shutil.which("clang++")
self.assertIsNotNone(compiler, "a host C++17 compiler is required")
with tempfile.TemporaryDirectory(prefix="wio-l2-gps-power-") as directory:
work = Path(directory)
files = {
"Arduino.h": ARDUINO, "Wire.h": WIRE, "Mesh.h": MESH,
"MicroNMEA.h": NMEA, "RTClib.h": RTC, "CayenneLPP.h": CAYENNE,
"helpers/ESP32Board.h": ESP_BOARD,
}
provider = (VARIANT / "WioTrackerL2LocationProvider.h").read_text()
if mutation == "never-power-off":
provider = provider.replace("_board.setGnssPower(false);", "// negative control")
# Copy the production adaptation unchanged except explicit negative
# controls; quoted includes still resolve the real board header.
files["WioTrackerL2LocationProvider.h"] = provider
for name, contents in files.items():
path = work / name
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(contents)
source = work / "case.cpp"
source.write_text(harness(mutation))
binary = work / "case"
command = [compiler, "-std=c++17", "-Wall", "-Wextra", "-Werror",
"-Wno-unused-parameter", "-DENV_INCLUDE_GPS=1",
"-DESP32_PLATFORM=1", "-DENV_SKIP_GPS_DETECT=1",
"-DPIN_GPS_TX=18", "-DPIN_GPS_RX=17", "-DGPS_BAUD_RATE=9600",
"-I", str(work), "-I", str(VARIANT),
"-I", str(ROOT / "src"), str(source),
str(ROOT / "src/helpers/SensorManager.cpp"), "-o", str(binary)]
if sys.platform.startswith("linux"):
command[1:1] = ["-fsanitize=address,undefined", "-fno-sanitize-recover=all",
"-fno-pie", "-no-pie"]
built = subprocess.run(command, text=True, capture_output=True)
self.assertEqual(built.returncode, 0, built.stderr)
ran = subprocess.run([str(binary), mode], text=True, capture_output=True)
if fail:
self.assertNotEqual(ran.returncode, 0, "negative control did not fail")
else:
self.assertEqual(ran.returncode, 0, ran.stdout + ran.stderr)
def test_boot_gnss_is_off_without_global_constructor_io(self):
self.compile_run("boot")
def test_begin_stop_are_idempotent_and_order_uart_before_rail(self):
self.compile_run("cycles")
def test_failed_expander_leaves_uart_released_and_provider_disabled(self):
self.compile_run("failed")
def test_actual_manager_saved_gps_off_and_scheduled_sleep_wake(self):
self.compile_run("manager")
def test_gps_tap_parser_and_actual_esp32_shutdown_are_preserved(self):
self.compile_run("tapshutdown")
def test_negative_controls_detect_boot_on_and_missing_rail_stop(self):
self.compile_run("boot", "boot-rail-on", fail=True)
self.compile_run("cycles", "never-power-off", fail=True)
def test_target_uses_board_provider_without_fake_enable_gpio(self):
target = (VARIANT / "target.cpp").read_text()
self.assertIn("GpsTapStream gps_tap(Serial1);", target)
self.assertIn("WioTrackerL2LocationProvider gps(board, gps_tap, &rtc_clock);", target)
self.assertIn("EnvironmentSensorManager sensors(gps);", target)
provider = (VARIANT / "WioTrackerL2LocationProvider.h").read_text()
self.assertIn("MicroNMEALocationProvider(stream, clock, -1, -1)", provider)
self.assertNotIn("getPinEn()", provider)
if __name__ == "__main__":
unittest.main()
+3
View File
@@ -55,6 +55,9 @@ PIN shown on the device screen.
- **GNSS**: GPS+BeiDou+GLONASS are enabled at start (`$PCAS04,7`). A watchdog
resets the module if it tracks 4+ strong satellites for minutes without
producing a fix. Raw NMEA can be logged to SD for diagnosis.
The GNSS rail starts off. The board-local provider powers it only while
GPS is enabled, including scheduled wake periods, and releases the UART
pads before cutting power so they cannot back-power the receiver.
- **Power**: CPU drops to 80 MHz while the screen sleeps; the speaker amp and
I2S clocks run only while a tone plays; the GNSS and Grove rails are gated;
idle Bluetooth switches to slow advertising.
@@ -66,9 +66,12 @@ bool WioTrackerL2Board::initExpander() {
delay(10);
expSetOutput(EXP_PIN_BAT_ADC_EN, HIGH);
delay(10);
expSetOutput(EXP_PIN_GNSS_EN, HIGH);
// The location provider owns this rail, including saved GPS-off settings.
// Keep it off until its explicit power/reset sequence starts acquisition.
expSetOutput(EXP_PIN_GNSS_EN, LOW);
delay(10);
// GNSS reset is active HIGH; hold 10ms then release LOW so the L76K runs
// Configure active-HIGH reset while unpowered. The provider pulses it again
// after enabling the GNSS rail, before it starts receiving NMEA.
expSetOutput(EXP_PIN_GNSS_RST, HIGH);
delay(10);
expWritePin(EXP_PIN_GNSS_RST, LOW);
@@ -0,0 +1,51 @@
#pragma once
#include "WioTrackerL2Board.h"
#include <helpers/sensors/MicroNMEALocationProvider.h>
// The GNSS enable/reset pins are TCA9535 channels, not ESP32 GPIOs. Keep this
// adaptation local to the board while retaining the normal NMEA/GpsTap stream.
class WioTrackerL2LocationProvider : public MicroNMEALocationProvider {
WioTrackerL2Board& _board;
bool _powered = false;
static void releaseUart() {
Serial1.end();
// The MCU's TX idle level must not feed an unpowered GNSS through its RX.
pinMode(PIN_GPS_TX, INPUT);
pinMode(PIN_GPS_RX, INPUT);
}
public:
WioTrackerL2LocationProvider(WioTrackerL2Board& board, Stream& stream,
mesh::RTCClock* clock)
: MicroNMEALocationProvider(stream, clock, -1, -1), _board(board) {}
void begin() override {
if (_powered) return;
// The manager starts Serial1 before provider begin(). Release its pads
// until the board-specific power/reset sequence has made GNSS ready.
releaseUart();
if (!_board.expanderOK()) return;
_board.setGnssPower(true);
Serial1.setPins(PIN_GPS_TX, PIN_GPS_RX);
#ifdef GPS_BAUD_RATE
Serial1.begin(GPS_BAUD_RATE);
#else
Serial1.begin(9600);
#endif
MicroNMEALocationProvider::begin();
clearPositionFix();
_powered = true;
}
void stop() override {
if (!_powered) return;
MicroNMEALocationProvider::stop();
releaseUart();
_board.setGnssPower(false);
_powered = false;
}
bool isEnabled() override { return _powered; }
};
+1 -1
View File
@@ -11,7 +11,7 @@ WRAPPER_CLASS radio_driver(radio, board);
ESP32RTCClock fallback_clock;
AutoDiscoverRTCClock rtc_clock(fallback_clock);
GpsTapStream gps_tap(Serial1); // GSV sats-in-view / SNR watcher
MicroNMEALocationProvider gps(gps_tap, &rtc_clock);
WioTrackerL2LocationProvider gps(board, gps_tap, &rtc_clock);
EnvironmentSensorManager sensors(gps);
#ifdef DISPLAY_CLASS
+1 -1
View File
@@ -8,7 +8,7 @@
#include <helpers/AutoDiscoverRTCClock.h>
#include <helpers/SensorManager.h>
#include <helpers/sensors/EnvironmentSensorManager.h>
#include <helpers/sensors/MicroNMEALocationProvider.h>
#include "WioTrackerL2LocationProvider.h"
#include "GpsTap.h"
#ifdef DISPLAY_CLASS
#include "WioTrackerL2Display.h"