Merge branch 'master' into storeforward

This commit is contained in:
Ben Meadors 2025-07-26 20:01:19 -05:00 committed by GitHub
commit 99ddf409be
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 267 additions and 31 deletions

@ -1 +1 @@
Subproject commit d31cd890d58ffa7e3524e0685a8617bbd181a1c6
Subproject commit 9bac2886f9344f25716921467a82e8b0326107cd

View File

@ -146,7 +146,7 @@ class MeshService
virtual void sendMqttMessageToClientProxy(meshtastic_MqttClientProxyMessage *m);
/// Send a ClientNotification to the phone
void sendClientNotification(meshtastic_ClientNotification *cn);
virtual void sendClientNotification(meshtastic_ClientNotification *cn);
/// Send an error response to the phone
void sendRoutingErrorResponse(meshtastic_Routing_Error error, const meshtastic_MeshPacket *mp);

View File

@ -362,7 +362,7 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg;
#define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_BackupPreferences_size
#define meshtastic_BackupPreferences_size 2271
#define meshtastic_ChannelFile_size 718
#define meshtastic_DeviceState_size 1724
#define meshtastic_DeviceState_size 1728
#define meshtastic_NodeInfoLite_size 196
#define meshtastic_PositionLite_size 28
#define meshtastic_UserLite_size 98

View File

