mirror of
https://github.com/pelgraine/Meck.git
synced 2026-05-08 22:34:49 +02:00
Merge branch 'ripplebiz:dev' into dev
This commit is contained in:
@@ -266,7 +266,7 @@ void UITask::buttonHandler() {
|
||||
digitalWrite(PIN_STATUS_LED, LOW);
|
||||
delay(10);
|
||||
#endif
|
||||
_board->powerOff();
|
||||
shutdown(); // without restart
|
||||
}
|
||||
}
|
||||
btn_state_change_time = millis();
|
||||
@@ -278,6 +278,30 @@ void UITask::buttonHandler() {
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/* hardware-agnostic pre-shutdown activity should be done here
|
||||
*/
|
||||
void UITask::shutdown(bool restart){
|
||||
|
||||
#ifdef PIN_BUZZER
|
||||
/* note: we have a choice here -
|
||||
we can do a blocking buzzer.loop() with non-deterministic consequences
|
||||
or we can set a flag and delay the shutdown for a couple of seconds
|
||||
while a non-blocking buzzer.loop() plays out in UITask::loop()
|
||||
*/
|
||||
buzzer.shutdown();
|
||||
uint32_t buzzer_timer = millis(); // fail-safe shutdown
|
||||
while (buzzer.isPlaying() && (millis() - 2500) < buzzer_timer)
|
||||
buzzer.loop();
|
||||
|
||||
#endif // PIN_BUZZER
|
||||
|
||||
if (restart)
|
||||
_board->reboot();
|
||||
else
|
||||
_board->powerOff();
|
||||
}
|
||||
|
||||
void UITask::loop() {
|
||||
buttonHandler();
|
||||
userLedHandler();
|
||||
|
||||
@@ -39,7 +39,6 @@ class UITask {
|
||||
void buttonHandler();
|
||||
void userLedHandler();
|
||||
void renderBatteryIndicator(uint16_t batteryMilliVolts);
|
||||
|
||||
|
||||
public:
|
||||
|
||||
@@ -55,5 +54,6 @@ public:
|
||||
void msgRead(int msgcount);
|
||||
void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount);
|
||||
void soundBuzzer(UIEventType bet = UIEventType::none);
|
||||
void shutdown(bool restart = false);
|
||||
void loop();
|
||||
};
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
#define FLOOD_SEND_TIMEOUT_FACTOR 16.0f
|
||||
#define DIRECT_SEND_PERHOP_FACTOR 6.0f
|
||||
#define DIRECT_SEND_PERHOP_EXTRA_MILLIS 250
|
||||
#define LAZY_CONTACTS_WRITE_DELAY 5000
|
||||
|
||||
#define PUBLIC_GROUP_PSK "izOH6cXN6mrJ5e26oRXNcg=="
|
||||
|
||||
@@ -198,6 +199,7 @@ class MyMesh : public BaseChatMesh {
|
||||
uint8_t app_target_ver;
|
||||
uint8_t* sign_data;
|
||||
uint32_t sign_data_len;
|
||||
unsigned long dirty_contacts_expiry;
|
||||
uint8_t cmd_frame[MAX_FRAME_SIZE+1];
|
||||
uint8_t out_frame[MAX_FRAME_SIZE+1];
|
||||
CayenneLPP telemetry;
|
||||
@@ -488,6 +490,10 @@ protected:
|
||||
return _prefs.airtime_factor;
|
||||
}
|
||||
|
||||
int getInterferenceThreshold() const override {
|
||||
return 14; // hard-coded for now
|
||||
}
|
||||
|
||||
int calcRxDelay(float score, uint32_t air_time) const override {
|
||||
if (_prefs.rx_delay_base <= 0.0f) return 0;
|
||||
return (int) ((pow(_prefs.rx_delay_base, 0.85f - score) - 1.0) * air_time);
|
||||
@@ -524,7 +530,7 @@ protected:
|
||||
#endif
|
||||
}
|
||||
|
||||
saveContacts();
|
||||
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY);
|
||||
}
|
||||
|
||||
void onContactPathUpdated(const ContactInfo& contact) override {
|
||||
@@ -532,7 +538,7 @@ protected:
|
||||
memcpy(&out_frame[1], contact.id.pub_key, PUB_KEY_SIZE);
|
||||
_serial->writeFrame(out_frame, 1 + PUB_KEY_SIZE); // NOTE: app may not be connected
|
||||
|
||||
saveContacts();
|
||||
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY);
|
||||
}
|
||||
|
||||
bool processAck(const uint8_t *data) override {
|
||||
@@ -603,7 +609,8 @@ protected:
|
||||
|
||||
void onSignedMessageRecv(const ContactInfo& from, mesh::Packet* pkt, uint32_t sender_timestamp, const uint8_t *sender_prefix, const char *text) override {
|
||||
markConnectionActive(from);
|
||||
saveContacts(); // from.sync_since change needs to be persisted
|
||||
// from.sync_since change needs to be persisted
|
||||
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY);
|
||||
queueMessage(from, TXT_TYPE_SIGNED_PLAIN, pkt, sender_timestamp, sender_prefix, 4, text);
|
||||
}
|
||||
|
||||
@@ -695,6 +702,7 @@ protected:
|
||||
if (memcmp(&data[4], "OK", 2) == 0) { // legacy Repeater login OK response
|
||||
out_frame[i++] = PUSH_CODE_LOGIN_SUCCESS;
|
||||
out_frame[i++] = 0; // legacy: is_admin = false
|
||||
memcpy(&out_frame[i], contact.id.pub_key, 6); i += 6; // pub_key_prefix
|
||||
} else if (data[4] == RESP_SERVER_LOGIN_OK) { // new login response
|
||||
uint16_t keep_alive_secs = ((uint16_t)data[5]) * 16;
|
||||
if (keep_alive_secs > 0) {
|
||||
@@ -702,11 +710,13 @@ protected:
|
||||
}
|
||||
out_frame[i++] = PUSH_CODE_LOGIN_SUCCESS;
|
||||
out_frame[i++] = data[6]; // permissions (eg. is_admin)
|
||||
memcpy(&out_frame[i], contact.id.pub_key, 6); i += 6; // pub_key_prefix
|
||||
memcpy(&out_frame[i], &tag, 4); i += 4; // NEW: include server timestamp
|
||||
} else {
|
||||
out_frame[i++] = PUSH_CODE_LOGIN_FAIL;
|
||||
out_frame[i++] = 0; // reserved
|
||||
memcpy(&out_frame[i], contact.id.pub_key, 6); i += 6; // pub_key_prefix
|
||||
}
|
||||
memcpy(&out_frame[i], contact.id.pub_key, 6); i += 6; // pub_key_prefix
|
||||
_serial->writeFrame(out_frame, i);
|
||||
} else if (len > 4 && // check for status response
|
||||
pending_status && memcmp(&pending_status, contact.id.pub_key, 4) == 0 // legacy matching scheme
|
||||
@@ -794,6 +804,7 @@ public:
|
||||
pending_login = pending_status = pending_telemetry = 0;
|
||||
next_ack_idx = 0;
|
||||
sign_data = NULL;
|
||||
dirty_contacts_expiry = 0;
|
||||
|
||||
// defaults
|
||||
memset(&_prefs, 0, sizeof(_prefs));
|
||||
@@ -1145,7 +1156,7 @@ public:
|
||||
if (recipient) {
|
||||
recipient->out_path_len = -1;
|
||||
//recipient->lastmod = ?? shouldn't be needed, app already has this version of contact
|
||||
saveContacts();
|
||||
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY);
|
||||
writeOKFrame();
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_NOT_FOUND); // unknown contact
|
||||
@@ -1156,7 +1167,7 @@ public:
|
||||
if (recipient) {
|
||||
updateContactFromFrame(*recipient, cmd_frame, len);
|
||||
//recipient->lastmod = ?? shouldn't be needed, app already has this version of contact
|
||||
saveContacts();
|
||||
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY);
|
||||
writeOKFrame();
|
||||
} else {
|
||||
ContactInfo contact;
|
||||
@@ -1164,7 +1175,7 @@ public:
|
||||
contact.lastmod = getRTCClock()->getCurrentTime();
|
||||
contact.sync_since = 0;
|
||||
if (addContact(contact)) {
|
||||
saveContacts();
|
||||
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY);
|
||||
writeOKFrame();
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_TABLE_FULL);
|
||||
@@ -1174,7 +1185,7 @@ public:
|
||||
uint8_t* pub_key = &cmd_frame[1];
|
||||
ContactInfo* recipient = lookupContactByPubKey(pub_key, PUB_KEY_SIZE);
|
||||
if (recipient && removeContact(*recipient)) {
|
||||
saveContacts();
|
||||
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY);
|
||||
writeOKFrame();
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_NOT_FOUND); // not found, or unable to remove
|
||||
@@ -1293,6 +1304,9 @@ public:
|
||||
savePrefs();
|
||||
writeOKFrame();
|
||||
} else if (cmd_frame[0] == CMD_REBOOT && memcmp(&cmd_frame[1], "reboot", 6) == 0) {
|
||||
if (dirty_contacts_expiry) { // is there are pending dirty contacts write needed?
|
||||
saveContacts();
|
||||
}
|
||||
board.reboot();
|
||||
} else if (cmd_frame[0] == CMD_GET_BATTERY_VOLTAGE) {
|
||||
uint8_t reply[3];
|
||||
@@ -1563,6 +1577,12 @@ public:
|
||||
checkConnections();
|
||||
}
|
||||
|
||||
// is there are pending dirty contacts write needed?
|
||||
if (dirty_contacts_expiry && millisHasNowPassed(dirty_contacts_expiry)) {
|
||||
saveContacts();
|
||||
dirty_contacts_expiry = 0;
|
||||
}
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
ui_task.setHasConnection(_serial->isConnected());
|
||||
ui_task.loop();
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
struct RepeaterStats {
|
||||
uint16_t batt_milli_volts;
|
||||
uint16_t curr_tx_queue_len;
|
||||
uint16_t curr_free_queue_len;
|
||||
int16_t noise_floor;
|
||||
int16_t last_rssi;
|
||||
uint32_t n_packets_recv;
|
||||
uint32_t n_packets_sent;
|
||||
@@ -183,7 +183,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
||||
RepeaterStats stats;
|
||||
stats.batt_milli_volts = board.getBattMilliVolts();
|
||||
stats.curr_tx_queue_len = _mgr->getOutboundCount(0xFFFFFFFF);
|
||||
stats.curr_free_queue_len = _mgr->getFreeCount();
|
||||
stats.noise_floor = (int16_t)_radio->getNoiseFloor();
|
||||
stats.last_rssi = (int16_t) radio_driver.getLastRSSI();
|
||||
stats.n_packets_recv = radio_driver.getPacketsRecv();
|
||||
stats.n_packets_sent = radio_driver.getPacketsSent();
|
||||
@@ -327,6 +327,9 @@ protected:
|
||||
uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.direct_tx_delay_factor);
|
||||
return getRNG()->nextInt(0, 6)*t;
|
||||
}
|
||||
int getInterferenceThreshold() const override {
|
||||
return _prefs.interference_threshold;
|
||||
}
|
||||
|
||||
void onAnonDataRecv(mesh::Packet* packet, uint8_t type, const mesh::Identity& sender, uint8_t* data, size_t len) override {
|
||||
if (type == PAYLOAD_TYPE_ANON_REQ) { // received an initial request by a possible admin client (unknown at this stage)
|
||||
@@ -565,6 +568,7 @@ public:
|
||||
_prefs.advert_interval = 1; // default to 2 minutes for NEW installs
|
||||
_prefs.flood_advert_interval = 3; // 3 hours
|
||||
_prefs.flood_max = 64;
|
||||
_prefs.interference_threshold = 14; // DB
|
||||
}
|
||||
|
||||
CommonCLI* getCLI() { return &_cli; }
|
||||
|
||||
@@ -126,7 +126,7 @@ struct PostInfo {
|
||||
struct ServerStats {
|
||||
uint16_t batt_milli_volts;
|
||||
uint16_t curr_tx_queue_len;
|
||||
uint16_t curr_free_queue_len;
|
||||
int16_t noise_floor;
|
||||
int16_t last_rssi;
|
||||
uint32_t n_packets_recv;
|
||||
uint32_t n_packets_sent;
|
||||
@@ -287,7 +287,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
||||
ServerStats stats;
|
||||
stats.batt_milli_volts = board.getBattMilliVolts();
|
||||
stats.curr_tx_queue_len = _mgr->getOutboundCount(0xFFFFFFFF);
|
||||
stats.curr_free_queue_len = _mgr->getFreeCount();
|
||||
stats.noise_floor = (int16_t)_radio->getNoiseFloor();
|
||||
stats.last_rssi = (int16_t) radio_driver.getLastRSSI();
|
||||
stats.n_packets_recv = radio_driver.getPacketsRecv();
|
||||
stats.n_packets_sent = radio_driver.getPacketsSent();
|
||||
@@ -406,6 +406,9 @@ protected:
|
||||
uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.direct_tx_delay_factor);
|
||||
return getRNG()->nextInt(0, 6)*t;
|
||||
}
|
||||
int getInterferenceThreshold() const override {
|
||||
return _prefs.interference_threshold;
|
||||
}
|
||||
|
||||
bool allowPacketForward(const mesh::Packet* packet) override {
|
||||
if (_prefs.disable_fwd) return false;
|
||||
@@ -711,6 +714,7 @@ public:
|
||||
_prefs.advert_interval = 1; // default to 2 minutes for NEW installs
|
||||
_prefs.flood_advert_interval = 3; // 3 hours
|
||||
_prefs.flood_max = 64;
|
||||
_prefs.interference_threshold = 14; // DB
|
||||
#ifdef ROOM_PASSWORD
|
||||
StrHelper::strncpy(_prefs.guest_password, ROOM_PASSWORD, sizeof(_prefs.guest_password));
|
||||
#endif
|
||||
|
||||
@@ -10,6 +10,10 @@ namespace mesh {
|
||||
|
||||
#define MAX_RX_DELAY_MILLIS 32000 // 32 seconds
|
||||
|
||||
#ifndef NOISE_FLOOR_CALIB_INTERVAL
|
||||
#define NOISE_FLOOR_CALIB_INTERVAL 2000 // 2 seconds
|
||||
#endif
|
||||
|
||||
void Dispatcher::begin() {
|
||||
n_sent_flood = n_sent_direct = 0;
|
||||
n_recv_flood = n_recv_direct = 0;
|
||||
@@ -36,6 +40,12 @@ uint32_t Dispatcher::getCADFailMaxDuration() const {
|
||||
}
|
||||
|
||||
void Dispatcher::loop() {
|
||||
if (millisHasNowPassed(next_floor_calib_time)) {
|
||||
_radio->triggerNoiseFloorCalibrate(getInterferenceThreshold());
|
||||
next_floor_calib_time = futureMillis(NOISE_FLOOR_CALIB_INTERVAL);
|
||||
}
|
||||
_radio->loop();
|
||||
|
||||
// check for radio 'stuck' in mode other than Rx
|
||||
bool is_recv = _radio->isInRecvMode();
|
||||
if (is_recv != prev_isrecv_mode) {
|
||||
|
||||
@@ -56,6 +56,15 @@ public:
|
||||
*/
|
||||
virtual void onSendFinished() = 0;
|
||||
|
||||
/**
|
||||
* \brief do any processing needed on each loop cycle
|
||||
*/
|
||||
virtual void loop() { }
|
||||
|
||||
virtual int getNoiseFloor() const { return 0; }
|
||||
|
||||
virtual void triggerNoiseFloorCalibrate(int threshold) { }
|
||||
|
||||
virtual bool isInRecvMode() const = 0;
|
||||
|
||||
/**
|
||||
@@ -107,6 +116,7 @@ class Dispatcher {
|
||||
unsigned long next_tx_time;
|
||||
unsigned long cad_busy_start;
|
||||
unsigned long radio_nonrx_start;
|
||||
unsigned long next_floor_calib_time;
|
||||
bool prev_isrecv_mode;
|
||||
uint32_t n_sent_flood, n_sent_direct;
|
||||
uint32_t n_recv_flood, n_recv_direct;
|
||||
@@ -124,6 +134,7 @@ protected:
|
||||
{
|
||||
outbound = NULL; total_air_time = 0; next_tx_time = 0;
|
||||
cad_busy_start = 0;
|
||||
next_floor_calib_time = 0;
|
||||
_err_flags = 0;
|
||||
radio_nonrx_start = 0;
|
||||
prev_isrecv_mode = true;
|
||||
@@ -142,6 +153,7 @@ protected:
|
||||
virtual int calcRxDelay(float score, uint32_t air_time) const;
|
||||
virtual uint32_t getCADFailRetryDelay() const;
|
||||
virtual uint32_t getCADFailMaxDuration() const;
|
||||
virtual int getInterferenceThreshold() const { return 0; } // disabled by default
|
||||
|
||||
public:
|
||||
void begin();
|
||||
|
||||
@@ -56,6 +56,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
file.read(pad, 4); // 120
|
||||
file.read((uint8_t *) &_prefs->flood_max, sizeof(_prefs->flood_max)); // 124
|
||||
file.read((uint8_t *) &_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); // 125
|
||||
file.read((uint8_t *) &_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); // 126
|
||||
|
||||
// sanitise bad pref values
|
||||
_prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f);
|
||||
@@ -109,6 +110,7 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
|
||||
file.write(pad, 4); // 120
|
||||
file.write((uint8_t *) &_prefs->flood_max, sizeof(_prefs->flood_max)); // 124
|
||||
file.write((uint8_t *) &_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); // 125
|
||||
file.write((uint8_t *) &_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); // 126
|
||||
|
||||
file.close();
|
||||
}
|
||||
@@ -176,6 +178,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
const char* config = &command[4];
|
||||
if (memcmp(config, "af", 2) == 0) {
|
||||
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->airtime_factor));
|
||||
} else if (memcmp(config, "int.thresh", 10) == 0) {
|
||||
sprintf(reply, "> %d", (uint32_t) _prefs->interference_threshold);
|
||||
} else if (memcmp(config, "allow.read.only", 15) == 0) {
|
||||
sprintf(reply, "> %s", _prefs->allow_read_only ? "on" : "off");
|
||||
} else if (memcmp(config, "flood.advert.interval", 21) == 0) {
|
||||
@@ -223,6 +227,10 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
_prefs->airtime_factor = atof(&config[3]);
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else if (memcmp(config, "int.thresh ", 11) == 0) {
|
||||
_prefs->interference_threshold = atoi(&config[11]);
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else if (memcmp(config, "allow.read.only ", 16) == 0) {
|
||||
_prefs->allow_read_only = memcmp(&config[16], "on", 2) == 0;
|
||||
savePrefs();
|
||||
|
||||
@@ -24,6 +24,7 @@ struct NodePrefs { // persisted to file
|
||||
uint8_t reserved2;
|
||||
float bw;
|
||||
uint8_t flood_max;
|
||||
uint8_t interference_threshold;
|
||||
};
|
||||
|
||||
class CommonCLICallbacks {
|
||||
|
||||
@@ -6,18 +6,11 @@
|
||||
class CustomLLCC68Wrapper : public RadioLibWrapper {
|
||||
public:
|
||||
CustomLLCC68Wrapper(CustomLLCC68& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { }
|
||||
bool isReceiving() override {
|
||||
if (((CustomLLCC68 *)_radio)->isReceiving()) return true;
|
||||
|
||||
idle(); // put sx126x into standby
|
||||
// do some basic CAD (blocks for ~12780 micros (on SF 10)!)
|
||||
bool activity = (((CustomLLCC68 *)_radio)->scanChannel() == RADIOLIB_LORA_DETECTED);
|
||||
if (activity) {
|
||||
startRecv();
|
||||
} else {
|
||||
idle();
|
||||
}
|
||||
return activity;
|
||||
bool isReceivingPacket() override {
|
||||
return ((CustomLLCC68 *)_radio)->isReceiving();
|
||||
}
|
||||
float getCurrentRSSI() override {
|
||||
return ((CustomLLCC68 *)_radio)->getRSSI(false);
|
||||
}
|
||||
float getLastRSSI() const override { return ((CustomLLCC68 *)_radio)->getRSSI(); }
|
||||
float getLastSNR() const override { return ((CustomLLCC68 *)_radio)->getSNR(); }
|
||||
|
||||
@@ -6,18 +6,13 @@
|
||||
class CustomLR1110Wrapper : public RadioLibWrapper {
|
||||
public:
|
||||
CustomLR1110Wrapper(CustomLR1110& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { }
|
||||
bool isReceiving() override {
|
||||
if (((CustomLR1110 *)_radio)->isReceiving()) return true;
|
||||
|
||||
idle(); // put sx126x into standby
|
||||
// do some basic CAD (blocks for ~12780 micros (on SF 10)!)
|
||||
bool activity = (((CustomLR1110 *)_radio)->scanChannel() == RADIOLIB_LORA_DETECTED);
|
||||
if (activity) {
|
||||
startRecv();
|
||||
} else {
|
||||
idle();
|
||||
}
|
||||
return activity;
|
||||
bool isReceivingPacket() override {
|
||||
return ((CustomLR1110 *)_radio)->isReceiving();
|
||||
}
|
||||
float getCurrentRSSI() override {
|
||||
float rssi = -110;
|
||||
((CustomLR1110 *)_radio)->getRssiInst(&rssi);
|
||||
return rssi;
|
||||
}
|
||||
|
||||
void onSendFinished() override {
|
||||
|
||||
@@ -7,18 +7,11 @@
|
||||
class CustomSTM32WLxWrapper : public RadioLibWrapper {
|
||||
public:
|
||||
CustomSTM32WLxWrapper(CustomSTM32WLx& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { }
|
||||
bool isReceiving() override {
|
||||
if (((CustomSTM32WLx *)_radio)->isReceiving()) return true;
|
||||
|
||||
idle(); // put sx126x into standby
|
||||
// do some basic CAD (blocks for ~12780 micros (on SF 10)!)
|
||||
bool activity = (((CustomSTM32WLx *)_radio)->scanChannel() == RADIOLIB_LORA_DETECTED);
|
||||
if (activity) {
|
||||
startRecv();
|
||||
} else {
|
||||
idle();
|
||||
}
|
||||
return activity;
|
||||
bool isReceivingPacket() override {
|
||||
return ((CustomSTM32WLx *)_radio)->isReceiving();
|
||||
}
|
||||
float getCurrentRSSI() override {
|
||||
return ((CustomSTM32WLx *)_radio)->getRSSI(false);
|
||||
}
|
||||
float getLastRSSI() const override { return ((CustomSTM32WLx *)_radio)->getRSSI(); }
|
||||
float getLastSNR() const override { return ((CustomSTM32WLx *)_radio)->getSNR(); }
|
||||
|
||||
@@ -2,23 +2,15 @@
|
||||
|
||||
#include "CustomSX1262.h"
|
||||
#include "RadioLibWrappers.h"
|
||||
#include <math.h>
|
||||
|
||||
class CustomSX1262Wrapper : public RadioLibWrapper {
|
||||
public:
|
||||
CustomSX1262Wrapper(CustomSX1262& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { }
|
||||
bool isReceiving() override {
|
||||
if (((CustomSX1262 *)_radio)->isReceiving()) return true;
|
||||
|
||||
idle(); // put sx126x into standby
|
||||
// do some basic CAD (blocks for ~12780 micros (on SF 10)!)
|
||||
bool activity = (((CustomSX1262 *)_radio)->scanChannel() == RADIOLIB_LORA_DETECTED);
|
||||
if (activity) {
|
||||
startRecv();
|
||||
} else {
|
||||
idle();
|
||||
}
|
||||
return activity;
|
||||
bool isReceivingPacket() override {
|
||||
return ((CustomSX1262 *)_radio)->isReceiving();
|
||||
}
|
||||
float getCurrentRSSI() override {
|
||||
return ((CustomSX1262 *)_radio)->getRSSI(false);
|
||||
}
|
||||
float getLastRSSI() const override { return ((CustomSX1262 *)_radio)->getRSSI(); }
|
||||
float getLastSNR() const override { return ((CustomSX1262 *)_radio)->getSNR(); }
|
||||
|
||||
@@ -6,18 +6,11 @@
|
||||
class CustomSX1268Wrapper : public RadioLibWrapper {
|
||||
public:
|
||||
CustomSX1268Wrapper(CustomSX1268& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { }
|
||||
bool isReceiving() override {
|
||||
if (((CustomSX1268 *)_radio)->isReceiving()) return true;
|
||||
|
||||
idle(); // put sx126x into standby
|
||||
// do some basic CAD (blocks for ~12780 micros (on SF 10)!)
|
||||
bool activity = (((CustomSX1268 *)_radio)->scanChannel() == RADIOLIB_LORA_DETECTED);
|
||||
if (activity) {
|
||||
startRecv();
|
||||
} else {
|
||||
idle();
|
||||
}
|
||||
return activity;
|
||||
bool isReceivingPacket() override {
|
||||
return ((CustomSX1268 *)_radio)->isReceiving();
|
||||
}
|
||||
float getCurrentRSSI() override {
|
||||
return ((CustomSX1268 *)_radio)->getRSSI(false);
|
||||
}
|
||||
float getLastRSSI() const override { return ((CustomSX1268 *)_radio)->getRSSI(); }
|
||||
float getLastSNR() const override { return ((CustomSX1268 *)_radio)->getSNR(); }
|
||||
|
||||
@@ -6,18 +6,11 @@
|
||||
class CustomSX1276Wrapper : public RadioLibWrapper {
|
||||
public:
|
||||
CustomSX1276Wrapper(CustomSX1276& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { }
|
||||
bool isReceiving() override {
|
||||
if (((CustomSX1276 *)_radio)->isReceiving()) return true;
|
||||
|
||||
idle(); // put into standby
|
||||
// do some basic CAD (blocks for ~12780 micros (on SF 10)!)
|
||||
bool activity = (((CustomSX1276 *)_radio)->tryScanChannel() == RADIOLIB_PREAMBLE_DETECTED);
|
||||
if (activity) {
|
||||
startRecv();
|
||||
} else {
|
||||
idle();
|
||||
}
|
||||
return activity;
|
||||
bool isReceivingPacket() override {
|
||||
return ((CustomSX1276 *)_radio)->isReceiving();
|
||||
}
|
||||
float getCurrentRSSI() override {
|
||||
return ((CustomSX1276 *)_radio)->getRSSI(false);
|
||||
}
|
||||
float getLastRSSI() const override { return ((CustomSX1276 *)_radio)->getRSSI(); }
|
||||
float getLastSNR() const override { return ((CustomSX1276 *)_radio)->getSNR(); }
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#define STATE_TX_DONE 4
|
||||
#define STATE_INT_READY 16
|
||||
|
||||
#define NUM_NOISE_FLOOR_SAMPLES 64
|
||||
|
||||
static volatile uint8_t state = STATE_IDLE;
|
||||
|
||||
// this function is called when a complete packet
|
||||
@@ -28,6 +30,13 @@ void RadioLibWrapper::begin() {
|
||||
if (_board->getStartupReason() == BD_STARTUP_RX_PACKET) { // received a LoRa packet (while in deep sleep)
|
||||
setFlag(); // LoRa packet is already received
|
||||
}
|
||||
|
||||
_noise_floor = 0;
|
||||
_threshold = 0;
|
||||
|
||||
// start average out some samples
|
||||
_num_floor_samples = 0;
|
||||
_floor_sample_sum = 0;
|
||||
}
|
||||
|
||||
void RadioLibWrapper::idle() {
|
||||
@@ -35,6 +44,31 @@ void RadioLibWrapper::idle() {
|
||||
state = STATE_IDLE; // need another startReceive()
|
||||
}
|
||||
|
||||
void RadioLibWrapper::triggerNoiseFloorCalibrate(int threshold) {
|
||||
_threshold = threshold;
|
||||
if (threshold > 0 && _num_floor_samples >= NUM_NOISE_FLOOR_SAMPLES) { // ignore trigger if currently sampling
|
||||
_num_floor_samples = 0;
|
||||
_floor_sample_sum = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void RadioLibWrapper::loop() {
|
||||
if (state == STATE_RX && _num_floor_samples < NUM_NOISE_FLOOR_SAMPLES) {
|
||||
if (!isReceivingPacket()) {
|
||||
int rssi = getCurrentRSSI();
|
||||
if (rssi < _noise_floor + _threshold) { // only consider samples below current floor+THRESHOLD
|
||||
_num_floor_samples++;
|
||||
_floor_sample_sum += rssi;
|
||||
}
|
||||
}
|
||||
} else if (_num_floor_samples >= NUM_NOISE_FLOOR_SAMPLES && _floor_sample_sum != 0) {
|
||||
_noise_floor = _floor_sample_sum / NUM_NOISE_FLOOR_SAMPLES;
|
||||
_floor_sample_sum = 0;
|
||||
|
||||
MESH_DEBUG_PRINTLN("RadioLibWrapper: noise_floor = %d", (int)_noise_floor);
|
||||
}
|
||||
}
|
||||
|
||||
void RadioLibWrapper::startRecv() {
|
||||
int err = _radio->startReceive();
|
||||
if (err == RADIOLIB_ERR_NONE) {
|
||||
@@ -108,6 +142,12 @@ void RadioLibWrapper::onSendFinished() {
|
||||
state = STATE_IDLE;
|
||||
}
|
||||
|
||||
bool RadioLibWrapper::isChannelActive() {
|
||||
return _threshold == 0
|
||||
? false // interference check is disabled
|
||||
: getCurrentRSSI() > _noise_floor + _threshold;
|
||||
}
|
||||
|
||||
float RadioLibWrapper::getLastRSSI() const {
|
||||
return _radio->getRSSI();
|
||||
}
|
||||
|
||||
@@ -8,10 +8,14 @@ protected:
|
||||
PhysicalLayer* _radio;
|
||||
mesh::MainBoard* _board;
|
||||
uint32_t n_recv, n_sent;
|
||||
int16_t _noise_floor, _threshold;
|
||||
uint16_t _num_floor_samples;
|
||||
int32_t _floor_sample_sum;
|
||||
|
||||
void idle();
|
||||
void startRecv();
|
||||
float packetScoreInt(float snr, int sf, int packet_len);
|
||||
virtual bool isReceivingPacket() =0;
|
||||
|
||||
public:
|
||||
RadioLibWrapper(PhysicalLayer& radio, mesh::MainBoard& board) : _radio(&radio), _board(&board) { n_recv = n_sent = 0; }
|
||||
@@ -23,6 +27,20 @@ public:
|
||||
bool isSendComplete() override;
|
||||
void onSendFinished() override;
|
||||
bool isInRecvMode() const override;
|
||||
bool isChannelActive();
|
||||
|
||||
bool isReceiving() override {
|
||||
if (isReceivingPacket()) return true;
|
||||
|
||||
return isChannelActive();
|
||||
}
|
||||
|
||||
virtual float getCurrentRSSI() =0;
|
||||
|
||||
int getNoiseFloor() const override { return _noise_floor; }
|
||||
void triggerNoiseFloorCalibrate(int threshold) override;
|
||||
|
||||
void loop() override;
|
||||
|
||||
uint32_t getPacketsRecv() const { return n_recv; }
|
||||
uint32_t getPacketsSent() const { return n_sent; }
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
|
||||
// Defined using AXP2102
|
||||
#define XPOWERS_CHIP_AXP2101
|
||||
#define PIN_BOARD_SDA1 42 //SDA for PMU and PFC8563 (RTC)
|
||||
#define PIN_BOARD_SCL1 41 //SCL for PMU and PFC8563 (RTC)
|
||||
#define PIN_PMU_IRQ 40 //IRQ pin for PMU
|
||||
|
||||
// LoRa radio module pins for TBeam
|
||||
#define P_LORA_DIO_0 26
|
||||
@@ -28,15 +31,13 @@
|
||||
#include <driver/rtc_io.h>
|
||||
|
||||
class TBeamBoard : public ESP32Board {
|
||||
XPowersAXP2101 power;
|
||||
|
||||
XPowersLibInterface *PMU = NULL;
|
||||
public:
|
||||
bool power_init();
|
||||
void printPMU();
|
||||
|
||||
void begin() {
|
||||
ESP32Board::begin();
|
||||
|
||||
power.setALDO2Voltage(3300);
|
||||
power.enableALDO2();
|
||||
|
||||
pinMode(38, INPUT_PULLUP);
|
||||
|
||||
esp_reset_reason_t reason = esp_reset_reason();
|
||||
@@ -49,6 +50,7 @@ public:
|
||||
rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS);
|
||||
rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1);
|
||||
}
|
||||
power_init();
|
||||
}
|
||||
|
||||
void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) {
|
||||
@@ -75,7 +77,8 @@ public:
|
||||
}
|
||||
|
||||
uint16_t getBattMilliVolts() override {
|
||||
return power.getBattVoltage();
|
||||
if(PMU) return PMU->getBattVoltage();
|
||||
else return 0;
|
||||
}
|
||||
|
||||
const char* getManufacturerName() const override {
|
||||
|
||||
@@ -75,6 +75,7 @@ public:
|
||||
rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS);
|
||||
rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1);
|
||||
}
|
||||
power_init();
|
||||
}
|
||||
|
||||
void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) {
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
|
||||
TBeamBoard board;
|
||||
|
||||
// Using PMU AXP2102
|
||||
#define PMU_WIRE_PORT Wire
|
||||
|
||||
bool pmuIntFlag = false;
|
||||
static void setPMUIntFlag(){
|
||||
pmuIntFlag = true;
|
||||
}
|
||||
|
||||
#if defined(P_LORA_SCLK)
|
||||
static SPIClass spi;
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_0, P_LORA_RESET, P_LORA_DIO_1, spi);
|
||||
@@ -24,6 +32,112 @@ SensorManager sensors;
|
||||
#define LORA_CR 5
|
||||
#endif
|
||||
|
||||
bool TBeamBoard::power_init()
|
||||
{
|
||||
if (!PMU)
|
||||
{
|
||||
PMU = new XPowersAXP2101(PMU_WIRE_PORT);
|
||||
if (!PMU->init())
|
||||
{
|
||||
// Serial.println("Warning: Failed to find AXP2101 power management");
|
||||
delete PMU;
|
||||
PMU = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Serial.println("AXP2101 PMU init succeeded, using AXP2101 PMU");
|
||||
}
|
||||
}
|
||||
if (!PMU)
|
||||
{
|
||||
PMU = new XPowersAXP192(PMU_WIRE_PORT);
|
||||
if (!PMU->init())
|
||||
{
|
||||
// Serial.println("Warning: Failed to find AXP192 power management");
|
||||
delete PMU;
|
||||
PMU = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Serial.println("AXP192 PMU init succeeded, using AXP192 PMU");
|
||||
}
|
||||
}
|
||||
|
||||
if (!PMU)
|
||||
{
|
||||
MESH_DEBUG_PRINTLN("PMU init failed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Serial.printf("PMU ID:0x%x\n", PMU->getChipID());
|
||||
// printPMU();
|
||||
if (PMU->getChipModel() == XPOWERS_AXP192)
|
||||
{
|
||||
// lora radio power channel
|
||||
PMU->setPowerChannelVoltage(XPOWERS_LDO2, 3300);
|
||||
PMU->enablePowerOutput(XPOWERS_LDO2);
|
||||
// oled module power channel,
|
||||
// disable it will cause abnormal communication between boot and AXP power supply,
|
||||
// do not turn it off
|
||||
PMU->setPowerChannelVoltage(XPOWERS_DCDC1, 3300);
|
||||
// enable oled power
|
||||
PMU->enablePowerOutput(XPOWERS_DCDC1);
|
||||
// gnss module power channel
|
||||
PMU->setPowerChannelVoltage(XPOWERS_LDO3, 3300);
|
||||
// power->enablePowerOutput(XPOWERS_LDO3);
|
||||
// protected oled power source
|
||||
PMU->setProtectedChannel(XPOWERS_DCDC1);
|
||||
// protected esp32 power source
|
||||
PMU->setProtectedChannel(XPOWERS_DCDC3);
|
||||
// disable not use channel
|
||||
PMU->disablePowerOutput(XPOWERS_DCDC2);
|
||||
// disable all axp chip interrupt
|
||||
PMU->disableIRQ(XPOWERS_AXP192_ALL_IRQ);
|
||||
PMU->setChargerConstantCurr(XPOWERS_AXP192_CHG_CUR_550MA);
|
||||
}
|
||||
else if (PMU->getChipModel() == XPOWERS_AXP2101)
|
||||
{
|
||||
// gnss module power channel
|
||||
PMU->setPowerChannelVoltage(XPOWERS_ALDO4, 3300);
|
||||
PMU->enablePowerOutput(XPOWERS_ALDO4);
|
||||
// lora radio power channel
|
||||
PMU->setPowerChannelVoltage(XPOWERS_ALDO3, 3300);
|
||||
PMU->enablePowerOutput(XPOWERS_ALDO3);
|
||||
// m.2 interface
|
||||
PMU->setPowerChannelVoltage(XPOWERS_DCDC3, 3300);
|
||||
PMU->enablePowerOutput(XPOWERS_DCDC3);
|
||||
// power->setPowerChannelVoltage(XPOWERS_DCDC4, 3300);
|
||||
// power->enablePowerOutput(XPOWERS_DCDC4);
|
||||
// not use channel
|
||||
PMU->disablePowerOutput(XPOWERS_DCDC2); // not elicited
|
||||
PMU->disablePowerOutput(XPOWERS_DCDC5); // not elicited
|
||||
PMU->disablePowerOutput(XPOWERS_DLDO1); // Invalid power channel, it does not exist
|
||||
PMU->disablePowerOutput(XPOWERS_DLDO2); // Invalid power channel, it does not exist
|
||||
PMU->disablePowerOutput(XPOWERS_VBACKUP);
|
||||
// disable all axp chip interrupt
|
||||
PMU->disableIRQ(XPOWERS_AXP2101_ALL_IRQ);
|
||||
PMU->setChargerConstantCurr(XPOWERS_AXP2101_CHG_CUR_500MA);
|
||||
|
||||
// Set up PMU interrupts
|
||||
// Serial.println("Setting up PMU interrupts");
|
||||
pinMode(PIN_PMU_IRQ, INPUT_PULLUP);
|
||||
attachInterrupt(PIN_PMU_IRQ, setPMUIntFlag, FALLING);
|
||||
|
||||
// Reset and re-enable PMU interrupts
|
||||
// Serial.println("Re-enable interrupts");
|
||||
PMU->disableIRQ(XPOWERS_AXP2101_ALL_IRQ);
|
||||
PMU->clearIrqStatus();
|
||||
PMU->enableIRQ(
|
||||
XPOWERS_AXP2101_BAT_INSERT_IRQ | XPOWERS_AXP2101_BAT_REMOVE_IRQ | // Battery interrupts
|
||||
XPOWERS_AXP2101_VBUS_INSERT_IRQ | XPOWERS_AXP2101_VBUS_REMOVE_IRQ | // VBUS interrupts
|
||||
XPOWERS_AXP2101_PKEY_SHORT_IRQ | XPOWERS_AXP2101_PKEY_LONG_IRQ | // Power Key interrupts
|
||||
XPOWERS_AXP2101_BAT_CHG_DONE_IRQ | XPOWERS_AXP2101_BAT_CHG_START_IRQ // Charging interrupts
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool radio_init() {
|
||||
fallback_clock.begin();
|
||||
rtc_clock.begin(Wire);
|
||||
@@ -66,3 +180,24 @@ mesh::LocalIdentity radio_new_identity() {
|
||||
RadioNoiseListener rng(radio);
|
||||
return mesh::LocalIdentity(&rng); // create new random identity
|
||||
}
|
||||
|
||||
#ifdef MESH_DEBUG
|
||||
void TBeamBoard::printPMU()
|
||||
{
|
||||
Serial.print("isCharging:"); Serial.println(PMU->isCharging() ? "YES" : "NO");
|
||||
Serial.print("isDischarge:"); Serial.println(PMU->isDischarge() ? "YES" : "NO");
|
||||
Serial.print("isVbusIn:"); Serial.println(PMU->isVbusIn() ? "YES" : "NO");
|
||||
Serial.print("getBattVoltage:"); Serial.print(PMU->getBattVoltage()); Serial.println("mV");
|
||||
Serial.print("getVbusVoltage:"); Serial.print(PMU->getVbusVoltage()); Serial.println("mV");
|
||||
Serial.print("getSystemVoltage:"); Serial.print(PMU->getSystemVoltage()); Serial.println("mV");
|
||||
|
||||
// The battery percentage may be inaccurate at first use, the PMU will automatically
|
||||
// learn the battery curve and will automatically calibrate the battery percentage
|
||||
// after a charge and discharge cycle
|
||||
if (PMU->isBatteryConnect()) {
|
||||
Serial.print("getBatteryPercent:"); Serial.print(PMU->getBatteryPercent()); Serial.println("%");
|
||||
}
|
||||
|
||||
Serial.println();
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user