mirror of
https://github.com/meshcore-dev/MeshCore.git
synced 2026-07-15 14:49:05 +00:00
Compare commits
128 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 63f4b173da | |||
| 2af1e6d099 | |||
| 64e8e24720 | |||
| d30d8ed748 | |||
| 05fdafbb2d | |||
| f4ed8c711b | |||
| dc0f837451 | |||
| 7f4f03d737 | |||
| 1bdacb3247 | |||
| 7e95c52972 | |||
| b81fb8ebe8 | |||
| 174bcdfbcc | |||
| 87bf371cd3 | |||
| 4f12dc02d5 | |||
| 1c5a8426e8 | |||
| 6bd66d7dc8 | |||
| e01a2565b9 | |||
| 17b6662aa5 | |||
| 55f43e52e2 | |||
| 8e0c9a241b | |||
| 1429d75f57 | |||
| 63731d3fbf | |||
| eb97a375d3 | |||
| 6343d8d96e | |||
| 47e1ce5bae | |||
| 07b543176b | |||
| 8ef369058d | |||
| 102f1d2a47 | |||
| 4605d3b6ce | |||
| 7f8522a362 | |||
| 2c9ab2fe5e | |||
| fec88e1300 | |||
| 3ee2f77877 | |||
| 9e66a2e246 | |||
| 4fafcd0e23 | |||
| cea246d0eb | |||
| 584f9e909b | |||
| 35f654ced3 | |||
| 6b205da4e9 | |||
| eec75eab31 | |||
| 7d7de88707 | |||
| 73b7367e45 | |||
| a92046b0eb | |||
| b40968a0f0 | |||
| 2867542cfa | |||
| 61e21462aa | |||
| acdf4e5bdc | |||
| 33fdf67e4d | |||
| 0d7379520f | |||
| affe61dc3b | |||
| 4f701b7aec | |||
| dd87371574 | |||
| 4f7c1cb88f | |||
| 2cb3a29fa8 | |||
| 1869b57d62 | |||
| 58b0f7df9c | |||
| bc35208682 | |||
| 4f8cb8db78 | |||
| 5ff39c6243 | |||
| dc200cf22d | |||
| 62b95b4e48 | |||
| f3d4d8cd5e | |||
| 3d4eec4a6a | |||
| b07aba7937 | |||
| 1f9bb67400 | |||
| b8f1fad654 | |||
| eb938a9b78 | |||
| 69df5c7a95 | |||
| 9c7d71b9be | |||
| 5d0ff7fe6e | |||
| b975180fc8 | |||
| 3192684582 | |||
| aa1cb1f663 | |||
| 6a63dfce1c | |||
| c2cfba4af3 | |||
| 62849ef114 | |||
| 60ea4a91bf | |||
| dd9b3b32c3 | |||
| 7c8e092457 | |||
| 099ef67348 | |||
| 04a6c7009a | |||
| 813d108c7a | |||
| 4f9a091671 | |||
| e7db7c5a94 | |||
| 29f0a7fc4f | |||
| 2e73fe948e | |||
| 538ac38e18 | |||
| c2d223ff55 | |||
| 07648e3344 | |||
| a5cb0c25ca | |||
| 5300fa18c7 | |||
| 3ee58fd2e3 | |||
| d5f74e93c5 | |||
| 1c4c995a41 | |||
| d2d3d44c9b | |||
| fe56d01da2 | |||
| 190dbb5293 | |||
| 9100a58671 | |||
| 5f3b7f25d0 | |||
| ae0bb7ee95 | |||
| 07bfe90695 | |||
| 4bf391f5c3 | |||
| 00d445a269 | |||
| 250f448c3f | |||
| f29cae602f | |||
| 3a546a6395 | |||
| 12e6899580 | |||
| ec9fe67afe | |||
| 8e787f6ee4 | |||
| 363ac44461 | |||
| 7e0d9f6f39 | |||
| 444dcfb8fd | |||
| 62b0d82682 | |||
| 9d26953398 | |||
| c42f6db0eb | |||
| 22f07b3e48 | |||
| ca047fe0c0 | |||
| ddedb3c7a7 | |||
| 9664305a87 | |||
| 2442e9a5bd | |||
| 65752fef72 | |||
| 8435464c84 | |||
| f9b830b5ea | |||
| 1ab8a6f6da | |||
| 53ff4ed57f | |||
| 9df34e09d0 | |||
| 9f38835543 | |||
| c89a0e9929 |
Vendored
-9
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"pioarduino.pioarduino-ide",
|
||||
"platformio.platformio-ide"
|
||||
],
|
||||
"unwantedRecommendations": [
|
||||
"ms-vscode.cpptools-extension-pack"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v6.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [
|
||||
["0x239A","0x4405"],
|
||||
["0x239A","0x0029"],
|
||||
["0x239A","0x002A"],
|
||||
["0x239A","0x0071"]
|
||||
],
|
||||
"usb_product": "HT-n5262",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "heltec_tower_v2",
|
||||
"bsp": {
|
||||
"name": "adafruit"
|
||||
},
|
||||
"softdevice": {
|
||||
"sd_flags": "-DS140",
|
||||
"sd_name": "s140",
|
||||
"sd_version": "6.1.1",
|
||||
"sd_fwid": "0x00B6"
|
||||
},
|
||||
"bootloader": {
|
||||
"settings_addr": "0xFF000"
|
||||
}
|
||||
},
|
||||
"connectivity": [
|
||||
"bluetooth"
|
||||
],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"svd_path": "nrf52840.svd",
|
||||
"openocd_target": "nrf52.cfg"
|
||||
},
|
||||
"frameworks": [
|
||||
"arduino"
|
||||
],
|
||||
"name": "Heltec Tower V2 Board",
|
||||
"upload": {
|
||||
"maximum_ram_size": 235520,
|
||||
"maximum_size": 815104,
|
||||
"speed": 115200,
|
||||
"protocol": "nrfutil",
|
||||
"protocols": [
|
||||
"jlink",
|
||||
"nrfjprog",
|
||||
"nrfutil",
|
||||
"stlink"
|
||||
],
|
||||
"use_1200bps_touch": true,
|
||||
"require_upload_port": true,
|
||||
"wait_for_upload_port": true
|
||||
},
|
||||
"url": "https://heltec.org/",
|
||||
"vendor": "Heltec"
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "esp32s3_out.ld",
|
||||
"memory_type": "qio_opi"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-D BOARD_HAS_PSRAM",
|
||||
"-D ARDUINO_USB_CDC_ON_BOOT=0",
|
||||
"-D ARDUINO_USB_MODE=0",
|
||||
"-D ARDUINO_RUNNING_CORE=1",
|
||||
"-D ARDUINO_EVENT_RUNNING_CORE=0"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "qio",
|
||||
"psram_type": "qio_opi",
|
||||
"hwids": [["0x303A", "0x1001"]],
|
||||
"mcu": "esp32s3",
|
||||
"variant": "ELECROW-ThinkNode-M7"
|
||||
},
|
||||
"connectivity": ["wifi", "bluetooth", "lora"],
|
||||
"debug": {
|
||||
"default_tool": "esp-builtin",
|
||||
"onboard_tools": ["esp-builtin"],
|
||||
"openocd_target": "esp32s3.cfg"
|
||||
},
|
||||
"frameworks": ["arduino", "espidf"],
|
||||
"name": "ELECROW ThinkNode M7",
|
||||
"upload": {
|
||||
"flash_size": "8MB",
|
||||
"maximum_ram_size": 524288,
|
||||
"maximum_size": 8388608,
|
||||
"use_1200bps_touch": true,
|
||||
"wait_for_upload_port": true,
|
||||
"require_upload_port": true,
|
||||
"speed": 921600
|
||||
},
|
||||
"url": "https://www.elecrow.com",
|
||||
"vendor": "ELECROW"
|
||||
}
|
||||
Executable
+57
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "esp32s3_out.ld",
|
||||
"memory_type": "qio_opi",
|
||||
"partitions": "default_16MB.csv"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-DBOARD_HAS_PSRAM",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=0",
|
||||
"-DARDUINO_USB_MODE=0",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=0"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "qio",
|
||||
"psram_type": "qio_opi",
|
||||
"hwids": [
|
||||
[
|
||||
"0x303A",
|
||||
"0x1001"
|
||||
]
|
||||
],
|
||||
"mcu": "esp32s3",
|
||||
"variant": "ELECROW-ThinkNode-M9"
|
||||
},
|
||||
"connectivity": [
|
||||
"wifi",
|
||||
"bluetooth",
|
||||
"lora"
|
||||
],
|
||||
"debug": {
|
||||
"default_tool": "esp-builtin",
|
||||
"onboard_tools": [
|
||||
"esp-builtin"
|
||||
],
|
||||
"openocd_target": "esp32s3.cfg"
|
||||
},
|
||||
"frameworks": [
|
||||
"arduino",
|
||||
"espidf"
|
||||
],
|
||||
"name": "elecrow-thinknode-m9",
|
||||
"upload": {
|
||||
"flash_size": "16MB",
|
||||
"maximum_ram_size": 524288,
|
||||
"maximum_size": 16777216,
|
||||
"use_1200bps_touch": true,
|
||||
"wait_for_upload_port": true,
|
||||
"require_upload_port": true,
|
||||
"speed": 921600
|
||||
},
|
||||
"url": "https://www.elecrow.com/thinknode-m1-meshtastic-lora-signal-transceiver-powered-by-nrf52840-with-154-screen-support-gps.html",
|
||||
"vendor": "ELECROW"
|
||||
}
|
||||
+4
-3
@@ -1,11 +1,12 @@
|
||||
{ pkgs ? import <nixpkgs> {} }:
|
||||
let
|
||||
{pkgs ? import <nixpkgs> {}}: let
|
||||
in
|
||||
pkgs.mkShell {
|
||||
buildInputs = [
|
||||
pkgs.platformio
|
||||
pkgs.python3
|
||||
pkgs.gcc
|
||||
pkgs.gtest
|
||||
# optional: needed as a programmer i.e. for esp32
|
||||
pkgs.avrdude
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,6 +263,20 @@ This document provides an overview of CLI commands that can be sent to MeshCore
|
||||
|
||||
---
|
||||
|
||||
#### View or change the LoRa FEM receive-path gain state on supported boards
|
||||
**Usage:**
|
||||
- `get radio.fem.rxgain`
|
||||
- `set radio.fem.rxgain <state>`
|
||||
|
||||
**Parameters:**
|
||||
- `state`: `on`|`off`
|
||||
|
||||
**Notes:**
|
||||
- This controls the external LoRa FEM receive-path LNA where the board supports it.
|
||||
- This is separate from `radio.rxgain`, which controls the radio chip receive gain mode.
|
||||
|
||||
---
|
||||
|
||||
### System
|
||||
|
||||
#### View or change this node's name
|
||||
@@ -564,6 +578,20 @@ This document provides an overview of CLI commands that can be sent to MeshCore
|
||||
|
||||
---
|
||||
|
||||
#### Enable or disable hardware Channel Activity Detection (CAD)
|
||||
**Usage:**
|
||||
- `get cad`
|
||||
- `set cad <on|off>`
|
||||
|
||||
**Description:** When enabled, the radio performs a hardware Channel Activity Detection scan before transmitting and defers if the channel is busy. Runs independently of `int.thresh` — either, both, or none may be active.
|
||||
|
||||
**Parameters:**
|
||||
- `on|off`: Enable or disable hardware CAD
|
||||
|
||||
**Default:** `off`
|
||||
|
||||
---
|
||||
|
||||
#### View or change the AGC Reset Interval
|
||||
**Usage:**
|
||||
- `get agc.reset.interval`
|
||||
|
||||
@@ -17,6 +17,8 @@ Once you have a working app/project, you need to be able to demonstrate it exist
|
||||
| 0000 - 00FF | -reserved for internal use- | |
|
||||
| 0100 | MeshCore Open | zsylvester@monitormx.com — https://github.com/zjs81/meshcore-open |
|
||||
| 0110 - 011F | Ripple | ripple_biz@protonmail.com — https://buymeacoffee.com/ripplebiz |
|
||||
| 0120 | MCO Advanced | most.original.address@gmail.com — https://hdden.ru/MCOa/ |
|
||||
| 0130 - 013F | StreamSensor | william@housedillon.com - https://housedillon.com/blog/lora-e5-with-seeed-fusion |
|
||||
| FF00 - FFFF | -reserved for testing/dev- | |
|
||||
|
||||
(add rows, inside the range 0100 - FEFF for custom apps)
|
||||
|
||||
@@ -545,7 +545,7 @@ bool DataStore::putBlobByKey(const uint8_t key[], int key_len, const uint8_t src
|
||||
uint32_t pos = 0, found_pos = 0;
|
||||
uint32_t min_timestamp = 0xFFFFFFFF;
|
||||
|
||||
// search for matching key OR evict by oldest timestmap
|
||||
// search for matching key OR evict by oldest timestamp
|
||||
BlobRec tmp;
|
||||
file.seek(0);
|
||||
while (file.read((uint8_t *) &tmp, sizeof(tmp)) == sizeof(tmp)) {
|
||||
|
||||
@@ -261,6 +261,9 @@ float MyMesh::getAirtimeBudgetFactor() const {
|
||||
int MyMesh::getInterferenceThreshold() const {
|
||||
return 0; // disabled for now, until currentRSSI() problem is resolved
|
||||
}
|
||||
bool MyMesh::getCADEnabled() const {
|
||||
return true; // hardware CAD before TX (no CLI toggle on companion; enabled by default)
|
||||
}
|
||||
|
||||
int MyMesh::calcRxDelay(float score, uint32_t air_time) const {
|
||||
if (_prefs.rx_delay_base <= 0.0f) return 0;
|
||||
@@ -854,7 +857,7 @@ void MyMesh::onSendTimeout() {}
|
||||
|
||||
MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMeshTables &tables, DataStore& store, AbstractUITask* ui)
|
||||
: BaseChatMesh(radio, *new ArduinoMillis(), rng, rtc, *new StaticPoolPacketManager(16), tables),
|
||||
_serial(NULL), telemetry(MAX_PACKET_PAYLOAD - 4), _store(&store), _ui(ui) {
|
||||
_serial(NULL), telemetry(MAX_PACKET_PAYLOAD - 4), _store(&store), _ui(ui), _iter(0) {
|
||||
_iter_started = false;
|
||||
_cli_rescue = false;
|
||||
offline_queue_len = 0;
|
||||
@@ -1542,6 +1545,7 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
memcpy(anon.id.pub_key, pub_key, PUB_KEY_SIZE);
|
||||
anon.out_path_len = 0; // default to zero-hop direct
|
||||
anon.type = ADV_TYPE_NONE; // unknown
|
||||
anon.lastmod = getRTCClock()->getCurrentTime();
|
||||
|
||||
if (addContact(anon)) recipient = &anon;
|
||||
}
|
||||
@@ -1981,6 +1985,7 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
sendPacket(pkt, priority, 0);
|
||||
writeOKFrame();
|
||||
} else {
|
||||
releasePacket(pkt);
|
||||
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
|
||||
}
|
||||
} else {
|
||||
@@ -2186,15 +2191,7 @@ void MyMesh::checkSerialInterface() {
|
||||
&& !_serial->isWriteBusy() // don't spam the Serial Interface too quickly!
|
||||
) {
|
||||
ContactInfo contact;
|
||||
bool found = false;
|
||||
while (_iter.hasNext(this, contact)) {
|
||||
if (contact.type != ADV_TYPE_NONE) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
if (_iter.hasNext(this, contact)) {
|
||||
if (contact.lastmod > _iter_filter_since) { // apply the 'since' filter
|
||||
writeContactRespFrame(RESP_CODE_CONTACT, contact);
|
||||
if (contact.lastmod > _most_recent_lastmod) {
|
||||
|
||||
@@ -105,6 +105,7 @@ public:
|
||||
protected:
|
||||
float getAirtimeBudgetFactor() const override;
|
||||
int getInterferenceThreshold() const override;
|
||||
bool getCADEnabled() const override;
|
||||
int calcRxDelay(float score, uint32_t air_time) const override;
|
||||
uint32_t getRetransmitDelay(const mesh::Packet *packet) override;
|
||||
uint32_t getDirectRetransmitDelay(const mesh::Packet *packet) override;
|
||||
|
||||
@@ -116,6 +116,10 @@ void setup() {
|
||||
|
||||
board.begin();
|
||||
|
||||
#ifdef HAS_EXTERNAL_WATCHDOG
|
||||
external_watchdog.begin();
|
||||
#endif
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
DisplayDriver* disp = NULL;
|
||||
if (display.begin()) {
|
||||
@@ -249,7 +253,10 @@ void loop() {
|
||||
ui_task.loop();
|
||||
#endif
|
||||
rtc_clock.tick();
|
||||
|
||||
#ifdef HAS_EXTERNAL_WATCHDOG
|
||||
external_watchdog.loop();
|
||||
#endif
|
||||
|
||||
if (!the_mesh.hasPendingWork()) {
|
||||
#if defined(NRF52_PLATFORM)
|
||||
board.sleep(0); // nrf ignores seconds param, sleeps whenever possible
|
||||
|
||||
@@ -697,8 +697,10 @@ void UITask::shutdown(bool restart){
|
||||
if (restart) {
|
||||
_board->reboot();
|
||||
} else {
|
||||
// still necessary until all boards are refactored to use poweroff
|
||||
_display->turnOff();
|
||||
radio_driver.powerOff();
|
||||
// Power off board including radio, display, GPS and components
|
||||
_board->powerOff();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,7 +307,9 @@ void UITask::shutdown(bool restart){
|
||||
if (restart) {
|
||||
_board->reboot();
|
||||
} else {
|
||||
_display->turnOff();
|
||||
radio_driver.powerOff();
|
||||
// Power off board including radio, display, GPS and components
|
||||
_board->powerOff();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -568,6 +568,7 @@ void UITask::shutdown(bool restart){
|
||||
} else {
|
||||
_display->turnOff();
|
||||
radio_driver.powerOff();
|
||||
// Power off board including radio, display, GPS and components
|
||||
_board->powerOff();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -549,8 +549,7 @@ uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) {
|
||||
return getRNG()->nextInt(0, 5*t + 1);
|
||||
}
|
||||
|
||||
bool MyMesh::filterRecvFloodPacket(mesh::Packet* pkt) {
|
||||
// just try to determine region for packet (apply later in allowPacketForward())
|
||||
mesh::DispatcherAction MyMesh::onRecvPacket(mesh::Packet* pkt) {
|
||||
if (pkt->getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD) {
|
||||
recv_pkt_region = region_map.findMatch(pkt, REGION_DENY_FLOOD);
|
||||
} else if (pkt->getRouteType() == ROUTE_TYPE_FLOOD) {
|
||||
@@ -562,8 +561,7 @@ bool MyMesh::filterRecvFloodPacket(mesh::Packet* pkt) {
|
||||
} else {
|
||||
recv_pkt_region = NULL;
|
||||
}
|
||||
// do normal processing
|
||||
return false;
|
||||
return Mesh::onRecvPacket(pkt);
|
||||
}
|
||||
|
||||
void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const mesh::Identity &sender,
|
||||
@@ -867,13 +865,13 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
set_radio_at = revert_radio_at = 0;
|
||||
_logging = false;
|
||||
region_load_active = false;
|
||||
recv_pkt_region = NULL;
|
||||
|
||||
#if MAX_NEIGHBOURS
|
||||
memset(neighbours, 0, sizeof(neighbours));
|
||||
#endif
|
||||
|
||||
// defaults
|
||||
memset(&_prefs, 0, sizeof(_prefs));
|
||||
_prefs.airtime_factor = 1.0;
|
||||
_prefs.rx_delay_base = 0.0f; // turn off by default, was 10.0;
|
||||
_prefs.tx_delay_factor = 0.5f; // was 0.25f
|
||||
@@ -893,6 +891,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
_prefs.flood_max_unscoped = 64;
|
||||
_prefs.flood_max_advert = 8;
|
||||
_prefs.interference_threshold = 0; // disabled
|
||||
_prefs.cad_enabled = 0; // hardware CAD before TX (off by default; 'set cad on')
|
||||
|
||||
// bridge defaults
|
||||
_prefs.bridge_enabled = 1; // enabled
|
||||
@@ -917,6 +916,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
_prefs.rx_boosted_gain = 1; // enabled by default;
|
||||
#endif
|
||||
#endif
|
||||
_prefs.radio_fem_rxgain = 1;
|
||||
|
||||
pending_discover_tag = 0;
|
||||
pending_discover_until = 0;
|
||||
@@ -965,6 +965,7 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
||||
radio_driver.setRxBoostedGainMode(_prefs.rx_boosted_gain);
|
||||
MESH_DEBUG_PRINTLN("RX Boosted Gain Mode: %s",
|
||||
radio_driver.getRxBoostedGainMode() ? "Enabled" : "Disabled");
|
||||
board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain);
|
||||
|
||||
updateAdvertTimer();
|
||||
updateFloodAdvertTimer();
|
||||
@@ -1059,11 +1060,9 @@ void MyMesh::setTxPower(int8_t power_dbm) {
|
||||
radio_driver.setTxPower(power_dbm);
|
||||
}
|
||||
|
||||
#if defined(USE_SX1262) || defined(USE_SX1268)
|
||||
void MyMesh::setRxBoostedGain(bool enable) {
|
||||
radio_driver.setRxBoostedGainMode(enable);
|
||||
bool MyMesh::setRxBoostedGain(bool enable) {
|
||||
return radio_driver.setRxBoostedGainMode(enable);
|
||||
}
|
||||
#endif
|
||||
|
||||
void MyMesh::formatNeighborsReply(char *reply) {
|
||||
char *dp = reply;
|
||||
|
||||
@@ -150,6 +150,9 @@ protected:
|
||||
int getInterferenceThreshold() const override {
|
||||
return _prefs.interference_threshold;
|
||||
}
|
||||
bool getCADEnabled() const override {
|
||||
return _prefs.cad_enabled;
|
||||
}
|
||||
int getAGCResetInterval() const override {
|
||||
return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds
|
||||
}
|
||||
@@ -163,7 +166,7 @@ protected:
|
||||
}
|
||||
#endif
|
||||
|
||||
bool filterRecvFloodPacket(mesh::Packet* pkt) override;
|
||||
mesh::DispatcherAction onRecvPacket(mesh::Packet* pkt) override;
|
||||
|
||||
void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override;
|
||||
int searchPeersByHash(const uint8_t* hash) override;
|
||||
@@ -249,7 +252,6 @@ public:
|
||||
// To check if there is pending work
|
||||
bool hasPendingWork() const;
|
||||
|
||||
#if defined(USE_SX1262) || defined(USE_SX1268)
|
||||
void setRxBoostedGain(bool enable) override;
|
||||
#endif
|
||||
bool setRxBoostedGain(bool enable) override;
|
||||
|
||||
};
|
||||
|
||||
@@ -41,7 +41,8 @@ void UITask::begin(NodePrefs* node_prefs, const char* build_date, const char* fi
|
||||
}
|
||||
|
||||
// v1.2.3 (1 Jan 2025)
|
||||
sprintf(_version_info, "%s (%s)", version, build_date);
|
||||
snprintf(_version_info, sizeof(_version_info), "%s (%s)", version, build_date);
|
||||
free(version);
|
||||
}
|
||||
|
||||
void UITask::renderCurrScreen() {
|
||||
|
||||
@@ -33,6 +33,10 @@ void setup() {
|
||||
|
||||
board.begin();
|
||||
|
||||
#ifdef HAS_EXTERNAL_WATCHDOG
|
||||
external_watchdog.begin();
|
||||
#endif
|
||||
|
||||
#if defined(MESH_DEBUG) && defined(NRF52_PLATFORM)
|
||||
// give some extra time for serial to settle so
|
||||
// boot debug messages can be seen on terminal
|
||||
@@ -152,7 +156,13 @@ void loop() {
|
||||
#endif
|
||||
rtc_clock.tick();
|
||||
|
||||
#ifdef HAS_EXTERNAL_WATCHDOG
|
||||
external_watchdog.loop();
|
||||
#endif
|
||||
if (the_mesh.getNodePrefs()->powersaving_enabled && !the_mesh.hasPendingWork()) {
|
||||
#ifdef HAS_EXTERNAL_WATCHDOG
|
||||
external_watchdog.feed();
|
||||
#endif
|
||||
#if defined(NRF52_PLATFORM)
|
||||
board.sleep(0); // nrf ignores seconds param, sleeps whenever possible
|
||||
#else
|
||||
|
||||
@@ -290,8 +290,7 @@ bool MyMesh::allowPacketForward(const mesh::Packet *packet) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MyMesh::filterRecvFloodPacket(mesh::Packet* pkt) {
|
||||
// just try to determine region for packet (apply later in allowPacketForward())
|
||||
mesh::DispatcherAction MyMesh::onRecvPacket(mesh::Packet* pkt) {
|
||||
if (pkt->getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD) {
|
||||
recv_pkt_region = region_map.findMatch(pkt, REGION_DENY_FLOOD);
|
||||
} else if (pkt->getRouteType() == ROUTE_TYPE_FLOOD) {
|
||||
@@ -303,8 +302,7 @@ bool MyMesh::filterRecvFloodPacket(mesh::Packet* pkt) {
|
||||
} else {
|
||||
recv_pkt_region = NULL;
|
||||
}
|
||||
// do normal processing
|
||||
return false;
|
||||
return Mesh::onRecvPacket(pkt);
|
||||
}
|
||||
|
||||
void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const mesh::Identity &sender,
|
||||
@@ -627,9 +625,9 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
_logging = false;
|
||||
region_load_active = false;
|
||||
set_radio_at = revert_radio_at = 0;
|
||||
recv_pkt_region = NULL;
|
||||
|
||||
// defaults
|
||||
memset(&_prefs, 0, sizeof(_prefs));
|
||||
_prefs.airtime_factor = 1.0;
|
||||
_prefs.rx_delay_base = 0.0f; // off by default, was 10.0
|
||||
_prefs.tx_delay_factor = 0.5f; // was 0.25f;
|
||||
@@ -650,6 +648,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
_prefs.flood_max_unscoped = 64;
|
||||
_prefs.flood_max_advert = 8;
|
||||
_prefs.interference_threshold = 0; // disabled
|
||||
_prefs.cad_enabled = 0; // hardware CAD before TX (off by default; 'set cad on')
|
||||
#ifdef ROOM_PASSWORD
|
||||
StrHelper::strncpy(_prefs.guest_password, ROOM_PASSWORD, sizeof(_prefs.guest_password));
|
||||
#endif
|
||||
@@ -658,6 +657,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
_prefs.gps_enabled = 0;
|
||||
_prefs.gps_interval = 0;
|
||||
_prefs.advert_loc_policy = ADVERT_LOC_PREFS;
|
||||
_prefs.radio_fem_rxgain = 1;
|
||||
|
||||
next_post_idx = 0;
|
||||
next_client_idx = 0;
|
||||
@@ -699,6 +699,7 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
||||
|
||||
radio_driver.setParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||
radio_driver.setTxPower(_prefs.tx_power_dbm);
|
||||
board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain);
|
||||
|
||||
updateAdvertTimer();
|
||||
updateFloodAdvertTimer();
|
||||
|
||||
@@ -144,6 +144,9 @@ protected:
|
||||
int getInterferenceThreshold() const override {
|
||||
return _prefs.interference_threshold;
|
||||
}
|
||||
bool getCADEnabled() const override {
|
||||
return _prefs.cad_enabled;
|
||||
}
|
||||
int getAGCResetInterval() const override {
|
||||
return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds
|
||||
}
|
||||
@@ -151,7 +154,7 @@ protected:
|
||||
return _prefs.multi_acks;
|
||||
}
|
||||
|
||||
bool filterRecvFloodPacket(mesh::Packet* pkt) override;
|
||||
mesh::DispatcherAction onRecvPacket(mesh::Packet* pkt) override;
|
||||
|
||||
bool allowPacketForward(const mesh::Packet* packet) override;
|
||||
void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override;
|
||||
|
||||
@@ -41,7 +41,8 @@ void UITask::begin(NodePrefs* node_prefs, const char* build_date, const char* fi
|
||||
}
|
||||
|
||||
// v1.2.3 (1 Jan 2025)
|
||||
sprintf(_version_info, "%s (%s)", version, build_date);
|
||||
snprintf(_version_info, sizeof(_version_info), "%s (%s)", version, build_date);
|
||||
free(version);
|
||||
}
|
||||
|
||||
void UITask::renderCurrScreen() {
|
||||
|
||||
@@ -24,6 +24,10 @@ void setup() {
|
||||
|
||||
board.begin();
|
||||
|
||||
#ifdef HAS_EXTERNAL_WATCHDOG
|
||||
external_watchdog.begin();
|
||||
#endif
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
if (display.begin()) {
|
||||
display.startFrame();
|
||||
@@ -115,4 +119,7 @@ void loop() {
|
||||
ui_task.loop();
|
||||
#endif
|
||||
rtc_clock.tick();
|
||||
#ifdef HAS_EXTERNAL_WATCHDOG
|
||||
external_watchdog.loop();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ class MyMesh : public BaseChatMesh, ContactVisitor {
|
||||
File file = _fs->open("/contacts", "w", true);
|
||||
#endif
|
||||
if (file) {
|
||||
ContactsIterator iter;
|
||||
ContactsIterator iter = startContactsIterator();
|
||||
ContactInfo c;
|
||||
uint8_t unused = 0;
|
||||
uint32_t reserved = 0;
|
||||
@@ -560,6 +560,10 @@ void setup() {
|
||||
|
||||
board.begin();
|
||||
|
||||
#ifdef HAS_EXTERNAL_WATCHDOG
|
||||
external_watchdog.begin();
|
||||
#endif
|
||||
|
||||
if (!radio_init()) { halt(); }
|
||||
|
||||
fast_rng.begin(radio_driver.getRngSeed());
|
||||
@@ -591,4 +595,7 @@ void setup() {
|
||||
void loop() {
|
||||
the_mesh.loop();
|
||||
rtc_clock.tick();
|
||||
#ifdef HAS_EXTERNAL_WATCHDOG
|
||||
external_watchdog.loop();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -323,6 +323,9 @@ uint32_t SensorMesh::getDirectRetransmitDelay(const mesh::Packet* packet) {
|
||||
int SensorMesh::getInterferenceThreshold() const {
|
||||
return _prefs.interference_threshold;
|
||||
}
|
||||
bool SensorMesh::getCADEnabled() const {
|
||||
return _prefs.cad_enabled;
|
||||
}
|
||||
int SensorMesh::getAGCResetInterval() const {
|
||||
return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds
|
||||
}
|
||||
@@ -707,7 +710,6 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise
|
||||
set_radio_at = revert_radio_at = 0;
|
||||
|
||||
// defaults
|
||||
memset(&_prefs, 0, sizeof(_prefs));
|
||||
_prefs.airtime_factor = 1.0;
|
||||
_prefs.rx_delay_base = 0.0f; // turn off by default, was 10.0;
|
||||
_prefs.tx_delay_factor = 0.5f; // was 0.25f
|
||||
@@ -726,11 +728,13 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise
|
||||
_prefs.disable_fwd = true;
|
||||
_prefs.flood_max = 64;
|
||||
_prefs.interference_threshold = 0; // disabled
|
||||
_prefs.cad_enabled = 0; // hardware CAD before TX (off by default; 'set cad on')
|
||||
|
||||
// GPS defaults
|
||||
_prefs.gps_enabled = 0;
|
||||
_prefs.gps_interval = 0;
|
||||
_prefs.advert_loc_policy = ADVERT_LOC_PREFS;
|
||||
_prefs.radio_fem_rxgain = 1;
|
||||
|
||||
memset(default_scope.key, 0, sizeof(default_scope.key));
|
||||
}
|
||||
@@ -766,6 +770,7 @@ void SensorMesh::begin(FILESYSTEM* fs) {
|
||||
|
||||
radio_driver.setParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||
radio_driver.setTxPower(_prefs.tx_power_dbm);
|
||||
board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain);
|
||||
|
||||
updateAdvertTimer();
|
||||
updateFloodAdvertTimer();
|
||||
|
||||
@@ -120,6 +120,7 @@ protected:
|
||||
uint32_t getRetransmitDelay(const mesh::Packet* packet) override;
|
||||
uint32_t getDirectRetransmitDelay(const mesh::Packet* packet) override;
|
||||
int getInterferenceThreshold() const override;
|
||||
bool getCADEnabled() const override;
|
||||
int getAGCResetInterval() const override;
|
||||
void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override;
|
||||
int searchPeersByHash(const uint8_t* hash) override;
|
||||
|
||||
@@ -41,7 +41,8 @@ void UITask::begin(NodePrefs* node_prefs, const char* build_date, const char* fi
|
||||
}
|
||||
|
||||
// v1.2.3 (1 Jan 2025)
|
||||
sprintf(_version_info, "%s (%s)", version, build_date);
|
||||
snprintf(_version_info, sizeof(_version_info), "%s (%s)", version, build_date);
|
||||
free(version);
|
||||
}
|
||||
|
||||
void UITask::renderCurrScreen() {
|
||||
|
||||
@@ -58,6 +58,10 @@ void setup() {
|
||||
|
||||
board.begin();
|
||||
|
||||
#ifdef HAS_EXTERNAL_WATCHDOG
|
||||
external_watchdog.begin();
|
||||
#endif
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
if (display.begin()) {
|
||||
display.startFrame();
|
||||
@@ -147,4 +151,7 @@ void loop() {
|
||||
ui_task.loop();
|
||||
#endif
|
||||
rtc_clock.tick();
|
||||
#ifdef HAS_EXTERNAL_WATCHDOG
|
||||
external_watchdog.loop();
|
||||
#endif
|
||||
}
|
||||
|
||||
+4
-1
@@ -19,7 +19,8 @@ monitor_speed = 115200
|
||||
lib_deps =
|
||||
SPI
|
||||
Wire
|
||||
jgromes/RadioLib @ ^7.6.0
|
||||
;jgromes/RadioLib @ ^7.7.1
|
||||
https://github.com/jgromes/RadioLib.git#6d8934836678d8894e3d556550475b37dce3e2b6
|
||||
rweather/Crypto @ ^0.4.0
|
||||
adafruit/RTClib @ ^2.1.3
|
||||
melopero/Melopero RV3028 @ ^1.1.0
|
||||
@@ -157,6 +158,7 @@ lib_deps =
|
||||
|
||||
[env:native]
|
||||
platform = native
|
||||
test_framework = googletest
|
||||
build_flags = -std=c++17
|
||||
-I src
|
||||
-I test/mocks
|
||||
@@ -164,5 +166,6 @@ test_build_src = yes
|
||||
build_src_filter =
|
||||
-<*>
|
||||
+<../src/Utils.cpp>
|
||||
+<../src/Packet.cpp>
|
||||
lib_deps =
|
||||
google/googletest @ 1.17.0
|
||||
|
||||
@@ -66,6 +66,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();
|
||||
|
||||
@@ -65,6 +65,8 @@ public:
|
||||
|
||||
virtual void triggerNoiseFloorCalibrate(int threshold) { }
|
||||
|
||||
virtual void setCADEnabled(bool enable) { }
|
||||
|
||||
virtual void resetAGC() { }
|
||||
|
||||
virtual bool isInRecvMode() const = 0;
|
||||
@@ -166,6 +168,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; }
|
||||
|
||||
|
||||
+31
-16
@@ -55,7 +55,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);
|
||||
|
||||
@@ -89,14 +90,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);
|
||||
@@ -117,7 +120,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);
|
||||
}
|
||||
@@ -134,7 +138,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(?)
|
||||
@@ -155,6 +160,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;
|
||||
@@ -193,7 +202,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);
|
||||
|
||||
@@ -220,7 +230,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);
|
||||
@@ -251,7 +262,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; }
|
||||
@@ -278,7 +290,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)
|
||||
}
|
||||
@@ -296,7 +309,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);
|
||||
|
||||
@@ -353,7 +367,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)
|
||||
}
|
||||
@@ -633,7 +648,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) {
|
||||
@@ -662,7 +677,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) {
|
||||
@@ -695,7 +710,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);
|
||||
}
|
||||
|
||||
@@ -705,7 +720,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);
|
||||
}
|
||||
@@ -718,7 +733,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
@@ -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
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -100,7 +101,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) { }
|
||||
|
||||
@@ -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
@@ -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
|
||||
//...
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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())) {
|
||||
@@ -930,11 +942,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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -166,7 +172,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);
|
||||
|
||||
+72
-80
@@ -28,16 +28,21 @@ 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("/ser_prefs")) {
|
||||
File file = fs->open("/ser_prefs");
|
||||
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 +93,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 +128,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("/ser_prefs");
|
||||
File file = fs->open("/ser_prefs", FILE_O_WRITE);
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
File file = fs->open("/com_prefs", "w");
|
||||
File file = fs->open("/ser_prefs", "w");
|
||||
#else
|
||||
File file = fs->open("/com_prefs", "w", true);
|
||||
File file = fs->open("/ser_prefs", "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 +462,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 +527,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,7 +747,7 @@ 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");
|
||||
};
|
||||
} else {
|
||||
strcpy(reply, "unknown config: ");
|
||||
@@ -776,6 +766,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 +793,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 +886,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,13 +909,9 @@ 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());
|
||||
|
||||
+124
-4
@@ -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,7 +20,9 @@
|
||||
#define LOOP_DETECT_MODERATE 2
|
||||
#define LOOP_DETECT_STRICT 3
|
||||
|
||||
struct NodePrefs { // persisted to file
|
||||
class NodePrefs : public ConfigSerializer {
|
||||
public:
|
||||
// in-memory backing data
|
||||
float airtime_factor;
|
||||
char node_name[32];
|
||||
double node_lat, node_lon;
|
||||
@@ -61,8 +64,125 @@ struct NodePrefs { // persisted to file
|
||||
float adc_multiplier;
|
||||
char owner_info[120];
|
||||
uint8_t rx_boosted_gain; // power settings
|
||||
uint8_t radio_fem_rxgain; // LoRa FEM RX gain setting
|
||||
uint8_t path_hash_mode; // which path mode to use when sending
|
||||
uint8_t loop_detect;
|
||||
uint8_t cad_enabled; // hardware Channel Activity Detection before TX (boolean)
|
||||
|
||||
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) { }
|
||||
};
|
||||
|
||||
class CommonCLICallbacks {
|
||||
@@ -109,8 +229,8 @@ 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
|
||||
};
|
||||
};
|
||||
|
||||
@@ -137,7 +257,7 @@ public:
|
||||
: _board(&board), _rtc(&rtc), _sensors(&sensors), _region_map(®ion_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);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,312 @@
|
||||
#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 == '"' || 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
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
#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, 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);
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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() { }
|
||||
};
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#if defined(NRF52_PLATFORM)
|
||||
#include "NRF52Board.h"
|
||||
#include <target.h>
|
||||
|
||||
#include <bluefruit.h>
|
||||
#include <nrf_soc.h>
|
||||
@@ -297,6 +298,37 @@ float NRF52Board::getMCUTemperature() {
|
||||
return temp * 0.25f; // Convert to *C
|
||||
}
|
||||
|
||||
void NRF52Board::powerOff() {
|
||||
// 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);
|
||||
|
||||
// Power off GPS if any
|
||||
if(sensors.getLocationProvider() != NULL) {
|
||||
sensors.getLocationProvider()->stop();
|
||||
}
|
||||
|
||||
// Flush serial buffers
|
||||
Serial.flush();
|
||||
delay(100);
|
||||
|
||||
// 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
|
||||
|
||||
@@ -50,6 +50,7 @@ public:
|
||||
virtual uint8_t getStartupReason() const override { return startup_reason; }
|
||||
virtual float getMCUTemperature() override;
|
||||
virtual void reboot() override { NVIC_SystemReset(); }
|
||||
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;
|
||||
|
||||
@@ -93,13 +93,15 @@ bool RegionMap::load(FILESYSTEM* _fs, const char* path) {
|
||||
while (num_regions < MAX_REGION_ENTRIES) {
|
||||
auto r = ®ions[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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -66,11 +66,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
|
||||
|
||||
@@ -33,8 +33,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();
|
||||
|
||||
@@ -26,6 +26,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 +41,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();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <RadioLib.h>
|
||||
#include "MeshCore.h"
|
||||
|
||||
#define SX126X_IRQ_HEADER_VALID 0b0000010000 // 4 4 valid LoRa header received
|
||||
#define SX126X_IRQ_PREAMBLE_DETECTED 0x04
|
||||
@@ -27,6 +28,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 +51,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,6 +93,8 @@ 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
|
||||
}
|
||||
|
||||
|
||||
@@ -40,8 +40,8 @@ public:
|
||||
|
||||
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
|
||||
|
||||
void setRxBoostedGainMode(bool en) override {
|
||||
((CustomSX1262 *)_radio)->setRxBoostedGainMode(en);
|
||||
bool setRxBoostedGainMode(bool en) override {
|
||||
return ((CustomSX1262 *)_radio)->setRxBoostedGainMode(en) == RADIOLIB_ERR_NONE;
|
||||
}
|
||||
bool getRxBoostedGainMode() const override {
|
||||
return ((CustomSX1262 *)_radio)->getRxBoostedGainMode();
|
||||
|
||||
@@ -66,11 +66,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
|
||||
|
||||
@@ -37,8 +37,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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -36,6 +36,7 @@ void RadioLibWrapper::begin() {
|
||||
|
||||
_noise_floor = 0;
|
||||
_threshold = 0;
|
||||
_cad_enabled = false;
|
||||
|
||||
// start average out some samples
|
||||
_num_floor_samples = 0;
|
||||
@@ -178,10 +179,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 {
|
||||
|
||||
@@ -9,6 +9,7 @@ protected:
|
||||
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;
|
||||
@@ -32,7 +33,7 @@ public:
|
||||
bool isInRecvMode() const override;
|
||||
bool isChannelActive();
|
||||
|
||||
bool isReceiving() override {
|
||||
bool isReceiving() override {
|
||||
if (isReceivingPacket()) return true;
|
||||
|
||||
return isChannelActive();
|
||||
@@ -46,9 +47,11 @@ public:
|
||||
virtual uint8_t getSpreadingFactor() const { return LORA_SF; }
|
||||
static uint16_t preambleLengthForSF(uint8_t sf) { return sf <= 8 ? 32 : 16; }
|
||||
void updatePreamble(uint8_t sf) { _preamble_sf = sf; _radio->setPreambleLength(preambleLengthForSF(sf)); }
|
||||
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;
|
||||
@@ -63,7 +66,7 @@ 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; }
|
||||
};
|
||||
|
||||
|
||||
@@ -889,11 +889,11 @@ void EnvironmentSensorManager::stop_gps() {
|
||||
void EnvironmentSensorManager::loop() {
|
||||
|
||||
#if ENV_INCLUDE_GPS
|
||||
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
|
||||
|
||||
@@ -142,7 +142,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;
|
||||
|
||||
@@ -42,14 +42,14 @@ 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);
|
||||
@@ -62,9 +62,7 @@ public :
|
||||
|
||||
void claim() {
|
||||
_claims++;
|
||||
if (_claims > 0) {
|
||||
if (_peripher_power) _peripher_power->claim();
|
||||
}
|
||||
if (_peripher_power) _peripher_power->claim();
|
||||
}
|
||||
|
||||
void release() {
|
||||
@@ -143,7 +141,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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,12 +1,405 @@
|
||||
#include "ST7735Display.h"
|
||||
|
||||
#ifndef DISPLAY_ROTATION
|
||||
#define DISPLAY_ROTATION 2
|
||||
//#include <Fonts/GFXFF/FreeSans9pt7b.h>
|
||||
|
||||
// Optimised ST7735 display driver, derived from Adafruit_ST7735 library.
|
||||
|
||||
#define ST_CMD_DELAY 0x80 // special signifier for command lists
|
||||
|
||||
#define ST77XX_NOP 0x00
|
||||
#define ST77XX_SWRESET 0x01
|
||||
#define ST77XX_RDDID 0x04
|
||||
#define ST77XX_RDDST 0x09
|
||||
|
||||
#define ST77XX_SLPIN 0x10
|
||||
#define ST77XX_SLPOUT 0x11
|
||||
#define ST77XX_PTLON 0x12
|
||||
#define ST77XX_NORON 0x13
|
||||
|
||||
#define ST77XX_INVOFF 0x20
|
||||
#define ST77XX_INVON 0x21
|
||||
#define ST77XX_DISPOFF 0x28
|
||||
#define ST77XX_DISPON 0x29
|
||||
#define ST77XX_CASET 0x2A
|
||||
#define ST77XX_RASET 0x2B
|
||||
#define ST77XX_RAMWR 0x2C
|
||||
#define ST77XX_RAMRD 0x2E
|
||||
|
||||
#define ST77XX_PTLAR 0x30
|
||||
#define ST77XX_TEOFF 0x34
|
||||
#define ST77XX_TEON 0x35
|
||||
#define ST77XX_MADCTL 0x36
|
||||
#define ST77XX_COLMOD 0x3A
|
||||
|
||||
#define ST77XX_MADCTL_MY 0x80
|
||||
#define ST77XX_MADCTL_MX 0x40
|
||||
#define ST77XX_MADCTL_MV 0x20
|
||||
#define ST77XX_MADCTL_ML 0x10
|
||||
#define ST77XX_MADCTL_RGB 0x00
|
||||
|
||||
#define ST77XX_RDID1 0xDA
|
||||
#define ST77XX_RDID2 0xDB
|
||||
#define ST77XX_RDID3 0xDC
|
||||
#define ST77XX_RDID4 0xDD
|
||||
|
||||
// Some ready-made 16-bit ('565') color settings:
|
||||
#define ST77XX_BLACK 0x0000
|
||||
#define ST77XX_WHITE 0xFFFF
|
||||
#define ST77XX_RED 0xF800
|
||||
#define ST77XX_GREEN 0x07E0
|
||||
#define ST77XX_BLUE 0x001F
|
||||
#define ST77XX_CYAN 0x07FF
|
||||
#define ST77XX_MAGENTA 0xF81F
|
||||
#define ST77XX_YELLOW 0xFFE0
|
||||
#define ST77XX_ORANGE 0xFC00
|
||||
|
||||
|
||||
// some flags for initR() :(
|
||||
#define INITR_GREENTAB 0x00
|
||||
#define INITR_REDTAB 0x01
|
||||
#define INITR_BLACKTAB 0x02
|
||||
#define INITR_18GREENTAB INITR_GREENTAB
|
||||
#define INITR_18REDTAB INITR_REDTAB
|
||||
#define INITR_18BLACKTAB INITR_BLACKTAB
|
||||
#define INITR_144GREENTAB 0x01
|
||||
#define INITR_MINI160x80 0x04
|
||||
#define INITR_HALLOWING 0x05
|
||||
#define INITR_MINI160x80_PLUGIN 0x06
|
||||
|
||||
// Some register settings
|
||||
#define ST7735_MADCTL_BGR 0x08
|
||||
#define ST7735_MADCTL_MH 0x04
|
||||
|
||||
#define ST7735_FRMCTR1 0xB1
|
||||
#define ST7735_FRMCTR2 0xB2
|
||||
#define ST7735_FRMCTR3 0xB3
|
||||
#define ST7735_INVCTR 0xB4
|
||||
#define ST7735_DISSET5 0xB6
|
||||
|
||||
#define ST7735_PWCTR1 0xC0
|
||||
#define ST7735_PWCTR2 0xC1
|
||||
#define ST7735_PWCTR3 0xC2
|
||||
#define ST7735_PWCTR4 0xC3
|
||||
#define ST7735_PWCTR5 0xC4
|
||||
#define ST7735_VMCTR1 0xC5
|
||||
|
||||
#define ST7735_PWCTR6 0xFC
|
||||
|
||||
#define ST7735_GMCTRP1 0xE0
|
||||
#define ST7735_GMCTRN1 0xE1
|
||||
|
||||
// Some ready-made 16-bit ('565') color settings:
|
||||
#define ST7735_BLACK ST77XX_BLACK
|
||||
#define ST7735_WHITE ST77XX_WHITE
|
||||
#define ST7735_RED ST77XX_RED
|
||||
#define ST7735_GREEN ST77XX_GREEN
|
||||
#define ST7735_BLUE ST77XX_BLUE
|
||||
#define ST7735_CYAN ST77XX_CYAN
|
||||
#define ST7735_MAGENTA ST77XX_MAGENTA
|
||||
#define ST7735_YELLOW ST77XX_YELLOW
|
||||
#define ST7735_ORANGE ST77XX_ORANGE
|
||||
|
||||
static TFT_eSPI lcd = TFT_eSPI(160, 80);
|
||||
static uint32_t curr_color;
|
||||
|
||||
#if defined(HELTEC_LORA_V3) || defined(HELTEC_TRACKER_V2)
|
||||
static SPIClass tft_spi(HSPI);
|
||||
#define _spi (&tft_spi)
|
||||
#else
|
||||
#define _spi (&SPI1)
|
||||
#endif
|
||||
|
||||
SPISettings _spiSettings = SPISettings(40000000, MSBFIRST, SPI_MODE0);
|
||||
|
||||
// clang-format off
|
||||
static const uint8_t PROGMEM
|
||||
Bcmd[] = { // Init commands for 7735B screens
|
||||
18, // 18 commands in list:
|
||||
ST77XX_SWRESET, ST_CMD_DELAY, // 1: Software reset, no args, w/delay
|
||||
50, // 50 ms delay
|
||||
ST77XX_SLPOUT, ST_CMD_DELAY, // 2: Out of sleep mode, no args, w/delay
|
||||
255, // 255 = max (500 ms) delay
|
||||
ST77XX_COLMOD, 1+ST_CMD_DELAY, // 3: Set color mode, 1 arg + delay:
|
||||
0x05, // 16-bit color
|
||||
10, // 10 ms delay
|
||||
ST7735_FRMCTR1, 3+ST_CMD_DELAY, // 4: Frame rate control, 3 args + delay:
|
||||
0x00, // fastest refresh
|
||||
0x06, // 6 lines front porch
|
||||
0x03, // 3 lines back porch
|
||||
10, // 10 ms delay
|
||||
ST77XX_MADCTL, 1, // 5: Mem access ctl (directions), 1 arg:
|
||||
0x08, // Row/col addr, bottom-top refresh
|
||||
ST7735_DISSET5, 2, // 6: Display settings #5, 2 args:
|
||||
0x15, // 1 clk cycle nonoverlap, 2 cycle gate
|
||||
// rise, 3 cycle osc equalize
|
||||
0x02, // Fix on VTL
|
||||
ST7735_INVCTR, 1, // 7: Display inversion control, 1 arg:
|
||||
0x0, // Line inversion
|
||||
ST7735_PWCTR1, 2+ST_CMD_DELAY, // 8: Power control, 2 args + delay:
|
||||
0x02, // GVDD = 4.7V
|
||||
0x70, // 1.0uA
|
||||
10, // 10 ms delay
|
||||
ST7735_PWCTR2, 1, // 9: Power control, 1 arg, no delay:
|
||||
0x05, // VGH = 14.7V, VGL = -7.35V
|
||||
ST7735_PWCTR3, 2, // 10: Power control, 2 args, no delay:
|
||||
0x01, // Opamp current small
|
||||
0x02, // Boost frequency
|
||||
ST7735_VMCTR1, 2+ST_CMD_DELAY, // 11: Power control, 2 args + delay:
|
||||
0x3C, // VCOMH = 4V
|
||||
0x38, // VCOML = -1.1V
|
||||
10, // 10 ms delay
|
||||
ST7735_PWCTR6, 2, // 12: Power control, 2 args, no delay:
|
||||
0x11, 0x15,
|
||||
ST7735_GMCTRP1,16, // 13: Gamma Adjustments (pos. polarity), 16 args + delay:
|
||||
0x09, 0x16, 0x09, 0x20, // (Not entirely necessary, but provides
|
||||
0x21, 0x1B, 0x13, 0x19, // accurate colors)
|
||||
0x17, 0x15, 0x1E, 0x2B,
|
||||
0x04, 0x05, 0x02, 0x0E,
|
||||
ST7735_GMCTRN1,16+ST_CMD_DELAY, // 14: Gamma Adjustments (neg. polarity), 16 args + delay:
|
||||
0x0B, 0x14, 0x08, 0x1E, // (Not entirely necessary, but provides
|
||||
0x22, 0x1D, 0x18, 0x1E, // accurate colors)
|
||||
0x1B, 0x1A, 0x24, 0x2B,
|
||||
0x06, 0x06, 0x02, 0x0F,
|
||||
10, // 10 ms delay
|
||||
ST77XX_CASET, 4, // 15: Column addr set, 4 args, no delay:
|
||||
0x00, 0x02, // XSTART = 2
|
||||
0x00, 0x81, // XEND = 129
|
||||
ST77XX_RASET, 4, // 16: Row addr set, 4 args, no delay:
|
||||
0x00, 0x02, // XSTART = 1
|
||||
0x00, 0x81, // XEND = 160
|
||||
ST77XX_NORON, ST_CMD_DELAY, // 17: Normal display on, no args, w/delay
|
||||
10, // 10 ms delay
|
||||
ST77XX_DISPON, ST_CMD_DELAY, // 18: Main screen turn on, no args, delay
|
||||
255 }, // 255 = max (500 ms) delay
|
||||
|
||||
Rcmd1[] = { // 7735R init, part 1 (red or green tab)
|
||||
14, // 14 commands in list:
|
||||
/*ST77XX_SWRESET, ST_CMD_DELAY, // 1: Software reset, 0 args, w/delay
|
||||
150, */ // 150 ms delay
|
||||
ST77XX_SLPOUT, ST_CMD_DELAY, // 2: Out of sleep mode, 0 args, w/delay
|
||||
120, // 120 ms delay
|
||||
ST7735_FRMCTR1, 3, // 3: Framerate ctrl - normal mode, 3 arg:
|
||||
0x01, 0x2C, 0x2D, // Rate = fosc/(1x2+40) * (LINE+2C+2D)
|
||||
ST7735_FRMCTR2, 3, // 4: Framerate ctrl - idle mode, 3 args:
|
||||
0x01, 0x2C, 0x2D, // Rate = fosc/(1x2+40) * (LINE+2C+2D)
|
||||
ST7735_FRMCTR3, 6, // 5: Framerate - partial mode, 6 args:
|
||||
0x01, 0x2C, 0x2D, // Dot inversion mode
|
||||
0x01, 0x2C, 0x2D, // Line inversion mode
|
||||
ST7735_INVCTR, 1, // 6: Display inversion ctrl, 1 arg:
|
||||
0x07, // No inversion
|
||||
ST7735_PWCTR1, 3, // 7: Power control, 3 args, no delay:
|
||||
0xA2,
|
||||
0x02, // -4.6V
|
||||
0x84, // AUTO mode
|
||||
ST7735_PWCTR2, 1, // 8: Power control, 1 arg, no delay:
|
||||
0xC5, // VGH25=2.4C VGSEL=-10 VGH=3 * AVDD
|
||||
ST7735_PWCTR3, 2, // 9: Power control, 2 args, no delay:
|
||||
0x0A, // Opamp current small
|
||||
0x00, // Boost frequency
|
||||
ST7735_PWCTR4, 2, // 10: Power control, 2 args, no delay:
|
||||
0x8A, // BCLK/2,
|
||||
0x2A, // opamp current small & medium low
|
||||
ST7735_PWCTR5, 2, // 11: Power control, 2 args, no delay:
|
||||
0x8A, 0xEE,
|
||||
ST7735_VMCTR1, 1, // 12: Power control, 1 arg, no delay:
|
||||
0x0E,
|
||||
ST77XX_INVOFF, 0, // 13: Don't invert display, no args
|
||||
ST77XX_MADCTL, 1, // 14: Mem access ctl (directions), 1 arg:
|
||||
0xC8, // row/col addr, bottom-top refresh
|
||||
ST77XX_COLMOD, 1, // 15: set color mode, 1 arg, no delay:
|
||||
0x05 }, // 16-bit color
|
||||
|
||||
Rcmd2green[] = { // 7735R init, part 2 (green tab only)
|
||||
2, // 2 commands in list:
|
||||
ST77XX_CASET, 4, // 1: Column addr set, 4 args, no delay:
|
||||
0x00, 0x02, // XSTART = 0
|
||||
0x00, 0x7F+0x02, // XEND = 127
|
||||
ST77XX_RASET, 4, // 2: Row addr set, 4 args, no delay:
|
||||
0x00, 0x01, // XSTART = 0
|
||||
0x00, 0x9F+0x01 }, // XEND = 159
|
||||
|
||||
Rcmd2red[] = { // 7735R init, part 2 (red tab only)
|
||||
2, // 2 commands in list:
|
||||
ST77XX_CASET, 4, // 1: Column addr set, 4 args, no delay:
|
||||
0x00, 0x00, // XSTART = 0
|
||||
0x00, 0x7F, // XEND = 127
|
||||
ST77XX_RASET, 4, // 2: Row addr set, 4 args, no delay:
|
||||
0x00, 0x00, // XSTART = 0
|
||||
0x00, 0x9F }, // XEND = 159
|
||||
|
||||
Rcmd2green144[] = { // 7735R init, part 2 (green 1.44 tab)
|
||||
2, // 2 commands in list:
|
||||
ST77XX_CASET, 4, // 1: Column addr set, 4 args, no delay:
|
||||
0x00, 0x00, // XSTART = 0
|
||||
0x00, 0x7F, // XEND = 127
|
||||
ST77XX_RASET, 4, // 2: Row addr set, 4 args, no delay:
|
||||
0x00, 0x00, // XSTART = 0
|
||||
0x00, 0x7F }, // XEND = 127
|
||||
|
||||
Rcmd2green160x80[] = { // 7735R init, part 2 (mini 160x80)
|
||||
2, // 2 commands in list:
|
||||
ST77XX_CASET, 4, // 1: Column addr set, 4 args, no delay:
|
||||
0x00, 0x00, // XSTART = 0
|
||||
0x00, 0x4F, // XEND = 79
|
||||
ST77XX_RASET, 4, // 2: Row addr set, 4 args, no delay:
|
||||
0x00, 0x00, // XSTART = 0
|
||||
0x00, 0x9F }, // XEND = 159
|
||||
|
||||
Rcmd2green160x80plugin[] = { // 7735R init, part 2 (mini 160x80 with plugin FPC)
|
||||
3, // 3 commands in list:
|
||||
ST77XX_INVON, 0, // 1: Display is inverted
|
||||
ST77XX_CASET, 4, // 2: Column addr set, 4 args, no delay:
|
||||
0x00, 0x00, // XSTART = 0
|
||||
0x00, 0x4F, // XEND = 79
|
||||
ST77XX_RASET, 4, // 3: Row addr set, 4 args, no delay:
|
||||
0x00, 0x00, // XSTART = 0
|
||||
0x00, 0x9F }, // XEND = 159
|
||||
|
||||
Rcmd3[] = { // 7735R init, part 3 (red or green tab)
|
||||
2, // 2 commands in list:
|
||||
ST7735_GMCTRP1, 16 , // 1: Gamma Adjustments (pos. polarity), 16 args + delay:
|
||||
0x02, 0x1c, 0x07, 0x12, // (Not entirely necessary, but provides
|
||||
0x37, 0x32, 0x29, 0x2d, // accurate colors)
|
||||
0x29, 0x25, 0x2B, 0x39,
|
||||
0x00, 0x01, 0x03, 0x10,
|
||||
ST7735_GMCTRN1, 16 , // 2: Gamma Adjustments (neg. polarity), 16 args + delay:
|
||||
0x03, 0x1d, 0x07, 0x06, // (Not entirely necessary, but provides
|
||||
0x2E, 0x2C, 0x29, 0x2D, // accurate colors)
|
||||
0x2E, 0x2E, 0x37, 0x3F,
|
||||
0x00, 0x00, 0x02, 0x10 }; // 100 ms delay
|
||||
|
||||
static int16_t _xstart = 0; ///< Internal framebuffer X offset
|
||||
static int16_t _ystart = 0; ///< Internal framebuffer Y offset
|
||||
static uint8_t _colstart = 0; ///< Some displays need this changed to offset
|
||||
static uint8_t _rowstart = 0; ///< Some displays need this changed to offset
|
||||
static uint8_t rotation = 0;
|
||||
static int16_t _width = 0; ///< Display width as modified by current rotation
|
||||
static int16_t _height = 0; ///< Display height as modified by current rotation
|
||||
|
||||
static void set_CS(uint8_t level) {
|
||||
//if (_cs != (uint8_t) -1) {
|
||||
digitalWrite(PIN_TFT_CS, level);
|
||||
//}
|
||||
}
|
||||
static void sendCommand(uint8_t com) {
|
||||
set_CS(HIGH);
|
||||
digitalWrite(PIN_TFT_DC, LOW);
|
||||
set_CS(LOW);
|
||||
_spi->beginTransaction(_spiSettings);
|
||||
_spi->transfer(com);
|
||||
_spi->endTransaction();
|
||||
set_CS(HIGH);
|
||||
digitalWrite(PIN_TFT_DC, HIGH);
|
||||
}
|
||||
|
||||
static void WriteData(uint8_t data) {
|
||||
digitalWrite(PIN_TFT_CS, LOW);
|
||||
_spi->beginTransaction(_spiSettings);
|
||||
_spi->transfer(data);
|
||||
_spi->endTransaction();
|
||||
digitalWrite(PIN_TFT_CS, HIGH);
|
||||
}
|
||||
static void SPI_WRITE32(uint32_t l) {
|
||||
_spi->transfer(l >> 24);
|
||||
_spi->transfer(l >> 16);
|
||||
_spi->transfer(l >> 8);
|
||||
_spi->transfer(l);
|
||||
}
|
||||
static void writeCommand(uint8_t cmd) {
|
||||
digitalWrite(PIN_TFT_DC, LOW);
|
||||
_spi->transfer(cmd);
|
||||
digitalWrite(PIN_TFT_DC, HIGH);
|
||||
}
|
||||
|
||||
static void displayInit(const uint8_t *addr) {
|
||||
uint8_t numCommands, cmd, numArgs;
|
||||
uint16_t ms;
|
||||
|
||||
numCommands = pgm_read_byte(addr++); // Number of commands to follow
|
||||
while (numCommands--) { // For each command...
|
||||
cmd = pgm_read_byte(addr++); // Read command
|
||||
numArgs = pgm_read_byte(addr++); // Number of args to follow
|
||||
ms = numArgs & ST_CMD_DELAY; // If hibit set, delay follows args
|
||||
numArgs &= ~ST_CMD_DELAY; // Mask out delay bit
|
||||
sendCommand(cmd);
|
||||
for (int k = 0; k < numArgs; k++) {
|
||||
WriteData(addr[k]);
|
||||
}
|
||||
addr += numArgs;
|
||||
|
||||
if (ms) {
|
||||
ms = pgm_read_byte(addr++); // Read post-command delay time (ms)
|
||||
if (ms == 255)
|
||||
ms = 500; // If 255, delay for 500 ms
|
||||
delay(ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void setRotation(uint8_t m) {
|
||||
uint8_t madctl = 0;
|
||||
|
||||
rotation = m & 3; // can't be higher than 3
|
||||
|
||||
switch (rotation) {
|
||||
case 0:
|
||||
madctl = ST77XX_MADCTL_MX | ST77XX_MADCTL_MY | ST7735_MADCTL_BGR;
|
||||
|
||||
_height = 160;
|
||||
_width = 80;
|
||||
_xstart = _colstart;
|
||||
_ystart = _rowstart;
|
||||
break;
|
||||
case 1:
|
||||
madctl = ST77XX_MADCTL_MY | ST77XX_MADCTL_MV | ST7735_MADCTL_BGR;
|
||||
|
||||
_width = 160;
|
||||
_height = 80;
|
||||
_ystart = _colstart;
|
||||
_xstart = _rowstart;
|
||||
break;
|
||||
case 2:
|
||||
madctl = ST7735_MADCTL_BGR;
|
||||
|
||||
_height = 160;
|
||||
_width = 80;
|
||||
_xstart = _colstart;
|
||||
_ystart = _rowstart;
|
||||
break;
|
||||
case 3:
|
||||
madctl = ST77XX_MADCTL_MX | ST77XX_MADCTL_MV | ST7735_MADCTL_BGR;
|
||||
|
||||
_width = 160;
|
||||
_height = 80;
|
||||
_ystart = _colstart;
|
||||
_xstart = _rowstart;
|
||||
break;
|
||||
}
|
||||
|
||||
sendCommand(ST77XX_MADCTL);
|
||||
WriteData(madctl);
|
||||
}
|
||||
|
||||
static void setAddrWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
|
||||
x += _xstart;
|
||||
y += _ystart;
|
||||
uint32_t xa = ((uint32_t)x << 16) | (x + w - 1);
|
||||
uint32_t ya = ((uint32_t)y << 16) | (y + h - 1);
|
||||
|
||||
writeCommand(ST77XX_CASET); // Column addr set
|
||||
SPI_WRITE32(xa);
|
||||
|
||||
writeCommand(ST77XX_RASET); // Row addr set
|
||||
SPI_WRITE32(ya);
|
||||
|
||||
writeCommand(ST77XX_RAMWR); // write to RAM
|
||||
}
|
||||
|
||||
#define SCALE_X 1.25f // 160 / 128
|
||||
#define SCALE_Y 1.25f // 80 / 64
|
||||
|
||||
static TFT_eSprite *sprite = NULL;
|
||||
|
||||
bool ST7735Display::i2c_probe(TwoWire& wire, uint8_t addr) {
|
||||
return true;
|
||||
/*
|
||||
@@ -16,53 +409,103 @@ bool ST7735Display::i2c_probe(TwoWire& wire, uint8_t addr) {
|
||||
*/
|
||||
}
|
||||
|
||||
#ifndef PIN_TFT_LEDA_CTL_ACTIVE
|
||||
#define PIN_TFT_LEDA_CTL_ACTIVE HIGH
|
||||
#endif
|
||||
|
||||
bool ST7735Display::begin() {
|
||||
if (!sprite) {
|
||||
// alloc offscreen canvas
|
||||
sprite = new TFT_eSprite(&lcd);
|
||||
if (sprite) {
|
||||
if (sprite->createSprite(160, 80)) {
|
||||
sprite->fillScreen(ST77XX_BLACK);
|
||||
sprite->setTextColor(curr_color = ST77XX_WHITE);
|
||||
} else {
|
||||
Serial.printf("ST7735Display: failed to alloc canvas pixels");
|
||||
}
|
||||
} else {
|
||||
Serial.printf("ST7735Display: failed to alloc canvas");
|
||||
}
|
||||
}
|
||||
|
||||
if (!_isOn) {
|
||||
if (_peripher_power) _peripher_power->claim();
|
||||
|
||||
delay(100); // TEMP!!
|
||||
pinMode(PIN_TFT_RST, OUTPUT);
|
||||
pinMode(PIN_TFT_CS, OUTPUT);
|
||||
pinMode(PIN_TFT_DC, OUTPUT);
|
||||
pinMode(PIN_TFT_LEDA_CTL, OUTPUT);
|
||||
#if defined(PIN_TFT_LEDA_CTL_ACTIVE)
|
||||
digitalWrite(PIN_TFT_LEDA_CTL, PIN_TFT_LEDA_CTL_ACTIVE);
|
||||
#else
|
||||
digitalWrite(PIN_TFT_LEDA_CTL, HIGH);
|
||||
#endif
|
||||
digitalWrite(PIN_TFT_RST, HIGH);
|
||||
|
||||
#if defined(HELTEC_T1)
|
||||
display.initR(INITR_MINI160x80);
|
||||
display.setRotation(DISPLAY_ROTATION);
|
||||
#elif defined(HELTEC_TRACKER_V2) || defined(HELTEC_T096)
|
||||
display.initR(INITR_MINI160x80);
|
||||
display.setRotation(DISPLAY_ROTATION);
|
||||
uint8_t madctl = ST77XX_MADCTL_MY | ST77XX_MADCTL_MV |ST7735_MADCTL_BGR;//Adjust color to BGR
|
||||
display.sendCommand(ST77XX_MADCTL, &madctl, 1);
|
||||
#ifdef ESP_PLATFORM
|
||||
_spi->begin(PIN_TFT_SCL, -1 /* _miso */, PIN_TFT_SDA /* _mosi */, -1);
|
||||
#else
|
||||
display.initR(INITR_MINI160x80_PLUGIN);
|
||||
display.setRotation(DISPLAY_ROTATION);
|
||||
_spi->begin();
|
||||
#endif
|
||||
display.setSPISpeed(40000000);
|
||||
display.fillScreen(ST77XX_BLACK);
|
||||
display.setTextColor(ST77XX_WHITE);
|
||||
display.setTextSize(2);
|
||||
display.cp437(true); // Use full 256 char 'Code Page 437' font
|
||||
_spi->setClockDivider(SPI_CLOCK_DIV2);
|
||||
|
||||
_height = 80;
|
||||
_width = 160;
|
||||
_colstart = 24;
|
||||
_rowstart = 0;
|
||||
|
||||
_resetAndInit();
|
||||
|
||||
sendCommand(ST77XX_DISPON);
|
||||
|
||||
_isOn = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ST7735Display::_resetAndInit() {
|
||||
// Pulse Reset low for 10ms
|
||||
digitalWrite(PIN_TFT_RST, HIGH);
|
||||
delay(2);
|
||||
digitalWrite(PIN_TFT_RST, LOW);
|
||||
delay(10);
|
||||
digitalWrite(PIN_TFT_RST, HIGH);
|
||||
delay(2);
|
||||
|
||||
// run init commands
|
||||
displayInit(Rcmd1);
|
||||
#if defined(HELTEC_TRACKER_V2) || defined(HELTEC_T096)
|
||||
displayInit(Rcmd2green160x80);
|
||||
//uint8_t madctl = ST77XX_MADCTL_MY | ST77XX_MADCTL_MV |ST7735_MADCTL_BGR;//Adjust color to BGR
|
||||
//display.sendCommand(ST77XX_MADCTL, &madctl, 1);
|
||||
#endif
|
||||
displayInit(Rcmd3);
|
||||
setRotation(DISPLAY_ROTATION);
|
||||
|
||||
// clear the buffer before display on
|
||||
sprite->fillScreen(ST77XX_BLACK);
|
||||
endFrame();
|
||||
|
||||
// turn on backlight
|
||||
digitalWrite(PIN_TFT_LEDA_CTL, PIN_TFT_LEDA_CTL_ACTIVE);
|
||||
|
||||
}
|
||||
|
||||
void ST7735Display::turnOn() {
|
||||
ST7735Display::begin();
|
||||
if (!_isOn) {
|
||||
if (_peripher_power) _peripher_power->claim();
|
||||
_resetAndInit();
|
||||
sendCommand(ST77XX_DISPON);
|
||||
|
||||
// Now turn on the backlight
|
||||
// digitalWrite(PIN_TFT_LEDA_CTL, PIN_TFT_LEDA_CTL_ACTIVE);
|
||||
_isOn = true;
|
||||
}
|
||||
}
|
||||
|
||||
void ST7735Display::turnOff() {
|
||||
if (_isOn) {
|
||||
digitalWrite(PIN_TFT_RST, LOW);
|
||||
#if defined(PIN_TFT_LEDA_CTL_ACTIVE)
|
||||
sendCommand(ST77XX_DISPOFF);
|
||||
|
||||
//digitalWrite(PIN_TFT_RST, LOW);
|
||||
// Now turn off the backlight
|
||||
digitalWrite(PIN_TFT_LEDA_CTL, !PIN_TFT_LEDA_CTL_ACTIVE);
|
||||
#else
|
||||
digitalWrite(PIN_TFT_LEDA_CTL, LOW);
|
||||
#endif
|
||||
_isOn = false;
|
||||
|
||||
if (_peripher_power) _peripher_power->release();
|
||||
@@ -70,78 +513,90 @@ void ST7735Display::turnOff() {
|
||||
}
|
||||
|
||||
void ST7735Display::clear() {
|
||||
//Serial.println("DBG: display.Clear");
|
||||
display.fillScreen(ST77XX_BLACK);
|
||||
sprite->fillScreen(ST77XX_BLACK);
|
||||
}
|
||||
|
||||
void ST7735Display::startFrame(Color bkg) {
|
||||
display.fillScreen(0x00);
|
||||
display.setTextColor(ST77XX_WHITE);
|
||||
display.setTextSize(1); // This one affects size of Please wait... message
|
||||
display.cp437(true); // Use full 256 char 'Code Page 437' font
|
||||
sprite->fillScreen(ST77XX_BLACK);
|
||||
sprite->setTextColor(curr_color = ST77XX_WHITE);
|
||||
sprite->setFreeFont();
|
||||
sprite->setTextSize(1); // This one affects size of Please wait... message
|
||||
//sprite->cp437(true); // Use full 256 char 'Code Page 437' font
|
||||
}
|
||||
|
||||
void ST7735Display::setTextSize(int sz) {
|
||||
display.setTextSize(sz);
|
||||
sprite->setTextSize(sz);
|
||||
}
|
||||
|
||||
void ST7735Display::setColor(Color c) {
|
||||
switch (c) {
|
||||
case DisplayDriver::DARK :
|
||||
_color = ST77XX_BLACK;
|
||||
curr_color = ST77XX_BLACK;
|
||||
break;
|
||||
case DisplayDriver::LIGHT :
|
||||
_color = ST77XX_WHITE;
|
||||
curr_color = ST77XX_WHITE;
|
||||
break;
|
||||
case DisplayDriver::RED :
|
||||
_color = ST77XX_RED;
|
||||
curr_color = ST77XX_RED;
|
||||
break;
|
||||
case DisplayDriver::GREEN :
|
||||
_color = ST77XX_GREEN;
|
||||
curr_color = ST77XX_GREEN;
|
||||
break;
|
||||
case DisplayDriver::BLUE :
|
||||
_color = ST77XX_BLUE;
|
||||
curr_color = ST77XX_BLUE;
|
||||
break;
|
||||
case DisplayDriver::YELLOW :
|
||||
_color = ST77XX_YELLOW;
|
||||
curr_color = ST77XX_YELLOW;
|
||||
break;
|
||||
case DisplayDriver::ORANGE :
|
||||
_color = ST77XX_ORANGE;
|
||||
curr_color = ST77XX_ORANGE;
|
||||
break;
|
||||
default:
|
||||
_color = ST77XX_WHITE;
|
||||
curr_color = ST77XX_WHITE;
|
||||
break;
|
||||
}
|
||||
display.setTextColor(_color);
|
||||
sprite->setTextColor(curr_color);
|
||||
}
|
||||
|
||||
void ST7735Display::setCursor(int x, int y) {
|
||||
display.setCursor(x*SCALE_X, y*SCALE_Y);
|
||||
sprite->setCursor(x*SCALE_X, y*SCALE_Y);
|
||||
}
|
||||
|
||||
void ST7735Display::print(const char* str) {
|
||||
display.print(str);
|
||||
sprite->print(str);
|
||||
}
|
||||
|
||||
void ST7735Display::fillRect(int x, int y, int w, int h) {
|
||||
display.fillRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, _color);
|
||||
sprite->fillRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, curr_color);
|
||||
}
|
||||
|
||||
void ST7735Display::drawRect(int x, int y, int w, int h) {
|
||||
display.drawRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, _color);
|
||||
sprite->drawRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, curr_color);
|
||||
}
|
||||
|
||||
void ST7735Display::drawXbm(int x, int y, const uint8_t* bits, int w, int h) {
|
||||
display.drawBitmap(x*SCALE_X, y*SCALE_Y, bits, w, h, _color);
|
||||
sprite->drawBitmap(x*SCALE_X, y*SCALE_Y, bits, w, h, curr_color);
|
||||
}
|
||||
|
||||
uint16_t ST7735Display::getTextWidth(const char* str) {
|
||||
int16_t x1, y1;
|
||||
uint16_t w, h;
|
||||
display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h);
|
||||
return w / SCALE_X;
|
||||
return sprite->textWidth(str) / SCALE_X;
|
||||
}
|
||||
|
||||
void ST7735Display::endFrame() {
|
||||
// display.display();
|
||||
// blit the canvas buffer to LCD
|
||||
set_CS(LOW);
|
||||
_spi->beginTransaction(_spiSettings);
|
||||
uint16_t x, y;
|
||||
uint16_t* pixels = (uint16_t *) ((TFT_eSprite *) sprite)->getPointer();
|
||||
for (y = 0; y < 80; y++, pixels += 160) {
|
||||
setAddrWindow(0, y, 160, 1);
|
||||
#ifdef ESP_PLATFORM
|
||||
_spi->transferBytes((uint8_t *)pixels, NULL, 2 * 160);
|
||||
#else
|
||||
_spi->transfer(pixels, NULL, 2 * 160);
|
||||
#endif
|
||||
}
|
||||
|
||||
_spi->endTransaction();
|
||||
set_CS(HIGH);
|
||||
}
|
||||
|
||||
@@ -3,28 +3,25 @@
|
||||
#include "DisplayDriver.h"
|
||||
#include <Wire.h>
|
||||
#include <SPI.h>
|
||||
#include <Adafruit_GFX.h>
|
||||
#include <Adafruit_ST7735.h>
|
||||
#include "TFT_eSPI.h"
|
||||
#include <helpers/RefCountedDigitalPin.h>
|
||||
|
||||
class ST7735Display : public DisplayDriver {
|
||||
Adafruit_ST7735 display;
|
||||
bool _isOn;
|
||||
uint16_t _color;
|
||||
RefCountedDigitalPin* _peripher_power;
|
||||
|
||||
bool i2c_probe(TwoWire& wire, uint8_t addr);
|
||||
public:
|
||||
#ifdef USE_PIN_TFT
|
||||
ST7735Display(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64),
|
||||
display(PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_SDA, PIN_TFT_SCL, PIN_TFT_RST),
|
||||
// display(PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_SDA, PIN_TFT_SCL, PIN_TFT_RST),
|
||||
_peripher_power(peripher_power)
|
||||
{
|
||||
_isOn = false;
|
||||
}
|
||||
#else
|
||||
ST7735Display(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64),
|
||||
display(&SPI1, PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_RST),
|
||||
// display(&SPI1, PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_RST),
|
||||
_peripher_power(peripher_power)
|
||||
{
|
||||
_isOn = false;
|
||||
@@ -46,4 +43,7 @@ public:
|
||||
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;
|
||||
|
||||
protected:
|
||||
void _resetAndInit();
|
||||
};
|
||||
|
||||
@@ -18,6 +18,14 @@
|
||||
#define SCALE_Y 2.109375f // 135 / 64
|
||||
#endif
|
||||
|
||||
#ifdef DISPLAY_SCALE_X
|
||||
#define SCALE_X DISPLAY_SCALE_X
|
||||
#endif
|
||||
|
||||
#ifdef DISPLAY_SCALE_Y
|
||||
#define SCALE_Y DISPLAY_SCALE_Y
|
||||
#endif
|
||||
|
||||
bool ST7789Display::begin() {
|
||||
if(!_isOn) {
|
||||
pinMode(PIN_TFT_VDD_CTL, OUTPUT);
|
||||
@@ -32,6 +40,9 @@ bool ST7789Display::begin() {
|
||||
|
||||
display.init();
|
||||
display.landscapeScreen();
|
||||
#ifdef DISPLAY_FLIP_VERTICALLY
|
||||
display.flipScreenVertically();
|
||||
#endif
|
||||
display.displayOn();
|
||||
setCursor(0,0);
|
||||
|
||||
@@ -49,6 +60,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
|
||||
|
||||
@@ -14,8 +14,10 @@ class ST7789Display : public DisplayDriver {
|
||||
|
||||
bool i2c_probe(TwoWire& wire, uint8_t addr);
|
||||
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(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
|
||||
|
||||
+25
-4
@@ -3,12 +3,33 @@
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// Mock SHA256 class for testing
|
||||
// Provides minimal interface to allow Utils.cpp to compile
|
||||
// Mock SHA256 for native testing — deterministic but not cryptographic.
|
||||
// finalize() writes real (non-garbage) output so calculatePacketHash() produces
|
||||
// distinguishable results for packets with different payloads.
|
||||
#include <string.h>
|
||||
|
||||
class SHA256 {
|
||||
uint8_t _state[32];
|
||||
size_t _len;
|
||||
public:
|
||||
void update(const uint8_t* data, size_t len) {}
|
||||
void finalize(uint8_t* hash, size_t hashLen) {}
|
||||
SHA256() : _len(0) { memset(_state, 0, sizeof(_state)); }
|
||||
|
||||
void update(const void* data, size_t len) {
|
||||
const uint8_t* bytes = static_cast<const uint8_t*>(data);
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
uint8_t b = bytes[i];
|
||||
_state[_len % 32] ^= b;
|
||||
_state[(_len + 1) % 32] += (uint8_t)((b >> 1) | (b << 7));
|
||||
_len++;
|
||||
}
|
||||
}
|
||||
|
||||
void finalize(uint8_t* hash, size_t hashLen) {
|
||||
for (size_t i = 0; i < hashLen; i++) {
|
||||
hash[i] = _state[i % 32];
|
||||
}
|
||||
}
|
||||
|
||||
void resetHMAC(const uint8_t* key, size_t keyLen) {}
|
||||
void finalizeHMAC(const uint8_t* key, size_t keyLen, uint8_t* hash, size_t hashLen) {}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "helpers/SimpleMeshTables.h"
|
||||
|
||||
using namespace mesh;
|
||||
|
||||
// Build a packet that calculatePacketHash() distinguishes by payload content.
|
||||
// header selects ROUTE_TYPE_FLOOD so isRouteDirect() returns false.
|
||||
static Packet makeFloodPacket(uint8_t seed) {
|
||||
Packet p;
|
||||
p.header = ROUTE_TYPE_FLOOD | (PAYLOAD_TYPE_ACK << PH_TYPE_SHIFT);
|
||||
p.payload[0] = seed;
|
||||
p.payload_len = 1;
|
||||
p.path_len = 0;
|
||||
return p;
|
||||
}
|
||||
|
||||
static Packet makeDirectPacket(uint8_t seed) {
|
||||
Packet p;
|
||||
p.header = ROUTE_TYPE_DIRECT | (PAYLOAD_TYPE_ACK << PH_TYPE_SHIFT);
|
||||
p.payload[0] = seed;
|
||||
p.payload_len = 1;
|
||||
p.path_len = 0;
|
||||
return p;
|
||||
}
|
||||
|
||||
// ── wasSeen: pure query ───────────────────────────────────────────────────────
|
||||
|
||||
TEST(SimpleMeshTables, WasSeen_ReturnsFalseForUnseen) {
|
||||
SimpleMeshTables t;
|
||||
Packet p = makeFloodPacket(0x01);
|
||||
EXPECT_FALSE(t.wasSeen(&p));
|
||||
}
|
||||
|
||||
// wasSeen shouldn't change state
|
||||
TEST(SimpleMeshTables, WasSeen_IsPureQuery_DoesNotInsert) {
|
||||
SimpleMeshTables t;
|
||||
Packet p = makeFloodPacket(0x01);
|
||||
EXPECT_FALSE(t.wasSeen(&p));
|
||||
EXPECT_FALSE(t.wasSeen(&p));
|
||||
}
|
||||
|
||||
// ── markSeen + wasSeen ───────────────────────────────────────────────────────
|
||||
|
||||
TEST(SimpleMeshTables, MarkSeen_MakesWasSeenReturnTrue) {
|
||||
SimpleMeshTables t;
|
||||
Packet p = makeFloodPacket(0x01);
|
||||
t.markSeen(&p);
|
||||
EXPECT_TRUE(t.wasSeen(&p));
|
||||
}
|
||||
|
||||
TEST(SimpleMeshTables, MarkSeen_DoesNotAffectOtherPackets) {
|
||||
SimpleMeshTables t;
|
||||
Packet p1 = makeFloodPacket(0x01);
|
||||
Packet p2 = makeFloodPacket(0x02);
|
||||
t.markSeen(&p1);
|
||||
EXPECT_FALSE(t.wasSeen(&p2));
|
||||
}
|
||||
|
||||
// Canonical pattern used at every onRecvPacket call site:
|
||||
// if (!wasSeen(pkt)) { markSeen(pkt); process(pkt); }
|
||||
TEST(SimpleMeshTables, QueryThenMark_WorksCorrectly) {
|
||||
SimpleMeshTables t;
|
||||
Packet p = makeFloodPacket(0x01);
|
||||
EXPECT_FALSE(t.wasSeen(&p));
|
||||
t.markSeen(&p);
|
||||
EXPECT_TRUE(t.wasSeen(&p));
|
||||
}
|
||||
|
||||
// ── dup stats ────────────────────────────────────────────────────────────────
|
||||
|
||||
TEST(SimpleMeshTables, WasSeen_IncrementsFloodDupStat) {
|
||||
SimpleMeshTables t;
|
||||
Packet p = makeFloodPacket(0x01);
|
||||
t.markSeen(&p);
|
||||
t.wasSeen(&p);
|
||||
EXPECT_EQ(1u, t.getNumFloodDups());
|
||||
EXPECT_EQ(0u, t.getNumDirectDups());
|
||||
}
|
||||
|
||||
TEST(SimpleMeshTables, WasSeen_IncrementsDirectDupStat) {
|
||||
SimpleMeshTables t;
|
||||
Packet p = makeDirectPacket(0x01);
|
||||
t.markSeen(&p);
|
||||
t.wasSeen(&p);
|
||||
EXPECT_EQ(0u, t.getNumFloodDups());
|
||||
EXPECT_EQ(1u, t.getNumDirectDups());
|
||||
}
|
||||
|
||||
// ── clear ────────────────────────────────────────────────────────────────────
|
||||
|
||||
TEST(SimpleMeshTables, Clear_RemovesSeenPacket) {
|
||||
SimpleMeshTables t;
|
||||
Packet p = makeFloodPacket(0x01);
|
||||
t.markSeen(&p);
|
||||
ASSERT_TRUE(t.wasSeen(&p));
|
||||
t.clear(&p);
|
||||
EXPECT_FALSE(t.wasSeen(&p));
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -47,7 +47,7 @@ public:
|
||||
uint32_t button_pin = PIN_BUTTON1;
|
||||
nrf_gpio_cfg_input(button_pin, NRF_GPIO_PIN_PULLUP);
|
||||
nrf_gpio_cfg_sense_set(button_pin, NRF_GPIO_PIN_SENSE_LOW);
|
||||
sd_power_system_off();
|
||||
NRF52Board::powerOff();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -47,7 +47,7 @@ public:
|
||||
uint32_t button_pin = PIN_BUTTON1;
|
||||
nrf_gpio_cfg_input(button_pin, NRF_GPIO_PIN_PULLUP);
|
||||
nrf_gpio_cfg_sense_set(button_pin, NRF_GPIO_PIN_SENSE_LOW);
|
||||
sd_power_system_off();
|
||||
NRF52Board::powerOff();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -47,7 +47,7 @@ public:
|
||||
uint32_t button_pin = PIN_BUTTON1;
|
||||
nrf_gpio_cfg_input(button_pin, NRF_GPIO_PIN_PULLUP);
|
||||
nrf_gpio_cfg_sense_set(button_pin, NRF_GPIO_PIN_SENSE_LOW);
|
||||
sd_power_system_off();
|
||||
NRF52Board::powerOff();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -38,7 +38,7 @@ public:
|
||||
uint32_t button_pin = PIN_BUTTON1;
|
||||
nrf_gpio_cfg_input(button_pin, NRF_GPIO_PIN_PULLUP);
|
||||
nrf_gpio_cfg_sense_set(button_pin, NRF_GPIO_PIN_SENSE_LOW);
|
||||
sd_power_system_off();
|
||||
NRF52Board::powerOff();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -20,33 +20,6 @@ void HeltecE213Board::begin() {
|
||||
}
|
||||
}
|
||||
|
||||
void HeltecE213Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) {
|
||||
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
|
||||
|
||||
// Make sure the DIO1 and NSS GPIOs are hold 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 HeltecE213Board::powerOff() {
|
||||
enterDeepSleep(0);
|
||||
}
|
||||
|
||||
uint16_t HeltecE213Board::getBattMilliVolts() {
|
||||
analogReadResolution(10);
|
||||
digitalWrite(PIN_ADC_CTRL, HIGH);
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <Arduino.h>
|
||||
#include <helpers/RefCountedDigitalPin.h>
|
||||
#include <helpers/ESP32Board.h>
|
||||
#include <driver/rtc_io.h>
|
||||
|
||||
class HeltecE213Board : public ESP32Board {
|
||||
|
||||
@@ -13,8 +12,6 @@ public:
|
||||
HeltecE213Board() : periph_power(PIN_VEXT_EN,PIN_VEXT_EN_ACTIVE) { }
|
||||
|
||||
void begin();
|
||||
void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1);
|
||||
void powerOff() override;
|
||||
uint16_t getBattMilliVolts() override;
|
||||
const char* getManufacturerName() const override ;
|
||||
};
|
||||
|
||||
@@ -20,33 +20,6 @@ void HeltecE290Board::begin() {
|
||||
}
|
||||
}
|
||||
|
||||
void HeltecE290Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) {
|
||||
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
|
||||
|
||||
// Make sure the DIO1 and NSS GPIOs are hold 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 HeltecE290Board::powerOff() {
|
||||
enterDeepSleep(0);
|
||||
}
|
||||
|
||||
uint16_t HeltecE290Board::getBattMilliVolts() {
|
||||
analogReadResolution(10);
|
||||
digitalWrite(PIN_ADC_CTRL, HIGH);
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <Arduino.h>
|
||||
#include <helpers/RefCountedDigitalPin.h>
|
||||
#include <helpers/ESP32Board.h>
|
||||
#include <driver/rtc_io.h>
|
||||
|
||||
class HeltecE290Board : public ESP32Board {
|
||||
|
||||
@@ -13,8 +12,6 @@ public:
|
||||
HeltecE290Board() : periph_power(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE) { }
|
||||
|
||||
void begin();
|
||||
void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1);
|
||||
void powerOff() override;
|
||||
uint16_t getBattMilliVolts() override;
|
||||
const char* getManufacturerName() const override ;
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
[Heltec_mesh_solar]
|
||||
extends = nrf52_base
|
||||
board = heltec_mesh_solar
|
||||
platform_packages = framework-arduinoadafruitnrf52
|
||||
board_build.ldscript = boards/nrf52840_s140_v6.ld
|
||||
build_flags = ${nrf52_base.build_flags}
|
||||
-I src/helpers/nrf52
|
||||
@@ -14,6 +13,11 @@ build_flags = ${nrf52_base.build_flags}
|
||||
-D LORA_TX_POWER=22
|
||||
-D SX126X_CURRENT_LIMIT=140
|
||||
-D SX126X_RX_BOOSTED_GAIN=1
|
||||
-D HAS_EXTERNAL_WATCHDOG
|
||||
-D EXTERNAL_WATCHDOG_DONE_PIN=9
|
||||
-D EXTERNAL_WATCHDOG_WAKE_PIN=10
|
||||
-D EXTERNAL_WATCHDOG_FEED_INTERVAL_MS=480000 ; 8 minute feed interval, safely inside the hardware watchdog timeout
|
||||
|
||||
build_src_filter = ${nrf52_base.build_src_filter}
|
||||
+<helpers/*.cpp>
|
||||
+<../variants/heltec_mesh_solar>
|
||||
|
||||
@@ -13,6 +13,7 @@ VolatileRTCClock fallback_clock;
|
||||
AutoDiscoverRTCClock rtc_clock(fallback_clock);
|
||||
MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock);
|
||||
SolarSensorManager sensors = SolarSensorManager(nmea);
|
||||
SolarExternalWatchdog external_watchdog;
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
DISPLAY_CLASS display;
|
||||
@@ -106,3 +107,34 @@ bool SolarSensorManager::setSettingValue(const char* name, const char* value) {
|
||||
}
|
||||
return false; // not supported
|
||||
}
|
||||
|
||||
bool SolarExternalWatchdog::begin() {
|
||||
last_feed_watchdog = 0;
|
||||
pinMode(EXTERNAL_WATCHDOG_WAKE_PIN, INPUT);
|
||||
pinMode(EXTERNAL_WATCHDOG_DONE_PIN, OUTPUT);
|
||||
delay(1);
|
||||
digitalWrite(EXTERNAL_WATCHDOG_DONE_PIN, LOW);
|
||||
delay(1);
|
||||
feed();
|
||||
return true;
|
||||
}
|
||||
void SolarExternalWatchdog::loop() {
|
||||
if (millis() - last_feed_watchdog >= EXTERNAL_WATCHDOG_FEED_INTERVAL_MS) {
|
||||
feed();
|
||||
}
|
||||
}
|
||||
|
||||
unsigned long SolarExternalWatchdog::getIntervalMs() const {
|
||||
unsigned long elapsed_ms = millis() - last_feed_watchdog;
|
||||
if (elapsed_ms >= EXTERNAL_WATCHDOG_FEED_INTERVAL_MS) {
|
||||
return 0;
|
||||
}
|
||||
return EXTERNAL_WATCHDOG_FEED_INTERVAL_MS - elapsed_ms;
|
||||
}
|
||||
|
||||
void SolarExternalWatchdog::feed() {
|
||||
digitalWrite(EXTERNAL_WATCHDOG_DONE_PIN, HIGH);
|
||||
delay(1);
|
||||
digitalWrite(EXTERNAL_WATCHDOG_DONE_PIN, LOW);
|
||||
last_feed_watchdog = millis();
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
#include <helpers/SensorManager.h>
|
||||
#include <helpers/sensors/LocationProvider.h>
|
||||
#include <helpers/ExternalWatchdogManager.h>
|
||||
#ifdef DISPLAY_CLASS
|
||||
#include <helpers/ui/ST7789Display.h>
|
||||
#endif
|
||||
@@ -30,10 +31,20 @@ public:
|
||||
bool setSettingValue(const char* name, const char* value) override;
|
||||
};
|
||||
|
||||
class SolarExternalWatchdog : public ExternalWatchdogManager {
|
||||
public:
|
||||
SolarExternalWatchdog() {}
|
||||
bool begin() override;
|
||||
void loop() override;
|
||||
unsigned long getIntervalMs() const override;
|
||||
void feed() override;
|
||||
};
|
||||
|
||||
extern MeshSolarBoard board;
|
||||
extern WRAPPER_CLASS radio_driver;
|
||||
extern AutoDiscoverRTCClock rtc_clock;
|
||||
extern SolarSensorManager sensors;
|
||||
extern SolarExternalWatchdog external_watchdog;
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
extern DISPLAY_CLASS display;
|
||||
|
||||
@@ -34,8 +34,8 @@
|
||||
#define PIN_SERIAL1_RX (37)
|
||||
#define PIN_SERIAL1_TX (39)
|
||||
|
||||
#define PIN_SERIAL2_RX (9)
|
||||
#define PIN_SERIAL2_TX (10)
|
||||
#define PIN_SERIAL2_RX (-1)
|
||||
#define PIN_SERIAL2_TX (-1)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// I2C pin definition
|
||||
|
||||
@@ -12,8 +12,9 @@ class LoRaFEMControl
|
||||
void setRxModeEnable(void);
|
||||
void setRxModeEnableWhenMCUSleep(void);
|
||||
void setLNAEnable(bool enabled);
|
||||
bool isLnaCanControl(void) { return lna_can_control; }
|
||||
bool isLnaCanControl(void) const { return lna_can_control; }
|
||||
void setLnaCanControl(bool can_control) { lna_can_control = can_control; }
|
||||
bool isLNAEnabled(void) const { return lna_enabled; }
|
||||
|
||||
private:
|
||||
bool lna_enabled = false;
|
||||
|
||||
@@ -112,15 +112,29 @@ void T096Board::variant_shutdown() {
|
||||
}
|
||||
|
||||
void T096Board::powerOff() {
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
pinMode(PIN_GPS_EN, OUTPUT);
|
||||
digitalWrite(PIN_GPS_EN, !PIN_GPS_EN_ACTIVE);
|
||||
#endif
|
||||
loRaFEMControl.setSleepModeEnable();
|
||||
variant_shutdown();
|
||||
sd_power_system_off();
|
||||
loRaFEMControl.setSleepModeEnable();
|
||||
nrf_gpio_cfg_default(PIN_GPS_EN); // 363uA down to 39uA
|
||||
NRF52Board::powerOff();
|
||||
}
|
||||
|
||||
const char* T096Board::getManufacturerName() const {
|
||||
return "Heltec T096";
|
||||
}
|
||||
}
|
||||
|
||||
bool T096Board::setLoRaFemLnaEnabled(bool enable) {
|
||||
if (!loRaFEMControl.isLnaCanControl()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
loRaFEMControl.setLNAEnable(enable);
|
||||
loRaFEMControl.setRxModeEnable();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool T096Board::canControlLoRaFemLna() const {
|
||||
return loRaFEMControl.isLnaCanControl();
|
||||
}
|
||||
|
||||
bool T096Board::isLoRaFemLnaEnabled() const {
|
||||
return loRaFEMControl.isLNAEnabled();
|
||||
}
|
||||
|
||||
@@ -25,4 +25,7 @@ public:
|
||||
uint16_t getBattMilliVolts() override;
|
||||
const char* getManufacturerName() const override ;
|
||||
void powerOff() override;
|
||||
bool setLoRaFemLnaEnabled(bool enable) override;
|
||||
bool canControlLoRaFemLna() const override;
|
||||
bool isLoRaFemLnaEnabled() const override;
|
||||
};
|
||||
|
||||
@@ -51,7 +51,7 @@ build_src_filter = ${nrf52_base.build_src_filter}
|
||||
lib_deps =
|
||||
${nrf52_base.lib_deps}
|
||||
${sensor_base.lib_deps}
|
||||
adafruit/Adafruit ST7735 and ST7789 Library @ ^1.11.0
|
||||
bodmer/TFT_eSPI @ ^2.4.31
|
||||
debug_tool = jlink
|
||||
upload_protocol = nrfutil
|
||||
|
||||
|
||||
@@ -81,34 +81,6 @@ uint16_t T1Board::getBattMilliVolts() {
|
||||
}
|
||||
|
||||
void T1Board::variant_shutdown() {
|
||||
nrf_gpio_cfg_default(PIN_TFT_CS);
|
||||
nrf_gpio_cfg_default(PIN_TFT_DC);
|
||||
nrf_gpio_cfg_default(PIN_TFT_SDA);
|
||||
nrf_gpio_cfg_default(PIN_TFT_SCL);
|
||||
nrf_gpio_cfg_default(PIN_TFT_RST);
|
||||
nrf_gpio_cfg_default(PIN_TFT_LEDA_CTL);
|
||||
nrf_gpio_cfg_default(PIN_TFT_VDD_CTL);
|
||||
|
||||
nrf_gpio_cfg_default(PIN_WIRE_SDA);
|
||||
nrf_gpio_cfg_default(PIN_WIRE_SCL);
|
||||
|
||||
nrf_gpio_cfg_default(LORA_CS);
|
||||
nrf_gpio_cfg_default(SX126X_DIO1);
|
||||
nrf_gpio_cfg_default(SX126X_BUSY);
|
||||
nrf_gpio_cfg_default(SX126X_RESET);
|
||||
nrf_gpio_cfg_default(PIN_SPI_MISO);
|
||||
nrf_gpio_cfg_default(PIN_SPI_MOSI);
|
||||
nrf_gpio_cfg_default(PIN_SPI_SCK);
|
||||
|
||||
nrf_gpio_cfg_default(PIN_SPI1_MOSI);
|
||||
nrf_gpio_cfg_default(PIN_SPI1_SCK);
|
||||
|
||||
nrf_gpio_cfg_default(PIN_GPS_RESET);
|
||||
nrf_gpio_cfg_default(PIN_GPS_EN);
|
||||
nrf_gpio_cfg_default(PIN_GPS_PPS);
|
||||
nrf_gpio_cfg_default(PIN_GPS_RX);
|
||||
nrf_gpio_cfg_default(PIN_GPS_TX);
|
||||
|
||||
nrf_gpio_cfg_default(PIN_BUZZER_VOLTAGE_MULTIPLIER_1);
|
||||
nrf_gpio_cfg_default(PIN_BUZZER_VOLTAGE_MULTIPLIER_2);
|
||||
|
||||
@@ -127,7 +99,7 @@ void T1Board::variant_shutdown() {
|
||||
|
||||
void T1Board::powerOff() {
|
||||
variant_shutdown();
|
||||
sd_power_system_off();
|
||||
NRF52Board::powerOff();
|
||||
}
|
||||
|
||||
const char* T1Board::getManufacturerName() const {
|
||||
|
||||
@@ -33,7 +33,7 @@ void T114Board::initiateShutdown(uint8_t reason) {
|
||||
#endif // NRF52_POWER_MANAGEMENT
|
||||
|
||||
void T114Board::begin() {
|
||||
NRF52Board::begin();
|
||||
NRF52BoardDCDC::begin();
|
||||
|
||||
pinMode(PIN_VBAT_READ, INPUT);
|
||||
|
||||
|
||||
@@ -50,10 +50,7 @@ public:
|
||||
#ifdef LED_PIN
|
||||
digitalWrite(LED_PIN, HIGH);
|
||||
#endif
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
pinMode(GPS_EN, OUTPUT);
|
||||
digitalWrite(GPS_EN, LOW);
|
||||
#endif
|
||||
sd_power_system_off();
|
||||
|
||||
NRF52Board::powerOff();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock);
|
||||
|
||||
#if ENV_INCLUDE_GPS
|
||||
#include <helpers/sensors/MicroNMEALocationProvider.h>
|
||||
MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1);
|
||||
MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock);
|
||||
EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea);
|
||||
#else
|
||||
EnvironmentSensorManager sensors;
|
||||
|
||||
@@ -20,33 +20,6 @@ void HeltecT190Board::begin() {
|
||||
}
|
||||
}
|
||||
|
||||
void HeltecT190Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) {
|
||||
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
|
||||
|
||||
// Make sure the DIO1 and NSS GPIOs are hold 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 HeltecT190Board::powerOff() {
|
||||
enterDeepSleep(0);
|
||||
}
|
||||
|
||||
uint16_t HeltecT190Board::getBattMilliVolts() {
|
||||
analogReadResolution(10);
|
||||
digitalWrite(PIN_ADC_CTRL, HIGH);
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <Arduino.h>
|
||||
#include <helpers/RefCountedDigitalPin.h>
|
||||
#include <helpers/ESP32Board.h>
|
||||
#include <driver/rtc_io.h>
|
||||
|
||||
class HeltecT190Board : public ESP32Board {
|
||||
|
||||
@@ -13,8 +12,6 @@ public:
|
||||
HeltecT190Board() : periph_power(PIN_VEXT_EN,PIN_VEXT_EN_ACTIVE) { }
|
||||
|
||||
void begin();
|
||||
void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1);
|
||||
void powerOff() override;
|
||||
uint16_t getBattMilliVolts() override;
|
||||
const char* getManufacturerName() const override ;
|
||||
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
#include "HeltecTowerV2Board.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
|
||||
extern void variant_shutdown();
|
||||
|
||||
#ifdef NRF52_POWER_MANAGEMENT
|
||||
const PowerMgtConfig power_config = {
|
||||
.lpcomp_ain_channel = PWRMGT_LPCOMP_AIN,
|
||||
.lpcomp_refsel = PWRMGT_LPCOMP_REFSEL,
|
||||
.voltage_bootlock = PWRMGT_VOLTAGE_BOOTLOCK
|
||||
};
|
||||
|
||||
void HeltecTowerV2Board::initiateShutdown(uint8_t reason) {
|
||||
pinMode(PIN_GPS_EN, OUTPUT);
|
||||
digitalWrite(PIN_GPS_EN, !PIN_GPS_EN_ACTIVE);
|
||||
pinMode(PIN_GPS_STANDBY, OUTPUT);
|
||||
digitalWrite(PIN_GPS_STANDBY, LOW);
|
||||
pinMode(PIN_GPS_RESET, OUTPUT);
|
||||
digitalWrite(PIN_GPS_RESET, GPS_RESET_MODE);
|
||||
loRaFEMControl.setSleepModeEnable();
|
||||
|
||||
bool enable_lpcomp = (reason == SHUTDOWN_REASON_LOW_VOLTAGE ||
|
||||
reason == SHUTDOWN_REASON_BOOT_PROTECT);
|
||||
pinMode(PIN_BAT_CTL, OUTPUT);
|
||||
digitalWrite(PIN_BAT_CTL, enable_lpcomp ? HIGH : LOW);
|
||||
|
||||
if (enable_lpcomp) {
|
||||
configureVoltageWake(power_config.lpcomp_ain_channel, power_config.lpcomp_refsel);
|
||||
}
|
||||
|
||||
variant_shutdown();
|
||||
enterSystemOff(reason);
|
||||
}
|
||||
#endif
|
||||
|
||||
void HeltecTowerV2Board::begin() {
|
||||
NRF52Board::begin();
|
||||
|
||||
pinMode(P_LORA_TX_LED, OUTPUT);
|
||||
digitalWrite(P_LORA_TX_LED, !LED_STATE_ON);
|
||||
|
||||
pinMode(PIN_BAT_CTL, OUTPUT);
|
||||
digitalWrite(PIN_BAT_CTL, LOW);
|
||||
|
||||
#ifdef NRF52_POWER_MANAGEMENT
|
||||
checkBootVoltage(&power_config);
|
||||
#endif
|
||||
|
||||
Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL);
|
||||
Wire.begin();
|
||||
|
||||
pinMode(PIN_GPS_EN, OUTPUT);
|
||||
digitalWrite(PIN_GPS_EN, !PIN_GPS_EN_ACTIVE);
|
||||
pinMode(PIN_GPS_RESET, OUTPUT);
|
||||
digitalWrite(PIN_GPS_RESET, GPS_RESET_MODE);
|
||||
pinMode(PIN_GPS_STANDBY, OUTPUT);
|
||||
digitalWrite(PIN_GPS_STANDBY, HIGH);
|
||||
loRaFEMControl.init();
|
||||
}
|
||||
|
||||
void HeltecTowerV2Board::onBeforeTransmit() {
|
||||
digitalWrite(P_LORA_TX_LED, LED_STATE_ON);
|
||||
loRaFEMControl.setTxModeEnable();
|
||||
}
|
||||
|
||||
void HeltecTowerV2Board::onAfterTransmit() {
|
||||
digitalWrite(P_LORA_TX_LED, !LED_STATE_ON);
|
||||
loRaFEMControl.setRxModeEnable();
|
||||
}
|
||||
|
||||
uint16_t HeltecTowerV2Board::getBattMilliVolts() {
|
||||
analogReadResolution(12);
|
||||
analogReference(VBAT_AR_INTERNAL);
|
||||
pinMode(PIN_VBAT_READ, INPUT);
|
||||
pinMode(PIN_BAT_CTL, OUTPUT);
|
||||
digitalWrite(PIN_BAT_CTL, HIGH);
|
||||
|
||||
delay(10);
|
||||
int adcvalue = analogRead(PIN_VBAT_READ);
|
||||
digitalWrite(PIN_BAT_CTL, LOW);
|
||||
|
||||
return (uint16_t)((float)adcvalue * MV_LSB * ADC_MULTIPLIER);
|
||||
}
|
||||
|
||||
const char* HeltecTowerV2Board::getManufacturerName() const {
|
||||
return "Heltec Tower V2";
|
||||
}
|
||||
|
||||
void HeltecTowerV2Board::powerOff() {
|
||||
pinMode(PIN_GPS_EN, OUTPUT);
|
||||
digitalWrite(PIN_GPS_EN, !PIN_GPS_EN_ACTIVE);
|
||||
pinMode(PIN_GPS_STANDBY, OUTPUT);
|
||||
digitalWrite(PIN_GPS_STANDBY, LOW);
|
||||
pinMode(PIN_GPS_RESET, OUTPUT);
|
||||
digitalWrite(PIN_GPS_RESET, GPS_RESET_MODE);
|
||||
loRaFEMControl.setSleepModeEnable();
|
||||
pinMode(PIN_BAT_CTL, OUTPUT);
|
||||
digitalWrite(PIN_BAT_CTL, LOW);
|
||||
variant_shutdown();
|
||||
sd_power_system_off();
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <MeshCore.h>
|
||||
#include <helpers/NRF52Board.h>
|
||||
#include "LoRaFEMControl.h"
|
||||
|
||||
class HeltecTowerV2Board : public NRF52BoardDCDC {
|
||||
protected:
|
||||
#ifdef NRF52_POWER_MANAGEMENT
|
||||
void initiateShutdown(uint8_t reason) override;
|
||||
#endif
|
||||
|
||||
public:
|
||||
LoRaFEMControl loRaFEMControl;
|
||||
|
||||
HeltecTowerV2Board() : NRF52Board("TOWER_V2_OTA") {}
|
||||
void begin();
|
||||
void onBeforeTransmit() override;
|
||||
void onAfterTransmit() override;
|
||||
uint16_t getBattMilliVolts() override;
|
||||
const char* getManufacturerName() const override;
|
||||
void powerOff() override;
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
#include "LoRaFEMControl.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "variant.h"
|
||||
|
||||
static void enableFEMPower() {
|
||||
bool wasOff = digitalRead(LORA_KCT8103L_EN) != HIGH;
|
||||
digitalWrite(LORA_KCT8103L_EN, HIGH);
|
||||
if (wasOff) {
|
||||
delay(5);
|
||||
}
|
||||
}
|
||||
|
||||
void LoRaFEMControl::init() {
|
||||
pinMode(LORA_KCT8103L_EN, OUTPUT);
|
||||
digitalWrite(LORA_KCT8103L_EN, HIGH);
|
||||
delay(1);
|
||||
pinMode(LORA_KCT8103L_TX_RX, OUTPUT);
|
||||
digitalWrite(LORA_KCT8103L_TX_RX, LOW);
|
||||
}
|
||||
|
||||
void LoRaFEMControl::setSleepModeEnable() {
|
||||
pinMode(LORA_KCT8103L_EN, OUTPUT);
|
||||
digitalWrite(LORA_KCT8103L_EN, LOW);
|
||||
}
|
||||
|
||||
void LoRaFEMControl::setTxModeEnable() {
|
||||
enableFEMPower();
|
||||
digitalWrite(LORA_KCT8103L_TX_RX, HIGH);
|
||||
}
|
||||
|
||||
void LoRaFEMControl::setRxModeEnable() {
|
||||
enableFEMPower();
|
||||
digitalWrite(LORA_KCT8103L_TX_RX, LOW);
|
||||
}
|
||||
|
||||
void LoRaFEMControl::setRxModeEnableWhenMCUSleep() {
|
||||
enableFEMPower();
|
||||
digitalWrite(LORA_KCT8103L_TX_RX, LOW);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user