Compare commits

..
Author SHA1 Message Date
Thomas Göttgens 377ba3deb9 Merge branch 't-impulse-plus' of https://github.com/meshtastic/firmware into t-impulse-plus 2026-05-19 14:36:40 +02:00
Thomas Göttgens b10d1d5d2c set correct geometry. 2026-05-19 14:36:31 +02:00
Thomas Göttgens ac23497a49 Merge branch 'master' into t-impulse-plus 2026-05-19 12:52:19 +02:00
Thomas GöttgensandCopilot Autofix powered by AI 64eded235f Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-19 12:51:59 +02:00
Thomas Göttgens 66d404932d Fix GPS pins and speed. Module enable is active low, speed is 38400 2026-05-19 12:46:30 +02:00
Thomas Göttgens ae63e9b80f Merge branch 't-impulse-plus' of https://github.com/meshtastic/firmware into t-impulse-plus 2026-05-19 12:37:37 +02:00
Thomas Göttgens 8a82f5317b Dim the haptic duration a bit. 2026-05-19 12:37:25 +02:00
github-actions[bot]andvidplace7 82aefd1af1 Upgrade trunk (#10503)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-05-19 05:23:01 -05:00
Ben Meadors c4daf0ae47 Merge branch 'master' into t-impulse-plus 2026-05-19 05:22:11 -05:00
RikerandThomas Göttgens 0f9eb86830 Enabled SX_LNA_EN by default (#10469)
* Enabled SX_LNA_EN by default
* Update I2C configuration for IO direction and pull settings

---------

Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
2026-05-19 04:56:01 -05:00
Thomas Göttgens c7c7e628c5 Merge branch 'master' into t-impulse-plus 2026-05-19 09:54:56 +02:00
Thomas Göttgens e2aa44ec54 T-Echo-Card support (#10267)
# Conflicts:
#	src/graphics/draw/UIRenderer.cpp
2026-05-19 09:53:45 +02:00
Thomas Göttgens 40e191ac58 enable proper device model 2026-05-19 08:10:09 +02:00
Thomas Göttgens c417ba8e72 Merge branch 't-impulse-plus' of https://github.com/meshtastic/firmware into t-impulse-plus 2026-05-19 08:01:42 +02:00
Thomas Göttgens 0832330327 Fix antenna switch initialization logic 2026-05-19 08:00:02 +02:00
Thomas Göttgens 2e343a156d trunk fmt 2026-05-19 07:22:30 +02:00
Thomas Göttgens 6fde0f949b enable nrfutil uploads 2026-05-19 07:22:30 +02:00
Thomas Göttgens 8fa7ae40a7 enable Charging Indicator 2026-05-19 07:22:30 +02:00
Thomas Göttgens 8a8e4b841a Haptic Feedback (short and long press) 2026-05-19 07:22:30 +02:00
Thomas Göttgens 0c1457b5dc trunk'd 2026-05-19 07:22:30 +02:00
Thomas Göttgens eda3a79a97 Enable small screen layout 64x32 2026-05-19 07:22:30 +02:00
vidplace7 da34334bb2 Add Lilygo T-Impulse-Plus 2026-05-19 07:22:30 +02:00
github-actions[bot]andcaveman99 af3739fd63 Update protobufs (#10499)
Co-authored-by: caveman99 <25002+caveman99@users.noreply.github.com>
2026-05-19 07:22:15 +02:00
Thomas Göttgens 254fc47b2c trunk fmt 2026-05-19 07:18:12 +02:00
github-actions[bot]andvidplace7 0148a89ddb Upgrade trunk (#10493)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-05-18 20:49:28 -05:00
Thomas Göttgens fb119a9ff3 enable nrfutil uploads 2026-05-18 23:42:11 +02:00
Thomas Göttgens b9d6903ddb enable Charging Indicator 2026-05-18 23:41:41 +02:00
Thomas Göttgens 6199faacf1 cherry pick backport fix for cardputer 2026-05-18 23:24:32 +02:00
Thomas Göttgens ef2912672b Haptic Feedback (short and long press) 2026-05-18 22:52:37 +02:00
Thomas Göttgens e8ecd0dda2 trunk'd 2026-05-18 22:21:33 +02:00
Thomas Göttgens 5025ca45df Enable small screen layout 64x32 2026-05-18 22:16:10 +02:00
vidplace7 62bb2c0c3c Add Lilygo T-Impulse-Plus 2026-05-18 14:28:22 -04:00
JordandCopilot Autofix powered by AI fc5556b8e6 Clamp direct position packets to channel precision (fixes #8640) (#10383)
* Fix position precision for direct sends

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Clarify zero position precision logging

* Use const channel reference for position precision

* Use C linkage for position precision test entrypoints

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-16 14:08:46 -05:00
github-actions[bot]andvidplace7 502c5af524 Upgrade trunk (#10481)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-05-15 20:03:50 -05:00
renovate[bot] 05707079bd Update libch341-spi-userspace digest to 2e5ff75 (#10485)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-15 19:30:04 -05:00
Ben Meadors 1c05633fcd Add more support for small fonts in screen resolution determination (#10480) 2026-05-15 05:59:15 -05:00
github-actions[bot]andvidplace7 fce419b335 Upgrade trunk (#10476)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-05-14 06:43:06 -05:00
Ben Meadors 1ae4a538f5 Trunk 2026-05-13 09:27:05 -05:00
Andros Fenollosa c756bbe2c1 Fix WiFi TCP/HTTP services not starting without USB serial connected (#10460)
Move WiFi.onEvent(WiFiEvent) registration before createSSLCert() to
prevent a race where the ESP32 auto-reconnects during cert generation
and fires GOT_IP before the handler is attached, causing
onNetworkConnected() to never run and the TCP/HTTP API services to
never initialize when booting without USB serial.

Also call onNetworkConnected() from reconnectWiFi() on all platforms
(not just RP2040) as a safety net; it is already guarded by
APStartupComplete so it only runs once.
2026-05-13 09:26:44 -05:00
Austin 4c3ba612bb VSCode: Prepare for pioarduino transition (#10471)
Start reccomending the pioarduino VS Code extension instead of the PlatformIO extension.

pioarduino-based builds cannot complete correctly using the platformio extension. Normal platformio builds (nrf52, stm32) are unaffected//still work correctly.

Devs may need to delete their ~.platformio and .pio directories once after install in order to build properly.
2026-05-13 09:25:11 -05:00
Ben MeadorsandCopilot Autofix powered by AI 59025e4820 Add initial support for Station G3 variant (#10457)
* Add initial support for Station G3 variant

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-13 08:07:24 -05:00
renovate[bot] 0a7b3c723e Update NeoPixel to v1.15.5 (#10466)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-13 10:57:48 +02:00
github-actions[bot]andvidplace7 cd5d608e8d Upgrade trunk (#10461)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-05-12 06:07:09 -05:00
Ben Meadors 7f5184281d Make power status logging less chatty and track battery presence transitions (#10453) 2026-05-11 16:09:33 -05:00
Jonathan Bennett 4446b0f1a2 Add variantDefaultConfig and set eth_enabled to default true (#10454) 2026-05-11 14:51:21 -05:00
62 changed files with 1772 additions and 131 deletions
+7 -3
View File
@@ -8,17 +8,21 @@
"features": {
"ghcr.io/devcontainers/features/python:1": {
"installTools": true,
"version": "3.14"
"version": "3.13"
}
},
"customizations": {
"vscode": {
"extensions": [
"ms-vscode.cpptools",
"platformio.platformio-ide",
"Jason2866.esp-decoder",
"pioarduino.pioarduino-ide",
"Trunk.io"
],
"unwantedRecommendations": ["ms-azuretools.vscode-docker"],
"unwantedRecommendations": [
"ms-azuretools.vscode-docker",
"platformio.platformio-ide"
],
"settings": {
"extensions.ignoreRecommendations": true
}
+5 -5
View File
@@ -4,19 +4,19 @@ cli:
plugins:
sources:
- id: trunk
ref: v1.9.0
ref: v1.10.0
uri: https://github.com/trunk-io/plugins
lint:
enabled:
- checkov@3.2.528
- checkov@3.2.529
- renovate@43.150.0
- prettier@3.8.3
- trufflehog@3.95.2
- trufflehog@3.95.3
- yamllint@1.38.0
- bandit@1.9.4
- trivy@0.70.0
- taplo@0.10.0
- ruff@0.15.12
- ruff@0.15.13
- isort@8.0.1
- markdownlint@0.48.0
- oxipng@10.1.1
@@ -26,7 +26,7 @@ lint:
- hadolint@2.14.0
- shfmt@3.6.0
- shellcheck@0.11.0
- black@26.3.1
- black@26.5.1
- git-diff-check
- gitleaks@8.30.1
- clang-format@16.0.3
+4 -4
View File
@@ -1,10 +1,10 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"platformio.platformio-ide"
"Jason2866.esp-decoder",
"pioarduino.pioarduino-ide"
],
"unwantedRecommendations": [
"ms-vscode.cpptools-extension-pack"
"ms-vscode.cpptools-extension-pack",
"platformio.platformio-ide"
]
}
+18
View File
@@ -0,0 +1,18 @@
# Station G3 motherboard with a Raspberry Pi Zero 2W as the MCU daughterboard.
# Verify spidev / I2C device paths for your OS — they may differ.
Meta:
name: Station G3
support: community
compatible:
- raspberry-pi
Lora:
Module: sx1262
IRQ: 22 # BCM pin — wiki spec
Reset: 16 # BCM pin — wiki spec
Busy: 24 # BCM pin — wiki spec
# CS: 8 # BCM 8 = SPI0 CE0 (default); uncomment only to override
DIO2_AS_RF_SWITCH: true
DIO3_TCXO_VOLTAGE: true
spidev: spidev0.0
# SX126X_MAX_POWER: 19 # matches Station G2 firmware cap; raise carefully per PA jumper mode
+41
View File
@@ -0,0 +1,41 @@
{
"build": {
"arduino": {
"ldscript": "esp32s3_out.ld",
"memory_type": "qio_opi"
},
"core": "esp32",
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=0"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"hwids": [["0x303A", "0x1001"]],
"mcu": "esp32s3",
"variant": "station-g3"
},
"connectivity": ["wifi", "bluetooth", "lora"],
"debug": {
"default_tool": "esp-builtin",
"onboard_tools": ["esp-builtin"],
"openocd_target": "esp32s3.cfg"
},
"frameworks": ["arduino", "espidf"],
"name": "BQ Station G3",
"upload": {
"flash_size": "16MB",
"maximum_ram_size": 327680,
"maximum_size": 16777216,
"use_1200bps_touch": true,
"wait_for_upload_port": true,
"require_upload_port": true,
"speed": 921600
},
"url": "",
"vendor": "BQ Consulting"
}
+53
View File
@@ -0,0 +1,53 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v6.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
"extra_flags": "-DARDUINO_NRF52840_T_IMPULSE_PLUS -DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [["0x239A", "0x8029"]],
"usb_product": "T-Impulse-Plus-nRF52840",
"mcu": "nrf52840",
"variant": "t-impulse-plus",
"variants_dir": "variants",
"bsp": {
"name": "adafruit"
},
"softdevice": {
"sd_flags": "-DS140",
"sd_name": "s140",
"sd_version": "6.1.1",
"sd_fwid": "0x00B6"
},
"bootloader": {
"settings_addr": "0xFF000"
}
},
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
"onboard_tools": ["jlink"],
"svd_path": "nrf52840.svd"
},
"frameworks": ["arduino"],
"name": "Lilygo T-Impulse-Plus-nRF52840",
"upload": {
"maximum_ram_size": 248832,
"maximum_size": 815104,
"require_upload_port": true,
"speed": 115200,
"protocol": "nrfutil",
"protocols": [
"jlink",
"nrfjprog",
"nrfutil",
"stlink",
"cmsis-dap",
"blackmagic"
]
},
"url": "https://www.lilygo.cc/",
"vendor": "Lilygo"
}
+1 -1
View File
@@ -138,7 +138,7 @@ lib_deps =
# renovate: datasource=github-tags depName=Adafruit GFX packageName=adafruit/Adafruit-GFX-Library
https://github.com/adafruit/Adafruit-GFX-Library/archive/refs/tags/1.12.6.zip
# renovate: datasource=github-tags depName=NeoPixel packageName=adafruit/Adafruit_NeoPixel
https://github.com/adafruit/Adafruit_NeoPixel/archive/refs/tags/1.15.4.zip
https://github.com/adafruit/Adafruit_NeoPixel/archive/1.15.5.zip
# renovate: datasource=github-tags depName=Adafruit SSD1306 packageName=adafruit/Adafruit_SSD1306
https://github.com/adafruit/Adafruit_SSD1306/archive/refs/tags/2.5.16.zip
# renovate: datasource=github-tags depName=Adafruit BMP280 packageName=adafruit/Adafruit_BMP280_Library
+25 -1
View File
@@ -23,6 +23,7 @@
#include "main.h"
#include "meshUtils.h"
#include "power/PowerHAL.h"
#include "power/SGM41562.h"
#include "sleep.h"
#if defined(ARCH_PORTDUINO)
@@ -453,6 +454,10 @@ class AnalogBatteryLevel : public HasBatteryLevel
/// source
virtual bool isVbusIn() override
{
#ifdef HAS_SGM41562
if (sgm41562 && sgm41562->refresh())
return sgm41562->isInputPowerGood();
#endif
#ifdef EXT_PWR_DETECT
#if defined(HELTEC_CAPSULE_SENSOR_V3) || defined(HELTEC_SENSOR_HUB)
// if external powered that pin will be pulled down
@@ -483,6 +488,10 @@ class AnalogBatteryLevel : public HasBatteryLevel
/// we can't be smart enough to say 'full'?
virtual bool isCharging() override
{
#ifdef HAS_SGM41562
if (sgm41562 && sgm41562->refresh())
return sgm41562->isCharging();
#endif
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && defined(HAS_RAKPROT) && !defined(HAS_PMU)
if (hasRAK()) {
return (rak9154Sensor.isCharging()) ? OptTrue : OptFalse;
@@ -697,6 +706,12 @@ bool Power::analogInit()
*/
bool Power::setup()
{
#ifdef HAS_SGM41562
// Initialize the charger early so AnalogBatteryLevel can read charging
// state from it. The charger does not provide battery voltage / percent —
// those still come from the platform ADC via analogInit() below.
initSGM41562(SGM41562_WIRE);
#endif
bool found = false;
if (axpChipInit()) {
found = true;
@@ -878,7 +893,16 @@ void Power::readPowerStatus()
// Notify any status instances that are observing us
const PowerStatus powerStatus2 = PowerStatus(hasBattery, usbPowered, isChargingNow, batteryVoltageMv, batteryChargePercent);
if (millis() > lastLogTime + 50 * 1000) {
// Log battery-presence transitions once; skip OptUnknown so we don't lie before the first probe.
static OptionalBool prevHasBattery = OptUnknown;
if (hasBattery != OptUnknown && hasBattery != prevHasBattery) {
LOG_INFO("Power: battery hardware %s", hasBattery == OptTrue ? "detected" : "absent (USB-only)");
prevHasBattery = hasBattery;
}
// Periodic telemetry only emits when a battery is actually present (otherwise values are constant -1/0).
if (hasBattery == OptTrue && !Throttle::isWithinTimespanMs(lastLogTime, 50 * 1000)) {
LOG_DEBUG("Battery: usbPower=%d, isCharging=%d, batMv=%d, batPct=%d", powerStatus2.getHasUSB(),
powerStatus2.getIsCharging(), powerStatus2.getBatteryVoltageMv(), powerStatus2.getBatteryChargePercent());
lastLogTime = millis();
+8 -3
View File
@@ -353,6 +353,11 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
#elif defined(USE_SSD1306)
dispdev = new SSD1306Wire(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
#if defined(OLED_Y_OFFSET_PAGES)
// Panels whose active window does not start at GDDRAM row 0 (e.g. 72x40
// modules on pages 3..7) need a fixed vertical page shift on every write.
static_cast<SSD1306Wire *>(dispdev)->setYOffset(OLED_Y_OFFSET_PAGES);
#endif
#elif defined(USE_SPISSD1306)
dispdev = new SSD1306Spi(SSD1306_RESET, SSD1306_RS, SSD1306_NSS, GEOMETRY_64_48);
if (!dispdev->init()) {
@@ -834,7 +839,7 @@ int32_t Screen::runOnce()
#ifndef DISABLE_WELCOME_UNSET
if (!NotificationRenderer::isOverlayBannerShowing() && config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
#if defined(M5STACK_UNITC6L)
#if defined(OLED_TINY)
menuHandler::LoraRegionPicker();
#else
menuHandler::OnboardMessage();
@@ -1058,7 +1063,7 @@ void Screen::setFrames(FrameFocus focus)
#if defined(DISPLAY_CLOCK_FRAME)
if (!hiddenFrames.clock) {
fsi.positions.clock = numframes;
#if defined(M5STACK_UNITC6L)
#if defined(OLED_TINY)
normalFrames[numframes++] = graphics::ClockRenderer::drawAnalogClockFrame;
#else
normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame
@@ -1511,7 +1516,7 @@ void Screen::showFrame(FrameDirection direction)
void Screen::setFastFramerate()
{
#if defined(M5STACK_UNITC6L)
#if defined(OLED_TINY)
dispdev->clear();
dispdev->display();
#endif
+1 -1
View File
@@ -96,7 +96,7 @@
#define FONT_SMALL FONT_MEDIUM_LOCAL // Height: 19
#define FONT_MEDIUM FONT_LARGE_LOCAL // Height: 28
#define FONT_LARGE FONT_LARGE_LOCAL // Height: 28
#elif defined(M5STACK_UNITC6L)
#elif defined(OLED_TINY)
#define FONT_SMALL FONT_SMALL_LOCAL // Height: 13
#define FONT_MEDIUM FONT_SMALL_LOCAL // Height: 13
#define FONT_LARGE FONT_SMALL_LOCAL // Height: 13
+7 -1
View File
@@ -27,6 +27,12 @@ ScreenResolution determineScreenResolution(int16_t screenheight, int16_t screenw
return ScreenResolution::UltraLow;
}
#ifdef DISPLAY_FORCE_SMALL_FONTS
if (screenwidth <= 160 && screenheight <= 80) {
return ScreenResolution::Low;
}
#endif
// Standard OLED screens
if (screenwidth > 128 && screenheight <= 64) {
return ScreenResolution::Low;
@@ -155,7 +161,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
int batteryX = 1;
int batteryY = HEADER_OFFSET_Y + 1;
#if !defined(M5STACK_UNITC6L)
#if !defined(OLED_TINY)
// === Battery Icons ===
if (usbPowered && !isCharging) { // This is a basic check to determine USB Powered is flagged but not charging
batteryX += 1;
+2 -2
View File
@@ -449,7 +449,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line++], frequencyslot);
#if !defined(M5STACK_UNITC6L)
#if !defined(OLED_TINY)
// === Fifth Row: Channel Utilization ===
const char *chUtil = "ChUtil:";
char chUtilPercentage[10];
@@ -569,7 +569,7 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
// Label
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->drawString(labelX, getTextPositions(display)[line], label);
#if !defined(M5STACK_UNITC6L)
#if !defined(OLED_TINY)
// Bar
int barY = getTextPositions(display)[line] + (FONT_HEIGHT_SMALL - barHeight) / 2;
display->setColor(WHITE);
+1 -1
View File
@@ -491,7 +491,7 @@ void menuHandler::TZPicker()
void menuHandler::clockMenu()
{
#if defined(M5STACK_UNITC6L)
#if defined(OLED_TINY)
static const char *optionsArray[] = {"Back", "Time Format", "Timezone"};
#else
static const char *optionsArray[] = {"Back", "Clock Face", "Time Format", "Timezone"};
+4 -4
View File
@@ -21,7 +21,7 @@ extern bool haveGlyphs(const char *str);
// Global screen instance
extern graphics::Screen *screen;
#if defined(M5STACK_UNITC6L)
#if defined(OLED_TINY)
static uint32_t lastSwitchTime = 0;
#endif
namespace graphics
@@ -670,7 +670,7 @@ void drawDynamicListScreen_Nodes(OLEDDisplay *display, OLEDDisplayUiState *state
unsigned long now = millis();
#if defined(M5STACK_UNITC6L)
#if defined(OLED_TINY)
display->clear();
if (now - lastSwitchTime >= 3000) {
display->display();
@@ -706,7 +706,7 @@ void drawDynamicListScreen_Location(OLEDDisplay *display, OLEDDisplayUiState *st
unsigned long now = millis();
#if defined(M5STACK_UNITC6L)
#if defined(OLED_TINY)
display->clear();
if (now - lastSwitchTime >= 3000) {
display->display();
@@ -771,7 +771,7 @@ void drawNodeListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state,
double lat = DegD(ourNode->position.latitude_i);
double lon = DegD(ourNode->position.longitude_i);
#if defined(M5STACK_UNITC6L)
#if defined(OLED_TINY)
display->clear();
uint32_t now = millis();
if (now - lastSwitchTime >= 2000) {
+1 -1
View File
@@ -580,7 +580,7 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
}
int16_t boxTop = (display->height() / 2) - (boxHeight / 2);
boxHeight += (currentResolution == ScreenResolution::High) ? 2 : 1;
#if defined(M5STACK_UNITC6L)
#if defined(OLED_TINY)
if (visibleTotalLines == 1) {
boxTop += 25;
}
+6 -6
View File
@@ -20,7 +20,7 @@
// External variables
extern graphics::Screen *screen;
#if defined(M5STACK_UNITC6L)
#if defined(OLED_TINY)
static uint32_t lastSwitchTime = 0;
#endif
namespace graphics
@@ -304,7 +304,7 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, i
if (!node || node->num == nodeDB->getNodeNum() || !node->is_favorite)
return;
display->clear();
#if defined(M5STACK_UNITC6L)
#if defined(OLED_TINY)
uint32_t now = millis();
if (now - lastSwitchTime >= 10000) // 10000 ms = 10 秒
{
@@ -518,7 +518,7 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, i
if (seenStr[0]) {
display->drawString(x, getTextPositions(display)[line++], seenStr);
}
#if !defined(M5STACK_UNITC6L)
#if !defined(OLED_TINY)
// === 4. Uptime (only show if metric is present) ===
char uptimeStr[32] = "";
if (node->has_device_metrics && node->device_metrics.has_uptime_seconds) {
@@ -795,7 +795,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
}
#endif
#if defined(M5STACK_UNITC6L)
#if defined(OLED_TINY)
line += 1;
// === Node Identity ===
@@ -1092,7 +1092,7 @@ void UIRenderer::drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLED
// needs to be drawn relative to x and y
// draw centered icon left to right and centered above the one line of app text
#if defined(M5STACK_UNITC6L)
#if defined(OLED_TINY)
display->drawXbm(x + (SCREEN_WIDTH - 50) / 2, y + (SCREEN_HEIGHT - 28) / 2, icon_width, icon_height, icon_bits);
display->setFont(FONT_MEDIUM);
display->setTextAlignment(TEXT_ALIGN_LEFT);
@@ -1243,7 +1243,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
}
display->drawString(x, getTextPositions(display)[line++], altitudeLine);
}
#if !defined(M5STACK_UNITC6L)
#if !defined(OLED_TINY)
// === Draw Compass if heading is valid ===
if (validHeading) {
// --- Compass Rendering: landscape (wide) screens use original side-aligned logic ---
+1 -1
View File
@@ -318,7 +318,7 @@ const uint8_t chirpy_small[] = {0x7f, 0x41, 0x55, 0x55, 0x55, 0x55, 0x41, 0x7f};
#define connection_icon_height 5
const uint8_t connection_icon[] = {0x36, 0x41, 0x5D, 0x41, 0x36};
#ifdef M5STACK_UNITC6L
#ifdef OLED_TINY
#include "img/icon_small.xbm"
#else
#include "img/icon.xbm"
+97
View File
@@ -0,0 +1,97 @@
#include "HapticFeedback.h"
#ifdef HAPTIC_FEEDBACK_PIN
#include <Arduino.h>
#ifdef HAPTIC_FEEDBACK_ACTIVE_LOW
#define HAPTIC_FEEDBACK_ON_STATE LOW
#define HAPTIC_FEEDBACK_OFF_STATE HIGH
#else
#define HAPTIC_FEEDBACK_ON_STATE HIGH
#define HAPTIC_FEEDBACK_OFF_STATE LOW
#endif
HapticFeedback *hapticFeedback = nullptr;
void initHapticFeedback()
{
if (!hapticFeedback)
hapticFeedback = new HapticFeedback();
}
HapticFeedback::HapticFeedback() : concurrency::OSThread("Haptic")
{
pinMode(HAPTIC_FEEDBACK_PIN, OUTPUT);
digitalWrite(HAPTIC_FEEDBACK_PIN, HAPTIC_FEEDBACK_OFF_STATE);
}
void HapticFeedback::motorWrite(bool on)
{
digitalWrite(HAPTIC_FEEDBACK_PIN, on ? HAPTIC_FEEDBACK_ON_STATE : HAPTIC_FEEDBACK_OFF_STATE);
}
void HapticFeedback::pulse(uint16_t durationMs)
{
motorWrite(true);
pulseOffAt = millis() + durationMs;
if (pulseOffAt == 0) // 0 is the "no pulse" sentinel
pulseOffAt = 1;
scheduleNext();
}
void HapticFeedback::armDelayedPulse(uint16_t delayMs, uint16_t durationMs)
{
delayedPulseAt = millis() + delayMs;
if (delayedPulseAt == 0)
delayedPulseAt = 1;
delayedPulseDuration = durationMs;
scheduleNext();
}
void HapticFeedback::cancelDelayedPulse()
{
delayedPulseAt = 0;
}
void HapticFeedback::scheduleNext()
{
uint32_t now = millis();
uint32_t next = 0;
if (pulseOffAt != 0)
next = pulseOffAt;
if (delayedPulseAt != 0 && (next == 0 || (int32_t)(delayedPulseAt - next) < 0))
next = delayedPulseAt;
if (next == 0)
return;
int32_t delay = (int32_t)(next - now);
setIntervalFromNow(delay > 0 ? (unsigned long)delay : 0);
}
int32_t HapticFeedback::runOnce()
{
uint32_t now = millis();
if (pulseOffAt != 0 && (int32_t)(now - pulseOffAt) >= 0) {
motorWrite(false);
pulseOffAt = 0;
}
if (delayedPulseAt != 0 && (int32_t)(now - delayedPulseAt) >= 0) {
uint16_t dur = delayedPulseDuration;
delayedPulseAt = 0;
pulse(dur);
}
uint32_t next = 0;
if (pulseOffAt != 0)
next = pulseOffAt;
if (delayedPulseAt != 0 && (next == 0 || (int32_t)(delayedPulseAt - next) < 0))
next = delayedPulseAt;
if (next == 0)
return 60 * 1000;
int32_t delay = (int32_t)(next - now);
return delay > 0 ? delay : 0;
}
#endif // HAPTIC_FEEDBACK_PIN
+35
View File
@@ -0,0 +1,35 @@
#pragma once
#include "configuration.h"
#ifdef HAPTIC_FEEDBACK_PIN
#include "concurrency/OSThread.h"
#include <stdint.h>
// Non-blocking pulses on a GPIO vibration motor. HAPTIC_FEEDBACK_ACTIVE_LOW inverts polarity.
class HapticFeedback : public concurrency::OSThread
{
public:
HapticFeedback();
void pulse(uint16_t durationMs = 30);
void armDelayedPulse(uint16_t delayMs, uint16_t durationMs = 30);
void cancelDelayedPulse();
protected:
int32_t runOnce() override;
private:
uint32_t pulseOffAt = 0;
uint32_t delayedPulseAt = 0;
uint16_t delayedPulseDuration = 0;
void motorWrite(bool on);
// Reschedule to the soonest pending event so later arms don't clobber earlier wakes.
void scheduleNext();
};
extern HapticFeedback *hapticFeedback;
void initHapticFeedback();
#endif // HAPTIC_FEEDBACK_PIN
+11
View File
@@ -2,6 +2,7 @@
#include "PowerFSM.h" // needed for event trigger
#include "configuration.h"
#include "graphics/Screen.h"
#include "input/HapticFeedback.h"
#include "modules/ExternalNotificationModule.h"
#if ARCH_PORTDUINO
@@ -237,6 +238,16 @@ void InputBroker::Init()
}
touchBacklightActive = false;
};
#endif
#if defined(HAPTIC_FEEDBACK_PIN)
// Blip on touch, second blip when long-press fires (500 ms = touchConfig.longPressTime default).
touchConfig.suppressLeadUpSound = true;
initHapticFeedback();
touchConfig.onPress = []() {
hapticFeedback->pulse(80);
hapticFeedback->armDelayedPulse(500, 80);
};
touchConfig.onRelease = []() { hapticFeedback->cancelDelayedPulse(); };
#endif
TouchButtonThread->initButton(touchConfig);
#endif
+5
View File
@@ -741,6 +741,11 @@ void setup()
}
}
#endif
#ifdef OLED_GEOMETRY_OVERRIDE
// Per-variant geometry (e.g. 72x40 micro-OLEDs). Takes precedence over the
// default GEOMETRY_128_64 set at the top of setup().
screen_geometry = OLED_GEOMETRY_OVERRIDE;
#endif
#endif
#if !MESHTASTIC_EXCLUDE_I2C
+3 -2
View File
@@ -836,7 +836,8 @@ void NodeDB::installDefaultModuleConfig()
moduleConfig.has_store_forward = true;
moduleConfig.has_telemetry = true;
moduleConfig.has_external_notification = true;
#if defined(PIN_BUZZER) || defined(PIN_VIBRATION) || defined(LED_NOTIFICATION) || defined(PCA_LED_NOTIFICATION)
#if defined(PIN_BUZZER) || defined(PIN_VIBRATION) || defined(LED_NOTIFICATION) || defined(PCA_LED_NOTIFICATION) || \
defined(NEOPIXEL_STATUS_NOTIFICATION_PIN)
moduleConfig.external_notification.enabled = true;
#endif
#if defined(PIN_BUZZER)
@@ -857,7 +858,7 @@ void NodeDB::installDefaultModuleConfig()
#endif
#if defined(PIN_VIBRATION)
moduleConfig.external_notification.nag_timeout = 2;
#elif defined(PIN_BUZZER) || defined(LED_NOTIFICATION)
#elif defined(PIN_BUZZER) || defined(LED_NOTIFICATION) || defined(NEOPIXEL_STATUS_NOTIFICATION_PIN)
moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs;
#endif
+75
View File
@@ -0,0 +1,75 @@
#include "PositionPrecision.h"
#include "Channels.h"
#include "mesh-pb-constants.h"
#include <Arduino.h>
uint32_t getPositionPrecisionForChannel(uint8_t channelIndex)
{
const meshtastic_Channel &channel = channels.getByIndex(channelIndex);
if (channel.settings.has_module_settings) {
return channel.settings.module_settings.position_precision;
} else if (channel.role == meshtastic_Channel_Role_PRIMARY) {
return 32;
} else {
return 0;
}
}
static int32_t truncateCoordinate(int32_t coordinate, uint32_t precision)
{
uint32_t coordinateBits = static_cast<uint32_t>(coordinate);
uint32_t truncated = coordinateBits & (UINT32_MAX << (32 - precision));
// Use the middle of the possible location, not the low edge of the bucket.
truncated += (1UL << (31 - precision));
return static_cast<int32_t>(truncated);
}
void applyPositionPrecision(meshtastic_Position &position, uint32_t precision)
{
if (precision == 0) {
uint32_t time = position.time;
position = meshtastic_Position_init_default;
position.time = time;
return;
}
uint32_t effectivePrecision = precision > 32 ? 32 : precision;
position.precision_bits = effectivePrecision;
if (effectivePrecision < 32) {
position.latitude_i = truncateCoordinate(position.latitude_i, effectivePrecision);
position.longitude_i = truncateCoordinate(position.longitude_i, effectivePrecision);
}
}
bool applyPositionPrecision(meshtastic_MeshPacket &packet, uint32_t precision)
{
if (packet.which_payload_variant != meshtastic_MeshPacket_decoded_tag ||
packet.decoded.portnum != meshtastic_PortNum_POSITION_APP) {
return true;
}
meshtastic_Position position = meshtastic_Position_init_default;
if (!pb_decode_from_bytes(packet.decoded.payload.bytes, packet.decoded.payload.size, &meshtastic_Position_msg, &position)) {
return false;
}
applyPositionPrecision(position, precision);
packet.decoded.payload.size = pb_encode_to_bytes(packet.decoded.payload.bytes, sizeof(packet.decoded.payload.bytes),
&meshtastic_Position_msg, &position);
return true;
}
bool applyPositionPrecisionForChannel(meshtastic_MeshPacket &packet, uint8_t channelIndex)
{
if (packet.which_payload_variant != meshtastic_MeshPacket_decoded_tag ||
packet.decoded.portnum != meshtastic_PortNum_POSITION_APP) {
return true;
}
return applyPositionPrecision(packet, getPositionPrecisionForChannel(channelIndex));
}
+9
View File
@@ -0,0 +1,9 @@
#pragma once
#include "meshtastic/mesh.pb.h"
#include <stdint.h>
uint32_t getPositionPrecisionForChannel(uint8_t channelIndex);
void applyPositionPrecision(meshtastic_Position &position, uint32_t precision);
bool applyPositionPrecision(meshtastic_MeshPacket &packet, uint32_t precision);
bool applyPositionPrecisionForChannel(meshtastic_MeshPacket &packet, uint8_t channelIndex);
+6
View File
@@ -4,6 +4,7 @@
#include "MeshRadio.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "PositionPrecision.h"
#include "RTC.h"
#include "configuration.h"
@@ -350,6 +351,11 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
}
fixPriority(p); // Before encryption, fix the priority if it's unset
if (!applyPositionPrecisionForChannel(*p, p->channel)) {
LOG_ERROR("Dropping malformed position packet before send");
packetPool.release(p);
return meshtastic_Routing_Error_BAD_REQUEST;
}
// If the packet is not yet encrypted, do so now
if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
@@ -15,6 +15,9 @@ PB_BIND(meshtastic_AdminMessage_InputEvent, meshtastic_AdminMessage_InputEvent,
PB_BIND(meshtastic_AdminMessage_OTAEvent, meshtastic_AdminMessage_OTAEvent, AUTO)
PB_BIND(meshtastic_LockdownAuth, meshtastic_LockdownAuth, AUTO)
PB_BIND(meshtastic_HamParameters, meshtastic_HamParameters, AUTO)
+66 -1
View File
@@ -130,6 +130,41 @@ typedef struct _meshtastic_AdminMessage_OTAEvent {
meshtastic_AdminMessage_OTAEvent_ota_hash_t ota_hash;
} meshtastic_AdminMessage_OTAEvent;
typedef PB_BYTES_ARRAY_T(32) meshtastic_LockdownAuth_passphrase_t;
/* Lockdown passphrase delivery payload.
One message handles three operations distinguished by content:
- Provision (first-time): passphrase set, lock_now=false. Firmware
generates DEK, wraps with passphrase-derived KEK, persists.
- Unlock: passphrase set, lock_now=false. Firmware verifies
passphrase against stored DEK, unlocks storage, authorizes the
connection that delivered this packet.
- Lock now: lock_now=true, passphrase ignored. Firmware revokes
all client auth and reboots into the locked state.
Firmware decides between provision and unlock based on its own state
(whether a DEK file already exists). Clients do not need to track
which case applies. */
typedef struct _meshtastic_LockdownAuth {
/* Passphrase bytes (1-32). Empty when lock_now is true.
Capped to 32 to match the proto cap on related security fields. */
meshtastic_LockdownAuth_passphrase_t passphrase;
/* Optional override of the boot-count token TTL granted on success.
0 = use firmware default (TOKEN_DEFAULT_BOOTS).
On reboot the firmware decrements this; when it reaches 0 the
device boots fully locked and requires a fresh passphrase. */
uint32_t boots_remaining;
/* Optional wall-clock expiry for the unlock token, as absolute
Unix-epoch seconds. 0 = no time limit (only the boot-count TTL
applies). On boot, if the device RTC is set and now > this value,
the token is treated as expired. */
uint32_t valid_until_epoch;
/* If true, ignore passphrase fields, immediately revoke all
connection-level admin authorization, and reboot the device into
the locked state. Always honoured regardless of current lock state. */
bool lock_now;
} meshtastic_LockdownAuth;
/* Parameters for setting up Meshtastic for ameteur radio usage */
typedef struct _meshtastic_HamParameters {
/* Amateur radio call sign, eg. KD2ABC */
@@ -384,6 +419,15 @@ typedef struct _meshtastic_AdminMessage {
meshtastic_AdminMessage_OTAEvent ota_request;
/* Parameters and sensor configuration */
meshtastic_SensorConfig sensor_config;
/* Lockdown passphrase delivery / unlock / lock-now command for hardened
firmware builds (see MESHTASTIC_LOCKDOWN). Used to provision the
passphrase on first boot, unlock encrypted storage on subsequent
reboots, re-verify on already-unlocked devices to authorize a new
client connection, or immediately re-lock the device.
Replaces the earlier scheme that repurposed SecurityConfig.private_key
to carry passphrase bytes; that hack is retired. */
meshtastic_LockdownAuth lockdown_auth;
};
/* The node generates this key and sends it with any get_x_response packets.
The client MUST include the same key with any set_x commands. Key expires after 300 seconds.
@@ -429,6 +473,7 @@ extern "C" {
#define meshtastic_KeyVerificationAdmin_message_type_ENUMTYPE meshtastic_KeyVerificationAdmin_MessageType
@@ -441,6 +486,7 @@ extern "C" {
#define meshtastic_AdminMessage_init_default {0, {0}, {0, {0}}}
#define meshtastic_AdminMessage_InputEvent_init_default {0, 0, 0, 0}
#define meshtastic_AdminMessage_OTAEvent_init_default {_meshtastic_OTAMode_MIN, {0, {0}}}
#define meshtastic_LockdownAuth_init_default {{0, {0}}, 0, 0, 0}
#define meshtastic_HamParameters_init_default {"", 0, 0, ""}
#define meshtastic_NodeRemoteHardwarePinsResponse_init_default {0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}}
#define meshtastic_SharedContact_init_default {0, false, meshtastic_User_init_default, 0, 0}
@@ -453,6 +499,7 @@ extern "C" {
#define meshtastic_AdminMessage_init_zero {0, {0}, {0, {0}}}
#define meshtastic_AdminMessage_InputEvent_init_zero {0, 0, 0, 0}
#define meshtastic_AdminMessage_OTAEvent_init_zero {_meshtastic_OTAMode_MIN, {0, {0}}}
#define meshtastic_LockdownAuth_init_zero {{0, {0}}, 0, 0, 0}
#define meshtastic_HamParameters_init_zero {"", 0, 0, ""}
#define meshtastic_NodeRemoteHardwarePinsResponse_init_zero {0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}}
#define meshtastic_SharedContact_init_zero {0, false, meshtastic_User_init_zero, 0, 0}
@@ -470,6 +517,10 @@ extern "C" {
#define meshtastic_AdminMessage_InputEvent_touch_y_tag 4
#define meshtastic_AdminMessage_OTAEvent_reboot_ota_mode_tag 1
#define meshtastic_AdminMessage_OTAEvent_ota_hash_tag 2
#define meshtastic_LockdownAuth_passphrase_tag 1
#define meshtastic_LockdownAuth_boots_remaining_tag 2
#define meshtastic_LockdownAuth_valid_until_epoch_tag 3
#define meshtastic_LockdownAuth_lock_now_tag 4
#define meshtastic_HamParameters_call_sign_tag 1
#define meshtastic_HamParameters_tx_power_tag 2
#define meshtastic_HamParameters_frequency_tag 3
@@ -560,6 +611,7 @@ extern "C" {
#define meshtastic_AdminMessage_nodedb_reset_tag 100
#define meshtastic_AdminMessage_ota_request_tag 102
#define meshtastic_AdminMessage_sensor_config_tag 103
#define meshtastic_AdminMessage_lockdown_auth_tag 104
#define meshtastic_AdminMessage_session_passkey_tag 101
/* Struct field encoding specification for nanopb */
@@ -621,7 +673,8 @@ X(a, STATIC, ONEOF, INT32, (payload_variant,factory_reset_config,factory
X(a, STATIC, ONEOF, BOOL, (payload_variant,nodedb_reset,nodedb_reset), 100) \
X(a, STATIC, SINGULAR, BYTES, session_passkey, 101) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,ota_request,ota_request), 102) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,sensor_config,sensor_config), 103)
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,sensor_config,sensor_config), 103) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,lockdown_auth,lockdown_auth), 104)
#define meshtastic_AdminMessage_CALLBACK NULL
#define meshtastic_AdminMessage_DEFAULT NULL
#define meshtastic_AdminMessage_payload_variant_get_channel_response_MSGTYPE meshtastic_Channel
@@ -644,6 +697,7 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,sensor_config,sensor_config)
#define meshtastic_AdminMessage_payload_variant_key_verification_MSGTYPE meshtastic_KeyVerificationAdmin
#define meshtastic_AdminMessage_payload_variant_ota_request_MSGTYPE meshtastic_AdminMessage_OTAEvent
#define meshtastic_AdminMessage_payload_variant_sensor_config_MSGTYPE meshtastic_SensorConfig
#define meshtastic_AdminMessage_payload_variant_lockdown_auth_MSGTYPE meshtastic_LockdownAuth
#define meshtastic_AdminMessage_InputEvent_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, event_code, 1) \
@@ -659,6 +713,14 @@ X(a, STATIC, SINGULAR, BYTES, ota_hash, 2)
#define meshtastic_AdminMessage_OTAEvent_CALLBACK NULL
#define meshtastic_AdminMessage_OTAEvent_DEFAULT NULL
#define meshtastic_LockdownAuth_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, BYTES, passphrase, 1) \
X(a, STATIC, SINGULAR, UINT32, boots_remaining, 2) \
X(a, STATIC, SINGULAR, UINT32, valid_until_epoch, 3) \
X(a, STATIC, SINGULAR, BOOL, lock_now, 4)
#define meshtastic_LockdownAuth_CALLBACK NULL
#define meshtastic_LockdownAuth_DEFAULT NULL
#define meshtastic_HamParameters_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, STRING, call_sign, 1) \
X(a, STATIC, SINGULAR, INT32, tx_power, 2) \
@@ -737,6 +799,7 @@ X(a, STATIC, OPTIONAL, UINT32, set_accuracy, 1)
extern const pb_msgdesc_t meshtastic_AdminMessage_msg;
extern const pb_msgdesc_t meshtastic_AdminMessage_InputEvent_msg;
extern const pb_msgdesc_t meshtastic_AdminMessage_OTAEvent_msg;
extern const pb_msgdesc_t meshtastic_LockdownAuth_msg;
extern const pb_msgdesc_t meshtastic_HamParameters_msg;
extern const pb_msgdesc_t meshtastic_NodeRemoteHardwarePinsResponse_msg;
extern const pb_msgdesc_t meshtastic_SharedContact_msg;
@@ -751,6 +814,7 @@ extern const pb_msgdesc_t meshtastic_SHTXX_config_msg;
#define meshtastic_AdminMessage_fields &meshtastic_AdminMessage_msg
#define meshtastic_AdminMessage_InputEvent_fields &meshtastic_AdminMessage_InputEvent_msg
#define meshtastic_AdminMessage_OTAEvent_fields &meshtastic_AdminMessage_OTAEvent_msg
#define meshtastic_LockdownAuth_fields &meshtastic_LockdownAuth_msg
#define meshtastic_HamParameters_fields &meshtastic_HamParameters_msg
#define meshtastic_NodeRemoteHardwarePinsResponse_fields &meshtastic_NodeRemoteHardwarePinsResponse_msg
#define meshtastic_SharedContact_fields &meshtastic_SharedContact_msg
@@ -768,6 +832,7 @@ extern const pb_msgdesc_t meshtastic_SHTXX_config_msg;
#define meshtastic_AdminMessage_size 511
#define meshtastic_HamParameters_size 31
#define meshtastic_KeyVerificationAdmin_size 25
#define meshtastic_LockdownAuth_size 48
#define meshtastic_NodeRemoteHardwarePinsResponse_size 496
#define meshtastic_SCD30_config_size 27
#define meshtastic_SCD4X_config_size 29
+5 -3
View File
@@ -198,7 +198,9 @@ typedef enum _meshtastic_Config_DisplayConfig_OledType {
/* Can not be auto detected but set by proto. Used for 128x64 screens */
meshtastic_Config_DisplayConfig_OledType_OLED_SH1107 = 3,
/* Can not be auto detected but set by proto. Used for 128x128 screens */
meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_128_128 = 4
meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_128_128 = 4,
/* Can not be auto detected but set by proto. Used for 64x128 rotated screens */
meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_ROTATED = 5
} meshtastic_Config_DisplayConfig_OledType;
typedef enum _meshtastic_Config_DisplayConfig_DisplayMode {
@@ -720,8 +722,8 @@ extern "C" {
#define _meshtastic_Config_DisplayConfig_DisplayUnits_ARRAYSIZE ((meshtastic_Config_DisplayConfig_DisplayUnits)(meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL+1))
#define _meshtastic_Config_DisplayConfig_OledType_MIN meshtastic_Config_DisplayConfig_OledType_OLED_AUTO
#define _meshtastic_Config_DisplayConfig_OledType_MAX meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_128_128
#define _meshtastic_Config_DisplayConfig_OledType_ARRAYSIZE ((meshtastic_Config_DisplayConfig_OledType)(meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_128_128+1))
#define _meshtastic_Config_DisplayConfig_OledType_MAX meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_ROTATED
#define _meshtastic_Config_DisplayConfig_OledType_ARRAYSIZE ((meshtastic_Config_DisplayConfig_OledType)(meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_ROTATED+1))
#define _meshtastic_Config_DisplayConfig_DisplayMode_MIN meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT
#define _meshtastic_Config_DisplayConfig_DisplayMode_MAX meshtastic_Config_DisplayConfig_DisplayMode_COLOR
@@ -57,6 +57,9 @@ PB_BIND(meshtastic_QueueStatus, meshtastic_QueueStatus, AUTO)
PB_BIND(meshtastic_FromRadio, meshtastic_FromRadio, 2)
PB_BIND(meshtastic_LockdownStatus, meshtastic_LockdownStatus, AUTO)
PB_BIND(meshtastic_ClientNotification, meshtastic_ClientNotification, 2)
@@ -134,6 +137,8 @@ PB_BIND(meshtastic_ChunkedPayloadResponse, meshtastic_ChunkedPayloadResponse, AU
+90 -1
View File
@@ -321,6 +321,10 @@ typedef enum _meshtastic_HardwareModel {
meshtastic_HardwareModel_HELTEC_MESH_NODE_T1 = 133,
/* B&Q Consulting Station G3: TBD */
meshtastic_HardwareModel_STATION_G3 = 134,
/* Lilygo T-Impulse-Plus */
meshtastic_HardwareModel_T_IMPULSE_PLUS = 135,
/* Lilygo T-Echo Card */
meshtastic_HardwareModel_T_ECHO_CARD = 136,
/* ------------------------------------------------------------------------------------------------------------------------------------------
Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
------------------------------------------------------------------------------------------------------------------------------------------ */
@@ -638,6 +642,25 @@ typedef enum _meshtastic_LogRecord_Level {
meshtastic_LogRecord_Level_TRACE = 5
} meshtastic_LogRecord_Level;
typedef enum _meshtastic_LockdownStatus_State {
/* Default; should not be sent. */
meshtastic_LockdownStatus_State_STATE_UNSPECIFIED = 0,
/* No passphrase has ever been provisioned on this device.
Client should prompt the operator to set one. */
meshtastic_LockdownStatus_State_NEEDS_PROVISION = 1,
/* Storage is locked or this client has not authenticated yet.
lock_reason carries a machine-readable detail string.
Client should present (or auto-replay) a passphrase via
AdminMessage.lockdown_auth. */
meshtastic_LockdownStatus_State_LOCKED = 2,
/* Passphrase accepted; client is now authorized for this connection.
boots_remaining and valid_until_epoch describe the active session
token's TTL. */
meshtastic_LockdownStatus_State_UNLOCKED = 3,
/* Passphrase rejected. backoff_seconds is non-zero when rate-limited. */
meshtastic_LockdownStatus_State_UNLOCK_FAILED = 4
} meshtastic_LockdownStatus_State;
/* Struct definitions */
/* A GPS Position */
typedef struct _meshtastic_Position {
@@ -1148,6 +1171,38 @@ typedef struct _meshtastic_QueueStatus {
uint32_t mesh_packet_id;
} meshtastic_QueueStatus;
/* Lockdown state report from firmware to client (for hardened builds
with MESHTASTIC_LOCKDOWN). Sent immediately after config_complete_id
to inform a freshly-connected unauthorized client what it must do,
and again in response to each LockdownAuth admin command. */
typedef struct _meshtastic_LockdownStatus {
/* Current lockdown state being reported. */
meshtastic_LockdownStatus_State state;
/* For LOCKED: machine-readable reason. Known values:
"needs_auth" — storage already unlocked, client must auth
"token_missing" — no boot token on flash
"token_expired" — boot token wall-clock TTL elapsed
"token_boots_zero" — boot token boot-count TTL exhausted
"token_hmac_fail" — token tampered or wrong device
"token_dek_fail" — token DEK decrypt failed
"token_wrong_size" — token file corrupted
"token_bad_magic" — token file corrupted
"not_provisioned" — should generally use NEEDS_PROVISION state instead
Other values may be added; clients should treat unknown values as
"locked, ask for passphrase". */
char lock_reason[32];
/* For UNLOCKED: remaining boots on the issued session token.
Decrements by 1 on each subsequent boot. */
uint32_t boots_remaining;
/* For UNLOCKED: wall-clock expiry of the issued session token,
absolute Unix-epoch seconds. 0 = no time limit. */
uint32_t valid_until_epoch;
/* For UNLOCK_FAILED: seconds the client must wait before another
passphrase attempt will be accepted. 0 = wrong passphrase, no
backoff (immediate retry allowed but advisable to prompt user). */
uint32_t backoff_seconds;
} meshtastic_LockdownStatus;
typedef struct _meshtastic_KeyVerificationNumberInform {
uint64_t nonce;
char remote_longname[40];
@@ -1321,6 +1376,12 @@ typedef struct _meshtastic_FromRadio {
meshtastic_ClientNotification clientNotification;
/* Persistent data for device-ui */
meshtastic_DeviceUIConfig deviceuiConfig;
/* Lockdown state notification for hardened firmware builds.
Sent post-config (so unauthorized clients learn they must
provision/unlock) and after each LockdownAuth admin command
to report success or failure. Replaces the earlier scheme of
encoding state as magic-string prefixes inside ClientNotification. */
meshtastic_LockdownStatus lockdown_status;
};
} meshtastic_FromRadio;
@@ -1462,6 +1523,10 @@ extern "C" {
#define _meshtastic_LogRecord_Level_MAX meshtastic_LogRecord_Level_CRITICAL
#define _meshtastic_LogRecord_Level_ARRAYSIZE ((meshtastic_LogRecord_Level)(meshtastic_LogRecord_Level_CRITICAL+1))
#define _meshtastic_LockdownStatus_State_MIN meshtastic_LockdownStatus_State_STATE_UNSPECIFIED
#define _meshtastic_LockdownStatus_State_MAX meshtastic_LockdownStatus_State_UNLOCK_FAILED
#define _meshtastic_LockdownStatus_State_ARRAYSIZE ((meshtastic_LockdownStatus_State)(meshtastic_LockdownStatus_State_UNLOCK_FAILED+1))
#define meshtastic_Position_location_source_ENUMTYPE meshtastic_Position_LocSource
#define meshtastic_Position_altitude_source_ENUMTYPE meshtastic_Position_AltSource
@@ -1492,6 +1557,8 @@ extern "C" {
#define meshtastic_LockdownStatus_state_ENUMTYPE meshtastic_LockdownStatus_State
#define meshtastic_ClientNotification_level_ENUMTYPE meshtastic_LogRecord_Level
@@ -1532,6 +1599,7 @@ extern "C" {
#define meshtastic_LogRecord_init_default {"", 0, "", _meshtastic_LogRecord_Level_MIN}
#define meshtastic_QueueStatus_init_default {0, 0, 0, 0}
#define meshtastic_FromRadio_init_default {0, 0, {meshtastic_MeshPacket_init_default}}
#define meshtastic_LockdownStatus_init_default {_meshtastic_LockdownStatus_State_MIN, "", 0, 0, 0}
#define meshtastic_ClientNotification_init_default {false, 0, 0, _meshtastic_LogRecord_Level_MIN, "", 0, {meshtastic_KeyVerificationNumberInform_init_default}}
#define meshtastic_KeyVerificationNumberInform_init_default {0, "", 0}
#define meshtastic_KeyVerificationNumberRequest_init_default {0, ""}
@@ -1566,6 +1634,7 @@ extern "C" {
#define meshtastic_LogRecord_init_zero {"", 0, "", _meshtastic_LogRecord_Level_MIN}
#define meshtastic_QueueStatus_init_zero {0, 0, 0, 0}
#define meshtastic_FromRadio_init_zero {0, 0, {meshtastic_MeshPacket_init_zero}}
#define meshtastic_LockdownStatus_init_zero {_meshtastic_LockdownStatus_State_MIN, "", 0, 0, 0}
#define meshtastic_ClientNotification_init_zero {false, 0, 0, _meshtastic_LogRecord_Level_MIN, "", 0, {meshtastic_KeyVerificationNumberInform_init_zero}}
#define meshtastic_KeyVerificationNumberInform_init_zero {0, "", 0}
#define meshtastic_KeyVerificationNumberRequest_init_zero {0, ""}
@@ -1718,6 +1787,11 @@ extern "C" {
#define meshtastic_QueueStatus_free_tag 2
#define meshtastic_QueueStatus_maxlen_tag 3
#define meshtastic_QueueStatus_mesh_packet_id_tag 4
#define meshtastic_LockdownStatus_state_tag 1
#define meshtastic_LockdownStatus_lock_reason_tag 2
#define meshtastic_LockdownStatus_boots_remaining_tag 3
#define meshtastic_LockdownStatus_valid_until_epoch_tag 4
#define meshtastic_LockdownStatus_backoff_seconds_tag 5
#define meshtastic_KeyVerificationNumberInform_nonce_tag 1
#define meshtastic_KeyVerificationNumberInform_remote_longname_tag 2
#define meshtastic_KeyVerificationNumberInform_security_number_tag 3
@@ -1777,6 +1851,7 @@ extern "C" {
#define meshtastic_FromRadio_fileInfo_tag 15
#define meshtastic_FromRadio_clientNotification_tag 16
#define meshtastic_FromRadio_deviceuiConfig_tag 17
#define meshtastic_FromRadio_lockdown_status_tag 18
#define meshtastic_Heartbeat_nonce_tag 1
#define meshtastic_ToRadio_packet_tag 1
#define meshtastic_ToRadio_want_config_id_tag 3
@@ -2017,7 +2092,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,metadata,metadata), 13) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,mqttClientProxyMessage,mqttClientProxyMessage), 14) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,fileInfo,fileInfo), 15) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,clientNotification,clientNotification), 16) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,deviceuiConfig,deviceuiConfig), 17)
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,deviceuiConfig,deviceuiConfig), 17) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,lockdown_status,lockdown_status), 18)
#define meshtastic_FromRadio_CALLBACK NULL
#define meshtastic_FromRadio_DEFAULT NULL
#define meshtastic_FromRadio_payload_variant_packet_MSGTYPE meshtastic_MeshPacket
@@ -2034,6 +2110,16 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,deviceuiConfig,deviceuiConfi
#define meshtastic_FromRadio_payload_variant_fileInfo_MSGTYPE meshtastic_FileInfo
#define meshtastic_FromRadio_payload_variant_clientNotification_MSGTYPE meshtastic_ClientNotification
#define meshtastic_FromRadio_payload_variant_deviceuiConfig_MSGTYPE meshtastic_DeviceUIConfig
#define meshtastic_FromRadio_payload_variant_lockdown_status_MSGTYPE meshtastic_LockdownStatus
#define meshtastic_LockdownStatus_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UENUM, state, 1) \
X(a, STATIC, SINGULAR, STRING, lock_reason, 2) \
X(a, STATIC, SINGULAR, UINT32, boots_remaining, 3) \
X(a, STATIC, SINGULAR, UINT32, valid_until_epoch, 4) \
X(a, STATIC, SINGULAR, UINT32, backoff_seconds, 5)
#define meshtastic_LockdownStatus_CALLBACK NULL
#define meshtastic_LockdownStatus_DEFAULT NULL
#define meshtastic_ClientNotification_FIELDLIST(X, a) \
X(a, STATIC, OPTIONAL, UINT32, reply_id, 1) \
@@ -2194,6 +2280,7 @@ extern const pb_msgdesc_t meshtastic_MyNodeInfo_msg;
extern const pb_msgdesc_t meshtastic_LogRecord_msg;
extern const pb_msgdesc_t meshtastic_QueueStatus_msg;
extern const pb_msgdesc_t meshtastic_FromRadio_msg;
extern const pb_msgdesc_t meshtastic_LockdownStatus_msg;
extern const pb_msgdesc_t meshtastic_ClientNotification_msg;
extern const pb_msgdesc_t meshtastic_KeyVerificationNumberInform_msg;
extern const pb_msgdesc_t meshtastic_KeyVerificationNumberRequest_msg;
@@ -2230,6 +2317,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg;
#define meshtastic_LogRecord_fields &meshtastic_LogRecord_msg
#define meshtastic_QueueStatus_fields &meshtastic_QueueStatus_msg
#define meshtastic_FromRadio_fields &meshtastic_FromRadio_msg
#define meshtastic_LockdownStatus_fields &meshtastic_LockdownStatus_msg
#define meshtastic_ClientNotification_fields &meshtastic_ClientNotification_msg
#define meshtastic_KeyVerificationNumberInform_fields &meshtastic_KeyVerificationNumberInform_msg
#define meshtastic_KeyVerificationNumberRequest_fields &meshtastic_KeyVerificationNumberRequest_msg
@@ -2265,6 +2353,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg;
#define meshtastic_KeyVerificationNumberInform_size 58
#define meshtastic_KeyVerificationNumberRequest_size 52
#define meshtastic_KeyVerification_size 79
#define meshtastic_LockdownStatus_size 53
#define meshtastic_LogRecord_size 426
#define meshtastic_LowEntropyKey_size 0
#define meshtastic_MeshPacket_size 381
@@ -144,7 +144,7 @@ typedef struct _meshtastic_ModuleConfig_MQTTConfig {
(the default official mqtt.meshtastic.org server can handle encrypted packets)
Decrypted packets may be useful for external systems that want to consume meshtastic packets */
bool encryption_enabled;
/* Whether to send / consume json packets on MQTT */
/* Deprecated: JSON packet support on MQTT was removed, and this field is ignored. */
bool json_enabled;
/* If true, we attempt to establish a secure connection using TLS */
bool tls_enabled;
+11 -7
View File
@@ -291,10 +291,8 @@ static int32_t reconnectWiFi()
#endif
return 1000; // check once per second
} else {
#ifdef ARCH_RP2040
onNetworkConnected(); // will only do anything once
#endif
return 300000; // every 5 minutes
onNetworkConnected(); // will only do anything once (guarded by APStartupComplete)
return 300000; // every 5 minutes
}
}
@@ -343,9 +341,6 @@ bool initWifi()
const char *wifiPsw = config.network.wifi_psk;
#ifndef ARCH_RP2040
#if !MESHTASTIC_EXCLUDE_WEBSERVER
createSSLCert(); // For WebServer
#endif
WiFi.persistent(false); // Disable flash storage for WiFi credentials
#endif
if (!*wifiPsw) // Treat empty password as no password
@@ -370,6 +365,9 @@ bool initWifi()
#endif
}
#ifdef ARCH_ESP32
// Register WiFi event handler BEFORE createSSLCert() to prevent race condition:
// Without this, WiFi can auto-reconnect during cert generation and fire GOT_IP
// before the handler is registered, causing onNetworkConnected() to never run.
WiFi.onEvent(WiFiEvent);
WiFi.setAutoReconnect(true);
WiFi.setSleep(false);
@@ -391,6 +389,12 @@ bool initWifi()
wifiDisconnectReason = info.wifi_sta_disconnected.reason;
},
WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_DISCONNECTED);
#endif
#ifndef ARCH_RP2040
#if !MESHTASTIC_EXCLUDE_WEBSERVER
createSSLCert(); // For WebServer - called after WiFi.onEvent() to avoid race condition
#endif
#endif
LOG_DEBUG("JOINING WIFI soon: ssid=%s", wifiName);
wifiReconnect = new Periodic("WifiConnect", reconnectWiFi);
@@ -206,6 +206,10 @@ void ExternalNotificationModule::setExternalState(uint8_t index, bool on)
#ifdef PCA_LED_NOTIFICATION
io.digitalWrite(PCA_LED_NOTIFICATION, on);
#endif
#ifdef NEOPIXEL_STATUS_NOTIFICATION_PIN
notificationPixel.setPixelColor(0, on ? NEOPIXEL_STATUS_NOTIFICATION_COLOR : 0);
notificationPixel.show();
#endif
break;
}
@@ -324,6 +328,12 @@ ExternalNotificationModule::ExternalNotificationModule()
LOG_INFO("Use Pin %i in digital mode", output);
pinMode(output, OUTPUT);
}
#ifdef NEOPIXEL_STATUS_NOTIFICATION_PIN
LOG_INFO("Use WS2812 on GPIO %d as notification LED", NEOPIXEL_STATUS_NOTIFICATION_PIN);
notificationPixel.begin();
notificationPixel.clear();
notificationPixel.show();
#endif
setExternalState(0, false);
externalTurnedOn[0] = 0;
if (moduleConfig.external_notification.output_vibra) {
+17
View File
@@ -10,6 +10,19 @@
extern AmbientLightingThread *ambientLightingThread;
#endif
// Drive a single WS2812 as the notification LED (M1/M2-style LED_NOTIFICATION
// but addressable). A variant defines NEOPIXEL_STATUS_NOTIFICATION_PIN to
// enable. Colour defaults to green but can be overridden.
#ifdef NEOPIXEL_STATUS_NOTIFICATION_PIN
#include <Adafruit_NeoPixel.h>
#ifndef NEOPIXEL_STATUS_TYPE
#define NEOPIXEL_STATUS_TYPE (NEO_GRB + NEO_KHZ800)
#endif
#ifndef NEOPIXEL_STATUS_NOTIFICATION_COLOR
#define NEOPIXEL_STATUS_NOTIFICATION_COLOR 0x00FF00 // green
#endif
#endif
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !defined(CONFIG_IDF_TARGET_ESP32C6)
#include <NonBlockingRtttl.h>
#else
@@ -38,6 +51,10 @@ class ExternalNotificationModule : public SinglePortModule, private concurrency:
CallbackObserver<ExternalNotificationModule, const InputEvent *>(this, &ExternalNotificationModule::handleInputEvent);
uint32_t output = 0;
#ifdef NEOPIXEL_STATUS_NOTIFICATION_PIN
Adafruit_NeoPixel notificationPixel = Adafruit_NeoPixel(1, NEOPIXEL_STATUS_NOTIFICATION_PIN, NEOPIXEL_STATUS_TYPE);
#endif
public:
ExternalNotificationModule();
+14 -34
View File
@@ -4,6 +4,7 @@
#include "GPS.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "PositionPrecision.h"
#include "RTC.h"
#include "Router.h"
#include "TransmitHistory.h"
@@ -107,13 +108,7 @@ bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes
}
nodeDB->updatePosition(getFrom(&mp), p);
if (channels.getByIndex(mp.channel).settings.has_module_settings) {
precision = channels.getByIndex(mp.channel).settings.module_settings.position_precision;
} else if (channels.getByIndex(mp.channel).role == meshtastic_Channel_Role_PRIMARY) {
precision = 32;
} else {
precision = 0;
}
precision = getPositionPrecisionForChannel(mp.channel);
return false; // Let others look at this message also if they want
}
@@ -121,15 +116,12 @@ bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes
void PositionModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtastic_Position *p)
{
// Phone position packets need to be truncated to the channel precision
if (isFromUs(&mp) && (precision < 32 && precision > 0)) {
LOG_DEBUG("Truncate phone position to channel precision %i", precision);
p->latitude_i = p->latitude_i & (UINT32_MAX << (32 - precision));
p->longitude_i = p->longitude_i & (UINT32_MAX << (32 - precision));
// We want the imprecise position to be the middle of the possible location, not
p->latitude_i += (1 << (31 - precision));
p->longitude_i += (1 << (31 - precision));
if (isFromUs(&mp)) {
if (precision == 0)
LOG_DEBUG("Strip phone position due to channel precision 0");
else if (precision < 32)
LOG_DEBUG("Truncate phone position to channel precision %i", precision);
applyPositionPrecision(*p, precision);
mp.decoded.payload.size =
pb_encode_to_bytes(mp.decoded.payload.bytes, sizeof(mp.decoded.payload.bytes), &meshtastic_Position_msg, p);
}
@@ -205,20 +197,11 @@ meshtastic_MeshPacket *PositionModule::allocPositionPacket()
// lat/lon are unconditionally included - IF AVAILABLE!
LOG_DEBUG("Send location with precision %i", precision);
if (precision < 32 && precision > 0) {
p.latitude_i = localPosition.latitude_i & (UINT32_MAX << (32 - precision));
p.longitude_i = localPosition.longitude_i & (UINT32_MAX << (32 - precision));
// We want the imprecise position to be the middle of the possible location, not
p.latitude_i += (1 << (31 - precision));
p.longitude_i += (1 << (31 - precision));
} else {
p.latitude_i = localPosition.latitude_i;
p.longitude_i = localPosition.longitude_i;
}
p.precision_bits = precision;
p.latitude_i = localPosition.latitude_i;
p.longitude_i = localPosition.longitude_i;
p.has_latitude_i = true;
p.has_longitude_i = true;
applyPositionPrecision(p, precision);
// Always use NTP / GPS time if available
if (getValidTime(RTCQualityNTP) > 0) {
p.time = getValidTime(RTCQualityNTP);
@@ -349,8 +332,7 @@ void PositionModule::sendOurPosition()
// If we changed channels, ask everyone else for their latest info
LOG_INFO("Send pos@%x:6 to mesh (wantReplies=%d)", localPosition.timestamp, requestReplies);
for (uint8_t channelNum = 0; channelNum < 8; channelNum++) {
if (channels.getByIndex(channelNum).settings.has_module_settings &&
channels.getByIndex(channelNum).settings.module_settings.position_precision != 0) {
if (getPositionPrecisionForChannel(channelNum) != 0) {
sendOurPosition(NODENUM_BROADCAST, requestReplies, channelNum);
return;
}
@@ -368,10 +350,8 @@ void PositionModule::sendOurPosition(NodeNum dest, bool wantReplies, uint8_t cha
if (prevPacketId) // if we wrap around to zero, we'll simply fail to cancel in that rare case (no big deal)
service->cancelSending(prevPacketId);
// Set's the class precision value for this particular packet
if (channels.getByIndex(channel).settings.has_module_settings) {
precision = channels.getByIndex(channel).settings.module_settings.position_precision;
}
// Set the class precision value for this particular packet.
precision = getPositionPrecisionForChannel(channel);
meshtastic_MeshPacket *p = allocPositionPacket();
if (p == nullptr) {
+33
View File
@@ -17,8 +17,29 @@ StatusLEDModule::StatusLEDModule() : concurrency::OSThread("StatusLEDModule")
if (inputBroker)
inputObserver.observe(inputBroker);
#endif
#ifdef NEOPIXEL_STATUS_POWER_PIN
powerPixel.begin();
powerPixel.clear();
powerPixel.show();
#endif
#ifdef NEOPIXEL_STATUS_PAIRING_PIN
pairingPixel.begin();
pairingPixel.clear();
pairingPixel.show();
#endif
}
// Helper: write a 1-pixel NeoPixel strand to `color` when stateOn, else clear.
// Kept as a static inline here (rather than a member) so it compiles out
// completely when no NeoPixel status pins are defined.
#if defined(NEOPIXEL_STATUS_POWER_PIN) || defined(NEOPIXEL_STATUS_PAIRING_PIN)
static inline void writeStatusPixel(Adafruit_NeoPixel &pixel, uint32_t color, bool stateOn)
{
pixel.setPixelColor(0, stateOn ? color : 0);
pixel.show();
}
#endif
int StatusLEDModule::handleStatusUpdate(const meshtastic::Status *arg)
{
switch (arg->getStatusType()) {
@@ -176,6 +197,12 @@ int32_t StatusLEDModule::runOnce()
#ifdef LED_PAIRING
digitalWrite(LED_PAIRING, PAIRING_LED_state);
#endif
#ifdef NEOPIXEL_STATUS_POWER_PIN
writeStatusPixel(powerPixel, NEOPIXEL_STATUS_POWER_COLOR, CHARGE_LED_state == LED_STATE_ON);
#endif
#ifdef NEOPIXEL_STATUS_PAIRING_PIN
writeStatusPixel(pairingPixel, NEOPIXEL_STATUS_PAIRING_COLOR, PAIRING_LED_state == LED_STATE_ON);
#endif
#ifdef RGB_LED_POWER
if (!config.device.led_heartbeat_disabled) {
@@ -225,6 +252,12 @@ void StatusLEDModule::setPowerLED(bool LEDon)
#ifdef LED_PAIRING
digitalWrite(LED_PAIRING, ledState);
#endif
#ifdef NEOPIXEL_STATUS_POWER_PIN
writeStatusPixel(powerPixel, NEOPIXEL_STATUS_POWER_COLOR, LEDon);
#endif
#ifdef NEOPIXEL_STATUS_PAIRING_PIN
writeStatusPixel(pairingPixel, NEOPIXEL_STATUS_PAIRING_COLOR, LEDon);
#endif
#ifdef Battery_LED_1
digitalWrite(Battery_LED_1, ledState);
+26
View File
@@ -13,6 +13,25 @@
#include "input/InputBroker.h"
#endif
// WS2812/NeoPixel status-LED support. A variant may define
// NEOPIXEL_STATUS_POWER_PIN (required to enable the power/charge pixel)
// NEOPIXEL_STATUS_POWER_COLOR (optional, default red 0xFF0000)
// NEOPIXEL_STATUS_PAIRING_PIN / _COLOR (default blue 0x0000FF)
// Each pixel is a standalone 1-LED strand on its own GPIO — this mirrors how
// boards like the LilyGo T-Echo-Card expose three independent WS2812s.
#if defined(NEOPIXEL_STATUS_POWER_PIN) || defined(NEOPIXEL_STATUS_PAIRING_PIN)
#include <Adafruit_NeoPixel.h>
#ifndef NEOPIXEL_STATUS_TYPE
#define NEOPIXEL_STATUS_TYPE (NEO_GRB + NEO_KHZ800)
#endif
#ifndef NEOPIXEL_STATUS_POWER_COLOR
#define NEOPIXEL_STATUS_POWER_COLOR 0xFF0000 // red
#endif
#ifndef NEOPIXEL_STATUS_PAIRING_COLOR
#define NEOPIXEL_STATUS_PAIRING_COLOR 0x0000FF // blue
#endif
#endif
class StatusLEDModule : private concurrency::OSThread
{
bool slowTrack = false;
@@ -27,6 +46,13 @@ class StatusLEDModule : private concurrency::OSThread
void setPowerLED(bool);
#ifdef NEOPIXEL_STATUS_POWER_PIN
Adafruit_NeoPixel powerPixel = Adafruit_NeoPixel(1, NEOPIXEL_STATUS_POWER_PIN, NEOPIXEL_STATUS_TYPE);
#endif
#ifdef NEOPIXEL_STATUS_PAIRING_PIN
Adafruit_NeoPixel pairingPixel = Adafruit_NeoPixel(1, NEOPIXEL_STATUS_PAIRING_PIN, NEOPIXEL_STATUS_TYPE);
#endif
protected:
unsigned int my_interval = 1000; // interval in millisconds
virtual int32_t runOnce() override;
+1 -1
View File
@@ -610,7 +610,7 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->setFont(FONT_MEDIUM);
display->drawString(x_offset + x, y_offset + y, "Bluetooth");
#if !defined(M5STACK_UNITC6L)
#if !defined(OLED_TINY)
display->setFont(FONT_SMALL);
y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_MEDIUM - 4 : y_offset + FONT_HEIGHT_MEDIUM + 5;
display->drawString(x_offset + x, y_offset + y, "Enter this code");
+2
View File
@@ -158,6 +158,8 @@
#define HW_VENDOR meshtastic_HardwareModel_CHATTER_2
#elif defined(STATION_G2)
#define HW_VENDOR meshtastic_HardwareModel_STATION_G2
#elif defined(STATION_G3)
#define HW_VENDOR meshtastic_HardwareModel_STATION_G3
#elif defined(UNPHONE)
#define HW_VENDOR meshtastic_HardwareModel_UNPHONE
#elif defined(WIPHONE)
@@ -1,13 +1,65 @@
#include "AudioBoard.h"
#include "configuration.h"
#ifdef M5STACK_CARDPUTER_ADV
#include "AudioBoard.h"
#include <Wire.h>
DriverPins PinsAudioBoardES8311;
AudioBoard board(AudioDriverES8311, PinsAudioBoardES8311);
// PI4IOE5V6408 on the optional Cap LoRa-1262 (and Cap LoRa868).
#define PI4IO_ADDR 0x43
#define PI4IO_REG_IO_DIR 0x03
#define PI4IO_REG_OUT_SET 0x05
#define PI4IO_REG_OUT_H_IM 0x07
static TwoWire *findLoraCapBus()
{
TwoWire *candidates[] = {&Wire1, &Wire};
for (size_t i = 0; i < sizeof(candidates) / sizeof(candidates[0]); ++i) {
candidates[i]->beginTransmission(PI4IO_ADDR);
if (candidates[i]->endTransmission() == 0) {
return candidates[i];
}
}
return nullptr;
}
static bool pi4ioWrite(TwoWire *bus, uint8_t reg, uint8_t val)
{
bus->beginTransmission(PI4IO_ADDR);
bus->write(reg);
bus->write(val);
uint8_t status = bus->endTransmission();
if (status != 0) {
LOG_DEBUG("PI4IO write reg=0x%02x val=0x%02x failed, I2C status=%u", reg, val, status);
return false;
}
return true;
}
static void initLoraCap()
{
TwoWire *bus = findLoraCapBus();
if (!bus) {
LOG_ERROR("Cap LoRa-1262 not found");
return;
}
bool ok = pi4ioWrite(bus, PI4IO_REG_IO_DIR, 0b00000001);
ok = ok && pi4ioWrite(bus, PI4IO_REG_OUT_H_IM, 0b00000000);
ok = ok && pi4ioWrite(bus, PI4IO_REG_OUT_SET, 0b00000001);
if (!ok) {
LOG_ERROR("Antenna switch init failed");
}
}
// M5stack Cardputer ADV specific init
void lateInitVariant()
{
initLoraCap();
// AudioDriverLogger.begin(Serial, AudioDriverLogLevel::Debug);
// I2C: function, scl, sda
PinsAudioBoardES8311.addI2C(PinFunction::CODEC, Wire);
@@ -38,3 +90,5 @@ void lateInitVariant()
es8311_write_reg(0x32, 0xBF); // DAC volume (0dB)
es8311_write_reg(0x37, 0x08); // EQ bypass
}
#endif
+2
View File
@@ -87,6 +87,8 @@
#define HW_VENDOR meshtastic_HardwareModel_T_ECHO_LITE
#elif defined(TTGO_T_ECHO_PLUS)
#define HW_VENDOR meshtastic_HardwareModel_T_ECHO_PLUS
#elif defined(T_IMPULSE_PLUS)
#define HW_VENDOR meshtastic_HardwareModel_T_IMPULSE_PLUS
#elif defined(ELECROW_ThinkNode_M1)
#define HW_VENDOR meshtastic_HardwareModel_THINKNODE_M1
#elif defined(ELECROW_ThinkNode_M3)
+109
View File
@@ -0,0 +1,109 @@
#include "SGM41562.h"
#ifdef HAS_SGM41562
#include <Arduino.h>
SGM41562 *sgm41562 = nullptr;
bool initSGM41562(TwoWire &wire)
{
if (sgm41562)
return true;
sgm41562 = new SGM41562();
if (!sgm41562->begin(wire)) {
delete sgm41562;
sgm41562 = nullptr;
return false;
}
return true;
}
bool SGM41562::readReg(uint8_t reg, uint8_t &value)
{
wire_->beginTransmission(address_);
wire_->write(reg);
if (wire_->endTransmission(false) != 0)
return false;
if (wire_->requestFrom((int)address_, 1) != 1)
return false;
value = wire_->read();
return true;
}
bool SGM41562::writeReg(uint8_t reg, uint8_t value)
{
wire_->beginTransmission(address_);
wire_->write(reg);
wire_->write(value);
return wire_->endTransmission() == 0;
}
bool SGM41562::updateReg(uint8_t reg, uint8_t mask, uint8_t value)
{
uint8_t cur;
if (!readReg(reg, cur))
return false;
cur = (cur & ~mask) | (value & mask);
return writeReg(reg, cur);
}
bool SGM41562::begin(TwoWire &wire, uint8_t address)
{
wire_ = &wire;
address_ = address;
uint8_t id;
if (!readReg(REG_DEVICE_ID, id)) {
LOG_WARN("SGM41562: I2C read failed at 0x%02X", address_);
return false;
}
if (id != DEVICE_ID_EXPECTED) {
LOG_WARN("SGM41562: unexpected device ID 0x%02X (expected 0x%02X)", id, DEVICE_ID_EXPECTED);
return false;
}
LOG_INFO("SGM41562: detected at 0x%02X (id 0x%02X)", address_, id);
// Mirror the vendor reference init sequence: PCB OTP off, NTC off,
// watchdog off, charger enabled. These match LilyGo's stock firmware
// for the T-Impulse Plus.
delay(120);
writeReg(REG_SYS_VOLTAGE_REG, 0xB7);
writeReg(REG_MISC_OP_CONTROL, 0x40);
writeReg(REG_CHARGE_TERM_TIMER, 0x1A);
writeReg(REG_POWER_ON_CFG, 0xA4);
return refresh();
}
bool SGM41562::refresh()
{
uint32_t now = millis();
if (lastRefreshMs_ != 0 && (now - lastRefreshMs_) < 250)
return true; // cached
lastRefreshMs_ = now == 0 ? 1 : now;
uint8_t status, fault;
if (!readReg(REG_SYSTEM_STATUS, status))
return false;
if (!readReg(REG_FAULT, fault))
return false;
chargeStatus_ = static_cast<ChargeStatus>((status >> SYS_STATUS_CHRG_SHIFT) & SYS_STATUS_CHRG_MASK);
inputPowerGood_ = (status & SYS_STATUS_PG) != 0;
thermalReg_ = (status & SYS_STATUS_THERM_REG) != 0;
faultMask_ = fault & 0x3F; // bits [7:6] are enter_ship_time config, not faults
return true;
}
bool SGM41562::setChargeEnable(bool enable)
{
return updateReg(REG_POWER_ON_CFG, POWER_ON_CFG_CHG_DISABLE, enable ? 0x00 : POWER_ON_CFG_CHG_DISABLE);
}
bool SGM41562::setShippingModeEnable(bool enable)
{
return updateReg(REG_MISC_OP_CONTROL, MISC_OP_SHIPPING_MODE, enable ? MISC_OP_SHIPPING_MODE : 0x00);
}
#endif // HAS_SGM41562
+102
View File
@@ -0,0 +1,102 @@
#pragma once
#include "configuration.h"
#ifdef HAS_SGM41562
#include <Wire.h>
#include <stdint.h>
// SG Micro SGM41562 — single-cell Li-ion buck charger, I²C-controlled, no
// fuel gauge. This driver exposes status (charging / input good / fault),
// charge enable, and shipping-mode control. Battery voltage/percent still
// come from the platform ADC path; the charger is plumbed in as a
// side-channel for isCharging()/isVbusIn() in AnalogBatteryLevel.
//
// Reference: SGM41562 datasheet (Cmd map + bit fields cross-verified against
// LilyGo's `Cpp_Bus_Driver::Sgm41562xx` driver, which is what their vendor
// example for this board uses).
#ifndef SGM41562_ADDR
#define SGM41562_ADDR 0x03 // Per datasheet — unusual but correct
#endif
#ifndef SGM41562_WIRE
#define SGM41562_WIRE Wire1 // Most boards put the PMU on the secondary bus
#endif
class SGM41562
{
public:
enum class ChargeStatus : uint8_t {
NotCharging = 0b00,
Precharge = 0b01,
FastCharge = 0b10,
ChargeDone = 0b11,
};
bool begin(TwoWire &wire, uint8_t address = SGM41562_ADDR);
// Re-read the system status + fault registers. Throttled internally to
// at most one I²C transaction per 250 ms — call as often as you like.
bool refresh();
// Status — cached from the most recent refresh().
ChargeStatus chargeStatus() const { return chargeStatus_; }
bool isCharging() const { return chargeStatus_ == ChargeStatus::Precharge || chargeStatus_ == ChargeStatus::FastCharge; }
bool isChargeDone() const { return chargeStatus_ == ChargeStatus::ChargeDone; }
bool isInputPowerGood() const { return inputPowerGood_; }
bool isThermalRegulation() const { return thermalReg_; }
uint8_t faultMask() const { return faultMask_; }
// Control.
bool setChargeEnable(bool enable);
bool setShippingModeEnable(bool enable);
private:
TwoWire *wire_ = nullptr;
uint8_t address_ = SGM41562_ADDR;
uint32_t lastRefreshMs_ = 0;
ChargeStatus chargeStatus_ = ChargeStatus::NotCharging;
bool inputPowerGood_ = false;
bool thermalReg_ = false;
uint8_t faultMask_ = 0;
bool readReg(uint8_t reg, uint8_t &value);
bool writeReg(uint8_t reg, uint8_t value);
bool updateReg(uint8_t reg, uint8_t mask, uint8_t value);
// SGM41562 register addresses
static constexpr uint8_t REG_INPUT_SOURCE = 0x00;
static constexpr uint8_t REG_POWER_ON_CFG = 0x01;
static constexpr uint8_t REG_CHARGE_CURRENT = 0x02;
static constexpr uint8_t REG_DISCHARGE_TERM_CURRENT = 0x03;
static constexpr uint8_t REG_CHARGE_VOLTAGE = 0x04;
static constexpr uint8_t REG_CHARGE_TERM_TIMER = 0x05;
static constexpr uint8_t REG_MISC_OP_CONTROL = 0x06;
static constexpr uint8_t REG_SYS_VOLTAGE_REG = 0x07;
static constexpr uint8_t REG_SYSTEM_STATUS = 0x08;
static constexpr uint8_t REG_FAULT = 0x09;
static constexpr uint8_t REG_I2C_ADDR_MISC = 0x0A;
static constexpr uint8_t REG_DEVICE_ID = 0x0B;
// Bit positions in REG_POWER_ON_CFG.
static constexpr uint8_t POWER_ON_CFG_CHG_DISABLE = 0x08; // bit 3: 1 = charging disabled
// Bit positions in REG_MISC_OP_CONTROL.
static constexpr uint8_t MISC_OP_SHIPPING_MODE = 0x20; // bit 5: 1 = enter shipping mode
// Bit positions in REG_SYSTEM_STATUS.
static constexpr uint8_t SYS_STATUS_CHRG_SHIFT = 3;
static constexpr uint8_t SYS_STATUS_CHRG_MASK = 0x03;
static constexpr uint8_t SYS_STATUS_PG = 0x02; // bit 1: input power good
static constexpr uint8_t SYS_STATUS_THERM_REG = 0x01; // bit 0: thermal regulation
static constexpr uint8_t DEVICE_ID_EXPECTED = 0x04;
};
extern SGM41562 *sgm41562;
// Lazy-instantiate the global on the supplied wire. Returns true on success.
bool initSGM41562(TwoWire &wire);
#endif // HAS_SGM41562
+100
View File
@@ -0,0 +1,100 @@
#include "PositionPrecision.h"
#include "TestUtil.h"
#include "mesh-pb-constants.h"
#include <unity.h>
static meshtastic_Position makePosition()
{
meshtastic_Position position = meshtastic_Position_init_default;
position.has_latitude_i = true;
position.latitude_i = static_cast<int32_t>(0x12345678);
position.has_longitude_i = true;
position.longitude_i = static_cast<int32_t>(0x22345678);
position.has_altitude = true;
position.altitude = 123;
position.time = 42;
position.location_source = meshtastic_Position_LocSource_LOC_EXTERNAL;
position.timestamp = 43;
position.sats_in_view = 10;
return position;
}
static void test_applyPositionPrecision_clampsLatLonAndSetsPrecisionBits()
{
meshtastic_Position position = makePosition();
applyPositionPrecision(position, 16);
TEST_ASSERT_EQUAL_INT32(static_cast<int32_t>(0x12348000), position.latitude_i);
TEST_ASSERT_EQUAL_INT32(static_cast<int32_t>(0x22348000), position.longitude_i);
TEST_ASSERT_EQUAL_UINT32(16, position.precision_bits);
TEST_ASSERT_TRUE(position.has_latitude_i);
TEST_ASSERT_TRUE(position.has_longitude_i);
}
static void test_applyPositionPrecision_fullPrecisionKeepsLatLon()
{
meshtastic_Position position = makePosition();
applyPositionPrecision(position, 32);
TEST_ASSERT_EQUAL_INT32(static_cast<int32_t>(0x12345678), position.latitude_i);
TEST_ASSERT_EQUAL_INT32(static_cast<int32_t>(0x22345678), position.longitude_i);
TEST_ASSERT_EQUAL_UINT32(32, position.precision_bits);
}
static void test_applyPositionPrecision_zeroScrubsLocationButKeepsTime()
{
meshtastic_Position position = makePosition();
applyPositionPrecision(position, 0);
TEST_ASSERT_FALSE(position.has_latitude_i);
TEST_ASSERT_EQUAL_INT32(0, position.latitude_i);
TEST_ASSERT_FALSE(position.has_longitude_i);
TEST_ASSERT_EQUAL_INT32(0, position.longitude_i);
TEST_ASSERT_FALSE(position.has_altitude);
TEST_ASSERT_EQUAL_INT32(0, position.altitude);
TEST_ASSERT_EQUAL_UINT32(42, position.time);
TEST_ASSERT_EQUAL_UINT32(0, position.timestamp);
TEST_ASSERT_EQUAL_UINT32(0, position.sats_in_view);
TEST_ASSERT_EQUAL_UINT32(0, position.precision_bits);
}
static void test_applyPositionPrecision_reencodesPositionPacket()
{
meshtastic_Position position = makePosition();
meshtastic_MeshPacket packet = meshtastic_MeshPacket_init_default;
packet.which_payload_variant = meshtastic_MeshPacket_decoded_tag;
packet.decoded.portnum = meshtastic_PortNum_POSITION_APP;
packet.decoded.payload.size = pb_encode_to_bytes(packet.decoded.payload.bytes, sizeof(packet.decoded.payload.bytes),
&meshtastic_Position_msg, &position);
TEST_ASSERT_TRUE(applyPositionPrecision(packet, 16));
meshtastic_Position decoded = meshtastic_Position_init_default;
TEST_ASSERT_TRUE(
pb_decode_from_bytes(packet.decoded.payload.bytes, packet.decoded.payload.size, &meshtastic_Position_msg, &decoded));
TEST_ASSERT_EQUAL_INT32(static_cast<int32_t>(0x12348000), decoded.latitude_i);
TEST_ASSERT_EQUAL_INT32(static_cast<int32_t>(0x22348000), decoded.longitude_i);
TEST_ASSERT_EQUAL_UINT32(16, decoded.precision_bits);
}
void setUp(void) {}
void tearDown(void) {}
extern "C" {
void setup()
{
initializeTestEnvironment();
UNITY_BEGIN();
RUN_TEST(test_applyPositionPrecision_clampsLatLonAndSetsPrecisionBits);
RUN_TEST(test_applyPositionPrecision_fullPrecisionKeepsLatLon);
RUN_TEST(test_applyPositionPrecision_zeroScrubsLocationButKeepsTime);
RUN_TEST(test_applyPositionPrecision_reencodesPositionPacket);
exit(UNITY_END());
}
void loop() {}
}
+11 -9
View File
@@ -52,23 +52,25 @@ void c6l_init()
vTaskDelay(10 / portTICK_PERIOD_MS);
i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_CHIP_RESET, &in_data);
vTaskDelay(10 / portTICK_PERIOD_MS);
i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_IO_DIR, 0b11000000); // 0: input 1: output
i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_IO_DIR, 0b11100000); // P5,P6,P7 as outputs
vTaskDelay(10 / portTICK_PERIOD_MS);
i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_H_IM, 0b00111100); // 使用到的引脚关闭High-Impedance
i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_H_IM, 0b00011100); // High-Impedance
vTaskDelay(10 / portTICK_PERIOD_MS);
i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_PULL_SEL, 0b11000011); // pull up/down select, 0 down, 1 up
i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_PULL_SEL, 0b11100011); // pull up/down select, 0 down, 1 up
vTaskDelay(10 / portTICK_PERIOD_MS);
i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_PULL_EN, 0b11000011); // pull up/down enable, 0 disable, 1 enable
i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_PULL_EN, 0b11100011); // pull up/down enable, 0 disable, 1 enable
vTaskDelay(10 / portTICK_PERIOD_MS);
i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_IN_DEF_STA, 0b00000011); // P0 P1 默认高电平, 按键按下触发中断
i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_IN_DEF_STA, 0b00000011); // P0 P1 default to high level; button press triggers the interrupt
vTaskDelay(10 / portTICK_PERIOD_MS);
i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_INT_MASK, 0b11111100); // P0 P1 中断使能 0 enable, 1 disable
i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_INT_MASK, 0b11111100); // P0 P1 interrupts enabled (0 = enable, 1 = disable)
vTaskDelay(10 / portTICK_PERIOD_MS);
i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_SET, 0b10000000); // 默认输出为0
i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_SET, 0b10000000); // default output is 0
vTaskDelay(10 / portTICK_PERIOD_MS);
i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_IRQ_STA, &in_data); // read IRQ_STA to clear the flag
vTaskDelay(10 / portTICK_PERIOD_MS);
i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_IRQ_STA, &in_data); // 读取IRQ_STA清除标志
i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_SET, &in_data);
setbit(in_data, 6); // HIGH
setbit(in_data, 6); // enable SX_ANT_SW
setbit(in_data, 5); // enable SX_LNA_EN
i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_OUT_SET, in_data);
}
@@ -48,6 +48,9 @@ void c6l_init();
#define SSD1306_RESET 15
// #define OLED_DG 1
#endif
// Tiny OLED panel — opts into compile-time layout/font/feature substitutions
// gated on OLED_TINY across the graphics stack.
#define OLED_TINY
#define SCREEN_TRANSITION_FRAMERATE 10
#define BRIGHTNESS_DEFAULT 130 // Medium Low Brightness
@@ -10,9 +10,6 @@ build_flags =
-D M5STACK_CARDPUTER_ADV
-D ARDUINO_USB_CDC_ON_BOOT=1
-I variants/esp32s3/m5stack_cardputer_adv
build_src_filter =
${esp32s3_base.build_src_filter}
+<../variants/esp32s3/m5stack_cardputer_adv>
lib_deps =
${esp32s3_base.lib_deps}
# renovate: datasource=git-refs depName=meshtastic-st7789 packageName=https://github.com/meshtastic/st7789 gitBranch=main
@@ -9,6 +9,7 @@
#define ST7789_BUSY -1
// #define VTFT_CTRL 38
#define VTFT_LEDA 38
#define TFT_BACKLIGHT_ON HIGH
// #define ST7789_BL (32+6)
#define ST7789_SPI_HOST SPI2_HOST
// #define TFT_BL (32+6)
@@ -0,0 +1,38 @@
// Shared pin/feature defines for BQ Station G2 and G3.
// Boards differ only in PA output power (SX126X_MAX_POWER) and the wiki URL.
#pragma once
// Station G2/G3 may not have GPS installed, but have a GROVE GPS Socket for an optional GPS module
#define GPS_RX_PIN 7
#define GPS_TX_PIN 15
// 1.3 inch OLED Screen
#define USE_SH1107_128_64
#define I2C_SDA 5
#define I2C_SCL 6
#define BUTTON_PIN 38 // Program button
#define BUTTON_NEED_PULLUP
#define USE_SX1262
#define LORA_MISO 14
#define LORA_SCK 12
#define LORA_MOSI 13
#define LORA_CS 11
#define LORA_RESET 21
#define LORA_DIO1 48
#ifdef USE_SX1262
#define SX126X_CS LORA_CS // Compatibility alias; prefer LORA_CS in board definitions.
#define SX126X_DIO1 LORA_DIO1
#define SX126X_BUSY 47
#define SX126X_RESET LORA_RESET
// DIO2 controls an antenna switch and the TCXO voltage is controlled by DIO3
#define SX126X_DIO2_AS_RF_SWITCH
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
// NOTE: SX126X_MAX_POWER is intentionally NOT defined here — each board sets it.
#endif
+2 -1
View File
@@ -22,10 +22,11 @@ upload_speed = 921600
build_unflags =
${esp32s3_base.build_unflags}
-DARDUINO_USB_MODE=0
build_flags =
build_flags =
${esp32s3_base.build_flags}
-D STATION_G2
-I variants/esp32s3/station-g2
-I variants/esp32s3/station-common
-DBOARD_HAS_PSRAM
-DSTATION_G2
-DARDUINO_USB_MODE=1
+1 -31
View File
@@ -2,39 +2,9 @@
Board Information: https://wiki.uniteng.com/en/meshtastic/station-g2
*/
// Station G2 may not have GPS installed, but it has a GROVE GPS Socket for Optional GPS Module
#define GPS_RX_PIN 7
#define GPS_TX_PIN 15
// Station G2 has 1.3 inch OLED Screen
#define USE_SH1107_128_64
#define I2C_SDA 5 // I2C pins for this board
#define I2C_SCL 6
#define BUTTON_PIN 38 // This is the Program Button
#define BUTTON_NEED_PULLUP
#define USE_SX1262
#define LORA_MISO 14
#define LORA_SCK 12
#define LORA_MOSI 13
#define LORA_CS 11
#define LORA_RESET 21
#define LORA_DIO1 48
#include "station_common.h"
#ifdef USE_SX1262
#define SX126X_CS LORA_CS // FIXME - we really should define LORA_CS instead
#define SX126X_DIO1 LORA_DIO1
#define SX126X_BUSY 47
#define SX126X_RESET LORA_RESET
// DIO2 controlls an antenna switch and the TCXO voltage is controlled by DIO3
#define SX126X_DIO2_AS_RF_SWITCH
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
// Ensure the PA does not exceed the saturation output power. More
// Info:https://wiki.uniteng.com/en/meshtastic/station-g2#summary-for-lora-power-amplifier-conduction-test
#define SX126X_MAX_POWER 19
@@ -0,0 +1,21 @@
#ifndef Pins_Arduino_h
#define Pins_Arduino_h
#include <stdint.h>
#define USB_VID 0x303a
#define USB_PID 0x1001
// GPIO48 Reference: https://github.com/espressif/arduino-esp32/pull/8600
// The default Wire will be mapped to Screen and Sensors
static const uint8_t SDA = 5;
static const uint8_t SCL = 6;
// Default SPI will be mapped to Radio
static const uint8_t MISO = 14;
static const uint8_t SCK = 12;
static const uint8_t MOSI = 13;
static const uint8_t SS = 11;
#endif /* Pins_Arduino_h */
@@ -0,0 +1,32 @@
[env:station-g3]
custom_meshtastic_hw_model = 134
custom_meshtastic_hw_model_slug = STATION_G3
custom_meshtastic_architecture = esp32-s3
custom_meshtastic_actively_supported = true
custom_meshtastic_support_level = 2
custom_meshtastic_display_name = Station G3
custom_meshtastic_images = station-g3.svg
custom_meshtastic_tags = B&Q
custom_meshtastic_requires_dfu = true
custom_meshtastic_partition_scheme = 16MB
extends = esp32s3_base
board = station-g3
board_level = pr
board_check = true
board_build.partitions = default_16MB.csv
board_build.mcu = esp32s3
upload_protocol = esptool
;upload_port = /dev/ttyACM0
upload_speed = 921600
build_unflags =
${esp32s3_base.build_unflags}
-DARDUINO_USB_MODE=0
build_flags =
${esp32s3_base.build_flags}
-D STATION_G3
-I variants/esp32s3/station-g3
-I variants/esp32s3/station-common
-DBOARD_HAS_PSRAM
-DSTATION_G3
-DARDUINO_USB_MODE=1
+22
View File
@@ -0,0 +1,22 @@
#include "station_common.h"
#ifdef USE_SX1262
// Station G3 reuses the same Fast-Transient DC-DC PA design as G2 (BQ35LORA900V1M).
// PA Operating Mode is set in hardware via the PA-PL1 / PA-PL2 jumpers.
// 19 matches the G2 cap (SX1262 19 dBm in → ~31 dBm PA out at Power Level 1, ISM compliant).
// Raise (max 22) only if running a higher PA Power Level and you can stay within local band limits.
#define SX126X_MAX_POWER 19
#endif
/*
#define BATTERY_PIN 4 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage
#define ADC_CHANNEL ADC1_GPIO4_CHANNEL
#define ADC_MULTIPLIER 4
#define BATTERY_SENSE_SAMPLES 15 // Set the number of samples, It has an effect of increasing sensitivity.
#define BAT_FULLVOLT 8400
#define BAT_EMPTYVOLT 5000
#define BAT_CHARGINGVOLT 8400
#define BAT_NOBATVOLT 4460
#define CELL_TYPE_LION // same curve for liion/lipo
#define NUM_CELLS 2
*/
+1 -1
View File
@@ -29,7 +29,7 @@ lib_deps =
# renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
lovyan03/LovyanGFX@1.2.21
; # renovate: datasource=git-refs depName=libch341-spi-userspace packageName=https://github.com/pine64/libch341-spi-userspace gitBranch=main
https://github.com/pine64/libch341-spi-userspace/archive/23c42319a69cffcb65868e3c72e6bed83974a393.zip
https://github.com/pine64/libch341-spi-userspace/archive/2e5ff751d0c39667993df672cb683740ed5c9394.zip
# renovate: datasource=custom.pio depName=adafruit/Adafruit seesaw Library packageName=adafruit/library/Adafruit seesaw Library
adafruit/Adafruit seesaw Library@1.7.9
# renovate: datasource=git-refs depName=RAK12034-BMX160 packageName=https://github.com/RAKWireless/RAK12034-BMX160 gitBranch=main
@@ -0,0 +1,12 @@
[env:t-echo-card]
extends = nrf52840_base
board = t-echo
board_level = extra
debug_tool = jlink
build_flags = ${nrf52840_base.build_flags}
-I variants/nrf52840/t-echo-card
-D PRIVATE_HW
-D T_ECHO_CARD
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/t-echo-card>
+66
View File
@@ -0,0 +1,66 @@
/*
Copyright (c) 2014-2015 Arduino LLC. All right reserved.
Copyright (c) 2016 Sandeep Mistry All right reserved.
Copyright (c) 2018, Adafruit Industries (adafruit.com)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "variant.h"
#include "Arduino.h"
#include "nrf.h"
#include "wiring_constants.h"
#include "wiring_digital.h"
const uint32_t g_ADigitalPinMap[] = {
// P0 - pins 0 and 1 are hardwired for xtal and should never be enabled
0xff, 0xff, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
// P1
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
void initVariant()
{
// No plain GPIO LEDs on this board (only WS2812 addressable LEDs, not driven here).
}
// Reproduces the vendor firmware's boot sequence from
// examples/original_test/original_test.ino. Runs before Meshtastic touches
// PIN_POWER_EN, so the RT9080 LDO gets a clean reset pulse and peripherals
// whose EN pins must be LOW at boot (GPS_EN, GPS_RF_EN, BUZZER) aren't left
// floating while the 3V3 rail is ramping.
void earlyInitVariant()
{
// 3.3V rail: toggle RT9080_EN HIGH → LOW → HIGH with 100 ms dwell so the
// LDO enters enable from a known state. The single-shot HIGH in main.cpp
// is not enough on this hardware — if the chip was in a half-enabled
// state from a previous reset, the rail brown-outs once LoRa TX fires.
pinMode(PIN_POWER_EN, OUTPUT);
digitalWrite(PIN_POWER_EN, HIGH);
delay(100);
digitalWrite(PIN_POWER_EN, LOW);
delay(100);
digitalWrite(PIN_POWER_EN, HIGH);
delay(100);
// Park peripherals with active-high enables LOW so they don't sink
// current while the rest of setup() runs.
pinMode(PIN_GPS_STANDBY, OUTPUT);
digitalWrite(PIN_GPS_STANDBY, LOW);
pinMode(PIN_GPS_RESET, OUTPUT);
digitalWrite(PIN_GPS_RESET, LOW);
pinMode(PIN_BUZZER, OUTPUT);
digitalWrite(PIN_BUZZER, LOW);
}
+202
View File
@@ -0,0 +1,202 @@
// Variant definition for LilyGo T-Echo-Card (nRF52840)
#ifndef _VARIANT_T_ECHO_CARD_
#define _VARIANT_T_ECHO_CARD_
/** Master clock frequency */
#define VARIANT_MCK (64000000ul)
#define USE_LFXO // Board uses 32kHz crystal for LF
/*----------------------------------------------------------------------------
* Headers
*----------------------------------------------------------------------------*/
#include "WVariant.h"
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
// Number of pins defined in PinDescription array
#define PINS_COUNT (48)
#define NUM_DIGITAL_PINS (48)
#define NUM_ANALOG_INPUTS (1)
#define NUM_ANALOG_OUTPUTS (0)
// LEDs - board only exposes 3x WS2812 addressable LEDs. No plain GPIO LEDs.
// Intentionally do not define PIN_LED1 on this variant, so nRF52 platform
// code does not auto-enable a nonexistent GPIO power/status LED.
#define LED_STATE_ON 1
// Three independent WS2812 data lines (one LED per line, not a daisy chain).
// Each is driven as a 1-pixel NeoPixel by StatusLEDModule / ExternalNotification,
// assigns LED_POWER (red) and LED_NOTIFICATION (green).
#define WS2812_DATA_1 (32 + 7) // P1.7 - charge/heartbeat (red)
#define WS2812_DATA_2 (32 + 12) // P1.12 - external notification (green)
#define WS2812_DATA_3 (0 + 28) // P0.28 - BLE pairing (blue)
// Wire each WS2812 to a status role. Colour defaults are scaled to 25%
// brightness (0x40) — the bare-die WS2812s on this board are very bright at
// full intensity in a close-range enclosure.
#define NEOPIXEL_STATUS_POWER_PIN WS2812_DATA_1
#define NEOPIXEL_STATUS_NOTIFICATION_PIN WS2812_DATA_2
#define NEOPIXEL_STATUS_PAIRING_PIN WS2812_DATA_3
#define NEOPIXEL_STATUS_POWER_COLOR 0x400000 // red @ 25%
#define NEOPIXEL_STATUS_NOTIFICATION_COLOR 0x004000 // green @ 25%
#define NEOPIXEL_STATUS_PAIRING_COLOR 0x000040 // blue @ 25%
// The charger IC does not blink on its own; let StatusLEDModule do the
// software blink while charging
// If left defined: hardware would be expected to handle the charging pulse.
// #define POWER_LED_HARDWARE_BLINKS_WHILE_CHARGING
// Buttons
#define PIN_BUTTON1 (32 + 10) // KEY_1: P1.10
#define BUTTON_CLICK_MS 400
// Analog pins
#define PIN_A0 (0 + 2) // Battery ADC (BATTERY_ADC_DATA)
#define BATTERY_PIN PIN_A0
static const uint8_t A0 = PIN_A0;
#define ADC_RESOLUTION 14
// BATTERY_MEASUREMENT_CONTROL - enable divider for battery reading
#define ADC_CTRL (0 + 31)
#define ADC_CTRL_ENABLED HIGH
// NFC placeholders, not used
#define PIN_NFC1 (9)
#define PIN_NFC2 (10)
// Wire Interfaces (IIC_1 on the vendor header)
#define WIRE_INTERFACES_COUNT 1
#define PIN_WIRE_SDA (32 + 4) // IIC_1_SDA: P1.4
#define PIN_WIRE_SCL (32 + 2) // IIC_1_SCL: P1.2
// External serial flash ZD25WQ32CEIGR
// QSPI Pins
#define PIN_QSPI_SCK (0 + 4)
#define PIN_QSPI_CS (0 + 12)
#define PIN_QSPI_IO0 (0 + 6) // MOSI if using two bit interface
#define PIN_QSPI_IO1 (0 + 8) // MISO if using two bit interface
#define PIN_QSPI_IO2 (32 + 9) // WP
#define PIN_QSPI_IO3 (0 + 26) // HOLD
// On-board QSPI Flash
#define EXTERNAL_FLASH_DEVICES ZD25WQ32CEIGR
#define EXTERNAL_FLASH_USE_QSPI
// Lora S62F (SX1262)
#define USE_SX1262
#define SX126X_CS (0 + 11)
#define SX126X_DIO1 (32 + 8)
#define SX126X_DIO2 (0 + 5)
#define SX126X_BUSY (0 + 14)
#define SX126X_RESET (0 + 7)
#define SX126X_RXEN (32 + 1) // SX1262_RF_VC2
#define SX126X_TXEN (0 + 27) // SX1262_RF_VC1
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
// ───────────────────────────────────────────────────────────────────────────
// OLED display: SSD1315 on I2C @ 0x3C (IIC_1). SSD1315 is register-compatible
// with SSD1306, so USE_SSD1306 initializes the controller correctly.
//
// Viewport: the physical panel is 72×40, mapped into the SSD1315's 128×64
// GDDRAM at columns 28..99, pages 3..7 (rows 24..63). The firmware handles
// this by:
// * asking the library for GEOMETRY_72_40, which sets the framebuffer to
// 72×40 and emits the right SETMULTIPLEX (39) / SETCOMPINS at init;
// * relying on SSD1306Wire's built-in horizontal auto-centering
// ((128 - width) / 2 = 28), so no horizontal shim is needed;
// * calling SSD1306Wire::setYOffset(3) in Screen.cpp when
// OLED_Y_OFFSET_PAGES is defined — this shifts every PAGEADDR write by
// three pages (24 rows) so data lands on the visible rows.
// ───────────────────────────────────────────────────────────────────────────
#define HAS_SCREEN 1
#define USE_SSD1306
#define OLED_GEOMETRY_OVERRIDE GEOMETRY_72_40
#define OLED_Y_OFFSET_PAGES 3
#define OLED_TINY
// Controls power 3V3 for all peripherals (GPS + LoRa + Sensor)
#define PIN_POWER_EN (0 + 30) // RT9080_EN
// SPI1 is unused (no external SPI display). Keep declarations for the core.
#define PIN_SPI1_MISO (-1)
#define PIN_SPI1_MOSI (-1)
#define PIN_SPI1_SCK (-1)
// GPS pins
#define GPS_L76K
#define GPS_BAUDRATE 9600
#define HAS_GPS 1
#define PIN_GPS_EN (32 + 15) // GPS_EN: P1.15 - GPS power enable
#define GPS_EN_ACTIVE 1
#define PIN_GPS_STANDBY (0 + 25) // GPS_WAKE_UP: P0.25 - wakeup pin
#define PIN_GPS_PPS (0 + 23) // GPS_1PPS: P0.23
#define GPS_RX_PIN (0 + 19) // MCU RX ← GPS's TX (vendor GPS_UART_TX / P0.19)
#define GPS_TX_PIN (0 + 21) // MCU TX → GPS's RX (vendor GPS_UART_RX / P0.21)
#define PIN_GPS_RESET (0 + 29) // GPS_RF_EN: GPS RF enable / reset
#define GPS_THREAD_INTERVAL 50
#define PIN_SERIAL1_RX GPS_RX_PIN
#define PIN_SERIAL1_TX GPS_TX_PIN
// SPI Interfaces (LoRa on SPI0)
#define SPI_INTERFACES_COUNT 2
// For LORA, SPI 0
#define PIN_SPI_MISO (0 + 17)
#define PIN_SPI_MOSI (0 + 15)
#define PIN_SPI_SCK (0 + 13)
// Battery
// The battery sense is hooked to PIN_A0 (P0.2) via a divider controlled by ADC_CTRL.
#define BATTERY_SENSE_RESOLUTION_BITS 12
#define BATTERY_SENSE_RESOLUTION 4096.0
#undef AREF_VOLTAGE
#define AREF_VOLTAGE 3.0
#define VBAT_AR_INTERNAL AR_INTERNAL_3_0
#define ADC_MULTIPLIER (2.0F)
// Buzzer (PWM output, passive piezo)
#define PIN_BUZZER (32 + 6) // BUZZER_DATA: P1.6
// ───────────────────────────────────────────────────────────────────────────
// I²S speaker (MAX98357 Class-D amp). Stereo I²S data path.
// Not supported on nrf52. These defines exist for out-of-tree code only.
// ───────────────────────────────────────────────────────────────────────────
#define SPEAKER_EN (32 + 11) // P1.11 - amp main enable
#define SPEAKER_EN_2 (0 + 3) // P0.3 - secondary enable (vendor firmware toggles both)
#define SPEAKER_BCLK (0 + 16) // P0.16 - I2S bit clock
#define SPEAKER_DATA (0 + 20) // P0.20 - I2S data (SDOUT)
#define SPEAKER_WS_LRCK (0 + 22) // P0.22 - I2S word select / LRCK
// ───────────────────────────────────────────────────────────────────────────
// PDM microphone (ST MP34DT05).
// TODO to enable a mic path:
// Use Adafruit nRF52 core's built-in PDM.h wrapper (Arduino-compatible
// API exists on nRF52840). Clock on MIC_SCLK, data on MIC_DATA.
// ───────────────────────────────────────────────────────────────────────────
#define MIC_SCLK (32 + 3) // P1.3 - PDM clock (MIC_SCLK on vendor header)
#define MIC_DATA (32 + 5) // P1.5 - PDM data (MIC_DATA on vendor header)
#define SERIAL_PRINT_PORT 0
#ifdef __cplusplus
}
#endif
/*----------------------------------------------------------------------------
* Arduino objects - C++ only
*----------------------------------------------------------------------------*/
#endif
@@ -0,0 +1,9 @@
[env:t-impulse-plus]
extends = nrf52840_base
board = t-impulse-plus
build_flags = ${nrf52840_base.build_flags}
-I variants/nrf52840/t-impulse-plus
-D T_IMPULSE_PLUS
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/t-impulse-plus>
@@ -0,0 +1,91 @@
/*
* variant.cpp - Digital pin mapping for LilyGo T-Impulse Plus
*
* Board: T-Impulse Plus V1.0 (nRF52840)
* Hardware:
* - SSD1315 OLED
* - SX1262 (S62F)
* - MIA-M10Q GPS
* - ICM20948 IMU
* - ZD25WQ32C Flash
* - TTP223 Touch Button
* - Vibration Motor
*/
#include "variant.h"
#include "nrf.h"
#include "wiring_constants.h"
#include "wiring_digital.h"
extern "C" {
const uint32_t g_ADigitalPinMap[] = {
// D0-D6: LoRa SX1262 (S62F module) SPI + control
2, // D0 P0.02 SX1262_RST
29, // D1 P0.29 SX1262_DIO1
31, // D2 P0.31 SX1262_BUSY
46, // D3 P1.14 SX1262_CS
3, // D4 P0.03 SPI_SCK
30, // D5 P0.30 SPI_MISO
28, // D6 P0.28 SPI_MOSI
// D7-D8: RF switch control
45, // D7 P1.13 SX1262_RF_VC1 (TXEN)
39, // D8 P1.07 SX1262_RF_VC2 (RXEN)
// D9-D11: GPS (u-blox MIA-M10Q)
44, // D9 P1.12 GPS_TX (MCU TX -> GPS RX)
43, // D10 P1.11 GPS_RX (MCU RX <- GPS TX)
42, // D11 P1.10 GPS_EN
// D12-D13: Display I2C (SSD1315)
20, // D12 P0.20 SCREEN_SDA
15, // D13 P0.15 SCREEN_SCL
// D14-D15: Sensor I2C (ICM20948, SGM41562)
40, // D14 P1.08 IMU_SDA
11, // D15 P0.11 IMU_SCL
// D16-D17: Battery management
5, // D16 P0.05 BATTERY_ADC
25, // D17 P0.25 BATTERY_MEASUREMENT_CONTROL
// D18: Touch button (TTP223)
36, // D18 P1.04 TTP223_KEY
// D19: Vibration motor
22, // D19 P0.22 VIBRATION_MOTOR
// D20: LDO enable
14, // D20 P0.14 RT9080_EN
// D21-D26: Flash QSPI (ZD25WQ32C)
12, // D21 P0.12 FLASH_CS
4, // D22 P0.04 FLASH_SCLK
6, // D23 P0.06 FLASH_IO0
41, // D24 P1.09 FLASH_IO1
8, // D25 P0.08 FLASH_IO2
26, // D26 P0.26 FLASH_IO3
// D27-D28: Interrupt lines
7, // D27 P0.07 ICM20948_INT
16, // D28 P0.16 SGM41562_INT
// D29: Boot button
24, // D29 P0.24 BOOT
};
}
void initVariant()
{
// Flash CS high (deselect)
pinMode(PIN_QSPI_CS, OUTPUT);
digitalWrite(PIN_QSPI_CS, HIGH);
// Enable battery voltage measurement
pinMode(BAT_READ, OUTPUT);
digitalWrite(BAT_READ, HIGH);
// Enable RT9080 LDO
pinMode(D20, OUTPUT);
digitalWrite(D20, HIGH);
}
+181
View File
@@ -0,0 +1,181 @@
#ifndef _T_IMPULSE_PLUS_H_
#define _T_IMPULSE_PLUS_H_
#include "WVariant.h"
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Clock Configuration
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#define VARIANT_MCK (64000000ul)
#define USE_LFXO
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Pin Capacity Definitions
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#define PINS_COUNT (30u)
#define NUM_DIGITAL_PINS (30u)
#define NUM_ANALOG_INPUTS (1u)
#define NUM_ANALOG_OUTPUTS (0u)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Digital Pin Mapping
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#define D0 0 // P0.02 SX1262_RST
#define D1 1 // P0.29 SX1262_DIO1
#define D2 2 // P0.31 SX1262_BUSY
#define D3 3 // P1.14 SX1262_CS
#define D4 4 // P0.03 SPI_SCK
#define D5 5 // P0.30 SPI_MISO
#define D6 6 // P0.28 SPI_MOSI
#define D7 7 // P1.13 RF_VC1 (TXEN)
#define D8 8 // P1.07 RF_VC2 (RXEN)
#define D9 9 // P1.12 GPS module TX → MCU RX
#define D10 10 // P1.11 GPS module RX ← MCU TX
#define D11 11 // P1.10 GPS_EN (active LOW)
#define D12 12 // P0.20 SCREEN_SDA
#define D13 13 // P0.15 SCREEN_SCL
#define D14 14 // P1.08 IMU_SDA
#define D15 15 // P0.11 IMU_SCL
#define D16 16 // P0.05 BATTERY_ADC
#define D17 17 // P0.25 BATTERY_CTL
#define D18 18 // P1.04 TTP223_KEY
#define D19 19 // P0.22 VIBRATION_MOTOR
#define D20 20 // P0.14 RT9080_EN
#define D21 21 // P0.12 FLASH_CS
#define D22 22 // P0.04 FLASH_SCLK
#define D23 23 // P0.06 FLASH_IO0
#define D24 24 // P1.09 FLASH_IO1
#define D25 25 // P0.08 FLASH_IO2
#define D26 26 // P0.26 FLASH_IO3
#define D27 27 // P0.07 ICM20948_INT
#define D28 28 // P0.16 SGM41562_INT
#define D29 29 // P0.24 BOOT
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// LED Configuration (no physical LED)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#define LED_STATE_ON 1
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Button Configuration (TTP223 capacitive touch)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#define PIN_BUTTON_TOUCH D18
#define BUTTON_TOUCH_ACTIVE_LOW true
#define BUTTON_TOUCH_ACTIVE_PULLUP false
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Analog Pin Definitions
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#define PIN_VBAT D16 // P0.05 Battery voltage sense
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// I2C Configuration
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Primary I2C: Display (SSD1315)
#define PIN_WIRE_SDA D12 // P0.20
#define PIN_WIRE_SCL D13 // P0.15
// Secondary I2C: IMU (ICM20948) + PMU (SGM41562)
#define WIRE_INTERFACES_COUNT 2
#define PIN_WIRE1_SDA D14 // P1.08
#define PIN_WIRE1_SCL D15 // P0.11
#define I2C_NO_RESCAN
static const uint8_t SDA = PIN_WIRE_SDA;
static const uint8_t SCL = PIN_WIRE_SCL;
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Display (SSD1315, compatible with SSD1306 driver)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#define HAS_SCREEN 1
#define USE_SSD1306 1
#define OLED_TINY
#define OLED_GEOMETRY_OVERRIDE GEOMETRY_64_32
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// SPI Configuration (SX1262 LoRa)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#define SPI_INTERFACES_COUNT 1
#define PIN_SPI_SCK D4 // P0.03
#define PIN_SPI_MISO D5 // P0.30
#define PIN_SPI_MOSI D6 // P0.28
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// SX1262 LoRa (S62F module)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#define USE_SX1262
#define SX126X_CS D3
#define SX126X_DIO1 D1
#define SX126X_BUSY D2
#define SX126X_RESET D0
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
#define SX126X_TXEN D7 // RF_VC1
#define SX126X_RXEN D8 // RF_VC2
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Power Management
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#define BAT_READ D17 // P0.25 Battery measurement control (HIGH = enable)
#define BATTERY_PIN PIN_VBAT
#define BATTERY_SENSE_RESOLUTION_BITS 12
#define ADC_MULTIPLIER 2.0
#define AREF_VOLTAGE 3.6
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// GPS (u-blox MIA-M10Q)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#define GPS_UBLOX
#define HAS_GPS 1
#define GPS_RX_PIN D9 // P1.12 — MCU RX, wired to GPS module TX
#define GPS_TX_PIN D10 // P1.11 — MCU TX, wired to GPS module RX
#define PIN_GPS_EN D11 // P1.10
#define GPS_EN_ACTIVE LOW
#define GPS_BAUDRATE 38400
#define GPS_THREAD_INTERVAL 50
#define PIN_SERIAL1_TX GPS_TX_PIN
#define PIN_SERIAL1_RX GPS_RX_PIN
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// On-board QSPI Flash (ZD25WQ32CEIGR)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#define PIN_QSPI_SCK D22 // P0.04
#define PIN_QSPI_CS D21 // P0.12
#define PIN_QSPI_IO0 D23 // P0.06
#define PIN_QSPI_IO1 D24 // P1.09
#define PIN_QSPI_IO2 D25 // P0.08
#define PIN_QSPI_IO3 D26 // P0.26
#define EXTERNAL_FLASH_DEVICES W25Q32JV_IQ
#define EXTERNAL_FLASH_USE_QSPI
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Vibration Motor (GPIO active-high)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#define LED_NOTIFICATION D19 // P0.22
#define HAPTIC_FEEDBACK_PIN LED_NOTIFICATION
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// IMU (ICM20948 on Wire1)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#define HAS_ICM20948
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Charger (SGM41562 on Wire1 @ 0x03)
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#define HAS_SGM41562
#define SGM41562_WIRE Wire1
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Compatibility Definitions
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#ifdef __cplusplus
extern "C" {
#endif
#define PIN_SERIAL2_RX (-1)
#define PIN_SERIAL2_TX (-1)
#ifdef __cplusplus
}
#endif
#endif // _T_IMPULSE_PLUS_H_