mirror of https://github.com/meshcore-dev/MeshCore
92 changed files with 4084 additions and 1986 deletions
@ -0,0 +1,84 @@ |
|||
# .clang-format |
|||
Language: Cpp |
|||
AccessModifierOffset: -2 |
|||
AlignAfterOpenBracket: Align |
|||
AlignConsecutiveAssignments: false |
|||
AlignConsecutiveDeclarations: false |
|||
AlignConsecutiveMacros: |
|||
Enabled: true |
|||
AcrossEmptyLines: true |
|||
AcrossComments: true |
|||
AlignOperands: true |
|||
AlignTrailingComments: true |
|||
AllowAllParametersOfDeclarationOnNextLine: false |
|||
AllowShortBlocksOnASingleLine: false |
|||
AllowShortCaseLabelsOnASingleLine: false |
|||
AllowShortFunctionsOnASingleLine: Inline |
|||
AllowShortIfStatementsOnASingleLine: true |
|||
AllowShortLoopsOnASingleLine: false |
|||
AlwaysBreakAfterDefinitionReturnType: None |
|||
AlwaysBreakAfterReturnType: None |
|||
AlwaysBreakBeforeMultilineStrings: false |
|||
AlwaysBreakTemplateDeclarations: No |
|||
BinPackArguments: true |
|||
BinPackParameters: true |
|||
BraceWrapping: |
|||
AfterClass: false |
|||
AfterControlStatement: false |
|||
AfterEnum: false |
|||
AfterFunction: false |
|||
AfterNamespace: false |
|||
AfterObjCDeclaration: false |
|||
AfterStruct: false |
|||
AfterUnion: false |
|||
BeforeCatch: true |
|||
BeforeElse: true |
|||
IndentBraces: false |
|||
BreakBeforeBinaryOperators: None |
|||
BreakBeforeBraces: Attach |
|||
BreakBeforeTernaryOperators: true |
|||
BreakConstructorInitializersBeforeComma: false |
|||
ColumnLimit: 110 |
|||
CommentPragmas: '^ IWYU pragma:' |
|||
CompactNamespaces: false |
|||
ConstructorInitializerAllOnOneLineOrOnePerLine: false |
|||
Cpp11BracedListStyle: false |
|||
DerivePointerAlignment: false |
|||
DisableFormat: false |
|||
IncludeBlocks: Regroup |
|||
IndentCaseLabels: false |
|||
IndentPPDirectives: None |
|||
IndentWidth: 2 |
|||
IndentWrappedFunctionNames: false |
|||
KeepEmptyLinesAtTheStartOfBlocks: true |
|||
MacroBlockBegin: '' |
|||
MacroBlockEnd: '' |
|||
MaxEmptyLinesToKeep: 1 |
|||
NamespaceIndentation: None |
|||
ObjCBinPackProtocolList: Auto |
|||
PenaltyBreakBeforeFirstCallParameter: 19 |
|||
PenaltyBreakComment: 300 |
|||
PenaltyBreakFirstLessLess: 120 |
|||
PenaltyBreakString: 1000 |
|||
PenaltyExcessCharacter: 100000 |
|||
PenaltyReturnTypeOnItsOwnLine: 60 |
|||
PointerAlignment: Right |
|||
ReflowComments: true |
|||
SortIncludes: true |
|||
SpaceAfterCStyleCast: false |
|||
SpaceAfterTemplateKeyword: true |
|||
SpaceBeforeAssignmentOperators: true |
|||
SpaceBeforeCtorInitializerColon: true |
|||
SpaceBeforeInheritanceColon: true |
|||
SpaceBeforeParens: ControlStatements |
|||
SpaceInEmptyParentheses: false |
|||
SpacesBeforeTrailingComments: 1 |
|||
SpacesInAngles: false |
|||
SpacesInContainerLiterals: false |
|||
SpacesInCStyleCastParentheses: false |
|||
SpacesInParentheses: false |
|||
SpacesInSquareBrackets: false |
|||
Standard: Auto |
|||
TabWidth: 2 |
|||
UseTab: Never |
|||
AlignEscapedNewlines: LeftWithLastLine |
|||
@ -0,0 +1,125 @@ |
|||
#include "Button.h" |
|||
|
|||
Button::Button(uint8_t pin, bool activeState) |
|||
: _pin(pin), _activeState(activeState), _isAnalog(false), _analogThreshold(20) { |
|||
_currentState = false; // Initialize as not pressed
|
|||
_lastState = _currentState; |
|||
} |
|||
|
|||
Button::Button(uint8_t pin, bool activeState, bool isAnalog, uint16_t analogThreshold) |
|||
: _pin(pin), _activeState(activeState), _isAnalog(isAnalog), _analogThreshold(analogThreshold) { |
|||
_currentState = false; // Initialize as not pressed
|
|||
_lastState = _currentState; |
|||
} |
|||
|
|||
void Button::begin() { |
|||
_currentState = readButton(); |
|||
_lastState = _currentState; |
|||
} |
|||
|
|||
void Button::update() { |
|||
uint32_t now = millis(); |
|||
|
|||
// Read button at specified interval
|
|||
if (now - _lastReadTime < BUTTON_READ_INTERVAL_MS) { |
|||
return; |
|||
} |
|||
_lastReadTime = now; |
|||
|
|||
bool newState = readButton(); |
|||
|
|||
// Check if state has changed
|
|||
if (newState != _lastState) { |
|||
_stateChangeTime = now; |
|||
} |
|||
|
|||
// Debounce check
|
|||
if ((now - _stateChangeTime) > BUTTON_DEBOUNCE_TIME_MS) { |
|||
if (newState != _currentState) { |
|||
_currentState = newState; |
|||
handleStateChange(); |
|||
} |
|||
} |
|||
|
|||
_lastState = newState; |
|||
|
|||
// Handle multi-click timeout
|
|||
if (_state == WAITING_FOR_MULTI_CLICK && (now - _releaseTime) > BUTTON_CLICK_TIMEOUT_MS) { |
|||
// Timeout reached, process the clicks
|
|||
if (_clickCount == 1) { |
|||
triggerEvent(SHORT_PRESS); |
|||
} else if (_clickCount == 2) { |
|||
triggerEvent(DOUBLE_PRESS); |
|||
} else if (_clickCount >= 3) { |
|||
triggerEvent(TRIPLE_PRESS); |
|||
} |
|||
_clickCount = 0; |
|||
_state = IDLE; |
|||
} |
|||
|
|||
// Handle long press while button is held
|
|||
if (_state == PRESSED && (now - _pressTime) > BUTTON_LONG_PRESS_TIME_MS) { |
|||
triggerEvent(LONG_PRESS); |
|||
_state = IDLE; // Prevent multiple press events
|
|||
_clickCount = 0; |
|||
} |
|||
} |
|||
|
|||
bool Button::readButton() { |
|||
if (_isAnalog) { |
|||
return (analogRead(_pin) < _analogThreshold); |
|||
} else { |
|||
return (digitalRead(_pin) == _activeState); |
|||
} |
|||
} |
|||
|
|||
void Button::handleStateChange() { |
|||
uint32_t now = millis(); |
|||
|
|||
if (_currentState) { |
|||
// Button pressed
|
|||
_pressTime = now; |
|||
_state = PRESSED; |
|||
triggerEvent(ANY_PRESS); |
|||
} else { |
|||
// Button released
|
|||
if (_state == PRESSED) { |
|||
uint32_t pressDuration = now - _pressTime; |
|||
|
|||
if (pressDuration < BUTTON_LONG_PRESS_TIME_MS) { |
|||
// Short press detected
|
|||
_clickCount++; |
|||
_releaseTime = now; |
|||
_state = WAITING_FOR_MULTI_CLICK; |
|||
} else { |
|||
// Long press already handled in update()
|
|||
_state = IDLE; |
|||
_clickCount = 0; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
void Button::triggerEvent(EventType event) { |
|||
_lastEvent = event; |
|||
|
|||
switch (event) { |
|||
case ANY_PRESS: |
|||
if (_onAnyPress) _onAnyPress(); |
|||
break; |
|||
case SHORT_PRESS: |
|||
if (_onShortPress) _onShortPress(); |
|||
break; |
|||
case DOUBLE_PRESS: |
|||
if (_onDoublePress) _onDoublePress(); |
|||
break; |
|||
case TRIPLE_PRESS: |
|||
if (_onTriplePress) _onTriplePress(); |
|||
break; |
|||
case LONG_PRESS: |
|||
if (_onLongPress) _onLongPress(); |
|||
break; |
|||
default: |
|||
break; |
|||
} |
|||
} |
|||
@ -0,0 +1,77 @@ |
|||
#pragma once |
|||
|
|||
#include <Arduino.h> |
|||
#include <functional> |
|||
|
|||
// Button timing configuration
|
|||
#define BUTTON_DEBOUNCE_TIME_MS 50 // Debounce time in ms
|
|||
#define BUTTON_CLICK_TIMEOUT_MS 500 // Max time between clicks for multi-click
|
|||
#define BUTTON_LONG_PRESS_TIME_MS 3000 // Time to trigger long press (3 seconds)
|
|||
#define BUTTON_READ_INTERVAL_MS 10 // How often to read the button
|
|||
|
|||
class Button { |
|||
public: |
|||
enum EventType { |
|||
NONE, |
|||
SHORT_PRESS, |
|||
DOUBLE_PRESS, |
|||
TRIPLE_PRESS, |
|||
LONG_PRESS, |
|||
ANY_PRESS |
|||
}; |
|||
|
|||
using EventCallback = std::function<void()>; |
|||
|
|||
Button(uint8_t pin, bool activeState = LOW); |
|||
Button(uint8_t pin, bool activeState, bool isAnalog, uint16_t analogThreshold = 20); |
|||
|
|||
void begin(); |
|||
void update(); |
|||
|
|||
// Set callbacks for different events
|
|||
void onShortPress(EventCallback callback) { _onShortPress = callback; } |
|||
void onDoublePress(EventCallback callback) { _onDoublePress = callback; } |
|||
void onTriplePress(EventCallback callback) { _onTriplePress = callback; } |
|||
void onLongPress(EventCallback callback) { _onLongPress = callback; } |
|||
void onAnyPress(EventCallback callback) { _onAnyPress = callback; } |
|||
|
|||
// State getters
|
|||
bool isPressed() const { return _currentState; } |
|||
EventType getLastEvent() const { return _lastEvent; } |
|||
|
|||
private: |
|||
enum State { |
|||
IDLE, |
|||
PRESSED, |
|||
RELEASED, |
|||
WAITING_FOR_MULTI_CLICK |
|||
}; |
|||
|
|||
uint8_t _pin; |
|||
bool _activeState; |
|||
bool _isAnalog; |
|||
uint16_t _analogThreshold; |
|||
|
|||
State _state = IDLE; |
|||
bool _currentState; |
|||
bool _lastState; |
|||
|
|||
uint32_t _stateChangeTime = 0; |
|||
uint32_t _pressTime = 0; |
|||
uint32_t _releaseTime = 0; |
|||
uint32_t _lastReadTime = 0; |
|||
|
|||
uint8_t _clickCount = 0; |
|||
EventType _lastEvent = NONE; |
|||
|
|||
// Callbacks
|
|||
EventCallback _onShortPress = nullptr; |
|||
EventCallback _onDoublePress = nullptr; |
|||
EventCallback _onTriplePress = nullptr; |
|||
EventCallback _onLongPress = nullptr; |
|||
EventCallback _onAnyPress = nullptr; |
|||
|
|||
bool readButton(); |
|||
void handleStateChange(); |
|||
void triggerEvent(EventType event); |
|||
}; |
|||
@ -0,0 +1,391 @@ |
|||
#include <Arduino.h> |
|||
#include "DataStore.h" |
|||
|
|||
DataStore::DataStore(FILESYSTEM& fs, mesh::RTCClock& clock) : _fs(&fs), _clock(&clock), |
|||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) |
|||
identity_store(fs, "") |
|||
#elif defined(RP2040_PLATFORM) |
|||
identity_store(fs, "/identity") |
|||
#else |
|||
identity_store(fs, "/identity") |
|||
#endif |
|||
{ |
|||
} |
|||
|
|||
static File openWrite(FILESYSTEM* _fs, const char* filename) { |
|||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) |
|||
_fs->remove(filename); |
|||
return _fs->open(filename, FILE_O_WRITE); |
|||
#elif defined(RP2040_PLATFORM) |
|||
return _fs->open(filename, "w"); |
|||
#else |
|||
return _fs->open(filename, "w", true); |
|||
#endif |
|||
} |
|||
|
|||
void DataStore::begin() { |
|||
#if defined(RP2040_PLATFORM) |
|||
identity_store.begin(); |
|||
#endif |
|||
|
|||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) |
|||
checkAdvBlobFile(); |
|||
#else |
|||
// init 'blob store' support
|
|||
_fs->mkdir("/bl"); |
|||
#endif |
|||
} |
|||
|
|||
#if defined(ESP32) |
|||
#include <SPIFFS.h> |
|||
#elif defined(RP2040_PLATFORM) |
|||
#include <LittleFS.h> |
|||
#endif |
|||
|
|||
File DataStore::openRead(const char* filename) { |
|||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) |
|||
return _fs->open(filename, FILE_O_READ); |
|||
#elif defined(RP2040_PLATFORM) |
|||
return _fs->open(filename, "r"); |
|||
#else |
|||
return _fs->open(filename, "r", false); |
|||
#endif |
|||
} |
|||
|
|||
bool DataStore::removeFile(const char* filename) { |
|||
return _fs->remove(filename); |
|||
} |
|||
|
|||
bool DataStore::formatFileSystem() { |
|||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) |
|||
return _fs->format(); |
|||
#elif defined(RP2040_PLATFORM) |
|||
return LittleFS.format(); |
|||
#elif defined(ESP32) |
|||
return ((fs::SPIFFSFS *)_fs)->format(); |
|||
#else |
|||
#error "need to implement format()" |
|||
#endif |
|||
} |
|||
|
|||
bool DataStore::loadMainIdentity(mesh::LocalIdentity &identity) { |
|||
return identity_store.load("_main", identity); |
|||
} |
|||
|
|||
bool DataStore::saveMainIdentity(const mesh::LocalIdentity &identity) { |
|||
return identity_store.save("_main", identity); |
|||
} |
|||
|
|||
void DataStore::loadPrefs(NodePrefs& prefs, double& node_lat, double& node_lon) { |
|||
if (_fs->exists("/new_prefs")) { |
|||
loadPrefsInt("/new_prefs", prefs, node_lat, node_lon); // new filename
|
|||
} else if (_fs->exists("/node_prefs")) { |
|||
loadPrefsInt("/node_prefs", prefs, node_lat, node_lon); |
|||
savePrefs(prefs, node_lat, node_lon); // save to new filename
|
|||
_fs->remove("/node_prefs"); // remove old
|
|||
} |
|||
} |
|||
|
|||
void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& node_lat, double& node_lon) { |
|||
#if defined(RP2040_PLATFORM) |
|||
File file = _fs->open(filename, "r"); |
|||
#else |
|||
File file = _fs->open(filename); |
|||
#endif |
|||
if (file) { |
|||
uint8_t pad[8]; |
|||
|
|||
file.read((uint8_t *)&_prefs.airtime_factor, sizeof(float)); // 0
|
|||
file.read((uint8_t *)_prefs.node_name, sizeof(_prefs.node_name)); // 4
|
|||
file.read(pad, 4); // 36
|
|||
file.read((uint8_t *)&node_lat, sizeof(node_lat)); // 40
|
|||
file.read((uint8_t *)&node_lon, sizeof(node_lon)); // 48
|
|||
file.read((uint8_t *)&_prefs.freq, sizeof(_prefs.freq)); // 56
|
|||
file.read((uint8_t *)&_prefs.sf, sizeof(_prefs.sf)); // 60
|
|||
file.read((uint8_t *)&_prefs.cr, sizeof(_prefs.cr)); // 61
|
|||
file.read((uint8_t *)&_prefs.reserved1, sizeof(_prefs.reserved1)); // 62
|
|||
file.read((uint8_t *)&_prefs.manual_add_contacts, sizeof(_prefs.manual_add_contacts)); // 63
|
|||
file.read((uint8_t *)&_prefs.bw, sizeof(_prefs.bw)); // 64
|
|||
file.read((uint8_t *)&_prefs.tx_power_dbm, sizeof(_prefs.tx_power_dbm)); // 68
|
|||
file.read((uint8_t *)&_prefs.telemetry_mode_base, sizeof(_prefs.telemetry_mode_base)); // 69
|
|||
file.read((uint8_t *)&_prefs.telemetry_mode_loc, sizeof(_prefs.telemetry_mode_loc)); // 70
|
|||
file.read((uint8_t *)&_prefs.telemetry_mode_env, sizeof(_prefs.telemetry_mode_env)); // 71
|
|||
file.read((uint8_t *)&_prefs.rx_delay_base, sizeof(_prefs.rx_delay_base)); // 72
|
|||
file.read(pad, 4); // 76
|
|||
file.read((uint8_t *)&_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80
|
|||
|
|||
file.close(); |
|||
} |
|||
} |
|||
|
|||
void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_lon) { |
|||
File file = openWrite(_fs, "/new_prefs"); |
|||
if (file) { |
|||
uint8_t pad[8]; |
|||
memset(pad, 0, sizeof(pad)); |
|||
|
|||
file.write((uint8_t *)&_prefs.airtime_factor, sizeof(float)); // 0
|
|||
file.write((uint8_t *)_prefs.node_name, sizeof(_prefs.node_name)); // 4
|
|||
file.write(pad, 4); // 36
|
|||
file.write((uint8_t *)&node_lat, sizeof(node_lat)); // 40
|
|||
file.write((uint8_t *)&node_lon, sizeof(node_lon)); // 48
|
|||
file.write((uint8_t *)&_prefs.freq, sizeof(_prefs.freq)); // 56
|
|||
file.write((uint8_t *)&_prefs.sf, sizeof(_prefs.sf)); // 60
|
|||
file.write((uint8_t *)&_prefs.cr, sizeof(_prefs.cr)); // 61
|
|||
file.write((uint8_t *)&_prefs.reserved1, sizeof(_prefs.reserved1)); // 62
|
|||
file.write((uint8_t *)&_prefs.manual_add_contacts, sizeof(_prefs.manual_add_contacts)); // 63
|
|||
file.write((uint8_t *)&_prefs.bw, sizeof(_prefs.bw)); // 64
|
|||
file.write((uint8_t *)&_prefs.tx_power_dbm, sizeof(_prefs.tx_power_dbm)); // 68
|
|||
file.write((uint8_t *)&_prefs.telemetry_mode_base, sizeof(_prefs.telemetry_mode_base)); // 69
|
|||
file.write((uint8_t *)&_prefs.telemetry_mode_loc, sizeof(_prefs.telemetry_mode_loc)); // 70
|
|||
file.write((uint8_t *)&_prefs.telemetry_mode_env, sizeof(_prefs.telemetry_mode_env)); // 71
|
|||
file.write((uint8_t *)&_prefs.rx_delay_base, sizeof(_prefs.rx_delay_base)); // 72
|
|||
file.write(pad, 4); // 76
|
|||
file.write((uint8_t *)&_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80
|
|||
|
|||
file.close(); |
|||
} |
|||
} |
|||
|
|||
void DataStore::loadContacts(DataStoreHost* host) { |
|||
if (_fs->exists("/contacts3")) { |
|||
#if defined(RP2040_PLATFORM) |
|||
File file = _fs->open("/contacts3", "r"); |
|||
#else |
|||
File file = _fs->open("/contacts3"); |
|||
#endif |
|||
if (file) { |
|||
bool full = false; |
|||
while (!full) { |
|||
ContactInfo c; |
|||
uint8_t pub_key[32]; |
|||
uint8_t unused; |
|||
|
|||
bool success = (file.read(pub_key, 32) == 32); |
|||
success = success && (file.read((uint8_t *)&c.name, 32) == 32); |
|||
success = success && (file.read(&c.type, 1) == 1); |
|||
success = success && (file.read(&c.flags, 1) == 1); |
|||
success = success && (file.read(&unused, 1) == 1); |
|||
success = success && (file.read((uint8_t *)&c.sync_since, 4) == 4); // was 'reserved'
|
|||
success = success && (file.read((uint8_t *)&c.out_path_len, 1) == 1); |
|||
success = success && (file.read((uint8_t *)&c.last_advert_timestamp, 4) == 4); |
|||
success = success && (file.read(c.out_path, 64) == 64); |
|||
success = success && (file.read((uint8_t *)&c.lastmod, 4) == 4); |
|||
success = success && (file.read((uint8_t *)&c.gps_lat, 4) == 4); |
|||
success = success && (file.read((uint8_t *)&c.gps_lon, 4) == 4); |
|||
|
|||
if (!success) break; // EOF
|
|||
|
|||
c.id = mesh::Identity(pub_key); |
|||
if (!host->onContactLoaded(c)) full = true; |
|||
} |
|||
file.close(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
void DataStore::saveContacts(DataStoreHost* host) { |
|||
File file = openWrite(_fs, "/contacts3"); |
|||
if (file) { |
|||
uint32_t idx = 0; |
|||
ContactInfo c; |
|||
uint8_t unused = 0; |
|||
|
|||
while (host->getContactForSave(idx, c)) { |
|||
bool success = (file.write(c.id.pub_key, 32) == 32); |
|||
success = success && (file.write((uint8_t *)&c.name, 32) == 32); |
|||
success = success && (file.write(&c.type, 1) == 1); |
|||
success = success && (file.write(&c.flags, 1) == 1); |
|||
success = success && (file.write(&unused, 1) == 1); |
|||
success = success && (file.write((uint8_t *)&c.sync_since, 4) == 4); |
|||
success = success && (file.write((uint8_t *)&c.out_path_len, 1) == 1); |
|||
success = success && (file.write((uint8_t *)&c.last_advert_timestamp, 4) == 4); |
|||
success = success && (file.write(c.out_path, 64) == 64); |
|||
success = success && (file.write((uint8_t *)&c.lastmod, 4) == 4); |
|||
success = success && (file.write((uint8_t *)&c.gps_lat, 4) == 4); |
|||
success = success && (file.write((uint8_t *)&c.gps_lon, 4) == 4); |
|||
|
|||
if (!success) break; // write failed
|
|||
|
|||
idx++; // advance to next contact
|
|||
} |
|||
file.close(); |
|||
} |
|||
} |
|||
|
|||
void DataStore::loadChannels(DataStoreHost* host) { |
|||
if (_fs->exists("/channels2")) { |
|||
#if defined(RP2040_PLATFORM) |
|||
File file = _fs->open("/channels2", "r"); |
|||
#else |
|||
File file = _fs->open("/channels2"); |
|||
#endif |
|||
if (file) { |
|||
bool full = false; |
|||
uint8_t channel_idx = 0; |
|||
while (!full) { |
|||
ChannelDetails ch; |
|||
uint8_t unused[4]; |
|||
|
|||
bool success = (file.read(unused, 4) == 4); |
|||
success = success && (file.read((uint8_t *)ch.name, 32) == 32); |
|||
success = success && (file.read((uint8_t *)ch.channel.secret, 32) == 32); |
|||
|
|||
if (!success) break; // EOF
|
|||
|
|||
if (host->onChannelLoaded(channel_idx, ch)) { |
|||
channel_idx++; |
|||
} else { |
|||
full = true; |
|||
} |
|||
} |
|||
file.close(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
void DataStore::saveChannels(DataStoreHost* host) { |
|||
File file = openWrite(_fs, "/channels2"); |
|||
if (file) { |
|||
uint8_t channel_idx = 0; |
|||
ChannelDetails ch; |
|||
uint8_t unused[4]; |
|||
memset(unused, 0, 4); |
|||
|
|||
while (host->getChannelForSave(channel_idx, ch)) { |
|||
bool success = (file.write(unused, 4) == 4); |
|||
success = success && (file.write((uint8_t *)ch.name, 32) == 32); |
|||
success = success && (file.write((uint8_t *)ch.channel.secret, 32) == 32); |
|||
|
|||
if (!success) break; // write failed
|
|||
channel_idx++; |
|||
} |
|||
file.close(); |
|||
} |
|||
} |
|||
|
|||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) |
|||
|
|||
#define MAX_ADVERT_PKT_LEN (2 + 32 + PUB_KEY_SIZE + 4 + SIGNATURE_SIZE + MAX_ADVERT_DATA_SIZE) |
|||
|
|||
struct BlobRec { |
|||
uint32_t timestamp; |
|||
uint8_t key[7]; |
|||
uint8_t len; |
|||
uint8_t data[MAX_ADVERT_PKT_LEN]; |
|||
}; |
|||
|
|||
void DataStore::checkAdvBlobFile() { |
|||
if (!_fs->exists("/adv_blobs")) { |
|||
File file = openWrite(_fs, "/adv_blobs"); |
|||
if (file) { |
|||
BlobRec zeroes; |
|||
memset(&zeroes, 0, sizeof(zeroes)); |
|||
for (int i = 0; i < 20; i++) { // pre-allocate to fixed size
|
|||
file.write((uint8_t *) &zeroes, sizeof(zeroes)); |
|||
} |
|||
file.close(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
uint8_t DataStore::getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]) { |
|||
File file = _fs->open("/adv_blobs"); |
|||
uint8_t len = 0; // 0 = not found
|
|||
|
|||
if (file) { |
|||
BlobRec tmp; |
|||
while (file.read((uint8_t *) &tmp, sizeof(tmp)) == sizeof(tmp)) { |
|||
if (memcmp(key, tmp.key, sizeof(tmp.key)) == 0) { // only match by 7 byte prefix
|
|||
len = tmp.len; |
|||
memcpy(dest_buf, tmp.data, len); |
|||
break; |
|||
} |
|||
} |
|||
file.close(); |
|||
} |
|||
return len; |
|||
} |
|||
|
|||
bool DataStore::putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], uint8_t len) { |
|||
if (len < PUB_KEY_SIZE+4+SIGNATURE_SIZE || len > MAX_ADVERT_PKT_LEN) return false; |
|||
|
|||
checkAdvBlobFile(); |
|||
|
|||
File file = _fs->open("/adv_blobs", FILE_O_WRITE); |
|||
if (file) { |
|||
uint32_t pos = 0, found_pos = 0; |
|||
uint32_t min_timestamp = 0xFFFFFFFF; |
|||
|
|||
// search for matching key OR evict by oldest timestmap
|
|||
BlobRec tmp; |
|||
file.seek(0); |
|||
while (file.read((uint8_t *) &tmp, sizeof(tmp)) == sizeof(tmp)) { |
|||
if (memcmp(key, tmp.key, sizeof(tmp.key)) == 0) { // only match by 7 byte prefix
|
|||
found_pos = pos; |
|||
break; |
|||
} |
|||
if (tmp.timestamp < min_timestamp) { |
|||
min_timestamp = tmp.timestamp; |
|||
found_pos = pos; |
|||
} |
|||
|
|||
pos += sizeof(tmp); |
|||
} |
|||
|
|||
memcpy(tmp.key, key, sizeof(tmp.key)); // just record 7 byte prefix of key
|
|||
memcpy(tmp.data, src_buf, len); |
|||
tmp.len = len; |
|||
tmp.timestamp = _clock->getCurrentTime(); |
|||
|
|||
file.seek(found_pos); |
|||
file.write((uint8_t *) &tmp, sizeof(tmp)); |
|||
|
|||
file.close(); |
|||
return true; |
|||
} |
|||
return false; // error
|
|||
} |
|||
#else |
|||
uint8_t DataStore::getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]) { |
|||
char path[64]; |
|||
char fname[18]; |
|||
|
|||
if (key_len > 8) key_len = 8; // just use first 8 bytes (prefix)
|
|||
mesh::Utils::toHex(fname, key, key_len); |
|||
sprintf(path, "/bl/%s", fname); |
|||
|
|||
if (_fs->exists(path)) { |
|||
#if defined(RP2040_PLATFORM) |
|||
File f = _fs->open(path, "r"); |
|||
#else |
|||
File f = _fs->open(path); |
|||
#endif |
|||
if (f) { |
|||
int len = f.read(dest_buf, 255); // currently MAX 255 byte blob len supported!!
|
|||
f.close(); |
|||
return len; |
|||
} |
|||
} |
|||
return 0; // not found
|
|||
} |
|||
|
|||
bool DataStore::putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], uint8_t len) { |
|||
char path[64]; |
|||
char fname[18]; |
|||
|
|||
if (key_len > 8) key_len = 8; // just use first 8 bytes (prefix)
|
|||
mesh::Utils::toHex(fname, key, key_len); |
|||
sprintf(path, "/bl/%s", fname); |
|||
|
|||
File f = openWrite(_fs, path); |
|||
if (f) { |
|||
int n = f.write(src_buf, len); |
|||
f.close(); |
|||
if (n == len) return true; // success!
|
|||
|
|||
_fs->remove(path); // blob was only partially written!
|
|||
} |
|||
return false; // error
|
|||
} |
|||
#endif |
|||
@ -0,0 +1,42 @@ |
|||
#pragma once |
|||
|
|||
#include <helpers/IdentityStore.h> |
|||
#include <helpers/ContactInfo.h> |
|||
#include <helpers/ChannelDetails.h> |
|||
#include "NodePrefs.h" |
|||
|
|||
class DataStoreHost { |
|||
public: |
|||
virtual bool onContactLoaded(const ContactInfo& contact) =0; |
|||
virtual bool getContactForSave(uint32_t idx, ContactInfo& contact) =0; |
|||
virtual bool onChannelLoaded(uint8_t channel_idx, const ChannelDetails& ch) =0; |
|||
virtual bool getChannelForSave(uint8_t channel_idx, ChannelDetails& ch) =0; |
|||
}; |
|||
|
|||
class DataStore { |
|||
FILESYSTEM* _fs; |
|||
mesh::RTCClock* _clock; |
|||
IdentityStore identity_store; |
|||
|
|||
void loadPrefsInt(const char *filename, NodePrefs& prefs, double& node_lat, double& node_lon); |
|||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) |
|||
void checkAdvBlobFile(); |
|||
#endif |
|||
|
|||
public: |
|||
DataStore(FILESYSTEM& fs, mesh::RTCClock& clock); |
|||
void begin(); |
|||
bool formatFileSystem(); |
|||
bool loadMainIdentity(mesh::LocalIdentity &identity); |
|||
bool saveMainIdentity(const mesh::LocalIdentity &identity); |
|||
void loadPrefs(NodePrefs& prefs, double& node_lat, double& node_lon); |
|||
void savePrefs(const NodePrefs& prefs, double node_lat, double node_lon); |
|||
void loadContacts(DataStoreHost* host); |
|||
void saveContacts(DataStoreHost* host); |
|||
void loadChannels(DataStoreHost* host); |
|||
void saveChannels(DataStoreHost* host); |
|||
uint8_t getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]); |
|||
bool putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], uint8_t len); |
|||
File openRead(const char* filename); |
|||
bool removeFile(const char* filename); |
|||
}; |
|||
File diff suppressed because it is too large
@ -0,0 +1,201 @@ |
|||
#pragma once |
|||
|
|||
#include <Arduino.h> |
|||
#include <Mesh.h> |
|||
#ifdef DISPLAY_CLASS |
|||
#include "UITask.h" |
|||
#endif |
|||
|
|||
/*------------ Frame Protocol --------------*/ |
|||
#define FIRMWARE_VER_CODE 5 |
|||
|
|||
#ifndef FIRMWARE_BUILD_DATE |
|||
#define FIRMWARE_BUILD_DATE "7 Jun 2025" |
|||
#endif |
|||
|
|||
#ifndef FIRMWARE_VERSION |
|||
#define FIRMWARE_VERSION "v1.7.0" |
|||
#endif |
|||
|
|||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) |
|||
#include <InternalFileSystem.h> |
|||
#elif defined(RP2040_PLATFORM) |
|||
#include <LittleFS.h> |
|||
#elif defined(ESP32) |
|||
#include <SPIFFS.h> |
|||
#endif |
|||
|
|||
#include "DataStore.h" |
|||
#include "NodePrefs.h" |
|||
|
|||
#include <RTClib.h> |
|||
#include <helpers/ArduinoHelpers.h> |
|||
#include <helpers/BaseSerialInterface.h> |
|||
#include <helpers/IdentityStore.h> |
|||
#include <helpers/SimpleMeshTables.h> |
|||
#include <helpers/StaticPoolPacketManager.h> |
|||
#include <target.h> |
|||
|
|||
/* ---------------------------------- CONFIGURATION ------------------------------------- */ |
|||
|
|||
#ifndef LORA_FREQ |
|||
#define LORA_FREQ 915.0 |
|||
#endif |
|||
#ifndef LORA_BW |
|||
#define LORA_BW 250 |
|||
#endif |
|||
#ifndef LORA_SF |
|||
#define LORA_SF 10 |
|||
#endif |
|||
#ifndef LORA_CR |
|||
#define LORA_CR 5 |
|||
#endif |
|||
#ifndef LORA_TX_POWER |
|||
#define LORA_TX_POWER 20 |
|||
#endif |
|||
#ifndef MAX_LORA_TX_POWER |
|||
#define MAX_LORA_TX_POWER LORA_TX_POWER |
|||
#endif |
|||
|
|||
#ifndef MAX_CONTACTS |
|||
#define MAX_CONTACTS 100 |
|||
#endif |
|||
|
|||
#ifndef OFFLINE_QUEUE_SIZE |
|||
#define OFFLINE_QUEUE_SIZE 16 |
|||
#endif |
|||
|
|||
#ifndef BLE_NAME_PREFIX |
|||
#define BLE_NAME_PREFIX "MeshCore-" |
|||
#endif |
|||
|
|||
#include <helpers/BaseChatMesh.h> |
|||
|
|||
/* -------------------------------------------------------------------------------------- */ |
|||
|
|||
#define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS
|
|||
#define REQ_TYPE_KEEP_ALIVE 0x02 |
|||
#define REQ_TYPE_GET_TELEMETRY_DATA 0x03 |
|||
|
|||
class MyMesh : public BaseChatMesh, public DataStoreHost { |
|||
public: |
|||
MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMeshTables &tables, DataStore& store); |
|||
|
|||
void begin(bool has_display); |
|||
void startInterface(BaseSerialInterface &serial); |
|||
|
|||
const char *getNodeName(); |
|||
NodePrefs *getNodePrefs(); |
|||
uint32_t getBLEPin(); |
|||
|
|||
void loop(); |
|||
void handleCmdFrame(size_t len); |
|||
bool advert(); |
|||
void enterCLIRescue(); |
|||
|
|||
protected: |
|||
float getAirtimeBudgetFactor() const override; |
|||
int getInterferenceThreshold() const override; |
|||
int calcRxDelay(float score, uint32_t air_time) const override; |
|||
|
|||
void logRxRaw(float snr, float rssi, const uint8_t raw[], int len) override; |
|||
bool isAutoAddEnabled() const override; |
|||
void onDiscoveredContact(ContactInfo &contact, bool is_new) override; |
|||
void onContactPathUpdated(const ContactInfo &contact) override; |
|||
bool processAck(const uint8_t *data) override; |
|||
void queueMessage(const ContactInfo &from, uint8_t txt_type, mesh::Packet *pkt, uint32_t sender_timestamp, |
|||
const uint8_t *extra, int extra_len, const char *text); |
|||
|
|||
void onMessageRecv(const ContactInfo &from, mesh::Packet *pkt, uint32_t sender_timestamp, |
|||
const char *text) override; |
|||
void onCommandDataRecv(const ContactInfo &from, mesh::Packet *pkt, uint32_t sender_timestamp, |
|||
const char *text) override; |
|||
void onSignedMessageRecv(const ContactInfo &from, mesh::Packet *pkt, uint32_t sender_timestamp, |
|||
const uint8_t *sender_prefix, const char *text) override; |
|||
void onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packet *pkt, uint32_t timestamp, |
|||
const char *text) override; |
|||
|
|||
uint8_t onContactRequest(const ContactInfo &contact, uint32_t sender_timestamp, const uint8_t *data, |
|||
uint8_t len, uint8_t *reply) override; |
|||
void onContactResponse(const ContactInfo &contact, const uint8_t *data, uint8_t len) override; |
|||
void onRawDataRecv(mesh::Packet *packet) override; |
|||
void onTraceRecv(mesh::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) override; |
|||
|
|||
uint32_t calcFloodTimeoutMillisFor(uint32_t pkt_airtime_millis) const override; |
|||
uint32_t calcDirectTimeoutMillisFor(uint32_t pkt_airtime_millis, uint8_t path_len) const override; |
|||
void onSendTimeout() override; |
|||
|
|||
// DataStoreHost methods
|
|||
bool onContactLoaded(const ContactInfo& contact) override { return addContact(contact); } |
|||
bool getContactForSave(uint32_t idx, ContactInfo& contact) override { return getContactByIdx(idx, contact); } |
|||
bool onChannelLoaded(uint8_t channel_idx, const ChannelDetails& ch) override { return setChannel(channel_idx, ch); } |
|||
bool getChannelForSave(uint8_t channel_idx, ChannelDetails& ch) override { return getChannel(channel_idx, ch); } |
|||
|
|||
private: |
|||
void writeOKFrame(); |
|||
void writeErrFrame(uint8_t err_code); |
|||
void writeDisabledFrame(); |
|||
void writeContactRespFrame(uint8_t code, const ContactInfo &contact); |
|||
void updateContactFromFrame(ContactInfo &contact, const uint8_t *frame, int len); |
|||
void addToOfflineQueue(const uint8_t frame[], int len); |
|||
int getFromOfflineQueue(uint8_t frame[]); |
|||
int getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]) override { |
|||
return _store->getBlobByKey(key, key_len, dest_buf); |
|||
} |
|||
bool putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], int len) override { |
|||
return _store->putBlobByKey(key, key_len, src_buf, len); |
|||
} |
|||
|
|||
void checkCLIRescueCmd(); |
|||
void checkSerialInterface(); |
|||
|
|||
// helpers, short-cuts
|
|||
void savePrefs() { _store->savePrefs(_prefs, sensors.node_lat, sensors.node_lon); } |
|||
void saveChannels() { _store->saveChannels(this); } |
|||
void saveContacts() { _store->saveContacts(this); } |
|||
|
|||
private: |
|||
DataStore* _store; |
|||
NodePrefs _prefs; |
|||
uint32_t pending_login; |
|||
uint32_t pending_status; |
|||
uint32_t pending_telemetry; |
|||
BaseSerialInterface *_serial; |
|||
|
|||
ContactsIterator _iter; |
|||
uint32_t _iter_filter_since; |
|||
uint32_t _most_recent_lastmod; |
|||
uint32_t _active_ble_pin; |
|||
bool _iter_started; |
|||
bool _cli_rescue; |
|||
char cli_command[80]; |
|||
uint8_t app_target_ver; |
|||
uint8_t *sign_data; |
|||
uint32_t sign_data_len; |
|||
unsigned long dirty_contacts_expiry; |
|||
|
|||
uint8_t cmd_frame[MAX_FRAME_SIZE + 1]; |
|||
uint8_t out_frame[MAX_FRAME_SIZE + 1]; |
|||
CayenneLPP telemetry; |
|||
|
|||
struct Frame { |
|||
uint8_t len; |
|||
uint8_t buf[MAX_FRAME_SIZE]; |
|||
}; |
|||
int offline_queue_len; |
|||
Frame offline_queue[OFFLINE_QUEUE_SIZE]; |
|||
|
|||
struct AckTableEntry { |
|||
unsigned long msg_sent; |
|||
uint32_t ack; |
|||
}; |
|||
#define EXPECTED_ACK_TABLE_SIZE 8 |
|||
AckTableEntry expected_ack_table[EXPECTED_ACK_TABLE_SIZE]; // circular table
|
|||
int next_ack_idx; |
|||
}; |
|||
|
|||
extern MyMesh the_mesh; |
|||
#ifdef DISPLAY_CLASS |
|||
extern UITask ui_task; |
|||
#endif |
|||
File diff suppressed because it is too large
@ -0,0 +1,9 @@ |
|||
#pragma once |
|||
|
|||
#include <Arduino.h> |
|||
#include <Mesh.h> |
|||
|
|||
struct ChannelDetails { |
|||
mesh::GroupChannel channel; |
|||
char name[32]; |
|||
}; |
|||
@ -0,0 +1,18 @@ |
|||
#pragma once |
|||
|
|||
#include <Arduino.h> |
|||
#include <Mesh.h> |
|||
|
|||
struct ContactInfo { |
|||
mesh::Identity id; |
|||
char name[32]; |
|||
uint8_t type; // on of ADV_TYPE_*
|
|||
uint8_t flags; |
|||
int8_t out_path_len; |
|||
uint8_t out_path[MAX_PATH_SIZE]; |
|||
uint32_t last_advert_timestamp; // by THEIR clock
|
|||
uint8_t shared_secret[PUB_KEY_SIZE]; |
|||
uint32_t lastmod; // by OUR clock
|
|||
int32_t gps_lat, gps_lon; // 6 dec places
|
|||
uint32_t sync_since; |
|||
}; |
|||
@ -0,0 +1,119 @@ |
|||
#include <Arduino.h> |
|||
#include "t1000e_sensors.h" |
|||
|
|||
#define HEATER_NTC_BX 4250 // thermistor coefficient B
|
|||
#define HEATER_NTC_RP 8250 // ohm, series resistance to thermistor
|
|||
#define HEATER_NTC_KA 273.15 // 25 Celsius at Kelvin
|
|||
#define NTC_REF_VCC 3000 // mV, output voltage of LDO
|
|||
#define LIGHT_REF_VCC 2400 //
|
|||
|
|||
static unsigned int ntc_res2[136]={ |
|||
113347,107565,102116,96978,92132,87559,83242,79166,75316,71677, |
|||
68237,64991,61919,59011,56258,53650,51178,48835,46613,44506, |
|||
42506,40600,38791,37073,35442,33892,32420,31020,29689,28423, |
|||
27219,26076,24988,23951,22963,22021,21123,20267,19450,18670, |
|||
17926,17214,16534,15886,15266,14674,14108,13566,13049,12554, |
|||
12081,11628,11195,10780,10382,10000,9634,9284,8947,8624, |
|||
8315,8018,7734,7461,7199,6948,6707,6475,6253,6039, |
|||
5834,5636,5445,5262,5086,4917,4754,4597,4446,4301, |
|||
4161,4026,3896,3771,3651,3535,3423,3315,3211,3111, |
|||
3014,2922,2834,2748,2666,2586,2509,2435,2364,2294, |
|||
2228,2163,2100,2040,1981,1925,1870,1817,1766,1716, |
|||
1669,1622,1578,1535,1493,1452,1413,1375,1338,1303, |
|||
1268,1234,1202,1170,1139,1110,1081,1053,1026,999, |
|||
974,949,925,902,880,858, |
|||
}; |
|||
|
|||
static char ntc_temp2[136]= |
|||
{ |
|||
-30,-29,-28,-27,-26,-25,-24,-23,-22,-21, |
|||
-20,-19,-18,-17,-16,-15,-14,-13,-12,-11, |
|||
-10,-9,-8,-7,-6,-5,-4,-3,-2,-1, |
|||
0,1,2,3,4,5,6,7,8,9, |
|||
10,11,12,13,14,15,16,17,18,19, |
|||
20,21,22,23,24,25,26,27,28,29, |
|||
30,31,32,33,34,35,36,37,38,39, |
|||
40,41,42,43,44,45,46,47,48,49, |
|||
50,51,52,53,54,55,56,57,58,59, |
|||
60,61,62,63,64,65,66,67,68,69, |
|||
70,71,72,73,74,75,76,77,78,79, |
|||
80,81,82,83,84,85,86,87,88,89, |
|||
90,91,92,93,94,95,96,97,98,99, |
|||
100,101,102,103,104,105, |
|||
}; |
|||
|
|||
static float get_heater_temperature( unsigned int vcc_volt, unsigned int ntc_volt ) |
|||
{ |
|||
int i = 0; |
|||
float Vout = 0, Rt = 0, temp = 0; |
|||
Vout = ntc_volt; |
|||
|
|||
Rt = ( HEATER_NTC_RP * vcc_volt ) / Vout - HEATER_NTC_RP; |
|||
|
|||
for( i = 0; i < 136; i++ ) |
|||
{ |
|||
if( Rt >= ntc_res2[i] ) |
|||
{ |
|||
break; |
|||
} |
|||
} |
|||
|
|||
temp = ntc_temp2[i - 1] + 1 * ( ntc_res2[i - 1] - Rt ) / ( float )( ntc_res2[i - 1] - ntc_res2[i] ); |
|||
|
|||
temp = ( temp * 100 + 5 ) / 100; |
|||
return temp; |
|||
} |
|||
|
|||
static int get_light_lv( unsigned int light_volt ) |
|||
{ |
|||
float Vout = 0, Vin = 0, Rt = 0, temp = 0; |
|||
unsigned int light_level = 0; |
|||
|
|||
if( light_volt <= 80 ) |
|||
{ |
|||
light_level = 0; |
|||
return light_level; |
|||
} |
|||
else if( light_volt >= 2480 ) |
|||
{ |
|||
light_level = 100; |
|||
return light_level; |
|||
} |
|||
Vout = light_volt; |
|||
light_level = 100 * ( Vout - 80 ) / LIGHT_REF_VCC; |
|||
|
|||
return light_level; |
|||
} |
|||
|
|||
float t1000e_get_temperature( void ) |
|||
{ |
|||
unsigned int ntc_v, vcc_v; |
|||
|
|||
digitalWrite(PIN_3V3_EN, HIGH); |
|||
digitalWrite(SENSOR_EN, HIGH); |
|||
analogReference(AR_INTERNAL_3_0); |
|||
analogReadResolution(12); |
|||
delay(10); |
|||
vcc_v = (1000.0*(analogRead(BATTERY_PIN) * ADC_MULTIPLIER * AREF_VOLTAGE)) / 4096; |
|||
ntc_v = (1000.0 * AREF_VOLTAGE * analogRead(TEMP_SENSOR)) / 4096; |
|||
digitalWrite(PIN_3V3_EN, LOW); |
|||
digitalWrite(SENSOR_EN, LOW); |
|||
|
|||
return get_heater_temperature (vcc_v, ntc_v); |
|||
} |
|||
|
|||
uint32_t t1000e_get_light( void ) |
|||
{ |
|||
int lux = 0; |
|||
unsigned int lux_v = 0; |
|||
|
|||
digitalWrite(SENSOR_EN, HIGH); |
|||
analogReference(AR_INTERNAL_3_0); |
|||
analogReadResolution(12); |
|||
delay(10); |
|||
lux_v = 1000 * analogRead(LUX_SENSOR) * AREF_VOLTAGE / 4096; |
|||
lux = get_light_lv( lux_v ); |
|||
digitalWrite(SENSOR_EN, LOW); |
|||
|
|||
return lux; |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
#pragma once |
|||
|
|||
// Light and temperature sensors are on ADC ports
|
|||
// functions adapted from Seeed examples to get values
|
|||
// see : https://github.com/Seeed-Studio/Seeed-Tracker-T1000-E-for-LoRaWAN-dev-board
|
|||
|
|||
extern uint32_t t1000e_get_light(); |
|||
extern float t1000e_get_temperature(); |
|||
@ -0,0 +1,10 @@ |
|||
#pragma once |
|||
|
|||
// UART Definitions
|
|||
// #ifndef SERIAL_UART_INSTANCE
|
|||
// #define SERIAL_UART_INSTANCE 101
|
|||
// #endif
|
|||
|
|||
#include <variant_generic.h> |
|||
|
|||
#undef RNG |
|||
@ -0,0 +1,34 @@ |
|||
[lora_e5_mini] |
|||
extends = stm32_base |
|||
board = lora_e5_mini |
|||
board_upload.maximum_size = 229376 ; 32kb for FS |
|||
build_flags = ${stm32_base.build_flags} |
|||
-D RADIO_CLASS=CustomSTM32WLx |
|||
-D WRAPPER_CLASS=CustomSTM32WLxWrapper |
|||
-D SPI_INTERFACES_COUNT=0 |
|||
-D RX_BOOSTED_GAIN=true |
|||
-I variants/wio-e5-mini |
|||
build_src_filter = ${stm32_base.build_src_filter} |
|||
+<../variants/wio-e5-mini> |
|||
lib_deps = ${stm32_base.lib_deps} |
|||
finitespace/BME280 @ ^3.0.0 |
|||
|
|||
[env:wio-e5-mini-repeater] |
|||
extends = lora_e5_mini |
|||
build_flags = ${lora_e5_mini.build_flags} |
|||
-D LORA_TX_POWER=22 |
|||
-D ADVERT_NAME='"wio-e5-mini Repeater"' |
|||
-D ADMIN_PASSWORD='"password"' |
|||
build_src_filter = ${lora_e5_mini.build_src_filter} |
|||
+<../examples/simple_repeater/main.cpp> |
|||
|
|||
[env:wio-e5-mini_companion_radio_usb] |
|||
extends = lora_e5_mini |
|||
build_flags = ${lora_e5_mini.build_flags} |
|||
-D LORA_TX_POWER=22 |
|||
-D MAX_CONTACTS=100 |
|||
-D MAX_GROUP_CHANNELS=8 |
|||
build_src_filter = ${lora_e5_mini.build_src_filter} |
|||
+<../examples/companion_radio/*.cpp> |
|||
lib_deps = ${lora_e5_mini.lib_deps} |
|||
densaugeo/base64 @ ~1.4.0 |
|||
@ -0,0 +1,90 @@ |
|||
#include <Arduino.h> |
|||
#include "target.h" |
|||
#include <helpers/ArduinoHelpers.h> |
|||
|
|||
WIOE5Board board; |
|||
|
|||
RADIO_CLASS radio = new STM32WLx_Module(); |
|||
|
|||
WRAPPER_CLASS radio_driver(radio, board); |
|||
|
|||
static const uint32_t rfswitch_pins[] = {PA4, PA5, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC}; |
|||
static const Module::RfSwitchMode_t rfswitch_table[] = { |
|||
{STM32WLx::MODE_IDLE, {LOW, LOW}}, |
|||
{STM32WLx::MODE_RX, {HIGH, LOW}}, |
|||
{STM32WLx::MODE_TX_HP, {LOW, HIGH}}, // for LoRa-E5 mini
|
|||
// {STM32WLx::MODE_TX_LP, {HIGH, HIGH}}, // for LoRa-E5-LE mini
|
|||
END_OF_MODE_TABLE, |
|||
}; |
|||
|
|||
VolatileRTCClock rtc_clock; |
|||
WIOE5SensorManager sensors; |
|||
|
|||
#ifndef LORA_CR |
|||
#define LORA_CR 5 |
|||
#endif |
|||
|
|||
bool radio_init() { |
|||
Wire.begin(); |
|||
|
|||
radio.setRfSwitchTable(rfswitch_pins, rfswitch_table); |
|||
|
|||
int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 8, 1.7, 0); |
|||
|
|||
if (status != RADIOLIB_ERR_NONE) { |
|||
Serial.print("ERROR: radio init failed: "); |
|||
Serial.println(status); |
|||
return false; // fail
|
|||
} |
|||
|
|||
#ifdef RX_BOOSTED_GAIN |
|||
radio.setRxBoostedGainMode(RX_BOOSTED_GAIN); |
|||
#endif |
|||
|
|||
radio.setCRC(1); |
|||
|
|||
return true; // success
|
|||
} |
|||
|
|||
uint32_t radio_get_rng_seed() { |
|||
return radio.random(0x7FFFFFFF); |
|||
} |
|||
|
|||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { |
|||
radio.setFrequency(freq); |
|||
radio.setSpreadingFactor(sf); |
|||
radio.setBandwidth(bw); |
|||
radio.setCodingRate(cr); |
|||
} |
|||
|
|||
void radio_set_tx_power(uint8_t dbm) { |
|||
radio.setOutputPower(dbm); |
|||
} |
|||
|
|||
mesh::LocalIdentity radio_new_identity() { |
|||
RadioNoiseListener rng(radio); |
|||
return mesh::LocalIdentity(&rng); // create new random identity
|
|||
} |
|||
|
|||
bool WIOE5SensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { |
|||
if (!has_bme) return false; |
|||
|
|||
float temp(NAN), hum(NAN), pres(NAN); |
|||
|
|||
BME280::TempUnit tempUnit(BME280::TempUnit_Celsius); |
|||
BME280::PresUnit presUnit(BME280::PresUnit_hPa); |
|||
|
|||
bme.read(pres, temp, hum, tempUnit, presUnit); |
|||
|
|||
telemetry.addTemperature(TELEM_CHANNEL_SELF, temp); |
|||
telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, hum); |
|||
telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, pres); |
|||
|
|||
return true; |
|||
} |
|||
|
|||
bool WIOE5SensorManager::begin() { |
|||
has_bme = bme.begin(); |
|||
|
|||
return has_bme; |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
#pragma once |
|||
|
|||
#define RADIOLIB_STATIC_ONLY 1 |
|||
#include <RadioLib.h> |
|||
#include <helpers/RadioLibWrappers.h> |
|||
#include <helpers/stm32/STM32Board.h> |
|||
#include <helpers/CustomSTM32WLxWrapper.h> |
|||
#include <helpers/ArduinoHelpers.h> |
|||
#include <helpers/SensorManager.h> |
|||
|
|||
#include <BME280I2C.h> |
|||
#include <Wire.h> |
|||
|
|||
class WIOE5Board : public STM32Board { |
|||
public: |
|||
const char* getManufacturerName() const override { |
|||
return "Seeed Wio E5 mini"; |
|||
} |
|||
|
|||
uint16_t getBattMilliVolts() override { |
|||
analogReadResolution(12); |
|||
uint32_t raw = analogRead(PIN_A3); |
|||
return raw; |
|||
} |
|||
}; |
|||
|
|||
class WIOE5SensorManager : public SensorManager { |
|||
BME280I2C bme; |
|||
bool has_bme = false; |
|||
|
|||
public: |
|||
WIOE5SensorManager() {} |
|||
bool begin() override; |
|||
bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override; |
|||
}; |
|||
|
|||
extern WIOE5Board board; |
|||
extern WRAPPER_CLASS radio_driver; |
|||
extern VolatileRTCClock rtc_clock; |
|||
extern WIOE5SensorManager sensors; |
|||
|
|||
bool radio_init(); |
|||
uint32_t radio_get_rng_seed(); |
|||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); |
|||
void radio_set_tx_power(uint8_t dbm); |
|||
mesh::LocalIdentity radio_new_identity(); |
|||
@ -1,9 +1,9 @@ |
|||
#pragma once |
|||
|
|||
// UART Definitions
|
|||
#ifndef SERIAL_UART_INSTANCE |
|||
#define SERIAL_UART_INSTANCE 101 |
|||
#endif |
|||
// #ifndef SERIAL_UART_INSTANCE
|
|||
// #define SERIAL_UART_INSTANCE 101
|
|||
// #endif
|
|||
|
|||
#include <variant_LORA_E5_MINI.h> |
|||
|
|||
Loading…
Reference in new issue