Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
f3b0709
indicator: RP2040 peripherals for the main firmware
caveman99 Jul 12, 2026
082e9d8
indicator: address review
caveman99 Jul 12, 2026
50eb4ec
indicator: assign the GPS FakeUART at runtime
caveman99 Jul 12, 2026
2c98c54
indicator: bump device-ui pin to 27e6c0c
caveman99 Jul 12, 2026
76adb48
indicator: ping/pong link probe, non-blocking runOnce, FakeI2C locking
caveman99 Jul 12, 2026
4a7d8d5
indicator: link resync, config-honoring GPS, bridged-bus routing, sta…
caveman99 Jul 12, 2026
ae839eb
indicator: retry lost link round trips, I2CResult UNSPECIFIED
caveman99 Jul 12, 2026
8ee2f70
indicator: nack responses, rename bridge classes to I2CProxy/UARTProxy
caveman99 Jul 13, 2026
e722467
indicator: refuse a co-processor that speaks another protocol version
caveman99 Jul 13, 2026
8057c61
indicator: regen protos, interdevice protocol version 2
caveman99 Jul 13, 2026
57866fa
indicator: per-task I2C contexts, gated handshake, retryable link fai…
caveman99 Jul 13, 2026
8b95f44
indicator: fail safe on a peer mismatch, wait out card maintenance
caveman99 Jul 13, 2026
ec544bf
indicator: regen protos, FileStatus back on the original tags
caveman99 Jul 13, 2026
d70bf24
indicator: regen protos, ping/pong carry the InterdeviceVersion enum
caveman99 Jul 13, 2026
4fe7538
indicator: point the protobufs submodule at the merged interdevice pr…
caveman99 Jul 13, 2026
a13bff5
indicator: pin device-ui to the branch with the remote SD support
caveman99 Jul 13, 2026
3225430
indicator: honor the txOnly flag of flush, report dropped GPS writes
caveman99 Jul 13, 2026
33ae81a
indicator: decide the log tag on the formatted message, hex request ids
caveman99 Jul 13, 2026
a92e3c7
indicator: SD mount, eject and format commands over the link
caveman99 Jul 14, 2026
04d2a9b
indicator: bound how long a busy card state blocks the UI task
caveman99 Jul 14, 2026
e5ee49f
indicator: a busy co-processor must not block the UI task for ever
caveman99 Jul 14, 2026
3793c09
indicator: start each request from an aligned receive buffer
caveman99 Jul 15, 2026
87362bf
indicator: enlarge the LVGL heap for low-zoom map tiles
caveman99 Jul 15, 2026
4a5111d
indicator: advance the device-ui and protobufs pins to the merged com…
caveman99 Jul 15, 2026
76e2bfa
Merge branch 'develop' into indicator-comms
caveman99 Jul 20, 2026
0d09dbd
Merge branch 'develop' into indicator-comms
mverch67 Jul 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ lib_deps =
[device-ui_base]
lib_deps =
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
https://github.com/meshtastic/device-ui/archive/ef573c368767625ffbe8c32cf921ea7366f2dd53.zip
https://github.com/meshtastic/device-ui/archive/6968b23e5270dccb2fc8531a7a37687131354fa8.zip

; Common libs for environmental measurements in telemetry module
[environmental_base]
Expand Down
30 changes: 20 additions & 10 deletions src/RedirectablePrint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ size_t RedirectablePrint::write(uint8_t c)
// serial port said (which could be zero)
}

size_t RedirectablePrint::vprintf(const char *logLevel, const char *format, va_list arg)
size_t RedirectablePrint::vprintf(const char *logLevel, const char *format, va_list arg, const char *threadName)
{
va_list copy;
#if ARCH_PORTDUINO
Expand Down Expand Up @@ -78,6 +78,21 @@ size_t RedirectablePrint::vprintf(const char *logLevel, const char *format, va_l
if (!std::isprint(static_cast<unsigned char>(printBuf[f])) && printBuf[f] != '\n')
printBuf[f] = '#';
}
// A message with its own "[Tag] " (the device-ui task has no OSThread)
// uses it instead of the thread name, printed uncolored like a real one.
size_t tagLen = 0;
if (printBuf[0] == '[') {
const char *end = (const char *)memchr(printBuf, ']', len < 24 ? len : 24);
if (end && (size_t)(end - printBuf) + 1 < len && end[1] == ' ')
tagLen = end - printBuf + 2;
}
if (tagLen)
Print::write(printBuf, tagLen);
else if (threadName) {
Print::write("[", 1);
Print::write(threadName, strlen(threadName));
Print::write("] ", 2);
}
if (color && logLevel != nullptr) {
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0)
Print::write("\u001b[34m", 5);
Expand All @@ -88,7 +103,7 @@ size_t RedirectablePrint::vprintf(const char *logLevel, const char *format, va_l
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_ERROR) == 0)
Print::write("\u001b[31m", 5);
}
len = Print::write(printBuf, len);
len = tagLen + Print::write(printBuf + tagLen, len - tagLen);
if (color && logLevel != nullptr) {
Print::write("\u001b[0m", 4);
}
Expand Down Expand Up @@ -161,13 +176,8 @@ void RedirectablePrint::log_to_serial(const char *logLevel, const char *format,
#endif
}
auto thread = concurrency::OSThread::currentThread;
if (thread) {
print("[");
// printf("%p ", thread);
// assert(thread->ThreadName.length());
print(thread->ThreadName);
print("] ");
}
// the tag is printed by vprintf, which knows whether the formatted
// message already carries one of its own

