Refine node info UI and peer announcements

This commit is contained in:
liu weikai
2026-04-21 22:32:52 +08:00
parent a022d4af8b
commit 110fa96be8
10 changed files with 1721 additions and 1007 deletions
@@ -5,6 +5,8 @@
#pragma once
#include <cstddef>
#include "chat/domain/contact_types.h"
#include "lvgl.h"
@@ -13,62 +15,53 @@ namespace node_info
namespace ui
{
static constexpr std::size_t kNodeInfoTileCount = 9;
static constexpr std::size_t kNodeInfoInfoLineCount = 8;
struct NodeInfoWidgets
{
lv_obj_t* root = nullptr;
lv_obj_t* header = nullptr;
lv_obj_t* content = nullptr;
lv_obj_t* top_row = nullptr;
lv_obj_t* info_card = nullptr;
lv_obj_t* info_header = nullptr;
lv_obj_t* info_footer = nullptr;
lv_obj_t* location_card = nullptr;
lv_obj_t* location_header = nullptr;
lv_obj_t* location_map = nullptr;
lv_obj_t* location_coords = nullptr;
lv_obj_t* location_updated = nullptr;
lv_obj_t* link_panel = nullptr;
lv_obj_t* link_header = nullptr;
lv_obj_t* link_row_1 = nullptr;
lv_obj_t* link_row_2 = nullptr;
lv_obj_t* back_btn = nullptr;
lv_obj_t* back_label = nullptr;
lv_obj_t* title_label = nullptr;
lv_obj_t* battery_label = nullptr;
lv_obj_t* avatar_bg = nullptr;
lv_obj_t* avatar_label = nullptr;
lv_obj_t* name_label = nullptr;
lv_obj_t* desc_label = nullptr;
lv_obj_t* map_stage = nullptr;
lv_obj_t* tile_layer = nullptr;
lv_obj_t* left_scrim = nullptr;
lv_obj_t* right_scrim = nullptr;
lv_obj_t* id_label = nullptr;
lv_obj_t* role_label = nullptr;
lv_obj_t* lon_label = nullptr;
lv_obj_t* lat_label = nullptr;
lv_obj_t* no_position_label = nullptr;
lv_obj_t* location_title_label = nullptr;
lv_obj_t* map_image = nullptr;
lv_obj_t* map_label = nullptr;
lv_obj_t* coords_latlon_label = nullptr;
lv_obj_t* coords_acc_label = nullptr;
lv_obj_t* coords_alt_label = nullptr;
lv_obj_t* updated_label = nullptr;
lv_obj_t* connection_line = nullptr;
lv_obj_t* marker_node_ring = nullptr;
lv_obj_t* marker_node_dot = nullptr;
lv_obj_t* marker_self_ring = nullptr;
lv_obj_t* marker_self_dot = nullptr;
lv_obj_t* distance_label = nullptr;
lv_obj_t* link_title_label = nullptr;
lv_obj_t* link_rssi_label = nullptr;
lv_obj_t* link_snr_label = nullptr;
lv_obj_t* link_ch_label = nullptr;
lv_obj_t* link_sf_label = nullptr;
lv_obj_t* link_bw_label = nullptr;
lv_obj_t* link_hop_label = nullptr;
lv_obj_t* link_last_heard_label = nullptr;
lv_obj_t* zoom_in_btn = nullptr;
lv_obj_t* zoom_out_btn = nullptr;
lv_obj_t* zoom_in_label = nullptr;
lv_obj_t* zoom_out_label = nullptr;
lv_obj_t* tile_images[kNodeInfoTileCount]{};
lv_obj_t* info_labels[kNodeInfoInfoLineCount]{};
};
/**
* @brief Create an empty Node Info screen structure.
* @brief Create the Node Info page widgets.
*/
NodeInfoWidgets create(lv_obj_t* parent);
/**
* @brief Destroy the Node Info screen (if created).
* @brief Destroy the Node Info page (if created).
*/
void destroy();
@@ -78,7 +71,7 @@ void destroy();
const NodeInfoWidgets& widgets();
/**
* @brief Update UI labels with NodeInfo data.
* @brief Update UI widgets with NodeInfo data.
*/
void set_node_info(const chat::contacts::NodeInfo& node);
@@ -1,6 +1,6 @@
/**
* @file node_info_page_layout.h
* @brief Node info page layout (structure only)
* @brief Node info page layout helpers
*/
#pragma once
@@ -17,18 +17,6 @@ namespace layout
lv_obj_t* create_root(lv_obj_t* parent);
lv_obj_t* create_header(lv_obj_t* root);
lv_obj_t* create_content(lv_obj_t* root);
lv_obj_t* create_top_row(lv_obj_t* content);
lv_obj_t* create_info_card(lv_obj_t* top_row);
lv_obj_t* create_info_header(lv_obj_t* info_card);
lv_obj_t* create_info_footer(lv_obj_t* info_card);
lv_obj_t* create_location_card(lv_obj_t* top_row);
lv_obj_t* create_location_header(lv_obj_t* location_card);
lv_obj_t* create_location_map(lv_obj_t* location_card);
lv_obj_t* create_location_coords(lv_obj_t* location_card);
lv_obj_t* create_location_updated(lv_obj_t* location_card);
lv_obj_t* create_link_panel(lv_obj_t* content);
lv_obj_t* create_link_header(lv_obj_t* link_panel);
lv_obj_t* create_link_row(lv_obj_t* link_panel);
} // namespace layout
} // namespace ui
File diff suppressed because it is too large Load Diff
@@ -1,66 +1,10 @@
/**
* UI Wireframe / Layout Tree (Node Info - matches provided mock)
* --------------------------------------------------------------------
*
* Root (COLUMN)
* +------------------------------------------------------------------+
* | Header (TopBar) [< Back] NODE INFO [Battery] |
* +------------------------------------------------------------------+
* | TopRow (ROW, grow) |
* | +-------------------------+ +---------------------------------+ |
* | | Info Card (left) | | Location Card (right) | |
* | | +---------------------+ | | +---------------------------+ | |
* | | | Avatar (S) | | | | Title: Location | | |
* | | | Name: ALFA-3 | | | +---------------------------+ | |
* | | | Desc: Relay Node | | | | Map Placeholder | | |
* | | +---------------------+ | | | [Map image] | | |
* | | | ID: !a1b2c3 | | | +---------------------------+ | |
* | | | Role: Router | | | | Lat,Lon +/-Acc Alt | | |
* | | +---------------------+ | | +---------------------------+ | |
* | | | | | Updated: 2m ago | | |
* | +-------------------------+ | +---------------------------+ | |
* | +---------------------------------+ |
* | |
* | Link Panel (full width) |
* | +------------------------------------------------------------+ |
* | | Title: Link | |
* | +------------------------------------------------------------+ |
* | | RSSI: -112 SNR: 7.5 Ch: 478.875 SF: 7 BW: 125k | |
* | +------------------------------------------------------------+ |
* | | Hop: 2 Last heard: 18s | |
* | +------------------------------------------------------------+ |
* +------------------------------------------------------------------+
*
* Tree view:
* Root(COL)
* - Header
* - Content(COL)
* - TopRow(ROW, grow=1)
* - InfoCard(COL)
* - InfoHeader(ROW)
* - InfoFooter(ROW)
* - LocationCard(COL, grow=1)
* - LocationHeader
* - LocationMap(grow=1)
* - LocationCoords
* - LocationUpdated
* - LinkPanel(COL)
* - LinkHeader
* - LinkRow1
* - LinkRow2
*
* Preconditions:
* - Root uses LV_FLEX_FLOW_COLUMN.
* - TopRow uses LV_FLEX_FLOW_ROW.
* - LinkPanel uses LV_FLEX_FLOW_COLUMN.
*/
/**
* @file node_info_page_layout.cpp
* @brief Node info page layout
* @brief Node info page layout helpers
*/
#include "ui/screens/node_info/node_info_page_layout.h"
#include "ui/page/page_profile.h"
#include "ui/widgets/top_bar.h"
@@ -73,8 +17,36 @@ namespace layout
namespace
{
constexpr lv_coord_t kScreenWidth = 480;
constexpr lv_coord_t kBaseContentHeight = 192;
lv_coord_t resolve_parent_width(lv_obj_t* parent)
{
if (parent)
{
const lv_coord_t width = lv_obj_get_width(parent);
if (width > 0)
{
return width;
}
}
const lv_coord_t width = lv_display_get_physical_horizontal_resolution(nullptr);
return width > 0 ? width : 320;
}
lv_coord_t resolve_parent_height(lv_obj_t* parent)
{
if (parent)
{
const lv_coord_t height = lv_obj_get_height(parent);
if (height > 0)
{
return height;
}
}
const lv_coord_t height = lv_display_get_physical_vertical_resolution(nullptr);
return height > 0 ? height : 240;
}
lv_coord_t top_bar_height()
{
@@ -83,196 +55,45 @@ lv_coord_t top_bar_height()
: static_cast<lv_coord_t>(::ui::widgets::kTopBarHeight);
}
lv_coord_t screen_height()
{
return top_bar_height() + kBaseContentHeight;
}
constexpr int kContentPadX = 8;
constexpr int kContentPadY = 4;
constexpr int kCardGapX = 8;
constexpr int kRowGap = 8;
constexpr int kTopRowX = kContentPadX;
constexpr int kTopRowY = kContentPadY;
constexpr int kTopRowWidth = kScreenWidth - (kContentPadX * 2);
constexpr int kTopRowHeight = 118;
constexpr int kInfoCardWidth = 190;
constexpr int kInfoCardHeight = 118;
constexpr int kLocationCardWidth = 266;
constexpr int kLocationCardHeight = 118;
constexpr int kLinkPanelX = kContentPadX;
constexpr int kLinkPanelY = kTopRowY + kTopRowHeight + kRowGap;
constexpr int kLinkPanelWidth = kTopRowWidth;
constexpr int kLinkPanelHeight = 60;
constexpr int kLocationHeaderHeight = 22;
constexpr int kLocationMapHeight = 52;
constexpr int kLocationCoordsHeight = 18;
constexpr int kLocationUpdatedHeight = 18;
constexpr int kInfoHeaderHeight = 70;
constexpr int kInfoFooterHeight = 36;
constexpr int kLinkHeaderHeight = 20;
constexpr int kLinkRowHeight = 16;
void make_non_scrollable(lv_obj_t* obj)
void make_plain(lv_obj_t* obj)
{
lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_set_scrollbar_mode(obj, LV_SCROLLBAR_MODE_OFF);
lv_obj_set_style_pad_all(obj, 0, 0);
lv_obj_set_style_border_width(obj, 0, 0);
lv_obj_set_style_radius(obj, 0, 0);
}
} // namespace
lv_obj_t* create_root(lv_obj_t* parent)
{
lv_obj_t* root = lv_obj_create(parent);
lv_obj_set_size(root, kScreenWidth, screen_height());
lv_obj_set_size(root, resolve_parent_width(parent), resolve_parent_height(parent));
lv_obj_set_pos(root, 0, 0);
lv_obj_set_style_pad_all(root, 0, 0);
make_non_scrollable(root);
make_plain(root);
return root;
}
lv_obj_t* create_header(lv_obj_t* root)
{
lv_obj_t* header = lv_obj_create(root);
lv_obj_set_size(header, kScreenWidth, top_bar_height());
lv_obj_set_size(header, lv_obj_get_width(root), top_bar_height());
lv_obj_set_pos(header, 0, 0);
make_non_scrollable(header);
make_plain(header);
return header;
}
lv_obj_t* create_content(lv_obj_t* root)
{
const lv_coord_t header_h = top_bar_height();
lv_obj_t* content = lv_obj_create(root);
lv_obj_set_size(content, kScreenWidth, kBaseContentHeight);
lv_obj_set_pos(content, 0, top_bar_height());
lv_obj_set_style_pad_all(content, 0, 0);
make_non_scrollable(content);
lv_obj_set_size(content, lv_obj_get_width(root), lv_obj_get_height(root) - header_h);
lv_obj_set_pos(content, 0, header_h);
make_plain(content);
return content;
}
lv_obj_t* create_top_row(lv_obj_t* content)
{
lv_obj_t* row = lv_obj_create(content);
lv_obj_set_size(row, kTopRowWidth, kTopRowHeight);
lv_obj_set_pos(row, kTopRowX, kTopRowY);
lv_obj_set_style_pad_all(row, 0, 0);
make_non_scrollable(row);
return row;
}
lv_obj_t* create_info_card(lv_obj_t* top_row)
{
lv_obj_t* card = lv_obj_create(top_row);
lv_obj_set_size(card, kInfoCardWidth, kInfoCardHeight);
lv_obj_set_pos(card, 0, 0);
lv_obj_set_style_pad_all(card, 0, 0);
make_non_scrollable(card);
return card;
}
lv_obj_t* create_info_header(lv_obj_t* info_card)
{
lv_obj_t* header = lv_obj_create(info_card);
lv_obj_set_size(header, kInfoCardWidth, kInfoHeaderHeight);
lv_obj_set_pos(header, 0, 0);
lv_obj_set_style_pad_all(header, 0, 0);
make_non_scrollable(header);
return header;
}
lv_obj_t* create_info_footer(lv_obj_t* info_card)
{
lv_obj_t* footer = lv_obj_create(info_card);
lv_obj_set_size(footer, 170, kInfoFooterHeight);
lv_obj_set_pos(footer, 10, 70);
lv_obj_set_style_pad_all(footer, 0, 0);
make_non_scrollable(footer);
return footer;
}
lv_obj_t* create_location_card(lv_obj_t* top_row)
{
lv_obj_t* card = lv_obj_create(top_row);
lv_obj_set_size(card, kLocationCardWidth, kLocationCardHeight);
lv_obj_set_pos(card, kInfoCardWidth + kCardGapX, 0);
lv_obj_set_style_pad_all(card, 0, 0);
make_non_scrollable(card);
return card;
}
lv_obj_t* create_location_header(lv_obj_t* location_card)
{
lv_obj_t* header = lv_obj_create(location_card);
lv_obj_set_size(header, kLocationCardWidth, kLocationHeaderHeight);
lv_obj_set_pos(header, 0, 0);
lv_obj_set_style_pad_all(header, 0, 0);
make_non_scrollable(header);
return header;
}
lv_obj_t* create_location_map(lv_obj_t* location_card)
{
lv_obj_t* map = lv_obj_create(location_card);
lv_obj_set_size(map, 246, kLocationMapHeight);
lv_obj_set_pos(map, 10, 30);
lv_obj_set_style_pad_all(map, 0, 0);
make_non_scrollable(map);
return map;
}
lv_obj_t* create_location_coords(lv_obj_t* location_card)
{
lv_obj_t* coords = lv_obj_create(location_card);
lv_obj_set_size(coords, 246, kLocationCoordsHeight);
lv_obj_set_pos(coords, 10, 86);
lv_obj_set_style_pad_all(coords, 0, 0);
make_non_scrollable(coords);
return coords;
}
lv_obj_t* create_location_updated(lv_obj_t* location_card)
{
lv_obj_t* updated = lv_obj_create(location_card);
lv_obj_set_size(updated, 246, kLocationUpdatedHeight);
lv_obj_set_pos(updated, 10, 106);
lv_obj_set_style_pad_all(updated, 0, 0);
make_non_scrollable(updated);
return updated;
}
lv_obj_t* create_link_panel(lv_obj_t* content)
{
lv_obj_t* panel = lv_obj_create(content);
lv_obj_set_size(panel, kLinkPanelWidth, kLinkPanelHeight);
lv_obj_set_pos(panel, kLinkPanelX, kLinkPanelY);
lv_obj_set_style_pad_all(panel, 0, 0);
make_non_scrollable(panel);
return panel;
}
lv_obj_t* create_link_header(lv_obj_t* link_panel)
{
lv_obj_t* header = lv_obj_create(link_panel);
lv_obj_set_size(header, kLinkPanelWidth, kLinkHeaderHeight);
lv_obj_set_pos(header, 0, 0);
lv_obj_set_style_pad_all(header, 0, 0);
make_non_scrollable(header);
return header;
}
lv_obj_t* create_link_row(lv_obj_t* link_panel)
{
lv_obj_t* row = lv_obj_create(link_panel);
lv_obj_set_size(row, kLinkPanelWidth, kLinkRowHeight);
lv_obj_set_style_pad_all(row, 0, 0);
make_non_scrollable(row);
return row;
}
} // namespace layout
} // namespace ui
} // namespace node_info
@@ -216,16 +216,16 @@ void enter(const shell::Host* host, lv_obj_t* parent)
lv_obj_center(stack);
lv_obj_t* title = lv_label_create(stack);
::ui::i18n::set_label_text(title, page_subtitle());
lv_obj_set_style_text_font(title, &lv_font_montserrat_18, 0);
::ui::i18n::set_label_text(title, page_subtitle());
s_status_label = lv_label_create(stack);
::ui::i18n::set_label_text(s_status_label, "Waiting for host...");
lv_obj_set_style_text_font(s_status_label, &lv_font_montserrat_18, 0);
::ui::i18n::set_label_text(s_status_label, "Waiting for host...");
s_count_label = lv_label_create(stack);
::ui::i18n::set_label_text(s_count_label, "RX: 0 TX: 0");
lv_obj_set_style_text_font(s_count_label, &lv_font_montserrat_16, 0);
::ui::i18n::set_label_text(s_count_label, "RX: 0 TX: 0");
if (!s_timer)
{
@@ -272,26 +272,26 @@ void enter(const shell::Host* host, lv_obj_t* parent)
if (!platform::ui::device::card_ready())
{
lv_obj_t* error_label = lv_label_create(s_content);
::ui::i18n::set_label_text(error_label, "SD Card Not Found\nPlease insert SD card");
lv_obj_center(error_label);
lv_obj_set_style_text_font(error_label, &lv_font_montserrat_18, LV_PART_MAIN);
lv_obj_set_style_text_color(error_label, lv_color_hex(0xCC0000), LV_PART_MAIN);
::ui::i18n::set_label_text(error_label, "SD Card Not Found\nPlease insert SD card");
lv_obj_center(error_label);
return;
}
s_status_label = lv_label_create(s_content);
::ui::i18n::set_label_text(s_status_label, "Initializing...");
lv_obj_center(s_status_label);
lv_obj_set_style_text_font(s_status_label, &lv_font_montserrat_18, LV_PART_MAIN);
lv_obj_set_style_text_color(s_status_label, lv_color_hex(0x3A2A1A), LV_PART_MAIN);
lv_obj_set_style_text_align(s_status_label, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN);
::ui::i18n::set_label_text(s_status_label, "Initializing...");
lv_obj_center(s_status_label);
lv_obj_t* info_label = lv_label_create(s_content);
::ui::i18n::set_label_text(info_label, "Press Back to exit USB mode");
lv_obj_align(info_label, LV_ALIGN_BOTTOM_MID, 0, -20);
lv_obj_set_style_text_font(info_label, &lv_font_montserrat_14, LV_PART_MAIN);
lv_obj_set_style_text_color(info_label, lv_color_hex(0x6A5646), LV_PART_MAIN);
lv_obj_set_style_text_opa(info_label, LV_OPA_80, LV_PART_MAIN);
::ui::i18n::set_label_text(info_label, "Press Back to exit USB mode");
lv_obj_align(info_label, LV_ALIGN_BOTTOM_MID, 0, -20);
update_status_label();
@@ -197,6 +197,7 @@ class MtAdapter : public chat::IMeshAdapter
static constexpr uint8_t MAX_RETRIES = 3;
static constexpr uint32_t NODEINFO_INTERVAL_MS = 3 * 60 * 60 * 1000;
static constexpr uint32_t NODEINFO_REPLY_SUPPRESS_MS = 12 * 60 * 60 * 1000;
static constexpr uint32_t NODEINFO_REANNOUNCE_SUPPRESS_MS = 60 * 1000;
static constexpr uint32_t POSITION_REPLY_SUPPRESS_MS = 3 * 60 * 1000;
static constexpr uint32_t PKI_BACKOFF_MS = 5 * 60 * 1000;
static constexpr size_t MAX_APP_QUEUE = 10;
@@ -230,6 +231,10 @@ class MtAdapter : public chat::IMeshAdapter
bool want_ack_flag,
bool want_response);
void maybeBroadcastNodeInfo(uint32_t now_ms);
void maybeBroadcastNodeInfoAfterPeerAnnouncement(uint32_t from_node,
uint32_t now_ms,
ChannelId channel,
bool from_mqtt);
void configureRadio();
void initNodeIdentity();
void updateChannelKeys();
@@ -1,21 +1,85 @@
#include "platform/esp/arduino_common/app_runtime_support.h"
#include <cstdio>
#include <string>
#include "app/app_config.h"
#include "app/app_facade_access.h"
#include "app/app_facades.h"
#include "ble/ble_manager.h"
#include "board/BoardBase.h"
#include "chat/usecase/chat_service.h"
#include "chat/usecase/contact_service.h"
#include "platform/esp/arduino_common/app_tasks.h"
#include "platform/esp/arduino_common/device_identity.h"
#include "platform/esp/arduino_common/hostlink/hostlink_service.h"
#include "platform/ui/settings_store.h"
#include "sys/event_bus.h"
#include "team/usecase/team_pairing_service.h"
#include "team/usecase/team_service.h"
#include "team/usecase/team_track_sampler.h"
#include "ui/localization.h"
#include "ui/widgets/system_notification.h"
namespace platform::esp::arduino_common
{
namespace
{
void triggerNodeInfoFeedback(app::IAppFacade& app_context)
{
BoardBase* board = app_context.getBoard();
if (!board)
{
return;
}
if (platform::ui::settings_store::get_bool("settings", "vibration_enabled", true))
{
board->vibrator();
}
board->playMessageTone();
}
std::string resolveNodeInfoName(app::IAppFacade& app_context, const sys::NodeInfoUpdateEvent& node_event)
{
std::string name = app_context.getContactService().getContactName(node_event.node_id);
if (!name.empty())
{
return name;
}
if (node_event.short_name[0] != '\0')
{
return std::string(node_event.short_name);
}
if (node_event.long_name[0] != '\0')
{
return std::string(node_event.long_name);
}
char fallback[16];
std::snprintf(fallback, sizeof(fallback), "%08lX", static_cast<unsigned long>(node_event.node_id));
return fallback;
}
void notifyNodeInfoUpdate(app::IAppFacade& app_context, const sys::NodeInfoUpdateEvent& node_event)
{
if (node_event.node_id == 0 || node_event.node_id == app_context.getSelfNodeId() || node_event.is_ignored)
{
return;
}
triggerNodeInfoFeedback(app_context);
const std::string message =
::ui::i18n::format("Node info: %s", resolveNodeInfoName(app_context, node_event).c_str());
::ui::SystemNotification::show(message.c_str(), 3000);
}
} // namespace
BackgroundTaskStartResult startBackgroundTasks(LoraBoard* board, chat::IMeshAdapter* adapter)
{
@@ -134,6 +198,7 @@ bool dispatchEvent(app::IAppFacade& app_context, sys::Event* event)
update.device_metrics = node_event->device_metrics;
}
app_context.getContactService().applyNodeUpdate(node_event->node_id, update);
notifyNodeInfoUpdate(app_context, *node_event);
delete event;
return true;
}
@@ -2142,6 +2142,14 @@ void MtAdapter::processReceivedPacket(const uint8_t* data, size_t size)
{
node_last_channel_[header.from] = channel_id;
}
if (nodeinfo_decoded)
{
maybeBroadcastNodeInfoAfterPeerAnnouncement(
header.from,
millis(),
channel_id,
(header.flags & chat::meshtastic::PACKET_FLAGS_VIA_MQTT_MASK) != 0);
}
if (want_ack_flag && to_us)
{
if (sendRoutingAck(header.from, header.id, header.channel, psk, psk_len))
@@ -2702,6 +2710,10 @@ bool MtAdapter::sendNodeInfoTo(uint32_t dest, bool want_response, ChannelId chan
}
bool ok = transmitWirePacket(wire_buffer, wire_size);
if (ok && dest == 0xFFFFFFFF)
{
last_nodeinfo_ms_ = millis();
}
LORA_LOG("[LORA] TX nodeinfo id=%08lX len=%u ok=%d\n",
(unsigned long)msg_id,
(unsigned)wire_size,
@@ -2844,6 +2856,39 @@ void MtAdapter::maybeBroadcastNodeInfo(uint32_t now_ms)
}
}
void MtAdapter::maybeBroadcastNodeInfoAfterPeerAnnouncement(uint32_t from_node,
uint32_t now_ms,
ChannelId channel,
bool from_mqtt)
{
if (!ready_ || from_mqtt || from_node == 0 || from_node == node_id_)
{
return;
}
if (last_nodeinfo_ms_ != 0 &&
(now_ms - last_nodeinfo_ms_) < NODEINFO_REANNOUNCE_SUPPRESS_MS)
{
LORA_LOG("[LORA] TX nodeinfo announce suppressed from=%08lX age=%lu\n",
(unsigned long)from_node,
(unsigned long)(now_ms - last_nodeinfo_ms_));
return;
}
if (sendNodeInfoTo(0xFFFFFFFF, false, channel))
{
LORA_LOG("[LORA] TX nodeinfo announce after peer from=%08lX ch=%u\n",
(unsigned long)from_node,
static_cast<unsigned>(channel));
}
else
{
LORA_LOG("[LORA] TX nodeinfo announce fail after peer from=%08lX ch=%u\n",
(unsigned long)from_node,
static_cast<unsigned>(channel));
}
}
void MtAdapter::configureRadio()
{
if (!board_.isRadioOnline())
@@ -7,6 +7,7 @@
#include <Preferences.h>
#include <SD.h>
#include <string>
namespace chat
{
@@ -15,6 +16,11 @@ namespace infra
namespace
{
std::string makeTempSdPath(const char* path)
{
return std::string(path ? path : "") + ".tmp";
}
bool isValidPrefKey(const char* key)
{
return key && key[0] != '\0';
@@ -117,12 +123,18 @@ bool saveRawBlobToSd(const char* path, const uint8_t* data, size_t len)
return false;
}
if (SD.exists(path))
const std::string temp_path = makeTempSdPath(path);
if (temp_path.empty())
{
SD.remove(path);
return false;
}
File file = SD.open(path, FILE_WRITE);
if (SD.exists(temp_path.c_str()))
{
SD.remove(temp_path.c_str());
}
File file = SD.open(temp_path.c_str(), FILE_WRITE);
if (!file)
{
return false;
@@ -134,7 +146,24 @@ bool saveRawBlobToSd(const char* path, const uint8_t* data, size_t len)
ok = (file.write(data, len) == len);
}
file.close();
return ok;
if (!ok)
{
SD.remove(temp_path.c_str());
return false;
}
if (SD.exists(path))
{
SD.remove(path);
}
if (!SD.rename(temp_path.c_str(), path))
{
SD.remove(temp_path.c_str());
return false;
}
return true;
}
bool loadRawBlobFromPreferences(const char* ns, const char* key, std::vector<uint8_t>& out)