Merge branch 'dev'

This commit is contained in:
Scott Powell
2025-03-03 13:26:43 +11:00
8 changed files with 238 additions and 26 deletions

View File

@@ -96,8 +96,15 @@ static uint32_t _atoi(const char* sp) {
/*------------ Frame Protocol --------------*/ /*------------ Frame Protocol --------------*/
#define FIRMWARE_VER_CODE 1 #define FIRMWARE_VER_CODE 2
#define FIRMWARE_BUILD_DATE "19 Feb 2025"
#ifndef FIRMWARE_BUILD_DATE
#define FIRMWARE_BUILD_DATE "3 Mar 2025"
#endif
#ifndef FIRMWARE_VERSION
#define FIRMWARE_VERSION "v1.0.0"
#endif
#define CMD_APP_START 1 #define CMD_APP_START 1
#define CMD_SEND_TXT_MSG 2 #define CMD_SEND_TXT_MSG 2
@@ -126,6 +133,8 @@ static uint32_t _atoi(const char* sp) {
#define CMD_SEND_RAW_DATA 25 #define CMD_SEND_RAW_DATA 25
#define CMD_SEND_LOGIN 26 #define CMD_SEND_LOGIN 26
#define CMD_SEND_STATUS_REQ 27 #define CMD_SEND_STATUS_REQ 27
#define CMD_HAS_CONNECTION 28
#define CMD_LOGOUT 29 // 'Disconnect'
#define RESP_CODE_OK 0 #define RESP_CODE_OK 0
#define RESP_CODE_ERR 1 #define RESP_CODE_ERR 1
@@ -435,16 +444,19 @@ protected:
expected_ack_crc = 0; // reset our expected hash, now that we have received ACK expected_ack_crc = 0; // reset our expected hash, now that we have received ACK
return true; return true;
} }
return false; return checkConnectionsAck(data);
} }
void queueMessage(const ContactInfo& from, uint8_t txt_type, uint8_t path_len, uint32_t sender_timestamp, const char *text) { void queueMessage(const ContactInfo& from, uint8_t txt_type, uint8_t path_len, uint32_t sender_timestamp, const uint8_t* extra, int extra_len, const char *text) {
int i = 0; int i = 0;
out_frame[i++] = RESP_CODE_CONTACT_MSG_RECV; out_frame[i++] = RESP_CODE_CONTACT_MSG_RECV;
memcpy(&out_frame[i], from.id.pub_key, 6); i += 6; // just 6-byte prefix memcpy(&out_frame[i], from.id.pub_key, 6); i += 6; // just 6-byte prefix
out_frame[i++] = path_len; out_frame[i++] = path_len;
out_frame[i++] = txt_type; out_frame[i++] = txt_type;
memcpy(&out_frame[i], &sender_timestamp, 4); i += 4; memcpy(&out_frame[i], &sender_timestamp, 4); i += 4;
if (extra_len > 0) {
memcpy(&out_frame[i], extra, extra_len); i += extra_len;
}
int tlen = strlen(text); // TODO: UTF-8 ?? int tlen = strlen(text); // TODO: UTF-8 ??
if (i + tlen > MAX_FRAME_SIZE) { if (i + tlen > MAX_FRAME_SIZE) {
tlen = MAX_FRAME_SIZE - i; tlen = MAX_FRAME_SIZE - i;
@@ -462,11 +474,19 @@ protected:
} }
void onMessageRecv(const ContactInfo& from, uint8_t path_len, uint32_t sender_timestamp, const char *text) override { void onMessageRecv(const ContactInfo& from, uint8_t path_len, uint32_t sender_timestamp, const char *text) override {
queueMessage(from, TXT_TYPE_PLAIN, path_len, sender_timestamp, text); markConnectionActive(from); // in case this is from a server, and we have a connection
queueMessage(from, TXT_TYPE_PLAIN, path_len, sender_timestamp, NULL, 0, text);
} }
void onCommandDataRecv(const ContactInfo& from, uint8_t path_len, uint32_t sender_timestamp, const char *text) override { void onCommandDataRecv(const ContactInfo& from, uint8_t path_len, uint32_t sender_timestamp, const char *text) override {
queueMessage(from, TXT_TYPE_CLI_DATA, path_len, sender_timestamp, text); markConnectionActive(from); // in case this is from a server, and we have a connection
queueMessage(from, TXT_TYPE_CLI_DATA, path_len, sender_timestamp, NULL, 0, text);
}
void onSignedMessageRecv(const ContactInfo& from, uint8_t path_len, uint32_t sender_timestamp, const uint8_t *sender_prefix, const char *text) override {
markConnectionActive(from);
saveContacts(); // from.sync_since change needs to be persisted
queueMessage(from, TXT_TYPE_SIGNED_PLAIN, path_len, sender_timestamp, sender_prefix, 4, text);
} }
void onChannelMessageRecv(const mesh::GroupChannel& channel, int in_path_len, uint32_t timestamp, const char *text) override { void onChannelMessageRecv(const mesh::GroupChannel& channel, int in_path_len, uint32_t timestamp, const char *text) override {
@@ -505,7 +525,10 @@ protected:
out_frame[i++] = PUSH_CODE_LOGIN_SUCCESS; out_frame[i++] = PUSH_CODE_LOGIN_SUCCESS;
out_frame[i++] = 0; // legacy: is_admin = false out_frame[i++] = 0; // legacy: is_admin = false
} else if (data[4] == RESP_SERVER_LOGIN_OK) { // new login response } else if (data[4] == RESP_SERVER_LOGIN_OK) { // new login response
// keep_alive_interval = data[5] * 16 uint16_t keep_alive_secs = ((uint16_t)data[5]) * 16;
if (keep_alive_secs > 0) {
startConnection(contact, keep_alive_secs);
}
out_frame[i++] = PUSH_CODE_LOGIN_SUCCESS; out_frame[i++] = PUSH_CODE_LOGIN_SUCCESS;
out_frame[i++] = data[6]; // permissions (eg. is_admin) out_frame[i++] = data[6]; // permissions (eg. is_admin)
} else { } else {
@@ -640,11 +663,9 @@ public:
out_frame[i++] = FIRMWARE_VER_CODE; out_frame[i++] = FIRMWARE_VER_CODE;
memset(&out_frame[i], 0, 6); i += 6; // reserved memset(&out_frame[i], 0, 6); i += 6; // reserved
memset(&out_frame[i], 0, 12); memset(&out_frame[i], 0, 12);
strcpy((char *) &out_frame[i], FIRMWARE_BUILD_DATE); strcpy((char *) &out_frame[i], FIRMWARE_BUILD_DATE); i += 12;
i += 12; StrHelper::strzcpy((char *) &out_frame[i], board.getManufacturerName(), 40); i += 40;
const char* name = board.getManufacturerName(); StrHelper::strzcpy((char *) &out_frame[i], FIRMWARE_VERSION, 20); i += 20;
int tlen = strlen(name);
memcpy(&out_frame[i], name, tlen); i += tlen;
_serial->writeFrame(out_frame, i); _serial->writeFrame(out_frame, i);
} else if (cmd_frame[0] == CMD_APP_START && len >= 8) { // sent when app establishes connection, respond with node ID } else if (cmd_frame[0] == CMD_APP_START && len >= 8) { // sent when app establishes connection, respond with node ID
// cmd_frame[1..7] reserved future // cmd_frame[1..7] reserved future
@@ -1013,6 +1034,17 @@ public:
} else { } else {
writeErrFrame(); // contact not found writeErrFrame(); // contact not found
} }
} else if (cmd_frame[0] == CMD_HAS_CONNECTION && len >= 1+PUB_KEY_SIZE) {
uint8_t* pub_key = &cmd_frame[1];
if (hasConnectionTo(pub_key)) {
writeOKFrame();
} else {
writeErrFrame();
}
} else if (cmd_frame[0] == CMD_LOGOUT && len >= 1+PUB_KEY_SIZE) {
uint8_t* pub_key = &cmd_frame[1];
stopConnection(pub_key);
writeOKFrame();
} else { } else {
writeErrFrame(); writeErrFrame();
MESH_DEBUG_PRINTLN("ERROR: unknown command: %02X", cmd_frame[0]); MESH_DEBUG_PRINTLN("ERROR: unknown command: %02X", cmd_frame[0]);
@@ -1042,6 +1074,8 @@ public:
_serial->writeFrame(out_frame, 5); _serial->writeFrame(out_frame, 5);
_iter_started = false; _iter_started = false;
} }
} else if (!_serial->isWriteBusy()) {
checkConnections();
} }
} }
}; };