#ifdef DEBUG_HEAP
// Add heap free space bytes prefix before every log message
Expand All @@ -178,7 +188,7 @@ void RedirectablePrint::log_to_serial(const char *logLevel, const char *format,
#endif
#endif // DEBUG_HEAP

r += vprintf(logLevel, format, arg);
r += vprintf(logLevel, format, arg, thread ? thread->ThreadName.c_str() : nullptr);
}

void RedirectablePrint::log_to_syslog(const char *logLevel, const char *format, va_list arg)
Expand Down
5 changes: 3 additions & 2 deletions src/RedirectablePrint.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ class RedirectablePrint : public Print
*/
void log(const char *logLevel, const char *format, ...) __attribute__((format(printf, 3, 4)));

/** like printf but va_list based */
size_t vprintf(const char *logLevel, const char *format, va_list arg);
/** like printf but va_list based. threadName is prefixed unless the
* message already starts with a "[Tag] " of its own */
size_t vprintf(const char *logLevel, const char *format, va_list arg, const char *threadName = nullptr);

void hexDump(const char *logLevel, const unsigned char *buf, uint16_t len);

Expand Down
3 changes: 2 additions & 1 deletion src/configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef WIRE_INTERFACES_COUNT
// Officially an NRF52 macro
// Repurposed cross-platform to identify devices using Wire1
#if defined(I2C_SDA1) || defined(PIN_WIRE1_SDA)
// The SenseCAP Indicator has a second bus bridged to the RP2040 (I2CProxy)
#if defined(I2C_SDA1) || defined(PIN_WIRE1_SDA) || defined(SENSECAP_INDICATOR)
#define WIRE_INTERFACES_COUNT 2
#elif HAS_WIRE
#define WIRE_INTERFACES_COUNT 1
Expand Down
11 changes: 10 additions & 1 deletion src/detect/ScanI2CTwoWire.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
#if defined(ARCH_PORTDUINO)
#include "linux/LinuxHardwareI2C.h"
#endif
#if defined(SENSECAP_INDICATOR)
#include "mesh/comms/I2CProxy.h"
#endif
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32)
#include "meshUtils.h" // vformat

Expand Down Expand Up @@ -283,7 +286,11 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)

