Merge branch 'dev' into t114-power-consumption
This commit is contained in:
@@ -15,28 +15,14 @@ src_filter = [
|
|||||||
# add build and include dirs according to CPPDEFINES
|
# add build and include dirs according to CPPDEFINES
|
||||||
for item in menv.get("CPPDEFINES", []):
|
for item in menv.get("CPPDEFINES", []):
|
||||||
|
|
||||||
# STM32
|
# PLATFORM HANDLING
|
||||||
if isinstance(item, str) and item == "STM32_PLATFORM":
|
if item == "STM32_PLATFORM":
|
||||||
menv.Append(CPPPATH=[realpath("src/helpers/stm32")])
|
|
||||||
menv.Append(BUILD_FLAGS=["-I src/helpers/stm32"])
|
|
||||||
src_filter.append("+<helpers/stm32/*>")
|
src_filter.append("+<helpers/stm32/*>")
|
||||||
|
elif item == "ESP32":
|
||||||
# ESP32
|
|
||||||
elif isinstance(item, str) and item == "ESP32":
|
|
||||||
menv.Append(CPPPATH=[realpath("src/helpers/esp32")])
|
|
||||||
menv.Append(BUILD_FLAGS=["-I src/helpers/esp32"])
|
|
||||||
src_filter.append("+<helpers/esp32/*>")
|
src_filter.append("+<helpers/esp32/*>")
|
||||||
|
elif item == "NRF52_PLATFORM":
|
||||||
# NRF52
|
|
||||||
elif isinstance(item, str) and item == "NRF52_PLATFORM":
|
|
||||||
menv.Append(CPPPATH=[realpath("src/helpers/nrf52")])
|
|
||||||
menv.Append(BUILD_FLAGS=["-I src/helpers/nrf52"])
|
|
||||||
src_filter.append("+<helpers/nrf52/*>")
|
src_filter.append("+<helpers/nrf52/*>")
|
||||||
|
elif item == "RP2040_PLATFORM":
|
||||||
# RP2040
|
|
||||||
elif isinstance(item, str) and item == "RP2040_PLATFORM":
|
|
||||||
menv.Append(CPPPATH=[realpath("src/helpers/rp2040")])
|
|
||||||
menv.Append(BUILD_FLAGS=["-I src/helpers/rp2040"])
|
|
||||||
src_filter.append("+<helpers/rp2040/*>")
|
src_filter.append("+<helpers/rp2040/*>")
|
||||||
|
|
||||||
# DISPLAY HANDLING
|
# DISPLAY HANDLING
|
||||||
@@ -50,19 +36,29 @@ for item in menv.get("CPPDEFINES", []):
|
|||||||
# VARIANTS HANDLING
|
# VARIANTS HANDLING
|
||||||
elif isinstance(item, tuple) and item[0] == "MC_VARIANT":
|
elif isinstance(item, tuple) and item[0] == "MC_VARIANT":
|
||||||
variant_name = item[1]
|
variant_name = item[1]
|
||||||
menv.Append(BUILD_FLAGS=[f"-I variants/{variant_name}"])
|
|
||||||
src_filter.append(f"+<../variants/{variant_name}>")
|
src_filter.append(f"+<../variants/{variant_name}>")
|
||||||
|
|
||||||
# INCLUDE EXAMPLE CODE IN BUILD (to provide your own support files without touching the tree)
|
# INCLUDE EXAMPLE CODE IN BUILD (to provide your own support files without touching the tree)
|
||||||
elif isinstance(item, tuple) and item[0] == "BUILD_EXAMPLE":
|
elif isinstance(item, tuple) and item[0] == "BUILD_EXAMPLE":
|
||||||
example_name = item[1]
|
example_name = item[1]
|
||||||
src_filter.append(f"+<../examples/{example_name}>")
|
src_filter.append(f"+<../examples/{example_name}/*.cpp>")
|
||||||
|
|
||||||
# EXCLUDE A SOURCE FILE FROM AN EXAMPLE (must be placed after example name or boom)
|
# EXCLUDE A SOURCE FILE FROM AN EXAMPLE (must be placed after example name or boom)
|
||||||
elif isinstance(item, tuple) and item[0] == "EXCLUDE_FROM_EXAMPLE":
|
elif isinstance(item, tuple) and item[0] == "EXCLUDE_FROM_EXAMPLE":
|
||||||
exclude_name = item[1]
|
exclude_name = item[1]
|
||||||
|
if example_name is None:
|
||||||
|
print("***** PLEASE DEFINE EXAMPLE FIRST *****")
|
||||||
|
break
|
||||||
src_filter.append(f"-<../examples/{example_name}/{exclude_name}>")
|
src_filter.append(f"-<../examples/{example_name}/{exclude_name}>")
|
||||||
|
|
||||||
|
# DEAL WITH UI VARIANT FOR AN EXAMPLE
|
||||||
|
elif isinstance(item, tuple) and item[0] == "MC_UI_FLAVOR":
|
||||||
|
ui_flavor = item[1]
|
||||||
|
if example_name is None:
|
||||||
|
print("***** PLEASE DEFINE EXAMPLE FIRST *****")
|
||||||
|
break
|
||||||
|
src_filter.append(f"+<../examples/{example_name}/{ui_flavor}/*.cpp>")
|
||||||
|
|
||||||
menv.Replace(SRC_FILTER=src_filter)
|
menv.Replace(SRC_FILTER=src_filter)
|
||||||
|
|
||||||
#print (menv.Dump())
|
#print (menv.Dump())
|
||||||
|
|||||||
@@ -613,6 +613,23 @@ void SensorMesh::getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SensorMesh::sendAckTo(const ContactInfo& dest, uint32_t ack_hash) {
|
||||||
|
if (dest.out_path_len < 0) {
|
||||||
|
mesh::Packet* ack = createAck(ack_hash);
|
||||||
|
if (ack) sendFlood(ack, TXT_ACK_DELAY);
|
||||||
|
} else {
|
||||||
|
uint32_t d = TXT_ACK_DELAY;
|
||||||
|
if (getExtraAckTransmitCount() > 0) {
|
||||||
|
mesh::Packet* a1 = createMultiAck(ack_hash, 1);
|
||||||
|
if (a1) sendDirect(a1, dest.out_path, dest.out_path_len, d);
|
||||||
|
d += 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
mesh::Packet* a2 = createAck(ack_hash);
|
||||||
|
if (a2) sendDirect(a2, dest.out_path, dest.out_path_len, d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) {
|
void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) {
|
||||||
int i = matching_peer_indexes[sender_idx];
|
int i = matching_peer_indexes[sender_idx];
|
||||||
if (i < 0 || i >= num_contacts) {
|
if (i < 0 || i >= num_contacts) {
|
||||||
@@ -656,38 +673,55 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i
|
|||||||
memcpy(&sender_timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong)
|
memcpy(&sender_timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong)
|
||||||
uint flags = (data[4] >> 2); // message attempt number, and other flags
|
uint flags = (data[4] >> 2); // message attempt number, and other flags
|
||||||
|
|
||||||
if (!(flags == TXT_TYPE_CLI_DATA)) {
|
if (sender_timestamp > from.last_timestamp) { // prevent replay attacks
|
||||||
MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported text type received: flags=%02x", (uint32_t)flags);
|
if (flags == TXT_TYPE_PLAIN) {
|
||||||
} else if (sender_timestamp > from.last_timestamp) { // prevent replay attacks
|
bool handled = handleIncomingMsg(from, sender_timestamp, &data[5], flags, len - 5);
|
||||||
from.last_timestamp = sender_timestamp;
|
if (handled) { // if msg was handled then send an ack
|
||||||
from.last_activity = getRTCClock()->getCurrentTime();
|
uint32_t ack_hash; // calc truncated hash of the message timestamp + text + sender pub_key, to prove to sender that we got it
|
||||||
|
mesh::Utils::sha256((uint8_t *) &ack_hash, 4, data, 5 + strlen((char *)&data[5]), from.id.pub_key, PUB_KEY_SIZE);
|
||||||
|
|
||||||
// len can be > original length, but 'text' will be padded with zeroes
|
if (packet->isRouteFlood()) {
|
||||||
data[len] = 0; // need to make a C string again, with null terminator
|
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the ACK
|
||||||
|
mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len,
|
||||||
uint8_t temp[166];
|
PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4);
|
||||||
char *command = (char *) &data[5];
|
if (path) sendFlood(path, TXT_ACK_DELAY);
|
||||||
char *reply = (char *) &temp[5];
|
|
||||||
handleCommand(sender_timestamp, command, reply);
|
|
||||||
|
|
||||||
int text_len = strlen(reply);
|
|
||||||
if (text_len > 0) {
|
|
||||||
uint32_t timestamp = getRTCClock()->getCurrentTimeUnique();
|
|
||||||
if (timestamp == sender_timestamp) {
|
|
||||||
// WORKAROUND: the two timestamps need to be different, in the CLI view
|
|
||||||
timestamp++;
|
|
||||||
}
|
|
||||||
memcpy(temp, ×tamp, 4); // mostly an extra blob to help make packet_hash unique
|
|
||||||
temp[4] = (TXT_TYPE_CLI_DATA << 2);
|
|
||||||
|
|
||||||
auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, from.id, secret, temp, 5 + text_len);
|
|
||||||
if (reply) {
|
|
||||||
if (from.out_path_len < 0) {
|
|
||||||
sendFlood(reply, CLI_REPLY_DELAY_MILLIS);
|
|
||||||
} else {
|
} else {
|
||||||
sendDirect(reply, from.out_path, from.out_path_len, CLI_REPLY_DELAY_MILLIS);
|
sendAckTo(from, ack_hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (flags == TXT_TYPE_CLI_DATA) {
|
||||||
|
from.last_timestamp = sender_timestamp;
|
||||||
|
from.last_activity = getRTCClock()->getCurrentTime();
|
||||||
|
|
||||||
|
// len can be > original length, but 'text' will be padded with zeroes
|
||||||
|
data[len] = 0; // need to make a C string again, with null terminator
|
||||||
|
|
||||||
|
uint8_t temp[166];
|
||||||
|
char *command = (char *) &data[5];
|
||||||
|
char *reply = (char *) &temp[5];
|
||||||
|
handleCommand(sender_timestamp, command, reply);
|
||||||
|
|
||||||
|
int text_len = strlen(reply);
|
||||||
|
if (text_len > 0) {
|
||||||
|
uint32_t timestamp = getRTCClock()->getCurrentTimeUnique();
|
||||||
|
if (timestamp == sender_timestamp) {
|
||||||
|
// WORKAROUND: the two timestamps need to be different, in the CLI view
|
||||||
|
timestamp++;
|
||||||
|
}
|
||||||
|
memcpy(temp, ×tamp, 4); // mostly an extra blob to help make packet_hash unique
|
||||||
|
temp[4] = (TXT_TYPE_CLI_DATA << 2);
|
||||||
|
|
||||||
|
auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, from.id, secret, temp, 5 + text_len);
|
||||||
|
if (reply) {
|
||||||
|
if (from.out_path_len < 0) {
|
||||||
|
sendFlood(reply, CLI_REPLY_DELAY_MILLIS);
|
||||||
|
} else {
|
||||||
|
sendDirect(reply, from.out_path, from.out_path_len, CLI_REPLY_DELAY_MILLIS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported text type received: flags=%02x", (uint32_t)flags);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
MESH_DEBUG_PRINTLN("onPeerDataRecv: possible replay attack detected");
|
MESH_DEBUG_PRINTLN("onPeerDataRecv: possible replay attack detected");
|
||||||
@@ -695,6 +729,15 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SensorMesh::handleIncomingMsg(ContactInfo& from, uint32_t timestamp, uint8_t* data, uint flags, size_t len) {
|
||||||
|
MESH_DEBUG_PRINT("handleIncomingMsg: unhandled msg from ");
|
||||||
|
#ifdef MESH_DEBUG
|
||||||
|
mesh::Utils::printHex(Serial, from.id.pub_key, PUB_KEY_SIZE);
|
||||||
|
Serial.printf(": %s\n", data);
|
||||||
|
#endif
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool SensorMesh::onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) {
|
bool SensorMesh::onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) {
|
||||||
int i = matching_peer_indexes[sender_idx];
|
int i = matching_peer_indexes[sender_idx];
|
||||||
if (i < 0 || i >= num_contacts) {
|
if (i < 0 || i >= num_contacts) {
|
||||||
|
|||||||
@@ -140,7 +140,8 @@ protected:
|
|||||||
void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override;
|
void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override;
|
||||||
bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override;
|
bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override;
|
||||||
void onAckRecv(mesh::Packet* packet, uint32_t ack_crc) override;
|
void onAckRecv(mesh::Packet* packet, uint32_t ack_crc) override;
|
||||||
|
virtual bool handleIncomingMsg(ContactInfo& from, uint32_t timestamp, uint8_t* data, uint flags, size_t len);
|
||||||
|
void sendAckTo(const ContactInfo& dest, uint32_t ack_hash);
|
||||||
private:
|
private:
|
||||||
FILESYSTEM* _fs;
|
FILESYSTEM* _fs;
|
||||||
unsigned long next_local_advert, next_flood_advert;
|
unsigned long next_local_advert, next_flood_advert;
|
||||||
|
|||||||
@@ -34,6 +34,65 @@ build_src_filter = ${nrf52840_t1000e.build_src_filter}
|
|||||||
debug_tool = jlink
|
debug_tool = jlink
|
||||||
upload_protocol = nrfutil
|
upload_protocol = nrfutil
|
||||||
|
|
||||||
|
[env:t1000e_repeater]
|
||||||
|
extends = t1000-e
|
||||||
|
build_flags = ${t1000-e.build_flags}
|
||||||
|
-I examples/companion_radio/ui-orig
|
||||||
|
-D ADVERT_NAME='"t1000-e Repeater"'
|
||||||
|
-D ADVERT_LAT=0.0
|
||||||
|
-D ADVERT_LON=0.0
|
||||||
|
-D ADMIN_PASSWORD='"password"'
|
||||||
|
-D MAX_NEIGHBOURS=8
|
||||||
|
; -D MESH_PACKET_LOGGING=1
|
||||||
|
; -D MESH_DEBUG=1
|
||||||
|
-D RX_BOOSTED_GAIN=true
|
||||||
|
-D RF_SWITCH_TABLE
|
||||||
|
build_src_filter = ${t1000-e.build_src_filter}
|
||||||
|
+<../examples/simple_repeater>
|
||||||
|
lib_deps = ${t1000-e.lib_deps}
|
||||||
|
stevemarple/MicroNMEA @ ^2.0.6
|
||||||
|
|
||||||
|
[env:t1000e_room_server]
|
||||||
|
extends = t1000-e
|
||||||
|
build_flags = ${t1000-e.build_flags}
|
||||||
|
-I examples/companion_radio/ui-orig
|
||||||
|
-D ADVERT_NAME='"t1000-e Room"'
|
||||||
|
-D ADVERT_LAT=0.0
|
||||||
|
-D ADVERT_LON=0.0
|
||||||
|
-D ADMIN_PASSWORD='"password"'
|
||||||
|
-D ROOM_PASSWORD='"hello"'
|
||||||
|
; -D MESH_PACKET_LOGGING=1
|
||||||
|
; -D MESH_DEBUG=1
|
||||||
|
-D RX_BOOSTED_GAIN=true
|
||||||
|
-D RF_SWITCH_TABLE
|
||||||
|
build_src_filter = ${t1000-e.build_src_filter}
|
||||||
|
+<../examples/simple_room_server>
|
||||||
|
lib_deps = ${t1000-e.lib_deps}
|
||||||
|
stevemarple/MicroNMEA @ ^2.0.6
|
||||||
|
|
||||||
|
[env:t1000e_companion_radio_usb]
|
||||||
|
extends = t1000-e
|
||||||
|
build_flags = ${t1000-e.build_flags}
|
||||||
|
-I examples/companion_radio/ui-orig
|
||||||
|
-D MAX_CONTACTS=100
|
||||||
|
-D MAX_GROUP_CHANNELS=8
|
||||||
|
; -D MESH_PACKET_LOGGING=1
|
||||||
|
; -D MESH_DEBUG=1
|
||||||
|
-D OFFLINE_QUEUE_SIZE=256
|
||||||
|
-D RX_BOOSTED_GAIN=true
|
||||||
|
-D RF_SWITCH_TABLE
|
||||||
|
-D DISPLAY_CLASS=NullDisplayDriver
|
||||||
|
-D PIN_BUZZER=25
|
||||||
|
-D PIN_BUZZER_EN=37 ; P1/5 - required for T1000-E
|
||||||
|
build_src_filter = ${t1000-e.build_src_filter}
|
||||||
|
+<helpers/ui/buzzer.cpp>
|
||||||
|
+<../examples/companion_radio/*.cpp>
|
||||||
|
+<../examples/companion_radio/ui-orig/*.cpp>
|
||||||
|
lib_deps = ${t1000-e.lib_deps}
|
||||||
|
densaugeo/base64 @ ~1.4.0
|
||||||
|
stevemarple/MicroNMEA @ ^2.0.6
|
||||||
|
end2endzone/NonBlockingRTTTL@^1.3.0
|
||||||
|
|
||||||
[env:t1000e_companion_radio_ble]
|
[env:t1000e_companion_radio_ble]
|
||||||
extends = t1000-e
|
extends = t1000-e
|
||||||
build_flags = ${t1000-e.build_flags}
|
build_flags = ${t1000-e.build_flags}
|
||||||
@@ -58,4 +117,4 @@ build_src_filter = ${t1000-e.build_src_filter}
|
|||||||
lib_deps = ${t1000-e.lib_deps}
|
lib_deps = ${t1000-e.lib_deps}
|
||||||
densaugeo/base64 @ ~1.4.0
|
densaugeo/base64 @ ~1.4.0
|
||||||
stevemarple/MicroNMEA @ ^2.0.6
|
stevemarple/MicroNMEA @ ^2.0.6
|
||||||
end2endzone/NonBlockingRTTTL@^1.3.0
|
end2endzone/NonBlockingRTTTL@^1.3.0
|
||||||
|
|||||||
@@ -121,3 +121,65 @@ build_src_filter = ${Meshimi.build_src_filter}
|
|||||||
lib_deps =
|
lib_deps =
|
||||||
${Meshimi.lib_deps}
|
${Meshimi.lib_deps}
|
||||||
densaugeo/base64 @ ~1.4.0
|
densaugeo/base64 @ ~1.4.0
|
||||||
|
|
||||||
|
; WHY2025 badge variant
|
||||||
|
; requires soldering 2 pins between the esp32-C6 and the lora chip as shown here: https://wiki.why2025.org/Project:Meshtastic_on_the_WHY2025_badge
|
||||||
|
; also requires wiping the esp32-P4
|
||||||
|
[WHY2025_badge]
|
||||||
|
extends = Xiao_C6
|
||||||
|
board_build.partitions = default_8MB.csv
|
||||||
|
board_upload.flash_size = 8MB
|
||||||
|
board_upload.maximum_size = 8388608
|
||||||
|
build_flags =
|
||||||
|
${Xiao_C6.build_flags}
|
||||||
|
-D P_LORA_SCLK=6
|
||||||
|
-D P_LORA_MISO=2
|
||||||
|
-D P_LORA_MOSI=7
|
||||||
|
-D P_LORA_NSS=4
|
||||||
|
-D P_LORA_DIO_1=5
|
||||||
|
-D P_LORA_BUSY=11
|
||||||
|
-D P_LORA_RESET=1
|
||||||
|
-D SX126X_TXEN=3
|
||||||
|
-UPIN_BOARD_SDA
|
||||||
|
-UPIN_BOARD_SCL
|
||||||
|
-UP_LORA_TX_LED
|
||||||
|
-USX126X_RXEN
|
||||||
|
-USX126X_DIO2_AS_RF_SWITCH
|
||||||
|
-USX126X_DIO3_TCXO_VOLTAGE
|
||||||
|
|
||||||
|
[env:WHY2025_badge_Repeater]
|
||||||
|
extends = WHY2025_badge
|
||||||
|
build_src_filter = ${WHY2025_badge.build_src_filter}
|
||||||
|
+<../examples/simple_repeater/main.cpp>
|
||||||
|
build_flags =
|
||||||
|
${WHY2025_badge.build_flags}
|
||||||
|
-D ADVERT_NAME='"WHY2025 Badge Repeater"'
|
||||||
|
-D ADVERT_LAT=0.0
|
||||||
|
-D ADVERT_LON=0.0
|
||||||
|
-D ADMIN_PASSWORD='"password"'
|
||||||
|
-D MAX_NEIGHBOURS=8
|
||||||
|
; -D MESH_PACKET_LOGGING=1
|
||||||
|
; -D MESH_DEBUG=1
|
||||||
|
lib_deps =
|
||||||
|
${WHY2025_badge.lib_deps}
|
||||||
|
; ${esp32_ota.lib_deps}
|
||||||
|
|
||||||
|
[env:WHY2025_badge_companion_radio_ble]
|
||||||
|
extends = WHY2025_badge
|
||||||
|
build_flags = ${WHY2025_badge.build_flags}
|
||||||
|
-D MAX_CONTACTS=100
|
||||||
|
-D MAX_GROUP_CHANNELS=8
|
||||||
|
-D BLE_PIN_CODE=123456
|
||||||
|
-D BLE_DEBUG_LOGGING=1
|
||||||
|
-D OFFLINE_QUEUE_SIZE=256
|
||||||
|
-D ENABLE_PRIVATE_KEY_IMPORT=1
|
||||||
|
-D ENABLE_PRIVATE_KEY_EXPORT=1
|
||||||
|
; -D MESH_PACKET_LOGGING=1
|
||||||
|
; -D MESH_DEBUG=1
|
||||||
|
build_src_filter = ${WHY2025_badge.build_src_filter}
|
||||||
|
+<helpers/esp32/*.cpp>
|
||||||
|
-<helpers/esp32/ESPNOWRadio.cpp>
|
||||||
|
+<../examples/companion_radio/*.cpp>
|
||||||
|
lib_deps =
|
||||||
|
${WHY2025_badge.lib_deps}
|
||||||
|
densaugeo/base64 @ ~1.4.0
|
||||||
|
|||||||
Reference in New Issue
Block a user