Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions boards/seeed_wio_tracker_l2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"build": {
"arduino": {
"ldscript": "esp32s3_out.ld",
"memory_type": "qio_opi",
"partitions": "default_16MB.csv"
},
"core": "esp32",
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"psram_type": "opi",
"hwids": [["0x303A", "0x1001"]],
"mcu": "esp32s3",
"variant": "esp32s3"
},
"connectivity": ["wifi", "bluetooth", "lora"],
"debug": {
"default_tool": "esp-builtin",
"onboard_tools": ["esp-builtin"],
"openocd_target": "esp32s3.cfg"
},
"frameworks": ["arduino"],
"name": "Seeed Wio Tracker L2 (16 MB flash, 8 MB OPI PSRAM)",
"upload": {
"flash_size": "16MB",
"maximum_ram_size": 327680,
"maximum_size": 16777216,
"use_1200bps_touch": true,
"wait_for_upload_port": true,
"require_upload_port": true,
"speed": 921600
},
"url": "https://www.seeedstudio.com/",
"vendor": "Seeed Studio"
}
6 changes: 6 additions & 0 deletions examples/companion_radio/AbstractUITask.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ class AbstractUITask {
void disableBluetooth() { _interfaceManager->disableBluetooth(); }
virtual void msgRead(int msgcount) = 0;
virtual void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) = 0;
virtual void msgAck(uint32_t ack_crc) { } // delivery ACK received (any origin)
virtual void msgEchoHeard() { } // our last sent message heard being retransmitted
virtual void loginResult(const uint8_t* pub_key, bool success) { } // repeater/room login outcome
virtual void statusResponse(const uint8_t* pub_key, const uint8_t* data, int len) { } // REQ_TYPE_GET_STATUS reply
virtual void cliResponse(const char* from_name, const char* text) { } // repeater CLI reply text
virtual void traceResponse(uint32_t tag, const uint8_t* path_hashes, const uint8_t* path_snrs, uint8_t hop_count, int8_t final_snr) { } // TRACE round trip returned
virtual void notify(UIEventType t = UIEventType::none) = 0;
virtual void loop() = 0;
};
92 changes: 92 additions & 0 deletions examples/companion_radio/MyMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,33 @@ uint8_t MyMesh::getExtraAckTransmitCount() const {
return _prefs.multi_acks;
}

static uint32_t fnv1aHash(const uint8_t* data, int len) {
uint32_t h = 2166136261u;
for (int i = 0; i < len; i++) {
h ^= data[i];
h *= 16777619u;
}
return h;
}

void MyMesh::logTx(mesh::Packet* packet, int len) {
// remember the payload signature of outgoing text so the UI can count
// repeaters echoing it (payload bytes survive retransmission unchanged)
uint8_t t = packet->getPayloadType();
if (_ui != NULL && (t == PAYLOAD_TYPE_TXT_MSG || t == PAYLOAD_TYPE_GRP_TXT) && packet->payload_len > 0) {
echo_hash = fnv1aHash(packet->payload, packet->payload_len);
echo_len = packet->payload_len;
echo_time = millis();
}
}

