Merge upstream companion-v1.17.0 (CAD) into power-saving

Adopts hardware Channel Activity Detection (wired into
RadioLibWrapper::isChannelActive() alongside our RSSI-threshold check
and RX duty-cycle power-save), MCU temperature telemetry, LR2021
standby workaround, DISPLAY_SCALE/FLIP overrides, NRF52Board
shutdownPeripherals() refactor, and misc upstream fixes.

Declines upstream's ConfigSerializer-based NodePrefs rewrite,
MultiSerialInterface/interface_manager, and UIColor palette system —
each would have broken large parts of the Solo-specific feature set
(NodePrefs fields, per-variant single serial_interface, enum-based
DisplayDriver::Color). Flagged as candidate follow-up migrations, not
permanent no's.

Also fixes several pre-existing bugs surfaced while chasing silent
merge breaks (stale newMsg() override signature in ui-tiny/ui-orig,
dead UIEventType::newContactMessage case, missing ContactsIterator
init), bumps FIRMWARE_VERSION/MESHCORE_VERSION to 1.17, and fixes a
missing <cstdlib> include that broke the native ConfigSerializer unit
tests.

Verified via 13+ pio run builds across ESP32/nRF52, all 3 companion UI
variants, and 7 display drivers, plus the full native unit test suite
(33/33 passing).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-08-11 17:25:35 +02:00
co-authored by Claude Sonnet 5
325 changed files with 9966 additions and 1119 deletions
+1
View File
@@ -68,6 +68,7 @@ uint32_t Dispatcher::getCADFailMaxDuration() const {
void Dispatcher::loop() {
if (millisHasNowPassed(next_floor_calib_time)) {
_radio->triggerNoiseFloorCalibrate(getInterferenceThreshold());
_radio->setCADEnabled(getCADEnabled());
next_floor_calib_time = futureMillis(NOISE_FLOOR_CALIB_INTERVAL);
}
_radio->loop();
+3
View File
@@ -65,6 +65,8 @@ public:
virtual void triggerNoiseFloorCalibrate(int threshold) { }
virtual void setCADEnabled(bool enable) { }
virtual void resetAGC() { }
virtual bool isInRecvMode() const = 0;
@@ -167,6 +169,7 @@ protected:
virtual uint32_t getCADFailRetryDelay() const;
virtual uint32_t getCADFailMaxDuration() const;
virtual int getInterferenceThreshold() const { return 0; } // disabled by default
virtual bool getCADEnabled() const { return false; } // hardware CAD disabled by default
virtual int getAGCResetInterval() const { return 0; } // disabled by default
virtual unsigned long getDutyCycleWindowMs() const { return 3600000; }
+19 -1
View File
@@ -4,6 +4,11 @@
#include <ed_25519.h>
#include <Ed25519.h>
#ifdef USE_CC310_HW_CRYPTO
#include <Adafruit_nRFCrypto.h>
#include "nrf_cc310/include/crys_ec_edw_api.h"
#endif
namespace mesh {
Identity::Identity() {
@@ -15,7 +20,20 @@ Identity::Identity(const char* pub_hex) {
}
bool Identity::verify(const uint8_t* sig, const uint8_t* message, int msg_len) const {
#if 0
#ifdef USE_CC310_HW_CRYPTO
// nRF52840 CryptoCell CC310 hardware Ed25519 verification. The software
// implementations need ~3KB of stack (which can overflow the Adafruit core's
// 4KB loop task stack from the advert receive path); the hardware path
// needs much less, around 600-700bytes. The CC310 workspace is static, faster,
// should save power at scale as well.
static CRYS_ECEDW_TempBuff_t cc310_tmp;
nRFCrypto.begin();
CRYSError_t rc = CRYS_ECEDW_Verify((uint8_t*)sig, CRYS_ECEDW_SIGNATURE_BYTES,
(uint8_t*)pub_key, CRYS_ECEDW_MOD_SIZE_IN_BYTES,
(uint8_t*)message, (size_t)msg_len, &cc310_tmp);
nRFCrypto.end();
return rc == CRYS_OK;
#elif 0
// NOTE: memory corruption bug was found in this function!!
return ed25519_verify(sig, message, msg_len, pub_key);
#else
+31 -16
View File
@@ -56,7 +56,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
uint16_t offset = (uint16_t)pkt->path_len << path_sz;
if (offset >= len) { // TRACE has reached end of given path
onTraceRecv(pkt, trace_tag, auth_code, flags, pkt->path, &pkt->payload[i], len);
} else if (self_id.isHashMatch(&pkt->payload[i + offset], 1 << path_sz) && allowPacketForward(pkt) && !_tables->hasSeen(pkt)) {
} else if (self_id.isHashMatch(&pkt->payload[i + offset], 1 << path_sz) && allowPacketForward(pkt) && !_tables->wasSeen(pkt)) {
_tables->markSeen(pkt);
// append SNR (Not hash!)
pkt->path[pkt->path_len++] = (int8_t) (pkt->getSNR()*4);
@@ -91,14 +92,16 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
if (pkt->getPayloadType() == PAYLOAD_TYPE_MULTIPART) {
return forwardMultipartDirect(pkt);
} else if (pkt->getPayloadType() == PAYLOAD_TYPE_ACK) {
if (!_tables->hasSeen(pkt)) { // don't retransmit!
if (!_tables->wasSeen(pkt)) { // don't retransmit!
_tables->markSeen(pkt);
removeSelfFromPath(pkt);
routeDirectRecvAcks(pkt, 0);
}
return ACTION_RELEASE;
}
if (!_tables->hasSeen(pkt)) {
if (!_tables->wasSeen(pkt)) {
_tables->markSeen(pkt);
removeSelfFromPath(pkt);
uint32_t d = getDirectRetransmitDelay(pkt);
@@ -120,7 +123,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
memcpy(&ack_crc, &pkt->payload[i], 4); i += 4;
if (i > pkt->payload_len) {
MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete ACK packet", getLogDateTime());
} else if (!_tables->hasSeen(pkt)) {
} else if (!_tables->wasSeen(pkt)) {
_tables->markSeen(pkt);
onAckRecv(pkt, ack_crc);
action = routeRecvPacket(pkt);
}
@@ -137,7 +141,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
uint8_t* macAndData = &pkt->payload[i]; // MAC + encrypted data
if (i + CIPHER_MAC_SIZE >= pkt->payload_len) {
MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete data packet", getLogDateTime());
} else if (!_tables->hasSeen(pkt)) {
} else if (!_tables->wasSeen(pkt)) {
_tables->markSeen(pkt);
// NOTE: this is a 'first packet wins' impl. When receiving from multiple paths, the first to arrive wins.
// For flood mode, the path may not be the 'best' in terms of hops.
// FUTURE: could send back multiple paths, using createPathReturn(), and let sender choose which to use(?)
@@ -158,6 +163,10 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
if (pkt->getPayloadType() == PAYLOAD_TYPE_PATH) {
int k = 0;
uint8_t path_len = data[k++];
if (!Packet::isValidPathLen(path_len)) {
MESH_DEBUG_PRINTLN("%s PAYLOAD_TYPE_PATH, bad path_len: %u", getLogDateTime(), (uint32_t)path_len);
break; // reject bad encoding
}
uint8_t hash_size = (path_len >> 6) + 1;
uint8_t hash_count = path_len & 63;
uint8_t* path = &data[k]; k += hash_size*hash_count;
@@ -196,7 +205,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
uint8_t* macAndData = &pkt->payload[i]; // MAC + encrypted data
if (i + 2 >= pkt->payload_len) {
MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete data packet", getLogDateTime());
} else if (!_tables->hasSeen(pkt)) {
} else if (!_tables->wasSeen(pkt)) {
_tables->markSeen(pkt);
if (self_id.isHashMatch(&dest_hash)) {
Identity sender(sender_pub_key);
@@ -223,7 +233,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
uint8_t* macAndData = &pkt->payload[i]; // MAC + encrypted data
if (i + 2 >= pkt->payload_len) {
MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete data packet", getLogDateTime());
} else if (!_tables->hasSeen(pkt)) {
} else if (!_tables->wasSeen(pkt)) {
_tables->markSeen(pkt);
// scan channels DB, for all matching hashes of 'channel_hash' (max 4 matches supported ATM)
GroupChannel channels[4];
int num = searchChannelsByHash(&channel_hash, channels, 4);
@@ -254,7 +265,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete advertisement packet", getLogDateTime());
} else if (self_id.matches(id.pub_key)) {
MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): receiving SELF advert packet", getLogDateTime());
} else if (!_tables->hasSeen(pkt)) {
} else if (!_tables->wasSeen(pkt)) {
_tables->markSeen(pkt);
uint8_t* app_data = &pkt->payload[i];
int app_data_len = pkt->payload_len - i;
if (app_data_len > MAX_ADVERT_DATA_SIZE) { app_data_len = MAX_ADVERT_DATA_SIZE; }
@@ -281,7 +293,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
break;
}
case PAYLOAD_TYPE_RAW_CUSTOM: {
if (pkt->isRouteDirect() && !_tables->hasSeen(pkt)) {
if (pkt->isRouteDirect() && !_tables->wasSeen(pkt)) {
_tables->markSeen(pkt);
onRawDataRecv(pkt);
//action = routeRecvPacket(pkt); don't flood route these (yet)
}
@@ -299,7 +312,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
tmp.payload_len = pkt->payload_len - 1;
memcpy(tmp.payload, &pkt->payload[1], tmp.payload_len);
if (!_tables->hasSeen(&tmp)) {
if (!_tables->wasSeen(&tmp)) {
_tables->markSeen(&tmp);
uint32_t ack_crc;
memcpy(&ack_crc, tmp.payload, 4);
@@ -357,7 +371,8 @@ DispatcherAction Mesh::forwardMultipartDirect(Packet* pkt) {
tmp.payload_len = pkt->payload_len - 1;
memcpy(tmp.payload, &pkt->payload[1], tmp.payload_len);
if (!_tables->hasSeen(&tmp)) { // don't retransmit!
if (!_tables->wasSeen(&tmp)) { // don't retransmit!
_tables->markSeen(&tmp);
removeSelfFromPath(&tmp);
routeDirectRecvAcks(&tmp, ((uint32_t)remaining + 1) * 300); // expect multipart ACKs 300ms apart (x2)
}
@@ -637,7 +652,7 @@ void Mesh::sendFlood(Packet* packet, uint32_t delay_millis, uint8_t path_hash_si
packet->header |= ROUTE_TYPE_FLOOD;
packet->setPathHashSizeAndCount(path_hash_size, 0);
_tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us
_tables->markSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us
uint8_t pri;
if (packet->getPayloadType() == PAYLOAD_TYPE_PATH) {
@@ -666,7 +681,7 @@ void Mesh::sendFlood(Packet* packet, uint16_t* transport_codes, uint32_t delay_m
packet->transport_codes[1] = transport_codes[1];
packet->setPathHashSizeAndCount(path_hash_size, 0);
_tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us
_tables->markSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us
uint8_t pri;
if (packet->getPayloadType() == PAYLOAD_TYPE_PATH) {
@@ -699,7 +714,7 @@ void Mesh::sendDirect(Packet* packet, const uint8_t* path, uint8_t path_len, uin
pri = 0;
}
}
_tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us
_tables->markSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us
sendPacket(packet, pri, delay_millis);
}
@@ -709,7 +724,7 @@ void Mesh::sendZeroHop(Packet* packet, uint32_t delay_millis) {
packet->path_len = 0; // path_len of zero means Zero Hop
_tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us
_tables->markSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us
sendPacket(packet, 0, delay_millis);
}
@@ -722,7 +737,7 @@ void Mesh::sendZeroHop(Packet* packet, uint16_t* transport_codes, uint32_t delay
packet->path_len = 0; // path_len of zero means Zero Hop
_tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us
_tables->markSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us
sendPacket(packet, 0, delay_millis);
}
+4 -3
View File
@@ -15,8 +15,9 @@ public:
*/
class MeshTables {
public:
virtual bool hasSeen(const Packet* packet) = 0;
virtual void clear(const Packet* packet) = 0; // remove this packet hash from table
virtual bool wasSeen(const Packet* packet) = 0;
virtual void markSeen(const Packet* packet) = 0;
virtual void clear(const Packet* packet) = 0; // remove this packet hash from table
};
/**
@@ -105,7 +106,7 @@ protected:
* \param auth_code a code to authenticate the packet
* \param flags zero for now
* \param path_snrs single byte SNR*4 for each hop in the path
* \param path_hashes hashes if each repeater in the path
* \param path_hashes hashes of each repeater in the path
* \param path_len length of the path_snrs[] and path_hashes[] arrays
*/
virtual void onTraceRecv(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) { }
+3
View File
@@ -64,6 +64,9 @@ public:
virtual uint8_t getStartupReason() const = 0;
virtual bool getBootloaderVersion(char* version, size_t max_len) { return false; }
virtual bool startOTAUpdate(const char* id, char reply[]) { return false; } // not supported
virtual bool setLoRaFemLnaEnabled(bool enable) { return false; }
virtual bool canControlLoRaFemLna() const { return false; }
virtual bool isLoRaFemLnaEnabled() const { return false; }
// Power management interface (boards with power management override these)
virtual bool isExternalPowered() { return false; }
+1 -1
View File
@@ -25,7 +25,7 @@ namespace mesh {
#define PAYLOAD_TYPE_GRP_DATA 0x06 // an (unverified) group datagram (prefixed with channel hash, MAC) (enc data: data_type(uint16), data_len, blob)
#define PAYLOAD_TYPE_ANON_REQ 0x07 // generic request (prefixed with dest_hash, ephemeral pub_key, MAC) (enc data: ...)
#define PAYLOAD_TYPE_PATH 0x08 // returned path (prefixed with dest/src hashes, MAC) (enc data: path, extra)
#define PAYLOAD_TYPE_TRACE 0x09 // trace a path, collecting SNI for each hop
#define PAYLOAD_TYPE_TRACE 0x09 // trace a path, collecting SNR for each hop
#define PAYLOAD_TYPE_MULTIPART 0x0A // packet is one of a set of packets
#define PAYLOAD_TYPE_CONTROL 0x0B // a control/discovery packet
//...
+97 -1
View File
@@ -2,6 +2,13 @@
#include <AES.h>
#include <SHA256.h>
#ifdef USE_CC310_HW_CRYPTO
#include <Adafruit_nRFCrypto.h>
#include "nrf_cc310/include/crys_hash.h"
#include "nrf_cc310/include/crys_hmac.h"
#include "nrf_cc310/include/ssi_aes.h"
#endif
#ifdef ARDUINO
#include <Arduino.h>
#endif
@@ -15,19 +22,58 @@ uint32_t RNG::nextInt(uint32_t _min, uint32_t _max) {
}
void Utils::sha256(uint8_t *hash, size_t hash_len, const uint8_t* msg, int msg_len) {
#ifdef USE_CC310_HW_CRYPTO
static CRYS_HASH_Result_t result;
nRFCrypto.begin();
CRYS_HASH(CRYS_HASH_SHA256_mode, (uint8_t*)msg, (size_t)msg_len, result);
nRFCrypto.end();
memcpy(hash, result, hash_len);
#else
SHA256 sha;
sha.update(msg, msg_len);
sha.finalize(hash, hash_len);
#endif
}
void Utils::sha256(uint8_t *hash, size_t hash_len, const uint8_t* frag1, int frag1_len, const uint8_t* frag2, int frag2_len) {
#ifdef USE_CC310_HW_CRYPTO
static CRYS_HASHUserContext_t ctx;
static CRYS_HASH_Result_t result;
nRFCrypto.begin();
CRYS_HASH_Init(&ctx, CRYS_HASH_SHA256_mode);
CRYS_HASH_Update(&ctx, (uint8_t*)frag1, (size_t)frag1_len);
CRYS_HASH_Update(&ctx, (uint8_t*)frag2, (size_t)frag2_len);
CRYS_HASH_Finish(&ctx, result);
nRFCrypto.end();
memcpy(hash, result, hash_len);
#else
SHA256 sha;
sha.update(frag1, frag1_len);
sha.update(frag2, frag2_len);
sha.finalize(hash, hash_len);
#endif
}
int Utils::decrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* src, int src_len) {
#ifdef USE_CC310_HW_CRYPTO
static SaSiAesUserContext_t ctx;
SaSiAesUserKeyData_t keyData = { (uint8_t*)shared_secret, CIPHER_KEY_SIZE };
uint8_t* dp = dest;
const uint8_t* sp = src;
size_t dummy_out = 0;
nRFCrypto.begin();
SaSi_AesInit(&ctx, SASI_AES_DECRYPT, SASI_AES_MODE_ECB, SASI_AES_PADDING_NONE);
SaSi_AesSetKey(&ctx, SASI_AES_USER_KEY, &keyData, sizeof(keyData));
while (sp - src < src_len) {
SaSi_AesBlock(&ctx, (uint8_t*)sp, 16, dp);
dp += 16; sp += 16;
}
SaSi_AesFinish(&ctx, 0, NULL, 0, NULL, &dummy_out);
SaSi_AesFree(&ctx);
nRFCrypto.end();
return sp - src;
#else
AES128 aes;
uint8_t* dp = dest;
const uint8_t* sp = src;
@@ -39,9 +85,34 @@ int Utils::decrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* s
}
return sp - src; // will always be multiple of 16
#endif
}
int Utils::encrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* src, int src_len) {
#ifdef USE_CC310_HW_CRYPTO
static SaSiAesUserContext_t ctx;
SaSiAesUserKeyData_t keyData = { (uint8_t*)shared_secret, CIPHER_KEY_SIZE };
uint8_t* dp = dest;
size_t dummy_out = 0;
nRFCrypto.begin();
SaSi_AesInit(&ctx, SASI_AES_ENCRYPT, SASI_AES_MODE_ECB, SASI_AES_PADDING_NONE);
SaSi_AesSetKey(&ctx, SASI_AES_USER_KEY, &keyData, sizeof(keyData));
while (src_len >= 16) {
SaSi_AesBlock(&ctx, (uint8_t*)src, 16, dp);
dp += 16; src += 16; src_len -= 16;
}
if (src_len > 0) { // remaining partial block — zero-pad to 16 bytes
uint8_t tmp[16] = {};
memcpy(tmp, src, src_len);
SaSi_AesBlock(&ctx, tmp, 16, dp);
dp += 16;
}
SaSi_AesFinish(&ctx, 0, NULL, 0, NULL, &dummy_out);
SaSi_AesFree(&ctx);
nRFCrypto.end();
return dp - dest;
#else
AES128 aes;
uint8_t* dp = dest;
@@ -58,15 +129,27 @@ int Utils::encrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* s
dp += 16;
}
return dp - dest; // will always be multiple of 16
#endif
}
int Utils::encryptThenMAC(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* src, int src_len) {
int enc_len = encrypt(shared_secret, dest + CIPHER_MAC_SIZE, src, src_len);
#ifdef USE_CC310_HW_CRYPTO
static CRYS_HMACUserContext_t hmac_ctx;
static CRYS_HASH_Result_t hmac_result;
nRFCrypto.begin();
CRYS_HMAC_Init(&hmac_ctx, CRYS_HASH_SHA256_mode, (uint8_t*)shared_secret, PUB_KEY_SIZE);
CRYS_HMAC_Update(&hmac_ctx, dest + CIPHER_MAC_SIZE, enc_len);
CRYS_HMAC_Finish(&hmac_ctx, hmac_result);
nRFCrypto.end();
memcpy(dest, hmac_result, CIPHER_MAC_SIZE);
#else
SHA256 sha;
sha.resetHMAC(shared_secret, PUB_KEY_SIZE);
sha.update(dest + CIPHER_MAC_SIZE, enc_len);
sha.finalizeHMAC(shared_secret, PUB_KEY_SIZE, dest, CIPHER_MAC_SIZE);
#endif
return CIPHER_MAC_SIZE + enc_len;
}
@@ -75,12 +158,25 @@ int Utils::MACThenDecrypt(const uint8_t* shared_secret, uint8_t* dest, const uin
if (src_len <= CIPHER_MAC_SIZE) return 0; // invalid src bytes
uint8_t hmac[CIPHER_MAC_SIZE];
#ifdef USE_CC310_HW_CRYPTO
{
static CRYS_HMACUserContext_t hmac_ctx;
static CRYS_HASH_Result_t hmac_result;
nRFCrypto.begin();
CRYS_HMAC_Init(&hmac_ctx, CRYS_HASH_SHA256_mode, (uint8_t*)shared_secret, PUB_KEY_SIZE);
CRYS_HMAC_Update(&hmac_ctx, (uint8_t*)(src + CIPHER_MAC_SIZE), src_len - CIPHER_MAC_SIZE);
CRYS_HMAC_Finish(&hmac_ctx, hmac_result);
nRFCrypto.end();
memcpy(hmac, hmac_result, CIPHER_MAC_SIZE);
}
#else
{
SHA256 sha;
sha.resetHMAC(shared_secret, PUB_KEY_SIZE);
sha.update(src + CIPHER_MAC_SIZE, src_len - CIPHER_MAC_SIZE);
sha.finalizeHMAC(shared_secret, PUB_KEY_SIZE, hmac, CIPHER_MAC_SIZE);
}
#endif
if (memcmp(hmac, src, CIPHER_MAC_SIZE) == 0) {
return decrypt(shared_secret, dest, src + CIPHER_MAC_SIZE, src_len - CIPHER_MAC_SIZE);
}
@@ -150,4 +246,4 @@ int Utils::parseTextParts(char* text, const char* parts[], int max_num, char sep
return num;
}
}
}
+8 -6
View File
@@ -1,4 +1,5 @@
#include <helpers/AdvertDataHelpers.h>
#include <helpers/UTF8Helpers.h>
uint8_t AdvertDataBuilder::encodeTo(uint8_t app_data[]) {
app_data[0] = _type;
@@ -16,11 +17,12 @@
app_data[0] |= ADV_FEAT2_MASK;
memcpy(&app_data[i], &_extra2, 2); i += 2;
}
if (_name && *_name != 0) {
app_data[0] |= ADV_NAME_MASK;
const char* sp = _name;
while (*sp && i < MAX_ADVERT_DATA_SIZE) {
app_data[i++] = *sp++;
if (_name && *_name != 0) {
size_t name_len = mesh::validUtf8PrefixLength(_name, MAX_ADVERT_DATA_SIZE - i);
if (name_len > 0) {
app_data[0] |= ADV_NAME_MASK;
memcpy(&app_data[i], _name, name_len);
i += name_len;
}
}
return i;
@@ -84,4 +86,4 @@ void AdvertTimeHelper::formatRelativeTimeDiff(char dest[], int32_t seconds_from_
}
}
}
}
}
+1
View File
@@ -42,6 +42,7 @@ void AutoDiscoverRTCClock::begin(TwoWire& wire) {
}
if (i2c_probe(wire, PCF8563_ADDRESS)) {
MESH_DEBUG_PRINTLN("PCF8563: Found");
rtc_8563_success = rtc_8563.begin(&wire);
}
+32 -20
View File
@@ -68,29 +68,36 @@ void BaseChatMesh::bootstrapRTCfromContacts() {
}
ContactInfo* BaseChatMesh::allocateContactSlot(bool transient_only) {
if (num_contacts < MAX_CONTACTS) {
return &contacts[num_contacts++];
} else if (transient_only || 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++) {
if (transient_only) {
if (contacts[i].type == ADV_TYPE_NONE && contacts[i].lastmod < oldest_lastmod) {
oldest_lastmod = contacts[i].lastmod;
oldest_idx = i;
}
} else {
int oldest_idx = -1;
uint32_t oldest_lastmod = 0xFFFFFFFF;
if (transient_only) {
// only allocate from first N
for (int i = 0; i < MAX_ANON_CONTACTS; i++) {
if (contacts[i].type == ADV_TYPE_NONE && contacts[i].lastmod < oldest_lastmod) {
oldest_lastmod = contacts[i].lastmod;
oldest_idx = i;
}
}
if (oldest_idx >= 0) {
// NOTE: do NOT call onContactOverwrite()
return &contacts[oldest_idx];
}
} else {
if (num_contacts < MAX_ANON_CONTACTS+MAX_CONTACTS) {
return &contacts[num_contacts++];
} else if (shouldOverwriteWhenFull()) {
// Find oldest non-favourite contact by oldest lastmod timestamp
for (int i = MAX_ANON_CONTACTS; i < num_contacts; i++) {
bool is_favourite = (contacts[i].flags & 0x01) != 0;
if (!is_favourite && contacts[i].lastmod < oldest_lastmod && contacts[i].type != ADV_TYPE_NONE) {
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];
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
@@ -139,6 +146,11 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id,
packet->header = save;
}
if (from && from->type == ADV_TYPE_NONE) { // already in contacts, but from a temporary ANON_REQ ?
memset(from, 0, sizeof(*from)); // clear the anon/temp slot
from = NULL; // do normal 'add' flow
}
bool is_new = false; // true = not in contacts[], false = exists in contacts[]
if (from == NULL) {
if (!shouldAutoAddContactType(parser.getType())) {
@@ -939,11 +951,11 @@ bool BaseChatMesh::getContactByIdx(uint32_t idx, ContactInfo& contact) {
}
ContactsIterator BaseChatMesh::startContactsIterator() {
return ContactsIterator();
return ContactsIterator(MAX_ANON_CONTACTS); // start at offset, skip the anon entries
}
bool ContactsIterator::hasNext(const BaseChatMesh* mesh, ContactInfo& dest) {
if (next_idx >= mesh->getNumContacts()) return false;
if (next_idx >= mesh->getTotalContactSlots()) return false;
dest = mesh->contacts[next_idx++];
return true;
+12 -5
View File
@@ -28,8 +28,9 @@ public:
class BaseChatMesh;
class ContactsIterator {
int next_idx = 0;
int next_idx;
public:
ContactsIterator(int start) { next_idx = start; }
bool hasNext(const BaseChatMesh* mesh, ContactInfo& dest);
};
@@ -79,8 +80,9 @@ class BaseChatMesh : public mesh::Mesh {
protected:
BaseChatMesh(mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::PacketManager& mgr, mesh::MeshTables& tables)
: mesh::Mesh(radio, ms, rng, rtc, mgr, tables)
{
num_contacts = 0;
{
resetContacts();
#ifdef MAX_GROUP_CHANNELS
memset(channels, 0, sizeof(channels));
num_channels = 0;
@@ -91,7 +93,11 @@ protected:
}
void bootstrapRTCfromContacts();
void resetContacts() { num_contacts = 0; }
void resetContacts() {
memset(contacts, 0, sizeof(contacts[0])*MAX_ANON_CONTACTS); // set all to have type = ADV_TYPE_NONE(0)
num_contacts = MAX_ANON_CONTACTS; // seed the first contacts for anon requests
}
void populateContactFromAdvert(ContactInfo& ci, const mesh::Identity& id, const AdvertDataParser& parser, uint32_t timestamp);
ContactInfo* allocateContactSlot(bool transient_only=false); // helper to find slot for new contact
@@ -167,7 +173,8 @@ public:
ContactInfo* lookupContactByPubKey(const uint8_t* pub_key, int prefix_len);
bool removeContact(ContactInfo& contact);
bool addContact(const ContactInfo& contact);
int getNumContacts() const { return num_contacts; }
int getTotalContactSlots() const { return num_contacts; }
int getNumContacts() const { return num_contacts - MAX_ANON_CONTACTS; } // don't include the reserved slots at start
bool getContactByIdx(uint32_t idx, ContactInfo& contact);
ContactsIterator startContactsIterator();
ChannelDetails* addChannel(const char* name, const char* psk_base64);
+106 -80
View File
@@ -28,16 +28,25 @@ static bool isValidName(const char *n) {
}
void CommonCLI::loadPrefs(FILESYSTEM* fs) {
if (fs->exists("/com_prefs")) {
loadPrefsInt(fs, "/com_prefs"); // new filename
} else if (fs->exists("/node_prefs")) {
loadPrefsInt(fs, "/node_prefs");
savePrefs(fs); // save to new filename
fs->remove("/node_prefs"); // remove old
if (fs->exists("/prefs.json")) {
#if defined(RP2040_PLATFORM)
File file = fs->open("/prefs.json", "r");
#else
File file = fs->open("/prefs.json");
#endif
if (file) {
_prefs->loadSerial(file); // new Serial prefs
file.close();
}
} else if (fs->exists("/com_prefs")) {
loadPrefsInt(fs, "/com_prefs");
if (savePrefs(fs)) { // save to new Serial prefs
// fs->remove("/com_prefs"); // remove old
}
}
}
void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { // Legacy prefs loader
#if defined(RP2040_PLATFORM)
File file = fs->open(filename, "r");
#else
@@ -88,10 +97,12 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
file.read((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162
file.read((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166
file.read((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170
file.read((uint8_t *)&_prefs->rx_boosted_gain, sizeof(_prefs->rx_boosted_gain)); // 290
file.read((uint8_t *)&_prefs->flood_max_unscoped, sizeof(_prefs->flood_max_unscoped)); // 291
file.read((uint8_t *)&_prefs->flood_max_advert, sizeof(_prefs->flood_max_advert)); // 292
// next: 293
file.read((uint8_t *)&_prefs->rx_boosted_gain, sizeof(_prefs->rx_boosted_gain)); // 290
file.read((uint8_t *)&_prefs->flood_max_unscoped, sizeof(_prefs->flood_max_unscoped)); // 291
file.read((uint8_t *)&_prefs->flood_max_advert, sizeof(_prefs->flood_max_advert)); // 292
file.read((uint8_t *)&_prefs->radio_fem_rxgain, sizeof(_prefs->radio_fem_rxgain)); // 293
file.read((uint8_t *)&_prefs->cad_enabled, sizeof(_prefs->cad_enabled)); // 294
// next: 295
// sanitise bad pref values
_prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f);
@@ -121,73 +132,28 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
// sanitise settings
_prefs->rx_boosted_gain = constrain(_prefs->rx_boosted_gain, 0, 1); // boolean
_prefs->radio_fem_rxgain = constrain(_prefs->radio_fem_rxgain, 0, 1); // boolean
_prefs->cad_enabled = constrain(_prefs->cad_enabled, 0, 1); // boolean
file.close();
}
}
void CommonCLI::savePrefs(FILESYSTEM* fs) {
bool CommonCLI::savePrefs(FILESYSTEM* fs) {
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
fs->remove("/com_prefs");
File file = fs->open("/com_prefs", FILE_O_WRITE);
fs->remove("/prefs.json");
File file = fs->open("/prefs.json", FILE_O_WRITE);
#elif defined(RP2040_PLATFORM)
File file = fs->open("/com_prefs", "w");
File file = fs->open("/prefs.json", "w");
#else
File file = fs->open("/com_prefs", "w", true);
File file = fs->open("/prefs.json", "w", true);
#endif
if (file) {
uint8_t pad[8];
memset(pad, 0, sizeof(pad));
file.write((uint8_t *)&_prefs->airtime_factor, sizeof(_prefs->airtime_factor)); // 0
file.write((uint8_t *)&_prefs->node_name, sizeof(_prefs->node_name)); // 4
file.write(pad, 4); // 36
file.write((uint8_t *)&_prefs->node_lat, sizeof(_prefs->node_lat)); // 40
file.write((uint8_t *)&_prefs->node_lon, sizeof(_prefs->node_lon)); // 48
file.write((uint8_t *)&_prefs->password[0], sizeof(_prefs->password)); // 56
file.write((uint8_t *)&_prefs->freq, sizeof(_prefs->freq)); // 72
file.write((uint8_t *)&_prefs->tx_power_dbm, sizeof(_prefs->tx_power_dbm)); // 76
file.write((uint8_t *)&_prefs->disable_fwd, sizeof(_prefs->disable_fwd)); // 77
file.write((uint8_t *)&_prefs->advert_interval, sizeof(_prefs->advert_interval)); // 78
file.write(pad, 1); // 79 : 1 byte unused (rx_boosted_gain moved to end)
file.write((uint8_t *)&_prefs->rx_delay_base, sizeof(_prefs->rx_delay_base)); // 80
file.write((uint8_t *)&_prefs->tx_delay_factor, sizeof(_prefs->tx_delay_factor)); // 84
file.write((uint8_t *)&_prefs->guest_password[0], sizeof(_prefs->guest_password)); // 88
file.write((uint8_t *)&_prefs->direct_tx_delay_factor, sizeof(_prefs->direct_tx_delay_factor)); // 104
file.write(pad, 4); // 108 : 4 byte unused
file.write((uint8_t *)&_prefs->sf, sizeof(_prefs->sf)); // 112
file.write((uint8_t *)&_prefs->cr, sizeof(_prefs->cr)); // 113
file.write((uint8_t *)&_prefs->allow_read_only, sizeof(_prefs->allow_read_only)); // 114
file.write((uint8_t *)&_prefs->multi_acks, sizeof(_prefs->multi_acks)); // 115
file.write((uint8_t *)&_prefs->bw, sizeof(_prefs->bw)); // 116
file.write((uint8_t *)&_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval)); // 120
file.write((uint8_t *)&_prefs->path_hash_mode, sizeof(_prefs->path_hash_mode)); // 121
file.write((uint8_t *)&_prefs->loop_detect, sizeof(_prefs->loop_detect)); // 122
file.write(pad, 1); // 123
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.write((uint8_t *)&_prefs->bridge_enabled, sizeof(_prefs->bridge_enabled)); // 127
file.write((uint8_t *)&_prefs->bridge_delay, sizeof(_prefs->bridge_delay)); // 128
file.write((uint8_t *)&_prefs->bridge_pkt_src, sizeof(_prefs->bridge_pkt_src)); // 130
file.write((uint8_t *)&_prefs->bridge_baud, sizeof(_prefs->bridge_baud)); // 131
file.write((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 135
file.write((uint8_t *)&_prefs->bridge_secret, sizeof(_prefs->bridge_secret)); // 136
file.write((uint8_t *)&_prefs->powersaving_enabled, sizeof(_prefs->powersaving_enabled)); // 152
file.write(pad, 3); // 153
file.write((uint8_t *)&_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156
file.write((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157
file.write((uint8_t *)&_prefs->advert_loc_policy, sizeof(_prefs->advert_loc_policy)); // 161
file.write((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162
file.write((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166
file.write((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170
file.write((uint8_t *)&_prefs->rx_boosted_gain, sizeof(_prefs->rx_boosted_gain)); // 290
file.write((uint8_t *)&_prefs->flood_max_unscoped, sizeof(_prefs->flood_max_unscoped)); // 291
file.write((uint8_t *)&_prefs->flood_max_advert, sizeof(_prefs->flood_max_advert)); // 292
// next: 293
bool success = _prefs->saveSerial(file);
file.close();
return success;
}
return false;
}
#define MIN_LOCAL_ADVERT_INTERVAL 60
@@ -500,6 +466,10 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
_prefs->interference_threshold = atoi(&config[11]);
savePrefs();
strcpy(reply, "OK");
} else if (memcmp(config, "cad ", 4) == 0) {
_prefs->cad_enabled = memcmp(&config[4], "on", 2) == 0;
savePrefs();
strcpy(reply, "OK");
} else if (memcmp(config, "agc.reset.interval ", 19) == 0) {
_prefs->agc_reset_interval = atoi(&config[19]) / 4;
savePrefs();
@@ -561,13 +531,37 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
_prefs->disable_fwd = memcmp(&config[7], "off", 3) == 0;
savePrefs();
strcpy(reply, _prefs->disable_fwd ? "OK - repeat is now OFF" : "OK - repeat is now ON");
#if defined(USE_SX1262) || defined(USE_SX1268) || defined(USE_LR1110)
} else if (memcmp(config, "radio.rxgain ", 13) == 0) {
_prefs->rx_boosted_gain = memcmp(&config[13], "on", 2) == 0;
strcpy(reply, "OK");
bool enabled = memcmp(&config[13], "on", 2) == 0;
_prefs->rx_boosted_gain = enabled;
savePrefs();
_callbacks->setRxBoostedGain(_prefs->rx_boosted_gain);
#endif
if (_callbacks->setRxBoostedGain(enabled)) {
strcpy(reply, "OK");
} else {
strcpy(reply, "Error: unsupported");
}
} else if (memcmp(config, "radio.fem.rxgain ", 17) == 0) {
if (!_board->canControlLoRaFemLna()) {
strcpy(reply, "Error: unsupported");
} else if (memcmp(&config[17], "on", 2) == 0) {
if (_board->setLoRaFemLnaEnabled(true)) {
_prefs->radio_fem_rxgain = 1;
savePrefs();
strcpy(reply, "OK - LoRa FEM RX gain on");
} else {
strcpy(reply, "Error: failed to apply LoRa FEM RX gain");
}
} else if (memcmp(&config[17], "off", 3) == 0) {
if (_board->setLoRaFemLnaEnabled(false)) {
_prefs->radio_fem_rxgain = 0;
savePrefs();
strcpy(reply, "OK - LoRa FEM RX gain off");
} else {
strcpy(reply, "Error: failed to apply LoRa FEM RX gain");
}
} else {
strcpy(reply, "Error: state must be on or off");
}
} else if (memcmp(config, "radio ", 6) == 0) {
strcpy(tmp, &config[6]);
const char *parts[4];
@@ -757,8 +751,30 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
}
} else {
_prefs->adc_multiplier = 0.0f;
strcpy(reply, "Error: unsupported by this board");
strcpy(reply, "Error: unsupported");
};
#if defined(USE_LR2021)
} else if (memcmp(config, "extra.sf ", 9) == 0) {
strcpy(tmp, &config[9]);
const char *parts[4];
uint8_t sideDetSFs[4];
int num = mesh::Utils::parseTextParts(tmp, parts, 4);
if (num > 3) {
sprintf(reply, "Invalid extra SF config");
} else {
for (int i = 0; i < num; i++) {
sideDetSFs[i] = atoi(parts[i]);
}
sideDetSFs[num] = 0;
if (_callbacks->configSideDetectors(sideDetSFs, num, _prefs->bw)) {
for (int i = 0; i <= num; i++) _prefs->extra_sf[i] = sideDetSFs[i];
savePrefs();
sprintf(reply, "OK - extra SFs set");
} else {
sprintf(reply, "Invalid extra SF config");
}
}
#endif
} else {
strcpy(reply, "unknown config: ");
StrHelper::strncpy(&reply[16], config, 160-17);
@@ -776,6 +792,8 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
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, "cad", 3) == 0) {
sprintf(reply, "> %s", _prefs->cad_enabled ? "on" : "off");
} else if (memcmp(config, "agc.reset.interval", 18) == 0) {
sprintf(reply, "> %d", ((uint32_t) _prefs->agc_reset_interval) * 4);
} else if (memcmp(config, "multi.acks", 10) == 0) {
@@ -801,10 +819,14 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->node_lat));
} else if (memcmp(config, "lon", 3) == 0) {
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->node_lon));
#if defined(USE_SX1262) || defined(USE_SX1268) || defined(USE_LR1110)
} else if (memcmp(config, "radio.rxgain", 12) == 0) {
sprintf(reply, "> %s", _prefs->rx_boosted_gain ? "on" : "off");
#endif
} else if (memcmp(config, "radio.fem.rxgain", 16) == 0) {
if (!_board->canControlLoRaFemLna()) {
strcpy(reply, "Error: unsupported");
} else {
sprintf(reply, "> %s", _board->isLoRaFemLnaEnabled() ? "on" : "off");
}
} else if (memcmp(config, "radio", 5) == 0) {
char freq[16], bw[16];
strcpy(freq, StrHelper::ftoa(_prefs->freq));
@@ -890,12 +912,12 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
strcpy(reply, "> unknown");
}
#else
strcpy(reply, "ERROR: unsupported");
strcpy(reply, "Error: unsupported");
#endif
} else if (memcmp(config, "adc.multiplier", 14) == 0) {
float adc_mult = _board->getAdcMultiplier();
if (adc_mult == 0.0f) {
strcpy(reply, "Error: unsupported by this board");
strcpy(reply, "Error: unsupported");
} else {
sprintf(reply, "> %.3f", adc_mult);
}
@@ -913,19 +935,23 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
strcpy(reply, "ERROR: Power management not supported");
#endif
} else if (memcmp(config, "pwrmgt.bootreason", 17) == 0) {
#ifdef NRF52_POWER_MANAGEMENT
sprintf(reply, "> Reset: %s; Shutdown: %s",
_board->getResetReasonString(_board->getResetReason()),
_board->getShutdownReasonString(_board->getShutdownReason()));
#else
strcpy(reply, "ERROR: Power management not supported");
#endif
} else if (memcmp(config, "pwrmgt.bootmv", 13) == 0) {
#ifdef NRF52_POWER_MANAGEMENT
sprintf(reply, "> %u mV", _board->getBootVoltage());
#else
strcpy(reply, "ERROR: Power management not supported");
#endif
} else if (memcmp(config, "extra.sf", 8) == 0) {
char* tmp = reply;
for (int i = 0; i < 3 && _prefs->extra_sf[i] != 0; i++) {
tmp += sprintf(tmp, "%s%d", (i == 0) ? "" : ",", _prefs->extra_sf[i]);
}
if (tmp == reply) {
sprintf(reply, "No extra SF configured");
}
} else {
sprintf(reply, "??: %s", config);
}
+171 -38
View File
@@ -5,6 +5,7 @@
#include <helpers/SensorManager.h>
#include <helpers/ClientACL.h>
#include <helpers/RegionMap.h>
#include <helpers/ConfigSerializer.h>
#if defined(WITH_RS232_BRIDGE) || defined(WITH_ESPNOW_BRIDGE)
#define WITH_BRIDGE
@@ -19,50 +20,176 @@
#define LOOP_DETECT_MODERATE 2
#define LOOP_DETECT_STRICT 3
struct NodePrefs { // persisted to file
float airtime_factor;
class NodePrefs : public ConfigSerializer {
public:
// in-memory backing data
float airtime_factor = 0;
char node_name[32];
double node_lat, node_lon;
double node_lat = 0, node_lon = 0;
char password[16];
float freq;
int8_t tx_power_dbm;
uint8_t disable_fwd;
uint8_t advert_interval; // minutes / 2
uint8_t flood_advert_interval; // hours
float rx_delay_base;
float tx_delay_factor;
float freq = 0;
int8_t tx_power_dbm = 0;
uint8_t disable_fwd = 0;
uint8_t advert_interval = 0; // minutes / 2
uint8_t flood_advert_interval = 0; // hours
float rx_delay_base = 0;
float tx_delay_factor = 0;
char guest_password[16];
float direct_tx_delay_factor;
float direct_tx_delay_factor = 0;
uint32_t guard;
uint8_t sf;
uint8_t cr;
uint8_t allow_read_only;
uint8_t multi_acks;
float bw;
uint8_t flood_max;
uint8_t flood_max_unscoped;
uint8_t flood_max_advert;
uint8_t interference_threshold;
uint8_t agc_reset_interval; // secs / 4
uint8_t sf = 0;
uint8_t cr = 0;
uint8_t allow_read_only = 0;
uint8_t multi_acks = 0;
float bw = 0;
uint8_t flood_max = 0;
uint8_t flood_max_unscoped = 0;
uint8_t flood_max_advert = 0;
uint8_t interference_threshold = 0;
uint8_t agc_reset_interval = 0; // secs / 4
// Bridge settings
uint8_t bridge_enabled; // boolean
uint16_t bridge_delay; // milliseconds (default 500 ms)
uint8_t bridge_pkt_src; // 0 = logTx, 1 = logRx (default logTx)
uint32_t bridge_baud; // 9600, 19200, 38400, 57600, 115200 (default 115200)
uint8_t bridge_channel; // 1-14 (ESP-NOW only)
uint8_t bridge_enabled = 0; // boolean
uint16_t bridge_delay = 0; // milliseconds (default 500 ms)
uint8_t bridge_pkt_src = 0; // 0 = logTx, 1 = logRx (default logTx)
uint32_t bridge_baud = 0; // 9600, 19200, 38400, 57600, 115200 (default 115200)
uint8_t bridge_channel = 0; // 1-14 (ESP-NOW only)
char bridge_secret[16]; // for XOR encryption of bridge packets (ESP-NOW only)
// Power setting
uint8_t powersaving_enabled; // boolean
uint8_t powersaving_enabled = 0; // boolean
// Gps settings
uint8_t gps_enabled;
uint32_t gps_interval; // in seconds
uint8_t advert_loc_policy;
uint32_t discovery_mod_timestamp;
float adc_multiplier;
uint8_t gps_enabled = 0;
uint32_t gps_interval = 0; // in seconds
uint8_t advert_loc_policy = 0;
uint32_t discovery_mod_timestamp = 0;
float adc_multiplier = 0;
char owner_info[120];
uint8_t rx_boosted_gain; // power settings
uint8_t path_hash_mode; // which path mode to use when sending
uint8_t loop_detect;
uint8_t rx_boosted_gain = 0; // power settings
uint8_t radio_fem_rxgain = 0; // LoRa FEM RX gain setting
uint8_t path_hash_mode = 0; // which path mode to use when sending
uint8_t loop_detect = 0;
uint8_t cad_enabled = 0; // hardware Channel Activity Detection before TX (boolean)
uint8_t extra_sf[4];
private:
class RadioPrefs : public ConfigSerializer {
NodePrefs* _parent;
protected:
void structure() override {
def("freq", _parent->freq);
def("bw", _parent->bw);
def("sf", _parent->sf);
def("cr", _parent->cr);
def("cad", _parent->cad_enabled);
def("int_thr", _parent->interference_threshold);
def("rxgain", _parent->rx_boosted_gain);
def("fem_rxgain", _parent->rx_boosted_gain);
def("tx", _parent->tx_power_dbm);
def("af", _parent->airtime_factor);
def("rxdelay", _parent->rx_delay_base);
def("f_txdelay", _parent->tx_delay_factor);
def("d_txdelay", _parent->direct_tx_delay_factor);
def("agc_int", _parent->agc_reset_interval);
def("hash_mode", _parent->path_hash_mode);
def("multi_ack", _parent->multi_acks);
}
public:
RadioPrefs(NodePrefs* parent) : _parent(parent) { }
};
RadioPrefs radio;
class BridgePrefs : public ConfigSerializer {
NodePrefs* _parent;
protected:
void structure() override {
def("en", _parent->bridge_enabled); // boolean
def("delay", _parent->bridge_delay); // milliseconds (default 500 ms)
def("src", _parent->bridge_pkt_src); // 0 = logTx, 1 = logRx (default logTx)
def("baud", _parent->bridge_baud); // 9600, 19200, 38400, 57600, 115200 (default 115200)
def("ch", _parent->bridge_channel); // 1-14 (ESP-NOW only)
def("secret", _parent->bridge_secret, sizeof(_parent->bridge_secret)); // for XOR encryption of bridge packets (ESP-NOW only)
}
public:
BridgePrefs(NodePrefs* parent) : _parent(parent) { }
};
BridgePrefs bridge;
class GPSPrefs : public ConfigSerializer {
NodePrefs* _parent;
protected:
void structure() override {
def("en", _parent->gps_enabled); // boolean
def("int", _parent->gps_interval); // interval in seconds
def("adv_loc", _parent->advert_loc_policy);
}
public:
GPSPrefs(NodePrefs* parent) : _parent(parent) { }
};
GPSPrefs gps;
class PowerPrefs : public ConfigSerializer {
NodePrefs* _parent;
protected:
void structure() override {
def("adc_mult", _parent->adc_multiplier);
def("pwr_sav_en", _parent->powersaving_enabled);
}
public:
PowerPrefs(NodePrefs* parent) : _parent(parent) { }
};
PowerPrefs power;
class RepeatPrefs : public ConfigSerializer {
NodePrefs* _parent;
protected:
void structure() override {
def("disable", _parent->disable_fwd);
def("f_max", _parent->flood_max);
def("f_max_uns", _parent->flood_max_unscoped);
def("f_max_adv", _parent->flood_max_advert);
def("loop", _parent->loop_detect);
}
public:
RepeatPrefs(NodePrefs* parent) : _parent(parent) { }
};
RepeatPrefs repeat;
class RoomPrefs : public ConfigSerializer {
NodePrefs* _parent;
protected:
void structure() override {
def("rd_only", _parent->allow_read_only);
}
public:
RoomPrefs(NodePrefs* parent) : _parent(parent) { }
};
RoomPrefs room;
protected:
void structure() override {
def("name", node_name, sizeof(node_name));
def("pass", password, sizeof(password));
def("guest", guest_password, sizeof(guest_password));
def("owner", owner_info, sizeof(owner_info));
def("adv_int", advert_interval);
def("f_adv_int", flood_advert_interval);
def("lat", node_lat);
def("lon", node_lon);
def("radio", radio);
def("bridge", bridge);
def("gps", gps);
def("repeat", repeat);
def("room", room);
def("power", power);
}
public:
NodePrefs() : ConfigSerializer(), bridge(this), gps(this), radio(this), power(this), repeat(this), room(this) {
node_name[0] = 0;
password[0] = 0;
guest_password[0] = 0;
bridge_secret[0] = 0;
owner_info[0] = 0;
}
};
class CommonCLICallbacks {
@@ -109,9 +236,15 @@ public:
// no op by default
};
virtual void setRxBoostedGain(bool enable) {
// no op by default
virtual bool setRxBoostedGain(bool enable) {
return false; // CommonCLI reports unsupported if not overridden by wrapper
};
#if defined(USE_LR2021)
virtual bool configSideDetectors(const uint8_t sideDetSFs[], uint8_t num, float bw) {
return false; // Override in wrapper
}
#endif
};
class CommonCLI {
@@ -137,7 +270,7 @@ public:
: _board(&board), _rtc(&rtc), _sensors(&sensors), _region_map(&region_map), _acl(&acl), _prefs(prefs), _callbacks(callbacks) { }
void loadPrefs(FILESYSTEM* _fs);
void savePrefs(FILESYSTEM* _fs);
bool savePrefs(FILESYSTEM* _fs);
void handleCommand(uint32_t sender_timestamp, char* command, char* reply);
uint8_t buildAdvertData(uint8_t node_type, uint8_t* app_data);
};
+331
View File
@@ -0,0 +1,331 @@
#include "ConfigSerializer.h"
bool ConfigSerializer::saveSerial(Stream& s) {
Context context(&s, OP::WRITE);
_context = &context; // set the context for structure() call
s.print("{"); // root object
_first = true;
structure();
if (s.print("}") != 1) context.success = false; // failure detect
_context = NULL;
return context.success;
}
#define TOK_ERROR -1
#define TOK_EOF 0
#define TOK_KEY 1
#define TOK_VALUE 2
#define TOK_START_OBJ 3
#define TOK_END_OBJ 4
#define TOK_WHITESPACE 5
static bool is_whitespace(char c) {
return c == ' ' || c == '\t' || c == '\r' || c == '\n';
}
static bool is_key_char(char c) {
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_';
}
static bool is_value_char(char c) {
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || c == '-' || c == '.';
}
#define EXPECT_OPEN_BRACE 0
#define EXPECT_KEY 1
#define EXPECT_VAL_OR_OBJ 2
#define EXPECT_STRING_VAL 3
#define EXPECT_STRING_ESCAPE 4
#define EXPECT_COMMA_OR_CLOSE 5
#define EXPECT_COMMA_OR_KEY 6
#define EXPECT_COMMA_OR_KEY_OR_CLOSE 7
int ConfigSerializer::Context::readNext() {
char c;
if (pending) {
c = pending;
pending = 0;
} else {
if (_f->available() == 0) return TOK_EOF;
int n = _f->read();
if (n < 0) return TOK_EOF;
c = (char)n;
}
switch (rd_mode) {
case EXPECT_OPEN_BRACE:
if (c == '{') { rd_mode = EXPECT_KEY; return TOK_START_OBJ; }
if (is_whitespace(c)) return TOK_WHITESPACE;
return TOK_ERROR;
case EXPECT_COMMA_OR_KEY_OR_CLOSE:
if (c == '}') { rd_mode = EXPECT_COMMA_OR_KEY_OR_CLOSE; return TOK_END_OBJ; }
case EXPECT_COMMA_OR_KEY:
if (c == ',') { rd_mode = EXPECT_KEY; return TOK_WHITESPACE; }
case EXPECT_KEY:
if (rd_len > 0 && c == ':') { rd_buf[rd_len] = 0; rd_len = 0; rd_mode = EXPECT_VAL_OR_OBJ; return TOK_KEY; }
if (rd_len == 0 && is_whitespace(c)) return TOK_WHITESPACE;
if (rd_len < CONFIG_MAX_KEYLEN-1 && is_key_char(c)) { rd_buf[rd_len++] = c; return TOK_WHITESPACE; }
return TOK_ERROR;
case EXPECT_VAL_OR_OBJ:
if (rd_len == 0 && is_whitespace(c)) return TOK_WHITESPACE;
if (rd_len == 0 && c == '"') { rd_mode = EXPECT_STRING_VAL; return TOK_WHITESPACE; }
if (rd_len == 0 && c == '{') { rd_mode = EXPECT_KEY; return TOK_START_OBJ; }
if (is_value_char(c) && rd_len < CONFIG_MAX_TOKEN_LEN-1) { rd_buf[rd_len++] = c; return TOK_WHITESPACE; }
if (rd_len > 0 && (c == ',' || c == '}' || is_whitespace(c))) { pending = c; rd_buf[rd_len] = 0; rd_len = 0; rd_mode = EXPECT_COMMA_OR_CLOSE; return TOK_VALUE; }
return TOK_ERROR;
case EXPECT_STRING_ESCAPE:
if ((c == 'n') && rd_len < CONFIG_MAX_TOKEN_LEN-1) { rd_buf[rd_len++] = '\n'; rd_mode = EXPECT_STRING_VAL; return TOK_WHITESPACE; }
if ((c == 'r') && rd_len < CONFIG_MAX_TOKEN_LEN-1) { rd_buf[rd_len++] = '\r'; rd_mode = EXPECT_STRING_VAL; return TOK_WHITESPACE; }
if ((c == '"' || c == '\\' || c == '/') && rd_len < CONFIG_MAX_TOKEN_LEN-1) { rd_buf[rd_len++] = c; rd_mode = EXPECT_STRING_VAL; return TOK_WHITESPACE; }
return TOK_ERROR; // unsupport escape
case EXPECT_STRING_VAL:
if (c == '"') { rd_buf[rd_len] = 0; rd_len = 0; rd_mode = EXPECT_COMMA_OR_CLOSE; return TOK_VALUE; }
if (c == '\\') { rd_mode = EXPECT_STRING_ESCAPE; return TOK_WHITESPACE; }
if (rd_len < CONFIG_MAX_TOKEN_LEN-1) { rd_buf[rd_len++] = c; return TOK_WHITESPACE; }
return TOK_ERROR;
case EXPECT_COMMA_OR_CLOSE:
if (c == ',') { rd_mode = EXPECT_KEY; return TOK_WHITESPACE; }
if (c == '}') { rd_mode = EXPECT_COMMA_OR_KEY_OR_CLOSE; return TOK_END_OBJ; }
if (is_whitespace(c)) return TOK_WHITESPACE;
return TOK_ERROR;
}
return TOK_ERROR; // unknown mode
}
bool ConfigSerializer::loadSerial(Stream& s) {
Context context(&s, OP::READ);
_context = &context; // set the context for structure() call
uint8_t sp = 0; // object nesting stack pointer
int next_tok;
// parse the Json file
while ((next_tok = context.readNext()) > TOK_EOF) {
if (next_tok == TOK_KEY) {
context.setKey(sp, context.getToken());
} else if (next_tok == TOK_VALUE) {
_depth = 1; // re-run the structure() hierarchy again (looking for specific key, at specific depth)
structure();
} else if (next_tok == TOK_START_OBJ) {
if (sp < CONFIG_MAX_DEPTH - 1) {
sp++;
} else {
//Serial.printf("Error: max nesting reached"); // TODO: debug logging
context.success = false;
break;
}
} else if (next_tok == TOK_END_OBJ) {
if (sp > 0) {
sp--;
} else {
//Serial.printf("Error: too many closing '}'"); // TODO: debug logging
context.success = false;
break;
}
}
}
if (sp != 0 || next_tok == TOK_ERROR) {
context.success = false; // unmatched { }, or other parse error
}
_context = NULL;
return context.success;
}
void ConfigSerializer::writeComma() {
if (_first) {
_first = false;
} else {
_context->file()->print(","); // comma separated properties
}
}
#include <Utils.h>
void ConfigSerializer::def(const char* key, void* value, size_t len) {
if (_context->op() == OP::WRITE) {
writeComma();
_context->file()->print(key);
_context->file()->print(":\"");
mesh::Utils::printHex(*_context->file(), (uint8_t*) value, len);
_context->file()->print("\"");
} else {
if (_context->keyMatch(_depth, key)) {
memset(value, 0, len);
mesh::Utils::fromHex((uint8_t *)value, len, _context->getToken());
}
}
}
void ConfigSerializer::def(const char* key, char* value, size_t max_len) {
if (_context->op() == OP::WRITE) {
writeComma();
_context->file()->print(key);
_context->file()->print(":\"");
char c;
while ((c = *value++) != 0) { // TODO: handle UTF-8 encoding
if (c == '"') {
_context->file()->print("\\\"");
} else if (c == '\\') {
_context->file()->print("\\\\");
} else if (c == '\n') {
_context->file()->print("\\n");
} else if (c == '\r') {
_context->file()->print("\\r");
} else {
_context->file()->print(c);
}
}
_context->file()->print("\"");
} else {
if (_context->keyMatch(_depth, key)) {
strncpy(value, _context->getToken(), max_len - 1);
value[max_len - 1] = 0;
}
}
}
void ConfigSerializer::def(const char* key, int32_t& value) {
if (_context->op() == OP::WRITE) {
writeComma();
_context->file()->print(key);
_context->file()->print(":");
_context->file()->print(value);
} else {
if (_context->keyMatch(_depth, key)) {
value = atol(_context->getToken());
}
}
}
void ConfigSerializer::def(const char* key, uint32_t& value) {
if (_context->op() == OP::WRITE) {
writeComma();
_context->file()->print(key);
_context->file()->print(":");
_context->file()->print(value);
} else {
if (_context->keyMatch(_depth, key)) {
value = atol(_context->getToken());
}
}
}
void ConfigSerializer::def(const char* key, int16_t& value) {
if (_context->op() == OP::WRITE) {
writeComma();
_context->file()->print(key);
_context->file()->print(":");
_context->file()->print((int32_t) value, 10);
} else {
if (_context->keyMatch(_depth, key)) {
value = atol(_context->getToken());
}
}
}
void ConfigSerializer::def(const char* key, uint16_t& value) {
if (_context->op() == OP::WRITE) {
writeComma();
_context->file()->print(key);
_context->file()->print(":");
_context->file()->print((uint32_t) value, 10);
} else {
if (_context->keyMatch(_depth, key)) {
value = atoi(_context->getToken());
}
}
}
void ConfigSerializer::def(const char* key, uint8_t& value) {
if (_context->op() == OP::WRITE) {
writeComma();
_context->file()->print(key);
_context->file()->print(":");
_context->file()->print((uint32_t) value, 10);
} else {
if (_context->keyMatch(_depth, key)) {
value = atoi(_context->getToken());
}
}
}
void ConfigSerializer::def(const char* key, int8_t& value) {
if (_context->op() == OP::WRITE) {
writeComma();
_context->file()->print(key);
_context->file()->print(":");
_context->file()->print((int32_t) value, 10);
} else {
if (_context->keyMatch(_depth, key)) {
value = atoi(_context->getToken());
}
}
}
void ConfigSerializer::def(const char* key, bool& value) {
if (_context->op() == OP::WRITE) {
writeComma();
_context->file()->print(key);
_context->file()->print(":");
_context->file()->print(value ? "true" : "false");
} else {
if (_context->keyMatch(_depth, key)) {
value = strcmp(_context->getToken(), "true") == 0 || atoi(_context->getToken()) != 0; // 'true' or a non-zero number
}
}
}
void ConfigSerializer::def(const char* key, double& value) {
if (_context->op() == OP::WRITE) {
writeComma();
_context->file()->print(key);
_context->file()->print(":");
if (value == 0.0) {
_context->file()->print("0"); // shorter encoding
} else {
_context->file()->print(value, 6); // REVISIT: how many dec places?
}
} else {
if (_context->keyMatch(_depth, key)) {
value = atof(_context->getToken());
}
}
}
void ConfigSerializer::def(const char* key, float& value) {
if (_context->op() == OP::WRITE) {
writeComma();
_context->file()->print(key);
_context->file()->print(":");
if (value == 0.0f) {
_context->file()->print("0"); // shorter encoding
} else {
_context->file()->print(value, 4); // REVISIT: how many dec places?
}
} else {
if (_context->keyMatch(_depth, key)) {
value = (float) atof(_context->getToken());
}
}
}
void ConfigSerializer::def(const char* key, ConfigSerializer& sub_obj) {
if (_context->op() == OP::WRITE) {
writeComma();
_context->file()->print(key);
_context->file()->print(":{");
sub_obj._context = _context; // inherit the Context
sub_obj._first = true;
sub_obj.structure(); // recurse into sub object
if (_context->file()->print("}") != 1) _context->success = false; // failure detect
} else {
if (_context->keyMatch(_depth, key)) {
sub_obj._context = _context; // inherit the Context
sub_obj._depth = _depth + 1;
sub_obj.structure(); // recurse into sub object
}
}
}
+68
View File
@@ -0,0 +1,68 @@
#pragma once
#include <Arduino.h>
#ifndef CONFIG_MAX_DEPTH
#define CONFIG_MAX_DEPTH 8
#endif
#ifndef CONFIG_MAX_KEYLEN
#define CONFIG_MAX_KEYLEN 16
#endif
#ifndef CONFIG_MAX_TOKEN_LEN
#define CONFIG_MAX_TOKEN_LEN 128
#endif
class ConfigSerializer {
bool _first;
int8_t _depth;
enum OP { READ, WRITE };
class Context {
Stream* _f;
OP _op;
uint8_t rd_len;
uint8_t rd_mode;
char pending;
char rd_buf[CONFIG_MAX_TOKEN_LEN];
char _keys[CONFIG_MAX_DEPTH][CONFIG_MAX_KEYLEN];
public:
bool success = true;
Context(Stream* f, OP op) : _f(f), _op(op) { rd_buf[rd_len = 0] = 0; rd_mode = 0; pending = 0; }
OP op() const { return _op; }
Stream* file() const { return _f; }
int readNext();
const char* getToken() const { return rd_buf; }
bool keyMatch(int8_t depth, const char* key) { return strcmp(key, _keys[depth]) == 0; }
void setKey(uint8_t depth, const char* key) { strcpy(_keys[depth], key); }
};
Context* _context = NULL;
void writeComma();
protected:
ConfigSerializer() { }
void def(const char* key, char* value, size_t max_len); // max_len inclusive of null
void def(const char* key, void* value, size_t len); // binary blob (encoded in hex)
void def(const char* key, int32_t& value);
void def(const char* key, int16_t& value);
void def(const char* key, int8_t& value);
void def(const char* key, uint32_t& value);
void def(const char* key, uint16_t& value);
void def(const char* key, uint8_t& value);
void def(const char* key, float& value);
void def(const char* key, double& value);
void def(const char* key, bool& value);
void def(const char* key, ConfigSerializer& sub_obj);
virtual void structure() = 0;
public:
bool loadSerial(Stream& s);
bool saveSerial(Stream& s);
};
+42
View File
@@ -1,6 +1,7 @@
#ifdef ESP_PLATFORM
#include "ESP32Board.h"
#include <target.h>
#if defined(ADMIN_PASSWORD) && !defined(DISABLE_WIFI_OTA) // Repeater or Room Server only
#include <WiFi.h>
@@ -44,4 +45,45 @@ bool ESP32Board::startOTAUpdate(const char* id, char reply[]) {
}
#endif
void ESP32Board::powerOff() {
enterDeepSleep(0); // Do not wakeup
}
void ESP32Board::enterDeepSleep(uint32_t secs) {
// Power off the display if any
#ifdef DISPLAY_CLASS
display.turnOff();
#endif
// Power off LoRa
radio_driver.powerOff();
// Keep LoRa inactive during deepsleep
digitalWrite(P_LORA_NSS, HIGH);
#if defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32C6)
gpio_hold_en((gpio_num_t)P_LORA_NSS);
#else
rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS);
#endif
// Power off GPS if any
if (sensors.getLocationProvider() != NULL) {
sensors.getLocationProvider()->stop();
}
// Flush serial buffers
Serial.flush();
delay(100);
// Clear stale wakeup sources to avoid ghost wakeup
// This is required when Power Management and automatic lightsleep are enabled
esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL);
if (secs > 0) {
esp_sleep_enable_timer_wakeup(secs * 1000000ULL);
}
// Finally set ESP32 into deepsleep
esp_deep_sleep_start(); // CPU halts here and never returns!
}
#endif
+40
View File
@@ -14,6 +14,7 @@
#include <Wire.h>
#include "soc/rtc.h"
#include "esp_system.h"
#include <driver/rtc_io.h>
class ESP32Board : public mesh::MainBoard {
protected:
@@ -62,6 +63,9 @@ public:
return raw / 4;
}
virtual void powerOff() override;
void enterDeepSleep(uint32_t secs);
uint32_t getIRQGpio() override {
return P_LORA_DIO_1; // default for SX1262
}
@@ -155,6 +159,42 @@ public:
void setInhibitSleep(bool inhibit) {
inhibit_sleep = inhibit;
}
uint32_t getResetReason() const override {
return esp_reset_reason();
}
// https://docs.espressif.com/projects/esp-idf/en/v4.4.7/esp32/api-reference/system/system.html
const char* getResetReasonString(uint32_t reason) {
switch (reason) {
case ESP_RST_UNKNOWN:
return "Unknown or first boot";
case ESP_RST_POWERON:
return "Power-on reset";
case ESP_RST_EXT:
return "External reset";
case ESP_RST_SW:
return "Software reset";
case ESP_RST_PANIC:
return "Panic / exception reset";
case ESP_RST_INT_WDT:
return "Interrupt watchdog reset";
case ESP_RST_TASK_WDT:
return "Task watchdog reset";
case ESP_RST_WDT:
return "Other watchdog reset";
case ESP_RST_DEEPSLEEP:
return "Wake from deep sleep";
case ESP_RST_BROWNOUT:
return "Brownout (low voltage)";
case ESP_RST_SDIO:
return "SDIO reset";
default:
static char buf[40];
snprintf(buf, sizeof(buf), "Unknown reset reason (%d)", reason);
return buf;
}
}
};
class ESP32RTCClock : public mesh::RTCClock {
+12
View File
@@ -0,0 +1,12 @@
#pragma once
class ExternalWatchdogManager {
protected:
unsigned long last_feed_watchdog;
public:
ExternalWatchdogManager() { last_feed_watchdog = 0; }
virtual bool begin() { return false; }
virtual void loop() { }
virtual unsigned long getIntervalMs() const { return 0; }
virtual void feed() { }
};
-30
View File
@@ -15,8 +15,6 @@
#include "ESP32Board.h"
#include <driver/rtc_io.h>
class MeshadventurerBoard : public ESP32Board {
public:
@@ -35,34 +33,6 @@ public:
}
}
void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) {
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
// Make sure the DIO1 and NSS GPIOs are held on required levels during deep sleep
rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY);
rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1);
rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS);
if (pin_wake_btn < 0) {
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet
} else {
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn
}
if (secs > 0) {
esp_sleep_enable_timer_wakeup(secs * 1000000);
}
// Finally set ESP32 into sleep
esp_deep_sleep_start(); // CPU halts here and never returns!
}
void powerOff() override {
// TODO: re-enable this when there is a definite wake-up source pin:
// enterDeepSleep(0);
}
uint16_t getBattMilliVolts() override {
analogReadResolution(12);
+200
View File
@@ -0,0 +1,200 @@
#pragma once
#include "BaseSerialInterface.h"
#ifndef MAX_INTERFACES
// ble, usb, wifi, ethernet
#define MAX_INTERFACES 4
#endif
enum class InterfaceType : uint8_t {
NONE,
Bluetooth,
USB,
WiFi,
Ethernet,
HardwareSerial
};
class MultiSerialInterface : public BaseSerialInterface {
private:
struct RegisteredInterface {
InterfaceType type = InterfaceType::NONE;
BaseSerialInterface* instance = nullptr;
};
bool _enabled = false;
RegisteredInterface _interfaces[MAX_INTERFACES] = {};
public:
bool addInterface(InterfaceType type, BaseSerialInterface* iface) {
// make sure an interface was provided
if(iface == nullptr){
return false;
}
// put it in the first free slot
for(int i = 0; i < MAX_INTERFACES; i++){
if(_interfaces[i].instance == nullptr){
_interfaces[i].instance = iface;
_interfaces[i].type = type;
return true;
}
}
// no free slots available
return false;
}
bool removeInterface(BaseSerialInterface* iface) {
// make sure an interface was provided
if(iface == nullptr){
return false;
}
// find and remove interface
for(int i = 0; i < MAX_INTERFACES; i++){
if(_interfaces[i].instance == iface){
_interfaces[i] = {};
return true;
}
}
// interface not found
return false;
}
void enableBluetooth() {
for(auto iface : _interfaces){
if(iface.instance && iface.type == InterfaceType::Bluetooth){
iface.instance->enable();
}
}
}
void disableBluetooth() {
for(auto iface : _interfaces){
if(iface.instance && iface.type == InterfaceType::Bluetooth){
iface.instance->disable();
}
}
}
bool isBluetoothEnabled() {
for(auto iface : _interfaces){
if(iface.instance && iface.type == InterfaceType::Bluetooth){
return iface.instance->isEnabled();
}
}
return false;
}
// enable all interfaces
void enable() override {
_enabled = true;
for(auto iface : _interfaces){
if(iface.instance){
iface.instance->enable();
}
}
}
// disable all interfaces
void disable() override {
_enabled = false;
for(auto iface : _interfaces){
if(iface.instance){
iface.instance->disable();
}
}
}
bool isEnabled() const override {
return _enabled;
}
bool isConnected() const override {
// not connected when disabled
if(!_enabled){
return false;
}
// check if any interface is connected
for(auto iface : _interfaces){
if(iface.instance && iface.instance->isConnected()) {
return true;
}
}
// nothing connected
return false;
}
// loop all interfaces
void loop() override {
for(auto iface : _interfaces){
if(iface.instance){
iface.instance->loop();
}
}
}
bool isWriteBusy() const override {
// not busy when disabled
if(!_enabled){
return false;
}
// check if any interface is busy
for(auto iface : _interfaces){
if(iface.instance && iface.instance->isEnabled() && iface.instance->isWriteBusy()){
return true;
}
}
// nothing busy
return false;
}
size_t writeFrame(const uint8_t src[], size_t len) override {
// don't write when disabled or nothing provided
if(!_enabled || len == 0){
return 0;
}
// write frame to all enabled interfaces
bool allSuccessful = true;
for(auto iface : _interfaces){
if(iface.instance && iface.instance->isEnabled()){
if(iface.instance->writeFrame(src, len) != len){
allSuccessful = false;
}
}
}
// report success if all writes completed successfully
return allSuccessful ? len : 0;
}
size_t checkRecvFrame(uint8_t dest[]) override {
// don't read when disabled
if(!_enabled){
return 0;
}
// try to read a frame from any enabled interface
for(auto iface : _interfaces){
if(iface.instance && iface.instance->isEnabled()){
size_t frameSize = iface.instance->checkRecvFrame(dest);
if(frameSize > 0){
return frameSize;
}
}
}
// no frame received
return 0;
}
};
+82 -8
View File
@@ -1,5 +1,6 @@
#if defined(NRF52_PLATFORM)
#include "NRF52Board.h"
#include <target.h>
#include <bluefruit.h>
#include <nrf_soc.h>
@@ -279,16 +280,29 @@ void NRF52Board::sleep(uint32_t secs) {
// Temperature from NRF52 MCU
float NRF52Board::getMCUTemperature() {
NRF_TEMP->TASKS_START = 1; // Start temperature measurement
long startTime = millis();
while (NRF_TEMP->EVENTS_DATARDY == 0) { // Wait for completion. Should complete in 50us
if(millis() - startTime > 5) { // To wait 5ms just in case
NRF_TEMP->TASKS_STOP = 1;
uint8_t sd_enabled = 0;
sd_softdevice_is_enabled(&sd_enabled);
if (sd_enabled) {
uint32_t err_code;
int32_t temp;
err_code = sd_temp_get(&temp);
if (err_code == NRF_SUCCESS) {
return (float)temp * 0.25f;
} else {
return NAN;
}
} else {
NRF_TEMP->TASKS_START = 1; // Start temperature measurement
long startTime = millis();
while (NRF_TEMP->EVENTS_DATARDY == 0) { // Wait for completion. Should complete in 50us
if(millis() - startTime > 5) { // To wait 5ms just in case
NRF_TEMP->TASKS_STOP = 1;
return NAN;
}
}
}
NRF_TEMP->EVENTS_DATARDY = 0; // Clear event flag
int32_t temp = NRF_TEMP->TEMP; // In 0.25 *C units
@@ -297,6 +311,65 @@ float NRF52Board::getMCUTemperature() {
return temp * 0.25f; // Convert to *C
}
void NRF52Board::shutdownPeripherals() {
// Power off the display if any
#ifdef DISPLAY_CLASS
if (display.isOn()) {
display.turnOff();
}
#endif
// Prep LoRa radio for power down
#ifdef P_LORA_RESET
digitalWrite(P_LORA_RESET, HIGH); // preload OUT latch so pinMode can't glitch NRESET low
pinMode(P_LORA_RESET, OUTPUT);
digitalWrite(P_LORA_RESET, LOW); // deliberate hardware reset (datasheet: >=100us)
delayMicroseconds(200);
digitalWrite(P_LORA_RESET, HIGH);
#endif
#if defined(P_LORA_SCLK) && defined(P_LORA_MISO) && defined(P_LORA_MOSI)
SPI.setPins(P_LORA_MISO, P_LORA_SCLK, P_LORA_MOSI);
SPI.begin(); // SPI may not be started on some shutdown paths, need it to shut down radio
#endif
#ifdef P_LORA_BUSY
pinMode(P_LORA_BUSY, INPUT);
uint32_t started_at = millis();
while (digitalRead(P_LORA_BUSY) && millis() - started_at < 10) {} //wait for radio to be ready
#endif
#ifdef P_LORA_NSS
pinMode(P_LORA_NSS, OUTPUT);
digitalWrite(P_LORA_NSS, HIGH);
#endif
// Power off LoRa
radio_driver.powerOff();
// Keep LoRa inactive during deepsleep
#ifdef P_LORA_NSS
digitalWrite(P_LORA_NSS, HIGH);
#endif
// Power off GPS if any
if(sensors.getLocationProvider() != NULL) {
sensors.getLocationProvider()->stop();
}
// Flush serial buffers
Serial.flush();
delay(100);
}
void NRF52Board::powerOff() {
shutdownPeripherals();
// Enter SYSTEMOFF
uint8_t sd_enabled = 0;
sd_softdevice_is_enabled(&sd_enabled);
if (sd_enabled) { // SoftDevice is enabled
sd_power_system_off();
} else { // SoftDevice is not enable
NRF_POWER->SYSTEMOFF = POWER_SYSTEMOFF_SYSTEMOFF_Enter;
}
}
bool NRF52Board::getBootloaderVersion(char* out, size_t max_len) {
static const char BOOTLOADER_MARKER[] = "UF2 Bootloader ";
const uint8_t* flash = (const uint8_t*)0x000FB000; // earliest known info.txt location is 0xFB90B, latest is 0xFCC4B
@@ -323,7 +396,8 @@ bool NRF52Board::startOTAUpdate(const char *id, char reply[]) {
Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16);
Bluefruit.begin(1, 0);
if (!Bluefruit.begin(1, 0)) return false;
// Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4
Bluefruit.setTxPower(4);
// Set the BLE device name
+2
View File
@@ -50,6 +50,8 @@ public:
virtual uint8_t getStartupReason() const override { return startup_reason; }
virtual float getMCUTemperature() override;
virtual void reboot() override { NVIC_SystemReset(); }
virtual void shutdownPeripherals();
virtual void powerOff() override;
virtual bool getBootloaderVersion(char* version, size_t max_len) override;
virtual bool startOTAUpdate(const char *id, char reply[]) override;
virtual void sleep(uint32_t secs) override;
+6 -4
View File
@@ -93,13 +93,15 @@ bool RegionMap::load(FILESYSTEM* _fs, const char* path) {
while (num_regions < MAX_REGION_ENTRIES) {
auto r = &regions[num_regions];
success = file.read((uint8_t *) &r->id, sizeof(r->id)) == sizeof(r->id);
int n = file.read((uint8_t *) &r->id, sizeof(r->id));
if (n == 0) break; // clean EOF
success = (n == sizeof(r->id));
success = success && file.read((uint8_t *) &r->parent, sizeof(r->parent)) == sizeof(r->parent);
success = success && file.read((uint8_t *) r->name, sizeof(r->name)) == sizeof(r->name);
success = success && file.read((uint8_t *) &r->flags, sizeof(r->flags)) == sizeof(r->flags);
success = success && file.read(pad, sizeof(pad)) == sizeof(pad);
if (!success) break; // EOF
if (!success) break; // partial read or corruption
if (r->id >= next_id) { // make sure next_id is valid
next_id = r->id + 1;
@@ -108,7 +110,7 @@ bool RegionMap::load(FILESYSTEM* _fs, const char* path) {
}
}
file.close();
return true;
return success;
}
}
return false; // failed
@@ -139,7 +141,7 @@ bool RegionMap::save(FILESYSTEM* _fs, const char* path) {
}
}
file.close();
return true;
return success;
}
return false; // failed
}
+10 -6
View File
@@ -31,27 +31,31 @@ public:
}
#endif
bool hasSeen(const mesh::Packet* packet) override {
bool wasSeen(const mesh::Packet* packet) override {
uint8_t hash[MAX_HASH_SIZE];
packet->calculatePacketHash(hash);
const uint8_t* sp = _hashes;
for (int i = 0; i < MAX_PACKET_HASHES; i++, sp += MAX_HASH_SIZE) {
if (memcmp(hash, sp, MAX_HASH_SIZE) == 0) {
if (memcmp(hash, sp, MAX_HASH_SIZE) == 0) {
if (packet->isRouteDirect()) {
_direct_dups++; // keep some stats
_direct_dups++;
} else {
_flood_dups++;
}
return true;
}
}
memcpy(&_hashes[_next_idx*MAX_HASH_SIZE], hash, MAX_HASH_SIZE);
_next_idx = (_next_idx + 1) % MAX_PACKET_HASHES; // cyclic table
return false;
}
void markSeen(const mesh::Packet* packet) override {
uint8_t hash[MAX_HASH_SIZE];
packet->calculatePacketHash(hash);
memcpy(&_hashes[_next_idx * MAX_HASH_SIZE], hash, MAX_HASH_SIZE);
_next_idx = (_next_idx + 1) % MAX_PACKET_HASHES;
}
void clear(const mesh::Packet* packet) override {
uint8_t hash[MAX_HASH_SIZE];
packet->calculatePacketHash(hash);
+56
View File
@@ -0,0 +1,56 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
namespace mesh {
inline bool isUtf8Continuation(uint8_t byte) {
return (byte & 0xC0) == 0x80;
}
inline size_t validUtf8PrefixLength(const char* text, size_t max_bytes) {
if (text == nullptr) return 0;
size_t offset = 0;
while (text[offset] != '\0') {
const uint8_t first = static_cast<uint8_t>(text[offset]);
size_t sequence_length = 0;
if (first <= 0x7F) {
sequence_length = 1;
} else if (first >= 0xC2 && first <= 0xDF) {
sequence_length = 2;
} else if (first >= 0xE0 && first <= 0xEF) {
sequence_length = 3;
} else if (first >= 0xF0 && first <= 0xF4) {
sequence_length = 4;
} else {
break;
}
if (offset + sequence_length > max_bytes) break;
bool complete = true;
for (size_t i = 1; i < sequence_length; i++) {
if (text[offset + i] == '\0' || !isUtf8Continuation(static_cast<uint8_t>(text[offset + i]))) {
complete = false;
break;
}
}
if (!complete) break;
if (sequence_length == 3) {
const uint8_t second = static_cast<uint8_t>(text[offset + 1]);
if ((first == 0xE0 && second < 0xA0) || (first == 0xED && second > 0x9F)) break;
} else if (sequence_length == 4) {
const uint8_t second = static_cast<uint8_t>(text[offset + 1]);
if ((first == 0xF0 && second < 0x90) || (first == 0xF4 && second > 0x8F)) break;
}
offset += sequence_length;
}
return offset;
}
} // namespace mesh
+2 -1
View File
@@ -39,7 +39,8 @@ void BridgeBase::handleReceivedPacket(mesh::Packet *packet) {
return;
}
if (!_seen_packets.hasSeen(packet)) {
if (!_seen_packets.wasSeen(packet)) {
_seen_packets.markSeen(packet);
// bridge_delay provides a buffer to prevent immediate processing conflicts in the mesh network.
_mgr->queueInbound(packet, millis() + _prefs->bridge_delay);
} else {
+1 -1
View File
@@ -110,7 +110,7 @@ protected:
* @brief Common packet handling for received packets
*
* Implements the standard pattern used by all bridges:
* - Check if packet was seen before using _seen_packets.hasSeen()
* - Check if packet was seen before using _seen_packets.wasSeen()
* - Queue packet for mesh processing if not seen before
* - Free packet if already seen to prevent duplicates
*
+3 -2
View File
@@ -32,7 +32,7 @@ void ESPNowBridge::begin() {
// Initialize WiFi in station mode
WiFi.mode(WIFI_STA);
// Set wifi channel
// Set Wi-Fi channel
if (esp_wifi_set_channel(_prefs->bridge_channel, WIFI_SECOND_CHAN_NONE) != ESP_OK) {
BRIDGE_DEBUG_PRINTLN("Error setting WIFI channel to %d\n", _prefs->bridge_channel);
return;
@@ -167,7 +167,8 @@ void ESPNowBridge::sendPacket(mesh::Packet *packet) {
return;
}
if (!_seen_packets.hasSeen(packet)) {
if (!_seen_packets.wasSeen(packet)) {
_seen_packets.markSeen(packet);
// Create a temporary buffer just for size calculation and reuse for actual writing
uint8_t sizingBuffer[MAX_PAYLOAD_SIZE];
uint16_t meshPacketLen = packet->writeTo(sizingBuffer);
+2 -1
View File
@@ -115,7 +115,8 @@ void RS232Bridge::sendPacket(mesh::Packet *packet) {
return;
}
if (!_seen_packets.hasSeen(packet)) {
if (!_seen_packets.wasSeen(packet)) {
_seen_packets.markSeen(packet);
uint8_t buffer[MAX_SERIAL_PACKET_SIZE];
uint16_t len = packet->writeTo(buffer + 4);
+1 -1
View File
@@ -38,7 +38,7 @@ public:
* These two functions do nothing for ESP-NOW, but are needed for the
* Radio interface.
*/
virtual void setRxBoostedGainMode(bool) { }
virtual bool setRxBoostedGainMode(bool) { }
virtual bool getRxBoostedGainMode() const { return false; }
uint32_t intID();
+18 -20
View File
@@ -103,10 +103,9 @@ void SerialBLEInterface::onMtuChanged(BLEServer* pServer, esp_ble_gatts_cb_param
void SerialBLEInterface::onDisconnect(BLEServer* pServer) {
BLE_DEBUG_PRINTLN("onDisconnect()");
deviceConnected = false;
if (_isEnabled) {
adv_restart_time = millis() + ADVERT_RESTART_DELAY;
// loop() will detect this on next loop, and set deviceConnected to false
}
}
@@ -118,17 +117,24 @@ void SerialBLEInterface::onWrite(BLECharacteristic* pCharacteristic, esp_ble_gat
if (len > MAX_FRAME_SIZE) {
BLE_DEBUG_PRINTLN("ERROR: onWrite(), frame too big, len=%d", len);
} else if (recv_queue_len >= FRAME_QUEUE_SIZE) {
BLE_DEBUG_PRINTLN("ERROR: onWrite(), recv_queue is full!");
} else {
recv_queue[recv_queue_len].len = len;
memcpy(recv_queue[recv_queue_len].buf, rxValue, len);
recv_queue_len++;
Frame frame = {};
frame.len = len;
memcpy(frame.buf, rxValue, len);
if (xQueueSend(recv_queue, &frame, 0) != pdTRUE) {
BLE_DEBUG_PRINTLN("ERROR: onWrite(), recv_queue is full!");
}
}
}
// ---------- public methods
void SerialBLEInterface::clearBuffers() {
xQueueReset(recv_queue);
send_queue_len = 0;
}
void SerialBLEInterface::enable() {
if (_isEnabled) return;
@@ -202,21 +208,13 @@ size_t SerialBLEInterface::checkRecvFrame(uint8_t dest[]) {
}
}
if (recv_queue_len > 0) { // check recv queue
size_t len = recv_queue[0].len; // take from top of queue
memcpy(dest, recv_queue[0].buf, len);
BLE_DEBUG_PRINTLN("readBytes: sz=%d, hdr=%d", len, (uint32_t) dest[0]);
recv_queue_len--;
for (int i = 0; i < recv_queue_len; i++) { // delete top item from queue
recv_queue[i] = recv_queue[i + 1];
}
return len;
Frame frame;
if (xQueueReceive(recv_queue, &frame, 0) == pdTRUE) {
memcpy(dest, frame.buf, frame.len);
BLE_DEBUG_PRINTLN("readBytes: sz=%d, hdr=%d", (uint32_t) frame.len, (uint32_t) dest[0]);
return frame.len;
}
if (pServer->getConnectedCount() == 0) deviceConnected = false;
if (deviceConnected != oldDeviceConnected) {
if (!deviceConnected) { // disconnecting
clearBuffers();
+10 -4
View File
@@ -5,6 +5,8 @@
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include <freertos/FreeRTOS.h>
#include <freertos/queue.h>
class SerialBLEInterface : public BaseSerialInterface, BLESecurityCallbacks, BLEServerCallbacks, BLECharacteristicCallbacks {
BLEServer *pServer;
@@ -24,12 +26,13 @@ class SerialBLEInterface : public BaseSerialInterface, BLESecurityCallbacks, BLE
};
#define FRAME_QUEUE_SIZE 4
int recv_queue_len;
Frame recv_queue[FRAME_QUEUE_SIZE];
StaticQueue_t recv_queue_state;
uint8_t recv_queue_storage[FRAME_QUEUE_SIZE * sizeof(Frame)];
QueueHandle_t recv_queue;
int send_queue_len;
Frame send_queue[FRAME_QUEUE_SIZE];
void clearBuffers() { recv_queue_len = 0; send_queue_len = 0; }
void clearBuffers();
protected:
// BLESecurityCallbacks methods
@@ -58,7 +61,10 @@ public:
_isEnabled = false;
_last_write = 0;
last_conn_id = 0;
send_queue_len = recv_queue_len = 0;
recv_queue = xQueueCreateStatic(
FRAME_QUEUE_SIZE, sizeof(Frame), recv_queue_storage, &recv_queue_state
);
send_queue_len = 0;
}
/**
+10
View File
@@ -16,6 +16,16 @@ void TBeamBoard::begin() {
ESP32Board::begin();
#ifdef TBEAM_SUPREME_SX1262
// On the T-Beam S3 Supreme the PMU + RTC sit on Wire1 (GPIO 42/41, brought
// up by XPowersLib), while the SH1106 OLED and the BME280/QMC6310 sensors
// sit on the primary bus, Wire (GPIO PIN_BOARD_SDA/PIN_BOARD_SCL). Nothing
// else initialises Wire on this board, so the display driver would
// otherwise talk on the wrong (default) pins and display.begin() fails,
// leaving the screen blank. Bring the OLED bus up here.
Wire.begin(PIN_BOARD_SDA, PIN_BOARD_SCL);
#endif
power_init();
//Configure user button
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#if defined(ETHERNET_ENABLED)
#if defined(ETHERNET_USE_CH390)
#include "helpers/ethernet/ch390/CH390EthernetInterface.h"
#elif defined(ETHERNET_USE_RAK13800)
#include "helpers/ethernet/RAK13800/RAK13800EthernetInterface.h"
#else
#error "ETHERNET_ENABLED is defined, but no specific driver flag (e.g. ETHERNET_USE_CH390) was provided!"
#endif
#endif
@@ -0,0 +1,109 @@
#include "RAK13800EthernetInterface.h"
#include "../../nrf52/EthernetMac.h"
#include <SPI.h>
#include <EthernetUdp.h>
#define PIN_SPI1_MISO (29) // (0 + 29)
#define PIN_SPI1_MOSI (30) // (0 + 30)
#define PIN_SPI1_SCK (3) // (0 + 3)
SPIClass ETHERNET_SPI_PORT(NRF_SPIM1, PIN_SPI1_MISO, PIN_SPI1_SCK, PIN_SPI1_MOSI);
#define PIN_ETHERNET_POWER_EN WB_IO2 // output, high to enable
#define PIN_ETHERNET_RESET 21
#define PIN_ETHERNET_SS 26
bool RAK13800EthernetInterface::begin() {
// WB_IO2 (power enable) is already driven HIGH by early constructor
// in RAK4631Board.cpp to support POE boot.
// Skip hardware reset — the W5100S comes out of power-on reset cleanly,
// and toggling reset kills the PHY link which breaks POE power.
#ifdef PIN_ETHERNET_RESET
pinMode(PIN_ETHERNET_RESET, OUTPUT);
digitalWrite(PIN_ETHERNET_RESET, HIGH);
#endif
// generate mac address
uint8_t mac[6];
generateEthernetMac(mac);
ETHERNET_DEBUG_PRINTLN(
"Ethernet MAC: %02X:%02X:%02X:%02X:%02X:%02X",
mac[0],
mac[1],
mac[2],
mac[3],
mac[4],
mac[5]);
ETHERNET_SPI_PORT.begin();
Ethernet.init(ETHERNET_SPI_PORT, PIN_ETHERNET_SS);
// Use static IP if build flags are defined, otherwise DHCP
#if defined(ETHERNET_STATIC_IP) && defined(ETHERNET_STATIC_GATEWAY) && defined(ETHERNET_STATIC_SUBNET) && defined(ETHERNET_STATIC_DNS)
IPAddress ip(ETHERNET_STATIC_IP);
IPAddress gateway(ETHERNET_STATIC_GATEWAY);
IPAddress subnet(ETHERNET_STATIC_SUBNET);
IPAddress dns(ETHERNET_STATIC_DNS);
Ethernet.begin(mac, ip, dns, gateway, subnet);
#else
if (Ethernet.begin(mac) == 0) {
ETHERNET_DEBUG_PRINTLN("Failed to initialize RAK13800 hardware.");
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
ETHERNET_DEBUG_PRINTLN("Ethernet hardware not found.");
} else if (Ethernet.linkStatus() == LinkOFF) {
ETHERNET_DEBUG_PRINTLN("Ethernet cable not connected.");
} else {
ETHERNET_DEBUG_PRINTLN("DHCP failed for unknown reason.");
}
return false;
}
#endif
ETHERNET_DEBUG_PRINTLN("Ethernet begin complete");
ETHERNET_DEBUG_PRINT_IP("IP Address", Ethernet.localIP());
ETHERNET_DEBUG_PRINT_IP("Subnet Mask", Ethernet.subnetMask());
ETHERNET_DEBUG_PRINT_IP("Gateway", Ethernet.gatewayIP());
ETHERNET_DEBUG_PRINT_IP("DNS", Ethernet.dnsServerIP());
server.begin();
ETHERNET_DEBUG_PRINTLN("listening on TCP port: %d", ETHERNET_TCP_PORT);
return true;
}
int RAK13800EthernetInterface::available() {
return client.available();
}
int RAK13800EthernetInterface::read() {
return client.read();
}
size_t RAK13800EthernetInterface::write(const uint8_t *buf, size_t size) {
return client.write(buf, size);
}
bool RAK13800EthernetInterface::isConnected() const {
return _isConnected;
}
void RAK13800EthernetInterface::loop() {
Ethernet.maintain();
auto newClient = server.accept();
if (newClient) {
IPAddress remoteIp = newClient.remoteIP();
uint16_t remotePort = newClient.remotePort();
ETHERNET_DEBUG_PRINTLN("New client accepted %u.%u.%u.%u:%u", remoteIp[0], remoteIp[1], remoteIp[2], remoteIp[3], remotePort);
if (client) {
ETHERNET_DEBUG_PRINTLN("Closing previous client");
client.stop();
}
client = newClient;
onClientConnected();
}
_isConnected = client.connected();
}
@@ -0,0 +1,27 @@
#pragma once
#include "../SerialEthernetInterface.h"
#include <SPI.h>
#include <RAK13800_W5100S.h>
class RAK13800EthernetInterface : public SerialEthernetInterface {
bool _isConnected;
EthernetServer server;
EthernetClient client;
public:
RAK13800EthernetInterface() : server(EthernetServer(ETHERNET_TCP_PORT)) {
_isConnected = false;
}
bool begin();
void loop() override;
// BaseSerialInterface methods
bool isConnected() const override;
int available() override;
int read() override;
size_t write(const uint8_t *buf, size_t size) override;
};
@@ -0,0 +1,140 @@
#include "SerialEthernetInterface.h"
#define RECV_STATE_IDLE 0
#define RECV_STATE_HDR_FOUND 1
#define RECV_STATE_LEN1_FOUND 2
#define RECV_STATE_LEN2_FOUND 3
bool SerialEthernetInterface::begin() {
return true;
}
void SerialEthernetInterface::enable() {
if (_isEnabled) return;
_isEnabled = true;
clearBuffers();
}
void SerialEthernetInterface::disable() {
_isEnabled = false;
}
size_t SerialEthernetInterface::writeFrame(const uint8_t src[], size_t len) {
if (len > MAX_FRAME_SIZE) {
ETHERNET_DEBUG_PRINTLN("writeFrame(), frame too big, len=%d\n", len);
return 0;
}
if (isConnected() && len > 0) {
if (send_queue_len >= FRAME_QUEUE_SIZE) {
ETHERNET_DEBUG_PRINTLN("writeFrame(), send_queue is full!");
return 0;
}
send_queue[send_queue_len].len = len; // add to send queue
memcpy(send_queue[send_queue_len].buf, src, len);
send_queue_len++;
return len;
}
return 0;
}
bool SerialEthernetInterface::isWriteBusy() const {
return false;
}
void SerialEthernetInterface::onClientConnected() {
_state = RECV_STATE_IDLE;
_frame_len = 0;
_rx_len = 0;
}
size_t SerialEthernetInterface::checkRecvFrame(uint8_t dest[]) {
if (isConnected()) {
if (send_queue_len > 0) { // first, check send queue
_last_write = millis();
int len = send_queue[0].len;
#if ETHERNET_RAW_LINE
ETHERNET_DEBUG_PRINTLN("TX line len=%d", len);
client.write(send_queue[0].buf, len);
client.write("\r\n", 2);
#else
uint8_t pkt[3+len]; // use same header as serial interface so client can delimit frames
pkt[0] = '>';
pkt[1] = (len & 0xFF); // LSB
pkt[2] = (len >> 8); // MSB
memcpy(&pkt[3], send_queue[0].buf, send_queue[0].len);
ETHERNET_DEBUG_PRINTLN("Sending frame len=%d", len);
#if ETHERNET_DEBUG_LOGGING && ARDUINO
ETHERNET_DEBUG_PRINTLN("TX frame len=%d", len);
#endif
write(pkt, 3 + len);
#endif
send_queue_len--;
for (int i = 0; i < send_queue_len; i++) { // delete top item from queue
send_queue[i] = send_queue[i + 1];
}
} else {
while (available()) {
int c = read();
if (c < 0) break;
#if ETHERNET_RAW_LINE
if (c == '\r' || c == '\n') {
if (_rx_len == 0) {
continue;
}
uint16_t out_len = _rx_len;
if (out_len > MAX_FRAME_SIZE) out_len = MAX_FRAME_SIZE;
memcpy(dest, _rx_buf, out_len);
_rx_len = 0;
return out_len;
}
if (_rx_len < MAX_FRAME_SIZE) {
_rx_buf[_rx_len] = (uint8_t)c;
_rx_len++;
}
#else
switch (_state) {
case RECV_STATE_IDLE:
if (c == '<') {
_state = RECV_STATE_HDR_FOUND;
}
break;
case RECV_STATE_HDR_FOUND:
_frame_len = (uint8_t)c;
_state = RECV_STATE_LEN1_FOUND;
break;
case RECV_STATE_LEN1_FOUND:
_frame_len |= ((uint16_t)c) << 8;
_rx_len = 0;
_state = _frame_len > 0 ? RECV_STATE_LEN2_FOUND : RECV_STATE_IDLE;
break;
default:
if (_rx_len < MAX_FRAME_SIZE) {
_rx_buf[_rx_len] = (uint8_t)c;
}
_rx_len++;
if (_rx_len >= _frame_len) {
if (_frame_len > MAX_FRAME_SIZE) {
_frame_len = MAX_FRAME_SIZE;
}
#if ETHERNET_DEBUG_LOGGING && ARDUINO
ETHERNET_DEBUG_PRINTLN("RX frame len=%d", _frame_len);
#endif
memcpy(dest, _rx_buf, _frame_len);
_state = RECV_STATE_IDLE;
return _frame_len;
}
}
#endif
}
}
}
return 0;
}
@@ -0,0 +1,75 @@
#pragma once
#include "../BaseSerialInterface.h"
#ifndef ETHERNET_TCP_PORT
#define ETHERNET_TCP_PORT 5000
#endif
// define ETHERNET_RAW_LINE=1 to use raw line-based CLI instead of framed packets
class SerialEthernetInterface : public BaseSerialInterface {
bool _isEnabled;
unsigned long _last_write;
uint8_t _state;
uint16_t _frame_len;
uint16_t _rx_len;
uint8_t _rx_buf[MAX_FRAME_SIZE];
struct Frame {
uint8_t len;
uint8_t buf[MAX_FRAME_SIZE];
};
#define FRAME_QUEUE_SIZE 4
int send_queue_len;
Frame send_queue[FRAME_QUEUE_SIZE];
void clearBuffers() {
send_queue_len = 0;
_state = 0;
_frame_len = 0;
_rx_len = 0;
}
protected:
public:
SerialEthernetInterface() {
_isEnabled = false;
_last_write = 0;
send_queue_len = 0;
_state = 0;
_frame_len = 0;
_rx_len = 0;
}
bool begin();
void onClientConnected();
// BaseSerialInterface methods
void enable() override;
void disable() override;
bool isEnabled() const override { return _isEnabled; }
bool isConnected() const override;
bool isWriteBusy() const override;
size_t writeFrame(const uint8_t src[], size_t len) override;
size_t checkRecvFrame(uint8_t dest[]) override;
virtual int available() = 0;
virtual int read() = 0;
virtual size_t write(const uint8_t *buf, size_t size) = 0;
};
#if ETHERNET_DEBUG_LOGGING && ARDUINO
#include <Arduino.h>
#define ETHERNET_DEBUG_PRINT(F, ...) Serial.printf("ETH: " F, ##__VA_ARGS__)
#define ETHERNET_DEBUG_PRINTLN(F, ...) Serial.printf("ETH: " F "\n", ##__VA_ARGS__)
#define ETHERNET_DEBUG_PRINT_IP(name, ip) Serial.printf("ETH: " name ": %u.%u.%u.%u" "\n", ip[0], ip[1], ip[2], ip[3])
#else
#define ETHERNET_DEBUG_PRINT(...) {}
#define ETHERNET_DEBUG_PRINTLN(...) {}
#define ETHERNET_DEBUG_PRINT_IP(...) {}
#endif
@@ -0,0 +1,94 @@
#include "CH390EthernetInterface.h"
void onWiFiEvent(WiFiEvent_t event) {
switch(event){
case ARDUINO_EVENT_ETH_START:
ETHERNET_DEBUG_PRINTLN("Ethernet Started");
break;
case ARDUINO_EVENT_ETH_CONNECTED:
ETHERNET_DEBUG_PRINTLN("Ethernet Connected");
break;
case ARDUINO_EVENT_ETH_DISCONNECTED:
ETHERNET_DEBUG_PRINTLN("Ethernet Disconnected");
break;
case ARDUINO_EVENT_ETH_GOT_IP:
ETHERNET_DEBUG_PRINTLN("Ethernet Got IP");
ETHERNET_DEBUG_PRINT_IP("IP Address", CH390.localIP());
ETHERNET_DEBUG_PRINT_IP("Subnet Mask", CH390.subnetMask());
ETHERNET_DEBUG_PRINT_IP("Gateway", CH390.gatewayIP());
ETHERNET_DEBUG_PRINT_IP("DNS", CH390.dnsIP());
ETHERNET_DEBUG_PRINTLN("MAC Address: %s", CH390.macAddress().c_str());
break;
default:
break;
}
}
bool CH390EthernetInterface::begin() {
// listen to ethernet events
WiFi.onEvent(onWiFiEvent);
// Init CH390
ch390_config_t config = CH390_DEFAULT_CONFIG();
config.spi_miso_gpio = ETH_MISO_PIN;
config.spi_mosi_gpio = ETH_MOSI_PIN;
config.spi_sck_gpio = ETH_SCLK_PIN;
config.spi_cs_gpio = ETH_CS_PIN;
config.int_gpio = ETH_INT_PIN;
if (!CH390.begin(config)) {
ETHERNET_DEBUG_PRINTLN("Failed to initialize CH390 hardware.");
return false;
}
// Setup Static IP if build flags are present
#if defined(ETHERNET_STATIC_IP) && defined(ETHERNET_STATIC_GATEWAY) && defined(ETHERNET_STATIC_SUBNET)
IPAddress ip(ETHERNET_STATIC_IP);
IPAddress gw(ETHERNET_STATIC_GATEWAY);
IPAddress sn(ETHERNET_STATIC_SUBNET);
CH390.config(ip, gw, sn);
#endif
// Start Server
server.begin(ETHERNET_TCP_PORT);
ETHERNET_DEBUG_PRINTLN("listening on TCP port: %d", ETHERNET_TCP_PORT);
return true;
}
int CH390EthernetInterface::available() {
return client.available();
}
int CH390EthernetInterface::read() {
return client.read();
}
size_t CH390EthernetInterface::write(const uint8_t *buf, size_t size) {
return client.write(buf, size);
}
bool CH390EthernetInterface::isConnected() const {
return _isConnected;
}
void CH390EthernetInterface::loop() {
if (server.hasClient()) {
auto newClient = server.available();
if (newClient) {
IPAddress remoteIp = newClient.remoteIP();
uint16_t remotePort = newClient.remotePort();
ETHERNET_DEBUG_PRINTLN("New client accepted %u.%u.%u.%u:%u", remoteIp[0], remoteIp[1], remoteIp[2], remoteIp[3], remotePort);
if (client) {
ETHERNET_DEBUG_PRINTLN("Closing previous client");
client.stop();
}
client = newClient;
onClientConnected();
}
}
_isConnected = client.connected();
}
@@ -0,0 +1,30 @@
#pragma once
#include "../SerialEthernetInterface.h"
#include <SPI.h>
#include <WiFi.h>
#include <WiFiServer.h>
#include <WiFiClient.h>
#include <ESP32_CH390.h>
class CH390EthernetInterface : public SerialEthernetInterface {
bool _isConnected;
WiFiServer server;
WiFiClient client;
public:
CH390EthernetInterface(){
_isConnected = false;
}
bool begin();
void loop() override;
// BaseSerialInterface methods
bool isConnected() const override;
int available() override;
int read() override;
size_t write(const uint8_t *buf, size_t size) override;
};
+157
View File
@@ -0,0 +1,157 @@
#pragma once
#ifdef ETHERNET_ENABLED
#include <Arduino.h>
#include <SPI.h>
#include <RAK13800_W5100S.h>
#include <helpers/nrf52/EthernetMac.h>
#define PIN_SPI1_MISO (29)
#define PIN_SPI1_MOSI (30)
#define PIN_SPI1_SCK (3)
static SPIClass ETHERNET_SPI_PORT(NRF_SPIM1, PIN_SPI1_MISO, PIN_SPI1_SCK, PIN_SPI1_MOSI);
#define PIN_ETHERNET_POWER_EN WB_IO2
#define PIN_ETHERNET_RESET 21
#define PIN_ETHERNET_SS 26
#ifndef ETHERNET_TCP_PORT
#define ETHERNET_TCP_PORT 23 // telnet port for CLI access
#endif
#ifndef ETHERNET_CLI_BANNER
#define ETHERNET_CLI_BANNER "MeshCore CLI"
#endif
#define ETHERNET_RETRY_INTERVAL_MS 30000
static EthernetServer ethernet_server(ETHERNET_TCP_PORT);
static EthernetClient ethernet_client;
static volatile bool ethernet_running = false;
// FreeRTOS task: handles hw init, DHCP, and retries in the background
static void ethernet_task(void* param) {
(void)param;
Serial.println("ETH: Initializing hardware");
// WB_IO2 (power enable) is already driven HIGH by early constructor
// in RAK4631Board.cpp to support POE boot.
// Skip hardware reset — the W5100S comes out of power-on reset cleanly,
// and toggling reset kills the PHY link which breaks POE power.
pinMode(PIN_ETHERNET_RESET, OUTPUT);
digitalWrite(PIN_ETHERNET_RESET, HIGH);
ETHERNET_SPI_PORT.begin();
Ethernet.init(ETHERNET_SPI_PORT, PIN_ETHERNET_SS);
uint8_t mac[6];
generateEthernetMac(mac);
Serial.printf("ETH: MAC: %02X:%02X:%02X:%02X:%02X:%02X\n",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
// Retry loop: keep trying until we get an IP
while (!ethernet_running) {
Serial.println("ETH: Attempting DHCP...");
if (Ethernet.begin(mac, 10000, 2000) == 0) {
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
Serial.println("ETH: Hardware not found, giving up");
vTaskDelete(NULL);
return;
}
if (Ethernet.linkStatus() == LinkOFF) {
Serial.println("ETH: Cable not connected, will retry");
} else {
Serial.println("ETH: DHCP failed, will retry");
}
vTaskDelay(pdMS_TO_TICKS(ETHERNET_RETRY_INTERVAL_MS));
continue;
}
IPAddress ip = Ethernet.localIP();
Serial.printf("ETH: IP: %u.%u.%u.%u\n", ip[0], ip[1], ip[2], ip[3]);
Serial.printf("ETH: Listening on TCP port %d\n", ETHERNET_TCP_PORT);
ethernet_server.begin();
ethernet_running = true;
}
// DHCP succeeded, task is done
vTaskDelete(NULL);
}
static void ethernet_start_task() {
xTaskCreate(ethernet_task, "eth_init", 1024, NULL, 1, NULL);
}
// Format ethernet status into reply buffer. Returns true if command was handled.
static bool ethernet_handle_command(const char* command, char* reply) {
if (strcmp(command, "eth.status") == 0) {
if (!ethernet_running) {
strcpy(reply, "ETH: not connected");
} else {
IPAddress ip = Ethernet.localIP();
sprintf(reply, "ETH: %u.%u.%u.%u:%d", ip[0], ip[1], ip[2], ip[3], ETHERNET_TCP_PORT);
}
return true;
}
return false;
}
// Check for new TCP client connections, replacing any existing connection.
// Use accept() (not available()) so we only see newly-accepted sockets;
// available() also returns existing connected sockets that have data, which
// would force us to disambiguate every inbound packet from a real new client.
static void ethernet_check_client() {
auto newClient = ethernet_server.accept();
if (newClient) {
if (ethernet_client) ethernet_client.stop();
ethernet_client = newClient;
IPAddress ip = ethernet_client.remoteIP();
Serial.printf("ETH: Client connected from %u.%u.%u.%u\n", ip[0], ip[1], ip[2], ip[3]);
ethernet_client.println(ETHERNET_CLI_BANNER);
}
}
// Call from loop() to maintain DHCP and check for new clients
static void ethernet_loop_maintain() {
if (ethernet_running) {
ethernet_check_client();
Ethernet.maintain();
}
}
// Read a line from the Ethernet client into the command buffer.
// Returns true when a complete line is ready to process (command is null-terminated).
// The caller should process the command and then reset ethernet_command[0] = 0.
static bool ethernet_read_line(char* ethernet_command, size_t buf_size) {
if (!ethernet_running || !ethernet_client || !ethernet_client.connected()) return false;
int elen = strlen(ethernet_command);
while (ethernet_client.available() && elen < (int)buf_size - 1) {
char c = ethernet_client.read();
if (c == '\n' && elen == 0) continue; // ignore leading LF (from CR+LF)
if (c == '\r' || c == '\n') { ethernet_command[elen++] = '\r'; break; }
ethernet_command[elen++] = c;
ethernet_command[elen] = 0;
}
if (elen == (int)buf_size - 1) {
ethernet_command[buf_size - 1] = '\r';
}
if (elen > 0 && ethernet_command[elen - 1] == '\r') {
ethernet_command[elen - 1] = 0;
ethernet_client.println();
return true;
}
return false;
}
// Send a reply to the Ethernet client
static void ethernet_send_reply(const char* reply) {
if (reply[0]) {
ethernet_client.print(" -> "); ethernet_client.println(reply);
}
}
#endif // ETHERNET_ENABLED
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include <Arduino.h>
static inline void generateEthernetMac(uint8_t mac[6]) {
uint32_t device_id = NRF_FICR->DEVICEID[0];
mac[0] = 0x02;
mac[1] = 0x92;
mac[2] = 0x1F;
mac[3] = (device_id >> 16) & 0xFF;
mac[4] = (device_id >> 8) & 0xFF;
mac[5] = device_id & 0xFF;
}
+63 -10
View File
@@ -2,10 +2,12 @@
#include <RadioLib.h>
#define SX126X_IRQ_HEADER_VALID 0b0000010000 // 4 4 valid LoRa header received
#define SX126X_IRQ_PREAMBLE_DETECTED 0x04
class CustomLLCC68 : public LLCC68 {
uint32_t _preambleMillis = 66;
uint32_t _maxPayloadMillis = 3934;
uint32_t _activityAt = 0;
bool _headerSeen = false;
public:
CustomLLCC68(Module *mod) : LLCC68(mod) { }
@@ -66,11 +68,11 @@ class CustomLLCC68 : public LLCC68 {
setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN);
#endif
#if defined(SX126X_RXEN) || defined(SX126X_TXEN)
#ifndef SX1262X_RXEN
#define SX1262X_RXEN RADIOLIB_NC
#ifndef SX126X_RXEN
#define SX126X_RXEN RADIOLIB_NC
#endif
#ifndef SX1262X_TXEN
#define SX1262X_TXEN RADIOLIB_NC
#ifndef SX126X_TXEN
#define SX126X_TXEN RADIOLIB_NC
#endif
setRfSwitchPins(SX126X_RXEN, SX126X_TXEN);
#endif
@@ -78,10 +80,61 @@ class CustomLLCC68 : public LLCC68 {
return true; // success
}
int16_t startReceive() override {
// include the PREAMBLE_DETECTED irq bit in reported flags
return LLCC68::startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF, RADIOLIB_IRQ_RX_DEFAULT_FLAGS | (1UL << RADIOLIB_IRQ_PREAMBLE_DETECTED), RADIOLIB_IRQ_RX_DEFAULT_MASK, 0);
}
bool isReceiving() {
uint16_t irq = getIrqFlags();
bool detected = (irq & SX126X_IRQ_HEADER_VALID) || (irq & SX126X_IRQ_PREAMBLE_DETECTED);
return detected;
uint32_t irq = getIrqFlags();
bool preamble = irq & RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED; // bit 2
bool header = irq & RADIOLIB_SX126X_IRQ_HEADER_VALID; // bit 4
bool hdrErr = irq & RADIOLIB_SX126X_IRQ_HEADER_ERR; // bit 5
uint32_t now = millis();
if (hdrErr) {
clearIrqFlags(RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED | RADIOLIB_SX126X_IRQ_HEADER_VALID | RADIOLIB_SX126X_IRQ_HEADER_ERR | RADIOLIB_SX126X_IRQ_SYNC_WORD_VALID);
_activityAt = 0;
_headerSeen = false;
return false;
}
if (!header && _headerSeen) {
// something cleared the header flag, reset our state.
_activityAt = 0; _headerSeen = false;
return false;
}
if (header) {
if (!_headerSeen) { _headerSeen = true; _activityAt = now; };
if (now - _activityAt > _maxPayloadMillis) {
MESH_DEBUG_PRINTLN("Clearing header IRQ after %ums", _maxPayloadMillis);
clearIrqFlags(RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED | RADIOLIB_SX126X_IRQ_HEADER_VALID | RADIOLIB_SX126X_IRQ_HEADER_ERR | RADIOLIB_SX126X_IRQ_SYNC_WORD_VALID);
_activityAt = 0; _headerSeen = false;
return false;
}
return true;
}
if (preamble) {
if (_activityAt == 0) _activityAt = now;
if (now - _activityAt > _preambleMillis) {
clearIrqFlags(RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED);
_activityAt = 0;
MESH_DEBUG_PRINTLN("Clearing preamble IRQ after %ums", _preambleMillis);
return false;
}
return true;
}
_activityAt = 0; _headerSeen = false;
return false;
}
void setPreambleMillis(uint32_t preambleMillis) {
_preambleMillis = preambleMillis;
MESH_DEBUG_PRINTLN("Set _preambleMillis=%u", _preambleMillis);
}
void setMaxPayloadMillis(uint32_t payloadMillis) {
_maxPayloadMillis = payloadMillis;
MESH_DEBUG_PRINTLN("Set _maxPayloadMillis=%u", _maxPayloadMillis);
}
bool getRxBoostedGainMode() {
+6 -2
View File
@@ -14,6 +14,10 @@ public:
((CustomLLCC68 *)_radio)->setBandwidth(bw);
((CustomLLCC68 *)_radio)->setCodingRate(cr);
updatePreamble(sf);
PacketMillis pm = calcMaxPacketMillis(sf, bw, cr, preambleLengthForSF(sf));
((CustomLLCC68 *)_radio)->setPreambleMillis(pm.preambleMillis);
((CustomLLCC68 *)_radio)->setMaxPayloadMillis(pm.payloadMillis);
}
bool isReceivingPacket() override {
@@ -33,8 +37,8 @@ public:
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
void setRxBoostedGainMode(bool en) override {
((CustomLLCC68 *)_radio)->setRxBoostedGainMode(en);
bool setRxBoostedGainMode(bool en) override {
return ((CustomLLCC68 *)_radio)->setRxBoostedGainMode(en) == RADIOLIB_ERR_NONE;
}
bool getRxBoostedGainMode() const override {
return ((CustomLLCC68 *)_radio)->getRxBoostedGainMode();
+58 -4
View File
@@ -4,6 +4,10 @@
#include "MeshCore.h"
class CustomLR1110 : public LR1110 {
uint32_t _preambleMillis = 66;
uint32_t _maxPayloadMillis = 3934;
uint32_t _activityAt = 0;
bool _headerSeen = false;
bool _rx_boosted = false;
public:
@@ -31,11 +35,61 @@ class CustomLR1110 : public LR1110 {
bool getRxBoostedGainMode() const { return _rx_boosted; }
int16_t startReceive() override {
// include the PREAMBLE_DETECTED irq bit in reported flags.
return LR1110::startReceive(RADIOLIB_LR11X0_RX_TIMEOUT_INF, RADIOLIB_IRQ_RX_DEFAULT_FLAGS | (1UL << RADIOLIB_IRQ_PREAMBLE_DETECTED), RADIOLIB_IRQ_RX_DEFAULT_MASK, 0);
}
bool isReceiving() {
uint16_t irq = getIrqStatus();
bool detected = ((irq & RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID) || (irq & RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED));
return detected;
uint32_t irq = getIrqStatus();
bool preamble = irq & RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED; // bit 4
bool header = irq & RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID; // bit 5
bool hdrErr = irq & RADIOLIB_LR11X0_IRQ_HEADER_ERR; // bit 6
uint32_t now = millis();
if (hdrErr) {
clearIrqState(RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED | RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID | RADIOLIB_LR11X0_IRQ_HEADER_ERR);
_activityAt = 0;
_headerSeen = false;
return false;
}
if (!header && _headerSeen) {
// something cleared the header flag, reset our state.
_activityAt = 0; _headerSeen = false;
return false;
}
if (header) {
if (!_headerSeen) { _headerSeen = true; _activityAt = now; };
if (now - _activityAt > _maxPayloadMillis) {
MESH_DEBUG_PRINTLN("Clearing header IRQ after %ums", _maxPayloadMillis);
clearIrqState(RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED | RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID | RADIOLIB_LR11X0_IRQ_HEADER_ERR);
_activityAt = 0; _headerSeen = false;
return false;
}
return true;
}
if (preamble) {
if (_activityAt == 0) _activityAt = now;
if (now - _activityAt > _preambleMillis) {
clearIrqState(RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED);
_activityAt = 0;
MESH_DEBUG_PRINTLN("Clearing preamble IRQ after %ums", _preambleMillis);
return false;
}
return true;
}
_activityAt = 0; _headerSeen = false;
return false;
}
void setPreambleMillis(uint32_t preambleMillis) {
_preambleMillis = preambleMillis;
MESH_DEBUG_PRINTLN("Set _preambleMillis=%u", _preambleMillis);
}
void setMaxPayloadMillis(uint32_t payloadMillis) {
_maxPayloadMillis = payloadMillis;
MESH_DEBUG_PRINTLN("Set _maxPayloadMillis=%u", _maxPayloadMillis);
}
uint8_t getSpreadingFactor() const { return spreadingFactor; }
};
};
+10 -2
View File
@@ -14,6 +14,9 @@ public:
((CustomLR1110 *)_radio)->setBandwidth(bw);
((CustomLR1110 *)_radio)->setCodingRate(cr);
updatePreamble(sf);
PacketMillis pm = calcMaxPacketMillis(sf, bw, cr, preambleLengthForSF(sf));
((CustomLR1110 *)_radio)->setPreambleMillis(pm.preambleMillis);
((CustomLR1110 *)_radio)->setMaxPayloadMillis(pm.payloadMillis);
}
void doResetAGC() override { lr11x0ResetAGC((LR11x0 *)_radio, ((CustomLR1110 *)_radio)->getFreqMHz()); }
@@ -26,6 +29,11 @@ public:
return rssi;
}
uint32_t getEstAirtimeFor(int len_bytes) override {
auto airtime = RadioLibWrapper::getEstAirtimeFor(len_bytes);
return airtime < 200 ? 200 : airtime; // at least 200 millis
}
void onSendFinished() override {
RadioLibWrapper::onSendFinished();
_radio->setPreambleLength(preambleLengthForSF(getSpreadingFactor())); // overcomes weird issues with small and big pkts
@@ -36,8 +44,8 @@ public:
uint8_t getSpreadingFactor() const override { return ((CustomLR1110 *)_radio)->getSpreadingFactor(); }
void setRxBoostedGainMode(bool en) override {
((CustomLR1110 *)_radio)->setRxBoostedGainMode(en);
bool setRxBoostedGainMode(bool en) override {
return ((CustomLR1110 *)_radio)->setRxBoostedGainMode(en) == RADIOLIB_ERR_NONE;
}
bool getRxBoostedGainMode() const override {
return ((CustomLR1110 *)_radio)->getRxBoostedGainMode();
+76
View File
@@ -0,0 +1,76 @@
#pragma once
#include <RadioLib.h>
#include "MeshCore.h"
class CustomLR2021 : public LR2021 {
bool _rx_boosted = false;
public:
CustomLR2021(Module *mod) : LR2021(mod) { irqDioNum = LR2021_IRQ_DIO; }
bool std_init(SPIClass* spi = NULL)
{
#ifdef LR2021_TCXO_VOLTAGE
float tcxo = LR2021_TCXO_VOLTAGE;
#else
float tcxo = 1.6f;
#endif
#ifdef LORA_CR
uint8_t cr = LORA_CR;
#else
uint8_t cr = 5;
#endif
#if defined(P_LORA_SCLK)
#ifdef NRF52_PLATFORM
if (spi) { spi->setPins(P_LORA_MISO, P_LORA_SCLK, P_LORA_MOSI); spi->begin(); }
#elif defined(RP2040_PLATFORM)
if (spi) {
spi->setMISO(P_LORA_MISO);
//spi->setCS(P_LORA_NSS); // Setting CS results in freeze
spi->setSCK(P_LORA_SCLK);
spi->setMOSI(P_LORA_MOSI);
spi->begin();
}
#else
if (spi) spi->begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI);
#endif
#endif
int status = begin(LORA_FREQ, LORA_BW, LORA_SF, cr, RADIOLIB_LR2021_LORA_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo);
// if radio init fails with -707/-706, try again with tcxo voltage set to 0.0f
if (status == RADIOLIB_ERR_SPI_CMD_FAILED || status == RADIOLIB_ERR_SPI_CMD_INVALID) {
tcxo = 0.0f;
status = begin(LORA_FREQ, LORA_BW, LORA_SF, cr, RADIOLIB_LR2021_LORA_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo);
}
if (status != RADIOLIB_ERR_NONE) {
Serial.print("ERROR: radio init failed: ");
Serial.println(status);
return false; // fail
}
setCRC(2);
explicitHeader();
#ifdef LR2021_RX_BOOSTED_GAIN
setRxBoostedGainMode(LR2021_RX_BOOSTED_GAIN);
#endif
return true; // success
}
float getFreqMHz() const { return freqMHz; }
bool getRxBoostedGainMode() const { return _rx_boosted; }
bool isReceiving() {
uint32_t irq = getIrqStatus();
bool detected = ((irq & RADIOLIB_LR2021_IRQ_SYNCWORD_VALID) || (irq & RADIOLIB_LR2021_IRQ_PREAMBLE_DETECTED));
return detected;
}
uint8_t getSpreadingFactor() const { return spreadingFactor; }
};
+107
View File
@@ -0,0 +1,107 @@
#pragma once
#include "CustomLR2021.h"
#include "RadioLibWrappers.h"
#ifndef USE_LR2021
#define USE_LR2021
#endif
#ifndef LR2021_RX_BOOST_LEVEL
#define LR2021_RX_BOOST_LEVEL 7
#endif
class CustomLR2021Wrapper : public RadioLibWrapper {
public:
CustomLR2021Wrapper(CustomLR2021& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { }
void setParams(float freq, float bw, uint8_t sf, uint8_t cr) override {
((CustomLR2021 *)_radio)->setFrequency(freq);
((CustomLR2021 *)_radio)->setSpreadingFactor(sf);
((CustomLR2021 *)_radio)->setBandwidth(bw);
((CustomLR2021 *)_radio)->setCodingRate(cr);
updatePreamble(sf);
applySideDetectorConfig();
}
bool configSideDetectors(const uint8_t* sideDetSFs, uint8_t num, float bw) override {
LR2021LoRaSideDetector_t tmp[3];
uint8_t sf = getSpreadingFactor();
if (sf >= 10 && num > 1) { return false; } // only 1 side detector allowed when primary SF >= 10
for (int i = 0; i < num; i++) {
if (sideDetSFs[i] > 12 || sideDetSFs[i] < 5) { return false; } // must be valid SF
if (sideDetSFs[i] <= sf) { return false; } // must be < primary SF
if (sideDetSFs[i] > sf + 4) { return false; } // span must not be > 4
tmp[i].sf = sideDetSFs[i];
float tSym = calcTsym(tmp[i].sf, bw);
if (tSym >= 16.0f) {
tmp[i].ldro = true;
} else {
tmp[i].ldro = false;
}
tmp[i].invertIQ = false;
tmp[i].syncWord = 0x12;
}
int16_t status = ((CustomLR2021 *)_radio)->setSideDetector(tmp, num);
RadioLibWrapper::idle(); // trigger startReceive()
MESH_DEBUG_PRINTLN("setSideDetector() returned %d", status);
if (status == RADIOLIB_ERR_NONE) {
for (int i = 0; i < num; i++) { _sideDet[i] = tmp[i]; }
_numSideDet = num;
} else {
return false;
}
return true;
}
int16_t applySideDetectorConfig() {
int16_t status = ((CustomLR2021 *)_radio)->setSideDetector(_sideDet, _numSideDet);
RadioLibWrapper::idle(); // trigger startReceive()
return status;
}
float calcTsym(uint8_t sf, float bw) {
float tSym = (float)(uint32_t(1) << sf) / (float)bw;
return tSym;
}
bool isReceivingPacket() override {
return ((CustomLR2021 *)_radio)->isReceiving();
}
float getCurrentRSSI() override {
float rssi = -110;
((CustomLR2021 *)_radio)->getRssiInst(&rssi);
return rssi;
}
void onSendFinished() override {
RadioLibWrapper::onSendFinished();
_radio->setPreambleLength(preambleLengthForSF(getSpreadingFactor())); // overcomes weird issues with small and big pkts
}
float getLastRSSI() const override { return ((CustomLR2021 *)_radio)->getRSSI(); }
float getLastSNR() const override { return ((CustomLR2021 *)_radio)->getSNR(); }
uint8_t getSpreadingFactor() const override { return ((CustomLR2021 *)_radio)->getSpreadingFactor(); }
bool setRxBoostedGainMode(bool en) override {
((CustomLR2021 *)_radio)->standby(); // LR2021 must be in standby to accept setRxBoostedGainMode
int16_t status = ((CustomLR2021 *)_radio)->setRxBoostedGainMode(en ? LR2021_RX_BOOST_LEVEL: 0);
RadioLibWrapper::idle(); // trigger startReceive()
return status == RADIOLIB_ERR_NONE;
}
bool getRxBoostedGainMode() const override {
return ((CustomLR2021 *)_radio)->getRxBoostedGainMode();
}
protected:
LR2021LoRaSideDetector_t _sideDet[3];
size_t _numSideDet = 0;
};
+61 -7
View File
@@ -2,16 +2,70 @@
#include <RadioLib.h>
#define SX126X_IRQ_HEADER_VALID 0b0000010000 // 4 4 valid LoRa header received
#define SX126X_IRQ_PREAMBLE_DETECTED 0x04
class CustomSTM32WLx : public STM32WLx {
uint32_t _preambleMillis = 66;
uint32_t _maxPayloadMillis = 3934;
uint32_t _activityAt = 0;
bool _headerSeen = false;
public:
CustomSTM32WLx(STM32WLx_Module *mod) : STM32WLx(mod) { }
bool isReceiving() {
uint16_t irq = getIrqFlags();
bool detected = (irq & SX126X_IRQ_HEADER_VALID) || (irq & SX126X_IRQ_PREAMBLE_DETECTED);
return detected;
int16_t startReceive() override {
// include the PREAMBLE_DETECTED irq bit in reported flags
return STM32WLx::startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF, RADIOLIB_IRQ_RX_DEFAULT_FLAGS | (1UL << RADIOLIB_IRQ_PREAMBLE_DETECTED), RADIOLIB_IRQ_RX_DEFAULT_MASK, 0);
}
bool isReceiving() {
uint32_t irq = getIrqFlags();
bool preamble = irq & RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED; // bit 2
bool header = irq & RADIOLIB_SX126X_IRQ_HEADER_VALID; // bit 4
bool hdrErr = irq & RADIOLIB_SX126X_IRQ_HEADER_ERR; // bit 5
uint32_t now = millis();
if (hdrErr) {
clearIrqFlags(RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED | RADIOLIB_SX126X_IRQ_HEADER_VALID | RADIOLIB_SX126X_IRQ_HEADER_ERR | RADIOLIB_SX126X_IRQ_SYNC_WORD_VALID);
_activityAt = 0;
_headerSeen = false;
return false;
}
if (!header && _headerSeen) {
// something cleared the header flag, reset our state.
_activityAt = 0; _headerSeen = false;
return false;
}
if (header) {
if (!_headerSeen) { _headerSeen = true; _activityAt = now; };
if (now - _activityAt > _maxPayloadMillis) {
MESH_DEBUG_PRINTLN("Clearing header IRQ after %ums", _maxPayloadMillis);
clearIrqFlags(RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED | RADIOLIB_SX126X_IRQ_HEADER_VALID | RADIOLIB_SX126X_IRQ_HEADER_ERR | RADIOLIB_SX126X_IRQ_SYNC_WORD_VALID);
_activityAt = 0; _headerSeen = false;
return false;
}
return true;
}
if (preamble) {
if (_activityAt == 0) _activityAt = now;
if (now - _activityAt > _preambleMillis) {
clearIrqFlags(RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED);
_activityAt = 0;
MESH_DEBUG_PRINTLN("Clearing preamble IRQ after %ums", _preambleMillis);
return false;
}
return true;
}
_activityAt = 0; _headerSeen = false;
return false;
}
void setPreambleMillis(uint32_t preambleMillis) {
_preambleMillis = preambleMillis;
MESH_DEBUG_PRINTLN("Set _preambleMillis=%u", _preambleMillis);
}
void setMaxPayloadMillis(uint32_t payloadMillis) {
_maxPayloadMillis = payloadMillis;
MESH_DEBUG_PRINTLN("Set _maxPayloadMillis=%u", _maxPayloadMillis);
}
};
@@ -15,6 +15,9 @@ public:
((CustomSTM32WLx *)_radio)->setBandwidth(bw);
((CustomSTM32WLx *)_radio)->setCodingRate(cr);
updatePreamble(sf);
PacketMillis pm = calcMaxPacketMillis(sf, bw, cr, preambleLengthForSF(sf));
((CustomSTM32WLx *)_radio)->setPreambleMillis(pm.preambleMillis);
((CustomSTM32WLx *)_radio)->setMaxPayloadMillis(pm.payloadMillis);
}
bool isReceivingPacket() override {
+73 -8
View File
@@ -1,11 +1,14 @@
#pragma once
#include <RadioLib.h>
#define SX126X_IRQ_HEADER_VALID 0b0000010000 // 4 4 valid LoRa header received
#define SX126X_IRQ_PREAMBLE_DETECTED 0x04
#include "MeshCore.h"
class CustomSX1262 : public SX1262 {
uint32_t _preambleMillis = 66;
uint32_t _maxPayloadMillis = 3934;
uint32_t _activityAt = 0;
bool _headerSeen = false;
public:
CustomSX1262(Module *mod) : SX1262(mod) { }
@@ -27,6 +30,14 @@ class CustomSX1262 : public SX1262 {
uint8_t cr = 5;
#endif
#ifdef SX126X_USE_REGULATOR_LDO
constexpr bool useRegulatorLDO = SX126X_USE_REGULATOR_LDO;
#else
constexpr bool useRegulatorLDO = false;
#endif
MESH_DEBUG_PRINTLN("SX1262 regulator requested: %s", useRegulatorLDO ? "LDO" : "DC-DC");
#if defined(P_LORA_SCLK)
#ifdef NRF52_PLATFORM
if (spi) { spi->setPins(P_LORA_MISO, P_LORA_SCLK, P_LORA_MOSI); spi->begin(); }
@@ -42,11 +53,12 @@ class CustomSX1262 : public SX1262 {
if (spi) spi->begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI);
#endif
#endif
int status = begin(LORA_FREQ, LORA_BW, LORA_SF, cr, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo);
int status = begin(LORA_FREQ, LORA_BW, LORA_SF, cr, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo, useRegulatorLDO);
// if radio init fails with -707/-706, try again with tcxo voltage set to 0.0f
if (status == RADIOLIB_ERR_SPI_CMD_FAILED || status == RADIOLIB_ERR_SPI_CMD_INVALID) {
MESH_DEBUG_PRINTLN("SX1262 init failed with error %d, retrying with TCXO at 0.0V", status);
tcxo = 0.0f;
status = begin(LORA_FREQ, LORA_BW, LORA_SF, cr, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo);
status = begin(LORA_FREQ, LORA_BW, LORA_SF, cr, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo, useRegulatorLDO);
}
if (status != RADIOLIB_ERR_NONE) {
Serial.print("ERROR: radio init failed: ");
@@ -83,13 +95,66 @@ class CustomSX1262 : public SX1262 {
writeRegister(0x8B5, &r_data, 1);
#endif
MESH_DEBUG_PRINTLN("SX1262 status=0x%02X device_errors=0x%04X", getStatus(), getDeviceErrors());
return true; // success
}
int16_t startReceive() override {
// include the PREAMBLE_DETECTED irq bit in reported flags
return SX1262::startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF, RADIOLIB_IRQ_RX_DEFAULT_FLAGS | (1UL << RADIOLIB_IRQ_PREAMBLE_DETECTED), RADIOLIB_IRQ_RX_DEFAULT_MASK, 0);
}
bool isReceiving() {
uint16_t irq = getIrqFlags();
bool detected = (irq & SX126X_IRQ_HEADER_VALID) || (irq & SX126X_IRQ_PREAMBLE_DETECTED);
return detected;
uint32_t irq = getIrqFlags();
bool preamble = irq & RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED; // bit 2
bool header = irq & RADIOLIB_SX126X_IRQ_HEADER_VALID; // bit 4
bool hdrErr = irq & RADIOLIB_SX126X_IRQ_HEADER_ERR; // bit 5
uint32_t now = millis();
if (hdrErr) {
clearIrqFlags(RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED | RADIOLIB_SX126X_IRQ_HEADER_VALID | RADIOLIB_SX126X_IRQ_HEADER_ERR | RADIOLIB_SX126X_IRQ_SYNC_WORD_VALID);
_activityAt = 0;
_headerSeen = false;
return false;
}
if (!header && _headerSeen) {
// something cleared the header flag, reset our state.
_activityAt = 0; _headerSeen = false;
return false;
}
if (header) {
if (!_headerSeen) { _headerSeen = true; _activityAt = now; };
if (now - _activityAt > _maxPayloadMillis) {
MESH_DEBUG_PRINTLN("Clearing header IRQ after %ums", _maxPayloadMillis);
clearIrqFlags(RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED | RADIOLIB_SX126X_IRQ_HEADER_VALID | RADIOLIB_SX126X_IRQ_HEADER_ERR | RADIOLIB_SX126X_IRQ_SYNC_WORD_VALID);
_activityAt = 0; _headerSeen = false;
return false;
}
return true;
}
if (preamble) {
if (_activityAt == 0) _activityAt = now;
if (now - _activityAt > _preambleMillis) {
clearIrqFlags(RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED);
_activityAt = 0;
MESH_DEBUG_PRINTLN("Clearing preamble IRQ after %ums", _preambleMillis);
return false;
}
return true;
}
_activityAt = 0; _headerSeen = false;
return false;
}
void setPreambleMillis(uint32_t preambleMillis) {
_preambleMillis = preambleMillis;
MESH_DEBUG_PRINTLN("Set _preambleMillis=%u", _preambleMillis);
}
void setMaxPayloadMillis(uint32_t payloadMillis) {
_maxPayloadMillis = payloadMillis;
MESH_DEBUG_PRINTLN("Set _maxPayloadMillis=%u", _maxPayloadMillis);
}
bool getRxBoostedGainMode() {
+5 -2
View File
@@ -28,6 +28,9 @@ public:
((CustomSX1262 *)_radio)->setBandwidth(bw);
((CustomSX1262 *)_radio)->setCodingRate(cr);
updatePreamble(sf);
PacketMillis pm = calcMaxPacketMillis(sf, bw, cr, preambleLengthForSF(sf));
((CustomSX1262 *)_radio)->setPreambleMillis(pm.preambleMillis);
((CustomSX1262 *)_radio)->setMaxPayloadMillis(pm.payloadMillis);
}
// From RadioLib's SX1262::setFrequency(): RADIOLIB_CHECK_RANGE(freq, 150.0f, 960.0f, ...).
@@ -64,9 +67,9 @@ public:
return ((SX126x *)_radio)->startReceiveDutyCycleAuto(preambleLengthForSF(_preamble_sf), 8);
}
void setRxBoostedGainMode(bool en) override {
bool setRxBoostedGainMode(bool en) override {
_wd_rx_boosted_gain = en;
((CustomSX1262 *)_radio)->setRxBoostedGainMode(en);
return ((CustomSX1262 *)_radio)->setRxBoostedGainMode(en) == RADIOLIB_ERR_NONE;
}
bool getRxBoostedGainMode() const override {
return ((CustomSX1262 *)_radio)->getRxBoostedGainMode();
+65 -11
View File
@@ -2,10 +2,12 @@
#include <RadioLib.h>
#define SX126X_IRQ_HEADER_VALID 0b0000010000 // 4 4 valid LoRa header received
#define SX126X_IRQ_PREAMBLE_DETECTED 0x04
class CustomSX1268 : public SX1268 {
uint32_t _preambleMillis = 66;
uint32_t _maxPayloadMillis = 3934;
uint32_t _activityAt = 0;
bool _headerSeen = false;
public:
CustomSX1268(Module *mod) : SX1268(mod) { }
@@ -66,11 +68,11 @@ class CustomSX1268 : public SX1268 {
setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN);
#endif
#if defined(SX126X_RXEN) || defined(SX126X_TXEN)
#ifndef SX1262X_RXEN
#define SX1262X_RXEN RADIOLIB_NC
#ifndef SX126X_RXEN
#define SX126X_RXEN RADIOLIB_NC
#endif
#ifndef SX1262X_TXEN
#define SX1262X_TXEN RADIOLIB_NC
#ifndef SX126X_TXEN
#define SX126X_TXEN RADIOLIB_NC
#endif
setRfSwitchPins(SX126X_RXEN, SX126X_TXEN);
#endif
@@ -78,12 +80,64 @@ class CustomSX1268 : public SX1268 {
return true; // success
}
bool isReceiving() {
uint16_t irq = getIrqFlags();
bool detected = (irq & SX126X_IRQ_HEADER_VALID) || (irq & SX126X_IRQ_PREAMBLE_DETECTED);
return detected;
int16_t startReceive() override {
// include the PREAMBLE_DETECTED irq bit in reported flags
return SX1268::startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF, RADIOLIB_IRQ_RX_DEFAULT_FLAGS | (1UL << RADIOLIB_IRQ_PREAMBLE_DETECTED), RADIOLIB_IRQ_RX_DEFAULT_MASK, 0);
}
bool isReceiving() {
uint32_t irq = getIrqFlags();
bool preamble = irq & RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED; // bit 2
bool header = irq & RADIOLIB_SX126X_IRQ_HEADER_VALID; // bit 4
bool hdrErr = irq & RADIOLIB_SX126X_IRQ_HEADER_ERR; // bit 5
uint32_t now = millis();
if (hdrErr) {
clearIrqFlags(RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED | RADIOLIB_SX126X_IRQ_HEADER_VALID | RADIOLIB_SX126X_IRQ_HEADER_ERR | RADIOLIB_SX126X_IRQ_SYNC_WORD_VALID);
_activityAt = 0;
_headerSeen = false;
return false;
}
if (!header && _headerSeen) {
// something cleared the header flag, reset our state.
_activityAt = 0; _headerSeen = false;
return false;
}
if (header) {
if (!_headerSeen) { _headerSeen = true; _activityAt = now; };
if (now - _activityAt > _maxPayloadMillis) {
MESH_DEBUG_PRINTLN("Clearing header IRQ after %ums", _maxPayloadMillis);
clearIrqFlags(RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED | RADIOLIB_SX126X_IRQ_HEADER_VALID | RADIOLIB_SX126X_IRQ_HEADER_ERR | RADIOLIB_SX126X_IRQ_SYNC_WORD_VALID);
_activityAt = 0; _headerSeen = false;
return false;
}
return true;
}
if (preamble) {
if (_activityAt == 0) _activityAt = now;
if (now - _activityAt > _preambleMillis) {
clearIrqFlags(RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED);
_activityAt = 0;
MESH_DEBUG_PRINTLN("Clearing preamble IRQ after %ums", _preambleMillis);
return false;
}
return true;
}
_activityAt = 0; _headerSeen = false;
return false;
}
void setPreambleMillis(uint32_t preambleMillis) {
_preambleMillis = preambleMillis;
MESH_DEBUG_PRINTLN("Set _preambleMillis=%u", _preambleMillis);
}
void setMaxPayloadMillis(uint32_t payloadMillis) {
_maxPayloadMillis = payloadMillis;
MESH_DEBUG_PRINTLN("Set _maxPayloadMillis=%u", _maxPayloadMillis);
}
bool getRxBoostedGainMode() {
uint8_t rxGain = 0;
readRegister(RADIOLIB_SX126X_REG_RX_GAIN, &rxGain, 1);
+5 -2
View File
@@ -18,6 +18,9 @@ public:
((CustomSX1268 *)_radio)->setBandwidth(bw);
((CustomSX1268 *)_radio)->setCodingRate(cr);
updatePreamble(sf);
PacketMillis pm = calcMaxPacketMillis(sf, bw, cr, preambleLengthForSF(sf));
((CustomSX1268 *)_radio)->setPreambleMillis(pm.preambleMillis);
((CustomSX1268 *)_radio)->setMaxPayloadMillis(pm.payloadMillis);
}
bool isReceivingPacket() override {
@@ -37,8 +40,8 @@ public:
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
void setRxBoostedGainMode(bool en) override {
((CustomSX1268 *)_radio)->setRxBoostedGainMode(en);
bool setRxBoostedGainMode(bool en) override {
return ((CustomSX1268 *)_radio)->setRxBoostedGainMode(en) == RADIOLIB_ERR_NONE;
}
bool getRxBoostedGainMode() const override {
return ((CustomSX1268 *)_radio)->getRxBoostedGainMode();
+6 -6
View File
@@ -50,14 +50,14 @@ class CustomSX1276 : public SX1276 {
setCurrentLimit(SX127X_CURRENT_LIMIT);
#endif
#if defined(SX176X_RXEN) || defined(SX176X_TXEN)
#ifndef SX176X_RXEN
#define SX176X_RXEN RADIOLIB_NC
#if defined(SX127X_RXEN) || defined(SX127X_TXEN)
#ifndef SX127X_RXEN
#define SX127X_RXEN RADIOLIB_NC
#endif
#ifndef SX176X_TXEN
#define SX176X_TXEN RADIOLIB_NC
#ifndef SX127X_TXEN
#define SX127X_TXEN RADIOLIB_NC
#endif
setRfSwitchPins(SX176X_RXEN, SX176X_TXEN);
setRfSwitchPins(SX127X_RXEN, SX127X_TXEN);
#endif
setCRC(1);
+45 -3
View File
@@ -40,6 +40,7 @@ void RadioLibWrapper::begin() {
_noise_floor = 0;
_threshold = 0;
_cad_enabled = false;
// start average out some samples
_num_floor_samples = 0;
@@ -219,6 +220,9 @@ void RadioLibWrapper::armRecv() {
_power_save = false;
}
_ps_active = false;
#if defined(USE_LR2021)
_radio->standby(); // without this LR2021 can throw -706 when calling startReceive after hardware CAD when side detectors are enabled
#endif
int err = _radio->startReceive();
if (err == RADIOLIB_ERR_NONE) {
state = STATE_RX;
@@ -247,7 +251,11 @@ int RadioLibWrapper::recvRaw(uint8_t* bytes, int sz) {
n_recv++;
}
}
#if defined(USE_LR2021)
state = STATE_RX; // LR2021 stays in Rx after readData, calling startReceive while still in Rx throws -706 errors
#else
state = STATE_IDLE; // need another startReceive()
#endif
}
if (state != STATE_RX) {
@@ -288,10 +296,26 @@ void RadioLibWrapper::onSendFinished() {
state = STATE_IDLE;
}
int16_t RadioLibWrapper::performChannelScan() {
return _radio->scanChannel();
}
bool RadioLibWrapper::isChannelActive() {
return _threshold == 0
? false // interference check is disabled
: getCurrentRSSI() > _noise_floor + _threshold;
// int.thresh: RSSI-based interference detection (relative to noise floor)
if (_threshold != 0 && getCurrentRSSI() > _noise_floor + _threshold) return true;
// cad: hardware channel activity detection
if (_cad_enabled) {
int16_t result = performChannelScan();
// scanChannel() triggers DIO interrupt (CAD done) which sets STATE_INT_READY
// via setFlag() ISR. Clear it before restarting RX so recvRaw() doesn't
// try to read a non-existent packet and count a spurious recv error.
state = STATE_IDLE;
startRecv();
if (result != RADIOLIB_CHANNEL_FREE) return true;
}
return false;
}
float RadioLibWrapper::getLastRSSI() const {
@@ -312,3 +336,21 @@ float RadioLibWrapper::packetScoreInt(float snr, int sf, int packet_len) {
return max(0.0, min(1.0, success_rate_based_on_snr * collision_penalty));
}
PacketMillis RadioLibWrapper::calcMaxPacketMillis(uint8_t sf, float bw, uint8_t cr, uint8_t preambleSymbols) {
// based on RadioLib's calculateTimeOnAir()
uint32_t tsym_us = ((uint32_t)10000 << sf) / (bw * 10);
uint32_t sfCoeff1_x4 = (sf == 5 || sf == 6) ? 25 : 17; // 6.25 : 4.25, semtech magic numbers to account for sync word + sfd
// preamble + syncword + sfd + header
uint32_t preamble_us = (((preambleSymbols + 8) * 4 + sfCoeff1_x4) * tsym_us) / 4;
// airtime for max packet at current radio settings
uint32_t total_us = _radio->getTimeOnAir(MAX_TRANS_UNIT);
// airtime for payload only (no preamble, header or SOF)
uint32_t payload_us = total_us > preamble_us ? total_us - preamble_us : 4000 - preamble_us; // fallback to 4 secs at worst case
// rescale payload_us for max possible CR
if (cr >= 5 && cr < 8) { payload_us = (payload_us * 8) / cr; }
return PacketMillis {(preamble_us + 999) / 1000, (payload_us + 999) / 1000};
}
+22 -1
View File
@@ -3,12 +3,21 @@
#include <Mesh.h>
#include <RadioLib.h>
#ifdef USE_CC310_HW_CRYPTO
#include <Adafruit_nRFCrypto.h>
#endif
struct PacketMillis {
uint32_t preambleMillis; // preamble-detect -> header-valid deadline
uint32_t payloadMillis; // header-valid -> rx-done deadline
};
class RadioLibWrapper : public mesh::Radio {
protected:
PhysicalLayer* _radio;
mesh::MainBoard* _board;
uint32_t n_recv, n_sent, n_recv_errors;
int16_t _noise_floor, _threshold;
bool _cad_enabled;
uint16_t _num_floor_samples;
int32_t _floor_sample_sum;
uint8_t _preamble_sf;
@@ -118,9 +127,12 @@ public:
return -7.5f - 2.5f * (float)(sf - 7);
}
void updatePreamble(uint8_t sf) { _preamble_sf = sf; _radio->setPreambleLength(preambleLengthForSF(sf)); }
PacketMillis calcMaxPacketMillis(uint8_t sf, float bw, uint8_t cr, uint8_t preambleSymbols);
virtual int16_t performChannelScan();
int getNoiseFloor() const override { return _noise_floor; }
void triggerNoiseFloorCalibrate(int threshold) override;
void setCADEnabled(bool enable) override { _cad_enabled = enable; }
void resetAGC() override;
void loop() override;
@@ -137,8 +149,10 @@ public:
float packetScore(float snr, int packet_len) override { return packetScoreInt(snr, 10, packet_len); } // assume sf=10
virtual void setRxBoostedGainMode(bool) { }
virtual bool setRxBoostedGainMode(bool) { return false; }
virtual bool getRxBoostedGainMode() const { return false; }
virtual bool configSideDetectors(const uint8_t sideDetSFs[], uint8_t num, float bw) { return false; }
};
/**
@@ -151,8 +165,15 @@ public:
RadioNoiseListener(PhysicalLayer& radio): _radio(&radio) { }
void random(uint8_t* dest, size_t sz) override {
#ifdef USE_CC310_HW_CRYPTO
// CC310 TRNG is higher quality and environment-independent vs radio RSSI noise.
nRFCrypto.begin();
nRFCrypto.Random.generate(dest, (uint16_t)sz);
nRFCrypto.end();
#else
for (int i = 0; i < sz; i++) {
dest[i] = _radio->randomByte() ^ (::random(0, 256) & 0xFF);
}
#endif
}
};
@@ -818,6 +818,13 @@ void EnvironmentSensorManager::rakGPSInit(){
bool EnvironmentSensorManager::gpsIsAwake(uint8_t ioPin){
#if defined(ETHERNET_ENABLED) && defined(RAK_BOARD)
if (ioPin == WB_IO2) {
// WB_IO2 powers the Ethernet module on RAK baseboards.
return false;
}
#endif
//set initial waking state
pinMode(ioPin,OUTPUT);
digitalWrite(ioPin,LOW);
@@ -952,11 +959,11 @@ void EnvironmentSensorManager::loop() {
#if ENV_INCLUDE_GPS
gpsDutyCycleLoop();
static long next_gps_update = 0;
static unsigned long next_gps_update = 0;
if (gps_active) {
_location->loop();
}
if (millis() > next_gps_update) {
if ((long)(millis() - next_gps_update) > 0) {
if(gps_active){
#ifdef RAK_WISBLOCK_GPS
+1 -1
View File
@@ -158,7 +158,7 @@ public:
case LPP_GPS:
_pos += 9; break;
case LPP_POLYLINE:
_pos += 8; break; // TODO: this is MINIMIUM
_pos += 8; break; // TODO: this is MINIMUM
case LPP_GYROMETER:
case LPP_ACCELEROMETER:
_pos += 6; break;
+22 -20
View File
@@ -13,8 +13,12 @@
#endif
#endif
#ifndef PIN_GPS_EN_ACTIVE
#define PIN_GPS_EN_ACTIVE HIGH
#ifndef GPS_EN_ACTIVE
#ifdef PIN_GPS_EN_ACTIVE
#define GPS_EN_ACTIVE PIN_GPS_EN_ACTIVE
#else
#define GPS_EN_ACTIVE HIGH
#endif
#endif
#ifndef GPS_RESET
@@ -25,11 +29,11 @@
#endif
#endif
#ifndef GPS_RESET_FORCE
#ifndef GPS_RESET_ACTIVE
#ifdef PIN_GPS_RESET_ACTIVE
#define GPS_RESET_FORCE PIN_GPS_RESET_ACTIVE
#define GPS_RESET_ACTIVE PIN_GPS_RESET_ACTIVE
#else
#define GPS_RESET_FORCE LOW
#define GPS_RESET_ACTIVE LOW
#endif
#endif
@@ -42,29 +46,27 @@ class MicroNMEALocationProvider : public LocationProvider {
int8_t _claims = 0;
int _pin_reset;
int _pin_en;
long next_check = 0;
unsigned long next_check = 0;
long time_valid = 0;
unsigned long _last_time_sync = 0;
static const unsigned long TIME_SYNC_INTERVAL = 1800000; // Re-sync every 30 minutes
public :
MicroNMEALocationProvider(Stream& ser, mesh::RTCClock* clock = NULL, int pin_reset = GPS_RESET, int pin_en = GPS_EN,RefCountedDigitalPin* peripher_power=NULL) :
_gps_serial(&ser), nmea(_nmeaBuffer, sizeof(_nmeaBuffer)), _pin_reset(pin_reset), _pin_en(pin_en), _clock(clock), _peripher_power(peripher_power) {
nmea(_nmeaBuffer, sizeof(_nmeaBuffer)), _clock(clock), _gps_serial(&ser), _peripher_power(peripher_power), _pin_reset(pin_reset), _pin_en(pin_en) {
if (_pin_reset != -1) {
pinMode(_pin_reset, OUTPUT);
digitalWrite(_pin_reset, GPS_RESET_FORCE);
digitalWrite(_pin_reset, GPS_RESET_ACTIVE);
}
if (_pin_en != -1) {
pinMode(_pin_en, OUTPUT);
digitalWrite(_pin_en, LOW);
digitalWrite(_pin_en, !GPS_EN_ACTIVE);
}
}
void claim() {
_claims++;
if (_claims > 0) {
if (_peripher_power) _peripher_power->claim();
}
if (_peripher_power) _peripher_power->claim();
}
void release() {
@@ -76,27 +78,27 @@ public :
void begin() override {
claim();
if (_pin_en != -1) {
digitalWrite(_pin_en, PIN_GPS_EN_ACTIVE);
digitalWrite(_pin_en, GPS_EN_ACTIVE);
}
if (_pin_reset != -1) {
digitalWrite(_pin_reset, !GPS_RESET_FORCE);
digitalWrite(_pin_reset, !GPS_RESET_ACTIVE);
}
}
void reset() override {
if (_pin_reset != -1) {
digitalWrite(_pin_reset, GPS_RESET_FORCE);
digitalWrite(_pin_reset, GPS_RESET_ACTIVE);
delay(10);
digitalWrite(_pin_reset, !GPS_RESET_FORCE);
digitalWrite(_pin_reset, !GPS_RESET_ACTIVE);
}
}
void stop() override {
if (_pin_en != -1) {
digitalWrite(_pin_en, !PIN_GPS_EN_ACTIVE);
digitalWrite(_pin_en, !GPS_EN_ACTIVE);
}
if (_pin_reset != -1) {
digitalWrite(_pin_reset, GPS_RESET_FORCE);
digitalWrite(_pin_reset, GPS_RESET_ACTIVE);
}
release();
}
@@ -105,7 +107,7 @@ public :
// directly read the enable pin if present as gps can be
// activated/deactivated outside of here ...
if (_pin_en != -1) {
return digitalRead(_pin_en) == PIN_GPS_EN_ACTIVE;
return digitalRead(_pin_en) == GPS_EN_ACTIVE;
} else {
return true; // no enable so must be active
}
@@ -144,7 +146,7 @@ public :
if (!isValid()) time_valid = 0;
if (millis() > next_check) {
if ((long)(millis() - next_check) > 0) {
next_check = millis() + 1000;
// Re-enable time sync periodically when GPS has valid fix
if (!_time_sync_needed && _clock != NULL && (millis() - _last_time_sync) > TIME_SYNC_INTERVAL) {
+3 -3
View File
@@ -37,7 +37,7 @@ static int _internal_flash_read(const struct lfs_config *c, lfs_block_t block, l
}
// Program a region in a block. The block must have previously
// been erased. Negative error codes are propogated to the user.
// been erased. Negative error codes are propagated to the user.
// May return LFS_ERR_CORRUPT if the block should be considered bad.
static int _internal_flash_prog(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size)
{
@@ -62,7 +62,7 @@ static int _internal_flash_prog(const struct lfs_config *c, lfs_block_t block, l
// Erase a block. A block must be erased before being programmed.
// The state of an erased block is undefined. Negative error codes
// are propogated to the user.
// are propagated to the user.
// May return LFS_ERR_CORRUPT if the block should be considered bad.
static int _internal_flash_erase(const struct lfs_config *c, lfs_block_t block)
{
@@ -87,7 +87,7 @@ static int _internal_flash_erase(const struct lfs_config *c, lfs_block_t block)
}
// Sync the state of the underlying block device. Negative error codes
// are propogated to the user.
// are propagated to the user.
static int _internal_flash_sync(const struct lfs_config *c)
{
return LFS_ERR_OK; // don't need sync
+47
View File
@@ -0,0 +1,47 @@
#ifdef HAS_DRV2605
#include "DRV2605Vibration.h"
#include <Wire.h>
void DRV2605Vibration::begin() {
#ifdef PIN_DRV_EN
pinMode(PIN_DRV_EN, OUTPUT);
digitalWrite(PIN_DRV_EN, HIGH); // power up the haptic driver
delay(10);
#endif
if (!drv.begin(&Wire)) {
return; // no haptic driver found, stay silent
}
#ifdef DRV2605_USE_LRA
// LRA mode: 4x brake factor, medium loop gain, back-EMF gain 2
drv.writeRegister8(DRV2605_REG_FEEDBACK, 0xB6);
#endif
drv.selectLibrary(1);
drv.setMode(DRV2605_MODE_INTTRIG);
_ready = true;
}
void DRV2605Vibration::trigger(bool force) {
if (!_ready || _quiet) return;
unsigned long now = millis();
if (!force && _last_trigger != 0 && now - _last_trigger < VIBRATION_TIMEOUT) return;
_last_trigger = now;
drv.setWaveform(0, DRV2605_EFFECT);
drv.setWaveform(1, 0); // pause
drv.setWaveform(2, DRV2605_EFFECT);
drv.setWaveform(3, 0); // end of sequence
drv.go();
}
void DRV2605Vibration::loop() {
}
bool DRV2605Vibration::isVibrating() {
return false; // effects are short, treat as instantaneous
}
void DRV2605Vibration::stop() {
if (_ready) drv.stop();
}
#endif // ifdef HAS_DRV2605
+39
View File
@@ -0,0 +1,39 @@
#pragma once
#ifdef HAS_DRV2605
#include <Arduino.h>
#include <Adafruit_DRV2605.h>
/*
* Vibration control class for boards where the motor is behind a
* DRV2605 haptic driver on I2C (e.g. Seeed SenseCAP MeshTracker X1).
* Same interface as GenericVibration.
*/
#ifndef VIBRATION_TIMEOUT
#define VIBRATION_TIMEOUT 5000 // cooldown between vibrations
#endif
#ifndef DRV2605_EFFECT
#define DRV2605_EFFECT 16 // "1000 ms alert" from the DRV2605 effect library
#endif
class DRV2605Vibration {
public:
void begin(); // power up and init the DRV2605
void trigger(bool force = false); // trigger vibration; force skips the cooldown
void loop(); // no-op, the DRV2605 plays effects autonomously
bool isVibrating();
void stop(); // stop vibration immediately
void quiet(bool q) { _quiet = q; }
bool isQuiet() const { return _quiet; }
private:
Adafruit_DRV2605 drv;
bool _ready = false;
bool _quiet = false;
unsigned long _last_trigger = 0;
};
#endif // ifdef HAS_DRV2605
+551
View File
@@ -0,0 +1,551 @@
#include "NV3001BDisplay.h"
#include <Arduino.h>
#include <string.h>
#ifndef SPI_FREQUENCY
#define SPI_FREQUENCY 8000000
#endif
#ifndef PIN_TFT_SCL
#error "PIN_TFT_SCL must be defined"
#endif
#ifndef PIN_TFT_SDA
#error "PIN_TFT_SDA must be defined"
#endif
#ifndef PIN_TFT_CS
#error "PIN_TFT_CS must be defined"
#endif
#ifndef PIN_TFT_DC
#error "PIN_TFT_DC must be defined"
#endif
#ifndef PIN_TFT_MISO
#define PIN_TFT_MISO -1
#endif
#ifndef PIN_TFT_RST
#define PIN_TFT_RST -1
#endif
#ifndef PIN_TFT_EN
#define PIN_TFT_EN -1
#endif
#ifndef PIN_TFT_BL
#define PIN_TFT_BL -1
#endif
#ifndef PIN_TFT_EN_ACTIVE
#define PIN_TFT_EN_ACTIVE LOW
#endif
#ifndef PIN_TFT_BL_ACTIVE
#define PIN_TFT_BL_ACTIVE HIGH
#endif
#ifndef DISPLAY_ROTATION
#define DISPLAY_ROTATION 0
#endif
#ifndef NV3001B_SCREEN_WIDTH
#define NV3001B_SCREEN_WIDTH 220
#endif
#ifndef NV3001B_SCREEN_HEIGHT
#define NV3001B_SCREEN_HEIGHT 128
#endif
#ifndef DISPLAY_SCALE_X
#define DISPLAY_SCALE_X ((float)NV3001B_SCREEN_WIDTH / NV3001B_LOGICAL_WIDTH)
#endif
#ifndef DISPLAY_SCALE_Y
#define DISPLAY_SCALE_Y ((float)NV3001B_SCREEN_HEIGHT / NV3001B_LOGICAL_HEIGHT)
#endif
#define NV3001B_SWRESET 0x01
#define NV3001B_SLPOUT 0x11
#define NV3001B_DISPON 0x29
#define NV3001B_CASET 0x2A
#define NV3001B_RASET 0x2B
#define NV3001B_RAMWR 0x2C
#define NV3001B_MADCTL 0x36
#define NV3001B_COLMOD 0x3A
#define NV3001B_MADCTL_MY 0x80
#define NV3001B_MADCTL_MX 0x40
#define NV3001B_MADCTL_MV 0x20
#define NV3001B_MADCTL_RGB 0x00
#ifndef NV3001B_TEXT_SIZE1_SCALE_X
#define NV3001B_TEXT_SIZE1_SCALE_X 1
#endif
#ifndef NV3001B_TEXT_SIZE1_SCALE_Y
#define NV3001B_TEXT_SIZE1_SCALE_Y 2
#endif
#ifndef NV3001B_TEXT_SIZE2_SCALE_X
#define NV3001B_TEXT_SIZE2_SCALE_X 2
#endif
#ifndef NV3001B_TEXT_SIZE2_SCALE_Y
#define NV3001B_TEXT_SIZE2_SCALE_Y 3
#endif
// Color scheme
ColorVal UIColor::window_bkg = 0xFFFF;
ColorVal UIColor::title_bkg = 0x001F;
ColorVal UIColor::title_txt = 0xFFFF;
ColorVal UIColor::primary_txt = 0x0000;
ColorVal UIColor::secondary_txt = (18 << 11) | (36 << 5) | 18; // mid-gray
ColorVal UIColor::warning_txt = 0xFD20;
ColorVal UIColor::popup_bkg = 0x07FF; // CYAN
ColorVal UIColor::popup_txt = 0x0000;
ColorVal UIColor::corp_blue = 0x001A;
static int scaleX(int x) {
return (int)(x * DISPLAY_SCALE_X);
}
static int scaleY(int y) {
return (int)(y * DISPLAY_SCALE_Y);
}
static int scaleWidth(int x, int w) {
if (w <= 0) return 0;
int scaled = scaleX(x + w) - scaleX(x);
return scaled > 0 ? scaled : 1;
}
static int scaleHeight(int y, int h) {
if (h <= 0) return 0;
int scaled = scaleY(y + h) - scaleY(y);
return scaled > 0 ? scaled : 1;
}
static uint8_t nv3001bMADCTL(uint8_t rotation) {
uint8_t madctl;
switch (rotation & 3) {
case 0:
madctl = NV3001B_MADCTL_MY | NV3001B_MADCTL_MV | NV3001B_MADCTL_RGB;
break;
case 1:
madctl = NV3001B_MADCTL_MY | NV3001B_MADCTL_MX | NV3001B_MADCTL_RGB;
break;
case 2:
madctl = NV3001B_MADCTL_RGB;
break;
default:
madctl = NV3001B_MADCTL_MX | NV3001B_MADCTL_MV | NV3001B_MADCTL_RGB;
break;
}
return madctl;
}
static const uint8_t font5x7[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5f, 0x00, 0x00, 0x00, 0x07, 0x00, 0x07, 0x00, 0x14,
0x7f, 0x14, 0x7f, 0x14, 0x24, 0x2a, 0x7f, 0x2a, 0x12, 0x23, 0x13, 0x08, 0x64, 0x62, 0x36, 0x49,
0x55, 0x22, 0x50, 0x00, 0x05, 0x03, 0x00, 0x00, 0x00, 0x1c, 0x22, 0x41, 0x00, 0x00, 0x41, 0x22,
0x1c, 0x00, 0x14, 0x08, 0x3e, 0x08, 0x14, 0x08, 0x08, 0x3e, 0x08, 0x08, 0x00, 0x50, 0x30, 0x00,
0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x60, 0x60, 0x00, 0x00, 0x20, 0x10, 0x08, 0x04, 0x02,
0x3e, 0x51, 0x49, 0x45, 0x3e, 0x00, 0x42, 0x7f, 0x40, 0x00, 0x42, 0x61, 0x51, 0x49, 0x46, 0x21,
0x41, 0x45, 0x4b, 0x31, 0x18, 0x14, 0x12, 0x7f, 0x10, 0x27, 0x45, 0x45, 0x45, 0x39, 0x3c, 0x4a,
0x49, 0x49, 0x30, 0x01, 0x71, 0x09, 0x05, 0x03, 0x36, 0x49, 0x49, 0x49, 0x36, 0x06, 0x49, 0x49,
0x29, 0x1e, 0x00, 0x36, 0x36, 0x00, 0x00, 0x00, 0x56, 0x36, 0x00, 0x00, 0x08, 0x14, 0x22, 0x41,
0x00, 0x14, 0x14, 0x14, 0x14, 0x14, 0x00, 0x41, 0x22, 0x14, 0x08, 0x02, 0x01, 0x51, 0x09, 0x06,
0x32, 0x49, 0x79, 0x41, 0x3e, 0x7e, 0x11, 0x11, 0x11, 0x7e, 0x7f, 0x49, 0x49, 0x49, 0x36, 0x3e,
0x41, 0x41, 0x41, 0x22, 0x7f, 0x41, 0x41, 0x22, 0x1c, 0x7f, 0x49, 0x49, 0x49, 0x41, 0x7f, 0x09,
0x09, 0x09, 0x01, 0x3e, 0x41, 0x49, 0x49, 0x7a, 0x7f, 0x08, 0x08, 0x08, 0x7f, 0x00, 0x41, 0x7f,
0x41, 0x00, 0x20, 0x40, 0x41, 0x3f, 0x01, 0x7f, 0x08, 0x14, 0x22, 0x41, 0x7f, 0x40, 0x40, 0x40,
0x40, 0x7f, 0x02, 0x0c, 0x02, 0x7f, 0x7f, 0x04, 0x08, 0x10, 0x7f, 0x3e, 0x41, 0x41, 0x41, 0x3e,
0x7f, 0x09, 0x09, 0x09, 0x06, 0x3e, 0x41, 0x51, 0x21, 0x5e, 0x7f, 0x09, 0x19, 0x29, 0x46, 0x46,
0x49, 0x49, 0x49, 0x31, 0x01, 0x01, 0x7f, 0x01, 0x01, 0x3f, 0x40, 0x40, 0x40, 0x3f, 0x1f, 0x20,
0x40, 0x20, 0x1f, 0x3f, 0x40, 0x38, 0x40, 0x3f, 0x63, 0x14, 0x08, 0x14, 0x63, 0x07, 0x08, 0x70,
0x08, 0x07, 0x61, 0x51, 0x49, 0x45, 0x43, 0x00, 0x7f, 0x41, 0x41, 0x00, 0x02, 0x04, 0x08, 0x10,
0x20, 0x00, 0x41, 0x41, 0x7f, 0x00, 0x04, 0x02, 0x01, 0x02, 0x04, 0x40, 0x40, 0x40, 0x40, 0x40,
0x00, 0x01, 0x02, 0x04, 0x00, 0x20, 0x54, 0x54, 0x54, 0x78, 0x7f, 0x48, 0x44, 0x44, 0x38, 0x38,
0x44, 0x44, 0x44, 0x20, 0x38, 0x44, 0x44, 0x48, 0x7f, 0x38, 0x54, 0x54, 0x54, 0x18, 0x08, 0x7e,
0x09, 0x01, 0x02, 0x0c, 0x52, 0x52, 0x52, 0x3e, 0x7f, 0x08, 0x04, 0x04, 0x78, 0x00, 0x44, 0x7d,
0x40, 0x00, 0x20, 0x40, 0x44, 0x3d, 0x00, 0x7f, 0x10, 0x28, 0x44, 0x00, 0x00, 0x41, 0x7f, 0x40,
0x00, 0x7c, 0x04, 0x18, 0x04, 0x78, 0x7c, 0x08, 0x04, 0x04, 0x78, 0x38, 0x44, 0x44, 0x44, 0x38,
0x7c, 0x14, 0x14, 0x14, 0x08, 0x08, 0x14, 0x14, 0x18, 0x7c, 0x7c, 0x08, 0x04, 0x04, 0x08, 0x48,
0x54, 0x54, 0x54, 0x20, 0x04, 0x3f, 0x44, 0x40, 0x20, 0x3c, 0x40, 0x40, 0x20, 0x7c, 0x1c, 0x20,
0x40, 0x20, 0x1c, 0x3c, 0x40, 0x30, 0x40, 0x3c, 0x44, 0x28, 0x10, 0x28, 0x44, 0x0c, 0x50, 0x50,
0x50, 0x3c, 0x44, 0x64, 0x54, 0x4c, 0x44, 0x00, 0x08, 0x36, 0x41, 0x00, 0x00, 0x00, 0x7f, 0x00,
0x00, 0x00, 0x41, 0x36, 0x08, 0x00, 0x08, 0x08, 0x2a, 0x1c, 0x08, 0x00, 0x06, 0x09, 0x09, 0x06
};
static int textPixelScaleX(uint8_t size) {
return size <= 1 ? NV3001B_TEXT_SIZE1_SCALE_X : NV3001B_TEXT_SIZE2_SCALE_X;
}
static int textPixelScaleY(uint8_t size) {
return size <= 1 ? NV3001B_TEXT_SIZE1_SCALE_Y : NV3001B_TEXT_SIZE2_SCALE_Y;
}
static void setupOptionalOutput(int pin, int level) {
if (pin < 0) return;
pinMode(pin, OUTPUT);
digitalWrite(pin, level);
}
static void writeOptionalPin(int pin, int level) {
if (pin < 0) return;
digitalWrite(pin, level);
}
void NV3001BDisplay::writeCommand(uint8_t cmd) {
spi.beginTransaction(SPISettings(SPI_FREQUENCY, MSBFIRST, SPI_MODE0));
digitalWrite(PIN_TFT_DC, LOW);
digitalWrite(PIN_TFT_CS, LOW);
spi.transfer(cmd);
digitalWrite(PIN_TFT_CS, HIGH);
spi.endTransaction();
}
void NV3001BDisplay::writeBytes(const uint8_t* data, size_t len) {
if (!data || len == 0) return;
spi.beginTransaction(SPISettings(SPI_FREQUENCY, MSBFIRST, SPI_MODE0));
digitalWrite(PIN_TFT_DC, HIGH);
digitalWrite(PIN_TFT_CS, LOW);
for (size_t i = 0; i < len; i++) {
spi.transfer(data[i]);
}
digitalWrite(PIN_TFT_CS, HIGH);
spi.endTransaction();
}
void NV3001BDisplay::writeCommandData(uint8_t cmd, const uint8_t* data, size_t len) {
writeCommand(cmd);
writeBytes(data, len);
}
void NV3001BDisplay::setAddrWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
uint16_t x2 = x + w - 1;
uint16_t y2 = y + h - 1;
uint8_t data[4];
data[0] = x >> 8;
data[1] = x & 0xff;
data[2] = x2 >> 8;
data[3] = x2 & 0xff;
writeCommandData(NV3001B_CASET, data, sizeof(data));
data[0] = y >> 8;
data[1] = y & 0xff;
data[2] = y2 >> 8;
data[3] = y2 & 0xff;
writeCommandData(NV3001B_RASET, data, sizeof(data));
writeCommand(NV3001B_RAMWR);
}
void NV3001BDisplay::writeColor(uint16_t rgb, uint32_t count) {
uint8_t hi = rgb >> 8;
uint8_t lo = rgb & 0xff;
spi.beginTransaction(SPISettings(SPI_FREQUENCY, MSBFIRST, SPI_MODE0));
digitalWrite(PIN_TFT_DC, HIGH);
digitalWrite(PIN_TFT_CS, LOW);
while (count--) {
spi.transfer(hi);
spi.transfer(lo);
}
digitalWrite(PIN_TFT_CS, HIGH);
spi.endTransaction();
}
void NV3001BDisplay::initPanel() {
#define CMD0(C) do { writeCommand(C); } while (0)
#define CMD1(C, A) do { const uint8_t d[] = { A }; writeCommandData(C, d, sizeof(d)); } while (0)
#define CMD2(C, A, B) do { const uint8_t d[] = { A, B }; writeCommandData(C, d, sizeof(d)); } while (0)
CMD0(NV3001B_SWRESET);
delay(120);
CMD1(0xFF, 0xA5);
CMD1(0x41, 0x00);
CMD1(0x50, 0x02);
CMD1(0x52, 0x6E);
CMD1(0x57, 0x02);
CMD1(0x46, 0x11);
CMD2(0x47, 0x00, 0x01);
CMD2(0x8F, 0x22, 0x03);
CMD1(0x9A, 0x78);
CMD1(0x9B, 0x78);
CMD1(0x9C, 0xA0);
CMD1(0x9D, 0x17);
CMD1(0x9E, 0xC1);
CMD1(0x83, 0x5A);
CMD1(0x84, 0xB6);
CMD1(0xFF, 0xA5);
CMD1(0x85, 0x5F);
CMD1(0x6E, 0x0F);
CMD1(0x7E, 0x0F);
CMD1(0x60, 0x00);
CMD1(0x70, 0x00);
CMD1(0x6D, 0x33);
CMD1(0x7D, 0x37);
CMD1(0x61, 0x09);
CMD1(0x71, 0x0A);
CMD1(0x6C, 0x2A);
CMD1(0x7C, 0x36);
CMD1(0x62, 0x11);
CMD1(0x72, 0x10);
CMD1(0x68, 0x4E);
CMD1(0x78, 0x4E);
CMD1(0x66, 0x36);
CMD1(0x76, 0x3C);
CMD1(0x1A, 0x1C);
CMD1(0x7B, 0x14);
CMD1(0x63, 0x0D);
CMD1(0x73, 0x0A);
CMD1(0x6A, 0x16);
CMD1(0x7A, 0x12);
CMD1(0x64, 0x0B);
CMD1(0x74, 0x0A);
CMD1(0x69, 0x08);
CMD1(0x79, 0x0A);
CMD1(0x65, 0x06);
CMD1(0x75, 0x07);
CMD1(0x67, 0x23);
CMD1(0x77, 0x44);
CMD1(0xE0, 0x00);
CMD1(0xE9, 0x30);
CMD1(0xEB, 0xB7);
CMD1(0xEC, 0x00);
CMD1(0xED, 0x11);
CMD1(0xF0, 0xB7);
CMD1(0x53, 0x04);
CMD1(0x54, 0x04);
CMD1(0x55, 0x40);
CMD1(0x56, 0x40);
CMD2(0xA0, 0x60, 0x01);
CMD1(0xA1, 0x84);
CMD1(0xA2, 0x85);
CMD2(0xAB, 0x00, 0x02);
CMD2(0xAC, 0x00, 0x06);
CMD2(0xAD, 0x00, 0x03);
CMD2(0xAE, 0x00, 0x07);
CMD1(0xC7, 0x01);
CMD1(0xB9, 0x82);
CMD1(0xBA, 0x83);
CMD1(0xBB, 0x00);
CMD1(0xBC, 0x81);
CMD1(0xBD, 0x02);
CMD1(0xBE, 0x01);
CMD1(0xBF, 0x04);
CMD1(0xC0, 0x03);
CMD1(0xC8, 0x55);
CMD1(0xC9, 0xC9);
CMD1(0xCA, 0xC8);
CMD1(0xCB, 0xCB);
CMD1(0xCC, 0xCA);
CMD1(0xCD, 0x55);
CMD1(0xCE, 0xCE);
CMD1(0xCF, 0xCD);
CMD1(0xD0, 0xD0);
CMD1(0xD1, 0xCF);
CMD1(0xF2, 0x46);
CMD1(0xA8, 0x04);
CMD1(0xA9, 0xB0);
CMD1(0xAA, 0xA3);
CMD1(0xB6, 0x00);
CMD1(0xB7, 0xB0);
CMD1(0xB8, 0xA3);
CMD1(0xC4, 0x03);
CMD1(0xC5, 0xB0);
CMD1(0xC6, 0xA3);
CMD1(0x80, 0x10);
CMD1(0xFF, 0x00);
CMD1(0x35, 0x00);
CMD0(NV3001B_SLPOUT);
delay(120);
CMD1(NV3001B_COLMOD, 0x05);
CMD1(NV3001B_MADCTL, nv3001bMADCTL(DISPLAY_ROTATION));
CMD0(NV3001B_DISPON);
delay(10);
#undef CMD0
#undef CMD1
#undef CMD2
}
void NV3001BDisplay::fillPhysicalRect(int x, int y, int w, int h) {
if (!is_on || w <= 0 || h <= 0) return;
if (x < 0) {
w += x;
x = 0;
}
if (y < 0) {
h += y;
y = 0;
}
if (x + w > NV3001B_SCREEN_WIDTH) w = NV3001B_SCREEN_WIDTH - x;
if (y + h > NV3001B_SCREEN_HEIGHT) h = NV3001B_SCREEN_HEIGHT - y;
if (w <= 0 || h <= 0) return;
setAddrWindow(x, y, w, h);
writeColor(color, (uint32_t)w * h);
}
void NV3001BDisplay::drawChar(int x, int y, char ch) {
if (ch < 32 || ch > 127) ch = '?';
uint16_t index = (uint16_t)(ch - 32) * 5;
int scale_x = textPixelScaleX(text_size);
int scale_y = textPixelScaleY(text_size);
for (int col = 0; col < 5; col++) {
uint8_t line = pgm_read_byte(font5x7 + index + col);
for (int row = 0; row < 7; row++) {
if (line & (1 << row)) {
fillPhysicalRect(x + col * scale_x, y + row * scale_y, scale_x, scale_y);
}
}
}
}
bool NV3001BDisplay::begin() {
if (is_on) return true;
if (periph_power) periph_power->claim();
setupOptionalOutput(PIN_TFT_EN, PIN_TFT_EN_ACTIVE);
setupOptionalOutput(PIN_TFT_BL, !PIN_TFT_BL_ACTIVE);
pinMode(PIN_TFT_CS, OUTPUT);
pinMode(PIN_TFT_DC, OUTPUT);
digitalWrite(PIN_TFT_CS, HIGH);
digitalWrite(PIN_TFT_DC, HIGH);
delay(20);
spi.begin(PIN_TFT_SCL, PIN_TFT_MISO, PIN_TFT_SDA, PIN_TFT_CS);
if (PIN_TFT_RST >= 0) {
pinMode(PIN_TFT_RST, OUTPUT);
digitalWrite(PIN_TFT_RST, HIGH);
delay(10);
digitalWrite(PIN_TFT_RST, LOW);
delay(20);
digitalWrite(PIN_TFT_RST, HIGH);
delay(120);
}
initPanel();
is_on = true;
color = 0x0000;
fillPhysicalRect(0, 0, NV3001B_SCREEN_WIDTH, NV3001B_SCREEN_HEIGHT);
color = 0xffff;
text_size = 1;
cursor_x = 0;
cursor_y = 0;
writeOptionalPin(PIN_TFT_BL, PIN_TFT_BL_ACTIVE);
return true;
}
void NV3001BDisplay::turnOn() {
begin();
}
void NV3001BDisplay::turnOff() {
if (!is_on) return;
writeOptionalPin(PIN_TFT_BL, !PIN_TFT_BL_ACTIVE);
writeOptionalPin(PIN_TFT_EN, !PIN_TFT_EN_ACTIVE);
is_on = false;
if (periph_power) periph_power->release();
}
void NV3001BDisplay::clear() {
uint16_t saved = color;
color = UIColor::window_bkg;
fillPhysicalRect(0, 0, NV3001B_SCREEN_WIDTH, NV3001B_SCREEN_HEIGHT);
color = saved;
}
void NV3001BDisplay::startFrame(ColorVal bkg) {
color = bkg;
fillPhysicalRect(0, 0, NV3001B_SCREEN_WIDTH, NV3001B_SCREEN_HEIGHT);
color = UIColor::primary_txt;
text_size = 1;
cursor_x = 0;
cursor_y = 0;
}
void NV3001BDisplay::setTextSize(int sz) {
text_size = sz < 1 ? 1 : sz;
}
void NV3001BDisplay::setColor(ColorVal c) {
color = c;
}
void NV3001BDisplay::setCursor(int x, int y) {
cursor_x = scaleX(x);
cursor_y = scaleY(y);
}
void NV3001BDisplay::print(const char* str) {
if (!str || !is_on) return;
int scale_x = textPixelScaleX(text_size);
int scale_y = textPixelScaleY(text_size);
while (*str) {
if (*str == '\n') {
cursor_x = 0;
cursor_y += 8 * scale_y;
} else if (*str == '\r') {
cursor_x = 0;
} else {
drawChar(cursor_x, cursor_y, *str);
cursor_x += 6 * scale_x;
}
str++;
}
}
void NV3001BDisplay::fillRect(int x, int y, int w, int h) {
fillPhysicalRect(scaleX(x), scaleY(y), scaleWidth(x, w), scaleHeight(y, h));
}
void NV3001BDisplay::drawRect(int x, int y, int w, int h) {
int x1 = scaleX(x);
int y1 = scaleY(y);
int sw = scaleWidth(x, w);
int sh = scaleHeight(y, h);
fillPhysicalRect(x1, y1, sw, 1);
fillPhysicalRect(x1, y1 + sh - 1, sw, 1);
fillPhysicalRect(x1, y1, 1, sh);
fillPhysicalRect(x1 + sw - 1, y1, 1, sh);
}
void NV3001BDisplay::drawXbm(int x, int y, const uint8_t* bits, int w, int h) {
if (!bits || !is_on) return;
int byte_width = (w + 7) / 8;
for (int j = 0; j < h; j++) {
for (int i = 0; i < w; i++) {
uint8_t byte = pgm_read_byte(bits + j * byte_width + i / 8);
if (byte & (0x80 >> (i & 7))) {
fillPhysicalRect(scaleX(x + i), scaleY(y + j), scaleWidth(x + i, 1), scaleHeight(y + j, 1));
}
}
}
}
uint16_t NV3001BDisplay::getTextWidth(const char* str) {
if (!str) return 0;
uint16_t len = 0;
while (str[len] && str[len] != '\n' && str[len] != '\r') len++;
return (uint16_t)((len * 6 * textPixelScaleX(text_size)) / DISPLAY_SCALE_X);
}
void NV3001BDisplay::endFrame() {
}
+68
View File
@@ -0,0 +1,68 @@
#pragma once
#include "DisplayDriver.h"
#include <SPI.h>
#include <helpers/RefCountedDigitalPin.h>
#ifndef NV3001B_LOGICAL_WIDTH
#define NV3001B_LOGICAL_WIDTH 128
#endif
#ifndef NV3001B_LOGICAL_HEIGHT
#define NV3001B_LOGICAL_HEIGHT 64
#endif
#ifndef NV3001B_PANEL_WIDTH
#define NV3001B_PANEL_WIDTH 128
#endif
#ifndef NV3001B_PANEL_HEIGHT
#define NV3001B_PANEL_HEIGHT 220
#endif
#ifndef NV3001B_SPI_HOST
#define NV3001B_SPI_HOST HSPI
#endif
class NV3001BDisplay : public DisplayDriver {
SPIClass spi;
RefCountedDigitalPin* periph_power;
bool is_on = false;
uint16_t color = 0xffff;
uint8_t text_size = 1;
int cursor_x = 0;
int cursor_y = 0;
void writeCommand(uint8_t cmd);
void writeBytes(const uint8_t* data, size_t len);
void writeCommandData(uint8_t cmd, const uint8_t* data, size_t len);
void setAddrWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h);
void writeColor(uint16_t rgb, uint32_t count);
void fillPhysicalRect(int x, int y, int w, int h);
void initPanel();
void drawChar(int x, int y, char ch);
public:
NV3001BDisplay(RefCountedDigitalPin* power = nullptr) :
DisplayDriver(NV3001B_LOGICAL_WIDTH, NV3001B_LOGICAL_HEIGHT), spi(NV3001B_SPI_HOST), periph_power(power) { }
bool begin();
static const char* driverName() { return "NV3001B"; }
static uint16_t physicalWidth() { return NV3001B_PANEL_WIDTH; }
static uint16_t physicalHeight() { return NV3001B_PANEL_HEIGHT; }
bool isOn() override { return is_on; }
void turnOn() override;
void turnOff() override;
void clear() override;
void startFrame(ColorVal bkg = UIColor::window_bkg) override;
void setTextSize(int sz) override;
void setColor(ColorVal c) override;
void setCursor(int x, int y) override;
void print(const char* str) override;
void fillRect(int x, int y, int w, int h) override;
void drawRect(int x, int y, int w, int h) override;
void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override;
uint16_t getTextWidth(const char* str) override;
void endFrame() override;
};
+1
View File
@@ -0,0 +1 @@
#include "NullDisplayDriver.h"
+1 -1
View File
@@ -1166,7 +1166,7 @@ char DefaultFontTableLookup(const uint8_t ch) {
uint8_t last = LASTCHAR; // get last char
LASTCHAR = ch;
switch (last) { // conversion depnding on first UTF8-character
switch (last) { // conversion depending on first UTF8-character
case 0xC2: return (uint8_t) ch;
case 0xC3: return (uint8_t) (ch | 0xC0);
case 0x82: if (ch == 0xAC) return (uint8_t) 0x80; // special case Euro-symbol
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include <Arduino.h>
enum class RotaryInputEvent : uint8_t {
None,
Next,
Prev,
};
class RotaryInput {
public:
virtual ~RotaryInput() = default;
virtual bool begin() = 0;
virtual RotaryInputEvent poll() = 0;
virtual bool isReady() const = 0;
};
+22 -6
View File
@@ -26,6 +26,16 @@
#define SCALE_Y 2.109375f // 135 / 64
#endif
#ifdef DISPLAY_SCALE_X
#undef SCALE_X
#define SCALE_X DISPLAY_SCALE_X
#endif
#ifdef DISPLAY_SCALE_Y
#undef SCALE_Y
#define SCALE_Y DISPLAY_SCALE_Y
#endif
#ifdef OLED_MISC_FIXED_FONT
// MiscFixedFont.h/LemonIcons.h store each glyph's rows bit-packed
// *continuously* (Adafruit_GFX's native GFXfont convention -- no per-row byte
@@ -75,6 +85,9 @@ bool ST7789Display::begin() {
display.init();
display.landscapeScreen();
#ifdef DISPLAY_FLIP_VERTICALLY
display.flipScreenVertically();
#endif
display.displayOn();
setCursor(0,0);
@@ -92,6 +105,9 @@ void ST7789Display::turnOn() {
// Re-initialize the display
display.init();
display.displayOn();
#ifdef DISPLAY_FLIP_VERTICALLY
display.flipScreenVertically();
#endif
delay(20);
// Now turn on the backlight
@@ -146,22 +162,22 @@ void ST7789Display::setColor(Color c) {
display.setColor(OLEDDISPLAY_COLOR::BLACK);
break;
#if 0
case DisplayDriver::LIGHT :
case DisplayDriver::LIGHT :
_color = ST77XX_WHITE;
break;
case DisplayDriver::RED :
case DisplayDriver::RED :
_color = ST77XX_RED;
break;
case DisplayDriver::GREEN :
case DisplayDriver::GREEN :
_color = ST77XX_GREEN;
break;
case DisplayDriver::BLUE :
case DisplayDriver::BLUE :
_color = ST77XX_BLUE;
break;
case DisplayDriver::YELLOW :
case DisplayDriver::YELLOW :
_color = ST77XX_YELLOW;
break;
case DisplayDriver::ORANGE :
case DisplayDriver::ORANGE :
_color = ST77XX_ORANGE;
break;
#endif
+3 -1
View File
@@ -22,10 +22,12 @@ class ST7789Display : public DisplayDriver {
uint8_t glyphXAdvance(uint32_t cp);
#endif
public:
#ifdef HELTEC_VISION_MASTER_T190
#if defined(HELTEC_VISION_MASTER_T190)
ST7789Display() : DisplayDriver(128, 64), display(&SPI, PIN_TFT_RST, PIN_TFT_DC, PIN_TFT_CS, GEOMETRY_RAWMODE, 320, 170,PIN_TFT_SDA,-1,PIN_TFT_SCL) {_isOn = false;}
#elif defined(CARDPUTER_ADV)
ST7789Display() : DisplayDriver(128, 64), display(&SPI, PIN_TFT_RST, PIN_TFT_DC, PIN_TFT_CS, GEOMETRY_RAWMODE, 240, 135, PIN_TFT_SDA, -1, PIN_TFT_SCL) {_isOn = false;}
#elif defined(THINKNODE_M9)
ST7789Display() : DisplayDriver(128, 64), display(&SPI, ST7789_RESET, ST7789_RS, ST7789_CS, GEOMETRY_RAWMODE, 320, 240, ST7789_SDA, ST7789_MISO, ST7789_SCK) {_isOn = false;}
#else
ST7789Display() : DisplayDriver(128, 64), display(&SPI1, PIN_TFT_RST, PIN_TFT_DC, PIN_TFT_CS, GEOMETRY_RAWMODE, 240, 135) {_isOn = false;}
#endif
+1
View File
@@ -0,0 +1 @@
#include "U8g2Display.h"