#if WIRE_INTERFACES_COUNT == 2
if (port == I2CPort::WIRE1) {
#if defined(SENSECAP_INDICATOR)
i2cBus = i2cProxy; // WIRE1 is bridged to the RP2040
#else
i2cBus = &Wire1;
#endif
} else {
#endif
i2cBus = &Wire;
Expand Down Expand Up @@ -968,7 +975,9 @@ TwoWire *ScanI2CTwoWire::fetchI2CBus(ScanI2C::DeviceAddress address)
if (address.port == ScanI2C::I2CPort::WIRE) {
return &Wire;
} else {
#if WIRE_INTERFACES_COUNT == 2
#if defined(SENSECAP_INDICATOR)
return i2cProxy; // WIRE1 is bridged to the RP2040
#elif WIRE_INTERFACES_COUNT == 2
return &Wire1;
#else
return &Wire;
Expand Down
32 changes: 31 additions & 1 deletion src/gps/GPS.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ template <typename T, std::size_t N> std::size_t array_count(const T (&)[N])
#define GPS_SERIAL_PORT Serial1
#endif

#if defined(ARCH_NRF52)
#if defined(SENSECAP_INDICATOR)
UARTProxy *GPS::_serial_gps = nullptr; // assigned in createGps(), see there
#elif defined(ARCH_NRF52)
Uart *GPS::_serial_gps = &GPS_SERIAL_PORT;
#elif defined(ARCH_ESP32) || defined(ARCH_PORTDUINO) || defined(ARCH_STM32)
HardwareSerial *GPS::_serial_gps = &GPS_SERIAL_PORT;
Expand Down Expand Up @@ -1428,6 +1430,21 @@ void GPS::publishUpdate()

int32_t GPS::runOnce()
{
#if defined(SENSECAP_INDICATOR)
// No model probe on the bridged fake UART (the module only streams
// NMEA), but the user's GPS mode setting must still be honored
if (!GPSInitFinished) {
if (!_serial_gps || config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT) {
LOG_INFO("GPS set to not-present. Skip probe");
return disable();
}
if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
return disable();
}
GPSInitFinished = true;
publishUpdate();
}
#else
if (!GPSInitFinished) {
if (!_serial_gps || config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT) {
LOG_INFO("GPS set to not-present. Skip probe");
Expand All @@ -1448,6 +1465,7 @@ int32_t GPS::runOnce()
GPSInitFinished = true;
publishUpdate();
}
#endif

// ======================== GPS_ACTIVE state ========================
// In GPS_ACTIVE state, GPS is powered on and we're receiving NMEA messages.
Expand Down Expand Up @@ -1910,8 +1928,16 @@ std::unique_ptr<GPS> GPS::createGps()
} else
return nullptr;
#endif
#if defined(SENSECAP_INDICATOR)
// assigned at runtime, static initialization order across translation
// units is undefined
_serial_gps = uartProxy;
if (!_serial_gps)
return nullptr;
#else
if (!_rx_gpio || !_serial_gps) // Configured to have no GPS at all
return nullptr;
#endif

auto new_gps = std::unique_ptr<GPS>(new GPS());
new_gps->rx_gpio = _rx_gpio;
Expand Down Expand Up @@ -1972,8 +1998,12 @@ std::unique_ptr<GPS> GPS::createGps()
_serial_gps->setRxBufferSize(SERIAL_BUFFER_SIZE); // the default is 256
#endif

#if defined(SENSECAP_INDICATOR)
LOG_DEBUG("Use the RP2040 tunnel for GPS, no local pins");
#else
LOG_DEBUG("Use GPIO%d for GPS RX", new_gps->rx_gpio);
LOG_DEBUG("Use GPIO%d for GPS TX", new_gps->tx_gpio);
#endif

// ESP32 has a special set of parameters vs other arduino ports
#if defined(ARCH_ESP32)
Expand Down
8 changes: 7 additions & 1 deletion src/gps/GPS.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
#include "input/UpDownInterruptImpl1.h"
#include "modules/PositionModule.h"

#ifdef SENSECAP_INDICATOR
#include "mesh/comms/UARTProxy.h"
#endif

// Allow defining the polarity of the ENABLE output. default is active high
#ifndef GPS_EN_ACTIVE
#define GPS_EN_ACTIVE 1
Expand Down Expand Up @@ -216,7 +220,9 @@ class GPS : private concurrency::OSThread
CallbackObserver<GPS, void *> notifyDeepSleepObserver = CallbackObserver<GPS, void *>(this, &GPS::prepareDeepSleep);

/** If !NULL we will use this serial port to construct our GPS */
#if defined(ARCH_RP2040)
#if defined(SENSECAP_INDICATOR)
static UARTProxy *_serial_gps;
#elif defined(ARCH_RP2040)
static SerialUART *_serial_gps;
#elif defined(ARCH_NRF52)
static Uart *_serial_gps;
Expand Down
9 changes: 5 additions & 4 deletions src/gps/RTC.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "gps/RTC.h"
#include "configuration.h"
#include "detect/ScanI2C.h"
#include "detect/ScanI2CTwoWire.h"
#include "main.h"
#include "modules/NodeInfoModule.h"
#include <Throttle.h>
Expand Down Expand Up @@ -97,7 +98,7 @@ RTCSetResult readFromRTC()
uint32_t now = millis();
Melopero_RV3028 rtc;
#if WIRE_INTERFACES_COUNT == 2
rtc.initI2C(rtc_found.port == ScanI2C::I2CPort::WIRE1 ? Wire1 : Wire);
rtc.initI2C(*ScanI2CTwoWire::fetchI2CBus(rtc_found));
#else
rtc.initI2C();
#endif
Expand Down Expand Up @@ -146,7 +147,7 @@ RTCSetResult readFromRTC()
uint32_t now = millis();

#if WIRE_INTERFACES_COUNT == 2
rtc.begin(rtc_found.port == ScanI2C::I2CPort::WIRE1 ? Wire1 : Wire);
rtc.begin(*ScanI2CTwoWire::fetchI2CBus(rtc_found));
#else
rtc.begin(Wire);
#endif
Expand Down Expand Up @@ -315,7 +316,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
if (rtc_found.address == RV3028_RTC) {
Melopero_RV3028 rtc;
#if WIRE_INTERFACES_COUNT == 2
rtc.initI2C(rtc_found.port == ScanI2C::I2CPort::WIRE1 ? Wire1 : Wire);
rtc.initI2C(*ScanI2CTwoWire::fetchI2CBus(rtc_found));
#else
rtc.initI2C();
#endif
Expand All @@ -340,7 +341,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
#endif

#if WIRE_INTERFACES_COUNT == 2
rtc.begin(rtc_found.port == ScanI2C::I2CPort::WIRE1 ? Wire1 : Wire);
rtc.begin(*ScanI2CTwoWire::fetchI2CBus(rtc_found));
#else
rtc.begin(Wire);
#endif
Expand Down
Loading
Loading