void MyMesh::logRxRaw(float snr, float rssi, const uint8_t raw[], int len) {
// the payload sits at the tail of the frame; a match on the last echo_len
// bytes means a neighbor just retransmitted our message
if (_ui != NULL && echo_len > 0 && len > (int) echo_len && millis() - echo_time < 60000) {
if (fnv1aHash(&raw[len - echo_len], echo_len) == echo_hash) _ui->msgEchoHeard();
}

if (_serial->isConnected() && len + 3 <= MAX_FRAME_SIZE) {
int i = 0;
out_frame[i++] = PUSH_CODE_LOG_RX_DATA;
Expand Down Expand Up @@ -465,6 +491,13 @@ void MyMesh::onContactPathUpdated(const ContactInfo &contact) {
}

ContactInfo* MyMesh::processAck(const uint8_t *data) {
#ifdef DISPLAY_CLASS
if (_ui) { // let on-device UI match acks for messages it originated
uint32_t ack_crc;
memcpy(&ack_crc, data, 4);
_ui->msgAck(ack_crc);
}
#endif
// see if matches any in a table
for (int i = 0; i < EXPECTED_ACK_TABLE_SIZE; i++) {
if (memcmp(data, &expected_ack_table[i].ack, 4) == 0) { // got an ACK from recipient
Expand Down Expand Up @@ -525,6 +558,8 @@ void MyMesh::queueMessage(const ContactInfo &from, uint8_t txt_type, mesh::Packe
if (!_serial->isConnected()) {
_ui->notify(UIEventType::contactMessage);
}
} else if (txt_type == TXT_TYPE_CLI_DATA && _ui) {
_ui->cliResponse(from.name, text); // for UIs with a repeater terminal
}
#endif
}
Expand Down Expand Up @@ -737,6 +772,35 @@ uint8_t MyMesh::onContactRequest(const ContactInfo &contact, uint32_t sender_tim
return 0; // unknown
}

int MyMesh::uiLogin(const ContactInfo &recipient, const char *password) {
uint32_t est_timeout;
int result = sendLogin(recipient, password, est_timeout);
if (result != MSG_SEND_FAILED) {
clearPendingReqs();
memcpy(&pending_login, recipient.id.pub_key, 4); // match this to onContactResponse()
}
return result;
}

int MyMesh::uiRequestStatus(const ContactInfo &recipient) {
uint32_t tag, est_timeout;
int result = sendRequest(recipient, REQ_TYPE_GET_STATUS, tag, est_timeout);
if (result != MSG_SEND_FAILED) {
clearPendingReqs();
memcpy(&pending_status, recipient.id.pub_key, 4); // match this to onContactResponse()
}
return result;
}

int MyMesh::uiTracePath(const uint8_t *path, uint8_t path_len, uint32_t tag) {
auto pkt = createTrace(tag, 0, 0); // flags = 0: one-byte path hashes
if (pkt == NULL) return 0;
sendDirect(pkt, path, path_len);

uint32_t t = _radio->getEstAirtimeFor(pkt->payload_len + pkt->path_len + 2);
return (int) calcDirectTimeoutMillisFor(t, path_len);
}

void MyMesh::onContactResponse(const ContactInfo &contact, const uint8_t *data, uint8_t len) {
uint32_t tag;
memcpy(&tag, data, 4);
Expand All @@ -745,6 +809,13 @@ void MyMesh::onContactResponse(const ContactInfo &contact, const uint8_t *data,
// yes, is response to pending sendLogin()
pending_login = 0;

#ifdef DISPLAY_CLASS
if (_ui) {
bool login_ok = (memcmp(&data[4], "OK", 2) == 0) || data[4] == RESP_SERVER_LOGIN_OK;
_ui->loginResult(contact.id.pub_key, login_ok);
}
#endif

int i = 0;
if (memcmp(&data[4], "OK", 2) == 0) { // legacy Repeater login OK response
out_frame[i++] = PUSH_CODE_LOGIN_SUCCESS;
Expand Down Expand Up @@ -778,6 +849,10 @@ void MyMesh::onContactResponse(const ContactInfo &contact, const uint8_t *data,
) {
pending_status = 0;

#ifdef DISPLAY_CLASS
if (_ui) _ui->statusResponse(contact.id.pub_key, &data[4], len - 4);
#endif

int i = 0;
out_frame[i++] = PUSH_CODE_STATUS_RESPONSE;
out_frame[i++] = 0; // reserved
Expand Down Expand Up @@ -888,6 +963,9 @@ void MyMesh::onRawDataRecv(mesh::Packet *packet) {
void MyMesh::onTraceRecv(mesh::Packet *packet, uint32_t tag, uint32_t auth_code, uint8_t flags,
const uint8_t *path_snrs, const uint8_t *path_hashes, uint8_t path_len) {
uint8_t path_sz = flags & 0x03; // NEW v1.11+
if (_ui != NULL) { // on-device trace UI (matches by tag)
_ui->traceResponse(tag, path_hashes, path_snrs, (uint8_t)(path_len >> path_sz), (int8_t)(packet->getSNR() * 4));
}
if (12 + path_len + (path_len >> path_sz) + 1 > sizeof(out_frame)) {
MESH_DEBUG_PRINTLN("onTraceRecv(), path_len is too long: %d", (uint32_t)path_len);
return;
Expand Down Expand Up @@ -2403,6 +2481,20 @@ void MyMesh::loop() {
#endif
}

bool MyMesh::advertFlood() {
mesh::Packet* pkt;
if (_prefs.advert_loc_policy == ADVERT_LOC_NONE) {
pkt = createSelfAdvert(_prefs.node_name);
} else {
pkt = createSelfAdvert(_prefs.node_name, sensors.node_lat, sensors.node_lon);
}
if (pkt == NULL) return false;
TransportKey default_scope;
memcpy(&default_scope.key, _prefs.default_scope_key, sizeof(default_scope.key));
sendFloodScoped(default_scope, pkt, 0);
return true;
}

bool MyMesh::advert() {
mesh::Packet* pkt;
if (_prefs.advert_loc_policy == ADVERT_LOC_NONE) {
Expand Down
22 changes: 19 additions & 3 deletions examples/companion_radio/MyMesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ class MyMesh : public BaseChatMesh, public DataStoreHost {
void loop();
void handleCmdFrame(size_t len);
bool advert();
bool advertFlood(); // same advert, flood-routed (phone app "flood advert")
const mesh::LocalIdentity& selfId() const { return self_id; }
void enterCLIRescue();

int getRecentlyHeard(AdvertPath dest[], int max_num);
Expand All @@ -117,6 +119,17 @@ class MyMesh : public BaseChatMesh, public DataStoreHost {
int getDiscoveredNodes(DiscoveredNode nodes[], int max_num);
#endif

// on-device UI login to repeater/room server (registers for the response
// like the phone CMD_SEND_LOGIN path does)
int uiLogin(const ContactInfo& recipient, const char* password);
int uiRequestStatus(const ContactInfo& recipient);
int uiTracePath(const uint8_t* path, uint8_t path_len, uint32_t tag);

// used by the on-device UI as well as the phone command handlers
void saveChannels() { _store->saveChannels(this); }
void saveContacts();
bool isValidClientRepeatFreq(uint32_t f) const;

protected:
float getAirtimeBudgetFactor() const override;
int getInterferenceThreshold() const override;
Expand All @@ -136,6 +149,7 @@ class MyMesh : public BaseChatMesh, public DataStoreHost {
void sendFloodScoped(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t delay_millis=0) override;

void logRxRaw(float snr, float rssi, const uint8_t raw[], int len) override;
void logTx(mesh::Packet* packet, int len) override;
bool isAutoAddEnabled() const override;
bool shouldAutoAddContactType(uint8_t type) const override;
bool shouldOverwriteWhenFull() const override;
Expand Down Expand Up @@ -224,11 +238,8 @@ class MyMesh : public BaseChatMesh, public DataStoreHost {
void checkCLIRescueCmd();
bool handleCommand(const char* text, uint32_t sender_timestamp, char* reply);
void checkSerialInterface();
bool isValidClientRepeatFreq(uint32_t f) const;

// helpers, short-cuts
void saveChannels() { _store->saveChannels(this); }
void saveContacts();

DataStore* _store;
NodePrefs _prefs;
Expand All @@ -255,6 +266,11 @@ class MyMesh : public BaseChatMesh, public DataStoreHost {

TransportKey send_scope;

// signature of the last transmitted text message, for UI echo detection
uint32_t echo_hash = 0;
uint16_t echo_len = 0;
unsigned long echo_time = 0;

uint8_t cmd_frame[MAX_FRAME_SIZE + 1];
uint8_t out_frame[MAX_FRAME_SIZE + 1];
CayenneLPP telemetry;
Expand Down
2 changes: 2 additions & 0 deletions examples/companion_radio/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,14 @@ void setup() {
DisplayDriver* disp = NULL;
if (display.begin()) {
disp = &display;
#ifndef UI_LVGL // LVGL UI shows its own splash; skip the text banner
disp->startFrame();
#ifdef ST7789
disp->setTextSize(2);
#endif
disp->drawTextCentered(disp->width() / 2, 28, "Loading...");
disp->endFrame();
#endif
}
#endif

Expand Down
Loading