mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-10-23 07:32:27 +00:00
Add saved LED controls across supported boards
Add get leds and set leds on|off for Companion, repeater, room server and sensor builds. Preserve the setting across reboots and cover GPIO, RGB, Bluetooth, notification and expander indicators without changing radio amplifier controls or screen backlights. RAK Companions blink while advertising and keep the Bluetooth LED off once connected, including late timer callbacks. Adapted from meshcore-dev/MeshCore#3605 and #2694. Validated with host regressions and firmware builds for SenseCAP Solar, RAK4631, T096, Heltec V4, T-Beam, Xiao S3, Pico W and Wio E5 Mini. Co-authored-by: R0ck <develop@hectospark.co.uk> Co-authored-by: Nick Dunklee <nick@dunklee.org>
This commit is contained in:
1 parent
c48e679284
commit
9d52b1fd68
91 files changed
+1485
-65
No files matched your search
@@ -1958,6 +1958,33 @@ the verified pin assignments, startup fixes, and saved amplifier settings.
|
||||
|
||||
---
|
||||
|
||||
#### Turn indicator LEDs on or off
|
||||
|
||||
```text
|
||||
get leds
|
||||
set leds off
|
||||
set leds on
|
||||
```
|
||||
|
||||
Available in Companion, repeater, room-server, and sensor firmware. The setting
|
||||
is saved across reboots and defaults to `on`. `off` suppresses software-controlled
|
||||
transmit, Bluetooth, status, and notification LEDs. `on` restores their normal
|
||||
behavior; it does not leave every LED lit. A build with `DISABLE_BLE_LED` keeps
|
||||
the Bluetooth indicator disabled.
|
||||
|
||||
Supported outputs include ordinary and inverted GPIO LEDs, addressable RGB LEDs,
|
||||
and board-controlled expander LEDs on nRF52, ESP32, RP2040, and STM32 boards.
|
||||
Screen backlights and manually controlled torches keep their own controls.
|
||||
Power and charging LEDs wired directly to a charger cannot be switched off by
|
||||
firmware. Bootloader and early startup indicators may still appear before the
|
||||
saved setting is loaded.
|
||||
|
||||
This extends the SenseCAP Solar LED switch from
|
||||
[upstream PR #3605](https://github.com/meshcore-dev/MeshCore/pull/3605).
|
||||
With LEDs enabled, RAK-family Companions blink their Bluetooth LED while
|
||||
advertising and leave it off once connected, following
|
||||
[upstream PR #2694](https://github.com/meshcore-dev/MeshCore/pull/2694).
|
||||
|
||||
#### Turn WiFi, ESP-NOW, or all 2.4 GHz services on or off
|
||||
|
||||
```text
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <helpers/RegulatorSettings.h>
|
||||
#include <helpers/LedSettings.h>
|
||||
#include <helpers/ui/DisplayPowerSettings.h>
|
||||
#include <helpers/BatteryChargeCLI.h>
|
||||
#include <helpers/MorseCLI.h>
|
||||
@@ -10725,6 +10726,7 @@ bool MyMesh::handleCommand(const char* command, uint32_t sender_timestamp,
|
||||
}
|
||||
|
||||
if (mesh::power::handleRegulatorCommand(command, reply, reply_capacity)) return true;
|
||||
if (mesh::leds::handleCommand(command, reply, reply_capacity)) return true;
|
||||
|
||||
#if COMPANION_FEATURE_READER
|
||||
if (sender_timestamp == 0 && mesh::morseTimingActive(_radio)
|
||||
|
||||
@@ -20,6 +20,7 @@ void MyMesh::pulse(mesh::notify::Output output,bool on,int8_t pin) {
|
||||
} else if(output==mesh::notify::Vibration) {
|
||||
if(_ui)_ui->notificationVibration(on);
|
||||
} else if(output==mesh::notify::Led) {
|
||||
on = on && mesh::leds::enabled();
|
||||
#if defined(PIN_STATUS_LED_R) && defined(PIN_STATUS_LED_G) && defined(PIN_STATUS_LED_B)
|
||||
analogWrite(PIN_STATUS_LED_R,on?255:0);
|
||||
analogWrite(PIN_STATUS_LED_G,on?90:0);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <helpers/RegulatorSettings.h>
|
||||
#include <helpers/LedSettings.h>
|
||||
#include <Arduino.h> // needed for PlatformIO
|
||||
#include <helpers/ui/StartupScreen.h>
|
||||
#include <helpers/ui/DisplayPowerSettings.h>
|
||||
@@ -3634,6 +3635,7 @@ void setup() {
|
||||
if (!mesh::storage::internalPrimaryFilesystemReady(primary_fs_boot)
|
||||
|| store.isVolatilePrimaryFS()) regulator_prefs_fs = nullptr;
|
||||
#endif
|
||||
mesh::leds::beginSettings(board, regulator_prefs_fs);
|
||||
if (!mesh::power::beginRegulatorSettings(board, radio_driver, regulator_prefs_fs)) {
|
||||
MESH_DEBUG_PRINTLN("Regulator selection could not be applied");
|
||||
}
|
||||
|
||||
@@ -2105,6 +2105,7 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text,
|
||||
}
|
||||
|
||||
void UITask::userLedHandler() {
|
||||
if (!mesh::leds::enabled()) return;
|
||||
if (_notification_led_owned || !(_notification_outputs & mesh::notify::Led)) return;
|
||||
#ifdef PIN_STATUS_LED
|
||||
const uint32_t cur_time = millis();
|
||||
|
||||
@@ -451,6 +451,7 @@ void UITask::renderCurrScreen() {
|
||||
|
||||
#ifdef STATUS_LED_RGB
|
||||
static void statusLedWrite(uint8_t r, uint8_t g, uint8_t b) {
|
||||
if (!mesh::leds::enabled()) r = g = b = 0;
|
||||
analogWrite(PIN_STATUS_LED_R, r);
|
||||
analogWrite(PIN_STATUS_LED_G, g);
|
||||
analogWrite(PIN_STATUS_LED_B, b);
|
||||
@@ -470,6 +471,7 @@ static void statusLedWheel(uint8_t pos) { // smooth hue sweep, 0..255
|
||||
#endif
|
||||
|
||||
void UITask::userLedHandler() {
|
||||
if (!mesh::leds::enabled()) return;
|
||||
if (_notification_led_owned || !(_notification_outputs & mesh::notify::Led)) return;
|
||||
#ifdef STATUS_LED_RGB
|
||||
enum class FlourishState : uint8_t { Unseen, Boot, Message, Idle };
|
||||
|
||||
@@ -751,6 +751,7 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text,
|
||||
}
|
||||
|
||||
void UITask::userLedHandler() {
|
||||
if (!mesh::leds::enabled()) return;
|
||||
if (_notification_led_owned || !(_notification_outputs & mesh::notify::Led)) return;
|
||||
#ifdef PIN_STATUS_LED
|
||||
const uint32_t cur_time = millis();
|
||||
|
||||
@@ -981,7 +981,7 @@ void UITask::loop() {
|
||||
|
||||
if (_powering_off_at > 0) { // power off timer armed
|
||||
#if defined(LED_PIN) && defined(LED_STATE_ON)
|
||||
digitalWrite(LED_PIN, LED_STATE_ON); // switch on the led until poweroff
|
||||
if (mesh::leds::enabled()) digitalWrite(LED_PIN, LED_STATE_ON);
|
||||
delay(1000);
|
||||
digitalWrite(LED_PIN, !LED_STATE_ON); // off LED
|
||||
#endif
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <helpers/RegulatorSettings.h>
|
||||
#include <helpers/LedSettings.h>
|
||||
#include <Arduino.h> // needed for PlatformIO
|
||||
#include <Mesh.h>
|
||||
#include <helpers/IdentityGeneration.h>
|
||||
@@ -208,6 +209,7 @@ void setup() {
|
||||
#if defined(NRF52_PLATFORM)
|
||||
if (volatile_primary_fs) regulator_prefs_fs = nullptr;
|
||||
#endif
|
||||
mesh::leds::beginSettings(board, regulator_prefs_fs);
|
||||
if (!mesh::power::beginRegulatorSettings(board, radio_driver, regulator_prefs_fs)) {
|
||||
MESH_DEBUG_PRINTLN("Regulator selection could not be applied");
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <helpers/RegulatorSettings.h>
|
||||
#include <helpers/LedSettings.h>
|
||||
#include <Arduino.h> // needed for PlatformIO
|
||||
#include <Mesh.h>
|
||||
#include <helpers/IdentityGeneration.h>
|
||||
@@ -142,6 +143,7 @@ void setup() {
|
||||
#if defined(NRF52_PLATFORM)
|
||||
if (volatile_primary_fs) regulator_prefs_fs = nullptr;
|
||||
#endif
|
||||
mesh::leds::beginSettings(board, regulator_prefs_fs);
|
||||
if (!mesh::power::beginRegulatorSettings(board, radio_driver, regulator_prefs_fs)) {
|
||||
MESH_DEBUG_PRINTLN("Regulator selection could not be applied");
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <helpers/RegulatorSettings.h>
|
||||
#include <helpers/LedSettings.h>
|
||||
#include "SensorMesh.h"
|
||||
#include <math.h>
|
||||
#include <helpers/IdentityGeneration.h>
|
||||
@@ -167,6 +168,7 @@ void setup() {
|
||||
#if defined(NRF52_PLATFORM)
|
||||
if (volatile_primary_fs) regulator_prefs_fs = nullptr;
|
||||
#endif
|
||||
mesh::leds::beginSettings(board, regulator_prefs_fs);
|
||||
if (!mesh::power::beginRegulatorSettings(board, radio_driver, regulator_prefs_fs)) {
|
||||
MESH_DEBUG_PRINTLN("Regulator selection could not be applied");
|
||||
}
|
||||
|
||||
@@ -155,6 +155,49 @@ def patched_trace_source(source):
|
||||
''')
|
||||
|
||||
|
||||
def patched_led_source(source):
|
||||
"""A queued advertising blink must not relight a disabled indicator."""
|
||||
source = source.replace("\r\n", "\n")
|
||||
old = """ (void) xTimer;
|
||||
digitalToggle(LED_BLUE);
|
||||
"""
|
||||
new = """ (void) xTimer;
|
||||
// MeshCore saved LED policy also covers already queued timer callbacks.
|
||||
if (LED_BLUE < 0 || (uint32_t)LED_BLUE >= PINS_COUNT) return;
|
||||
if (digitalPinToPinName(LED_BLUE) == 0xff) return;
|
||||
#ifdef DISABLE_BLE_LED
|
||||
const bool enabled = false;
|
||||
#elif defined(RAK_BOARD) && defined(BLE_PIN_CODE)
|
||||
const bool enabled = mesh::leds::enabled() && !Bluefruit.connected();
|
||||
#else
|
||||
const bool enabled = mesh::leds::enabled();
|
||||
#endif
|
||||
if (enabled) digitalToggle(LED_BLUE);
|
||||
else digitalWrite(LED_BLUE, !LED_STATE_ON);
|
||||
"""
|
||||
include = '#include "bluefruit.h"\n'
|
||||
policy_include = '#include <helpers/LedPolicy.h>\n'
|
||||
old_connected = " digitalWrite(LED_BLUE, on_off ? LED_STATE_ON : (1-LED_STATE_ON) );"
|
||||
new_connected = """ // RAK Companions blink while advertising, then go dark when connected.
|
||||
#if defined(DISABLE_BLE_LED) || (defined(RAK_BOARD) && defined(BLE_PIN_CODE))
|
||||
on_off = false;
|
||||
#else
|
||||
on_off = on_off && mesh::leds::enabled();
|
||||
#endif
|
||||
if (LED_BLUE >= 0 && (uint32_t)LED_BLUE < PINS_COUNT
|
||||
&& digitalPinToPinName(LED_BLUE) != 0xff)
|
||||
digitalWrite(LED_BLUE, on_off ? LED_STATE_ON : (1-LED_STATE_ON));"""
|
||||
if (new in source and new_connected in source
|
||||
and policy_include in source and old not in source
|
||||
and old_connected not in source):
|
||||
return source
|
||||
if (source.count(old) != 1 or source.count(include) != 1
|
||||
or source.count(old_connected) != 1):
|
||||
raise RuntimeError("nRF52 BLE LED policy: unrecognized timer callback")
|
||||
return source.replace(old, new).replace(old_connected, new_connected).replace(
|
||||
include, include + policy_include)
|
||||
|
||||
|
||||
def replace_framework_source(build_env, node):
|
||||
source = Path(node.srcnode().get_abspath())
|
||||
if source.name not in ("BLEGatt.cpp", "BLEConnection.cpp", "bluefruit.cpp") or source.parent.name != "src":
|
||||
@@ -166,6 +209,8 @@ def replace_framework_source(build_env, node):
|
||||
patched = patched_connection_source(original)
|
||||
else:
|
||||
patched = patched_bluefruit_source(original)
|
||||
patched = patched_led_source(patched)
|
||||
build_env.AppendUnique(CPPPATH=[build_env.subst("$PROJECT_DIR/src")])
|
||||
if "MESH_NRF52_BLE_TRACE" in str(build_env.get("CPPDEFINES", [])):
|
||||
patched = patched_trace_source(patched)
|
||||
build_env.AppendUnique(CPPPATH=[build_env.subst("$PROJECT_DIR/src")])
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "helpers/BatteryChargeSupport.h"
|
||||
#include "helpers/RegulatorMode.h"
|
||||
#include "helpers/PowerSavingDiagnostics.h"
|
||||
#include "helpers/LedPolicy.h"
|
||||
#if defined(ARDUINO) && (defined(RP2040_PLATFORM) || defined(STM32_PLATFORM))
|
||||
#include <Arduino.h>
|
||||
#endif
|
||||
@@ -145,6 +146,8 @@ public:
|
||||
virtual const char* getManufacturerName() const = 0;
|
||||
virtual void onBeforeTransmit() { }
|
||||
virtual void onAfterTransmit() { }
|
||||
// Update only indicator outputs; transmit hooks may also control RF hardware.
|
||||
virtual void applyLedPolicy() { }
|
||||
virtual void reboot() = 0;
|
||||
MESH_REGULATOR_VIRTUAL uint8_t mcuRegulatorModes() const { return 0; }
|
||||
MESH_REGULATOR_VIRTUAL power::RegulatorMode defaultMcuRegulatorMode() const {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <helpers/RegulatorSettings.h>
|
||||
#include <helpers/LedSettings.h>
|
||||
#include <Arduino.h>
|
||||
#include "CarrierTools.h"
|
||||
#include "PacketFileLoggingConfig.h"
|
||||
@@ -2980,6 +2981,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re
|
||||
if (mesh::power::handleBatteryChargeCommand(*_board, command, reply, 160)) return;
|
||||
#endif
|
||||
if (mesh::power::handleRegulatorCommand(command, reply, 157)) return;
|
||||
if (mesh::leds::handleCommand(command, reply, 157)) return;
|
||||
if (mesh::handleUsbLoggingWatchdogCommand(command, reply, 160)) return;
|
||||
if (mesh::powerdiag::handleCommand(command, reply, 157, millis())) return;
|
||||
if (handleManagementCommand(command, reply)) return;
|
||||
|
||||
@@ -271,9 +271,18 @@ public:
|
||||
uint8_t getStartupReason() const override { return startup_reason; }
|
||||
bool isUserGpioAvailable(uint8_t pin) const override;
|
||||
|
||||
void applyLedPolicy() override {
|
||||
if (mesh::leds::enabled()) return;
|
||||
#if defined(P_LORA_TX_LED)
|
||||
digitalWrite(P_LORA_TX_LED, LOW);
|
||||
#elif defined(P_LORA_TX_NEOPIXEL_LED)
|
||||
neopixelWrite(P_LORA_TX_NEOPIXEL_LED, 0, 0, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(P_LORA_TX_LED)
|
||||
void onBeforeTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
if (mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
}
|
||||
void onAfterTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off
|
||||
@@ -282,7 +291,9 @@ public:
|
||||
#define NEOPIXEL_BRIGHTNESS 64 // white brightness (max 255)
|
||||
|
||||
void onBeforeTransmit() override {
|
||||
neopixelWrite(P_LORA_TX_NEOPIXEL_LED, NEOPIXEL_BRIGHTNESS, NEOPIXEL_BRIGHTNESS, NEOPIXEL_BRIGHTNESS); // turn TX neopixel on (White)
|
||||
if (mesh::leds::enabled()) {
|
||||
neopixelWrite(P_LORA_TX_NEOPIXEL_LED, NEOPIXEL_BRIGHTNESS, NEOPIXEL_BRIGHTNESS, NEOPIXEL_BRIGHTNESS); // turn TX neopixel on (White)
|
||||
}
|
||||
}
|
||||
void onAfterTransmit() override {
|
||||
neopixelWrite(P_LORA_TX_NEOPIXEL_LED, 0, 0, 0); // turn TX neopixel off
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
#include "LedPolicy.h"
|
||||
#include <Arduino.h>
|
||||
#include <target.h>
|
||||
|
||||
namespace mesh { namespace leds {
|
||||
|
||||
void applyStatusPolicy() {
|
||||
if (enabled()) return;
|
||||
#if defined(PIN_STATUS_LED_R) && defined(PIN_STATUS_LED_G) && defined(PIN_STATUS_LED_B)
|
||||
analogWrite(PIN_STATUS_LED_R, 0);
|
||||
analogWrite(PIN_STATUS_LED_G, 0);
|
||||
analogWrite(PIN_STATUS_LED_B, 0);
|
||||
#elif defined(PIN_STATUS_LED)
|
||||
if (PIN_STATUS_LED < 0) return;
|
||||
#ifdef LED_STATE_ON
|
||||
digitalWrite(PIN_STATUS_LED, !LED_STATE_ON);
|
||||
#else
|
||||
digitalWrite(PIN_STATUS_LED, LOW);
|
||||
#endif
|
||||
pinMode(PIN_STATUS_LED, OUTPUT);
|
||||
#endif
|
||||
}
|
||||
|
||||
}} // namespace mesh::leds
|
||||
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace mesh { namespace leds {
|
||||
|
||||
inline std::atomic<bool>& policy() {
|
||||
static std::atomic<bool> value{true};
|
||||
return value;
|
||||
}
|
||||
|
||||
inline bool enabled() { return policy().load(std::memory_order_relaxed); }
|
||||
inline void setEnabled(bool value) { policy().store(value, std::memory_order_relaxed); }
|
||||
void applyStatusPolicy();
|
||||
|
||||
}} // namespace mesh::leds
|
||||
@@ -0,0 +1,107 @@
|
||||
#include "LedSettings.h"
|
||||
#include <MeshCore.h>
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
#include <helpers/AtomicFileWriter.h>
|
||||
#else
|
||||
#include <helpers/ContactFileTransaction.h>
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace mesh { namespace leds {
|
||||
namespace {
|
||||
MainBoard* main_board = nullptr;
|
||||
FILESYSTEM* settings_fs = nullptr;
|
||||
const char* const path = "/leds_v1";
|
||||
const char* const backup = "/leds_v1.bak";
|
||||
|
||||
void encode(bool on, uint8_t (&data)[8]) {
|
||||
const uint8_t value = on ? 1 : 0;
|
||||
const uint8_t image[] = {'L', 'E', 'D', 1, value,
|
||||
static_cast<uint8_t>(value ^ 0xff), 0, 0};
|
||||
memcpy(data, image, sizeof(data));
|
||||
}
|
||||
|
||||
bool readSetting(const char* name, bool& on) {
|
||||
if (!settings_fs) return false;
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
File file(*settings_fs);
|
||||
if (!file.open(name, FILE_O_READ)) return false;
|
||||
#else
|
||||
File file = settings_fs->open(name, "r");
|
||||
if (!file) return false;
|
||||
#endif
|
||||
uint8_t data[8], expected[8];
|
||||
const bool complete = file.size() == sizeof(data)
|
||||
&& file.read(data, sizeof(data)) == sizeof(data);
|
||||
file.close();
|
||||
if (!complete || data[4] > 1) return false;
|
||||
encode(data[4] != 0, expected);
|
||||
if (memcmp(data, expected, sizeof(data)) != 0) return false;
|
||||
on = data[4] != 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool saveSetting(bool on) {
|
||||
if (!settings_fs) return false;
|
||||
uint8_t data[8];
|
||||
encode(on, data);
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
AtomicFileWriter file(settings_fs, path);
|
||||
#else
|
||||
ContactFileTransaction file(settings_fs, path);
|
||||
#endif
|
||||
if (!file || file.write(data, sizeof(data)) != sizeof(data) || !file.commit())
|
||||
return false;
|
||||
bool checked = !on;
|
||||
return readSetting(path, checked) && checked == on;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void beginSettings(MainBoard& board, FILESYSTEM* fs) {
|
||||
main_board = &board;
|
||||
settings_fs = fs;
|
||||
bool on = true;
|
||||
if (!readSetting(path, on) && readSetting(backup, on)) {
|
||||
// Recover the rename gap without discarding the last complete preference.
|
||||
if ((!fs->exists(path) || fs->remove(path)) && fs->rename(backup, path)) {
|
||||
bool checked = !on;
|
||||
if (!readSetting(path, checked) || checked != on) settings_fs = nullptr;
|
||||
} else {
|
||||
settings_fs = nullptr;
|
||||
}
|
||||
}
|
||||
setEnabled(on);
|
||||
board.applyLedPolicy();
|
||||
applyStatusPolicy();
|
||||
}
|
||||
|
||||
bool handleCommand(const char* command, char* reply, size_t capacity) {
|
||||
if (!command || !reply || capacity == 0) return false;
|
||||
const bool get = strncmp(command, "get ", 4) == 0;
|
||||
const bool set = strncmp(command, "set ", 4) == 0;
|
||||
if ((!get && !set) || strncmp(command + 4, "leds", 4) != 0) return false;
|
||||
const char* value = command + 8;
|
||||
if (*value && *value != ' ' && *value != '\t') return false;
|
||||
if (get) {
|
||||
if (*value) snprintf(reply, capacity, "Error: usage get leds");
|
||||
else snprintf(reply, capacity, "> %s", enabled() ? "on" : "off");
|
||||
return true;
|
||||
}
|
||||
while (*value == ' ' || *value == '\t') ++value;
|
||||
if (strcmp(value, "on") != 0 && strcmp(value, "off") != 0) {
|
||||
snprintf(reply, capacity, "Error: usage set leds on|off");
|
||||
return true;
|
||||
}
|
||||
const bool on = strcmp(value, "on") == 0;
|
||||
if (!main_board || !saveSetting(on)) {
|
||||
snprintf(reply, capacity, "Error: LED setting save unconfirmed; reboot may differ");
|
||||
return true;
|
||||
}
|
||||
setEnabled(on);
|
||||
main_board->applyLedPolicy();
|
||||
applyStatusPolicy();
|
||||
snprintf(reply, capacity, "OK");
|
||||
return true;
|
||||
}
|
||||
} } // namespace mesh::leds
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <helpers/IdentityStore.h>
|
||||
#include <stddef.h>
|
||||
|
||||
namespace mesh {
|
||||
class MainBoard;
|
||||
namespace leds {
|
||||
|
||||
void beginSettings(MainBoard& board, FILESYSTEM* fs);
|
||||
bool handleCommand(const char* command, char* reply, size_t capacity);
|
||||
|
||||
} // namespace leds
|
||||
} // namespace mesh
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "nrf52/SoftDeviceState.h"
|
||||
#include "nrf52/SleepCycleState.h"
|
||||
#include "nrf52/ShutdownLeds.h"
|
||||
#include "nrf52/BluetoothLed.h"
|
||||
#include <target.h>
|
||||
#include "UserGpioPinPolicy.h"
|
||||
#include "PowerSavingDiagnostics.h"
|
||||
@@ -263,6 +264,10 @@ bool NRF52Board::applyMcuRegulator(mesh::power::RegulatorMode mode) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void NRF52Board::applyLedPolicy() {
|
||||
mesh_nrf52::applyBluetoothLedPolicy();
|
||||
}
|
||||
|
||||
void NRF52Board::loop() {
|
||||
#ifdef NRF52_POWER_MANAGEMENT
|
||||
if (!mesh::power::voltagePolicySupported()) return;
|
||||
@@ -959,10 +964,12 @@ bool NRF52Board::startOTAUpdate(const char *id, char reply[], bool force_ap) {
|
||||
// Even a failed begin can leave SoftDevice enabled partway through SDK
|
||||
// startup. Require a fresh authoritative query before the next sleep.
|
||||
mesh_nrf52::sleepCycleState().invalidate();
|
||||
mesh_nrf52::applyBluetoothLedPolicy();
|
||||
if (!Bluefruit.begin(1, 0)) {
|
||||
return false;
|
||||
}
|
||||
ota_ble_started = true;
|
||||
mesh_nrf52::bluetoothLedStarted();
|
||||
|
||||
// To be consistent OTA DFU should be added first if it exists
|
||||
bledfu.begin();
|
||||
|
||||
@@ -61,6 +61,7 @@ public:
|
||||
{}
|
||||
virtual void begin();
|
||||
void loop() override;
|
||||
void applyLedPolicy() override;
|
||||
// Feed the hardware main-loop watchdog. The first call starts it, so normal
|
||||
// setup (including filesystem mount and radio initialization) is not timed.
|
||||
// A loop blocked forever in SoftDevice-backed flash I/O will stop feeding it
|
||||
|
||||
@@ -139,9 +139,23 @@ public:
|
||||
return charge_target_restore_failed;
|
||||
}
|
||||
|
||||
void applyLedPolicy() override {
|
||||
#ifndef TBEAM_SUPREME_SX1262
|
||||
if (!mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, HIGH);
|
||||
#else
|
||||
ESP32Board::applyLedPolicy();
|
||||
#endif
|
||||
#if !defined(PORTABLE_MQTT_OBSERVER) || defined(TBEAM_SUPREME_SX1262)
|
||||
if (charge_pmu_ready && PMU) {
|
||||
PMU->setChargingLedMode(mesh::leds::enabled()
|
||||
? XPOWERS_CHG_LED_CTRL_CHG : XPOWERS_CHG_LED_OFF);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef TBEAM_SUPREME_SX1262
|
||||
void onBeforeTransmit() override{
|
||||
digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED on - invert pin for SX1276
|
||||
if (mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED on - invert pin for SX1276
|
||||
}
|
||||
void onAfterTransmit() override{
|
||||
digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED off - invert pin for SX1276
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include "../LedPolicy.h"
|
||||
#include "ShutdownLeds.h"
|
||||
#include <bluefruit.h>
|
||||
|
||||
namespace mesh_nrf52 {
|
||||
|
||||
inline bool& bluetoothLedReady() {
|
||||
static bool ready = false;
|
||||
return ready;
|
||||
}
|
||||
|
||||
inline void applyBluetoothLedPolicy() {
|
||||
#ifdef LED_BLUE
|
||||
if (LED_BLUE < 0 || static_cast<uint32_t>(LED_BLUE) >= PINS_COUNT
|
||||
|| digitalPinToPinName(LED_BLUE) == 0xff) {
|
||||
Bluefruit.autoConnLed(false);
|
||||
return;
|
||||
}
|
||||
#else
|
||||
Bluefruit.autoConnLed(false);
|
||||
return;
|
||||
#endif
|
||||
#ifdef DISABLE_BLE_LED
|
||||
const bool enable = false;
|
||||
#else
|
||||
const bool enable = mesh::leds::enabled();
|
||||
#endif
|
||||
Bluefruit.autoConnLed(enable);
|
||||
if (!enable) {
|
||||
#ifdef LED_BLUE
|
||||
turnOffShutdownLed(LED_BLUE);
|
||||
#endif
|
||||
} else if (bluetoothLedReady()) {
|
||||
#if defined(RAK_BOARD) && defined(BLE_PIN_CODE)
|
||||
Bluefruit._setConnLed(false);
|
||||
#else
|
||||
Bluefruit._setConnLed(Bluefruit.connected() != 0);
|
||||
#endif
|
||||
if (Bluefruit.Advertising.isRunning()) Bluefruit._startConnLed();
|
||||
}
|
||||
}
|
||||
|
||||
inline void bluetoothLedStarted() {
|
||||
bluetoothLedReady() = true;
|
||||
applyBluetoothLedPolicy();
|
||||
}
|
||||
|
||||
} // namespace mesh_nrf52
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "SoftDeviceState.h"
|
||||
#include "SleepCycleState.h"
|
||||
#include "BleDebugTrace.h"
|
||||
#include "BluetoothLed.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "ble_gap.h"
|
||||
@@ -836,11 +837,7 @@ bool SerialBLEInterface::begin(const char* prefix, const char* name,
|
||||
char charpin[20];
|
||||
snprintf(charpin, sizeof(charpin), "%lu", (unsigned long)pin_code);
|
||||
|
||||
#ifdef DISABLE_BLE_LED
|
||||
// Opt out of Bluefruit's automatic LED control without changing the normal
|
||||
// board/library default or the application's own LED handling.
|
||||
Bluefruit.autoConnLed(false);
|
||||
#endif
|
||||
mesh_nrf52::applyBluetoothLedPolicy();
|
||||
// Configure individual connection values rather than the all-or-nothing
|
||||
// Bluefruit preset. Defaults are identical to BANDWIDTH_MAX; constrained
|
||||
// board profiles can lower event/buffer reservations without shrinking the
|
||||
@@ -875,6 +872,7 @@ bool SerialBLEInterface::begin(const char* prefix, const char* name,
|
||||
return false;
|
||||
}
|
||||
|
||||
mesh_nrf52::bluetoothLedStarted();
|
||||
if (clear_bonds) Bluefruit.Periph.clearBonds();
|
||||
|
||||
if (custom_address != nullptr) {
|
||||
|
||||
@@ -36,8 +36,12 @@ public:
|
||||
}
|
||||
|
||||
#if defined(P_LORA_TX_LED)
|
||||
void applyLedPolicy() override {
|
||||
if (!mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, HIGH);
|
||||
}
|
||||
|
||||
void onBeforeTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED on
|
||||
if (mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED on
|
||||
}
|
||||
void onAfterTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED off
|
||||
|
||||
+19
@@ -208,6 +208,25 @@ int main() {
|
||||
checkGuard(node, false);
|
||||
});
|
||||
|
||||
run("board-led-policy-overrides-alerts-and-heartbeat", [] {
|
||||
MyMesh node;
|
||||
mesh::leds::setEnabled(false);
|
||||
command(node, "set notify.led all 50,50");
|
||||
command(node, "notify.test all", "OK - alert test started");
|
||||
expectLed(false);
|
||||
checkGuard(node, false);
|
||||
seedUiRgb();
|
||||
expectLed(false);
|
||||
node._notifications.loop(1050, false);
|
||||
node._notifications.loop(1100, false);
|
||||
mesh::leds::setEnabled(true);
|
||||
node.syncNotifications();
|
||||
checkGuard(node, true);
|
||||
node.pulse(mesh::notify::Led, true, -1);
|
||||
expectLed(true);
|
||||
node.pulse(mesh::notify::Led, false, -1);
|
||||
});
|
||||
|
||||
run("disabled-feature-restores-ui-policy", [] {
|
||||
MyMesh node;
|
||||
startAlert(node);
|
||||
|
||||
+1
@@ -31,6 +31,7 @@
|
||||
#define pdMS_TO_TICKS(ms) (ms)
|
||||
|
||||
#include <helpers/nrf52/ShutdownLeds.h>
|
||||
#include <helpers/LedPolicy.h>
|
||||
|
||||
std::vector<Event> events;
|
||||
int levels[PINS_COUNT];
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
"""Exercise production board LED callbacks without touching hardware."""
|
||||
from pathlib import Path
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def body(path, name, index=0):
|
||||
source = (ROOT / path).read_text()
|
||||
matches = list(re.finditer(
|
||||
rf"\bvoid\s+(?:\w+::)?{name}\([^)]*\)(?:\s+override)?\s*\{{", source))
|
||||
start = matches[index].end()
|
||||
end, depth = start, 1
|
||||
while depth:
|
||||
depth += (source[end] == "{") - (source[end] == "}")
|
||||
end += 1
|
||||
return source[start:end - 1]
|
||||
|
||||
|
||||
STUBS = r"""
|
||||
#include <cassert>
|
||||
#include <helpers/LedPolicy.h>
|
||||
#define HIGH 1
|
||||
#define LOW 0
|
||||
#define P_LORA_TX_LED 1
|
||||
#define LED_PIN 1
|
||||
#define LED_BUILTIN 1
|
||||
#define LED_RED 1
|
||||
#define EXP_PIN_LED 1
|
||||
#define EXP_PIN_USER_LED 1
|
||||
#define LED_STATE_ON LOW
|
||||
int levels[8], tx, rx, fan_calls, fan_state;
|
||||
void digitalWrite(int pin, int value) { levels[pin] = value; }
|
||||
struct Expander { void digitalWrite(int p, int v) { ::digitalWrite(p, v); } } expander;
|
||||
void expWritePin(int p, int v) { digitalWrite(p, v); }
|
||||
struct NRF52Board { static void applyLedPolicy() {} };
|
||||
struct Fem {
|
||||
void setTxModeEnable() { ++tx; }
|
||||
void setRxModeEnable() { ++rx; }
|
||||
} loRaFEMControl;
|
||||
void reset() {
|
||||
for (auto &v : levels) v = -1;
|
||||
tx = rx = fan_calls = fan_state = 0;
|
||||
mesh::leds::setEnabled(false);
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
class BoardLedPolicyTests(unittest.TestCase):
|
||||
def run_cpp(self, source):
|
||||
compiler = os.environ.get("CXX") or shutil.which("g++") or shutil.which("clang++")
|
||||
if not compiler:
|
||||
self.skipTest("C++ compiler unavailable")
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
directory = Path(directory)
|
||||
cpp = directory / "led_policy.cpp"
|
||||
exe = directory / ("led_policy.exe" if os.name == "nt" else "led_policy")
|
||||
cpp.write_text(source)
|
||||
result = subprocess.run(
|
||||
[compiler, "-std=c++17", "-I", str(ROOT / "src"), str(cpp), "-o", str(exe)],
|
||||
capture_output=True, text=True)
|
||||
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
||||
result = subprocess.run([str(exe)], capture_output=True, text=True)
|
||||
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
||||
|
||||
def test_gpio_polarity_and_fem_callbacks(self):
|
||||
# Exercise active-low, active-high, LED_STATE_ON and expander boards.
|
||||
cases = [
|
||||
("variants/lilygo_tdeck/TDeckBoard.h", 0, False, None),
|
||||
("variants/thinknode_m7/ThinkNodeM7Board.h", 0, False, None),
|
||||
("variants/thinknode_m5/ThinknodeM5Board.h", 1, False, None),
|
||||
("variants/xiao_nrf52/XiaoNrf52Board.h", 0, False, None),
|
||||
("variants/rak11310/RAK11310Board.h", 1, False, None),
|
||||
("variants/xiao_rp2040/XiaoRP2040Board.h", 1, False, None),
|
||||
("variants/rpi_picow/PicoWBoard.h", 1, False, None),
|
||||
("variants/sensecap_solar/SenseCapSolarBoard.h", 1, False, None),
|
||||
("variants/heltec_t096/T096Board.cpp", 1, True, None),
|
||||
("variants/heltec_tower_v2/HeltecTowerV2Board.cpp", 0, True, None),
|
||||
("variants/heltec_v4/HeltecV4Board.cpp", 1, True, "src/helpers/ESP32Board.h"),
|
||||
("variants/heltec_v4_r8/HeltecV4R8Board.cpp", 1, True, "src/helpers/ESP32Board.h"),
|
||||
("src/helpers/stm32/STM32Board.h", 0, False, None),
|
||||
]
|
||||
source = STUBS
|
||||
for index, (path, on, fem, policy_path) in enumerate(cases):
|
||||
source += f"""
|
||||
namespace test{index} {{
|
||||
void before() {{ {body(path, "onBeforeTransmit")} }}
|
||||
void after() {{ {body(path, "onAfterTransmit")} }}
|
||||
void apply() {{ {body(policy_path or path, "applyLedPolicy")} }}
|
||||
void run() {{
|
||||
reset();
|
||||
levels[1] = {on};
|
||||
apply();
|
||||
assert(levels[1] == {1 - on});
|
||||
assert(tx == 0 && rx == 0);
|
||||
before();
|
||||
assert(levels[1] == {1 - on});
|
||||
assert(tx == {int(fem)} && rx == 0);
|
||||
after();
|
||||
assert(levels[1] == {1 - on});
|
||||
assert(rx == {int(fem)});
|
||||
mesh::leds::setEnabled(true);
|
||||
before();
|
||||
assert(levels[1] == {on});
|
||||
after();
|
||||
assert(levels[1] == {1 - on});
|
||||
}}
|
||||
}}
|
||||
"""
|
||||
source += "int main() {" + "".join(f"test{i}::run();" for i in range(len(cases))) + "}"
|
||||
self.run_cpp(source)
|
||||
|
||||
def test_wio_expander_preserves_gnss_wake_level(self):
|
||||
path = "variants/wio-tracker-l2/WioTrackerL2Board.h"
|
||||
self.run_cpp(STUBS + f"""
|
||||
bool expander_ok = true;
|
||||
void setLed(bool on) {{ {body("variants/wio-tracker-l2/WioTrackerL2Board.cpp", "setLed")} }}
|
||||
void before() {{ {body(path, "onBeforeTransmit")} }}
|
||||
void after() {{ {body(path, "onAfterTransmit")} }}
|
||||
void apply() {{ {body(path, "applyLedPolicy")} }}
|
||||
int main() {{
|
||||
reset();
|
||||
apply(); assert(levels[1] == HIGH);
|
||||
before(); assert(levels[1] == HIGH);
|
||||
after(); assert(levels[1] == HIGH);
|
||||
mesh::leds::setEnabled(true);
|
||||
before(); assert(levels[1] == LOW);
|
||||
after(); assert(levels[1] == HIGH);
|
||||
expander_ok = false; levels[1] = -1;
|
||||
apply(); before(); after(); assert(levels[1] == -1);
|
||||
}}
|
||||
""")
|
||||
|
||||
def test_tbeam_fan_runs_with_leds_disabled(self):
|
||||
path = "variants/lilygo_tbeam_1w/TBeam1WBoard.cpp"
|
||||
self.run_cpp(STUBS + r"""
|
||||
constexpr int FAN_AUTO = 2, FAN_TX_COOLDOWN_MS = 1000;
|
||||
int _fan_mux = 0, _mode = FAN_AUTO;
|
||||
bool _tx_active = false, _stopped = false, _tx_cooldown_active = false;
|
||||
unsigned _tx_until_ms = 0;
|
||||
void portENTER_CRITICAL(int*) {}
|
||||
void portEXIT_CRITICAL(int*) {}
|
||||
unsigned millis() { return 23; }
|
||||
void setFanOutputLocked(bool on) { ++fan_calls; fan_state = on; }
|
||||
""" + f"""
|
||||
void before() {{ {body(path, "onBeforeTransmit")} }}
|
||||
void after() {{ {body(path, "onAfterTransmit")} }}
|
||||
void apply() {{ {body(path, "applyLedPolicy")} }}
|
||||
int main() {{
|
||||
reset(); apply();
|
||||
before();
|
||||
assert(levels[1] == LOW && _tx_active && fan_calls == 1 && fan_state);
|
||||
apply(); assert(_tx_active && fan_calls == 1 && !_tx_cooldown_active);
|
||||
after();
|
||||
assert(!_tx_active && _tx_cooldown_active && _tx_until_ms == 1023);
|
||||
}}
|
||||
""")
|
||||
|
||||
def test_rgb_tx_does_not_change_torch(self):
|
||||
path = "variants/lilygo_techo_card/TechoCardBoard.cpp"
|
||||
self.run_cpp(STUBS + r"""
|
||||
struct Pixel {
|
||||
int value = 0, shows = 0;
|
||||
void setPixelColor(int, int r, int g, int b) { value = r + g + b; }
|
||||
void show() { ++shows; }
|
||||
} Led_A, Led_C;
|
||||
""" + f"""
|
||||
void before() {{ {body(path, "onBeforeTransmit")} }}
|
||||
void after() {{ {body(path, "onAfterTransmit")} }}
|
||||
void apply() {{ {body(path, "applyLedPolicy")} }}
|
||||
int main() {{
|
||||
reset(); Led_A.value = 60; Led_C.value = 765;
|
||||
apply(); before();
|
||||
assert(Led_A.value == 0 && Led_C.value == 765 && Led_C.shows == 0);
|
||||
mesh::leds::setEnabled(true); before(); assert(Led_A.value == 60);
|
||||
after(); assert(Led_A.value == 0 && Led_C.value == 765);
|
||||
}}
|
||||
""")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -272,6 +272,7 @@ struct Radio { bool active = false; };
|
||||
namespace mesh {
|
||||
bool morseTimingActive(const Radio* radio) { return radio && radio->active; }
|
||||
namespace power { bool handleRegulatorCommand(const char*,char*,size_t) { return false; } }
|
||||
namespace leds { bool handleCommand(const char*,char*,size_t) { return false; } }
|
||||
}
|
||||
@PREDICATE@
|
||||
struct MyMesh {
|
||||
|
||||
@@ -30,12 +30,14 @@ inline uint32_t analogReadMilliVolts(int) { return 830; }
|
||||
BASE_BOARD = r'''
|
||||
#pragma once
|
||||
#include <Arduino.h>
|
||||
#include <helpers/LedPolicy.h>
|
||||
class ESP32Board {
|
||||
protected:
|
||||
uint8_t startup_reason = 0;
|
||||
public:
|
||||
void begin() {}
|
||||
virtual ~ESP32Board() = default;
|
||||
virtual void applyLedPolicy() {}
|
||||
virtual void onBeforeTransmit() {}
|
||||
virtual void onAfterTransmit() {}
|
||||
virtual void powerOff() {}
|
||||
|
||||
@@ -0,0 +1,229 @@
|
||||
"""Exercise persisted LED commands on every filesystem backend."""
|
||||
from pathlib import Path
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
from test_regulator_settings import FILESYSTEM
|
||||
from test_serial_wifi_sessions import SANITIZER_FLAGS
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
BOARD = r'''
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
namespace mesh {
|
||||
namespace leds {
|
||||
inline bool& state() { static bool value = true; return value; }
|
||||
inline bool enabled() { return state(); }
|
||||
inline void setEnabled(bool on) { state() = on; }
|
||||
inline void applyStatusPolicy() {}
|
||||
}
|
||||
struct MainBoard {
|
||||
unsigned applies = 0;
|
||||
bool last_applied = true;
|
||||
void applyLedPolicy() { ++applies; last_applied = leds::enabled(); }
|
||||
};
|
||||
}
|
||||
'''
|
||||
|
||||
HARNESS = r'''
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <helpers/IdentityStore.h>
|
||||
#include <MeshCore.h>
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
#define ATOMIC_FILE_WRITER_IMPLEMENTATION
|
||||
#include <helpers/AtomicFileWriter.h>
|
||||
#undef ATOMIC_FILE_WRITER_IMPLEMENTATION
|
||||
#endif
|
||||
#include <helpers/LedSettings.cpp>
|
||||
|
||||
using namespace mesh::leds;
|
||||
static const char* const target = "/leds_v1";
|
||||
static const char* const rollback = "/leds_v1.bak";
|
||||
|
||||
std::string command(const char* text) {
|
||||
char reply[160] = {};
|
||||
assert(handleCommand(text, reply, sizeof(reply)));
|
||||
return reply;
|
||||
}
|
||||
void defaults() {
|
||||
mesh::MainBoard board;
|
||||
MockFS fs;
|
||||
setEnabled(false);
|
||||
beginSettings(board, &fs);
|
||||
assert(enabled() && board.last_applied && board.applies == 1);
|
||||
assert(command("get leds") == "> on");
|
||||
assert(fs.writes == 0);
|
||||
assert(command("set leds off") == "OK");
|
||||
assert(!enabled() && !board.last_applied && board.applies == 2);
|
||||
assert(command("get leds") == "> off");
|
||||
assert(fs.files[target].size() == 8);
|
||||
assert(!fs.exists("/leds_v1.tmp") && !fs.exists(rollback));
|
||||
setEnabled(true);
|
||||
beginSettings(board, &fs);
|
||||
assert(!enabled() && !board.last_applied);
|
||||
assert(command("set leds on") == "OK");
|
||||
beginSettings(board, &fs);
|
||||
assert(enabled() && board.last_applied);
|
||||
beginSettings(board, nullptr);
|
||||
const unsigned applies = board.applies;
|
||||
assert(command("set leds off").find("save unconfirmed") != std::string::npos);
|
||||
assert(enabled() && board.applies == applies);
|
||||
}
|
||||
void parsing() {
|
||||
mesh::MainBoard board;
|
||||
MockFS fs;
|
||||
beginSettings(board, &fs);
|
||||
const char* unrelated[] = {"", "g", "get", "get ", "get led", "get ledsX",
|
||||
"set ledsX off", "set led off", "get\tleds"};
|
||||
for (const char* text : unrelated) {
|
||||
char reply[80] = "untouched";
|
||||
assert(!handleCommand(text, reply, sizeof(reply)));
|
||||
assert(strcmp(reply, "untouched") == 0);
|
||||
}
|
||||
const char* invalid[] = {"set leds", "set leds ", "set leds 0",
|
||||
"set leds off extra", "set leds on ", "set leds ON", "get leds off", "get leds "};
|
||||
for (const char* text : invalid)
|
||||
assert(command(text).find("Error: usage") == 0);
|
||||
assert(fs.writes == 0 && board.applies == 1);
|
||||
assert(command("set leds\t off") == "OK" && !enabled());
|
||||
char bounded[3] = {'x', 'y', 'z'};
|
||||
assert(handleCommand("get leds", bounded, 1));
|
||||
assert(bounded[0] == 0 && bounded[1] == 'y' && bounded[2] == 'z');
|
||||
assert(!handleCommand(nullptr, bounded, sizeof(bounded)));
|
||||
assert(!handleCommand("get leds", nullptr, sizeof(bounded)));
|
||||
assert(!handleCommand("get leds", bounded, 0));
|
||||
}
|
||||
void failures() {
|
||||
for (unsigned failure = 0; failure < 5; ++failure) {
|
||||
mesh::MainBoard board;
|
||||
MockFS fs;
|
||||
beginSettings(board, &fs);
|
||||
assert(command("set leds on") == "OK");
|
||||
const auto original = fs.files[target];
|
||||
const unsigned applies = board.applies;
|
||||
if (failure == 0) fs.fail_open = true;
|
||||
if (failure == 1) fs.fail_write = true;
|
||||
if (failure == 2) fs.corrupt_write = true;
|
||||
if (failure == 3) fs.fail_rename_from = "/leds_v1.tmp";
|
||||
if (failure == 4) fs.fail_read_path = target;
|
||||
assert(command("set leds off").find("save unconfirmed") != std::string::npos);
|
||||
assert(enabled() && board.applies == applies);
|
||||
if (failure != 4) assert(fs.files[target] == original);
|
||||
fs.fail_open = fs.fail_write = fs.corrupt_write = false;
|
||||
fs.fail_rename_from.clear();
|
||||
fs.fail_read_path.clear();
|
||||
beginSettings(board, &fs);
|
||||
assert(enabled() == (failure != 4));
|
||||
}
|
||||
}
|
||||
void recovery() {
|
||||
mesh::MainBoard board;
|
||||
MockFS fs;
|
||||
beginSettings(board, &fs);
|
||||
assert(command("set leds off") == "OK");
|
||||
const auto saved = fs.files[target];
|
||||
fs.files[rollback] = saved;
|
||||
fs.files.erase(target);
|
||||
fs.files["/leds_v1.tmp"] = {'x'};
|
||||
beginSettings(board, &fs);
|
||||
assert(!enabled() && fs.files[target] == saved && !fs.exists(rollback));
|
||||
assert(command("set leds on") == "OK");
|
||||
assert(!fs.exists("/leds_v1.tmp"));
|
||||
fs.files[rollback] = saved;
|
||||
fs.files[target] = {'x'};
|
||||
beginSettings(board, &fs);
|
||||
assert(!enabled() && fs.files[target] == saved);
|
||||
fs.files[rollback] = saved;
|
||||
fs.files.erase(target);
|
||||
fs.fail_rename_from = rollback;
|
||||
beginSettings(board, &fs);
|
||||
assert(!enabled() && fs.files[rollback] == saved);
|
||||
const unsigned applies = board.applies;
|
||||
assert(command("set leds on").find("save unconfirmed") != std::string::npos);
|
||||
assert(!enabled() && board.applies == applies && fs.files[rollback] == saved);
|
||||
}
|
||||
void corrupt() {
|
||||
mesh::MainBoard board;
|
||||
MockFS fs;
|
||||
beginSettings(board, &fs);
|
||||
assert(command("set leds off") == "OK");
|
||||
const auto saved = fs.files[target];
|
||||
for (unsigned i = 0; i < saved.size(); ++i) {
|
||||
fs.files[target] = saved;
|
||||
fs.files[target][i] ^= 2;
|
||||
beginSettings(board, &fs);
|
||||
assert(enabled());
|
||||
}
|
||||
for (unsigned length = 0; length < saved.size(); ++length) {
|
||||
fs.files[target] = {saved.begin(), saved.begin() + length};
|
||||
beginSettings(board, &fs);
|
||||
assert(enabled());
|
||||
}
|
||||
fs.files[target] = saved;
|
||||
fs.files[target].push_back(0);
|
||||
beginSettings(board, &fs);
|
||||
assert(enabled());
|
||||
}
|
||||
int main() {
|
||||
defaults();
|
||||
parsing();
|
||||
failures();
|
||||
recovery();
|
||||
corrupt();
|
||||
}
|
||||
'''
|
||||
|
||||
|
||||
class LedSettingsTests(unittest.TestCase):
|
||||
def test_commands_persistence_failures_and_recovery_across_platforms(self):
|
||||
compiler = os.environ.get("CXX") or shutil.which("g++") or shutil.which("clang++")
|
||||
if not compiler:
|
||||
self.skipTest("native C++ compiler unavailable")
|
||||
with tempfile.TemporaryDirectory(prefix="meshcore-led-settings-") as directory:
|
||||
folder = Path(directory)
|
||||
helpers = folder / "helpers"
|
||||
helpers.mkdir()
|
||||
filesystem = FILESYSTEM.replace(
|
||||
" void flush() {}", " void flush() {}\n bool setBufferSize(size_t) { return true; }"
|
||||
).replace(
|
||||
"#if !defined(NRF52_PLATFORM)",
|
||||
"#if !defined(NRF52_PLATFORM) && !defined(STM32_PLATFORM)",
|
||||
)
|
||||
(helpers / "IdentityStore.h").write_text(filesystem)
|
||||
(helpers / "ContactFileTransaction.h").write_text(
|
||||
(ROOT / "src/helpers/ContactFileTransaction.h").read_text()
|
||||
)
|
||||
(folder / "MeshCore.h").write_text(BOARD)
|
||||
source = folder / "test.cpp"
|
||||
source.write_text(HARNESS)
|
||||
for platform, flags in {
|
||||
"esp32": ["ESP32=1", "ESP32_PLATFORM=1"],
|
||||
"nrf52": ["NRF52_PLATFORM=1"],
|
||||
"stm32": ["STM32_PLATFORM=1"],
|
||||
"rp2040": ["RP2040_PLATFORM=1"],
|
||||
}.items():
|
||||
with self.subTest(platform=platform):
|
||||
binary = folder / platform
|
||||
built = subprocess.run(
|
||||
[compiler, "-std=c++17", "-Wall", "-Wextra", "-Werror",
|
||||
*SANITIZER_FLAGS, *["-D" + flag for flag in flags],
|
||||
"-I", str(folder), "-I", str(ROOT / "src"),
|
||||
"-I", str(ROOT / "src/helpers"),
|
||||
str(source), "-o", str(binary)],
|
||||
capture_output=True, text=True, timeout=60,
|
||||
)
|
||||
self.assertEqual(built.returncode, 0, built.stdout + built.stderr)
|
||||
result = subprocess.run(
|
||||
[str(binary)], capture_output=True, text=True, timeout=20
|
||||
)
|
||||
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -24,6 +24,7 @@ RGB = "defined(PIN_STATUS_LED_R) && defined(PIN_STATUS_LED_G) && defined(PIN_STA
|
||||
HARNESS = r'''
|
||||
#pragma once
|
||||
#include <helpers/CompanionNotificationPolicy.h>
|
||||
#include <helpers/LedPolicy.h>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
inline unsigned levels[64] = {}, analog_writes[64] = {}, digital_writes[64] = {}, mode_writes[64] = {};
|
||||
@@ -133,7 +134,7 @@ class NotificationLedOwnershipTest(unittest.TestCase):
|
||||
checked = subprocess.run([str(binary)], capture_output=True, text=True, timeout=20)
|
||||
print(f"\n[{name}]\n" + checked.stdout + checked.stderr, flush=True)
|
||||
self.assertEqual(checked.returncode, 0, checked.stdout + checked.stderr)
|
||||
expected_scenarios = 10 if expected_rgb or expected_mono else 4
|
||||
expected_scenarios = 11 if expected_rgb or expected_mono else 4
|
||||
self.assertEqual(checked.stdout.count("PASS:"), expected_scenarios, checked.stdout)
|
||||
self.assertRegex(checked.stdout, rf"{expected_scenarios} scenarios, [0-9]+ checks, 0 failures")
|
||||
|
||||
|
||||
@@ -13,6 +13,13 @@ ROOT = Path(__file__).resolve().parents[1]
|
||||
RTOS = r'''
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#define PINS_COUNT 64
|
||||
#define LED_STATE_ON 1
|
||||
#define LED_BLUE 12
|
||||
#define OUTPUT 1
|
||||
inline int digitalPinToPinName(int pin) { return pin; }
|
||||
inline void digitalWrite(int, int) {}
|
||||
inline void pinMode(int, int) {}
|
||||
using BaseType_t = int;
|
||||
using UBaseType_t = unsigned;
|
||||
using TaskHandle_t = void*;
|
||||
@@ -89,6 +96,7 @@ extern "C" BaseType_t __real_xTaskCreate(TaskFunction_t, const char* name,
|
||||
return pdPASS;
|
||||
}
|
||||
struct Settings {
|
||||
bool isRunning() const { return false; }
|
||||
template<class... T> void clearBonds(T...) {}
|
||||
template<class... T> void setMITM(T...) {}
|
||||
template<class... T> void setPIN(T...) {}
|
||||
@@ -132,8 +140,13 @@ struct BluefruitStub {
|
||||
}
|
||||
void setTxPower(int) {}
|
||||
void setName(const char*) {}
|
||||
void autoConnLed(bool) {}
|
||||
void _setConnLed(bool) {}
|
||||
void _startConnLed() {}
|
||||
unsigned connected() const { return 0; }
|
||||
template<class T> void setEventCallback(T) {}
|
||||
} Bluefruit;
|
||||
#include <helpers/nrf52/BluetoothLed.h>
|
||||
struct Service {
|
||||
int id;
|
||||
int begin() { return fail_service == id ? 1 : ERROR_NONE; }
|
||||
@@ -251,6 +264,7 @@ class Nrf52BleStartupTest(unittest.TestCase):
|
||||
with tempfile.TemporaryDirectory(prefix="meshcore-ble-start-") as temp:
|
||||
temp = Path(temp)
|
||||
(temp / "Arduino.h").write_text(RTOS)
|
||||
(temp / "bluefruit.h").write_text("#pragma once\n")
|
||||
for name in ("FreeRTOS.h", "task.h"):
|
||||
(temp / name).write_text('#include "Arduino.h"\n')
|
||||
cpp = temp / "startup.cpp"
|
||||
|
||||
@@ -0,0 +1,301 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Run the nRF52 LED policy and patched Bluefruit timer callback on the host."""
|
||||
|
||||
from importlib.util import module_from_spec, spec_from_file_location
|
||||
from pathlib import Path
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SPEC = spec_from_file_location("nrf52_led_patch", ROOT / "scripts/nrf52_ble_cccd_fix.py")
|
||||
PATCH = module_from_spec(SPEC)
|
||||
SPEC.loader.exec_module(PATCH)
|
||||
|
||||
ARDUINO = r'''
|
||||
#pragma once
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#define PINS_COUNT 64
|
||||
#define OUTPUT 1
|
||||
extern int led_level, writes;
|
||||
inline int digitalPinToPinName(int pin) { return pin == 62 ? 0xff : pin; }
|
||||
inline bool validPin(int pin) {
|
||||
return pin >= 0 && pin < PINS_COUNT && digitalPinToPinName(pin) != 0xff;
|
||||
}
|
||||
inline void digitalWrite(int pin, int value) {
|
||||
assert(validPin(pin));
|
||||
led_level = value;
|
||||
++writes;
|
||||
}
|
||||
inline void digitalToggle(int pin) { digitalWrite(pin, !led_level); }
|
||||
inline void pinMode(int pin, int) { assert(validPin(pin)); }
|
||||
'''
|
||||
|
||||
BLUEFRUIT = r'''
|
||||
#pragma once
|
||||
#include <Arduino.h>
|
||||
using TimerHandle_t = void*;
|
||||
struct Advertiser {
|
||||
bool running = false;
|
||||
bool isRunning() const { return running; }
|
||||
};
|
||||
struct AdafruitBluefruit {
|
||||
bool ready = false, automatic = true, linked = false;
|
||||
bool _led_conn = true;
|
||||
int timer_starts = 0, timer_stops = 0, connection_queries = 0;
|
||||
Advertiser Advertising;
|
||||
void autoConnLed(bool enabled) {
|
||||
automatic = _led_conn = enabled;
|
||||
if (!enabled && ready) ++timer_stops;
|
||||
}
|
||||
unsigned connected() {
|
||||
assert(ready);
|
||||
++connection_queries;
|
||||
return linked;
|
||||
}
|
||||
void _startConnLed() {
|
||||
assert(ready);
|
||||
if (automatic) ++timer_starts;
|
||||
}
|
||||
#ifdef EXTERNAL_LED_METHOD
|
||||
void _setConnLed(bool on);
|
||||
#else
|
||||
void _setConnLed(bool on) {
|
||||
assert(ready);
|
||||
#ifdef LED_BLUE
|
||||
if (automatic) digitalWrite(LED_BLUE, on ? LED_STATE_ON : !LED_STATE_ON);
|
||||
#else
|
||||
(void)on;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
};
|
||||
using MockBluefruit = AdafruitBluefruit;
|
||||
extern MockBluefruit Bluefruit;
|
||||
'''
|
||||
|
||||
# Exact callback from the pinned Adafruit nRF52 Bluefruit52Lib/src/bluefruit.cpp.
|
||||
# The build middleware patches this body in its private framework copy.
|
||||
CALLBACK = r'''#include "bluefruit.h"
|
||||
static void bluefruit_blinky_cb( TimerHandle_t xTimer )
|
||||
{
|
||||
(void) xTimer;
|
||||
digitalToggle(LED_BLUE);
|
||||
}
|
||||
|
||||
void AdafruitBluefruit::_setConnLed (bool on_off)
|
||||
{
|
||||
if (_led_conn)
|
||||
{
|
||||
digitalWrite(LED_BLUE, on_off ? LED_STATE_ON : (1-LED_STATE_ON) );
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
HARNESS = r'''
|
||||
#include <helpers/nrf52/BluetoothLed.h>
|
||||
MockBluefruit Bluefruit;
|
||||
int led_level = LED_STATE_ON, writes = 0;
|
||||
int main() {
|
||||
#if defined(DISABLE_BLE_LED) || !defined(LED_BLUE)
|
||||
const bool supported = false;
|
||||
#else
|
||||
const bool supported = validPin(LED_BLUE);
|
||||
#endif
|
||||
// With the default enabled preference, the helper may configure the owner
|
||||
// before begin(), but must not query a connection or start a missing timer.
|
||||
mesh_nrf52::applyBluetoothLedPolicy();
|
||||
assert(Bluefruit.automatic == supported);
|
||||
assert(Bluefruit.timer_starts == 0 && Bluefruit.timer_stops == 0);
|
||||
assert(Bluefruit.connection_queries == 0);
|
||||
|
||||
// Saved preferences are applied before begin() has created an LED timer.
|
||||
mesh::leds::setEnabled(false);
|
||||
mesh_nrf52::applyBluetoothLedPolicy();
|
||||
assert(!Bluefruit.automatic);
|
||||
assert(Bluefruit.timer_starts == 0 && Bluefruit.timer_stops == 0);
|
||||
assert(Bluefruit.connection_queries == 0);
|
||||
#if defined(LED_BLUE)
|
||||
if (validPin(LED_BLUE)) assert(led_level == !LED_STATE_ON);
|
||||
#endif
|
||||
|
||||
// A later stack start must preserve the off preference.
|
||||
Bluefruit.ready = true;
|
||||
Bluefruit.Advertising.running = true;
|
||||
mesh_nrf52::bluetoothLedStarted();
|
||||
assert(!Bluefruit.automatic && Bluefruit.timer_starts == 0);
|
||||
|
||||
// Enabling again restores the current state without restarting Bluetooth.
|
||||
mesh::leds::setEnabled(true);
|
||||
mesh_nrf52::applyBluetoothLedPolicy();
|
||||
assert(Bluefruit.automatic == supported);
|
||||
if (supported) {
|
||||
assert(Bluefruit.timer_starts == 1);
|
||||
assert(led_level == !LED_STATE_ON);
|
||||
} else {
|
||||
assert(Bluefruit.timer_starts == 0);
|
||||
}
|
||||
|
||||
Bluefruit.Advertising.running = false;
|
||||
Bluefruit.linked = true;
|
||||
mesh_nrf52::applyBluetoothLedPolicy();
|
||||
#if defined(RAK_BOARD) && defined(BLE_PIN_CODE)
|
||||
if (supported) assert(led_level == !LED_STATE_ON);
|
||||
#else
|
||||
if (supported) assert(led_level == LED_STATE_ON);
|
||||
#endif
|
||||
mesh::leds::setEnabled(false);
|
||||
mesh_nrf52::applyBluetoothLedPolicy();
|
||||
assert(!Bluefruit.automatic);
|
||||
#ifdef LED_BLUE
|
||||
if (validPin(LED_BLUE)) assert(led_level == !LED_STATE_ON);
|
||||
else assert(writes == 0);
|
||||
#else
|
||||
assert(writes == 0);
|
||||
#endif
|
||||
}
|
||||
'''
|
||||
|
||||
TIMER_HARNESS = r'''
|
||||
MockBluefruit Bluefruit;
|
||||
int led_level = LED_STATE_ON, writes = 0;
|
||||
int main() {
|
||||
Bluefruit.ready = true;
|
||||
// A previously queued callback can still arrive after autoConnLed(false).
|
||||
mesh::leds::setEnabled(false);
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
bluefruit_blinky_cb(nullptr);
|
||||
if (validPin(LED_BLUE)) assert(led_level == !LED_STATE_ON);
|
||||
}
|
||||
Bluefruit._setConnLed(true);
|
||||
if (validPin(LED_BLUE)) assert(led_level == !LED_STATE_ON);
|
||||
mesh::leds::setEnabled(true);
|
||||
bluefruit_blinky_cb(nullptr);
|
||||
#ifdef DISABLE_BLE_LED
|
||||
if (validPin(LED_BLUE)) assert(led_level == !LED_STATE_ON);
|
||||
#else
|
||||
if (validPin(LED_BLUE)) assert(led_level == LED_STATE_ON);
|
||||
#endif
|
||||
// PR #2694: advertising still blinks on a RAK Companion, but connecting
|
||||
// leaves the LED dark. Other boards retain the steady connected indicator.
|
||||
Bluefruit.linked = true;
|
||||
Bluefruit._setConnLed(true);
|
||||
#if defined(DISABLE_BLE_LED) || (defined(RAK_BOARD) && defined(BLE_PIN_CODE))
|
||||
if (validPin(LED_BLUE)) assert(led_level == !LED_STATE_ON);
|
||||
#else
|
||||
if (validPin(LED_BLUE)) assert(led_level == LED_STATE_ON);
|
||||
#endif
|
||||
#if defined(RAK_BOARD) && defined(BLE_PIN_CODE)
|
||||
// Stopping the advertising timer is asynchronous. Any callback already
|
||||
// queued when the connection completes must leave the RAK indicator dark.
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
bluefruit_blinky_cb(nullptr);
|
||||
if (validPin(LED_BLUE)) assert(led_level == !LED_STATE_ON);
|
||||
}
|
||||
#endif
|
||||
Bluefruit.linked = false;
|
||||
Bluefruit._setConnLed(false);
|
||||
if (validPin(LED_BLUE)) assert(led_level == !LED_STATE_ON);
|
||||
#if defined(RAK_BOARD) && defined(BLE_PIN_CODE)
|
||||
// A later disconnection lets the existing advertising timer blink again.
|
||||
bluefruit_blinky_cb(nullptr);
|
||||
#ifdef DISABLE_BLE_LED
|
||||
if (validPin(LED_BLUE)) assert(led_level == !LED_STATE_ON);
|
||||
#else
|
||||
if (validPin(LED_BLUE)) assert(led_level == LED_STATE_ON);
|
||||
#endif
|
||||
#endif
|
||||
if (!validPin(LED_BLUE)) assert(writes == 0);
|
||||
}
|
||||
'''
|
||||
|
||||
|
||||
def compile_run(case, source, defines, expect_success=True):
|
||||
if "AdafruitBluefruit::_setConnLed" in source:
|
||||
defines = [*defines, "EXTERNAL_LED_METHOD"]
|
||||
compiler = shutil.which("c++") or shutil.which("g++")
|
||||
case.assertIsNotNone(compiler, "run this host suite where c++ is available")
|
||||
with tempfile.TemporaryDirectory(prefix="mesh-nrf52-led-policy-") as folder:
|
||||
folder = Path(folder)
|
||||
(folder / "Arduino.h").write_text(ARDUINO)
|
||||
(folder / "bluefruit.h").write_text(BLUEFRUIT)
|
||||
(folder / "case.cpp").write_text(source)
|
||||
binary = folder / ("case.exe" if os.name == "nt" else "case")
|
||||
built = subprocess.run([
|
||||
compiler, "-std=c++11", "-Wall", "-Wextra", "-Werror",
|
||||
"-I", str(folder), "-I", str(ROOT / "src"),
|
||||
*["-D" + value for value in defines], str(folder / "case.cpp"),
|
||||
"-o", str(binary)], text=True, capture_output=True, timeout=30)
|
||||
case.assertEqual(built.returncode, 0, built.stderr)
|
||||
result = subprocess.run([str(binary)], text=True, capture_output=True, timeout=10)
|
||||
if expect_success:
|
||||
case.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
||||
else:
|
||||
case.assertNotEqual(result.returncode, 0, "negative control did not fail")
|
||||
|
||||
|
||||
class Nrf52LedPolicyTests(unittest.TestCase):
|
||||
def test_policy_before_start_and_during_advertising_and_connection(self):
|
||||
for polarity in (0, 1):
|
||||
for disabled in (False, True):
|
||||
defines = ["LED_BLUE=12", f"LED_STATE_ON={polarity}"]
|
||||
if disabled:
|
||||
defines.append("DISABLE_BLE_LED")
|
||||
with self.subTest(polarity=polarity, disabled=disabled):
|
||||
compile_run(self, HARNESS, defines)
|
||||
|
||||
def test_absent_and_unmapped_pins_never_enable_bluefruit_led(self):
|
||||
for pin in (None, "-1", "255", "PINS_COUNT", "62"):
|
||||
defines = ["LED_STATE_ON=0"] + ([] if pin is None else ["LED_BLUE=" + pin])
|
||||
with self.subTest(pin=pin):
|
||||
compile_run(self, HARNESS, defines)
|
||||
|
||||
def test_rak_companion_policy_leaves_connection_indicator_off(self):
|
||||
# https://github.com/meshcore-dev/MeshCore/pull/2694
|
||||
for flags in (["RAK_BOARD"], ["BLE_PIN_CODE=123456"],
|
||||
["RAK_BOARD", "BLE_PIN_CODE=123456"]):
|
||||
with self.subTest(flags=flags):
|
||||
compile_run(self, HARNESS, ["LED_BLUE=12", "LED_STATE_ON=0", *flags])
|
||||
|
||||
def test_pending_callback_respects_saved_policy_and_compile_time_disable(self):
|
||||
source = PATCH.patched_led_source(CALLBACK) + TIMER_HARNESS
|
||||
for polarity in (0, 1):
|
||||
for disabled in (False, True):
|
||||
defines = ["LED_BLUE=12", f"LED_STATE_ON={polarity}"]
|
||||
if disabled:
|
||||
defines.append("DISABLE_BLE_LED")
|
||||
with self.subTest(polarity=polarity, disabled=disabled):
|
||||
compile_run(self, source, defines)
|
||||
|
||||
def test_pending_callback_ignores_invalid_pins(self):
|
||||
source = PATCH.patched_led_source(CALLBACK) + TIMER_HARNESS
|
||||
for pin in ("-1", "255", "PINS_COUNT", "62"):
|
||||
with self.subTest(pin=pin):
|
||||
compile_run(self, source, ["LED_BLUE=" + pin, "LED_STATE_ON=0"])
|
||||
|
||||
def test_rak_companion_blinks_while_advertising_then_goes_dark(self):
|
||||
source = PATCH.patched_led_source(CALLBACK) + TIMER_HARNESS
|
||||
for flags in (["RAK_BOARD"], ["BLE_PIN_CODE=123456"],
|
||||
["RAK_BOARD", "BLE_PIN_CODE=123456"],
|
||||
["RAK_BOARD", "BLE_PIN_CODE=123456", "DISABLE_BLE_LED"]):
|
||||
for polarity in (0, 1):
|
||||
with self.subTest(flags=flags, polarity=polarity):
|
||||
compile_run(self, source,
|
||||
["LED_BLUE=12", f"LED_STATE_ON={polarity}", *flags])
|
||||
|
||||
def test_unpatched_callback_negative_control(self):
|
||||
compile_run(self, "#include <helpers/LedPolicy.h>\n" + CALLBACK + TIMER_HARNESS,
|
||||
["LED_BLUE=12", "LED_STATE_ON=0"], expect_success=False)
|
||||
|
||||
def test_patch_is_idempotent_and_rejects_unrecognized_callback(self):
|
||||
patched = PATCH.patched_led_source(CALLBACK)
|
||||
self.assertEqual(PATCH.patched_led_source(patched), patched)
|
||||
with self.assertRaises(RuntimeError):
|
||||
PATCH.patched_led_source(CALLBACK.replace("digitalToggle(LED_BLUE);", "changed();"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,134 @@
|
||||
"""Run the common LED policy and Xiao S3 LED initialization with fake GPIO."""
|
||||
|
||||
from pathlib import Path
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
from test_replay_reset_integration import extract_braced
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
ARDUINO = r'''
|
||||
#pragma once
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
#define LOW 0
|
||||
#define HIGH 1
|
||||
#define INPUT 0
|
||||
#define OUTPUT 1
|
||||
struct Event { int kind, pin, value; };
|
||||
extern std::vector<Event> events;
|
||||
inline void digitalWrite(int pin, int value) { events.push_back({0, pin, value}); }
|
||||
inline void pinMode(int pin, int mode) { events.push_back({1, pin, mode}); }
|
||||
inline void analogWrite(int pin, int value) { events.push_back({2, pin, value}); }
|
||||
'''
|
||||
|
||||
HARNESS = r'''
|
||||
#include <Arduino.h>
|
||||
#include <target.h>
|
||||
#include <helpers/LedPolicy.h>
|
||||
std::vector<Event> events;
|
||||
int main() {
|
||||
// Enabling permits the existing heartbeat owner to run; it does not light
|
||||
// an idle LED or change its output mode as a side effect of the command.
|
||||
assert(mesh::leds::enabled());
|
||||
mesh::leds::applyStatusPolicy();
|
||||
assert(events.empty());
|
||||
mesh::leds::setEnabled(false);
|
||||
mesh::leds::applyStatusPolicy();
|
||||
assert(!mesh::leds::enabled());
|
||||
assert(events.size() == EXPECT_EVENTS);
|
||||
#if EXPECT_EVENTS == 2
|
||||
assert(events[0].kind == 0 && events[0].pin == PIN_STATUS_LED);
|
||||
assert(events[0].value == EXPECT_OFF);
|
||||
assert(events[1].kind == 1 && events[1].pin == PIN_STATUS_LED);
|
||||
assert(events[1].value == OUTPUT); // Set the off latch before enabling output.
|
||||
#elif EXPECT_EVENTS == 3
|
||||
const int pins[] = {PIN_STATUS_LED_R, PIN_STATUS_LED_G, PIN_STATUS_LED_B};
|
||||
for (unsigned i = 0; i < 3; ++i)
|
||||
assert(events[i].kind == 2 && events[i].pin == pins[i] && events[i].value == 0);
|
||||
#endif
|
||||
events.clear();
|
||||
mesh::leds::setEnabled(true);
|
||||
mesh::leds::applyStatusPolicy();
|
||||
assert(mesh::leds::enabled() && events.empty());
|
||||
}
|
||||
'''
|
||||
|
||||
|
||||
def compile_run(case, source, defines):
|
||||
compiler = shutil.which("c++") or shutil.which("g++")
|
||||
case.assertIsNotNone(compiler, "run this host suite where c++ is available")
|
||||
with tempfile.TemporaryDirectory(prefix="mesh-status-led-") as folder:
|
||||
folder = Path(folder)
|
||||
(folder / "Arduino.h").write_text(ARDUINO)
|
||||
(folder / "target.h").write_text("#pragma once\n" + "\n".join(
|
||||
"#define " + define.replace("=", " ", 1) for define in defines))
|
||||
(folder / "case.cpp").write_text(source)
|
||||
binary = folder / "case"
|
||||
built = subprocess.run([
|
||||
compiler, "-std=c++11", "-Wall", "-Wextra", "-Werror",
|
||||
"-I", str(folder), "-I", str(ROOT / "src"),
|
||||
str(ROOT / "src/helpers/LedPolicy.cpp"), str(folder / "case.cpp"),
|
||||
"-o", str(binary)], text=True, capture_output=True, timeout=30)
|
||||
case.assertEqual(built.returncode, 0, built.stderr)
|
||||
ran = subprocess.run([str(binary)], text=True, capture_output=True, timeout=10)
|
||||
case.assertEqual(ran.returncode, 0, ran.stdout + ran.stderr)
|
||||
|
||||
|
||||
class StatusLedPolicyTests(unittest.TestCase):
|
||||
def test_off_is_immediate_and_on_leaves_heartbeat_in_control(self):
|
||||
cases = (
|
||||
("active-high", ["PIN_STATUS_LED=12", "LED_STATE_ON=HIGH",
|
||||
"EXPECT_OFF=LOW", "EXPECT_EVENTS=2"]),
|
||||
("active-low", ["PIN_STATUS_LED=12", "LED_STATE_ON=LOW",
|
||||
"EXPECT_OFF=HIGH", "EXPECT_EVENTS=2"]),
|
||||
("default-polarity", ["PIN_STATUS_LED=12", "EXPECT_OFF=LOW",
|
||||
"EXPECT_EVENTS=2"]),
|
||||
("absent", ["EXPECT_EVENTS=0"]),
|
||||
("negative-pin", ["PIN_STATUS_LED=-1", "EXPECT_EVENTS=0"]),
|
||||
("rgb", ["PIN_STATUS_LED_R=3", "PIN_STATUS_LED_G=24",
|
||||
"PIN_STATUS_LED_B=28", "PIN_STATUS_LED=24", "EXPECT_EVENTS=3"]),
|
||||
)
|
||||
for name, defines in cases:
|
||||
with self.subTest(board=name):
|
||||
compile_run(self, HARNESS, defines)
|
||||
|
||||
def test_xiao_s3_uses_active_low_output_during_radio_init(self):
|
||||
ini = (ROOT / "variants/xiao_s3/platformio.ini").read_text()
|
||||
names = ("PIN_STATUS_LED", "LED_STATE_ON", "P_LORA_SCLK",
|
||||
"P_LORA_MISO", "P_LORA_MOSI")
|
||||
definitions = {name: re.findall(r"-D\s+" + name + r"=([^\s;]+)", ini)
|
||||
for name in names}
|
||||
self.assertEqual(definitions["PIN_STATUS_LED"], ["21"])
|
||||
self.assertEqual(definitions["LED_STATE_ON"], ["LOW"])
|
||||
for name in names:
|
||||
self.assertEqual(len(definitions[name]), 1, name)
|
||||
method = extract_braced((ROOT / "variants/xiao_s3/target.cpp").read_text(),
|
||||
"bool radio_init()")
|
||||
source = r'''
|
||||
#include <Arduino.h>
|
||||
#include <target.h>
|
||||
std::vector<Event> events;
|
||||
struct Clock { template<class... T> void begin(T...) {} } fallback_clock, rtc_clock;
|
||||
struct Spi { void begin(int, int, int) {} } spi;
|
||||
struct Radio { bool std_init(Spi*) { return true; } } radio;
|
||||
int Wire = 0;
|
||||
@METHOD@
|
||||
int main() {
|
||||
assert(radio_init());
|
||||
assert(events.size() == 3);
|
||||
assert(events[0].kind == 0 && events[0].pin == 21 && events[0].value == HIGH);
|
||||
assert(events[1].kind == 1 && events[1].pin == 21 && events[1].value == OUTPUT);
|
||||
assert(events[2].kind == 1 && events[2].pin == 48 && events[2].value == OUTPUT);
|
||||
}
|
||||
'''
|
||||
compile_run(self, source.replace("@METHOD@", method),
|
||||
[name + "=" + definitions[name][0] for name in names])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -231,6 +231,7 @@ MOCK_SDK = r'''
|
||||
#pragma once
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <helpers/LedPolicy.h>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -291,6 +292,7 @@ public:
|
||||
int startup_reason = 0;
|
||||
virtual ~ESP32Board() {}
|
||||
void begin() {}
|
||||
virtual void applyLedPolicy() {}
|
||||
virtual void onBeforeTransmit() {}
|
||||
virtual void onAfterTransmit() {}
|
||||
virtual bool getBatteryChargeTarget(uint16_t&) { return false; }
|
||||
@@ -305,6 +307,7 @@ constexpr int XPOWERS_DCDC2 = 0, XPOWERS_DCDC3 = 0, XPOWERS_DCDC4 = 0;
|
||||
constexpr int XPOWERS_DCDC5 = 0, XPOWERS_ALDO1 = 0, XPOWERS_ALDO2 = 0;
|
||||
constexpr int XPOWERS_ALDO3 = 0, XPOWERS_ALDO4 = 0, XPOWERS_BLDO1 = 0;
|
||||
constexpr int XPOWERS_BLDO2 = 0, XPOWERS_VBACKUP = 0, XPOWERS_CHG_LED_CTRL_CHG = 0;
|
||||
constexpr int XPOWERS_CHG_LED_OFF = 1;
|
||||
constexpr int XPOWERS_DLDO1 = 0, XPOWERS_DLDO2 = 0;
|
||||
constexpr int XPOWERS_AXP2101_DCDC4_VOL2_MAX = 0, XPOWERS_AXP192_ALL_IRQ = 0;
|
||||
constexpr int XPOWERS_AXP2101_ALL_IRQ = 0, XPOWERS_AXP192_CHG_CUR_450MA = 0;
|
||||
|
||||
@@ -114,9 +114,11 @@ inline TwoWire Wire;
|
||||
ESP_BOARD = r'''
|
||||
#pragma once
|
||||
#include <Arduino.h>
|
||||
#include <helpers/LedPolicy.h>
|
||||
class ESP32Board {
|
||||
public:
|
||||
virtual void begin() {}
|
||||
virtual void applyLedPolicy() {}
|
||||
virtual uint16_t getBattMilliVolts() { return 0; }
|
||||
virtual const char* getManufacturerName() const { return "host"; }
|
||||
virtual void onBeforeTransmit() {}
|
||||
|
||||
@@ -34,8 +34,13 @@ public:
|
||||
}
|
||||
|
||||
#if defined(P_LORA_TX_LED)
|
||||
void applyLedPolicy() override {
|
||||
NRF52Board::applyLedPolicy();
|
||||
if (!mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, LOW);
|
||||
}
|
||||
|
||||
void onBeforeTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
if (mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
}
|
||||
|
||||
void onAfterTransmit() override {
|
||||
|
||||
@@ -34,8 +34,13 @@ public:
|
||||
}
|
||||
|
||||
#if defined(P_LORA_TX_LED)
|
||||
void applyLedPolicy() override {
|
||||
NRF52Board::applyLedPolicy();
|
||||
if (!mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, LOW);
|
||||
}
|
||||
|
||||
void onBeforeTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
if (mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
}
|
||||
|
||||
void onAfterTransmit() override {
|
||||
|
||||
@@ -34,8 +34,13 @@ public:
|
||||
}
|
||||
|
||||
#if defined(P_LORA_TX_LED)
|
||||
void applyLedPolicy() override {
|
||||
NRF52Board::applyLedPolicy();
|
||||
if (!mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, LOW);
|
||||
}
|
||||
|
||||
void onBeforeTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
if (mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
}
|
||||
|
||||
void onAfterTransmit() override {
|
||||
|
||||
@@ -56,7 +56,7 @@ void HeltecRC32Board::powerOff() {
|
||||
}
|
||||
|
||||
void HeltecRC32Board::onBeforeTransmit() {
|
||||
digitalWrite(P_LORA_TX_LED, HIGH);
|
||||
if (mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, HIGH);
|
||||
}
|
||||
|
||||
void HeltecRC32Board::onAfterTransmit() {
|
||||
|
||||
@@ -56,8 +56,13 @@ void T096Board::begin() {
|
||||
delay(1);
|
||||
}
|
||||
|
||||
void T096Board::applyLedPolicy() {
|
||||
NRF52Board::applyLedPolicy();
|
||||
if (!mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, LOW);
|
||||
}
|
||||
|
||||
void T096Board::onBeforeTransmit() {
|
||||
digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
if (mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
loRaFEMControl.setTxModeEnable();
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ public:
|
||||
void attachDynamicPrefs(KeyValueStore* prefs);
|
||||
bool handleCommand(const char* command, uint32_t sender_timestamp, char* reply) override;
|
||||
|
||||
void applyLedPolicy() override;
|
||||
void onBeforeTransmit(void) override;
|
||||
void onAfterTransmit(void) override;
|
||||
uint16_t getBattMilliVolts() override;
|
||||
|
||||
@@ -58,8 +58,13 @@ void T1Board::begin() {
|
||||
delay(1);
|
||||
}
|
||||
|
||||
void T1Board::applyLedPolicy() {
|
||||
NRF52Board::applyLedPolicy();
|
||||
if (!mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, !LED_STATE_ON);
|
||||
}
|
||||
|
||||
void T1Board::onBeforeTransmit() {
|
||||
digitalWrite(P_LORA_TX_LED, LED_STATE_ON);
|
||||
if (mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, LED_STATE_ON);
|
||||
}
|
||||
|
||||
void T1Board::onAfterTransmit() {
|
||||
|
||||
@@ -18,6 +18,7 @@ public:
|
||||
T1Board() : periph_power(PIN_TFT_VDD_CTL, PIN_TFT_VDD_CTL_ACTIVE), NRF52Board("T1_OTA") {}
|
||||
|
||||
void begin();
|
||||
void applyLedPolicy() override;
|
||||
void onBeforeTransmit() override;
|
||||
void onAfterTransmit() override;
|
||||
uint16_t getBattMilliVolts() override;
|
||||
|
||||
@@ -20,8 +20,13 @@ public:
|
||||
void begin();
|
||||
|
||||
#if defined(P_LORA_TX_LED)
|
||||
void applyLedPolicy() override {
|
||||
NRF52Board::applyLedPolicy();
|
||||
if (!mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, HIGH);
|
||||
}
|
||||
|
||||
void onBeforeTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED on
|
||||
if (mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED on
|
||||
}
|
||||
void onAfterTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED off
|
||||
|
||||
@@ -60,8 +60,13 @@ void HeltecTowerV2Board::begin() {
|
||||
loRaFEMControl.init();
|
||||
}
|
||||
|
||||
void HeltecTowerV2Board::applyLedPolicy() {
|
||||
NRF52Board::applyLedPolicy();
|
||||
if (!mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, !LED_STATE_ON);
|
||||
}
|
||||
|
||||
void HeltecTowerV2Board::onBeforeTransmit() {
|
||||
digitalWrite(P_LORA_TX_LED, LED_STATE_ON);
|
||||
if (mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, LED_STATE_ON);
|
||||
loRaFEMControl.setTxModeEnable();
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ public:
|
||||
|
||||
HeltecTowerV2Board() : NRF52Board("TOWER_V2_OTA") {}
|
||||
void begin();
|
||||
void applyLedPolicy() override;
|
||||
void onBeforeTransmit() override;
|
||||
void onAfterTransmit() override;
|
||||
uint16_t getBattMilliVolts() override;
|
||||
|
||||
@@ -27,7 +27,7 @@ void HeltecTrackerV2Board::begin() {
|
||||
}
|
||||
|
||||
void HeltecTrackerV2Board::onBeforeTransmit(void) {
|
||||
digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
if (mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
loRaFEMControl.setTxModeEnable();
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ void HeltecV4Board::begin() {
|
||||
}
|
||||
|
||||
void HeltecV4Board::onBeforeTransmit(void) {
|
||||
digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
if (mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
loRaFEMControl.setTxModeEnable();
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ void HeltecV4R8Board::begin() {
|
||||
}
|
||||
|
||||
void HeltecV4R8Board::onBeforeTransmit(void) {
|
||||
digitalWrite(P_LORA_TX_LED, HIGH);
|
||||
if (mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, HIGH);
|
||||
loRaFEMControl.setTxModeEnable();
|
||||
}
|
||||
|
||||
|
||||
@@ -12,8 +12,13 @@ public:
|
||||
void begin();
|
||||
|
||||
#if defined(P_LORA_TX_LED)
|
||||
void applyLedPolicy() override {
|
||||
NRF52Board::applyLedPolicy();
|
||||
if (!mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, HIGH);
|
||||
}
|
||||
|
||||
void onBeforeTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED on
|
||||
if (mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED on
|
||||
}
|
||||
void onAfterTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED off
|
||||
|
||||
@@ -12,8 +12,13 @@ public:
|
||||
void begin();
|
||||
|
||||
#if defined(P_LORA_TX_LED)
|
||||
void applyLedPolicy() override {
|
||||
NRF52Board::applyLedPolicy();
|
||||
if (!mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, HIGH);
|
||||
}
|
||||
|
||||
void onBeforeTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED on
|
||||
if (mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED on
|
||||
#if defined(LED_BLUE)
|
||||
// turn off that annoying blue LED before transmitting
|
||||
digitalWrite(LED_BLUE, HIGH);
|
||||
|
||||
@@ -12,8 +12,13 @@ public:
|
||||
void begin();
|
||||
|
||||
#if defined(P_LORA_TX_LED)
|
||||
void applyLedPolicy() override {
|
||||
NRF52Board::applyLedPolicy();
|
||||
if (!mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, HIGH);
|
||||
}
|
||||
|
||||
void onBeforeTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED on
|
||||
if (mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED on
|
||||
#if defined(LED_BLUE)
|
||||
// turn off that annoying blue LED before transmitting
|
||||
digitalWrite(LED_BLUE, HIGH);
|
||||
|
||||
@@ -30,8 +30,13 @@ public:
|
||||
}
|
||||
|
||||
#if defined(P_LORA_TX_LED)
|
||||
void applyLedPolicy() override {
|
||||
NRF52Board::applyLedPolicy();
|
||||
if (!mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, LOW);
|
||||
}
|
||||
|
||||
void onBeforeTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
if (mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
}
|
||||
void onAfterTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off
|
||||
|
||||
@@ -13,8 +13,13 @@ public:
|
||||
void begin();
|
||||
|
||||
#if defined(P_LORA_TX_LED)
|
||||
void applyLedPolicy() override {
|
||||
NRF52Board::applyLedPolicy();
|
||||
if (!mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, LOW);
|
||||
}
|
||||
|
||||
void onBeforeTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
if (mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
}
|
||||
void onAfterTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off
|
||||
|
||||
@@ -84,8 +84,12 @@ void TBeam1WBoard::powerCycleRadio() {
|
||||
power_off_level, digitalRead(SX126X_POWER_EN));
|
||||
}
|
||||
|
||||
void TBeam1WBoard::applyLedPolicy() {
|
||||
if (!mesh::leds::enabled()) digitalWrite(LED_PIN, LOW);
|
||||
}
|
||||
|
||||
void TBeam1WBoard::onBeforeTransmit() {
|
||||
digitalWrite(LED_PIN, HIGH); // TX LED on
|
||||
if (mesh::leds::enabled()) digitalWrite(LED_PIN, HIGH); // TX LED on
|
||||
portENTER_CRITICAL(&_fan_mux);
|
||||
_tx_active = true;
|
||||
if (_mode == FAN_AUTO && !_stopped) {
|
||||
|
||||
@@ -71,6 +71,7 @@ public:
|
||||
void begin();
|
||||
void attachDynamicPrefs(KeyValueStore* prefs);
|
||||
bool handleCommand(const char* command, uint32_t sender_timestamp, char* reply) override;
|
||||
void applyLedPolicy() override;
|
||||
void onBeforeTransmit() override;
|
||||
void onAfterTransmit() override;
|
||||
uint16_t getBattMilliVolts() override;
|
||||
|
||||
@@ -13,13 +13,17 @@ public:
|
||||
void begin();
|
||||
|
||||
#ifdef P_LORA_TX_LED
|
||||
void onBeforeTransmit() override{
|
||||
digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED on - invert pin for SX1276
|
||||
}
|
||||
void applyLedPolicy() override {
|
||||
if (!mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, HIGH);
|
||||
}
|
||||
|
||||
void onAfterTransmit() override{
|
||||
digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED off - invert pin for SX1276
|
||||
}
|
||||
void onBeforeTransmit() override{
|
||||
if (mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED on - invert pin for SX1276
|
||||
}
|
||||
|
||||
void onAfterTransmit() override{
|
||||
digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED off - invert pin for SX1276
|
||||
}
|
||||
#endif
|
||||
|
||||
uint16_t getBattMilliVolts() {
|
||||
@@ -41,4 +45,4 @@ public:
|
||||
const char* getManufacturerName() const{
|
||||
return "LilyGo T-Deck";
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -57,7 +57,16 @@ uint16_t TechoCardBoard::getBattMilliVolts() {
|
||||
return (uint16_t)((float)adcvalue * REAL_VBAT_MV_PER_LSB);
|
||||
}
|
||||
|
||||
void TechoCardBoard::applyLedPolicy() {
|
||||
NRF52Board::applyLedPolicy();
|
||||
if (!mesh::leds::enabled()) {
|
||||
Led_A.setPixelColor(0, 0, 0, 0);
|
||||
Led_A.show();
|
||||
}
|
||||
}
|
||||
|
||||
void TechoCardBoard::onBeforeTransmit() {
|
||||
if (!mesh::leds::enabled()) return;
|
||||
Led_A.setPixelColor(0, 20, 20, 20); // turn TX LED on
|
||||
Led_A.show();
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ public:
|
||||
TechoCardBoard() : NRF52Board("TECHO_OTA") {}
|
||||
void begin();
|
||||
uint16_t getBattMilliVolts() override;
|
||||
void applyLedPolicy() override;
|
||||
void onBeforeTransmit(void) override;
|
||||
void onAfterTransmit(void) override;
|
||||
|
||||
|
||||
@@ -29,8 +29,12 @@ void MeshnologyW12Board::begin() {
|
||||
}
|
||||
}
|
||||
|
||||
void MeshnologyW12Board::applyLedPolicy() {
|
||||
if (!mesh::leds::enabled()) neopixelWrite(NEOPIXEL_LED, 0, 0, 0);
|
||||
}
|
||||
|
||||
void MeshnologyW12Board::onBeforeTransmit(void) {
|
||||
neopixelWrite(NEOPIXEL_LED, NEOPIXEL_BRIGHTNESS, NEOPIXEL_BRIGHTNESS, NEOPIXEL_BRIGHTNESS); // turn TX neopixel on (White)
|
||||
if (mesh::leds::enabled()) neopixelWrite(NEOPIXEL_LED, NEOPIXEL_BRIGHTNESS, NEOPIXEL_BRIGHTNESS, NEOPIXEL_BRIGHTNESS); // turn TX neopixel on (White)
|
||||
}
|
||||
|
||||
void MeshnologyW12Board::onAfterTransmit(void) {
|
||||
|
||||
@@ -22,6 +22,7 @@ public:
|
||||
MeshnologyW12Board() : periph_power(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE) { }
|
||||
|
||||
void begin();
|
||||
void applyLedPolicy() override;
|
||||
void onBeforeTransmit(void) override;
|
||||
void onAfterTransmit(void) override;
|
||||
void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1);
|
||||
|
||||
@@ -13,8 +13,13 @@ public:
|
||||
void begin();
|
||||
|
||||
#if defined(P_LORA_TX_LED)
|
||||
void applyLedPolicy() override {
|
||||
NRF52Board::applyLedPolicy();
|
||||
if (!mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, LOW);
|
||||
}
|
||||
|
||||
void onBeforeTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
if (mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
}
|
||||
void onAfterTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off
|
||||
|
||||
@@ -78,7 +78,7 @@ public:
|
||||
|
||||
// set led on and wait for button release before poweroff
|
||||
#ifdef LED_PIN
|
||||
digitalWrite(LED_PIN, HIGH);
|
||||
if (mesh::leds::enabled()) digitalWrite(LED_PIN, HIGH);
|
||||
#endif
|
||||
#ifdef BUTTON_PIN
|
||||
while(digitalRead(BUTTON_PIN));
|
||||
|
||||
@@ -70,8 +70,13 @@ public:
|
||||
}
|
||||
|
||||
#if defined(P_LORA_TX_LED)
|
||||
void applyLedPolicy() override {
|
||||
NRF52Board::applyLedPolicy();
|
||||
if (!mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, LOW);
|
||||
}
|
||||
|
||||
void onBeforeTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, HIGH);// turn TX LED on
|
||||
if (mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, HIGH);// turn TX LED on
|
||||
}
|
||||
void onAfterTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off
|
||||
|
||||
@@ -26,8 +26,13 @@ public:
|
||||
#endif
|
||||
|
||||
#if defined(P_LORA_TX_LED)
|
||||
void applyLedPolicy() override {
|
||||
NRF52Board::applyLedPolicy();
|
||||
if (!mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, LOW);
|
||||
}
|
||||
|
||||
void onBeforeTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
if (mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
#if defined(LED_BLUE)
|
||||
// turn off that annoying blue LED before transmitting
|
||||
digitalWrite(LED_BLUE, LOW);
|
||||
|
||||
@@ -24,7 +24,11 @@ public:
|
||||
void attachDynamicPrefs(KeyValueStore* prefs) { (void)prefs; } // no-op
|
||||
|
||||
#ifdef P_LORA_TX_LED
|
||||
void onBeforeTransmit() override { digitalWrite(P_LORA_TX_LED, HIGH); }
|
||||
void applyLedPolicy() override {
|
||||
if (!mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, LOW);
|
||||
}
|
||||
|
||||
void onBeforeTransmit() override { if (mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, HIGH); }
|
||||
void onAfterTransmit() override { digitalWrite(P_LORA_TX_LED, LOW); }
|
||||
#endif
|
||||
|
||||
|
||||
@@ -14,8 +14,13 @@ public:
|
||||
void begin();
|
||||
|
||||
#if defined(P_LORA_TX_LED) && defined(LED_STATE_ON)
|
||||
void applyLedPolicy() override {
|
||||
NRF52Board::applyLedPolicy();
|
||||
if (!mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, !LED_STATE_ON);
|
||||
}
|
||||
|
||||
void onBeforeTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, LED_STATE_ON); // turn TX LED on
|
||||
if (mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, LED_STATE_ON); // turn TX LED on
|
||||
}
|
||||
void onAfterTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, !LED_STATE_ON); // turn TX LED off
|
||||
@@ -53,7 +58,7 @@ public:
|
||||
|
||||
// set led on and wait for button release before poweroff
|
||||
#ifdef LED_PIN
|
||||
digitalWrite(LED_PIN, HIGH);
|
||||
if (mesh::leds::enabled()) digitalWrite(LED_PIN, HIGH);
|
||||
#endif
|
||||
#ifdef BUTTON_PIN
|
||||
// wismesh tag uses LOW to indicate button is pressed, wait until it goes HIGH to indicate it was released
|
||||
|
||||
@@ -19,8 +19,12 @@ public:
|
||||
|
||||
void attachDynamicPrefs(KeyValueStore* prefs) { (void)prefs; } // no-op
|
||||
|
||||
void applyLedPolicy() override {
|
||||
if (!mesh::leds::enabled()) digitalWrite(LED_BUILTIN, LOW);
|
||||
}
|
||||
|
||||
void onBeforeTransmit() override {
|
||||
digitalWrite(LED_BUILTIN, HIGH); // turn TX LED on
|
||||
if (mesh::leds::enabled()) digitalWrite(LED_BUILTIN, HIGH); // turn TX LED on
|
||||
}
|
||||
|
||||
void onAfterTransmit() override {
|
||||
|
||||
@@ -15,8 +15,13 @@ public:
|
||||
void begin();
|
||||
|
||||
#if defined(P_LORA_TX_LED)
|
||||
void applyLedPolicy() override {
|
||||
NRF52Board::applyLedPolicy();
|
||||
if (!mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, LOW);
|
||||
}
|
||||
|
||||
void onBeforeTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
if (mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
}
|
||||
void onAfterTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off
|
||||
|
||||
@@ -73,7 +73,7 @@ public:
|
||||
#endif
|
||||
// set led on and wait for button release before poweroff
|
||||
#ifdef LED_PIN
|
||||
digitalWrite(LED_PIN, HIGH);
|
||||
if (mesh::leds::enabled()) digitalWrite(LED_PIN, HIGH);
|
||||
#endif
|
||||
#ifdef BUTTON_PIN
|
||||
while(digitalRead(BUTTON_PIN));
|
||||
|
||||
@@ -20,8 +20,13 @@ public:
|
||||
uint16_t getBattMilliVolts() override;
|
||||
|
||||
#if defined(P_LORA_TX_LED)
|
||||
void applyLedPolicy() override {
|
||||
NRF52Board::applyLedPolicy();
|
||||
if (!mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, LOW);
|
||||
}
|
||||
|
||||
void onBeforeTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
if (mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
}
|
||||
void onAfterTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off
|
||||
|
||||
@@ -19,8 +19,13 @@ public:
|
||||
uint16_t getBattMilliVolts() override;
|
||||
|
||||
#ifdef P_LORA_TX_LED
|
||||
void applyLedPolicy() override {
|
||||
NRF52Board::applyLedPolicy();
|
||||
if (!mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, !LED_STATE_ON);
|
||||
}
|
||||
|
||||
void onBeforeTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, LED_STATE_ON); // turn TX LED on
|
||||
if (mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, LED_STATE_ON); // turn TX LED on
|
||||
}
|
||||
void onAfterTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, !LED_STATE_ON); // turn TX LED off
|
||||
|
||||
@@ -20,8 +20,13 @@ public:
|
||||
uint16_t getBattMilliVolts() override;
|
||||
|
||||
#ifdef P_LORA_TX_LED
|
||||
void applyLedPolicy() override {
|
||||
NRF52Board::applyLedPolicy();
|
||||
if (!mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, !LED_STATE_ON);
|
||||
}
|
||||
|
||||
void onBeforeTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, LED_STATE_ON); // turn TX LED on
|
||||
if (mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, LED_STATE_ON); // turn TX LED on
|
||||
}
|
||||
void onAfterTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, !LED_STATE_ON); // turn TX LED off
|
||||
@@ -48,7 +53,7 @@ public:
|
||||
NRF52Board::shutdownPeripherals();
|
||||
|
||||
#ifdef LED_STATUS
|
||||
digitalWrite(LED_STATUS, HIGH);
|
||||
if (mesh::leds::enabled()) digitalWrite(LED_STATUS, HIGH);
|
||||
#endif
|
||||
#ifdef BUTTON_PIN
|
||||
while(!digitalRead(BUTTON_PIN));
|
||||
|
||||
@@ -15,8 +15,12 @@ public:
|
||||
uint16_t getBattMilliVolts() override;
|
||||
const char* getManufacturerName() const override ;
|
||||
|
||||
void applyLedPolicy() override {
|
||||
if (!mesh::leds::enabled()) expander.digitalWrite(EXP_PIN_LED, LOW);
|
||||
}
|
||||
|
||||
void onBeforeTransmit() override {
|
||||
expander.digitalWrite(EXP_PIN_LED, HIGH); // turn TX LED on
|
||||
if (mesh::leds::enabled()) expander.digitalWrite(EXP_PIN_LED, HIGH); // turn TX LED on
|
||||
}
|
||||
void onAfterTransmit() override {
|
||||
expander.digitalWrite(EXP_PIN_LED, LOW); // turn TX LED off
|
||||
|
||||
@@ -24,8 +24,13 @@ public:
|
||||
uint16_t getBattMilliVolts() override;
|
||||
|
||||
#if defined(P_LORA_TX_LED)
|
||||
void applyLedPolicy() override {
|
||||
NRF52Board::applyLedPolicy();
|
||||
if (!mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, LOW);
|
||||
}
|
||||
|
||||
void onBeforeTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
if (mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
}
|
||||
void onAfterTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off
|
||||
|
||||
@@ -12,8 +12,12 @@ class ThinkNodeM7Board : public ESP32Board {
|
||||
public:
|
||||
void begin();
|
||||
const char* getManufacturerName() const override;
|
||||
void applyLedPolicy() override {
|
||||
if (!mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, HIGH);
|
||||
}
|
||||
|
||||
void onBeforeTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, LOW);
|
||||
if (mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, LOW);
|
||||
}
|
||||
void onAfterTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, HIGH);
|
||||
|
||||
@@ -20,8 +20,13 @@ public:
|
||||
uint16_t getBattMilliVolts() override;
|
||||
|
||||
#if defined(P_LORA_TX_LED)
|
||||
void applyLedPolicy() override {
|
||||
NRF52Board::applyLedPolicy();
|
||||
if (!mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, LOW);
|
||||
}
|
||||
|
||||
void onBeforeTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
if (mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
}
|
||||
void onAfterTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off
|
||||
|
||||
@@ -36,7 +36,11 @@ public:
|
||||
void attachDynamicPrefs(KeyValueStore* prefs) { (void)prefs; } // no-op
|
||||
|
||||
#ifdef P_LORA_TX_LED
|
||||
void onBeforeTransmit() override { digitalWrite(P_LORA_TX_LED, HIGH); }
|
||||
void applyLedPolicy() override {
|
||||
if (!mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, LOW);
|
||||
}
|
||||
|
||||
void onBeforeTransmit() override { if (mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, HIGH); }
|
||||
void onAfterTransmit() override { digitalWrite(P_LORA_TX_LED, LOW); }
|
||||
#endif
|
||||
|
||||
|
||||
@@ -19,8 +19,13 @@ public:
|
||||
bool canControlLoRaFemPaGain() const override { return true; }
|
||||
|
||||
#if defined(P_LORA_TX_LED)
|
||||
void applyLedPolicy() override {
|
||||
NRF52Board::applyLedPolicy();
|
||||
if (!mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, LOW);
|
||||
}
|
||||
|
||||
void onBeforeTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
if (mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
}
|
||||
void onAfterTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off
|
||||
|
||||
@@ -13,8 +13,13 @@ public:
|
||||
void begin();
|
||||
|
||||
#if defined(P_LORA_TX_LED)
|
||||
void applyLedPolicy() override {
|
||||
NRF52Board::applyLedPolicy();
|
||||
if (!mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, LOW);
|
||||
}
|
||||
|
||||
void onBeforeTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
if (mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
}
|
||||
void onAfterTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off
|
||||
|
||||
@@ -146,7 +146,7 @@ void WioTrackerL2Board::setLed(bool on) {
|
||||
// P10 doubles as GNSS wakeup and idles HIGH; blink = brief LOW pulses so
|
||||
// the GPS never sees a sustained low level
|
||||
if (expander_ok) {
|
||||
expWritePin(EXP_PIN_USER_LED, !on);
|
||||
expWritePin(EXP_PIN_USER_LED, !(on && mesh::leds::enabled()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,9 @@ public:
|
||||
|
||||
void onBeforeTransmit() override { setLed(true); }
|
||||
void onAfterTransmit() override { setLed(false); }
|
||||
void applyLedPolicy() override {
|
||||
if (!mesh::leds::enabled()) setLed(false);
|
||||
}
|
||||
|
||||
// Mesh/user LED lives on the IO expander, not a GPIO
|
||||
void setLed(bool on);
|
||||
|
||||
@@ -13,7 +13,7 @@ void WioWM1110Board::begin() {
|
||||
pinMode(LED_RED, OUTPUT);
|
||||
pinMode(SENSOR_POWER_PIN, OUTPUT);
|
||||
|
||||
digitalWrite(LED_GREEN, HIGH);
|
||||
digitalWrite(LED_GREEN, mesh::leds::enabled() ? HIGH : LOW);
|
||||
digitalWrite(LED_RED, LOW);
|
||||
digitalWrite(SENSOR_POWER_PIN, LOW);
|
||||
|
||||
|
||||
@@ -17,8 +17,14 @@ public:
|
||||
void begin();
|
||||
|
||||
#if defined(LED_GREEN)
|
||||
void applyLedPolicy() override {
|
||||
NRF52Board::applyLedPolicy();
|
||||
digitalWrite(LED_GREEN, mesh::leds::enabled() ? HIGH : LOW);
|
||||
if (!mesh::leds::enabled()) digitalWrite(LED_RED, LOW);
|
||||
}
|
||||
|
||||
void onBeforeTransmit() override {
|
||||
digitalWrite(LED_RED, HIGH);
|
||||
if (mesh::leds::enabled()) digitalWrite(LED_RED, HIGH);
|
||||
}
|
||||
void onAfterTransmit() override {
|
||||
digitalWrite(LED_RED, LOW);
|
||||
|
||||
@@ -42,7 +42,7 @@ public:
|
||||
#if defined(LORA_TX_BOOST_PIN) || defined(P_LORA_TX_LED)
|
||||
void onBeforeTransmit() override {
|
||||
#if defined(P_LORA_TX_LED)
|
||||
digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
if (mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
#endif
|
||||
#if defined(LORA_TX_BOOST_PIN)
|
||||
digitalWrite(LORA_TX_BOOST_PIN, LOW);
|
||||
|
||||
@@ -21,8 +21,13 @@ public:
|
||||
void begin();
|
||||
|
||||
#if defined(P_LORA_TX_LED)
|
||||
void applyLedPolicy() override {
|
||||
NRF52Board::applyLedPolicy();
|
||||
if (!mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, HIGH);
|
||||
}
|
||||
|
||||
void onBeforeTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED on
|
||||
if (mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED on
|
||||
}
|
||||
void onAfterTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED off
|
||||
@@ -43,7 +48,7 @@ public:
|
||||
NRF52Board::shutdownPeripherals();
|
||||
|
||||
// set led on and wait for button release before poweroff
|
||||
digitalWrite(PIN_LED, LOW);
|
||||
if (mesh::leds::enabled()) digitalWrite(PIN_LED, LOW);
|
||||
#ifdef PIN_USER_BTN
|
||||
while(digitalRead(PIN_USER_BTN) == USER_BTN_PRESSED);
|
||||
#endif
|
||||
|
||||
@@ -34,7 +34,11 @@ public:
|
||||
void attachDynamicPrefs(KeyValueStore* prefs) { (void)prefs; } // no-op
|
||||
|
||||
#ifdef P_LORA_TX_LED
|
||||
void onBeforeTransmit() override { digitalWrite(P_LORA_TX_LED, HIGH); }
|
||||
void applyLedPolicy() override {
|
||||
if (!mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, LOW);
|
||||
}
|
||||
|
||||
void onBeforeTransmit() override { if (mesh::leds::enabled()) digitalWrite(P_LORA_TX_LED, HIGH); }
|
||||
void onAfterTransmit() override { digitalWrite(P_LORA_TX_LED, LOW); }
|
||||
#endif
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ build_flags = ${esp32_base.build_flags}
|
||||
-D P_LORA_MOSI=9 ; D10
|
||||
-D PIN_USER_BTN=-1 ; NC
|
||||
-D PIN_STATUS_LED=21 ; Orange user led, LOW=On
|
||||
-D LED_STATE_ON=LOW
|
||||
-D PIN_BOARD_SDA=D6 ; D6=43
|
||||
-D PIN_BOARD_SCL=D7 ; D7=44
|
||||
-D SX126X_RXEN=6 ; D5
|
||||
|
||||
@@ -24,7 +24,8 @@ EnvironmentSensorManager sensors;
|
||||
bool radio_init() {
|
||||
fallback_clock.begin();
|
||||
rtc_clock.begin(Wire);
|
||||
pinMode(21, INPUT);
|
||||
digitalWrite(PIN_STATUS_LED, !LED_STATE_ON);
|
||||
pinMode(PIN_STATUS_LED, OUTPUT);
|
||||
pinMode(48, OUTPUT);
|
||||
|
||||
#if defined(P_LORA_SCLK)
|
||||
|
||||
Reference in new issue
Block a user