Merge pull request #1379 from oltaco/improved-contact-mgmt
Companion: Improved Contact Management
This commit is contained in:
@@ -227,6 +227,7 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no
|
|||||||
file.read((uint8_t *)&_prefs.buzzer_quiet, sizeof(_prefs.buzzer_quiet)); // 84
|
file.read((uint8_t *)&_prefs.buzzer_quiet, sizeof(_prefs.buzzer_quiet)); // 84
|
||||||
file.read((uint8_t *)&_prefs.gps_enabled, sizeof(_prefs.gps_enabled)); // 85
|
file.read((uint8_t *)&_prefs.gps_enabled, sizeof(_prefs.gps_enabled)); // 85
|
||||||
file.read((uint8_t *)&_prefs.gps_interval, sizeof(_prefs.gps_interval)); // 86
|
file.read((uint8_t *)&_prefs.gps_interval, sizeof(_prefs.gps_interval)); // 86
|
||||||
|
file.read((uint8_t *)&_prefs.autoadd_config, sizeof(_prefs.autoadd_config)); // 87
|
||||||
|
|
||||||
file.close();
|
file.close();
|
||||||
}
|
}
|
||||||
@@ -261,6 +262,7 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_
|
|||||||
file.write((uint8_t *)&_prefs.buzzer_quiet, sizeof(_prefs.buzzer_quiet)); // 84
|
file.write((uint8_t *)&_prefs.buzzer_quiet, sizeof(_prefs.buzzer_quiet)); // 84
|
||||||
file.write((uint8_t *)&_prefs.gps_enabled, sizeof(_prefs.gps_enabled)); // 85
|
file.write((uint8_t *)&_prefs.gps_enabled, sizeof(_prefs.gps_enabled)); // 85
|
||||||
file.write((uint8_t *)&_prefs.gps_interval, sizeof(_prefs.gps_interval)); // 86
|
file.write((uint8_t *)&_prefs.gps_interval, sizeof(_prefs.gps_interval)); // 86
|
||||||
|
file.write((uint8_t *)&_prefs.autoadd_config, sizeof(_prefs.autoadd_config)); // 87
|
||||||
|
|
||||||
file.close();
|
file.close();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,8 @@
|
|||||||
#define CMD_SEND_CONTROL_DATA 55 // v8+
|
#define CMD_SEND_CONTROL_DATA 55 // v8+
|
||||||
#define CMD_GET_STATS 56 // v8+, second byte is stats type
|
#define CMD_GET_STATS 56 // v8+, second byte is stats type
|
||||||
#define CMD_SEND_ANON_REQ 57
|
#define CMD_SEND_ANON_REQ 57
|
||||||
|
#define CMD_SET_AUTOADD_CONFIG 58
|
||||||
|
#define CMD_GET_AUTOADD_CONFIG 59
|
||||||
|
|
||||||
// Stats sub-types for CMD_GET_STATS
|
// Stats sub-types for CMD_GET_STATS
|
||||||
#define STATS_TYPE_CORE 0
|
#define STATS_TYPE_CORE 0
|
||||||
@@ -85,6 +87,7 @@
|
|||||||
#define RESP_CODE_ADVERT_PATH 22
|
#define RESP_CODE_ADVERT_PATH 22
|
||||||
#define RESP_CODE_TUNING_PARAMS 23
|
#define RESP_CODE_TUNING_PARAMS 23
|
||||||
#define RESP_CODE_STATS 24 // v8+, second byte is stats type
|
#define RESP_CODE_STATS 24 // v8+, second byte is stats type
|
||||||
|
#define RESP_CODE_AUTOADD_CONFIG 25
|
||||||
|
|
||||||
#define SEND_TIMEOUT_BASE_MILLIS 500
|
#define SEND_TIMEOUT_BASE_MILLIS 500
|
||||||
#define FLOOD_SEND_TIMEOUT_FACTOR 16.0f
|
#define FLOOD_SEND_TIMEOUT_FACTOR 16.0f
|
||||||
@@ -110,6 +113,8 @@
|
|||||||
#define PUSH_CODE_BINARY_RESPONSE 0x8C
|
#define PUSH_CODE_BINARY_RESPONSE 0x8C
|
||||||
#define PUSH_CODE_PATH_DISCOVERY_RESPONSE 0x8D
|
#define PUSH_CODE_PATH_DISCOVERY_RESPONSE 0x8D
|
||||||
#define PUSH_CODE_CONTROL_DATA 0x8E // v8+
|
#define PUSH_CODE_CONTROL_DATA 0x8E // v8+
|
||||||
|
#define PUSH_CODE_CONTACT_DELETED 0x8F // used to notify client app of deleted contact when overwriting oldest
|
||||||
|
#define PUSH_CODE_CONTACTS_FULL 0x90 // used to notify client app that contacts storage is full
|
||||||
|
|
||||||
#define ERR_CODE_UNSUPPORTED_CMD 1
|
#define ERR_CODE_UNSUPPORTED_CMD 1
|
||||||
#define ERR_CODE_NOT_FOUND 2
|
#define ERR_CODE_NOT_FOUND 2
|
||||||
@@ -120,6 +125,15 @@
|
|||||||
|
|
||||||
#define MAX_SIGN_DATA_LEN (8 * 1024) // 8K
|
#define MAX_SIGN_DATA_LEN (8 * 1024) // 8K
|
||||||
|
|
||||||
|
// Auto-add config bitmask
|
||||||
|
// Bit 0: If set, overwrite oldest non-favourite contact when contacts file is full
|
||||||
|
// Bits 1-4: these indicate which contact types to auto-add when manual_contact_mode = 0x01
|
||||||
|
#define AUTO_ADD_OVERWRITE_OLDEST (1 << 0) // 0x01 - overwrite oldest non-favourite when full
|
||||||
|
#define AUTO_ADD_CHAT (1 << 1) // 0x02 - auto-add Chat (Companion) (ADV_TYPE_CHAT)
|
||||||
|
#define AUTO_ADD_REPEATER (1 << 2) // 0x04 - auto-add Repeater (ADV_TYPE_REPEATER)
|
||||||
|
#define AUTO_ADD_ROOM_SERVER (1 << 3) // 0x08 - auto-add Room Server (ADV_TYPE_ROOM)
|
||||||
|
#define AUTO_ADD_SENSOR (1 << 4) // 0x10 - auto-add Sensor (ADV_TYPE_SENSOR)
|
||||||
|
|
||||||
void MyMesh::writeOKFrame() {
|
void MyMesh::writeOKFrame() {
|
||||||
uint8_t buf[1];
|
uint8_t buf[1];
|
||||||
buf[0] = RESP_CODE_OK;
|
buf[0] = RESP_CODE_OK;
|
||||||
@@ -262,9 +276,54 @@ bool MyMesh::isAutoAddEnabled() const {
|
|||||||
return (_prefs.manual_add_contacts & 1) == 0;
|
return (_prefs.manual_add_contacts & 1) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MyMesh::shouldAutoAddContactType(uint8_t contact_type) const {
|
||||||
|
if ((_prefs.manual_add_contacts & 1) == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t type_bit = 0;
|
||||||
|
switch (contact_type) {
|
||||||
|
case ADV_TYPE_CHAT:
|
||||||
|
type_bit = AUTO_ADD_CHAT;
|
||||||
|
break;
|
||||||
|
case ADV_TYPE_REPEATER:
|
||||||
|
type_bit = AUTO_ADD_REPEATER;
|
||||||
|
break;
|
||||||
|
case ADV_TYPE_ROOM:
|
||||||
|
type_bit = AUTO_ADD_ROOM_SERVER;
|
||||||
|
break;
|
||||||
|
case ADV_TYPE_SENSOR:
|
||||||
|
type_bit = AUTO_ADD_SENSOR;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false; // Unknown type, don't auto-add
|
||||||
|
}
|
||||||
|
|
||||||
|
return (_prefs.autoadd_config & type_bit) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MyMesh::shouldOverwriteWhenFull() const {
|
||||||
|
return (_prefs.autoadd_config & AUTO_ADD_OVERWRITE_OLDEST) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MyMesh::onContactOverwrite(const uint8_t* pub_key) {
|
||||||
|
if (_serial->isConnected()) {
|
||||||
|
out_frame[0] = PUSH_CODE_CONTACT_DELETED;
|
||||||
|
memcpy(&out_frame[1], pub_key, PUB_KEY_SIZE);
|
||||||
|
_serial->writeFrame(out_frame, 1 + PUB_KEY_SIZE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MyMesh::onContactsFull() {
|
||||||
|
if (_serial->isConnected()) {
|
||||||
|
out_frame[0] = PUSH_CODE_CONTACTS_FULL;
|
||||||
|
_serial->writeFrame(out_frame, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path_len, const uint8_t* path) {
|
void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path_len, const uint8_t* path) {
|
||||||
if (_serial->isConnected()) {
|
if (_serial->isConnected()) {
|
||||||
if (!isAutoAddEnabled() && is_new) {
|
if (!shouldAutoAddContactType(contact.type) && is_new) {
|
||||||
writeContactRespFrame(PUSH_CODE_NEW_ADVERT, contact);
|
writeContactRespFrame(PUSH_CODE_NEW_ADVERT, contact);
|
||||||
} else {
|
} else {
|
||||||
out_frame[0] = PUSH_CODE_ADVERT;
|
out_frame[0] = PUSH_CODE_ADVERT;
|
||||||
@@ -803,6 +862,7 @@ void MyMesh::begin(bool has_display) {
|
|||||||
|
|
||||||
resetContacts();
|
resetContacts();
|
||||||
_store->loadContacts(this);
|
_store->loadContacts(this);
|
||||||
|
bootstrapRTCfromContacts();
|
||||||
addChannel("Public", PUBLIC_GROUP_PSK); // pre-configure Andy's public channel
|
addChannel("Public", PUBLIC_GROUP_PSK); // pre-configure Andy's public channel
|
||||||
_store->loadChannels(this);
|
_store->loadChannels(this);
|
||||||
|
|
||||||
@@ -1663,6 +1723,15 @@ void MyMesh::handleCmdFrame(size_t len) {
|
|||||||
} else {
|
} else {
|
||||||
writeErrFrame(ERR_CODE_TABLE_FULL);
|
writeErrFrame(ERR_CODE_TABLE_FULL);
|
||||||
}
|
}
|
||||||
|
} else if (cmd_frame[0] == CMD_SET_AUTOADD_CONFIG) {
|
||||||
|
_prefs.autoadd_config = cmd_frame[1];
|
||||||
|
savePrefs();
|
||||||
|
writeOKFrame();
|
||||||
|
} else if (cmd_frame[0] == CMD_GET_AUTOADD_CONFIG) {
|
||||||
|
int i = 0;
|
||||||
|
out_frame[i++] = RESP_CODE_AUTOADD_CONFIG;
|
||||||
|
out_frame[i++] = _prefs.autoadd_config;
|
||||||
|
_serial->writeFrame(out_frame, i);
|
||||||
} else {
|
} else {
|
||||||
writeErrFrame(ERR_CODE_UNSUPPORTED_CMD);
|
writeErrFrame(ERR_CODE_UNSUPPORTED_CMD);
|
||||||
MESH_DEBUG_PRINTLN("ERROR: unknown command: %02X", cmd_frame[0]);
|
MESH_DEBUG_PRINTLN("ERROR: unknown command: %02X", cmd_frame[0]);
|
||||||
|
|||||||
@@ -114,6 +114,10 @@ protected:
|
|||||||
|
|
||||||
void logRxRaw(float snr, float rssi, const uint8_t raw[], int len) override;
|
void logRxRaw(float snr, float rssi, const uint8_t raw[], int len) override;
|
||||||
bool isAutoAddEnabled() const override;
|
bool isAutoAddEnabled() const override;
|
||||||
|
bool shouldAutoAddContactType(uint8_t type) const override;
|
||||||
|
bool shouldOverwriteWhenFull() const override;
|
||||||
|
void onContactsFull() override;
|
||||||
|
void onContactOverwrite(const uint8_t* pub_key) override;
|
||||||
bool onContactPathRecv(ContactInfo& from, uint8_t* in_path, uint8_t in_path_len, uint8_t* out_path, uint8_t out_path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override;
|
bool onContactPathRecv(ContactInfo& from, uint8_t* in_path, uint8_t in_path_len, uint8_t* out_path, uint8_t out_path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override;
|
||||||
void onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path_len, const uint8_t* path) override;
|
void onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path_len, const uint8_t* path) override;
|
||||||
void onContactPathUpdated(const ContactInfo &contact) override;
|
void onContactPathUpdated(const ContactInfo &contact) override;
|
||||||
|
|||||||
@@ -27,4 +27,5 @@ struct NodePrefs { // persisted to file
|
|||||||
uint8_t buzzer_quiet;
|
uint8_t buzzer_quiet;
|
||||||
uint8_t gps_enabled; // GPS enabled flag (0=disabled, 1=enabled)
|
uint8_t gps_enabled; // GPS enabled flag (0=disabled, 1=enabled)
|
||||||
uint32_t gps_interval; // GPS read interval in seconds
|
uint32_t gps_interval; // GPS read interval in seconds
|
||||||
|
uint8_t autoadd_config; // bitmask for auto-add contacts config
|
||||||
};
|
};
|
||||||
@@ -55,6 +55,54 @@ void BaseChatMesh::sendAckTo(const ContactInfo& dest, uint32_t ack_hash) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BaseChatMesh::bootstrapRTCfromContacts() {
|
||||||
|
uint32_t latest = 0;
|
||||||
|
for (int i = 0; i < num_contacts; i++) {
|
||||||
|
if (contacts[i].lastmod > latest) {
|
||||||
|
latest = contacts[i].lastmod;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (latest != 0) {
|
||||||
|
getRTCClock()->setCurrentTime(latest + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ContactInfo* BaseChatMesh::allocateContactSlot() {
|
||||||
|
if (num_contacts < MAX_CONTACTS) {
|
||||||
|
return &contacts[num_contacts++];
|
||||||
|
} else if (shouldOverwriteWhenFull()) {
|
||||||
|
// Find oldest non-favourite contact by oldest lastmod timestamp
|
||||||
|
int oldest_idx = -1;
|
||||||
|
uint32_t oldest_lastmod = 0xFFFFFFFF;
|
||||||
|
for (int i = 0; i < num_contacts; i++) {
|
||||||
|
bool is_favourite = (contacts[i].flags & 0x01) != 0;
|
||||||
|
if (!is_favourite && contacts[i].lastmod < oldest_lastmod) {
|
||||||
|
oldest_lastmod = contacts[i].lastmod;
|
||||||
|
oldest_idx = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (oldest_idx >= 0) {
|
||||||
|
onContactOverwrite(contacts[oldest_idx].id.pub_key);
|
||||||
|
return &contacts[oldest_idx];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL; // no space, no overwrite or all contacts are all favourites
|
||||||
|
}
|
||||||
|
|
||||||
|
void BaseChatMesh::populateContactFromAdvert(ContactInfo& ci, const mesh::Identity& id, const AdvertDataParser& parser, uint32_t timestamp) {
|
||||||
|
memset(&ci, 0, sizeof(ci));
|
||||||
|
ci.id = id;
|
||||||
|
ci.out_path_len = -1; // initially out_path is unknown
|
||||||
|
StrHelper::strncpy(ci.name, parser.getName(), sizeof(ci.name));
|
||||||
|
ci.type = parser.getType();
|
||||||
|
if (parser.hasLatLon()) {
|
||||||
|
ci.gps_lat = parser.getIntLat();
|
||||||
|
ci.gps_lon = parser.getIntLon();
|
||||||
|
}
|
||||||
|
ci.last_advert_timestamp = timestamp;
|
||||||
|
ci.lastmod = getRTCClock()->getCurrentTime();
|
||||||
|
}
|
||||||
|
|
||||||
void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len) {
|
void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len) {
|
||||||
AdvertDataParser parser(app_data, app_data_len);
|
AdvertDataParser parser(app_data, app_data_len);
|
||||||
if (!(parser.isValid() && parser.hasName())) {
|
if (!(parser.isValid() && parser.hasName())) {
|
||||||
@@ -87,48 +135,37 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id,
|
|||||||
|
|
||||||
bool is_new = false;
|
bool is_new = false;
|
||||||
if (from == NULL) {
|
if (from == NULL) {
|
||||||
if (!isAutoAddEnabled()) {
|
if (!shouldAutoAddContactType(parser.getType())) {
|
||||||
ContactInfo ci;
|
ContactInfo ci;
|
||||||
memset(&ci, 0, sizeof(ci));
|
populateContactFromAdvert(ci, id, parser, timestamp);
|
||||||
ci.id = id;
|
|
||||||
ci.out_path_len = -1; // initially out_path is unknown
|
|
||||||
StrHelper::strncpy(ci.name, parser.getName(), sizeof(ci.name));
|
|
||||||
ci.type = parser.getType();
|
|
||||||
if (parser.hasLatLon()) {
|
|
||||||
ci.gps_lat = parser.getIntLat();
|
|
||||||
ci.gps_lon = parser.getIntLon();
|
|
||||||
}
|
|
||||||
ci.last_advert_timestamp = timestamp;
|
|
||||||
ci.lastmod = getRTCClock()->getCurrentTime();
|
|
||||||
onDiscoveredContact(ci, true, packet->path_len, packet->path); // let UI know
|
onDiscoveredContact(ci, true, packet->path_len, packet->path); // let UI know
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
is_new = true;
|
is_new = true;
|
||||||
if (num_contacts < MAX_CONTACTS) {
|
from = allocateContactSlot();
|
||||||
from = &contacts[num_contacts++];
|
if (from == NULL) {
|
||||||
from->id = id;
|
ContactInfo ci;
|
||||||
from->out_path_len = -1; // initially out_path is unknown
|
populateContactFromAdvert(ci, id, parser, timestamp);
|
||||||
from->gps_lat = 0; // initially unknown GPS loc
|
onDiscoveredContact(ci, true, packet->path_len, packet->path);
|
||||||
from->gps_lon = 0;
|
onContactsFull();
|
||||||
from->sync_since = 0;
|
MESH_DEBUG_PRINTLN("onAdvertRecv: unable to allocate contact slot for new contact");
|
||||||
|
|
||||||
from->shared_secret_valid = false; // ecdh shared_secret will be calculated later on demand
|
|
||||||
} else {
|
|
||||||
MESH_DEBUG_PRINTLN("onAdvertRecv: contacts table is full!");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
populateContactFromAdvert(*from, id, parser, timestamp);
|
||||||
|
from->sync_since = 0;
|
||||||
|
from->shared_secret_valid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// update
|
// update
|
||||||
StrHelper::strncpy(from->name, parser.getName(), sizeof(from->name));
|
StrHelper::strncpy(from->name, parser.getName(), sizeof(from->name));
|
||||||
from->type = parser.getType();
|
from->type = parser.getType();
|
||||||
if (parser.hasLatLon()) {
|
if (parser.hasLatLon()) {
|
||||||
from->gps_lat = parser.getIntLat();
|
from->gps_lat = parser.getIntLat();
|
||||||
from->gps_lon = parser.getIntLon();
|
from->gps_lon = parser.getIntLon();
|
||||||
}
|
}
|
||||||
from->last_advert_timestamp = timestamp;
|
from->last_advert_timestamp = timestamp;
|
||||||
from->lastmod = getRTCClock()->getCurrentTime();
|
from->lastmod = getRTCClock()->getCurrentTime();
|
||||||
|
|
||||||
onDiscoveredContact(*from, is_new, packet->path_len, packet->path); // let UI know
|
onDiscoveredContact(*from, is_new, packet->path_len, packet->path); // let UI know
|
||||||
}
|
}
|
||||||
@@ -722,10 +759,9 @@ ContactInfo* BaseChatMesh::lookupContactByPubKey(const uint8_t* pub_key, int pre
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool BaseChatMesh::addContact(const ContactInfo& contact) {
|
bool BaseChatMesh::addContact(const ContactInfo& contact) {
|
||||||
if (num_contacts < MAX_CONTACTS) {
|
ContactInfo* dest = allocateContactSlot();
|
||||||
auto dest = &contacts[num_contacts++];
|
if (dest) {
|
||||||
*dest = contact;
|
*dest = contact;
|
||||||
|
|
||||||
dest->shared_secret_valid = false; // mark shared_secret as needing calculation
|
dest->shared_secret_valid = false; // mark shared_secret as needing calculation
|
||||||
return true; // success
|
return true; // success
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,10 +88,17 @@ protected:
|
|||||||
memset(connections, 0, sizeof(connections));
|
memset(connections, 0, sizeof(connections));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void bootstrapRTCfromContacts();
|
||||||
void resetContacts() { num_contacts = 0; }
|
void resetContacts() { num_contacts = 0; }
|
||||||
|
void populateContactFromAdvert(ContactInfo& ci, const mesh::Identity& id, const AdvertDataParser& parser, uint32_t timestamp);
|
||||||
|
ContactInfo* allocateContactSlot(); // helper to find slot for new contact
|
||||||
|
|
||||||
// 'UI' concepts, for sub-classes to implement
|
// 'UI' concepts, for sub-classes to implement
|
||||||
virtual bool isAutoAddEnabled() const { return true; }
|
virtual bool isAutoAddEnabled() const { return true; }
|
||||||
|
virtual bool shouldAutoAddContactType(uint8_t type) const { return true; }
|
||||||
|
virtual void onContactsFull() {};
|
||||||
|
virtual bool shouldOverwriteWhenFull() const { return false; }
|
||||||
|
virtual void onContactOverwrite(const uint8_t* pub_key) {};
|
||||||
virtual void onDiscoveredContact(ContactInfo& contact, bool is_new, uint8_t path_len, const uint8_t* path) = 0;
|
virtual void onDiscoveredContact(ContactInfo& contact, bool is_new, uint8_t path_len, const uint8_t* path) = 0;
|
||||||
virtual ContactInfo* processAck(const uint8_t *data) = 0;
|
virtual ContactInfo* processAck(const uint8_t *data) = 0;
|
||||||
virtual void onContactPathUpdated(const ContactInfo& contact) = 0;
|
virtual void onContactPathUpdated(const ContactInfo& contact) = 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user