View File

@@ -21,7 +21,13 @@
/* ------------------------------ Config -------------------------------- */ /* ------------------------------ Config -------------------------------- */
#define FIRMWARE_VER_TEXT "v6 (build: 27 Feb 2025)" #ifndef FIRMWARE_BUILD_DATE
#define FIRMWARE_BUILD_DATE "3 Mar 2025"
#endif
#ifndef FIRMWARE_VERSION
#define FIRMWARE_VERSION "v1.0.0"
#endif
#ifndef LORA_FREQ #ifndef LORA_FREQ
#define LORA_FREQ 915.0 #define LORA_FREQ 915.0
@@ -516,7 +522,8 @@ public:
updateAdvertTimer(); updateAdvertTimer();
} }
const char* getFirmwareVer() override { return FIRMWARE_VER_TEXT; } const char* getFirmwareVer() override { return FIRMWARE_VERSION; }
const char* getBuildDate() override { return FIRMWARE_BUILD_DATE; }
void savePrefs() override { void savePrefs() override {
#if defined(NRF52_PLATFORM) #if defined(NRF52_PLATFORM)

View File

@@ -21,7 +21,13 @@
/* ------------------------------ Config -------------------------------- */ /* ------------------------------ Config -------------------------------- */
#define FIRMWARE_VER_TEXT "v6 (build: 27 Feb 2025)" #ifndef FIRMWARE_BUILD_DATE
#define FIRMWARE_BUILD_DATE "3 Mar 2025"
#endif
#ifndef FIRMWARE_VERSION
#define FIRMWARE_VERSION "v1.0.0"
#endif
#ifndef LORA_FREQ #ifndef LORA_FREQ
#define LORA_FREQ 915.0 #define LORA_FREQ 915.0
@@ -117,7 +123,8 @@ struct PostInfo {
#define CLIENT_KEEP_ALIVE_SECS 128 #define CLIENT_KEEP_ALIVE_SECS 128
#define REQ_TYPE_KEEP_ALIVE 1 #define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS
#define REQ_TYPE_KEEP_ALIVE 0x02
#define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ #define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ
@@ -305,7 +312,7 @@ protected:
// TODO: maybe reply with count of messages waiting to be synced for THIS client? // TODO: maybe reply with count of messages waiting to be synced for THIS client?
reply_data[4] = RESP_SERVER_LOGIN_OK; reply_data[4] = RESP_SERVER_LOGIN_OK;
reply_data[5] = (CLIENT_KEEP_ALIVE_SECS >> 4); // NEW: recommended keep-alive interval (secs / 16) reply_data[5] = (CLIENT_KEEP_ALIVE_SECS >> 4); // NEW: recommended keep-alive interval (secs / 16)
reply_data[6] = 0; // FUTURE: reserved reply_data[6] = is_admin ? 1 : 0;
reply_data[7] = 0; // FUTURE: reserved reply_data[7] = 0; // FUTURE: reserved
memcpy(&reply_data[8], "OK", 2); // REVISIT: not really needed memcpy(&reply_data[8], "OK", 2); // REVISIT: not really needed
@@ -436,12 +443,11 @@ protected:
uint32_t forceSince = 0; uint32_t forceSince = 0;
if (len >= 9) { // optional - last post_timestamp client received if (len >= 9) { // optional - last post_timestamp client received
memcpy(&forceSince, &data[5], 4); // NOTE: this may be 0, if part of decrypted PADDING! memcpy(&forceSince, &data[5], 4); // NOTE: this may be 0, if part of decrypted PADDING!
} else {
memcpy(&data[5], &forceSince, 4); // make sure there are zeroes in payload (for ack_hash calc below)
} }
if (forceSince > 0) { if (forceSince > 0) {
client->sync_since = forceSince; // force-update the 'sync since' client->sync_since = forceSince; // force-update the 'sync since'
len = 9; // for ACK hash calc below
} else {
len = 5; // for ACK hash calc below
} }
uint32_t now = getRTCClock()->getCurrentTime(); uint32_t now = getRTCClock()->getCurrentTime();
@@ -455,7 +461,7 @@ protected:
// RULE: only send keep_alive response DIRECT! // RULE: only send keep_alive response DIRECT!
if (client->out_path_len >= 0) { if (client->out_path_len >= 0) {
uint32_t ack_hash; // calc ACK to prove to sender that we got request uint32_t ack_hash; // calc ACK to prove to sender that we got request
mesh::Utils::sha256((uint8_t *) &ack_hash, 4, data, len, client->id.pub_key, PUB_KEY_SIZE); mesh::Utils::sha256((uint8_t *) &ack_hash, 4, data, 9, client->id.pub_key, PUB_KEY_SIZE);
auto reply = createAck(ack_hash); auto reply = createAck(ack_hash);
if (reply) { if (reply) {
@@ -551,7 +557,8 @@ public:
updateAdvertTimer(); updateAdvertTimer();
} }
const char* getFirmwareVer() override { return FIRMWARE_VER_TEXT; } const char* getFirmwareVer() override { return FIRMWARE_VERSION; }
const char* getBuildDate() override { return FIRMWARE_BUILD_DATE; }
void savePrefs() override { void savePrefs() override {
#if defined(NRF52_PLATFORM) #if defined(NRF52_PLATFORM)

View File

@@ -261,6 +261,8 @@ protected:
void onCommandDataRecv(const ContactInfo& from, uint8_t path_len, uint32_t sender_timestamp, const char *text) override { void onCommandDataRecv(const ContactInfo& from, uint8_t path_len, uint32_t sender_timestamp, const char *text) override {
} }
void onSignedMessageRecv(const ContactInfo& from, uint8_t path_len, uint32_t sender_timestamp, const uint8_t *sender_prefix, const char *text) override {
}
void onChannelMessageRecv(const mesh::GroupChannel& channel, int in_path_len, uint32_t timestamp, const char *text) override { void onChannelMessageRecv(const mesh::GroupChannel& channel, int in_path_len, uint32_t timestamp, const char *text) override {
if (in_path_len < 0) { if (in_path_len < 0) {

View File

@@ -136,7 +136,29 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender
if (path) sendFlood(path); if (path) sendFlood(path);
} }
} else if (flags == TXT_TYPE_SIGNED_PLAIN) { } else if (flags == TXT_TYPE_SIGNED_PLAIN) {
// TODO if (timestamp > from.sync_since) { // make sure 'sync_since' is up-to-date
from.sync_since = timestamp;
}
onSignedMessageRecv(from, packet->isRouteFlood() ? packet->path_len : 0xFF, timestamp, &data[5], (const char *) &data[9]); // let UI know
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, 9 + strlen((char *)&data[9]), from.id.pub_key, PUB_KEY_SIZE);
if (packet->isRouteFlood()) {
// 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,
PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4);
if (path) sendFlood(path);
} else {
mesh::Packet* ack = createAck(ack_hash);
if (ack) {
if (from.out_path_len < 0) {
sendFlood(ack);
} else {
sendDirect(ack, from.out_path, from.out_path_len);
}
}
}
} else { } else {
MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported message type: %u", (uint32_t) flags); MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported message type: %u", (uint32_t) flags);
} }
@@ -352,7 +374,7 @@ int BaseChatMesh::sendStatusRequest(const ContactInfo& recipient, uint32_t& est
uint8_t temp[13]; uint8_t temp[13];
uint32_t now = getRTCClock()->getCurrentTimeUnique(); uint32_t now = getRTCClock()->getCurrentTimeUnique();
memcpy(temp, &now, 4); // mostly an extra blob to help make packet_hash unique memcpy(temp, &now, 4); // mostly an extra blob to help make packet_hash unique
temp[4] = CMD_GET_STATUS; temp[4] = REQ_TYPE_GET_STATUS;
memset(&temp[5], 0, 4); // reserved (possibly for 'since' param) memset(&temp[5], 0, 4); // reserved (possibly for 'since' param)
getRNG()->random(&temp[9], 4); // random blob to help make packet-hash unique getRNG()->random(&temp[9], 4); // random blob to help make packet-hash unique
@@ -372,6 +394,121 @@ int BaseChatMesh::sendStatusRequest(const ContactInfo& recipient, uint32_t& est
return MSG_SEND_FAILED; return MSG_SEND_FAILED;
} }
bool BaseChatMesh::startConnection(const ContactInfo& contact, uint16_t keep_alive_secs) {
int use_idx = -1;
for (int i = 0; i < MAX_CONNECTIONS; i++) {
if (connections[i].keep_alive_millis == 0) { // free slot?
use_idx = i;
} else if (connections[i].server_id.matches(contact.id)) { // already in table?
use_idx = i;
break;
}
}
if (use_idx < 0) {
return false; // table is full
}
connections[use_idx].server_id = contact.id;
uint32_t interval = connections[use_idx].keep_alive_millis = ((uint32_t)keep_alive_secs)*1000;
connections[use_idx].next_ping = futureMillis(interval);
connections[use_idx].expected_ack = 0;
connections[use_idx].last_activity = getRTCClock()->getCurrentTime();
return true; // success
}
void BaseChatMesh::stopConnection(const uint8_t* pub_key) {
for (int i = 0; i < MAX_CONNECTIONS; i++) {
if (connections[i].server_id.matches(pub_key)) {
connections[i].keep_alive_millis = 0; // mark slot as now free
connections[i].next_ping = 0;
connections[i].expected_ack = 0;
connections[i].last_activity = 0;
break;
}
}
}
bool BaseChatMesh::hasConnectionTo(const uint8_t* pub_key) {
for (int i = 0; i < MAX_CONNECTIONS; i++) {
if (connections[i].keep_alive_millis > 0 && connections[i].server_id.matches(pub_key)) return true;
}
return false;
}
void BaseChatMesh::markConnectionActive(const ContactInfo& contact) {
for (int i = 0; i < MAX_CONNECTIONS; i++) {
if (connections[i].keep_alive_millis > 0 && connections[i].server_id.matches(contact.id)) {
connections[i].last_activity = getRTCClock()->getCurrentTime();
// re-schedule next KEEP_ALIVE, now that we have heard from server
connections[i].next_ping = futureMillis(connections[i].keep_alive_millis);
break;
}
}
}
bool BaseChatMesh::checkConnectionsAck(const uint8_t* data) {
for (int i = 0; i < MAX_CONNECTIONS; i++) {
if (connections[i].keep_alive_millis > 0 && memcmp(&connections[i].expected_ack, data, 4) == 0) {
// yes, got an ack for our keep_alive request!
connections[i].expected_ack = 0;
connections[i].last_activity = getRTCClock()->getCurrentTime();
// re-schedule next KEEP_ALIVE, now that we have heard from server
connections[i].next_ping = futureMillis(connections[i].keep_alive_millis);
return true; // yes, a match
}
}
return false; /// no match
}
void BaseChatMesh::checkConnections() {
// scan connections[] table, send KEEP_ALIVE requests
for (int i = 0; i < MAX_CONNECTIONS; i++) {
if (connections[i].keep_alive_millis == 0) continue; // unused slot
uint32_t now = getRTCClock()->getCurrentTime();
uint32_t expire_secs = (connections[i].keep_alive_millis / 1000) * 5 / 2; // 2.5 x keep_alive interval
if (now >= connections[i].last_activity + expire_secs) {
// connection now lost
connections[i].keep_alive_millis = 0;
connections[i].next_ping = 0;
connections[i].expected_ack = 0;
connections[i].last_activity = 0;
continue;
}
if (millisHasNowPassed(connections[i].next_ping)) {
auto contact = lookupContactByPubKey(connections[i].server_id.pub_key, PUB_KEY_SIZE);
if (contact == NULL) {
MESH_DEBUG_PRINTLN("checkConnections(): Keep_alive contact not found!");
continue;
}
if (contact->out_path_len < 0) {
MESH_DEBUG_PRINTLN("checkConnections(): Keep_alive contact, no out_path!");
continue;
}
// send KEEP_ALIVE request
uint8_t data[9];
uint32_t now = getRTCClock()->getCurrentTimeUnique();
memcpy(data, &now, 4);
data[4] = REQ_TYPE_KEEP_ALIVE;
memcpy(&data[5], &contact->sync_since, 4);
// calc expected ACK reply
mesh::Utils::sha256((uint8_t *)&connections[i].expected_ack, 4, data, 9, self_id.pub_key, PUB_KEY_SIZE);
auto pkt = createDatagram(PAYLOAD_TYPE_REQ, contact->id, contact->shared_secret, data, 9);
if (pkt) {
sendDirect(pkt, contact->out_path, contact->out_path_len);
}
// schedule next KEEP_ALIVE
connections[i].next_ping = futureMillis(connections[i].keep_alive_millis);
}
}
}
void BaseChatMesh::resetPathTo(ContactInfo& recipient) { void BaseChatMesh::resetPathTo(ContactInfo& recipient) {
recipient.out_path_len = -1; recipient.out_path_len = -1;
} }

View File

@@ -27,7 +27,8 @@ struct ContactInfo {
#define MSG_SEND_SENT_FLOOD 1 #define MSG_SEND_SENT_FLOOD 1
#define MSG_SEND_SENT_DIRECT 2 #define MSG_SEND_SENT_DIRECT 2
#define CMD_GET_STATUS 0x01 // same as _GET_STATS #define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS
#define REQ_TYPE_KEEP_ALIVE 0x02
#define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ #define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ
@@ -48,6 +49,18 @@ public:
#define MAX_CONTACTS 32 #define MAX_CONTACTS 32
#endif #endif
#ifndef MAX_CONNECTIONS
#define MAX_CONNECTIONS 16
#endif
struct ConnectionInfo {
mesh::Identity server_id;
unsigned long next_ping;
uint32_t last_activity;
uint32_t keep_alive_millis;
uint32_t expected_ack;
};
/** /**
* \brief abstract Mesh class for common 'chat' client * \brief abstract Mesh class for common 'chat' client
*/ */
@@ -66,6 +79,7 @@ class BaseChatMesh : public mesh::Mesh {
#endif #endif
mesh::Packet* _pendingLoopback; mesh::Packet* _pendingLoopback;
uint8_t temp_buf[MAX_TRANS_UNIT]; uint8_t temp_buf[MAX_TRANS_UNIT];
ConnectionInfo connections[MAX_CONNECTIONS];
mesh::Packet* composeMsgPacket(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char *text, uint32_t& expected_ack); mesh::Packet* composeMsgPacket(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char *text, uint32_t& expected_ack);
@@ -79,6 +93,7 @@ protected:
#endif #endif
txt_send_timeout = 0; txt_send_timeout = 0;
_pendingLoopback = NULL; _pendingLoopback = NULL;
memset(connections, 0, sizeof(connections));
} }
// 'UI' concepts, for sub-classes to implement // 'UI' concepts, for sub-classes to implement
@@ -87,6 +102,7 @@ protected:
virtual void onContactPathUpdated(const ContactInfo& contact) = 0; virtual void onContactPathUpdated(const ContactInfo& contact) = 0;
virtual void onMessageRecv(const ContactInfo& contact, uint8_t path_len, uint32_t sender_timestamp, const char *text) = 0; virtual void onMessageRecv(const ContactInfo& contact, uint8_t path_len, uint32_t sender_timestamp, const char *text) = 0;
virtual void onCommandDataRecv(const ContactInfo& contact, uint8_t path_len, uint32_t sender_timestamp, const char *text) = 0; virtual void onCommandDataRecv(const ContactInfo& contact, uint8_t path_len, uint32_t sender_timestamp, const char *text) = 0;
virtual void onSignedMessageRecv(const ContactInfo& contact, uint8_t path_len, uint32_t sender_timestamp, const uint8_t *sender_prefix, const char *text) = 0;
virtual uint32_t calcFloodTimeoutMillisFor(uint32_t pkt_airtime_millis) const = 0; virtual uint32_t calcFloodTimeoutMillisFor(uint32_t pkt_airtime_millis) const = 0;
virtual uint32_t calcDirectTimeoutMillisFor(uint32_t pkt_airtime_millis, uint8_t path_len) const = 0; virtual uint32_t calcDirectTimeoutMillisFor(uint32_t pkt_airtime_millis, uint8_t path_len) const = 0;
virtual void onSendTimeout() = 0; virtual void onSendTimeout() = 0;
@@ -109,6 +125,14 @@ protected:
#endif #endif
void onGroupDataRecv(mesh::Packet* packet, uint8_t type, const mesh::GroupChannel& channel, uint8_t* data, size_t len) override; void onGroupDataRecv(mesh::Packet* packet, uint8_t type, const mesh::GroupChannel& channel, uint8_t* data, size_t len) override;
// Connections
bool startConnection(const ContactInfo& contact, uint16_t keep_alive_secs);
void stopConnection(const uint8_t* pub_key);
bool hasConnectionTo(const uint8_t* pub_key);
void markConnectionActive(const ContactInfo& contact);
bool checkConnectionsAck(const uint8_t* data);
void checkConnections();
public: public:
mesh::Packet* createSelfAdvert(const char* name, double lat=0.0, double lon=0.0); mesh::Packet* createSelfAdvert(const char* name, double lat=0.0, double lon=0.0);
int sendMessage(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char* text, uint32_t& expected_ack, uint32_t& est_timeout); int sendMessage(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char* text, uint32_t& expected_ack, uint32_t& est_timeout);

View File

@@ -204,7 +204,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
bool s = _callbacks->formatFileSystem(); bool s = _callbacks->formatFileSystem();
sprintf(reply, "File system erase: %s", s ? "OK" : "Err"); sprintf(reply, "File system erase: %s", s ? "OK" : "Err");
} else if (memcmp(command, "ver", 3) == 0) { } else if (memcmp(command, "ver", 3) == 0) {
strcpy(reply, _callbacks->getFirmwareVer()); sprintf(reply, "%s (Build: %s)", _callbacks->getFirmwareVer(), _callbacks->getBuildDate());
} else if (memcmp(command, "log start", 9) == 0) { } else if (memcmp(command, "log start", 9) == 0) {
_callbacks->setLoggingOn(true); _callbacks->setLoggingOn(true);
strcpy(reply, " logging on"); strcpy(reply, " logging on");

View File

@@ -28,6 +28,7 @@ class CommonCLICallbacks {
public: public:
virtual void savePrefs() = 0; virtual void savePrefs() = 0;
virtual const char* getFirmwareVer() = 0; virtual const char* getFirmwareVer() = 0;
virtual const char* getBuildDate() = 0;
virtual bool formatFileSystem() = 0; virtual bool formatFileSystem() = 0;
virtual void sendSelfAdvertisement(int delay_millis) = 0; virtual void sendSelfAdvertisement(int delay_millis) = 0;
virtual void updateAdvertTimer() = 0; virtual void updateAdvertTimer() = 0;