@ -935,6 +935,9 @@ typedef struct _meshtastic_MyNodeInfo {
char pio_env[40];
/* The indicator for whether this device is running event firmware and which */
meshtastic_FirmwareEdition firmware_edition;
/* The number of nodes in the nodedb.
This is used by the phone to know how many NodeInfo packets to expect on want_config */
uint16_t nodedb_count;
} meshtastic_MyNodeInfo;
/* Debug output from the device.
@ -1322,7 +1325,7 @@ extern "C" {
#define meshtastic_MqttClientProxyMessage_init_default {"", 0, {{0, {0}}}, 0}
#define meshtastic_MeshPacket_init_default {0, 0, 0, 0, {meshtastic_Data_init_default}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0, 0}
#define meshtastic_NodeInfo_init_default {0, false, meshtastic_User_init_default, false, meshtastic_Position_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0, 0, 0}
#define meshtastic_MyNodeInfo_init_default {0, 0, 0, {0, {0}}, "", _meshtastic_FirmwareEdition_MIN}
#define meshtastic_MyNodeInfo_init_default {0, 0, 0, {0, {0}}, "", _meshtastic_FirmwareEdition_MIN, 0}
#define meshtastic_LogRecord_init_default {"", 0, "", _meshtastic_LogRecord_Level_MIN}
#define meshtastic_QueueStatus_init_default {0, 0, 0, 0}
#define meshtastic_FromRadio_init_default {0, 0, {meshtastic_MeshPacket_init_default}}
@ -1353,7 +1356,7 @@ extern "C" {
#define meshtastic_MqttClientProxyMessage_init_zero {"", 0, {{0, {0}}}, 0}
#define meshtastic_MeshPacket_init_zero {0, 0, 0, 0, {meshtastic_Data_init_zero}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0, 0}
#define meshtastic_NodeInfo_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_Position_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0, 0, 0}
#define meshtastic_MyNodeInfo_init_zero {0, 0, 0, {0, {0}}, "", _meshtastic_FirmwareEdition_MIN}
#define meshtastic_MyNodeInfo_init_zero {0, 0, 0, {0, {0}}, "", _meshtastic_FirmwareEdition_MIN, 0}
#define meshtastic_LogRecord_init_zero {"", 0, "", _meshtastic_LogRecord_Level_MIN}
#define meshtastic_QueueStatus_init_zero {0, 0, 0, 0}
#define meshtastic_FromRadio_init_zero {0, 0, {meshtastic_MeshPacket_init_zero}}
@ -1477,6 +1480,7 @@ extern "C" {
#define meshtastic_MyNodeInfo_device_id_tag 12
#define meshtastic_MyNodeInfo_pio_env_tag 13
#define meshtastic_MyNodeInfo_firmware_edition_tag 14
#define meshtastic_MyNodeInfo_nodedb_count_tag 15
#define meshtastic_LogRecord_message_tag 1
#define meshtastic_LogRecord_time_tag 2
#define meshtastic_LogRecord_source_tag 3
@ -1710,7 +1714,8 @@ X(a, STATIC, SINGULAR, UINT32, reboot_count, 8) \
X(a, STATIC, SINGULAR, UINT32, min_app_version, 11) \
X(a, STATIC, SINGULAR, BYTES, device_id, 12) \
X(a, STATIC, SINGULAR, STRING, pio_env, 13) \
X(a, STATIC, SINGULAR, UENUM, firmware_edition, 14)
X(a, STATIC, SINGULAR, UENUM, firmware_edition, 14) \
X(a, STATIC, SINGULAR, UINT32, nodedb_count, 15)
#define meshtastic_MyNodeInfo_CALLBACK NULL
#define meshtastic_MyNodeInfo_DEFAULT NULL
@ -1993,7 +1998,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg;
#define meshtastic_LowEntropyKey_size 0
#define meshtastic_MeshPacket_size 378
#define meshtastic_MqttClientProxyMessage_size 501
#define meshtastic_MyNodeInfo_size 79
#define meshtastic_MyNodeInfo_size 83
#define meshtastic_NeighborInfo_size 258
#define meshtastic_Neighbor_size 22
#define meshtastic_NodeInfo_size 323

View File

@ -43,6 +43,10 @@
#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C
#include "motion/AccelerometerThread.h"
#endif
#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \
!defined(CONFIG_IDF_TARGET_ESP32C3)
#include "SerialModule.h"
#endif
AdminModule *adminModule;
bool hasOpenEditTransaction;
@ -638,7 +642,16 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
case meshtastic_Config_position_tag:
LOG_INFO("Set config: Position");
config.has_position = true;
// If we have turned off the GPS (disabled or not present) and we're not using fixed position,
// clear the stored position since it may not get updated
if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED &&
c.payload_variant.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED &&
config.position.fixed_position == false && c.payload_variant.position.fixed_position == false) {
nodeDB->clearLocalPosition();
saveChanges(SEGMENT_NODEDATABASE | SEGMENT_CONFIG, false);
}
config.position = c.payload_variant.position;
// Save nodedb as well in case we got a fixed position packet
break;
case meshtastic_Config_power_tag:
@ -798,8 +811,13 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
bool AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c)
{
if (!hasOpenEditTransaction)
// If we are in an open transaction or configuring MQTT or Serial (which have validation), defer disabling Bluetooth
// Otherwise, disable Bluetooth to prevent the phone from interfering with the config
if (!hasOpenEditTransaction &&
!IS_ONE_OF(c.which_payload_variant, meshtastic_ModuleConfig_mqtt_tag, meshtastic_ModuleConfig_serial_tag)) {
disableBluetooth();
}
switch (c.which_payload_variant) {
case meshtastic_ModuleConfig_mqtt_tag:
#if MESHTASTIC_EXCLUDE_MQTT
@ -810,12 +828,22 @@ bool AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c)
if (!MQTT::isValidConfig(c.payload_variant.mqtt)) {
return false;
}
// Disable Bluetooth to prevent interference during MQTT configuration
disableBluetooth();
moduleConfig.has_mqtt = true;
moduleConfig.mqtt = c.payload_variant.mqtt;
#endif
break;
case meshtastic_ModuleConfig_serial_tag:
LOG_INFO("Set module config: Serial");
#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \
!defined(CONFIG_IDF_TARGET_ESP32C3)
if (!SerialModule::isValidConfig(c.payload_variant.serial)) {
LOG_ERROR("Invalid serial config");
return false;
}
disableBluetooth(); // Disable Bluetooth to prevent interference during Serial configuration
#endif
moduleConfig.has_serial = true;
moduleConfig.serial = c.payload_variant.serial;
break;
@ -971,9 +999,10 @@ void AdminModule::handleGetConfig(const meshtastic_MeshPacket &req, const uint32
// So even if we internally use 0 to represent 'use default' we still need to send the value we are
// using to the app (so that even old phone apps work with new device loads).
// r.get_radio_response.preferences.ls_secs = getPref_ls_secs();
// hideSecret(r.get_radio_response.preferences.wifi_ssid); // hmm - leave public for now, because only minimally private
// and useful for users to know current provisioning) hideSecret(r.get_radio_response.preferences.wifi_password);
// r.get_config_response.which_payloadVariant = Config_ModuleConfig_telemetry_tag;
// hideSecret(r.get_radio_response.preferences.wifi_ssid); // hmm - leave public for now, because only minimally
// private and useful for users to know current provisioning)
// hideSecret(r.get_radio_response.preferences.wifi_password); r.get_config_response.which_payloadVariant =
// Config_ModuleConfig_telemetry_tag;
res.which_payload_variant = meshtastic_AdminMessage_get_config_response_tag;
setPassKey(&res);
myReply = allocDataProtobuf(res);
@ -1057,9 +1086,10 @@ void AdminModule::handleGetModuleConfig(const meshtastic_MeshPacket &req, const
// So even if we internally use 0 to represent 'use default' we still need to send the value we are
// using to the app (so that even old phone apps work with new device loads).
// r.get_radio_response.preferences.ls_secs = getPref_ls_secs();
// hideSecret(r.get_radio_response.preferences.wifi_ssid); // hmm - leave public for now, because only minimally private
// and useful for users to know current provisioning) hideSecret(r.get_radio_response.preferences.wifi_password);
// r.get_config_response.which_payloadVariant = Config_ModuleConfig_telemetry_tag;
// hideSecret(r.get_radio_response.preferences.wifi_ssid); // hmm - leave public for now, because only minimally
// private and useful for users to know current provisioning)
// hideSecret(r.get_radio_response.preferences.wifi_password); r.get_config_response.which_payloadVariant =
// Config_ModuleConfig_telemetry_tag;
res.which_payload_variant = meshtastic_AdminMessage_get_module_config_response_tag;
setPassKey(&res);
myReply = allocDataProtobuf(res);

View File

@ -74,6 +74,26 @@ static Print *serialPrint = &Serial2;
char serialBytes[512];
size_t serialPayloadSize;
bool SerialModule::isValidConfig(const meshtastic_ModuleConfig_SerialConfig &config)
{
if (config.override_console_serial_port && !IS_ONE_OF(config.mode, meshtastic_ModuleConfig_SerialConfig_Serial_Mode_NMEA,
meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO)) {
const char *warning =
"Invalid Serial config: override console serial port is only supported in NMEA and CalTopo output-only modes.";
LOG_ERROR(warning);
#if !IS_RUNNING_TESTS
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
cn->level = meshtastic_LogRecord_Level_ERROR;
cn->time = getValidTime(RTCQualityFromNet);
snprintf(cn->message, sizeof(cn->message), "%s", warning);
service->sendClientNotification(cn);
#endif
return false;
}
return true;
}
SerialModuleRadio::SerialModuleRadio() : MeshModule("SerialModuleRadio")
{
switch (moduleConfig.serial.mode) {

View File

@ -20,6 +20,8 @@ class SerialModule : public StreamAPI, private concurrency::OSThread
public:
SerialModule();
static bool isValidConfig(const meshtastic_ModuleConfig_SerialConfig &config);
protected:
virtual int32_t runOnce() override;

View File

@ -89,6 +89,11 @@ int SystemCommandsModule::handleInputEvent(const InputEvent *event)
#if !MESHTASTIC_EXCLUDE_GPS
if (gps) {
LOG_WARN("GPS Toggle2");
if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED &&
config.position.fixed_position == false) {
nodeDB->clearLocalPosition();
nodeDB->saveToDisk();
}
gps->toggleGpsMode();
const char *msg =
(config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) ? "GPS Enabled" : "GPS Disabled";

View File

@ -39,6 +39,7 @@
#include <machine/endian.h>
#define ntohl __ntohl
#endif
#include <RTC.h>
MQTT *mqtt;
@ -624,18 +625,32 @@ bool MQTT::isValidConfig(const meshtastic_ModuleConfig_MQTTConfig &config, MQTTC
return connectPubSub(parsed, *pubSub, (client != nullptr) ? *client : *clientConnection);
}
#else
LOG_ERROR("Invalid MQTT config: proxy_to_client_enabled must be enabled on nodes that do not have a network");
const char *warning = "Invalid MQTT config: proxy_to_client_enabled must be enabled on nodes that do not have a network";
LOG_ERROR(warning);
#if !IS_RUNNING_TESTS
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
cn->level = meshtastic_LogRecord_Level_ERROR;
cn->time = getValidTime(RTCQualityFromNet);
strncpy(cn->message, warning, sizeof(cn->message) - 1);
cn->message[sizeof(cn->message) - 1] = '\0'; // Ensure null termination
service->sendClientNotification(cn);
#endif
return false;
#endif
}
const bool defaultServer = isDefaultServer(parsed.serverAddr);
if (defaultServer && config.tls_enabled) {
LOG_ERROR("Invalid MQTT config: TLS was enabled, but the default server does not support TLS");
return false;
}
if (defaultServer && parsed.serverPort != PubSubConfig::defaultPort) {
LOG_ERROR("Invalid MQTT config: Unsupported port '%d' for the default MQTT server", parsed.serverPort);
const char *warning = "Invalid MQTT config: default server address must not have a port specified";
LOG_ERROR(warning);
#if !IS_RUNNING_TESTS
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
cn->level = meshtastic_LogRecord_Level_ERROR;
cn->time = getValidTime(RTCQualityFromNet);
strncpy(cn->message, warning, sizeof(cn->message) - 1);
cn->message[sizeof(cn->message) - 1] = '\0'; // Ensure null termination
service->sendClientNotification(cn);
#endif
return false;
}
return true;

View File

@ -27,6 +27,12 @@
#include <utility>
#include <variant>
#if defined(UNIT_TEST)
#define IS_RUNNING_TESTS 1
#else
#define IS_RUNNING_TESTS 0
#endif
namespace
{
// Minimal router needed to receive messages from MQTT.
@ -56,7 +62,13 @@ class MockMeshService : public MeshService
messages_.emplace_back(*m);
releaseMqttClientProxyMessageToPool(m);
}
std::list<meshtastic_MqttClientProxyMessage> messages_; // Messages received from the MeshService.
void sendClientNotification(meshtastic_ClientNotification *n) override
{
notifications_.emplace_back(*n);
releaseClientNotificationToPool(n);
}
std::list<meshtastic_MqttClientProxyMessage> messages_; // Messages received from the MeshService.
std::list<meshtastic_ClientNotification> notifications_; // Notifications received from the MeshService.
};
// Minimal NodeDB needed to return values from getMeshNode.
@ -823,14 +835,6 @@ void test_configWithDefaultServerAndInvalidPort(void)
TEST_ASSERT_FALSE(MQTT::isValidConfig(config));
}
// Configuration with the default server and tls_enabled = true is invalid.
void test_configWithDefaultServerAndInvalidTLSEnabled(void)
{
meshtastic_ModuleConfig_MQTTConfig config = {.tls_enabled = true};
TEST_ASSERT_FALSE(MQTT::isValidConfig(config));
}
// isValidConfig connects to a custom host and port.
void test_configCustomHostAndPort(void)
{
@ -911,7 +915,6 @@ void setup()
RUN_TEST(test_configEnabledEmptyIsValid);
RUN_TEST(test_configWithDefaultServer);
RUN_TEST(test_configWithDefaultServerAndInvalidPort);
RUN_TEST(test_configWithDefaultServerAndInvalidTLSEnabled);
RUN_TEST(test_configCustomHostAndPort);
RUN_TEST(test_configWithConnectionFailure);
RUN_TEST(test_configWithTLSEnabled);

View File

@ -0,0 +1,156 @@
#include "DebugConfiguration.h"
#include "TestUtil.h"
#include <unity.h>
#ifdef ARCH_PORTDUINO
#include "configuration.h"
#if defined(UNIT_TEST)
#define IS_RUNNING_TESTS 1
#else
#define IS_RUNNING_TESTS 0
#endif
#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \
!defined(CONFIG_IDF_TARGET_ESP32C3)
#include "modules/SerialModule.h"
#endif
#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \
!defined(CONFIG_IDF_TARGET_ESP32C3)
// Test that empty configuration is valid.
void test_serialConfigEmptyIsValid(void)
{
meshtastic_ModuleConfig_SerialConfig config = {};
TEST_ASSERT_TRUE(SerialModule::isValidConfig(config));
}
// Test that basic enabled configuration is valid.
void test_serialConfigEnabledIsValid(void)
{
meshtastic_ModuleConfig_SerialConfig config = {.enabled = true};
TEST_ASSERT_TRUE(SerialModule::isValidConfig(config));
}
// Test that configuration with override_console_serial_port and NMEA mode is valid.
void test_serialConfigWithOverrideConsoleNmeaModeIsValid(void)
{
meshtastic_ModuleConfig_SerialConfig config = {
.enabled = true, .override_console_serial_port = true, .mode = meshtastic_ModuleConfig_SerialConfig_Serial_Mode_NMEA};
TEST_ASSERT_TRUE(SerialModule::isValidConfig(config));
}
// Test that configuration with override_console_serial_port and CalTopo mode is valid.
void test_serialConfigWithOverrideConsoleCalTopoModeIsValid(void)
{
meshtastic_ModuleConfig_SerialConfig config = {
.enabled = true, .override_console_serial_port = true, .mode = meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO};
TEST_ASSERT_TRUE(SerialModule::isValidConfig(config));
}
// Test that configuration with override_console_serial_port and DEFAULT mode is invalid.
void test_serialConfigWithOverrideConsoleDefaultModeIsInvalid(void)
{
meshtastic_ModuleConfig_SerialConfig config = {
.enabled = true, .override_console_serial_port = true, .mode = meshtastic_ModuleConfig_SerialConfig_Serial_Mode_DEFAULT};
TEST_ASSERT_FALSE(SerialModule::isValidConfig(config));
}
// Test that configuration with override_console_serial_port and SIMPLE mode is invalid.
void test_serialConfigWithOverrideConsoleSimpleModeIsInvalid(void)
{
meshtastic_ModuleConfig_SerialConfig config = {
.enabled = true, .override_console_serial_port = true, .mode = meshtastic_ModuleConfig_SerialConfig_Serial_Mode_SIMPLE};
TEST_ASSERT_FALSE(SerialModule::isValidConfig(config));
}
// Test that configuration with override_console_serial_port and TEXTMSG mode is invalid.
void test_serialConfigWithOverrideConsoleTextMsgModeIsInvalid(void)
{
meshtastic_ModuleConfig_SerialConfig config = {
.enabled = true, .override_console_serial_port = true, .mode = meshtastic_ModuleConfig_SerialConfig_Serial_Mode_TEXTMSG};
TEST_ASSERT_FALSE(SerialModule::isValidConfig(config));
}
// Test that configuration with override_console_serial_port and PROTO mode is invalid.
void test_serialConfigWithOverrideConsoleProtoModeIsInvalid(void)
{
meshtastic_ModuleConfig_SerialConfig config = {
.enabled = true, .override_console_serial_port = true, .mode = meshtastic_ModuleConfig_SerialConfig_Serial_Mode_PROTO};
TEST_ASSERT_FALSE(SerialModule::isValidConfig(config));
}
// Test that various modes work without override_console_serial_port.
void test_serialConfigVariousModesWithoutOverrideAreValid(void)
{
meshtastic_ModuleConfig_SerialConfig config = {.enabled = true, .override_console_serial_port = false};
// Test DEFAULT mode
config.mode = meshtastic_ModuleConfig_SerialConfig_Serial_Mode_DEFAULT;
TEST_ASSERT_TRUE(SerialModule::isValidConfig(config));
// Test SIMPLE mode
config.mode = meshtastic_ModuleConfig_SerialConfig_Serial_Mode_SIMPLE;
TEST_ASSERT_TRUE(SerialModule::isValidConfig(config));
// Test TEXTMSG mode
config.mode = meshtastic_ModuleConfig_SerialConfig_Serial_Mode_TEXTMSG;
TEST_ASSERT_TRUE(SerialModule::isValidConfig(config));
// Test PROTO mode
config.mode = meshtastic_ModuleConfig_SerialConfig_Serial_Mode_PROTO;
TEST_ASSERT_TRUE(SerialModule::isValidConfig(config));
// Test NMEA mode
config.mode = meshtastic_ModuleConfig_SerialConfig_Serial_Mode_NMEA;
TEST_ASSERT_TRUE(SerialModule::isValidConfig(config));
// Test CALTOPO mode
config.mode = meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO;
TEST_ASSERT_TRUE(SerialModule::isValidConfig(config));
}
#endif // Architecture check
void setup()
{
initializeTestEnvironment();
#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \
!defined(CONFIG_IDF_TARGET_ESP32C3)
UNITY_BEGIN();
RUN_TEST(test_serialConfigEmptyIsValid);
RUN_TEST(test_serialConfigEnabledIsValid);
RUN_TEST(test_serialConfigWithOverrideConsoleNmeaModeIsValid);
RUN_TEST(test_serialConfigWithOverrideConsoleCalTopoModeIsValid);
RUN_TEST(test_serialConfigWithOverrideConsoleDefaultModeIsInvalid);
RUN_TEST(test_serialConfigWithOverrideConsoleSimpleModeIsInvalid);
RUN_TEST(test_serialConfigWithOverrideConsoleTextMsgModeIsInvalid);
RUN_TEST(test_serialConfigWithOverrideConsoleProtoModeIsInvalid);
RUN_TEST(test_serialConfigVariousModesWithoutOverrideAreValid);
exit(UNITY_END());
#else
LOG_WARN("This test requires ESP32, NRF52, or RP2040 architecture");
UNITY_BEGIN();
UNITY_END();
#endif
}
#else
void setup()
{
initializeTestEnvironment();
LOG_WARN("This test requires the ARCH_PORTDUINO variant");
UNITY_BEGIN();
UNITY_END();
}
#endif
void loop() {}

View File

@ -10,4 +10,4 @@ build_flags = ${nrf52840_base.build_flags}
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/diy/nrf52_promicro_diy_tcxo>
lib_deps =
${nrf52840_base.lib_deps}
debug_tool = jlink
debug_tool = jlink