mirror of
https://github.com/meshcore-dev/MeshCore.git
synced 2026-04-20 22:13:47 +00:00
Merge branch 'dev' of https://github.com/ripplebiz/MeshCore into dev
This commit is contained in:
commit
97c43a8937
112 changed files with 2848 additions and 344 deletions
|
|
@ -13,9 +13,11 @@ namespace mesh {
|
|||
void Dispatcher::begin() {
|
||||
n_sent_flood = n_sent_direct = 0;
|
||||
n_recv_flood = n_recv_direct = 0;
|
||||
n_full_events = 0;
|
||||
_err_flags = 0;
|
||||
radio_nonrx_start = _ms->getMillis();
|
||||
|
||||
_radio->begin();
|
||||
prev_isrecv_mode = _radio->isInRecvMode();
|
||||
}
|
||||
|
||||
float Dispatcher::getAirtimeBudgetFactor() const {
|
||||
|
|
@ -34,6 +36,18 @@ uint32_t Dispatcher::getCADFailMaxDuration() const {
|
|||
}
|
||||
|
||||
void Dispatcher::loop() {
|
||||
// check for radio 'stuck' in mode other than Rx
|
||||
bool is_recv = _radio->isInRecvMode();
|
||||
if (is_recv != prev_isrecv_mode) {
|
||||
prev_isrecv_mode = is_recv;
|
||||
if (!is_recv) {
|
||||
radio_nonrx_start = _ms->getMillis();
|
||||
}
|
||||
}
|
||||
if (!is_recv && _ms->getMillis() - radio_nonrx_start > 8000) { // radio has not been in Rx mode for 8 seconds!
|
||||
_err_flags |= ERR_EVENT_STARTRX_TIMEOUT;
|
||||
}
|
||||
|
||||
if (outbound) { // waiting for outbound send to be completed
|
||||
if (_radio->isSendComplete()) {
|
||||
long t = _ms->getMillis() - outbound_start;
|
||||
|
|
@ -191,7 +205,7 @@ void Dispatcher::processRecvPacket(Packet* pkt) {
|
|||
}
|
||||
|
||||
void Dispatcher::checkSend() {
|
||||
if (_mgr->getOutboundCount() == 0) return; // nothing waiting to send
|
||||
if (_mgr->getOutboundCount(_ms->getMillis()) == 0) return; // nothing waiting to send
|
||||
if (!millisHasNowPassed(next_tx_time)) return; // still in 'radio silence' phase (from airtime budget setting)
|
||||
if (_radio->isReceiving()) { // LBT - check if radio is currently mid-receive, or if channel activity
|
||||
if (cad_busy_start == 0) {
|
||||
|
|
@ -199,6 +213,8 @@ void Dispatcher::checkSend() {
|
|||
}
|
||||
|
||||
if (_ms->getMillis() - cad_busy_start > getCADFailMaxDuration()) {
|
||||
_err_flags |= ERR_EVENT_CAD_TIMEOUT;
|
||||
|
||||
MESH_DEBUG_PRINTLN("%s Dispatcher::checkSend(): CAD busy max duration reached!", getLogDateTime());
|
||||
// channel activity has gone on too long... (Radio might be in a bad state)
|
||||
// force the pending transmit below...
|
||||
|
|
@ -234,7 +250,16 @@ void Dispatcher::checkSend() {
|
|||
|
||||
uint32_t max_airtime = _radio->getEstAirtimeFor(len)*3/2;
|
||||
outbound_start = _ms->getMillis();
|
||||
_radio->startSendRaw(raw, len);
|
||||
bool success = _radio->startSendRaw(raw, len);
|
||||
if (!success) {
|
||||
MESH_DEBUG_PRINTLN("%s Dispatcher::loop(): ERROR: send start failed!", getLogDateTime());
|
||||
|
||||
logTxFail(outbound, outbound->getRawLength());
|
||||
|
||||
releasePacket(outbound); // return to pool
|
||||
outbound = NULL;
|
||||
return;
|
||||
}
|
||||
outbound_expiry = futureMillis(max_airtime);
|
||||
|
||||
#if MESH_PACKET_LOGGING
|
||||
|
|
@ -255,7 +280,7 @@ void Dispatcher::checkSend() {
|
|||
Packet* Dispatcher::obtainNewPacket() {
|
||||
auto pkt = _mgr->allocNew(); // TODO: zero out all fields
|
||||
if (pkt == NULL) {
|
||||
n_full_events++;
|
||||
_err_flags |= ERR_EVENT_FULL;
|
||||
} else {
|
||||
pkt->payload_len = pkt->path_len = 0;
|
||||
pkt->_snr = 0;
|
||||
|
|
|
|||
|
|
@ -42,8 +42,9 @@ public:
|
|||
* \brief starts the raw packet send. (no wait)
|
||||
* \param bytes the raw packet data
|
||||
* \param len the length in bytes
|
||||
* \returns true if successfully started
|
||||
*/
|
||||
virtual void startSendRaw(const uint8_t* bytes, int len) = 0;
|
||||
virtual bool startSendRaw(const uint8_t* bytes, int len) = 0;
|
||||
|
||||
/**
|
||||
* \returns true if the previous 'startSendRaw()' completed successfully.
|
||||
|
|
@ -55,6 +56,8 @@ public:
|
|||
*/
|
||||
virtual void onSendFinished() = 0;
|
||||
|
||||
virtual bool isInRecvMode() const = 0;
|
||||
|
||||
/**
|
||||
* \returns true if the radio is currently mid-receive of a packet.
|
||||
*/
|
||||
|
|
@ -75,7 +78,7 @@ public:
|
|||
|
||||
virtual void queueOutbound(Packet* packet, uint8_t priority, uint32_t scheduled_for) = 0;
|
||||
virtual Packet* getNextOutbound(uint32_t now) = 0; // by priority
|
||||
virtual int getOutboundCount() const = 0;
|
||||
virtual int getOutboundCount(uint32_t now) const = 0;
|
||||
virtual int getFreeCount() const = 0;
|
||||
virtual Packet* getOutboundByIdx(int i) = 0;
|
||||
virtual Packet* removeOutboundByIdx(int i) = 0;
|
||||
|
|
@ -90,6 +93,10 @@ typedef uint32_t DispatcherAction;
|
|||
#define ACTION_RETRANSMIT(pri) (((uint32_t)1 + (pri))<<24)
|
||||
#define ACTION_RETRANSMIT_DELAYED(pri, _delay) ((((uint32_t)1 + (pri))<<24) | (_delay))
|
||||
|
||||
#define ERR_EVENT_FULL (1 << 0)
|
||||
#define ERR_EVENT_CAD_TIMEOUT (1 << 1)
|
||||
#define ERR_EVENT_STARTRX_TIMEOUT (1 << 2)
|
||||
|
||||
/**
|
||||
* \brief The low-level task that manages detecting incoming Packets, and the queueing
|
||||
* and scheduling of outbound Packets.
|
||||
|
|
@ -99,9 +106,10 @@ class Dispatcher {
|
|||
unsigned long outbound_expiry, outbound_start, total_air_time;
|
||||
unsigned long next_tx_time;
|
||||
unsigned long cad_busy_start;
|
||||
unsigned long radio_nonrx_start;
|
||||
bool prev_isrecv_mode;
|
||||
uint32_t n_sent_flood, n_sent_direct;
|
||||
uint32_t n_recv_flood, n_recv_direct;
|
||||
uint32_t n_full_events;
|
||||
|
||||
void processRecvPacket(Packet* pkt);
|
||||
|
||||
|
|
@ -109,12 +117,16 @@ protected:
|
|||
PacketManager* _mgr;
|
||||
Radio* _radio;
|
||||
MillisecondClock* _ms;
|
||||
uint16_t _err_flags;
|
||||
|
||||
Dispatcher(Radio& radio, MillisecondClock& ms, PacketManager& mgr)
|
||||
: _radio(&radio), _ms(&ms), _mgr(&mgr)
|
||||
{
|
||||
outbound = NULL; total_air_time = 0; next_tx_time = 0;
|
||||
cad_busy_start = 0;
|
||||
_err_flags = 0;
|
||||
radio_nonrx_start = 0;
|
||||
prev_isrecv_mode = true;
|
||||
}
|
||||
|
||||
virtual DispatcherAction onRecvPacket(Packet* pkt) = 0;
|
||||
|
|
@ -144,7 +156,10 @@ public:
|
|||
uint32_t getNumSentDirect() const { return n_sent_direct; }
|
||||
uint32_t getNumRecvFlood() const { return n_recv_flood; }
|
||||
uint32_t getNumRecvDirect() const { return n_recv_direct; }
|
||||
uint32_t getNumFullEvents() const { return n_full_events; }
|
||||
void resetStats() {
|
||||
n_sent_flood = n_sent_direct = n_recv_flood = n_recv_direct = 0;
|
||||
_err_flags = 0;
|
||||
}
|
||||
|
||||
// helper methods
|
||||
bool millisHasNowPassed(unsigned long timestamp) const;
|
||||
|
|
|
|||
|
|
@ -71,7 +71,15 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
|
|||
|
||||
// remove our hash from 'path', then re-broadcast
|
||||
pkt->path_len -= PATH_HASH_SIZE;
|
||||
#if 0
|
||||
memcpy(pkt->path, &pkt->path[PATH_HASH_SIZE], pkt->path_len);
|
||||
#elif PATH_HASH_SIZE == 1
|
||||
for (int k = 0; k < pkt->path_len; k++) { // shuffle bytes by 1
|
||||
pkt->path[k] = pkt->path[k + 1];
|
||||
}
|
||||
#else
|
||||
#error "need path remove impl"
|
||||
#endif
|
||||
|
||||
uint32_t d = getDirectRetransmitDelay(pkt);
|
||||
return ACTION_RETRANSMIT_DELAYED(0, d); // Routed traffic is HIGHEST priority
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ public:
|
|||
class MeshTables {
|
||||
public:
|
||||
virtual bool hasSeen(const Packet* packet) = 0;
|
||||
virtual void clear(const Packet* packet) = 0; // remove this packet hash from table
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -8,8 +8,14 @@
|
|||
memcpy(&app_data[i], &_lat, 4); i += 4;
|
||||
memcpy(&app_data[i], &_lon, 4); i += 4;
|
||||
}
|
||||
// TODO: BATTERY encoding
|
||||
// TODO: TEMPERATURE encoding
|
||||
if (_extra1) {
|
||||
app_data[0] |= ADV_FEAT1_MASK;
|
||||
memcpy(&app_data[i], &_extra1, 2); i += 2;
|
||||
}
|
||||
if (_extra2) {
|
||||
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;
|
||||
|
|
@ -25,17 +31,18 @@
|
|||
_lat = _lon = 0;
|
||||
_flags = app_data[0];
|
||||
_valid = false;
|
||||
|
||||
_extra1 = _extra2 = 0;
|
||||
|
||||
int i = 1;
|
||||
if (_flags & ADV_LATLON_MASK) {
|
||||
memcpy(&_lat, &app_data[i], 4); i += 4;
|
||||
memcpy(&_lon, &app_data[i], 4); i += 4;
|
||||
}
|
||||
if (_flags & ADV_BATTERY_MASK) {
|
||||
/* TODO: somewhere to store battery volts? */ i += 2;
|
||||
if (_flags & ADV_FEAT1_MASK) {
|
||||
memcpy(&_extra1, &app_data[i], 2); i += 2;
|
||||
}
|
||||
if (_flags & ADV_TEMPERATURE_MASK) {
|
||||
/* TODO: somewhere to store temperature? */ i += 2;
|
||||
if (_flags & ADV_FEAT2_MASK) {
|
||||
memcpy(&_extra2, &app_data[i], 2); i += 2;
|
||||
}
|
||||
|
||||
if (app_data_len >= i) {
|
||||
|
|
|
|||
|
|
@ -11,20 +11,25 @@
|
|||
//FUTURE: 4..15
|
||||
|
||||
#define ADV_LATLON_MASK 0x10
|
||||
#define ADV_BATTERY_MASK 0x20
|
||||
#define ADV_TEMPERATURE_MASK 0x40
|
||||
#define ADV_FEAT1_MASK 0x20 // FUTURE
|
||||
#define ADV_FEAT2_MASK 0x40 // FUTURE
|
||||
#define ADV_NAME_MASK 0x80
|
||||
|
||||
class AdvertDataBuilder {
|
||||
uint8_t _type;
|
||||
const char* _name;
|
||||
int32_t _lat, _lon;
|
||||
uint16_t _extra1 = 0;
|
||||
uint16_t _extra2 = 0;
|
||||
public:
|
||||
AdvertDataBuilder(uint8_t adv_type) : _type(adv_type), _name(NULL), _lat(0), _lon(0) { }
|
||||
AdvertDataBuilder(uint8_t adv_type, const char* name) : _type(adv_type), _name(name), _lat(0), _lon(0) { }
|
||||
AdvertDataBuilder(uint8_t adv_type, const char* name, double lat, double lon) :
|
||||
_type(adv_type), _name(name), _lat(lat * 1E6), _lon(lon * 1E6) { }
|
||||
|
||||
void setFeat1(uint16_t extra) { _extra1 = extra; }
|
||||
void setFeat2(uint16_t extra) { _extra2 = extra; }
|
||||
|
||||
/**
|
||||
* \brief encode the given advertisement data.
|
||||
* \param app_data dest array, must be MAX_ADVERT_DATA_SIZE
|
||||
|
|
@ -38,11 +43,15 @@ class AdvertDataParser {
|
|||
bool _valid;
|
||||
char _name[MAX_ADVERT_DATA_SIZE];
|
||||
int32_t _lat, _lon;
|
||||
uint16_t _extra1;
|
||||
uint16_t _extra2;
|
||||
public:
|
||||
AdvertDataParser(const uint8_t app_data[], uint8_t app_data_len);
|
||||
|
||||
bool isValid() const { return _valid; }
|
||||
uint8_t getType() const { return _flags & 0x0F; }
|
||||
uint16_t getFeat1() const { return _extra1; }
|
||||
uint16_t getFeat2() const { return _extra2; }
|
||||
|
||||
bool hasName() const { return _name[0] != 0; }
|
||||
const char* getName() const { return _name; }
|
||||
|
|
|
|||
|
|
@ -373,6 +373,7 @@ bool BaseChatMesh::importContact(const uint8_t src_buf[], uint8_t len) {
|
|||
if (pkt) {
|
||||
if (pkt->readFrom(src_buf, len) && pkt->getPayloadType() == PAYLOAD_TYPE_ADVERT) {
|
||||
pkt->header |= ROUTE_TYPE_FLOOD; // simulate it being received flood-mode
|
||||
getTables()->clear(pkt); // remove packet hash from table, so we can receive/process it again
|
||||
_pendingLoopback = pkt; // loop-back, as if received over radio
|
||||
return true; // success
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -74,8 +74,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
|||
|
||||
void CommonCLI::savePrefs(FILESYSTEM* fs) {
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
fs->remove("/com_prefs");
|
||||
File file = fs->open("/com_prefs", FILE_O_WRITE);
|
||||
if (file) { file.seek(0); file.truncate(); }
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
File file = fs->open("/com_prefs", "w");
|
||||
#else
|
||||
|
|
@ -169,6 +169,9 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
|||
checkAdvertInterval();
|
||||
savePrefs();
|
||||
sprintf(reply, "password now: %s", _prefs->password); // echo back just to let admin know for sure!!
|
||||
} else if (memcmp(command, "clear stats", 11) == 0) {
|
||||
_callbacks->clearStats();
|
||||
strcpy(reply, "(OK - stats reset)");
|
||||
} else if (memcmp(command, "get ", 4) == 0) {
|
||||
const char* config = &command[4];
|
||||
if (memcmp(config, "af", 2) == 0) {
|
||||
|
|
@ -355,6 +358,6 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
|||
_callbacks->dumpLogFile();
|
||||
strcpy(reply, " EOF");
|
||||
} else {
|
||||
sprintf(reply, "Unknown: %s", command);
|
||||
strcpy(reply, "Unknown command");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ public:
|
|||
virtual void setTxPower(uint8_t power_dbm) = 0;
|
||||
virtual void formatNeighborsReply(char *reply) = 0;
|
||||
virtual const uint8_t* getSelfIdPubKey() = 0;
|
||||
virtual void clearStats() = 0;
|
||||
};
|
||||
|
||||
class CommonCLI {
|
||||
|
|
|
|||
|
|
@ -47,8 +47,8 @@ bool IdentityStore::save(const char *name, const mesh::LocalIdentity& id) {
|
|||
sprintf(filename, "%s/%s.id", _dir, name);
|
||||
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
_fs->remove(filename);
|
||||
File file = _fs->open(filename, FILE_O_WRITE);
|
||||
if (file) { file.seek(0); file.truncate(); }
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
File file = _fs->open(filename, "w");
|
||||
#else
|
||||
|
|
@ -69,8 +69,8 @@ bool IdentityStore::save(const char *name, const mesh::LocalIdentity& id, const
|
|||
sprintf(filename, "%s/%s.id", _dir, name);
|
||||
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
_fs->remove(filename);
|
||||
File file = _fs->open(filename, FILE_O_WRITE);
|
||||
if (file) { file.seek(0); file.truncate(); }
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
File file = _fs->open(filename, "w");
|
||||
#else
|
||||
|
|
|
|||
|
|
@ -44,6 +44,10 @@ void RadioLibWrapper::startRecv() {
|
|||
}
|
||||
}
|
||||
|
||||
bool RadioLibWrapper::isInRecvMode() const {
|
||||
return (state & ~STATE_INT_READY) == STATE_RX;
|
||||
}
|
||||
|
||||
int RadioLibWrapper::recvRaw(uint8_t* bytes, int sz) {
|
||||
if (state & STATE_INT_READY) {
|
||||
int len = _radio->getPacketLength();
|
||||
|
|
@ -77,13 +81,16 @@ uint32_t RadioLibWrapper::getEstAirtimeFor(int len_bytes) {
|
|||
return _radio->getTimeOnAir(len_bytes) / 1000;
|
||||
}
|
||||
|
||||
void RadioLibWrapper::startSendRaw(const uint8_t* bytes, int len) {
|
||||
state = STATE_TX_WAIT;
|
||||
bool RadioLibWrapper::startSendRaw(const uint8_t* bytes, int len) {
|
||||
_board->onBeforeTransmit();
|
||||
int err = _radio->startTransmit((uint8_t *) bytes, len);
|
||||
if (err != RADIOLIB_ERR_NONE) {
|
||||
MESH_DEBUG_PRINTLN("RadioLibWrapper: error: startTransmit(%d)", err);
|
||||
if (err == RADIOLIB_ERR_NONE) {
|
||||
state = STATE_TX_WAIT;
|
||||
return true;
|
||||
}
|
||||
MESH_DEBUG_PRINTLN("RadioLibWrapper: error: startTransmit(%d)", err);
|
||||
idle(); // trigger another startRecv()
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RadioLibWrapper::isSendComplete() {
|
||||
|
|
|
|||
|
|
@ -19,12 +19,15 @@ public:
|
|||
void begin() override;
|
||||
int recvRaw(uint8_t* bytes, int sz) override;
|
||||
uint32_t getEstAirtimeFor(int len_bytes) override;
|
||||
void startSendRaw(const uint8_t* bytes, int len) override;
|
||||
bool startSendRaw(const uint8_t* bytes, int len) override;
|
||||
bool isSendComplete() override;
|
||||
void onSendFinished() override;
|
||||
bool isInRecvMode() const override;
|
||||
|
||||
uint32_t getPacketsRecv() const { return n_recv; }
|
||||
uint32_t getPacketsSent() const { return n_sent; }
|
||||
void resetStats() { n_recv = n_sent = 0; }
|
||||
|
||||
virtual float getLastRSSI() const override;
|
||||
virtual float getLastSNR() const override;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,16 +2,18 @@
|
|||
|
||||
#include <CayenneLPP.h>
|
||||
|
||||
#define TELEM_PERM_BASE 0x01 // 'base' permission includes battery
|
||||
#define TELEM_PERM_LOCATION 0x02
|
||||
#define TELEM_PERM_BASE 0x01 // 'base' permission includes battery
|
||||
#define TELEM_PERM_LOCATION 0x02
|
||||
#define TELEM_PERM_ENVIRONMENT 0x04 // permission to access environment sensors
|
||||
|
||||
#define TELEM_CHANNEL_SELF 1 // LPP data channel for 'self' device
|
||||
|
||||
class SensorManager {
|
||||
public:
|
||||
double node_lat, node_lon; // modify these, if you want to affect Advert location
|
||||
double node_altitude; // altitude in meters
|
||||
|
||||
SensorManager() { node_lat = 0; node_lon = 0; }
|
||||
SensorManager() { node_lat = 0; node_lon = 0; node_altitude = 0; }
|
||||
virtual bool begin() { return false; }
|
||||
virtual bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { return false; }
|
||||
virtual void loop() { }
|
||||
|
|
|
|||
|
|
@ -80,7 +80,32 @@ public:
|
|||
return false;
|
||||
}
|
||||
|
||||
void clear(const mesh::Packet* packet) override {
|
||||
if (packet->getPayloadType() == PAYLOAD_TYPE_ACK) {
|
||||
uint32_t ack;
|
||||
memcpy(&ack, packet->payload, 4);
|
||||
for (int i = 0; i < MAX_PACKET_ACKS; i++) {
|
||||
if (ack == _acks[i]) {
|
||||
_acks[i] = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
uint8_t hash[MAX_HASH_SIZE];
|
||||
packet->calculatePacketHash(hash);
|
||||
|
||||
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) {
|
||||
memset(sp, 0, MAX_HASH_SIZE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t getNumDirectDups() const { return _direct_dups; }
|
||||
uint32_t getNumFloodDups() const { return _flood_dups; }
|
||||
|
||||
void resetStats() { _direct_dups = _flood_dups = 0; }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,6 +8,15 @@ PacketQueue::PacketQueue(int max_entries) {
|
|||
_num = 0;
|
||||
}
|
||||
|
||||
int PacketQueue::countBefore(uint32_t now) const {
|
||||
int n = 0;
|
||||
for (int j = 0; j < _num; j++) {
|
||||
if (_schedule_table[j] > now) continue; // scheduled for future... ignore for now
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
mesh::Packet* PacketQueue::get(uint32_t now) {
|
||||
uint8_t min_pri = 0xFF;
|
||||
int best_idx = -1;
|
||||
|
|
@ -81,8 +90,8 @@ mesh::Packet* StaticPoolPacketManager::getNextOutbound(uint32_t now) {
|
|||
return send_queue.get(now);
|
||||
}
|
||||
|
||||
int StaticPoolPacketManager::getOutboundCount() const {
|
||||
return send_queue.count();
|
||||
int StaticPoolPacketManager::getOutboundCount(uint32_t now) const {
|
||||
return send_queue.countBefore(now);
|
||||
}
|
||||
|
||||
int StaticPoolPacketManager::getFreeCount() const {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ public:
|
|||
mesh::Packet* get(uint32_t now);
|
||||
void add(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for);
|
||||
int count() const { return _num; }
|
||||
int countBefore(uint32_t now) const;
|
||||
mesh::Packet* itemAt(int i) const { return _table[i]; }
|
||||
mesh::Packet* removeByIdx(int i);
|
||||
};
|
||||
|
|
@ -27,7 +28,7 @@ public:
|
|||
void free(mesh::Packet* packet) override;
|
||||
void queueOutbound(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for) override;
|
||||
mesh::Packet* getNextOutbound(uint32_t now) override;
|
||||
int getOutboundCount() const override;
|
||||
int getOutboundCount(uint32_t now) const override;
|
||||
int getFreeCount() const override;
|
||||
mesh::Packet* getOutboundByIdx(int i) override;
|
||||
mesh::Packet* removeOutboundByIdx(int i) override;
|
||||
|
|
|
|||
|
|
@ -15,19 +15,19 @@
|
|||
#define P_LORA_MISO 13 //SX1262 MISO pin
|
||||
#define P_LORA_MOSI 11 //SX1262 MOSI pin
|
||||
|
||||
#define PIN_BOARD_SDA 17 //SDA for OLED, BME280, and QMC6310U (0x1C)
|
||||
#define PIN_BOARD_SCL 18 //SCL for OLED, BME280, and QMC6310U (0x1C)
|
||||
//#define PIN_BOARD_SDA 17 //SDA for OLED, BME280, and QMC6310U (0x1C)
|
||||
//#define PIN_BOARD_SCL 18 //SCL for OLED, BME280, and QMC6310U (0x1C)
|
||||
|
||||
#define PIN_BOARD_SDA1 42 //SDA for PMU and PFC8563 (RTC)
|
||||
#define PIN_BOARD_SCL1 41 //SCL for PMU and PFC8563 (RTC)
|
||||
#define PIN_PMU_IRQ 40 //IRQ pin for PMU
|
||||
|
||||
#define PIN_USER_BTN 0
|
||||
//#define PIN_USER_BTN 0
|
||||
|
||||
#define P_BOARD_SPI_MOSI 35 //SPI for SD Card and QMI8653 (IMU)
|
||||
#define P_BOARD_SPI_MISO 37 //SPI for SD Card and QMI8653 (IMU)
|
||||
#define P_BOARD_SPI_SCK 36 //SPI for SD Card and QMI8653 (IMU)
|
||||
#define P_BPARD_SPI_CS 47 //SPI for SD Card and QMI8653 (IMU)
|
||||
#define P_BPARD_SPI_CS 47 //Pin for SD Card CS
|
||||
#define P_BOARD_IMU_CS 34 //Pin for QMI8653 (IMU) CS
|
||||
|
||||
#define P_BOARD_IMU_INT 33 //IMU Int pin
|
||||
|
|
@ -36,7 +36,8 @@
|
|||
#define P_GPS_RX 9 //GPS RX pin
|
||||
#define P_GPS_TX 8 //GPS TX pin
|
||||
#define P_GPS_WAKE 7 //GPS Wakeup pin
|
||||
#define P_GPS_1PPS 6 //GPS 1PPS pin
|
||||
//#define P_GPS_1PPS 6 //GPS 1PPS pin - repurposed for lora tx led
|
||||
#define GPS_BAUD_RATE 9600
|
||||
|
||||
//I2C Wire addresses
|
||||
#define I2C_BME280_ADD 0x76 //BME280 sensor I2C address on Wire
|
||||
|
|
@ -57,9 +58,13 @@ public:
|
|||
void printPMU();
|
||||
#endif
|
||||
bool power_init();
|
||||
|
||||
void begin() {
|
||||
|
||||
ESP32Board::begin();
|
||||
|
||||
power_init();
|
||||
|
||||
esp_reset_reason_t reason = esp_reset_reason();
|
||||
if (reason == ESP_RST_DEEPSLEEP) {
|
||||
long wakeup_source = esp_sleep_get_ext1_wakeup_status();
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
static uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||
static esp_now_peer_info_t peerInfo;
|
||||
static bool is_send_complete = false;
|
||||
static volatile bool is_send_complete = false;
|
||||
static esp_err_t last_send_result;
|
||||
static uint8_t rx_buf[256];
|
||||
static uint8_t last_rx_len = 0;
|
||||
|
|
@ -44,6 +44,8 @@ void ESPNOWRadio::init() {
|
|||
peerInfo.channel = 0;
|
||||
peerInfo.encrypt = false;
|
||||
|
||||
is_send_complete = true;
|
||||
|
||||
// Add peer
|
||||
if (esp_now_add_peer(&peerInfo) == ESP_OK) {
|
||||
ESPNOW_DEBUG_PRINTLN("init success");
|
||||
|
|
@ -67,24 +69,30 @@ uint32_t ESPNOWRadio::intID() {
|
|||
return n + m;
|
||||
}
|
||||
|
||||
void ESPNOWRadio::startSendRaw(const uint8_t* bytes, int len) {
|
||||
bool ESPNOWRadio::startSendRaw(const uint8_t* bytes, int len) {
|
||||
// Send message via ESP-NOW
|
||||
is_send_complete = false;
|
||||
esp_err_t result = esp_now_send(broadcastAddress, bytes, len);
|
||||
if (result == ESP_OK) {
|
||||
n_sent++;
|
||||
ESPNOW_DEBUG_PRINTLN("Send success");
|
||||
} else {
|
||||
last_send_result = result;
|
||||
is_send_complete = true;
|
||||
ESPNOW_DEBUG_PRINTLN("Send failed: %d", result);
|
||||
return true;
|
||||
}
|
||||
last_send_result = result;
|
||||
is_send_complete = true;
|
||||
ESPNOW_DEBUG_PRINTLN("Send failed: %d", result);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ESPNOWRadio::isSendComplete() {
|
||||
return is_send_complete;
|
||||
}
|
||||
void ESPNOWRadio::onSendFinished() {
|
||||
is_send_complete = true;
|
||||
}
|
||||
|
||||
bool ESPNOWRadio::isInRecvMode() const {
|
||||
return is_send_complete; // if NO send in progress, then we're in Rx mode
|
||||
}
|
||||
|
||||
float ESPNOWRadio::getLastRSSI() const { return 0; }
|
||||
|
|
|
|||
|
|
@ -12,12 +12,15 @@ public:
|
|||
void init();
|
||||
int recvRaw(uint8_t* bytes, int sz) override;
|
||||
uint32_t getEstAirtimeFor(int len_bytes) override;
|
||||
void startSendRaw(const uint8_t* bytes, int len) override;
|
||||
bool startSendRaw(const uint8_t* bytes, int len) override;
|
||||
bool isSendComplete() override;
|
||||
void onSendFinished() override;
|
||||
bool isInRecvMode() const override;
|
||||
|
||||
uint32_t getPacketsRecv() const { return n_recv; }
|
||||
uint32_t getPacketsSent() const { return n_sent; }
|
||||
void resetStats() { n_recv = n_sent = 0; }
|
||||
|
||||
virtual float getLastRSSI() const override;
|
||||
virtual float getLastSNR() const override;
|
||||
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ size_t SerialBLEInterface::writeFrame(const uint8_t src[], size_t len) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
#define BLE_WRITE_MIN_INTERVAL 20
|
||||
#define BLE_WRITE_MIN_INTERVAL 60
|
||||
|
||||
bool SerialBLEInterface::isWriteBusy() const {
|
||||
return millis() < _last_write + BLE_WRITE_MIN_INTERVAL; // still too soon to start another write?
|
||||
|
|
|
|||
|
|
@ -26,6 +26,10 @@ void RAK4631Board::begin() {
|
|||
pinMode(PIN_USER_BTN, INPUT_PULLUP);
|
||||
#endif
|
||||
|
||||
#ifdef PIN_USER_BTN_ANA
|
||||
pinMode(PIN_USER_BTN_ANA, INPUT_PULLUP);
|
||||
#endif
|
||||
|
||||
#if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL)
|
||||
Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL);
|
||||
#endif
|
||||
|
|
@ -76,6 +80,11 @@ bool RAK4631Board::startOTAUpdate(const char* id, char reply[]) {
|
|||
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
|
||||
Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds
|
||||
|
||||
strcpy(reply, "OK - started");
|
||||
uint8_t mac_addr[6];
|
||||
memset(mac_addr, 0, sizeof(mac_addr));
|
||||
Bluefruit.getAddr(mac_addr);
|
||||
sprintf(reply, "OK - mac: %02X:%02X:%02X:%02X:%02X:%02X",
|
||||
mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ size_t SerialBLEInterface::writeFrame(const uint8_t src[], size_t len) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
#define BLE_WRITE_MIN_INTERVAL 20
|
||||
#define BLE_WRITE_MIN_INTERVAL 60
|
||||
|
||||
bool SerialBLEInterface::isWriteBusy() const {
|
||||
return millis() < _last_write + BLE_WRITE_MIN_INTERVAL; // still too soon to start another write?
|
||||
|
|
|
|||
231
src/helpers/sensors/EnvironmentSensorManager.cpp
Normal file
231
src/helpers/sensors/EnvironmentSensorManager.cpp
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
#include "EnvironmentSensorManager.h"
|
||||
|
||||
#if ENV_INCLUDE_AHTX0
|
||||
#define TELEM_AHTX_ADDRESS 0x38 // AHT10, AHT20 temperature and humidity sensor I2C address
|
||||
#include <Adafruit_AHTX0.h>
|
||||
static Adafruit_AHTX0 AHTX0;
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_BME280
|
||||
#define TELEM_BME280_ADDRESS 0x76 // BME280 environmental sensor I2C address
|
||||
#define TELEM_BME280_SEALEVELPRESSURE_HPA (1013.25) // Athmospheric pressure at sea level
|
||||
#include <Adafruit_BME280.h>
|
||||
static Adafruit_BME280 BME280;
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_INA3221
|
||||
#define TELEM_INA3221_ADDRESS 0x42 // INA3221 3 channel current sensor I2C address
|
||||
#define TELEM_INA3221_SHUNT_VALUE 0.100 // most variants will have a 0.1 ohm shunts
|
||||
#define TELEM_INA3221_NUM_CHANNELS 3
|
||||
#include <Adafruit_INA3221.h>
|
||||
static Adafruit_INA3221 INA3221;
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_INA219
|
||||
#define TELEM_INA219_ADDRESS 0x40 // INA219 single channel current sensor I2C address
|
||||
#include <Adafruit_INA219.h>
|
||||
static Adafruit_INA219 INA219(TELEM_INA219_ADDRESS);
|
||||
#endif
|
||||
|
||||
bool EnvironmentSensorManager::begin() {
|
||||
#if ENV_INCLUDE_GPS
|
||||
initBasicGPS();
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_AHTX0
|
||||
if (AHTX0.begin(&Wire, 0, TELEM_AHTX_ADDRESS)) {
|
||||
MESH_DEBUG_PRINTLN("Found AHT10/AHT20 at address: %02X", TELEM_AHTX_ADDRESS);
|
||||
AHTX0_initialized = true;
|
||||
} else {
|
||||
AHTX0_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("AHT10/AHT20 was not found at I2C address %02X", TELEM_AHTX_ADDRESS);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_BME280
|
||||
if (BME280.begin(TELEM_BME280_ADDRESS, &Wire)) {
|
||||
MESH_DEBUG_PRINTLN("Found BME280 at address: %02X", TELEM_BME280_ADDRESS);
|
||||
MESH_DEBUG_PRINTLN("BME sensor ID: %02X", BME280.sensorID());
|
||||
BME280_initialized = true;
|
||||
} else {
|
||||
BME280_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("BME280 was not found at I2C address %02X", TELEM_BME280_ADDRESS);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_INA3221
|
||||
if (INA3221.begin(TELEM_INA3221_ADDRESS, &Wire)) {
|
||||
MESH_DEBUG_PRINTLN("Found INA3221 at address: %02X", TELEM_INA3221_ADDRESS);
|
||||
MESH_DEBUG_PRINTLN("%04X %04X", INA3221.getDieID(), INA3221.getManufacturerID());
|
||||
|
||||
for(int i = 0; i < 3; i++) {
|
||||
INA3221.setShuntResistance(i, TELEM_INA3221_SHUNT_VALUE);
|
||||
}
|
||||
INA3221_initialized = true;
|
||||
} else {
|
||||
INA3221_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("INA3221 was not found at I2C address %02X", TELEM_INA3221_ADDRESS);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_INA219
|
||||
if (INA219.begin(&Wire)) {
|
||||
MESH_DEBUG_PRINTLN("Found INA219 at address: %02X", TELEM_INA219_ADDRESS);
|
||||
INA219_initialized = true;
|
||||
} else {
|
||||
INA219_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("INA219 was not found at I2C address %02X", TELEM_INA219_ADDRESS);
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) {
|
||||
next_available_channel = TELEM_CHANNEL_SELF + 1;
|
||||
|
||||
if (requester_permissions & TELEM_PERM_LOCATION) {
|
||||
telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, 0.0f); // allow lat/lon via telemetry even if no GPS is detected
|
||||
}
|
||||
|
||||
if (requester_permissions & TELEM_PERM_ENVIRONMENT) {
|
||||
|
||||
#if ENV_INCLUDE_AHTX0
|
||||
if (AHTX0_initialized) {
|
||||
sensors_event_t humidity, temp;
|
||||
AHTX0.getEvent(&humidity, &temp);
|
||||
telemetry.addTemperature(TELEM_CHANNEL_SELF, temp.temperature);
|
||||
telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, humidity.relative_humidity);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_BME280
|
||||
if (BME280_initialized) {
|
||||
telemetry.addTemperature(TELEM_CHANNEL_SELF, BME280.readTemperature());
|
||||
telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, BME280.readHumidity());
|
||||
telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BME280.readPressure());
|
||||
telemetry.addAltitude(TELEM_CHANNEL_SELF, BME280.readAltitude(TELEM_BME280_SEALEVELPRESSURE_HPA));
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_INA3221
|
||||
if (INA3221_initialized) {
|
||||
for(int i = 0; i < TELEM_INA3221_NUM_CHANNELS; i++) {
|
||||
// add only enabled INA3221 channels to telemetry
|
||||
if (INA3221.isChannelEnabled(i)) {
|
||||
float voltage = INA3221.getBusVoltage(i);
|
||||
float current = INA3221.getCurrentAmps(i);
|
||||
telemetry.addVoltage(next_available_channel, voltage);
|
||||
telemetry.addCurrent(next_available_channel, current);
|
||||
telemetry.addPower(next_available_channel, voltage * current);
|
||||
next_available_channel++;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_INA219
|
||||
if (INA219_initialized) {
|
||||
telemetry.addVoltage(next_available_channel, INA219.getBusVoltage_V());
|
||||
telemetry.addCurrent(next_available_channel, INA219.getCurrent_mA() / 1000);
|
||||
telemetry.addPower(next_available_channel, INA219.getPower_mW() / 1000);
|
||||
next_available_channel++;
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
int EnvironmentSensorManager::getNumSettings() const {
|
||||
#if ENV_INCLUDE_GPS
|
||||
return gps_detected ? 1 : 0; // only show GPS setting if GPS is detected
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
const char* EnvironmentSensorManager::getSettingName(int i) const {
|
||||
#if ENV_INCLUDE_GPS
|
||||
return (gps_detected && i == 0) ? "gps" : NULL;
|
||||
#else
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
const char* EnvironmentSensorManager::getSettingValue(int i) const {
|
||||
#if ENV_INCLUDE_GPS
|
||||
if (gps_detected && i == 0) {
|
||||
return gps_active ? "1" : "0";
|
||||
}
|
||||
#endif
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool EnvironmentSensorManager::setSettingValue(const char* name, const char* value) {
|
||||
#if ENV_INCLUDE_GPS
|
||||
if (gps_detected && strcmp(name, "gps") == 0) {
|
||||
if (strcmp(value, "0") == 0) {
|
||||
stop_gps();
|
||||
} else {
|
||||
start_gps();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
return false; // not supported
|
||||
}
|
||||
|
||||
#if ENV_INCLUDE_GPS
|
||||
void EnvironmentSensorManager::initBasicGPS() {
|
||||
Serial1.setPins(PIN_GPS_TX, PIN_GPS_RX);
|
||||
Serial1.begin(9600);
|
||||
|
||||
// Try to detect if GPS is physically connected to determine if we should expose the setting
|
||||
pinMode(PIN_GPS_EN, OUTPUT);
|
||||
digitalWrite(PIN_GPS_EN, HIGH); // Power on GPS
|
||||
|
||||
// Give GPS a moment to power up and send data
|
||||
delay(1000);
|
||||
|
||||
// We'll consider GPS detected if we see any data on Serial1
|
||||
gps_detected = (Serial1.available() > 0);
|
||||
|
||||
if (gps_detected) {
|
||||
MESH_DEBUG_PRINTLN("GPS detected");
|
||||
digitalWrite(PIN_GPS_EN, LOW); // Power off GPS until the setting is changed
|
||||
} else {
|
||||
MESH_DEBUG_PRINTLN("No GPS detected");
|
||||
digitalWrite(PIN_GPS_EN, LOW);
|
||||
}
|
||||
}
|
||||
|
||||
void EnvironmentSensorManager::start_gps() {
|
||||
gps_active = true;
|
||||
pinMode(PIN_GPS_EN, OUTPUT);
|
||||
digitalWrite(PIN_GPS_EN, HIGH);
|
||||
}
|
||||
|
||||
void EnvironmentSensorManager::stop_gps() {
|
||||
gps_active = false;
|
||||
pinMode(PIN_GPS_EN, OUTPUT);
|
||||
digitalWrite(PIN_GPS_EN, LOW);
|
||||
}
|
||||
|
||||
void EnvironmentSensorManager::loop() {
|
||||
static long next_gps_update = 0;
|
||||
|
||||
_location->loop();
|
||||
|
||||
if (millis() > next_gps_update) {
|
||||
if (_location->isValid()) {
|
||||
node_lat = ((double)_location->getLatitude())/1000000.;
|
||||
node_lon = ((double)_location->getLongitude())/1000000.;
|
||||
MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon);
|
||||
}
|
||||
next_gps_update = millis() + 1000;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
42
src/helpers/sensors/EnvironmentSensorManager.h
Normal file
42
src/helpers/sensors/EnvironmentSensorManager.h
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
#pragma once
|
||||
|
||||
#include <Mesh.h>
|
||||
#include <helpers/SensorManager.h>
|
||||
#include <helpers/sensors/LocationProvider.h>
|
||||
|
||||
class EnvironmentSensorManager : public SensorManager {
|
||||
protected:
|
||||
int next_available_channel = TELEM_CHANNEL_SELF + 1;
|
||||
|
||||
bool AHTX0_initialized = false;
|
||||
bool BME280_initialized = false;
|
||||
bool INA3221_initialized = false;
|
||||
bool INA219_initialized = false;
|
||||
|
||||
bool gps_detected = false;
|
||||
bool gps_active = false;
|
||||
|
||||
#if ENV_INCLUDE_GPS
|
||||
LocationProvider* _location;
|
||||
void start_gps();
|
||||
void stop_gps();
|
||||
void initBasicGPS();
|
||||
#endif
|
||||
|
||||
|
||||
public:
|
||||
#if ENV_INCLUDE_GPS
|
||||
EnvironmentSensorManager(LocationProvider &location): _location(&location){};
|
||||
#else
|
||||
EnvironmentSensorManager(){};
|
||||
#endif
|
||||
bool begin() override;
|
||||
bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override;
|
||||
#if ENV_INCLUDE_GPS
|
||||
void loop() override;
|
||||
#endif
|
||||
int getNumSettings() const override;
|
||||
const char* getSettingName(int i) const override;
|
||||
const char* getSettingValue(int i) const override;
|
||||
bool setSettingValue(const char* name, const char* value) override;
|
||||
};
|
||||
|
|
@ -8,6 +8,7 @@ class LocationProvider {
|
|||
public:
|
||||
virtual long getLatitude() = 0;
|
||||
virtual long getLongitude() = 0;
|
||||
virtual long getAltitude() = 0;
|
||||
virtual bool isValid() = 0;
|
||||
virtual long getTimestamp() = 0;
|
||||
virtual void reset();
|
||||
|
|
|
|||
|
|
@ -61,6 +61,11 @@ public :
|
|||
|
||||
long getLatitude() override { return nmea.getLatitude(); }
|
||||
long getLongitude() override { return nmea.getLongitude(); }
|
||||
long getAltitude() override {
|
||||
long alt = 0;
|
||||
nmea.getAltitude(alt);
|
||||
return alt;
|
||||
}
|
||||
bool isValid() override { return nmea.isValid(); }
|
||||
|
||||
long getTimestamp() override {
|
||||
|
|
|
|||
|
|
@ -126,6 +126,9 @@ InternalFileSystem::InternalFileSystem(void)
|
|||
|
||||
bool InternalFileSystem::begin(void)
|
||||
{
|
||||
#ifdef FORMAT_FS
|
||||
this->format();
|
||||
#endif
|
||||
// failed to mount, erase all sector then format and mount again
|
||||
if ( !Adafruit_LittleFS::begin() )
|
||||
{
|
||||
|
|
|
|||
91
src/helpers/ui/SH1106Display.cpp
Normal file
91
src/helpers/ui/SH1106Display.cpp
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
#include "SH1106Display.h"
|
||||
#include <Adafruit_GrayOLED.h>
|
||||
#include "Adafruit_SH110X.h"
|
||||
|
||||
bool SH1106Display::i2c_probe(TwoWire &wire, uint8_t addr)
|
||||
{
|
||||
wire.beginTransmission(addr);
|
||||
uint8_t error = wire.endTransmission();
|
||||
return (error == 0);
|
||||
}
|
||||
|
||||
bool SH1106Display::begin()
|
||||
{
|
||||
return display.begin(DISPLAY_ADDRESS, true) && i2c_probe(Wire, DISPLAY_ADDRESS);
|
||||
}
|
||||
|
||||
void SH1106Display::turnOn()
|
||||
{
|
||||
display.oled_command(SH110X_DISPLAYON);
|
||||
_isOn = true;
|
||||
}
|
||||
|
||||
void SH1106Display::turnOff()
|
||||
{
|
||||
display.oled_command(SH110X_DISPLAYOFF);
|
||||
_isOn = false;
|
||||
}
|
||||
|
||||
void SH1106Display::clear()
|
||||
{
|
||||
display.clearDisplay();
|
||||
display.display();
|
||||
}
|
||||
|
||||
void SH1106Display::startFrame(Color bkg)
|
||||
{
|
||||
display.clearDisplay(); // TODO: apply 'bkg'
|
||||
_color = SH110X_WHITE;
|
||||
display.setTextColor(_color);
|
||||
display.setTextSize(1);
|
||||
display.cp437(true); // Use full 256 char 'Code Page 437' font
|
||||
}
|
||||
|
||||
void SH1106Display::setTextSize(int sz)
|
||||
{
|
||||
display.setTextSize(sz);
|
||||
}
|
||||
|
||||
void SH1106Display::setColor(Color c)
|
||||
{
|
||||
_color = (c != 0) ? SH110X_WHITE : SH110X_BLACK;
|
||||
display.setTextColor(_color);
|
||||
}
|
||||
|
||||
void SH1106Display::setCursor(int x, int y)
|
||||
{
|
||||
display.setCursor(x, y);
|
||||
}
|
||||
|
||||
void SH1106Display::print(const char *str)
|
||||
{
|
||||
display.print(str);
|
||||
}
|
||||
|
||||
void SH1106Display::fillRect(int x, int y, int w, int h)
|
||||
{
|
||||
display.fillRect(x, y, w, h, _color);
|
||||
}
|
||||
|
||||
void SH1106Display::drawRect(int x, int y, int w, int h)
|
||||
{
|
||||
display.drawRect(x, y, w, h, _color);
|
||||
}
|
||||
|
||||
void SH1106Display::drawXbm(int x, int y, const uint8_t *bits, int w, int h)
|
||||
{
|
||||
display.drawBitmap(x, y, bits, w, h, SH110X_WHITE);
|
||||
}
|
||||
|
||||
uint16_t SH1106Display::getTextWidth(const char *str)
|
||||
{
|
||||
int16_t x1, y1;
|
||||
uint16_t w, h;
|
||||
display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h);
|
||||
return w;
|
||||
}
|
||||
|
||||
void SH1106Display::endFrame()
|
||||
{
|
||||
display.display();
|
||||
}
|
||||
43
src/helpers/ui/SH1106Display.h
Normal file
43
src/helpers/ui/SH1106Display.h
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
#include "DisplayDriver.h"
|
||||
#include <Wire.h>
|
||||
#include <Adafruit_GFX.h>
|
||||
#define SH110X_NO_SPLASH
|
||||
#include <Adafruit_SH110X.h>
|
||||
|
||||
#ifndef PIN_OLED_RESET
|
||||
#define PIN_OLED_RESET -1
|
||||
#endif
|
||||
|
||||
#ifndef DISPLAY_ADDRESS
|
||||
#define DISPLAY_ADDRESS 0x3C
|
||||
#endif
|
||||
|
||||
class SH1106Display : public DisplayDriver
|
||||
{
|
||||
Adafruit_SH1106G display;
|
||||
bool _isOn;
|
||||
uint8_t _color;
|
||||
|
||||
bool i2c_probe(TwoWire &wire, uint8_t addr);
|
||||
|
||||
public:
|
||||
SH1106Display() : DisplayDriver(128, 64), display(128, 64, &Wire, PIN_OLED_RESET) { _isOn = false; }
|
||||
bool begin();
|
||||
|
||||
bool isOn() override { return _isOn; }
|
||||
void turnOn() override;
|
||||
void turnOff() override;
|
||||
void clear() override;
|
||||
void startFrame(Color bkg = DARK) override;
|
||||
void setTextSize(int sz) override;
|
||||
void setColor(Color 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,5 +1,3 @@
|
|||
#ifdef ST7735
|
||||
|
||||
#include "ST7735Display.h"
|
||||
|
||||
#ifndef DISPLAY_ROTATION
|
||||
|
|
@ -130,5 +128,3 @@ uint16_t ST7735Display::getTextWidth(const char* str) {
|
|||
void ST7735Display::endFrame() {
|
||||
// display.display();
|
||||
}
|
||||
|
||||
#endif
|
||||
54
src/helpers/ui/buzzer.cpp
Normal file
54
src/helpers/ui/buzzer.cpp
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
#ifdef PIN_BUZZER
|
||||
#include "buzzer.h"
|
||||
|
||||
void genericBuzzer::begin() {
|
||||
// Serial.print("DBG: Setting up buzzer on pin ");
|
||||
// Serial.println(PIN_BUZZER);
|
||||
#ifdef PIN_BUZZER_EN
|
||||
pinMode(PIN_BUZZER_EN, OUTPUT);
|
||||
digitalWrite(PIN_BUZZER_EN, HIGH);
|
||||
#endif
|
||||
|
||||
quiet(false);
|
||||
pinMode(PIN_BUZZER, OUTPUT);
|
||||
startup();
|
||||
}
|
||||
|
||||
void genericBuzzer::play(const char *melody) {
|
||||
if (isPlaying()) // interrupt existing
|
||||
{
|
||||
rtttl::stop();
|
||||
}
|
||||
|
||||
if (_is_quiet) return;
|
||||
|
||||
rtttl::begin(PIN_BUZZER,melody);
|
||||
// Serial.print("DBG: Playing melody - isQuiet: ");
|
||||
// Serial.println(isQuiet());
|
||||
}
|
||||
|
||||
bool genericBuzzer::isPlaying() {
|
||||
return rtttl::isPlaying();
|
||||
}
|
||||
|
||||
void genericBuzzer::loop() {
|
||||
if (!rtttl::done()) rtttl::play();
|
||||
}
|
||||
|
||||
void genericBuzzer::startup() {
|
||||
play(startup_song);
|
||||
}
|
||||
|
||||
void genericBuzzer::shutdown() {
|
||||
play(shutdown_song);
|
||||
}
|
||||
|
||||
void genericBuzzer::quiet(bool buzzer_state) {
|
||||
_is_quiet = buzzer_state;
|
||||
}
|
||||
|
||||
bool genericBuzzer::isQuiet() {
|
||||
return _is_quiet;
|
||||
}
|
||||
|
||||
#endif // ifdef PIN_BUZZER
|
||||
37
src/helpers/ui/buzzer.h
Normal file
37
src/helpers/ui/buzzer.h
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <NonBlockingRtttl.h>
|
||||
|
||||
/* class abstracts underlying RTTTL library
|
||||
|
||||
Just a simple imlementation to start. At the moment use same
|
||||
melody for message and discovery
|
||||
Suggest enum type for different sounds
|
||||
- on message
|
||||
- on discovery
|
||||
|
||||
TODO
|
||||
- make message ring tone configurable
|
||||
|
||||
*/
|
||||
|
||||
class genericBuzzer
|
||||
{
|
||||
public:
|
||||
void begin(); // set up buzzer port
|
||||
void play(const char *melody); // Generic play function
|
||||
void loop(); // loop driven-nonblocking
|
||||
void startup(); // play startup sound
|
||||
void shutdown(); // play shutdown sound
|
||||
bool isPlaying(); // returns true if a sound is still playing else false
|
||||
void quiet(bool buzzer_state); // enables or disables the buzzer
|
||||
bool isQuiet(); // get buzzer state on/off
|
||||
|
||||
private:
|
||||
// gemini's picks:
|
||||
const char *startup_song = "Startup:d=4,o=5,b=160:16c6,16e6,8g6";
|
||||
const char *shutdown_song = "Shutdown:d=4,o=5,b=100:8g5,16e5,16c5";
|
||||
|
||||
bool _is_quiet = true;
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue