mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-10-23 14:32:22 +00:00
Merge branch 'observer-firmware-dev' into fix/mqtt-review-2026-09-09
This commit is contained in:
commit
800a27fe83
66 files changed
+1874
-179
No files matched your search
@@ -128,6 +128,6 @@ There are a number of fairly major features in the pipeline, with no particular
|
||||
|
||||
## 📞 Get Support
|
||||
|
||||
- Report bugs and request features on the [GitHub Issues](https://github.com/ripplebiz/MeshCore/issues) page.
|
||||
- Report bugs and request features on the [GitHub Issues](https://github.com/meshcore-dev/meshcore/issues) page.
|
||||
- Find additional guides and components on [my site](https://buymeacoffee.com/ripplebiz).
|
||||
- Join [MeshCore Discord](https://meshcore.gg) to chat with the developers and get help from the community.
|
||||
@@ -20,10 +20,12 @@ for item in menv.get("CPPDEFINES", []):
|
||||
src_filter.append("+<helpers/stm32/*>")
|
||||
elif item == "ESP32":
|
||||
src_filter.append("+<helpers/esp32/*>")
|
||||
src_filter.append("+<helpers/wifi/*>")
|
||||
elif item == "NRF52_PLATFORM":
|
||||
src_filter.append("+<helpers/nrf52/*>")
|
||||
elif item == "RP2040_PLATFORM":
|
||||
src_filter.append("+<helpers/rp2040/*>")
|
||||
src_filter.append("+<helpers/wifi/*>")
|
||||
|
||||
# DISPLAY HANDLING
|
||||
elif isinstance(item, tuple) and item[0] == "DISPLAY_CLASS":
|
||||
|
||||
+10
-5
@@ -225,7 +225,7 @@ Elsewhere it replies `Err - neighbors not enabled in this build`. If a
|
||||
|
||||
**Set by build flag:** `LORA_FREQ`, `LORA_BW`, `LORA_SF`, `LORA_CR`
|
||||
|
||||
**Default:** `869.525,250,11,5`
|
||||
**Default:** `869.618,62.5,8,5`
|
||||
|
||||
**Note:** Requires reboot to apply
|
||||
|
||||
@@ -270,7 +270,7 @@ Elsewhere it replies `Err - neighbors not enabled in this build`. If a
|
||||
**Parameters:**
|
||||
- `frequency`: Frequency in MHz
|
||||
|
||||
**Default:** `869.525`
|
||||
**Default:** `869.618`
|
||||
|
||||
**Note:** Requires reboot to apply
|
||||
**Serial Only:** `set freq <frequency>`
|
||||
@@ -551,7 +551,7 @@ Elsewhere it replies `Err - neighbors not enabled in this build`. If a
|
||||
**Parameters:**
|
||||
- `value`: Direct transmit delay factor (0-2)
|
||||
|
||||
**Default:** `0.2`
|
||||
**Default:** `0.3` (Repeater) - `0.2` (Room Server, Sensor)
|
||||
|
||||
**Note:** Same collision-avoidance random window as `txdelay`, but applied to direct (non-flood, routed) traffic. The default is lower because direct packets are addressed to a specific next hop, so far fewer nodes compete to retransmit them.
|
||||
|
||||
@@ -713,7 +713,7 @@ update may simply still be carrying `display.flip 1`. The boot log reports the c
|
||||
**Parameters:**
|
||||
- `hours`: Interval in hours (3-168)
|
||||
|
||||
**Default:** `12` (Repeater) - `0` (Sensor)
|
||||
**Default:** `47` (Repeater, Room Server) - `0`, disabled (Sensor)
|
||||
|
||||
---
|
||||
|
||||
@@ -725,7 +725,12 @@ update may simply still be carrying `display.flip 1`. The boot log reports the c
|
||||
**Parameters:**
|
||||
- `minutes`: Interval in minutes rounded down to the nearest multiple of 2 (61 becomes 60) (60-240)
|
||||
|
||||
**Default:** `0`
|
||||
**Default:** `2` on a factory-fresh node, then `0` (disabled) once configured.
|
||||
|
||||
**Note:** A new install ships with a 2 minute zero-hop advert interval. Saving
|
||||
any setting resets an interval below the 60 minute minimum to `0`, on the
|
||||
assumption that the node has now been deliberately configured. To keep zero-hop
|
||||
adverts running, set an explicit value in the 60-240 range.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+39
-15
@@ -206,7 +206,7 @@ Byte 1: 0x03
|
||||
**Command Format**:
|
||||
```
|
||||
Byte 0: 0x1F
|
||||
Byte 1: Channel Index (0-7)
|
||||
Byte 1: Channel Index (0 .. max_channels-1)
|
||||
```
|
||||
|
||||
**Example** (get channel 1):
|
||||
@@ -225,7 +225,7 @@ Byte 1: Channel Index (0-7)
|
||||
**Command Format**:
|
||||
```
|
||||
Byte 0: 0x20
|
||||
Byte 1: Channel Index (0-7)
|
||||
Byte 1: Channel Index (0 .. max_channels-1)
|
||||
Bytes 2-33: Channel Name (32 bytes, UTF-8, null-padded)
|
||||
Bytes 34-49: Secret (16 bytes)
|
||||
```
|
||||
@@ -233,8 +233,12 @@ Bytes 34-49: Secret (16 bytes)
|
||||
**Total Length**: 50 bytes
|
||||
|
||||
**Channel Index**:
|
||||
- Index 0: Reserved for public channels (no secret)
|
||||
- Indices 1-7: Available for private channels
|
||||
- Valid range is `0` to `max_channels - 1`. Read `max_channels` from byte 3 of
|
||||
the `PACKET_DEVICE_INFO` response; do not assume 8. Builds ship with
|
||||
`MAX_GROUP_CHANNELS` set to 1, 8 or 40 depending on the board.
|
||||
- Index 0: pre-populated by the firmware with the built-in "Public" channel.
|
||||
It can be overwritten like any other slot.
|
||||
- An out-of-range index is rejected with `PACKET_ERROR` / `ERR_CODE_NOT_FOUND`.
|
||||
|
||||
**Channel Name**:
|
||||
- UTF-8 encoded
|
||||
@@ -242,8 +246,18 @@ Bytes 34-49: Secret (16 bytes)
|
||||
- Padded with null bytes (0x00) if shorter
|
||||
|
||||
**Secret Field** (16 bytes):
|
||||
- For **private channels**: 16-byte secret
|
||||
- For **public channels**: All zeros (0x00)
|
||||
- Always a real 16-byte key; the firmware derives the channel hash from
|
||||
`SHA256(secret)` (`BaseChatMesh.cpp:936-942`). There is no "no secret" form.
|
||||
- Do **not** write an all-zero secret. `SHA256` over 16 zero bytes is a fixed,
|
||||
globally identical value, so a zero-secret channel is a well-known channel
|
||||
with an all-zero AES key that any node can read — not a private one. The
|
||||
firmware skips *unnamed* slots when matching inbound group traffic for
|
||||
exactly this reason (`BaseChatMesh.cpp:392-401`), but a slot that has been
|
||||
given a name and a zero secret is not skipped, and will absorb null-key
|
||||
group traffic from any node on the mesh.
|
||||
- The built-in public channel uses the well-known key
|
||||
`izOH6cXN6mrJ5e26oRXNcg==` (base64), i.e.
|
||||
`8b3387e9c5cdea6ac9e5edbaa115cd72` in hex.
|
||||
|
||||
**Example** (create channel "YourChannelName" at index 1 with secret):
|
||||
```
|
||||
@@ -265,7 +279,7 @@ Bytes 34-49: Secret (16 bytes)
|
||||
```
|
||||
Byte 0: 0x03
|
||||
Byte 1: 0x00
|
||||
Byte 2: Channel Index (0-7)
|
||||
Byte 2: Channel Index (0 .. max_channels-1)
|
||||
Bytes 3-6: Timestamp (32-bit little-endian Unix timestamp, seconds)
|
||||
Bytes 7+: Message Text (UTF-8, variable length)
|
||||
```
|
||||
@@ -288,7 +302,7 @@ Bytes 7+: Message Text (UTF-8, variable length)
|
||||
**Command Format**:
|
||||
```
|
||||
Byte 0: 0x3E
|
||||
Byte 1: Channel Index (0-7)
|
||||
Byte 1: Channel Index (0 .. max_channels-1)
|
||||
Byte 2: Path Length (0xFF = flood, otherwise actual path length)
|
||||
Bytes 3 .. 2+path_len: Path (omitted when path_len == 0xFF)
|
||||
Next 2 bytes (little-endian): Data Type (`data_type`, uint16)
|
||||
@@ -306,13 +320,23 @@ Remaining bytes: Binary payload (variable length)
|
||||
- Values `0x0001`–`0xFFFE` are available for registered application/community namespaces. See the [Registered data_type values](#registered-data_type-values) table below.
|
||||
|
||||
**Limits**:
|
||||
- Maximum payload length is `MAX_CHANNEL_DATA_LENGTH = MAX_FRAME_SIZE - 9 = 163` bytes.
|
||||
- Larger payloads are rejected with `PACKET_ERROR` (`ERR_CODE_ILLEGAL_ARG`).
|
||||
- Maximum payload length is **165** bytes — `MAX_GROUP_DATA_LENGTH =
|
||||
MAX_PACKET_PAYLOAD - CIPHER_BLOCK_SIZE - 3` (`src/MeshCore.h:21`). This is the
|
||||
radio-side limit and the one clients should enforce.
|
||||
- Two different bounds are checked, and they do not agree. The host-frame check
|
||||
uses the larger `MAX_CHANNEL_DATA_LENGTH = MAX_FRAME_SIZE - 9 = 167`
|
||||
(`companion_radio/MyMesh.cpp:1265`); the radio-side check uses 165
|
||||
(`BaseChatMesh.cpp:544`).
|
||||
- Payloads **above 167** are rejected with `ERR_CODE_ILLEGAL_ARG` (6).
|
||||
- Payloads of **166 or 167** pass the frame check, then fail the radio-side
|
||||
check and return `ERR_CODE_TABLE_FULL` (3). Despite that code's usual
|
||||
meaning, such a send can never succeed — do not retry it. Keep payloads at
|
||||
165 bytes or fewer and this case cannot arise.
|
||||
|
||||
**Response**: `PACKET_OK` (0x00) on success, or `PACKET_ERROR` (0x01) with one of:
|
||||
- `ERR_CODE_NOT_FOUND` (2) — unknown `channel_idx`
|
||||
- `ERR_CODE_ILLEGAL_ARG` (6) — invalid `path_len`, reserved `data_type` (`0x0000`), or payload larger than `MAX_CHANNEL_DATA_LENGTH`
|
||||
- `ERR_CODE_TABLE_FULL` (3) — outbound send queue is full; retry later
|
||||
- `ERR_CODE_ILLEGAL_ARG` (6) — invalid `path_len`, reserved `data_type` (`0x0000`), or payload larger than `MAX_CHANNEL_DATA_LENGTH` (167)
|
||||
- `ERR_CODE_TABLE_FULL` (3) — outbound send queue is full (retry later), **or** a payload of 166-167 bytes that exceeds the radio-side limit (permanent; see Limits above)
|
||||
|
||||
**Inbound datagrams** are delivered to the host via `RESP_CODE_CHANNEL_DATA_RECV` (0x1B); see [Receive Channel Data Datagram](#receive-channel-data-datagram).
|
||||
|
||||
@@ -341,7 +365,7 @@ Inbound group datagrams (radio-level `PAYLOAD_TYPE_GRP_DATA`, 0x06) are forwarde
|
||||
Byte 0: 0x1B (packet type)
|
||||
Byte 1: SNR (signed int8, scaled ×4 — divide by 4.0 to recover dB)
|
||||
Bytes 2-3: Reserved (clients MUST ignore)
|
||||
Byte 4: Channel Index (0-7)
|
||||
Byte 4: Channel Index (0 .. max_channels-1)
|
||||
Byte 5: Path Length (actual path length when flooded, otherwise 0xFF for direct)
|
||||
Bytes 6-7: Data Type (uint16 little-endian)
|
||||
Byte 8: Data Length
|
||||
@@ -550,7 +574,7 @@ def parse_contact_message(data):
|
||||
**Standard Format** (`PACKET_CHANNEL_MSG_RECV`, 0x08):
|
||||
```
|
||||
Byte 0: 0x08 (packet type)
|
||||
Byte 1: Channel Index (0-7)
|
||||
Byte 1: Channel Index (0 .. max_channels-1)
|
||||
Byte 2: Path Length
|
||||
Byte 3: Text Type
|
||||
Bytes 4-7: Timestamp (32-bit little-endian)
|
||||
@@ -562,7 +586,7 @@ Bytes 8+: Message Text (UTF-8)
|
||||
Byte 0: 0x11 (packet type)
|
||||
Byte 1: SNR (signed byte, multiplied by 4)
|
||||
Bytes 2-3: Reserved
|
||||
Byte 4: Channel Index (0-7)
|
||||
Byte 4: Channel Index (0 .. max_channels-1)
|
||||
Byte 5: Path Length
|
||||
Byte 6: Text Type
|
||||
Bytes 7-10: Timestamp (32-bit little-endian)
|
||||
|
||||
+9
-9
@@ -537,18 +537,18 @@ Mac: python3 should be already installed.
|
||||
|
||||
Then it should be the same for all platforms:
|
||||
```
|
||||
python3 -m venv meshcore
|
||||
cd meshcore && source bin/activate
|
||||
python3 -m venv meshcore-venv
|
||||
cd meshcore-venv && source bin/activate
|
||||
pip install -U platformio
|
||||
git clone https://github.com/ripplebiz/MeshCore.git
|
||||
cd MeshCore
|
||||
git clone https://github.com/meshcore-dev/meshcore.git
|
||||
cd meshcore
|
||||
```
|
||||
open platformio.ini and in `[arduino_base]` edit the `LORA_FREQ=867.5`
|
||||
save, then run:
|
||||
open platformio.ini and in `[arduino_base]` edit `LORA_FREQ` (it defaults to
|
||||
`869.618`) to the frequency for your region, save, then run:
|
||||
```
|
||||
pio run -e RAK_4631_Repeater
|
||||
pio run -e RAK_4631_repeater
|
||||
```
|
||||
then you'll find `firmware.zip` in `.pio/build/RAK_4631_Repeater`
|
||||
then you'll find `firmware.zip` in `.pio/build/RAK_4631_repeater`
|
||||
|
||||
### 5.10. Q: Are there other MeshCore related open source projects?
|
||||
|
||||
@@ -592,7 +592,7 @@ For ESP-based devices (e.g. Heltec V3) you need:
|
||||
1. Download the firmware file from <https://flasher.meshcore.io>.
|
||||
- Go to the website in a browser and find the section that has the firmware you need.
|
||||
- Click the Download button, right-click on the file you need, for example:
|
||||
- `Heltec_V3_companion_radio_ble-v1.7.1-165fb33.bin`
|
||||
- `Heltec_v3_companion_radio_ble-v1.7.1-165fb33.bin`
|
||||
- Non-merged bin keeps the existing Bluetooth pairing database.
|
||||
- `Heltec_v3_companion_radio_usb-v1.7.1-165fb33-merged.bin`
|
||||
- Merged bin overwrites everything including the bootloader and existing Bluetooth pairing database, but keeps configurations.
|
||||
|
||||
@@ -186,11 +186,19 @@ Power management status can be queried via the CLI:
|
||||
| `get pwrmgt.bootreason` | Returns reset and shutdown reason strings |
|
||||
| `get pwrmgt.bootmv` | Returns boot voltage in millivolts |
|
||||
|
||||
On boards without power management enabled, all commands except `get pwrmgt.support` return:
|
||||
On boards without power management enabled, `get pwrmgt.source` and
|
||||
`get pwrmgt.bootmv` return:
|
||||
```
|
||||
ERROR: Power management not supported
|
||||
```
|
||||
|
||||
`get pwrmgt.support` returns `unsupported`. `get pwrmgt.bootreason` is not
|
||||
compiled out at all and answers on every board: `getResetReason()` and
|
||||
`getShutdownReason()` are virtuals on the base board class, so a board that
|
||||
does not override them reports `Not available` rather than an error. ESP32
|
||||
boards override the reset half with `esp_reset_reason()`, so they return a real
|
||||
reset reason and `Not available` for the shutdown reason.
|
||||
|
||||
## Debug Output
|
||||
|
||||
When `MESH_DEBUG=1` is enabled, the power management module outputs:
|
||||
|
||||
@@ -27,15 +27,17 @@ set lon {longitude}
|
||||
```
|
||||
Sets your advertisement map longitude. (decimal degrees)
|
||||
|
||||
```
|
||||
set dutycycle {percent}
|
||||
```
|
||||
Sets the transmit duty cycle limit (1-100%). Example: `set dutycycle 10` for 10%.
|
||||
|
||||
```
|
||||
set af {air-time-factor}
|
||||
```
|
||||
Sets the transmit air-time-factor. Deprecated — use `set dutycycle` instead.
|
||||
Sets the transmit air-time-factor.
|
||||
|
||||
> **Note:** `set dutycycle` is *not* available in Terminal Chat. Every other
|
||||
> firmware — Repeater, Room Server, Sensor and Companion Radio — routes its
|
||||
> `set`/`get` commands through the shared radio prefs handler and does support
|
||||
> it (see [CLI Commands](./cli_commands.md)). Terminal Chat is the sole
|
||||
> exception: it has its own inline `set` handler covering only the six options
|
||||
> listed above.
|
||||
|
||||
|
||||
```
|
||||
@@ -99,3 +101,8 @@ Resets the path to current recipient, for new path discovery.
|
||||
public {text}
|
||||
```
|
||||
Sends the text message to the built-in 'public' group channel
|
||||
|
||||
```
|
||||
help
|
||||
```
|
||||
Lists the available commands.
|
||||
@@ -1,6 +1,9 @@
|
||||
#include "MyMesh.h"
|
||||
|
||||
#include <Arduino.h> // needed for PlatformIO
|
||||
#ifdef ENABLE_WIFI_INTERFACE
|
||||
#include <WiFi.h>
|
||||
#endif
|
||||
#include <Mesh.h>
|
||||
|
||||
#define CMD_APP_START 1
|
||||
@@ -136,6 +139,10 @@
|
||||
#define ERR_CODE_FILE_IO_ERROR 5
|
||||
#define ERR_CODE_ILLEGAL_ARG 6
|
||||
|
||||
// Copied from simple_repeater (could probably be shared)
|
||||
#define CTL_TYPE_NODE_DISCOVER_REQ 0x80
|
||||
#define CTL_TYPE_NODE_DISCOVER_RESP 0x90
|
||||
|
||||
#define MAX_SIGN_DATA_LEN (8 * 1024) // 8K
|
||||
|
||||
// Auto-add config bitmask
|
||||
@@ -260,25 +267,24 @@ float MyMesh::getAirtimeBudgetFactor() const {
|
||||
return _prefs.airtime_factor;
|
||||
}
|
||||
|
||||
int MyMesh::getInterferenceThreshold() const {
|
||||
return _prefs.interference_threshold;
|
||||
}
|
||||
bool MyMesh::getCADEnabled() const {
|
||||
return _prefs.cad_enabled;
|
||||
}
|
||||
|
||||
int MyMesh::getInterferenceThreshold() const {
|
||||
return 0; // disabled for now, until currentRSSI() problem is resolved
|
||||
}
|
||||
|
||||
int MyMesh::calcRxDelay(float score, uint32_t air_time) const {
|
||||
if (_prefs.rx_delay_base <= 0.0f) return 0;
|
||||
return (int)((pow(_prefs.rx_delay_base, 0.85f - score) - 1.0) * air_time);
|
||||
}
|
||||
|
||||
uint32_t MyMesh::getRetransmitDelay(const mesh::Packet *packet) {
|
||||
uint32_t t = (_radio->getEstAirtimeFor(packet->getPathByteLen() + packet->payload_len + 2) * 0.5f);
|
||||
uint32_t t = (_radio->getEstAirtimeFor(packet->getPathByteLen() + packet->payload_len + 2) * _prefs.tx_delay_factor);
|
||||
return getRNG()->nextInt(0, 5*t + 1);
|
||||
}
|
||||
uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) {
|
||||
uint32_t t = (_radio->getEstAirtimeFor(packet->getPathByteLen() + packet->payload_len + 2) * 0.2f);
|
||||
uint32_t t = (_radio->getEstAirtimeFor(packet->getPathByteLen() + packet->payload_len + 2) * _prefs.direct_tx_delay_factor);
|
||||
return getRNG()->nextInt(0, 5*t + 1);
|
||||
}
|
||||
|
||||
@@ -406,6 +412,53 @@ int MyMesh::getRecentlyHeard(AdvertPath dest[], int max_num) {
|
||||
return max_num;
|
||||
}
|
||||
|
||||
#if defined(DISPLAY_CLASS) && !(defined(UI_NO_DISCOVER_SCREEN) && (UI_NO_DISCOVER_SCREEN + 0 != 0))
|
||||
int MyMesh::getDiscoveredNodes(DiscoveredNode nodes[], int max_num) {
|
||||
if (max_num > DISCOVERED_NODES_TABLE_SIZE) max_num = DISCOVERED_NODES_TABLE_SIZE;
|
||||
if (max_num > disc_nodes_count) max_num = disc_nodes_count;
|
||||
|
||||
for (int i = 0; i < max_num; i++) {
|
||||
nodes[i] = discovered_nodes[i];
|
||||
}
|
||||
return max_num;
|
||||
}
|
||||
|
||||
bool MyMesh::requestRepeatersDiscovery() {
|
||||
uint8_t cmd_bytes[6];
|
||||
cmd_bytes[0] = CTL_TYPE_NODE_DISCOVER_REQ | 1; // DISCOVER_REQ | prefix only
|
||||
cmd_bytes[1] = 0xFF; // Repeaters
|
||||
getRNG()->random(&cmd_bytes[2], 4); // tag
|
||||
disc_nodes_count = 0;
|
||||
disc_node_req_tag = *((uint32_t*)&cmd_bytes[2]);
|
||||
mesh::Packet* req = createControlData(cmd_bytes, sizeof(cmd_bytes));
|
||||
if (req) {
|
||||
sendZeroHop(req);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void MyMesh::checkControlDataForPendingDiscovery(uint8_t payload[], size_t p_len) {
|
||||
if ((p_len < 12)
|
||||
|| (payload[0] & 0xF0 != CTL_TYPE_NODE_DISCOVER_RESP)
|
||||
|| (disc_nodes_count >= DISCOVERED_NODES_TABLE_SIZE)
|
||||
|| (memcmp(&payload[2], &disc_node_req_tag, 4))) {
|
||||
return;
|
||||
}
|
||||
memcpy(&discovered_nodes[disc_nodes_count].pubkey_prefix, &payload[6], 8);
|
||||
discovered_nodes[disc_nodes_count].type = payload[0] & 0xF;
|
||||
discovered_nodes[disc_nodes_count].snr_out = ((int8_t)payload[1]) / 4.0;
|
||||
discovered_nodes[disc_nodes_count].snr_in = _radio->getLastSNR();
|
||||
ContactInfo* c = lookupContactByPubKey(&payload[6], 8);
|
||||
if (c != NULL) {
|
||||
strncpy(discovered_nodes[disc_nodes_count].name, c->name, 32);
|
||||
} else {
|
||||
discovered_nodes[disc_nodes_count].name[0] = 0;
|
||||
}
|
||||
disc_nodes_count ++;
|
||||
}
|
||||
#endif
|
||||
|
||||
void MyMesh::onContactPathUpdated(const ContactInfo &contact) {
|
||||
out_frame[0] = PUSH_CODE_PATH_UPDATED;
|
||||
memcpy(&out_frame[1], contact.id.pub_key, PUB_KEY_SIZE);
|
||||
@@ -797,6 +850,9 @@ void MyMesh::onControlDataRecv(mesh::Packet *packet) {
|
||||
MESH_DEBUG_PRINTLN("onControlDataRecv(), payload_len too long: %d", packet->payload_len);
|
||||
return;
|
||||
}
|
||||
#if defined(DISPLAY_CLASS) && !(defined(UI_NO_DISCOVER_SCREEN) && (UI_NO_DISCOVER_SCREEN + 0 != 0))
|
||||
checkControlDataForPendingDiscovery(packet->payload, packet->payload_len);
|
||||
#endif
|
||||
int i = 0;
|
||||
out_frame[i++] = PUSH_CODE_CONTROL_DATA;
|
||||
out_frame[i++] = (int8_t)(_radio->getLastSNR() * 4);
|
||||
@@ -879,6 +935,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
|
||||
_serial(NULL), telemetry(MAX_PACKET_PAYLOAD - 4), _store(&store), _ui(ui), _iter(0) {
|
||||
_iter_started = false;
|
||||
_cli_rescue = false;
|
||||
cli_command[0] = 0;
|
||||
offline_queue_len = 0;
|
||||
app_target_ver = 0;
|
||||
clearPendingReqs();
|
||||
@@ -890,7 +947,9 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
|
||||
send_unscoped = false;
|
||||
|
||||
// defaults
|
||||
_prefs.airtime_factor = 1.0; // one half
|
||||
_prefs.airtime_factor = 1.0;
|
||||
_prefs.tx_delay_factor = 0.5f;
|
||||
_prefs.direct_tx_delay_factor = 0.2f;
|
||||
strcpy(_prefs.node_name, "NONAME");
|
||||
_prefs.freq = LORA_FREQ;
|
||||
_prefs.sf = LORA_SF;
|
||||
@@ -900,7 +959,11 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
|
||||
_prefs.gps_enabled = 0; // GPS disabled by default
|
||||
_prefs.gps_interval = 0; // No automatic GPS updates by default
|
||||
_prefs.radio_fem_rxgain = 1;
|
||||
#ifdef RADIO_FEM_TXGAIN
|
||||
_prefs.radio_fem_txgain = RADIO_FEM_TXGAIN; // board-specific default for PA
|
||||
#else
|
||||
_prefs.radio_fem_txgain = 0;
|
||||
#endif
|
||||
//_prefs.rx_delay_base = 10.0f; enable once new algo fixed
|
||||
_prefs.setRepeatEn(false);
|
||||
#if defined(USE_SX1262) || defined(USE_SX1268)
|
||||
@@ -953,6 +1016,8 @@ void MyMesh::begin(bool has_display) {
|
||||
|
||||
// sanitise bad pref values
|
||||
_prefs.rx_delay_base = constrain(_prefs.rx_delay_base, 0, 20.0f);
|
||||
_prefs.tx_delay_factor = constrain(_prefs.tx_delay_factor, 0, 2.0f);
|
||||
_prefs.direct_tx_delay_factor = constrain(_prefs.direct_tx_delay_factor, 0, 2.0f);
|
||||
_prefs.airtime_factor = constrain(_prefs.airtime_factor, 0, 9.0f);
|
||||
_prefs.freq = constrain(_prefs.freq, 150.0f, 2500.0f);
|
||||
_prefs.bw = constrain(_prefs.bw, 7.8f, 500.0f);
|
||||
@@ -1211,8 +1276,8 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
writeErrFrame(ERR_CODE_NOT_FOUND); // bad channel_idx
|
||||
} else if (data_type == DATA_TYPE_RESERVED) {
|
||||
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
|
||||
} else if (payload_len > MAX_CHANNEL_DATA_LENGTH) {
|
||||
MESH_DEBUG_PRINTLN("CMD_SEND_CHANNEL_DATA payload too long: %d > %d", payload_len, MAX_CHANNEL_DATA_LENGTH);
|
||||
} else if (payload_len > MAX_GROUP_DATA_LENGTH) {
|
||||
MESH_DEBUG_PRINTLN("CMD_SEND_CHANNEL_DATA payload too long: %d > %d", payload_len, MAX_GROUP_DATA_LENGTH);
|
||||
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
|
||||
} else if (sendGroupData(channel.channel, path, path_len, data_type, payload, payload_len)) {
|
||||
writeOKFrame();
|
||||
@@ -2105,6 +2170,54 @@ bool MyMesh::handleCommand(const char* command, uint32_t sender_timestamp, char*
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_WIFI_INTERFACE
|
||||
if (memcmp(command, "set wifi.ssid ", 14) == 0) {
|
||||
StrHelper::strncpy(_prefs.wifi_ssid, &command[14], sizeof(_prefs.wifi_ssid));
|
||||
savePrefs();
|
||||
sprintf(reply, "> wifi.ssid is now %s (set wifi.pwd too, then reboot)", _prefs.wifi_ssid);
|
||||
return true;
|
||||
}
|
||||
if (memcmp(command, "set wifi.pwd ", 13) == 0) {
|
||||
StrHelper::strncpy(_prefs.wifi_pwd, &command[13], sizeof(_prefs.wifi_pwd));
|
||||
savePrefs();
|
||||
strcpy(reply, "> wifi.pwd updated (reboot to apply)");
|
||||
return true;
|
||||
}
|
||||
if (strcmp(command, "set wifi.clear") == 0) {
|
||||
_prefs.wifi_ssid[0] = 0;
|
||||
_prefs.wifi_pwd[0] = 0;
|
||||
savePrefs();
|
||||
strcpy(reply, "> wifi config cleared (reboot to apply)");
|
||||
return true;
|
||||
}
|
||||
if (strcmp(command, "get wifi.ssid") == 0) { // no 'get wifi.pwd', by design
|
||||
sprintf(reply, "> %s", _prefs.getWifiSSID()[0] ? _prefs.getWifiSSID() : "(not set)");
|
||||
return true;
|
||||
}
|
||||
if (memcmp(command, "set wifi.enabled ", 17) == 0) {
|
||||
_prefs.wifi_enabled = atoi(&command[17]) ? 1 : 0;
|
||||
savePrefs();
|
||||
sprintf(reply, "> wifi.enabled is now %d (reboot to apply)", _prefs.wifi_enabled);
|
||||
return true;
|
||||
}
|
||||
if (strcmp(command, "get wifi.enabled") == 0) {
|
||||
sprintf(reply, "> %d", _prefs.wifi_enabled);
|
||||
return true;
|
||||
}
|
||||
if (strcmp(command, "get wifi.status") == 0) {
|
||||
strcpy(reply, WiFi.status() == WL_CONNECTED ? "> connected" : "> disconnected");
|
||||
return true;
|
||||
}
|
||||
if (strcmp(command, "get wifi.ip") == 0) {
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
sprintf(reply, "> %s", WiFi.localIP().toString().c_str());
|
||||
} else {
|
||||
strcpy(reply, "> (not connected)");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (strcmp(command, "board") == 0) {
|
||||
strcpy(reply, board.getManufacturerName());
|
||||
return true;
|
||||
@@ -2115,11 +2228,34 @@ bool MyMesh::handleCommand(const char* command, uint32_t sender_timestamp, char*
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strcmp(command, "get tz.offset") == 0) {
|
||||
sprintf(reply, "> %d", _prefs.tz_offset);
|
||||
return true;
|
||||
}
|
||||
if (memcmp(command, "set tz.offset ", 14) == 0) {
|
||||
int8_t tz = atof(&command[14]);
|
||||
if (tz < -12 || tz > 14) {
|
||||
strcpy(reply, "Error, must be from -12 to +14");
|
||||
} else {
|
||||
_prefs.tz_offset = tz;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false; // not handled
|
||||
}
|
||||
|
||||
void MyMesh::checkCLIRescueCmd() {
|
||||
int len = strlen(cli_command);
|
||||
// `cli_command` must stay NUL-terminated within its bounds. If it ever isn't,
|
||||
// strlen() above can return >= sizeof(cli_command) and the loop below would
|
||||
// then index past the buffer, so clamp defensively.
|
||||
if (len >= (int)sizeof(cli_command)) {
|
||||
cli_command[0] = 0;
|
||||
len = 0;
|
||||
}
|
||||
while (Serial.available() && len < sizeof(cli_command)-1) {
|
||||
char c = Serial.read();
|
||||
if (c != '\n') {
|
||||
@@ -2128,8 +2264,9 @@ void MyMesh::checkCLIRescueCmd() {
|
||||
}
|
||||
Serial.print(c); // echo
|
||||
}
|
||||
if (len == sizeof(cli_command)-1) { // command buffer full
|
||||
cli_command[sizeof(cli_command)-1] = '\r';
|
||||
if (len == sizeof(cli_command)-1) { // buffer full: treat as a completed line
|
||||
cli_command[sizeof(cli_command)-2] = '\r'; // place end-of-line marker inside the buffer
|
||||
cli_command[sizeof(cli_command)-1] = 0; // keep the buffer NUL-terminated
|
||||
}
|
||||
|
||||
if (len > 0 && cli_command[len - 1] == '\r') { // received complete line
|
||||
@@ -2319,6 +2456,11 @@ void MyMesh::loop() {
|
||||
checkCLIRescueCmd();
|
||||
} else {
|
||||
checkSerialInterface();
|
||||
#if defined(ENABLE_WIFI_INTERFACE) && defined(RP2040_PLATFORM) && !defined(ENABLE_USB_INTERFACE)
|
||||
// RP2040 WiFi builds are headless and have no way into the rescue CLI (that needs a
|
||||
// display + long-press), so serve config commands on the otherwise unused USB serial
|
||||
checkCLIRescueCmd();
|
||||
#endif
|
||||
}
|
||||
|
||||
// is there are pending dirty contacts write needed?
|
||||
|
||||
@@ -84,6 +84,16 @@ struct AdvertPath {
|
||||
uint8_t path[MAX_PATH_SIZE];
|
||||
};
|
||||
|
||||
#if defined(DISPLAY_CLASS) && !(defined(UI_NO_DISCOVER_SCREEN) && (UI_NO_DISCOVER_SCREEN + 0 != 0))
|
||||
struct DiscoveredNode {
|
||||
uint8_t pubkey_prefix[9];
|
||||
float snr_in;
|
||||
float snr_out;
|
||||
char name[32];
|
||||
uint8_t type;
|
||||
};
|
||||
#endif
|
||||
|
||||
class MyMesh : public BaseChatMesh, public DataStoreHost {
|
||||
public:
|
||||
MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMeshTables &tables, DataStore& store, AbstractUITask* ui=NULL);
|
||||
@@ -102,10 +112,18 @@ public:
|
||||
|
||||
int getRecentlyHeard(AdvertPath dest[], int max_num);
|
||||
|
||||
#if defined(DISPLAY_CLASS) && !(defined(UI_NO_DISCOVER_SCREEN) && (UI_NO_DISCOVER_SCREEN + 0 != 0))
|
||||
bool requestRepeatersDiscovery();
|
||||
int getDiscoveredNodes(DiscoveredNode nodes[], int max_num);
|
||||
#endif
|
||||
|
||||
protected:
|
||||
float getAirtimeBudgetFactor() const override;
|
||||
int getInterferenceThreshold() const override;
|
||||
bool getCADEnabled() const override;
|
||||
int getAGCResetInterval() const override {
|
||||
return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds
|
||||
}
|
||||
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;
|
||||
@@ -261,6 +279,19 @@ private:
|
||||
|
||||
#define ADVERT_PATH_TABLE_SIZE 16
|
||||
AdvertPath advert_paths[ADVERT_PATH_TABLE_SIZE]; // circular table
|
||||
|
||||
#if defined(DISPLAY_CLASS) && !(defined(UI_NO_DISCOVER_SCREEN) && (UI_NO_DISCOVER_SCREEN + 0 != 0))
|
||||
#ifdef UI_RECENT_LIST_SIZE
|
||||
#define DISCOVERED_NODES_TABLE_SIZE UI_RECENT_LIST_SIZE
|
||||
#else
|
||||
#define DISCOVERED_NODES_TABLE_SIZE 4
|
||||
#endif
|
||||
DiscoveredNode discovered_nodes[DISCOVERED_NODES_TABLE_SIZE]; // not circular, latest discovered nodes are not kept
|
||||
uint32_t disc_node_req_tag = 0;
|
||||
uint32_t disc_nodes_count = 0;
|
||||
|
||||
void checkControlDataForPendingDiscovery(uint8_t payload[], size_t p_len);
|
||||
#endif
|
||||
};
|
||||
|
||||
extern MyMesh the_mesh;
|
||||
@@ -27,6 +27,8 @@ public:
|
||||
uint8_t telemetry_mode_loc = 0;
|
||||
uint8_t telemetry_mode_env = 0;
|
||||
float rx_delay_base = 0;
|
||||
float tx_delay_factor = 0;
|
||||
float direct_tx_delay_factor = 0;
|
||||
uint32_t ble_pin = 0;
|
||||
uint8_t advert_loc_policy = 0;
|
||||
uint8_t buzzer_quiet = 0;
|
||||
@@ -41,8 +43,21 @@ public:
|
||||
uint8_t path_hash_mode = 0; // which path mode to use when sending
|
||||
uint8_t autoadd_max_hops = 0; // 0 = no limit, 1 = direct (0 hops), N = up to N-1 hops (max 64)
|
||||
uint8_t cad_enabled = 0;
|
||||
uint8_t interference_threshold = 0;
|
||||
uint8_t agc_reset_interval = 0; // secs / 4
|
||||
char default_scope_name[31];
|
||||
uint8_t default_scope_key[16];
|
||||
int8_t tz_offset = 0;
|
||||
#ifdef ENABLE_WIFI_INTERFACE
|
||||
#ifndef WIFI_SSID
|
||||
#define WIFI_SSID ""
|
||||
#endif
|
||||
char wifi_ssid[33] = {0}; // if empty, the compile-time WIFI_SSID is used
|
||||
char wifi_pwd[64] = {0};
|
||||
uint8_t wifi_enabled = 1; // enabled by default to allow wifi only builds to work. wifi won't be started if ssid is empty
|
||||
// use ssid from prefs, or fallback to ssid from build flags
|
||||
const char* getWifiSSID() const { return wifi_ssid[0] ? wifi_ssid : WIFI_SSID; }
|
||||
#endif
|
||||
|
||||
private:
|
||||
class RadioPrefs : public CommonRadioPrefs {
|
||||
@@ -54,16 +69,16 @@ private:
|
||||
def("sf", _parent->sf);
|
||||
def("cr", _parent->cr);
|
||||
def("cad", _parent->cad_enabled);
|
||||
//def("int_thr", _parent->interference_threshold);
|
||||
def("int_thr", _parent->interference_threshold);
|
||||
def("rxgain", _parent->rx_boosted_gain);
|
||||
def("fem_rxgain", _parent->radio_fem_rxgain); // fem_rxgain WAS mapped to wrong JSON property previously
|
||||
def("fem_txgain", _parent->radio_fem_txgain);
|
||||
def("tx", _parent->tx_power_dbm);
|
||||
def("af", _parent->airtime_factor);
|
||||
def("rxdelay", _parent->rx_delay_base);
|
||||
//def("f_txdelay", _parent->tx_delay_factor); currently hard-coded
|
||||
//def("d_txdelay", _parent->direct_tx_delay_factor); currently hard-coded
|
||||
//def("agc_int", _parent->agc_reset_interval);
|
||||
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);
|
||||
}
|
||||
@@ -83,24 +98,24 @@ private:
|
||||
void setAirtimeFactor(float af) override { _parent->airtime_factor = af; markDirty(); }
|
||||
bool isCadEnabled() const override { return _parent->cad_enabled; }
|
||||
void setCadEnabled(bool en) override { _parent->cad_enabled = en; markDirty(); }
|
||||
uint8_t getIntThresh() const override { return 0; }
|
||||
void setIntThresh(uint8_t t) override { /* no-op */ }
|
||||
uint8_t getIntThresh() const override { return _parent->interference_threshold; }
|
||||
void setIntThresh(uint8_t t) override { _parent->interference_threshold = t; markDirty(); }
|
||||
uint8_t getRxGain() const override { return _parent->rx_boosted_gain; }
|
||||
void setRxGain(uint8_t g) override { _parent->rx_boosted_gain = g; markDirty(); }
|
||||
int8_t getTxPower() const override { return _parent->tx_power_dbm; }
|
||||
void setTxPower(int8_t dbm) override { _parent->tx_power_dbm = dbm; markDirty(); }
|
||||
float getRxDelay() const override { return _parent->rx_delay_base; }
|
||||
void setRxDelay(float d) override { _parent->rx_delay_base = d; markDirty(); }
|
||||
uint8_t getAgcResetInt() const override { return 0; }
|
||||
void setAgcResetInt(uint8_t secs) override { /* no-op */ }
|
||||
uint8_t getAgcResetInt() const override { return _parent->agc_reset_interval * 4; }
|
||||
void setAgcResetInt(uint8_t secs) override { _parent->agc_reset_interval = secs / 4; markDirty(); }
|
||||
uint8_t getHashMode() const override { return _parent->path_hash_mode; }
|
||||
void setHashMode(uint8_t m) override { _parent->path_hash_mode = m; markDirty(); }
|
||||
uint8_t getMultiAcks() const override { return _parent->multi_acks; }
|
||||
void setMultiAcks(uint8_t m) override { _parent->multi_acks = m; markDirty(); }
|
||||
float getFloodTxDelay() const override { return 0.5f; } // currently hard-coded
|
||||
void setFloodTxDelay(float d) override { /* no-op */ }
|
||||
float getDirectTxDelay() const override { return 0.2f; } // currently hard-coded
|
||||
void setDirectTxDelay(float d) override { /* no-op */ }
|
||||
float getFloodTxDelay() const override { return _parent->tx_delay_factor; }
|
||||
void setFloodTxDelay(float d) override { _parent->tx_delay_factor = d; markDirty(); }
|
||||
float getDirectTxDelay() const override { return _parent->direct_tx_delay_factor; }
|
||||
void setDirectTxDelay(float d) override { _parent->direct_tx_delay_factor = d; markDirty(); }
|
||||
uint8_t getFEMRxGain() const override { return _parent->radio_fem_rxgain; }
|
||||
void setFEMRxGain(uint8_t g) override { _parent->radio_fem_rxgain = g; markDirty(); }
|
||||
uint8_t getFEMTxGain() const override { return _parent->radio_fem_txgain; }
|
||||
@@ -150,6 +165,7 @@ private:
|
||||
def("tel_base", _parent->telemetry_mode_base);
|
||||
def("tel_loc", _parent->telemetry_mode_loc);
|
||||
def("tel_env", _parent->telemetry_mode_env);
|
||||
def("tz_offset", _parent->tz_offset);
|
||||
}
|
||||
public:
|
||||
CompanionPrefs(NodePrefs* parent) : _parent(parent) { }
|
||||
@@ -158,6 +174,21 @@ private:
|
||||
|
||||
DynamicConfigSerializer custom;
|
||||
|
||||
#ifdef ENABLE_WIFI_INTERFACE
|
||||
class WiFiPrefs : public ConfigSerializer {
|
||||
NodePrefs* _parent;
|
||||
protected:
|
||||
void structure() override {
|
||||
def("ssid", _parent->wifi_ssid, sizeof(_parent->wifi_ssid));
|
||||
def("pwd", _parent->wifi_pwd, sizeof(_parent->wifi_pwd));
|
||||
def("enabled", _parent->wifi_enabled);
|
||||
}
|
||||
public:
|
||||
WiFiPrefs(NodePrefs* parent) : _parent(parent) { }
|
||||
};
|
||||
WiFiPrefs wifi;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
void structure() override {
|
||||
def("name", node_name, sizeof(node_name));
|
||||
@@ -170,9 +201,16 @@ protected:
|
||||
def("repeat", repeat);
|
||||
def("comp", companion);
|
||||
def("custom", custom);
|
||||
#ifdef ENABLE_WIFI_INTERFACE
|
||||
def("wifi", wifi);
|
||||
#endif
|
||||
}
|
||||
public:
|
||||
NodePrefs() : radio(this), gps(this), companion(this), custom(&radio) {
|
||||
NodePrefs() : radio(this), gps(this), companion(this), custom(&radio)
|
||||
#ifdef ENABLE_WIFI_INTERFACE
|
||||
, wifi(this)
|
||||
#endif
|
||||
{
|
||||
node_name[0] = 0;
|
||||
default_scope_name[0] = 0;
|
||||
memset(default_scope_key, 0, sizeof(default_scope_key));
|
||||
|
||||
@@ -26,19 +26,38 @@ MultiSerialInterface interface_manager;
|
||||
// include nrf52 bluetooth interface
|
||||
#include <helpers/nrf52/SerialBLEInterface.h>
|
||||
SerialBLEInterface bluetooth_interface;
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
// include rp2040 (Pico W / CYW43) bluetooth interface
|
||||
#include <helpers/rp2040/SerialBLEInterface.h>
|
||||
SerialBLEInterface bluetooth_interface;
|
||||
#else
|
||||
#error "SerialBLEInterface is not defined for this platform"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// include wifi interface
|
||||
#ifdef WIFI_SSID
|
||||
#ifdef ENABLE_WIFI_INTERFACE
|
||||
#ifndef WIFI_SSID
|
||||
#define WIFI_SSID ""
|
||||
#endif
|
||||
#ifndef WIFI_PWD
|
||||
#define WIFI_PWD ""
|
||||
#endif
|
||||
#ifndef TCP_PORT
|
||||
#define TCP_PORT 5000
|
||||
#endif
|
||||
#ifdef ESP32
|
||||
// include esp32 wifi interface
|
||||
#include <helpers/esp32/SerialWifiInterface.h>
|
||||
#ifndef WIFI_RETRY_INTERVAL
|
||||
#if defined(RP2040_PLATFORM)
|
||||
#define WIFI_RETRY_INTERVAL 30000 // each attempt blocks loop(), so retry less often
|
||||
#else
|
||||
#define WIFI_RETRY_INTERVAL 10000 // millis between reconnect attempts
|
||||
#endif
|
||||
#endif
|
||||
#ifndef WIFI_RETRY_TIMEOUT
|
||||
#define WIFI_RETRY_TIMEOUT 5000 // RP2040: cap on how long one join may block loop()
|
||||
#endif
|
||||
#if defined(ESP32) || defined(RP2040_PLATFORM)
|
||||
#include <helpers/wifi/SerialWifiInterface.h>
|
||||
SerialWifiInterface wifi_interface;
|
||||
#else
|
||||
#error "SerialWifiInterface is not defined for this platform"
|
||||
@@ -108,9 +127,13 @@ void halt() {
|
||||
}
|
||||
|
||||
/* WIFI RECONNECT TRACKERS */
|
||||
#if defined(ESP32) && defined(WIFI_SSID)
|
||||
#ifdef ENABLE_WIFI_INTERFACE
|
||||
bool wifi_needs_reconnect = false;
|
||||
unsigned long last_wifi_reconnect_attempt = 0;
|
||||
char wifi_ssid[33] = WIFI_SSID; // replaced by stored prefs at boot, if set
|
||||
char wifi_pwd[64] = WIFI_PWD;
|
||||
bool wifi_was_connected = false;
|
||||
bool wifi_enabled = false; // set at boot from prefs; false also when the effective SSID is blank
|
||||
#endif
|
||||
|
||||
void setup() {
|
||||
@@ -191,23 +214,47 @@ void setup() {
|
||||
#endif
|
||||
|
||||
// add wifi interface
|
||||
#ifdef WIFI_SSID
|
||||
board.setInhibitSleep(true); // prevent sleep when WiFi is active
|
||||
WiFi.setAutoReconnect(true);
|
||||
#ifdef ENABLE_WIFI_INTERFACE
|
||||
// use wifi ssid and password from prefs if ssid is not empty, otherwise use the build flag defaults
|
||||
if (the_mesh.getNodePrefs()->wifi_ssid[0]) {
|
||||
strcpy(wifi_ssid, the_mesh.getNodePrefs()->wifi_ssid);
|
||||
strcpy(wifi_pwd, the_mesh.getNodePrefs()->wifi_pwd);
|
||||
}
|
||||
// only start wifi if enabled and ssid is not empty
|
||||
wifi_enabled = the_mesh.getNodePrefs()->wifi_enabled;
|
||||
if (wifi_enabled && wifi_ssid[0]) {
|
||||
#if defined(ESP32)
|
||||
board.setInhibitSleep(true); // prevent sleep when WiFi is active
|
||||
WiFi.setAutoReconnect(true);
|
||||
|
||||
WiFi.onEvent([](WiFiEvent_t event, WiFiEventInfo_t info){
|
||||
if (event == ARDUINO_EVENT_WIFI_STA_DISCONNECTED) {
|
||||
WIFI_DEBUG_PRINTLN("WiFi disconnected. Flagging for reconnect...");
|
||||
wifi_needs_reconnect = true;
|
||||
} else if (event == ARDUINO_EVENT_WIFI_STA_GOT_IP) {
|
||||
WIFI_DEBUG_PRINTLN("WiFi connected successfully!");
|
||||
wifi_needs_reconnect = false;
|
||||
}
|
||||
});
|
||||
WiFi.onEvent([](WiFiEvent_t event, WiFiEventInfo_t info){
|
||||
if (event == ARDUINO_EVENT_WIFI_STA_DISCONNECTED) {
|
||||
WIFI_DEBUG_PRINTLN("WiFi disconnected (reason=%s). Flagging for reconnect...",
|
||||
WiFi.disconnectReasonName((wifi_err_reason_t)info.wifi_sta_disconnected.reason));
|
||||
wifi_needs_reconnect = true;
|
||||
} else if (event == ARDUINO_EVENT_WIFI_STA_GOT_IP) {
|
||||
WIFI_DEBUG_PRINTLN("WiFi connected successfully!");
|
||||
wifi_needs_reconnect = false;
|
||||
}
|
||||
});
|
||||
#endif
|
||||
|
||||
WiFi.begin(WIFI_SSID, WIFI_PWD);
|
||||
wifi_interface.begin(TCP_PORT);
|
||||
interface_manager.addInterface(InterfaceType::WiFi, &wifi_interface);
|
||||
WIFI_DEBUG_PRINTLN("connecting to %s", wifi_ssid);
|
||||
|
||||
#if defined(RP2040_PLATFORM)
|
||||
// the join itself blocks inside the core (CYW43::begin busy-waits for the
|
||||
// association), so every attempt stalls the mesh loop. beginNoBlock() only skips the
|
||||
// extra DHCP wait. Give the first connect a full window, then bound the retries below.
|
||||
WiFi.beginNoBlock(wifi_ssid, wifi_pwd);
|
||||
last_wifi_reconnect_attempt = millis(); // let DHCP finish before the poll can retry
|
||||
#else
|
||||
WiFi.begin(wifi_ssid, wifi_pwd);
|
||||
#endif
|
||||
wifi_interface.begin(TCP_PORT);
|
||||
interface_manager.addInterface(InterfaceType::WiFi, &wifi_interface);
|
||||
} else {
|
||||
WIFI_DEBUG_PRINTLN("wifi disabled");
|
||||
}
|
||||
#endif
|
||||
|
||||
// add usb interface
|
||||
@@ -262,13 +309,34 @@ void loop() {
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(ESP32) && defined(WIFI_SSID)
|
||||
// Safely attempt to reconnect every 10 seconds if flagged
|
||||
if (wifi_needs_reconnect && (millis() - last_wifi_reconnect_attempt > 10000)) {
|
||||
WIFI_DEBUG_PRINTLN("Attempting manual WiFi reconnect...");
|
||||
WiFi.disconnect();
|
||||
WiFi.reconnect();
|
||||
last_wifi_reconnect_attempt = millis();
|
||||
#ifdef ENABLE_WIFI_INTERFACE
|
||||
if (wifi_enabled) {
|
||||
// RP2040 has no WiFi event callbacks, so poll the link state instead
|
||||
#if defined(RP2040_PLATFORM)
|
||||
wifi_needs_reconnect = (WiFi.status() != WL_CONNECTED);
|
||||
if (wifi_was_connected == wifi_needs_reconnect) { // link state changed
|
||||
wifi_was_connected = !wifi_needs_reconnect;
|
||||
if (wifi_was_connected) {
|
||||
WIFI_DEBUG_PRINTLN("connected, listening on %s:%d", WiFi.localIP().toString().c_str(), TCP_PORT);
|
||||
} else {
|
||||
WIFI_DEBUG_PRINTLN("link lost");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Safely attempt to reconnect if flagged. On RP2040 each attempt blocks the mesh loop
|
||||
// for up to WIFI_RETRY_TIMEOUT, so retry less often and cap how long a join may stall.
|
||||
if (wifi_needs_reconnect && (millis() - last_wifi_reconnect_attempt > WIFI_RETRY_INTERVAL)) {
|
||||
WIFI_DEBUG_PRINTLN("Attempting manual WiFi reconnect to %s (status %d)...", wifi_ssid, WiFi.status());
|
||||
#if defined(RP2040_PLATFORM)
|
||||
WiFi.setTimeout(WIFI_RETRY_TIMEOUT);
|
||||
WiFi.beginNoBlock(wifi_ssid, wifi_pwd); // no reconnect() on this platform
|
||||
#else
|
||||
WiFi.disconnect();
|
||||
WiFi.reconnect();
|
||||
#endif
|
||||
last_wifi_reconnect_attempt = millis();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -3,14 +3,10 @@
|
||||
#include "../MyMesh.h"
|
||||
#include "target.h"
|
||||
#include <time.h>
|
||||
#ifdef WIFI_SSID
|
||||
#ifdef ENABLE_WIFI_INTERFACE
|
||||
#include <WiFi.h>
|
||||
#endif
|
||||
|
||||
#ifndef UI_TZ_OFFSET
|
||||
#define UI_TZ_OFFSET 0
|
||||
#endif
|
||||
|
||||
#ifndef AUTO_OFF_MILLIS
|
||||
#define AUTO_OFF_MILLIS 15000 // 15 seconds
|
||||
#endif
|
||||
@@ -24,6 +20,7 @@
|
||||
|
||||
#define LONG_PRESS_MILLIS 1200
|
||||
|
||||
// Used both for recent adverts and discovered nodes
|
||||
#ifndef UI_RECENT_LIST_SIZE
|
||||
#define UI_RECENT_LIST_SIZE 4
|
||||
#endif
|
||||
@@ -102,6 +99,9 @@ class HomeScreen : public UIScreen {
|
||||
#if UI_SENSORS_PAGE == 1
|
||||
SENSORS,
|
||||
#endif
|
||||
#if !(defined(UI_NO_DISCOVER_SCREEN) && (UI_NO_DISCOVER_SCREEN + 0 != 0))
|
||||
DISCOVERY,
|
||||
#endif
|
||||
#ifndef UI_NO_HIBERNATE
|
||||
SHUTDOWN,
|
||||
#endif
|
||||
@@ -115,7 +115,11 @@ class HomeScreen : public UIScreen {
|
||||
uint8_t _page;
|
||||
bool _shutdown_init;
|
||||
AdvertPath recent[UI_RECENT_LIST_SIZE];
|
||||
|
||||
#if !(defined(UI_NO_DISCOVER_SCREEN) && (UI_NO_DISCOVER_SCREEN + 0 != 0))
|
||||
DiscoveredNode discovered[UI_RECENT_LIST_SIZE];
|
||||
uint32_t discovery_req_time = 0;
|
||||
bool discovery_disp_names = true; // by default desplay names if available (removes SNR_O)
|
||||
#endif
|
||||
|
||||
void renderBatteryIndicator(DisplayDriver& display, uint16_t batteryMilliVolts) {
|
||||
// Convert millivolts to percentage
|
||||
@@ -248,8 +252,7 @@ public:
|
||||
#ifdef UI_SHOW_CLOCK
|
||||
display.setTextSize(3);
|
||||
uint32_t now = _rtc->getCurrentTime();
|
||||
int8_t tz = UI_TZ_OFFSET; // for now draw time from Santo Domingo ...
|
||||
now += (int32_t)tz * 3600;
|
||||
now += (int32_t)_node_prefs->tz_offset * 3600;
|
||||
DateTime dt (now);
|
||||
sprintf(tmp, "%02d:%02d", dt.hour(), dt.minute());
|
||||
display.drawTextCentered(display.width() / 2, 60, tmp);
|
||||
@@ -257,7 +260,7 @@ public:
|
||||
sprintf(tmp, "%02d/%02d/%d", dt.day(), dt.month(), dt.year());
|
||||
display.drawTextCentered(display.width() / 2, 80, tmp);
|
||||
#endif
|
||||
#ifdef WIFI_SSID
|
||||
#ifdef ENABLE_WIFI_INTERFACE
|
||||
IPAddress ip = WiFi.localIP();
|
||||
snprintf(tmp, sizeof(tmp), "IP: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
|
||||
display.setTextSize(1);
|
||||
@@ -461,6 +464,40 @@ public:
|
||||
if (sensors_scroll) sensors_scroll_offset = (sensors_scroll_offset+1)%sensors_nb;
|
||||
else sensors_scroll_offset = 0;
|
||||
#endif
|
||||
#if !(defined(UI_NO_DISCOVER_SCREEN) && (UI_NO_DISCOVER_SCREEN + 0 != 0))
|
||||
} else if (_page == HomePage::DISCOVERY) {
|
||||
int count = the_mesh.getDiscoveredNodes(discovered, UI_RECENT_LIST_SIZE);
|
||||
display.setColor(UIColor::primary_txt);
|
||||
int y = 20;
|
||||
for (int i = 0; i < count; i++, y += 11) {
|
||||
char name[32];
|
||||
auto a = &discovered[i];
|
||||
if ((a->name[0] == 0) || !discovery_disp_names) {
|
||||
mesh::Utils::toHex(name, a->pubkey_prefix, 4);
|
||||
} else {
|
||||
strncpy(name, a->name, 32);
|
||||
}
|
||||
char filtered_name[sizeof(name)];
|
||||
char snr_s[12];
|
||||
if (strlen(name) <= 8) { // display snr_o
|
||||
sprintf(snr_s, "%02.1f>%02.1f", a->snr_out, a->snr_in);
|
||||
} else {
|
||||
sprintf(snr_s, "%02.1f", a->snr_in);
|
||||
}
|
||||
int snr_width = display.getTextWidth(snr_s);
|
||||
int max_name_width = display.width() - snr_width - 1;
|
||||
display.translateUTF8ToBlocks(filtered_name, name, sizeof(filtered_name));
|
||||
display.drawTextEllipsized(0, y, max_name_width, filtered_name);
|
||||
display.setCursor(display.width() - snr_width - 1, y);
|
||||
display.print(snr_s);
|
||||
}
|
||||
if (millis() < discovery_req_time + 5000) {
|
||||
return 1000; // more frequent updates just after req
|
||||
} else if (count < UI_RECENT_LIST_SIZE -1) { // show only 5 sec after last disc
|
||||
y = 10 + 11 * UI_RECENT_LIST_SIZE;
|
||||
display.drawTextCentered(display.width() / 2, y, "discover: " PRESS_LABEL);
|
||||
}
|
||||
#endif
|
||||
#ifndef UI_NO_HIBERNATE
|
||||
} else if (_page == HomePage::SHUTDOWN) {
|
||||
display.setColor(UIColor::corp_blue);
|
||||
@@ -488,6 +525,11 @@ public:
|
||||
if (_page == HomePage::RECENT) {
|
||||
_task->showAlert("Recent adverts", 800);
|
||||
}
|
||||
#if !(defined(UI_NO_DISCOVER_SCREEN) && (UI_NO_DISCOVER_SCREEN + 0 != 0))
|
||||
if (_page == HomePage::DISCOVERY) {
|
||||
_task->showAlert("Repeater disc", 800);
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
if (c == KEY_ENTER && _page == HomePage::BLUETOOTH) {
|
||||
@@ -520,6 +562,19 @@ public:
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#if !(defined(UI_NO_DISCOVER_SCREEN) && (UI_NO_DISCOVER_SCREEN + 0 != 0))
|
||||
if (c == KEY_ENTER && _page == HomePage::DISCOVERY) {
|
||||
if (millis() > discovery_req_time + 5000) { // rate limiter
|
||||
the_mesh.requestRepeatersDiscovery();
|
||||
discovery_req_time = millis();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (c == KEY_SELECT && _page == HomePage::DISCOVERY) {
|
||||
discovery_disp_names = !discovery_disp_names;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifndef UI_NO_HIBERNATE
|
||||
if (c == KEY_ENTER && _page == HomePage::SHUTDOWN) {
|
||||
_shutdown_init = true; // need to wait for button to be released
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "target.h"
|
||||
#include "u8g2_icons.h"
|
||||
|
||||
#ifdef WIFI_SSID
|
||||
#ifdef ENABLE_WIFI_INTERFACE
|
||||
#include <WiFi.h>
|
||||
#endif
|
||||
|
||||
@@ -173,7 +173,7 @@ public:
|
||||
display.setCursor(0, 19);
|
||||
display.print(tmp);
|
||||
|
||||
#ifdef WIFI_SSID
|
||||
#ifdef ENABLE_WIFI_INTERFACE
|
||||
IPAddress ip = WiFi.localIP();
|
||||
snprintf(tmp, sizeof(tmp), "IP: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
|
||||
display.setTextSize(1);
|
||||
|
||||
@@ -140,6 +140,13 @@ void setup() {
|
||||
void loop() {
|
||||
// Handle Serial CLI
|
||||
int len = strlen(command);
|
||||
// `command` must stay NUL-terminated within its bounds. If it ever isn't,
|
||||
// strlen() above can return >= sizeof(command) and the loop below would then
|
||||
// index past the buffer, so clamp defensively.
|
||||
if (len >= (int)sizeof(command)) {
|
||||
command[0] = 0;
|
||||
len = 0;
|
||||
}
|
||||
while (Serial.available() && len < sizeof(command)-1) {
|
||||
char c = Serial.read();
|
||||
if (c != '\n') {
|
||||
@@ -149,8 +156,9 @@ void loop() {
|
||||
}
|
||||
if (c == '\r') break;
|
||||
}
|
||||
if (len == sizeof(command)-1) { // command buffer full
|
||||
command[sizeof(command)-1] = '\r';
|
||||
if (len == sizeof(command)-1) { // buffer full: treat as a completed line
|
||||
command[sizeof(command)-2] = '\r'; // place end-of-line marker inside the buffer
|
||||
command[sizeof(command)-1] = 0; // keep the buffer NUL-terminated
|
||||
}
|
||||
|
||||
if (len > 0 && command[len - 1] == '\r') { // received complete line
|
||||
|
||||
@@ -115,6 +115,13 @@ void setup() {
|
||||
|
||||
void loop() {
|
||||
int len = strlen(command);
|
||||
// `command` must stay NUL-terminated within its bounds. If it ever isn't,
|
||||
// strlen() above can return >= sizeof(command) and the loop below would then
|
||||
// index past the buffer, so clamp defensively.
|
||||
if (len >= (int)sizeof(command)) {
|
||||
command[0] = 0;
|
||||
len = 0;
|
||||
}
|
||||
while (Serial.available() && len < sizeof(command)-1) {
|
||||
char c = Serial.read();
|
||||
if (c != '\n') {
|
||||
@@ -123,8 +130,9 @@ void loop() {
|
||||
}
|
||||
Serial.print(c);
|
||||
}
|
||||
if (len == sizeof(command)-1) { // command buffer full
|
||||
command[sizeof(command)-1] = '\r';
|
||||
if (len == sizeof(command)-1) { // buffer full: treat as a completed line
|
||||
command[sizeof(command)-2] = '\r'; // place end-of-line marker inside the buffer
|
||||
command[sizeof(command)-1] = 0; // keep the buffer NUL-terminated
|
||||
}
|
||||
|
||||
if (len > 0 && command[len - 1] == '\r') { // received complete line
|
||||
|
||||
@@ -530,6 +530,13 @@ public:
|
||||
BaseChatMesh::loop();
|
||||
|
||||
int len = strlen(command);
|
||||
// `command` must stay NUL-terminated within its bounds. If it ever isn't,
|
||||
// strlen() above can return >= sizeof(command) and the loop below would then
|
||||
// index past the buffer, so clamp defensively.
|
||||
if (len >= (int)sizeof(command)) {
|
||||
command[0] = 0;
|
||||
len = 0;
|
||||
}
|
||||
while (Serial.available() && len < sizeof(command)-1) {
|
||||
char c = Serial.read();
|
||||
if (c != '\n') {
|
||||
@@ -538,8 +545,9 @@ public:
|
||||
}
|
||||
Serial.print(c);
|
||||
}
|
||||
if (len == sizeof(command)-1) { // command buffer full
|
||||
command[sizeof(command)-1] = '\r';
|
||||
if (len == sizeof(command)-1) { // buffer full: treat as a completed line
|
||||
command[sizeof(command)-2] = '\r'; // place end-of-line marker inside the buffer
|
||||
command[sizeof(command)-1] = 0; // keep the buffer NUL-terminated
|
||||
}
|
||||
|
||||
if (len > 0 && command[len - 1] == '\r') { // received complete line
|
||||
|
||||
@@ -122,6 +122,13 @@ void setup() {
|
||||
|
||||
void loop() {
|
||||
int len = strlen(command);
|
||||
// `command` must stay NUL-terminated within its bounds. If it ever isn't,
|
||||
// strlen() above can return >= sizeof(command) and the loop below would then
|
||||
// index past the buffer, so clamp defensively.
|
||||
if (len >= (int)sizeof(command)) {
|
||||
command[0] = 0;
|
||||
len = 0;
|
||||
}
|
||||
while (Serial.available() && len < sizeof(command)-1) {
|
||||
char c = Serial.read();
|
||||
if (c != '\n') {
|
||||
@@ -130,8 +137,9 @@ void loop() {
|
||||
}
|
||||
Serial.print(c);
|
||||
}
|
||||
if (len == sizeof(command)-1) { // command buffer full
|
||||
command[sizeof(command)-1] = '\r';
|
||||
if (len == sizeof(command)-1) { // buffer full: treat as a completed line
|
||||
command[sizeof(command)-2] = '\r'; // place end-of-line marker inside the buffer
|
||||
command[sizeof(command)-1] = 0; // keep the buffer NUL-terminated
|
||||
}
|
||||
|
||||
if (len > 0 && command[len - 1] == '\r') { // received complete line
|
||||
|
||||
+1
-1
@@ -96,7 +96,7 @@ platform_packages =
|
||||
; use internal fork that includes patch to ble stack to prevent firmware lockup during rapid connect/disconnect
|
||||
; https://github.com/meshcore-dev/MeshCore/pull/1177
|
||||
; https://github.com/meshcore-dev/MeshCore/pull/1295
|
||||
framework-arduinoadafruitnrf52 @ https://github.com/meshcore-dev/Adafruit_nRF52_Arduino#d541301
|
||||
framework-arduinoadafruitnrf52 @ https://github.com/meshcore-dev/Adafruit_nRF52_Arduino#d541301665b40959682252911e57b11df3ee651a
|
||||
platformio/toolchain-gccarmnoneeabi@^1.140201.0
|
||||
extra_scripts = create-uf2.py
|
||||
build_flags = ${arduino_base.build_flags}
|
||||
|
||||
@@ -75,8 +75,10 @@ public:
|
||||
virtual bool otaFromManifest(const char* current_ver, bool dry_run, char reply[]) { return false; }
|
||||
|
||||
// Power management interface (boards with power management override these)
|
||||
virtual bool isPwrMgtInitialised() const { return false; }
|
||||
virtual bool isExternalPowered() { return false; }
|
||||
virtual uint16_t getBootVoltage() { return 0; }
|
||||
virtual bool getWakeLpcompSupported() const { return false; }
|
||||
virtual uint32_t getResetReason() const { return 0; }
|
||||
virtual const char* getResetReasonString(uint32_t reason) { return "Not available"; }
|
||||
virtual uint8_t getShutdownReason() const { return 0; }
|
||||
|
||||
@@ -67,8 +67,14 @@ bool CommonRadioPrefs::handleCommand(const char* command, uint32_t sender_timest
|
||||
return true;
|
||||
}
|
||||
if (memcmp(command, "set af ", 7) == 0) {
|
||||
setAirtimeFactor(atof(&command[7]));
|
||||
strcpy(reply, "OK");
|
||||
char* end;
|
||||
float af = strtof(&command[7], &end);
|
||||
if (end == &command[7] || af < 0 || af > 9) {
|
||||
strcpy(reply, "ERROR: af must be 0-9");
|
||||
} else {
|
||||
setAirtimeFactor(af);
|
||||
strcpy(reply, "OK");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ int ConfigSerializer::Context::readNext() {
|
||||
if (c == ',') { rd_mode = EXPECT_KEY; return TOK_WHITESPACE; }
|
||||
case EXPECT_KEY:
|
||||
read_key:
|
||||
if (rd_len == 0 && c == '}') { rd_mode = EXPECT_COMMA_OR_KEY_OR_CLOSE; return TOK_END_OBJ; } // empty object after whitespace
|
||||
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 &&
|
||||
|
||||
+27
-20
@@ -47,7 +47,8 @@ static void __attribute__((constructor(101))) nrf52_early_reset_capture() {
|
||||
g_nrf52_shutdown_reason = NRF_POWER->GPREGRET2;
|
||||
}
|
||||
|
||||
void NRF52Board::initPowerMgr() {
|
||||
void NRF52Board::pwrmgtInit() {
|
||||
if (pwrmgt_initialised) return;
|
||||
// Copy early-captured register values
|
||||
reset_reason = g_nrf52_reset_reason;
|
||||
shutdown_reason = g_nrf52_shutdown_reason;
|
||||
@@ -74,6 +75,7 @@ void NRF52Board::initPowerMgr() {
|
||||
MESH_DEBUG_PRINTLN("PWRMGT: Reset = %s (0x%lX)",
|
||||
getResetReasonString(reset_reason), (unsigned long)reset_reason);
|
||||
}
|
||||
pwrmgt_initialised = true;
|
||||
}
|
||||
|
||||
const char* NRF52Board::getResetReasonString(uint32_t reason) {
|
||||
@@ -98,6 +100,7 @@ const char* NRF52Board::getResetReasonString(uint32_t reason) {
|
||||
|
||||
const char* NRF52Board::getShutdownReasonString(uint8_t reason) {
|
||||
switch (reason) {
|
||||
case SHUTDOWN_REASON_NONE: return "None";
|
||||
case SHUTDOWN_REASON_LOW_VOLTAGE: return "Low Voltage";
|
||||
case SHUTDOWN_REASON_USER: return "User Request";
|
||||
case SHUTDOWN_REASON_BOOT_PROTECT: return "Boot Protection";
|
||||
@@ -106,7 +109,7 @@ const char* NRF52Board::getShutdownReasonString(uint8_t reason) {
|
||||
}
|
||||
|
||||
bool NRF52Board::checkBootVoltage(const PowerMgtConfig* config) {
|
||||
initPowerMgr();
|
||||
pwrmgtInit();
|
||||
|
||||
// Read boot voltage
|
||||
boot_voltage_mv = getBattMilliVolts();
|
||||
@@ -173,24 +176,30 @@ void NRF52Board::enterSystemOff(uint8_t reason) {
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
|
||||
void NRF52Board::configureVoltageWake(uint8_t ain_channel, uint8_t refsel) {
|
||||
void NRF52Board::configureVoltageWake(uint8_t ain_channel, uint8_t lpcomp_refsel) {
|
||||
pwrmgtWakeArmVbus();
|
||||
if (!isPwrMgtInitialised()) {
|
||||
MESH_DEBUG_PRINTLN("PWRMGT: LPCOMP wake not armed. Reason: Power Management not initialised");
|
||||
return;
|
||||
}
|
||||
if (!getWakeLpcompSupported()) {
|
||||
MESH_DEBUG_PRINTLN("PWRMGT: LPCOMP wake not armed. Reason: LPCOMP unsupported on variant");
|
||||
return;
|
||||
}
|
||||
// LPCOMP is not managed by SoftDevice - direct register access required
|
||||
// Halt and disable before reconfiguration
|
||||
NRF_LPCOMP->TASKS_STOP = 1;
|
||||
NRF_LPCOMP->ENABLE = LPCOMP_ENABLE_ENABLE_Disabled;
|
||||
|
||||
// Select analog input (AIN0-7 maps to PSEL 0-7)
|
||||
NRF_LPCOMP->PSEL = ((uint32_t)ain_channel << LPCOMP_PSEL_PSEL_Pos) & LPCOMP_PSEL_PSEL_Msk;
|
||||
NRF_LPCOMP->PSEL = ((uint32_t)PWRMGT_LPCOMP_AIN << LPCOMP_PSEL_PSEL_Pos) & LPCOMP_PSEL_PSEL_Msk;
|
||||
|
||||
// Reference: REFSEL (0-6=1/8..7/8, 7=ARef, 8-15=1/16..15/16)
|
||||
NRF_LPCOMP->REFSEL = ((uint32_t)refsel << LPCOMP_REFSEL_REFSEL_Pos) & LPCOMP_REFSEL_REFSEL_Msk;
|
||||
NRF_LPCOMP->REFSEL = ((uint32_t)lpcomp_refsel << LPCOMP_REFSEL_REFSEL_Pos) & LPCOMP_REFSEL_REFSEL_Msk;
|
||||
|
||||
// Detect UP events (voltage rises above threshold for battery recovery)
|
||||
NRF_LPCOMP->ANADETECT = LPCOMP_ANADETECT_ANADETECT_Up;
|
||||
|
||||
// Enable 50mV hysteresis for noise immunity
|
||||
NRF_LPCOMP->HYST = LPCOMP_HYST_HYST_Hyst50mV;
|
||||
|
||||
// Clear stale events/interrupts before enabling wake
|
||||
NRF_LPCOMP->EVENTS_READY = 0;
|
||||
NRF_LPCOMP->EVENTS_DOWN = 0;
|
||||
@@ -204,23 +213,22 @@ void NRF52Board::configureVoltageWake(uint8_t ain_channel, uint8_t refsel) {
|
||||
NRF_LPCOMP->ENABLE = LPCOMP_ENABLE_ENABLE_Enabled;
|
||||
NRF_LPCOMP->TASKS_START = 1;
|
||||
|
||||
// Wait for comparator to settle before entering SYSTEMOFF
|
||||
// Wait for comparator to settle
|
||||
for (uint8_t i = 0; i < 20 && !NRF_LPCOMP->EVENTS_READY; i++) {
|
||||
delayMicroseconds(50);
|
||||
}
|
||||
|
||||
if (refsel == 7) {
|
||||
MESH_DEBUG_PRINTLN("PWRMGT: LPCOMP wake configured (AIN%d, ref=ARef)", ain_channel);
|
||||
} else if (refsel <= 6) {
|
||||
MESH_DEBUG_PRINTLN("PWRMGT: LPCOMP wake configured (AIN%d, ref=%d/8 VDD)",
|
||||
ain_channel, refsel + 1);
|
||||
if (lpcomp_refsel == 7) {
|
||||
MESH_DEBUG_PRINTLN("PWRMGT: LPCOMP wake armed (AIN%d, ref=ARef)", PWRMGT_LPCOMP_AIN);
|
||||
} else if (lpcomp_refsel <= 6) {
|
||||
MESH_DEBUG_PRINTLN("PWRMGT: LPCOMP wake armed (AIN%d, ref=%d/8 VDD)", PWRMGT_LPCOMP_AIN, lpcomp_refsel + 1);
|
||||
} else {
|
||||
uint8_t ref_num = (uint8_t)((refsel - 8) * 2 + 1);
|
||||
MESH_DEBUG_PRINTLN("PWRMGT: LPCOMP wake configured (AIN%d, ref=%d/16 VDD)",
|
||||
ain_channel, ref_num);
|
||||
uint8_t ref_num = (uint8_t)((lpcomp_refsel - 8) * 2 + 1);
|
||||
MESH_DEBUG_PRINTLN("PWRMGT: LPCOMP wake armed (AIN%d, ref=%d/16 VDD)", PWRMGT_LPCOMP_AIN, ref_num);
|
||||
}
|
||||
}
|
||||
|
||||
// Configure VBUS (USB power) wake alongside LPCOMP
|
||||
void NRF52Board::pwrmgtWakeArmVbus() {
|
||||
uint8_t sd_enabled = 0;
|
||||
sd_softdevice_is_enabled(&sd_enabled);
|
||||
if (sd_enabled) {
|
||||
@@ -229,8 +237,7 @@ void NRF52Board::configureVoltageWake(uint8_t ain_channel, uint8_t refsel) {
|
||||
NRF_POWER->EVENTS_USBDETECTED = 0;
|
||||
NRF_POWER->INTENSET = POWER_INTENSET_USBDETECTED_Msk;
|
||||
}
|
||||
|
||||
MESH_DEBUG_PRINTLN("PWRMGT: VBUS wake configured");
|
||||
MESH_DEBUG_PRINTLN("PWRMGT: VBUS wake armed");
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -26,15 +26,15 @@ struct PowerMgtConfig {
|
||||
#endif
|
||||
|
||||
class NRF52Board : public mesh::MainBoard {
|
||||
#ifdef NRF52_POWER_MANAGEMENT
|
||||
void initPowerMgr();
|
||||
#endif
|
||||
private:
|
||||
bool pwrmgt_initialised = false;
|
||||
|
||||
protected:
|
||||
uint8_t startup_reason;
|
||||
char *ota_name;
|
||||
|
||||
#ifdef NRF52_POWER_MANAGEMENT
|
||||
void pwrmgtInit();
|
||||
uint32_t reset_reason; // RESETREAS register value
|
||||
uint8_t shutdown_reason; // GPREGRET value (why we entered last SYSTEMOFF)
|
||||
uint16_t boot_voltage_mv; // Battery voltage at boot (millivolts)
|
||||
@@ -42,6 +42,7 @@ protected:
|
||||
bool checkBootVoltage(const PowerMgtConfig* config);
|
||||
void enterSystemOff(uint8_t reason);
|
||||
void configureVoltageWake(uint8_t ain_channel, uint8_t refsel);
|
||||
void pwrmgtWakeArmVbus();
|
||||
virtual void initiateShutdown(uint8_t reason);
|
||||
#endif
|
||||
|
||||
@@ -66,6 +67,10 @@ public:
|
||||
uint8_t getShutdownReason() const override { return shutdown_reason; }
|
||||
const char* getResetReasonString(uint32_t reason) override;
|
||||
const char* getShutdownReasonString(uint8_t reason) override;
|
||||
bool isPwrMgtInitialised() const override { return pwrmgt_initialised; }
|
||||
#ifdef PWRMGT_LPCOMP_AIN
|
||||
bool getWakeLpcompSupported() const override { return true; }
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ public:
|
||||
virtual const char* getSettingValue(int i) const { return NULL; }
|
||||
virtual bool setSettingValue(const char* name, const char* value) { return false; }
|
||||
virtual LocationProvider* getLocationProvider() { return NULL; }
|
||||
virtual bool isGPSDetected() const { return false; }
|
||||
|
||||
// Helper functions to manage setting by keys (useful in many places ...)
|
||||
const char* getSettingByKey(const char* key) {
|
||||
|
||||
@@ -44,7 +44,7 @@ mesh::Packet* PacketQueue::get(uint32_t now) {
|
||||
}
|
||||
|
||||
mesh::Packet* PacketQueue::removeByIdx(int i) {
|
||||
if (i >= _num) return NULL; // invalid index
|
||||
if (i < 0 || i >= _num) return NULL; // invalid index
|
||||
|
||||
mesh::Packet* item = _table[i];
|
||||
_num--;
|
||||
@@ -80,10 +80,14 @@ mesh::Packet* StaticPoolPacketManager::allocNew() {
|
||||
}
|
||||
|
||||
void StaticPoolPacketManager::free(mesh::Packet* packet) {
|
||||
unused.add(packet, 0, 0);
|
||||
if (packet == NULL) return;
|
||||
if (!unused.add(packet, 0, 0)) {
|
||||
MESH_DEBUG_PRINTLN("free: unused queue full, possible double-free detected");
|
||||
}
|
||||
}
|
||||
|
||||
void StaticPoolPacketManager::queueOutbound(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for) {
|
||||
if (packet == NULL) return;
|
||||
if (!send_queue.add(packet, priority, scheduled_for)) {
|
||||
MESH_DEBUG_PRINTLN("queueOutbound: send queue full, dropping packet");
|
||||
free(packet);
|
||||
@@ -115,6 +119,7 @@ mesh::Packet* StaticPoolPacketManager::removeOutboundByIdx(int i) {
|
||||
}
|
||||
|
||||
void StaticPoolPacketManager::queueInbound(mesh::Packet* packet, uint32_t scheduled_for) {
|
||||
if (packet == NULL) return;
|
||||
if (!rx_queue.add(packet, 0, scheduled_for)) {
|
||||
MESH_DEBUG_PRINTLN("queueInbound: rx queue full, dropping packet");
|
||||
free(packet);
|
||||
|
||||
@@ -14,7 +14,10 @@ public:
|
||||
bool add(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for);
|
||||
int count() const { return _num; }
|
||||
int countBefore(uint32_t now) const;
|
||||
mesh::Packet* itemAt(int i) const { return _table[i]; }
|
||||
mesh::Packet* itemAt(int i) const {
|
||||
if (i < 0 || i >= _num) return NULL;
|
||||
return _table[i];
|
||||
}
|
||||
mesh::Packet* removeByIdx(int i);
|
||||
};
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ class CustomLR2021 : public LR2021 {
|
||||
|
||||
int16_t startReceive() override {
|
||||
// include the PREAMBLE_DETECTED irq bit in reported flags
|
||||
return LR2021::startReceive(RADIOLIB_LR2021_RX_TIMEOUT_INF, RADIOLIB_IRQ_RX_DEFAULT_FLAGS | (1UL << RADIOLIB_LR2021_IRQ_PREAMBLE_DETECTED), RADIOLIB_IRQ_RX_DEFAULT_MASK, 0);
|
||||
return LR2021::startReceive(RADIOLIB_LR2021_RX_TIMEOUT_INF, RADIOLIB_IRQ_RX_DEFAULT_FLAGS | (1UL << RADIOLIB_IRQ_PREAMBLE_DETECTED), RADIOLIB_IRQ_RX_DEFAULT_MASK, 0);
|
||||
}
|
||||
|
||||
bool isReceiving() {
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
// only built when the env enables the core BLE stack (build_as_lib.py globs this dir)
|
||||
#ifdef PIO_FRAMEWORK_ARDUINO_ENABLE_BLUETOOTH
|
||||
#include "SerialBLEInterface.h"
|
||||
#include <BluetoothLock.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
// Nordic UART 6E4000xx-B5A3-F393-E0A9-E50E24DCCA9E, as raw bytes: the core lib's string
|
||||
// parser uses sscanf("%llx"), which newlib-nano on the RP2040 doesn't support (yields all zeros)
|
||||
#define NUS_UUID(n) { 0x6E, 0x40, 0x00, n, 0xB5, 0xA3, 0xF3, 0x93, 0xE0, 0xA9, 0xE5, 0x0E, 0x24, 0xDC, 0xCA, 0x9E }
|
||||
static const uint8_t SERVICE_UUID[16] = NUS_UUID(0x01);
|
||||
static const uint8_t CHARACTERISTIC_UUID_RX[16] = NUS_UUID(0x02);
|
||||
static const uint8_t CHARACTERISTIC_UUID_TX[16] = NUS_UUID(0x03);
|
||||
|
||||
// The BLE core lib's own setValue()/notify path reallocs the value buffer on every call,
|
||||
// so a second frame queued before the radio drained the first would corrupt it. We keep our
|
||||
// own frame queue and drive att_server_notify() from the can-send-now callback instead.
|
||||
|
||||
SerialBLEInterface::SerialBLEInterface()
|
||||
: BLEService(BLEUUID(SERVICE_UUID)),
|
||||
_rx(BLEUUID(CHARACTERISTIC_UUID_RX), BLEWrite, nullptr, ATT_SECURITY_AUTHENTICATED, ATT_SECURITY_AUTHENTICATED),
|
||||
_tx(BLEUUID(CHARACTERISTIC_UUID_TX), BLERead | BLENotify, nullptr, ATT_SECURITY_AUTHENTICATED, ATT_SECURITY_AUTHENTICATED)
|
||||
{
|
||||
_isEnabled = false;
|
||||
_tx_pending = false;
|
||||
send_queue_len = 0;
|
||||
recv_queue_len = 0;
|
||||
memset(&_can_send, 0, sizeof(_can_send));
|
||||
_rx.setCallbacks(this);
|
||||
addCharacteristic(&_rx);
|
||||
addCharacteristic(&_tx);
|
||||
}
|
||||
|
||||
void SerialBLEInterface::begin(const char* prefix, char* name, uint32_t pin_code) {
|
||||
// JustWorks here only makes the server request pairing on connect; caps are overridden below
|
||||
BLE.setSecurity(BLESecurityJustWorks);
|
||||
// adv data carries the 128-bit service UUID, leaving room for only 8 name chars;
|
||||
// the full name goes in the scan response
|
||||
BLE.begin(prefix);
|
||||
|
||||
if (strcmp(name, "@@MAC") == 0) {
|
||||
bd_addr_t a;
|
||||
gap_local_bd_addr(a);
|
||||
sprintf(name, "%02X%02X%02X%02X%02X%02X", a[0], a[1], a[2], a[3], a[4], a[5]); // modify (IN-OUT param)
|
||||
}
|
||||
char dev_name[32+16];
|
||||
snprintf(dev_name, sizeof(dev_name), "%s%s", prefix, name);
|
||||
|
||||
BLE.server()->setName(dev_name); // GAP device name characteristic
|
||||
BLE.server()->addService(this);
|
||||
BLE.server()->setCallbacks(this);
|
||||
|
||||
// static passkey pairing with MITM protection, matching the esp32/nrf52 interfaces
|
||||
sm_set_io_capabilities(IO_CAPABILITY_DISPLAY_ONLY);
|
||||
sm_set_authentication_requirements(SM_AUTHREQ_MITM_PROTECTION | SM_AUTHREQ_BONDING);
|
||||
sm_use_fixed_passkey_in_display_role(pin_code);
|
||||
|
||||
size_t n = strlen(dev_name);
|
||||
if (n > sizeof(_scan_rsp) - 2) n = sizeof(_scan_rsp) - 2;
|
||||
_scan_rsp[0] = n + 1;
|
||||
_scan_rsp[1] = BLUETOOTH_DATA_TYPE_COMPLETE_LOCAL_NAME;
|
||||
memcpy(&_scan_rsp[2], dev_name, n);
|
||||
gap_scan_response_set_data(n + 2, _scan_rsp);
|
||||
|
||||
BLE_DEBUG_PRINTLN("begin: name=%s", dev_name);
|
||||
}
|
||||
|
||||
void SerialBLEInterface::clearBuffers() {
|
||||
send_queue_len = 0;
|
||||
recv_queue_len = 0;
|
||||
_tx_pending = false; // a stale registration just fires into an empty queue; BTstack ignores double-adds
|
||||
}
|
||||
|
||||
void SerialBLEInterface::onConnect(BLEServer* s) {
|
||||
BLE_DEBUG_PRINTLN("connected handle=0x%04X", _tx.conHandle());
|
||||
clearBuffers();
|
||||
}
|
||||
|
||||
void SerialBLEInterface::onDisconnect(BLEServer* s) {
|
||||
BLE_DEBUG_PRINTLN("disconnected");
|
||||
clearBuffers(); // BTstack re-enables advertising on its own
|
||||
}
|
||||
|
||||
void SerialBLEInterface::onWrite(BLECharacteristic* c) {
|
||||
if (c != &_rx) return;
|
||||
size_t len = _rx.valueLen();
|
||||
if (len == 0 || len > MAX_FRAME_SIZE) {
|
||||
BLE_DEBUG_PRINTLN("onWrite: bad frame len=%u", (unsigned)len);
|
||||
return;
|
||||
}
|
||||
if (recv_queue_len >= FRAME_QUEUE_SIZE) {
|
||||
BLE_DEBUG_PRINTLN("onWrite: recv queue full, dropping frame");
|
||||
return;
|
||||
}
|
||||
recv_queue[recv_queue_len].len = len;
|
||||
memcpy(recv_queue[recv_queue_len].buf, _rx.valueData(), len);
|
||||
recv_queue_len++;
|
||||
}
|
||||
|
||||
// caller holds the BT lock (or is in the BT context)
|
||||
void SerialBLEInterface::kickSend() {
|
||||
if (_tx_pending) return;
|
||||
_tx_pending = true;
|
||||
_can_send.callback = onCanSend;
|
||||
_can_send.context = this;
|
||||
if (att_server_register_can_send_now_callback(&_can_send, _tx.conHandle()) != 0) {
|
||||
_tx_pending = false;
|
||||
}
|
||||
}
|
||||
|
||||
// BT context: one notification per can-send-now, re-arm while frames remain
|
||||
void SerialBLEInterface::sendNext() {
|
||||
_tx_pending = false;
|
||||
if (send_queue_len == 0) return;
|
||||
if (!isConnected()) {
|
||||
BLE_DEBUG_PRINTLN("sendNext: not connected, clearing send queue");
|
||||
send_queue_len = 0;
|
||||
return;
|
||||
}
|
||||
uint16_t h = _tx.conHandle();
|
||||
Frame& f = send_queue[0];
|
||||
uint16_t mtu = att_server_get_mtu(h);
|
||||
if (f.len + 3 > mtu) {
|
||||
// att would silently truncate; drop instead (client must negotiate MTU >= MAX_FRAME_SIZE+3)
|
||||
BLE_DEBUG_PRINTLN("sendNext: frame len=%u exceeds mtu=%u, dropping", f.len, mtu);
|
||||
} else {
|
||||
uint8_t err = att_server_notify(h, _tx.valueHandle(), f.buf, f.len);
|
||||
if (err == BTSTACK_ACL_BUFFERS_FULL) {
|
||||
kickSend();
|
||||
return;
|
||||
}
|
||||
if (err) {
|
||||
BLE_DEBUG_PRINTLN("sendNext: notify failed err=%u, dropping", err);
|
||||
} else {
|
||||
BLE_DEBUG_PRINTLN("writeBytes: sz=%u, hdr=%u", f.len, f.buf[0]);
|
||||
}
|
||||
}
|
||||
send_queue_len--;
|
||||
memmove(&send_queue[0], &send_queue[1], send_queue_len * sizeof(Frame));
|
||||
if (send_queue_len > 0) kickSend();
|
||||
}
|
||||
|
||||
void SerialBLEInterface::enable() {
|
||||
if (_isEnabled) return;
|
||||
_isEnabled = true;
|
||||
clearBuffers();
|
||||
BLE.startAdvertising(true);
|
||||
}
|
||||
|
||||
void SerialBLEInterface::disconnect() {
|
||||
uint16_t h = _tx.conHandle();
|
||||
if (h) gap_disconnect(h);
|
||||
}
|
||||
|
||||
void SerialBLEInterface::disable() {
|
||||
_isEnabled = false;
|
||||
BLE_DEBUG_PRINTLN("disable");
|
||||
disconnect();
|
||||
BLE.stopAdvertising();
|
||||
}
|
||||
|
||||
bool SerialBLEInterface::isConnected() const {
|
||||
// notifications can only be enabled once the link is authenticated (CCCD inherits the perms)
|
||||
return _isEnabled && _tx.conHandle() != 0 && _tx.notifyEnabled();
|
||||
}
|
||||
|
||||
bool SerialBLEInterface::isWriteBusy() const {
|
||||
return send_queue_len >= (FRAME_QUEUE_SIZE * 2 / 3);
|
||||
}
|
||||
|
||||
size_t SerialBLEInterface::writeFrame(const uint8_t src[], size_t len) {
|
||||
if (len == 0 || len > MAX_FRAME_SIZE) {
|
||||
BLE_DEBUG_PRINTLN("writeFrame(), frame too big, len=%u", (unsigned)len);
|
||||
return 0;
|
||||
}
|
||||
if (!isConnected()) return 0;
|
||||
|
||||
BluetoothLock lock;
|
||||
if (send_queue_len >= FRAME_QUEUE_SIZE) {
|
||||
BLE_DEBUG_PRINTLN("writeFrame(), send_queue is full!");
|
||||
return 0;
|
||||
}
|
||||
send_queue[send_queue_len].len = len;
|
||||
memcpy(send_queue[send_queue_len].buf, src, len);
|
||||
send_queue_len++;
|
||||
kickSend();
|
||||
return len;
|
||||
}
|
||||
|
||||
size_t SerialBLEInterface::checkRecvFrame(uint8_t dest[]) {
|
||||
BluetoothLock lock;
|
||||
if (recv_queue_len == 0) return 0;
|
||||
|
||||
size_t len = recv_queue[0].len;
|
||||
memcpy(dest, recv_queue[0].buf, len);
|
||||
recv_queue_len--;
|
||||
memmove(&recv_queue[0], &recv_queue[1], recv_queue_len * sizeof(Frame));
|
||||
BLE_DEBUG_PRINTLN("readBytes: sz=%u, hdr=%u", (unsigned)len, dest[0]);
|
||||
return len;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
|
||||
#include "../BaseSerialInterface.h"
|
||||
#include <BLE.h>
|
||||
#include <btstack.h>
|
||||
|
||||
// Nordic UART service over the arduino-pico BLE library (BTstack on the CYW43).
|
||||
// Build with -D PIO_FRAMEWORK_ARDUINO_ENABLE_BLUETOOTH so the core links liblwip-bt.
|
||||
class SerialBLEInterface : public BaseSerialInterface, BLEService, BLEServerCallbacks, BLECharacteristicCallbacks {
|
||||
// subclass only to reach the protected connection/notify state
|
||||
struct Characteristic : public BLECharacteristic {
|
||||
using BLECharacteristic::BLECharacteristic;
|
||||
uint16_t valueHandle() const { return _valueHandle; }
|
||||
uint16_t conHandle() const { return con_handle; }
|
||||
bool notifyEnabled() const { return _notificationEnabled; }
|
||||
};
|
||||
|
||||
struct Frame {
|
||||
uint8_t len;
|
||||
uint8_t buf[MAX_FRAME_SIZE];
|
||||
};
|
||||
|
||||
#define FRAME_QUEUE_SIZE 8
|
||||
|
||||
Characteristic _rx;
|
||||
Characteristic _tx;
|
||||
bool _isEnabled;
|
||||
bool _tx_pending; // a can-send-now callback is registered
|
||||
btstack_context_callback_registration_t _can_send;
|
||||
uint8_t _scan_rsp[31]; // complete local name; BTstack keeps the pointer
|
||||
|
||||
uint8_t send_queue_len;
|
||||
Frame send_queue[FRAME_QUEUE_SIZE];
|
||||
uint8_t recv_queue_len;
|
||||
Frame recv_queue[FRAME_QUEUE_SIZE];
|
||||
|
||||
void clearBuffers();
|
||||
void kickSend();
|
||||
void sendNext();
|
||||
static void onCanSend(void* ctx) { ((SerialBLEInterface*)ctx)->sendNext(); }
|
||||
|
||||
// BLE library callbacks (run in the BT context)
|
||||
void onWrite(BLECharacteristic* c) override;
|
||||
void onConnect(BLEServer* s) override;
|
||||
void onDisconnect(BLEServer* s) override;
|
||||
|
||||
public:
|
||||
SerialBLEInterface();
|
||||
|
||||
/**
|
||||
* init the BLE interface.
|
||||
* @param prefix a prefix for the device name
|
||||
* @param name IN/OUT - a name for the device (combined with prefix). If "@@MAC", is modified and returned
|
||||
* @param pin_code the BLE security pin
|
||||
*/
|
||||
void begin(const char* prefix, char* name, uint32_t pin_code);
|
||||
|
||||
void disconnect();
|
||||
void enable() override;
|
||||
void disable() override;
|
||||
bool isEnabled() const override { return _isEnabled; }
|
||||
bool isConnected() const override;
|
||||
bool isWriteBusy() const override;
|
||||
size_t writeFrame(const uint8_t src[], size_t len) override;
|
||||
size_t checkRecvFrame(uint8_t dest[]) override;
|
||||
};
|
||||
|
||||
#if BLE_DEBUG_LOGGING && ARDUINO
|
||||
#include <Arduino.h>
|
||||
#define BLE_DEBUG_PRINT(F, ...) Serial.printf("BLE: " F, ##__VA_ARGS__)
|
||||
#define BLE_DEBUG_PRINTLN(F, ...) Serial.printf("BLE: " F "\n", ##__VA_ARGS__)
|
||||
#else
|
||||
#define BLE_DEBUG_PRINT(...) {}
|
||||
#define BLE_DEBUG_PRINTLN(...) {}
|
||||
#endif
|
||||
@@ -38,6 +38,7 @@ public:
|
||||
#if ENV_INCLUDE_GPS
|
||||
EnvironmentSensorManager(LocationProvider &location): _location(&location){};
|
||||
LocationProvider* getLocationProvider() { return _location; }
|
||||
bool isGPSDetected() const override { return gps_detected; }
|
||||
#else
|
||||
EnvironmentSensorManager(){};
|
||||
#endif
|
||||
|
||||
File renamed without changes.
File renamed without changes.
@@ -193,6 +193,47 @@ TEST(ConfigSerializer, LoadSerial_IgnoreUnknowns) {
|
||||
EXPECT_TRUE(match);
|
||||
}
|
||||
|
||||
class TestNested : public ConfigSerializer {
|
||||
class Inner : public ConfigSerializer {
|
||||
protected:
|
||||
void structure() override { } // no properties, so it writes as '{}'
|
||||
};
|
||||
Inner inner;
|
||||
protected:
|
||||
void structure() override {
|
||||
def("age", age);
|
||||
def("inner", inner);
|
||||
def("name", name, sizeof(name)); // comes *after* the empty sub-object
|
||||
}
|
||||
public:
|
||||
int32_t age;
|
||||
char name[16];
|
||||
};
|
||||
|
||||
TEST(ConfigSerializer, LoadSerial_EmptyObject) {
|
||||
MockInputStream s("{age:" TEST_INT_S ",inner:{},name:\"Scott\"}");
|
||||
TestNested data;
|
||||
data.name[0] = 0;
|
||||
|
||||
bool success = data.loadSerial(s);
|
||||
EXPECT_TRUE(success);
|
||||
|
||||
EXPECT_EQ(TEST_INT, data.age);
|
||||
bool match = strcmp("Scott", data.name) == 0;
|
||||
EXPECT_TRUE(match); // properties after an empty object must still load
|
||||
}
|
||||
|
||||
TEST(ConfigSerializer, LoadSerial_EmptyObjectWithWhitespace) {
|
||||
MockInputStream s("{age:" TEST_INT_S ",inner:{ },name:\"Scott\"}");
|
||||
TestNested data;
|
||||
data.name[0] = 0;
|
||||
|
||||
bool success = data.loadSerial(s);
|
||||
EXPECT_TRUE(success);
|
||||
bool match = strcmp("Scott", data.name) == 0;
|
||||
EXPECT_TRUE(match);
|
||||
}
|
||||
|
||||
TEST(DynamicConfigSerializer, GetSet_Basic) {
|
||||
DynamicConfigSerializer data;
|
||||
|
||||
|
||||
@@ -173,6 +173,7 @@ extends = Heltec_RC32
|
||||
build_flags =
|
||||
${Heltec_RC32.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D ENABLE_WIFI_INTERFACE
|
||||
-D DISPLAY_CLASS=NullDisplayDriver
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
@@ -185,6 +186,7 @@ build_src_filter = ${Heltec_RC32.build_src_filter}
|
||||
+<helpers/ui/buzzer.cpp>
|
||||
+<helpers/ui/NullDisplayDriver.cpp>
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/wifi/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
@@ -314,6 +316,7 @@ extends = Heltec_RC32_with_display
|
||||
build_flags =
|
||||
${Heltec_RC32_with_display.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D ENABLE_WIFI_INTERFACE
|
||||
-D UI_HAS_ROTARY_INPUT
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
@@ -324,6 +327,7 @@ build_flags =
|
||||
build_src_filter = ${Heltec_RC32_with_display.build_src_filter}
|
||||
+<helpers/ui/buzzer.cpp>
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/wifi/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
|
||||
@@ -383,6 +383,7 @@ extends = Heltec_tracker_v2
|
||||
build_flags =
|
||||
${Heltec_tracker_v2.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D ENABLE_WIFI_INTERFACE
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=ST7735Display
|
||||
@@ -396,6 +397,7 @@ build_src_filter = ${Heltec_tracker_v2.build_src_filter}
|
||||
+<helpers/ui/ST7735Display.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/wifi/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
|
||||
@@ -178,6 +178,7 @@ extends = Heltec_lora32_v2
|
||||
build_flags =
|
||||
${Heltec_lora32_v2.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D ENABLE_WIFI_INTERFACE
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D MAX_CONTACTS=160
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
@@ -189,6 +190,7 @@ build_flags =
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_lora32_v2.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/wifi/*.cpp>
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
|
||||
@@ -301,6 +301,7 @@ extends = Heltec_lora32_v3
|
||||
build_flags =
|
||||
${Heltec_lora32_v3.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D ENABLE_WIFI_INTERFACE
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
@@ -314,6 +315,7 @@ build_src_filter = ${Heltec_lora32_v3.build_src_filter}
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/wifi/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
@@ -459,6 +461,7 @@ lib_deps =
|
||||
extends = Heltec_lora32_v3
|
||||
build_flags =
|
||||
${Heltec_lora32_v3.build_flags}
|
||||
-D ENABLE_WIFI_INTERFACE
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D WIFI_DEBUG_LOGGING=1
|
||||
@@ -469,6 +472,7 @@ build_flags =
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_lora32_v3.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/wifi/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
lib_deps =
|
||||
${Heltec_lora32_v3.lib_deps}
|
||||
|
||||
@@ -465,6 +465,7 @@ extends = heltec_v4_oled
|
||||
build_flags =
|
||||
${heltec_v4_oled.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D ENABLE_WIFI_INTERFACE
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
@@ -478,6 +479,7 @@ build_src_filter = ${heltec_v4_oled.build_src_filter}
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/wifi/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
@@ -630,6 +632,7 @@ extends = heltec_v4_tft
|
||||
build_flags =
|
||||
${heltec_v4_tft.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D ENABLE_WIFI_INTERFACE
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
@@ -643,6 +646,7 @@ build_src_filter = ${heltec_v4_tft.build_src_filter}
|
||||
+<helpers/ui/ST7789LCDDisplay.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/wifi/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
|
||||
@@ -258,6 +258,7 @@ extends = heltec_v4_r8_oled
|
||||
build_flags =
|
||||
${heltec_v4_r8_oled.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D ENABLE_WIFI_INTERFACE
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
@@ -269,6 +270,7 @@ build_src_filter = ${heltec_v4_r8_oled.build_src_filter}
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/wifi/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
@@ -511,6 +513,7 @@ extends = heltec_v4_r8_tft
|
||||
build_flags =
|
||||
${heltec_v4_r8_tft.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D ENABLE_WIFI_INTERFACE
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
@@ -522,6 +525,7 @@ build_src_filter = ${heltec_v4_r8_tft.build_src_filter}
|
||||
+<helpers/ui/ST7789LCDDisplay.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/wifi/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
#include "TBeam1WBoard.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
void TBeam1WBoard::begin() {
|
||||
ESP32Board::begin();
|
||||
|
||||
@@ -15,31 +20,61 @@ void TBeam1WBoard::begin() {
|
||||
pinMode(LED_PIN, OUTPUT);
|
||||
digitalWrite(LED_PIN, LOW);
|
||||
|
||||
// Initialize fan control (on by default - 1W PA can overheat)
|
||||
// NTC ADC (PA-adjacent thermistor)
|
||||
pinMode(NTC_PIN, INPUT);
|
||||
analogSetPinAttenuation(NTC_PIN, ADC_11db);
|
||||
analogReadResolution(12);
|
||||
|
||||
// Fan: auto/onoff. Thermal on at 36C / off below 30C; TX still forces a cooldown.
|
||||
pinMode(FAN_CTRL_PIN, OUTPUT);
|
||||
digitalWrite(FAN_CTRL_PIN, HIGH);
|
||||
_fan_on = true;
|
||||
_temp_c = readNtcTempC();
|
||||
startFanTask();
|
||||
}
|
||||
|
||||
void TBeam1WBoard::startFanTask() {
|
||||
if (_fan_task) return;
|
||||
xTaskCreate(fanTaskThunk, "tbeam1w_fan", 4096, this, 1, &_fan_task);
|
||||
}
|
||||
|
||||
void TBeam1WBoard::fanTaskThunk(void* arg) {
|
||||
auto* self = static_cast<TBeam1WBoard*>(arg);
|
||||
for (;;) {
|
||||
self->updateFan();
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
}
|
||||
|
||||
void TBeam1WBoard::onBeforeTransmit() {
|
||||
// RF switching handled by RadioLib via SX126X_DIO2_AS_RF_SWITCH and setRfSwitchPins()
|
||||
digitalWrite(LED_PIN, HIGH); // TX LED on
|
||||
portENTER_CRITICAL(&_fan_mux);
|
||||
_tx_active = true;
|
||||
if (_mode == FAN_AUTO && !_stopped) {
|
||||
setFanOutputLocked(true);
|
||||
}
|
||||
portEXIT_CRITICAL(&_fan_mux);
|
||||
}
|
||||
|
||||
void TBeam1WBoard::onAfterTransmit() {
|
||||
digitalWrite(LED_PIN, LOW); // TX LED off
|
||||
portENTER_CRITICAL(&_fan_mux);
|
||||
if (!_stopped) {
|
||||
_tx_until_ms = millis() + FAN_TX_COOLDOWN_MS;
|
||||
_tx_cooldown_active = true;
|
||||
}
|
||||
_tx_active = false;
|
||||
portEXIT_CRITICAL(&_fan_mux);
|
||||
}
|
||||
|
||||
uint16_t TBeam1WBoard::getBattMilliVolts() {
|
||||
// T-Beam 1W uses 7.4V battery with voltage divider
|
||||
// ADC reads through divider - adjust multiplier based on actual divider ratio
|
||||
analogReadResolution(12);
|
||||
uint32_t raw = 0;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
raw += analogRead(BATTERY_PIN);
|
||||
}
|
||||
raw = raw / 8;
|
||||
// Assuming voltage divider ratio from ADC_MULTIPLIER
|
||||
// 3.3V reference, 12-bit ADC (4095 max)
|
||||
return static_cast<uint16_t>((raw * 3300 * ADC_MULTIPLIER) / 4095);
|
||||
}
|
||||
|
||||
@@ -48,6 +83,13 @@ const char* TBeam1WBoard::getManufacturerName() const {
|
||||
}
|
||||
|
||||
void TBeam1WBoard::powerOff() {
|
||||
portENTER_CRITICAL(&_fan_mux);
|
||||
_stopped = true;
|
||||
_tx_active = false;
|
||||
_tx_cooldown_active = false;
|
||||
setFanOutputLocked(false);
|
||||
portEXIT_CRITICAL(&_fan_mux);
|
||||
|
||||
// Turn off radio LNA (CTRL pin must be LOW when not receiving)
|
||||
digitalWrite(SX126X_RXEN, LOW);
|
||||
|
||||
@@ -55,17 +97,254 @@ void TBeam1WBoard::powerOff() {
|
||||
digitalWrite(SX126X_POWER_EN, LOW);
|
||||
radio_powered = false;
|
||||
|
||||
// Turn off LED and fan
|
||||
digitalWrite(LED_PIN, LOW);
|
||||
digitalWrite(FAN_CTRL_PIN, LOW);
|
||||
|
||||
ESP32Board::powerOff();
|
||||
}
|
||||
|
||||
void TBeam1WBoard::setFanEnabled(bool enabled) {
|
||||
digitalWrite(FAN_CTRL_PIN, enabled ? HIGH : LOW);
|
||||
portENTER_CRITICAL(&_fan_mux);
|
||||
setFanOutputLocked(enabled && !_stopped);
|
||||
portEXIT_CRITICAL(&_fan_mux);
|
||||
}
|
||||
|
||||
bool TBeam1WBoard::isFanEnabled() const {
|
||||
return digitalRead(FAN_CTRL_PIN) == HIGH;
|
||||
portENTER_CRITICAL(&_fan_mux);
|
||||
bool enabled = _fan_on;
|
||||
portEXIT_CRITICAL(&_fan_mux);
|
||||
return enabled;
|
||||
}
|
||||
|
||||
float TBeam1WBoard::readNtcTempC() {
|
||||
analogReadMilliVolts(NTC_PIN); // settle
|
||||
uint32_t sum = 0;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
uint32_t sample_mv = analogReadMilliVolts(NTC_PIN);
|
||||
// GPIO14 is ADC2 on ESP32-S3. Wi-Fi/ESP-NOW arbitration failures are
|
||||
// reported by Arduino as 0 mV; never average a failed sample into a
|
||||
// plausible-but-low temperature.
|
||||
if (sample_mv == 0 || sample_mv >= NTC_VCC_MV) return NAN;
|
||||
sum += sample_mv;
|
||||
}
|
||||
float mv = sum / 8.0f;
|
||||
|
||||
// R_ntc = R_fixed * (Vcc - V) / V for 3.3V-NTC-ADC-10k-GND
|
||||
float r_ntc = NTC_R_FIXED * (NTC_VCC_MV - mv) / mv;
|
||||
if (r_ntc <= 0.0f) return NAN;
|
||||
|
||||
float temp_k = 1.0f / (1.0f / 298.15f + (1.0f / NTC_B) * logf(r_ntc / NTC_R25));
|
||||
return temp_k - 273.15f;
|
||||
}
|
||||
|
||||
bool TBeam1WBoard::ntcImplausible(float temp_c) const {
|
||||
return isnan(temp_c) || temp_c < -20.0f || temp_c > 120.0f;
|
||||
}
|
||||
|
||||
bool TBeam1WBoard::isTxCoolingLocked(uint32_t now) {
|
||||
if (_tx_active) return true;
|
||||
if (!_tx_cooldown_active) return false;
|
||||
if ((int32_t)(now - _tx_until_ms) >= 0) {
|
||||
_tx_cooldown_active = false;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int TBeam1WBoard::cooldownSecsLocked() {
|
||||
if (_tx_active) return (FAN_TX_COOLDOWN_MS + 999) / 1000;
|
||||
if (!_tx_cooldown_active) return 0;
|
||||
int32_t remain_ms = (int32_t)(_tx_until_ms - millis());
|
||||
if (remain_ms <= 0) {
|
||||
_tx_cooldown_active = false;
|
||||
return 0;
|
||||
}
|
||||
return (remain_ms + 999) / 1000;
|
||||
}
|
||||
|
||||
void TBeam1WBoard::setFanOutputLocked(bool enabled) {
|
||||
_fan_on = enabled;
|
||||
digitalWrite(FAN_CTRL_PIN, enabled ? HIGH : LOW);
|
||||
}
|
||||
|
||||
void TBeam1WBoard::updateFan() {
|
||||
float t = readNtcTempC();
|
||||
uint32_t now = millis();
|
||||
|
||||
portENTER_CRITICAL(&_fan_mux);
|
||||
if (_stopped) {
|
||||
portEXIT_CRITICAL(&_fan_mux);
|
||||
return;
|
||||
}
|
||||
|
||||
_temp_c = t;
|
||||
bool tx_cooling = isTxCoolingLocked(now);
|
||||
|
||||
bool enabled;
|
||||
if (_mode == FAN_ON) {
|
||||
enabled = true;
|
||||
} else if (_mode == FAN_OFF) {
|
||||
enabled = false;
|
||||
} else if (ntcImplausible(t)) {
|
||||
enabled = true; // fail-safe: treat bad NTC as hot
|
||||
} else {
|
||||
// TX cooldown must not latch _thermal_on, or a TX at 30C keeps the fan
|
||||
// running until the temperature dips under lo.
|
||||
if (t >= (float)_hi_c) _thermal_on = true;
|
||||
else if (t < (float)_lo_c) _thermal_on = false;
|
||||
enabled = _thermal_on || tx_cooling;
|
||||
}
|
||||
|
||||
setFanOutputLocked(enabled);
|
||||
portEXIT_CRITICAL(&_fan_mux);
|
||||
}
|
||||
|
||||
bool TBeam1WBoard::persistKey(const char* key, const char* value) {
|
||||
return _prefs && _prefs->setByKey(key, value);
|
||||
}
|
||||
|
||||
bool TBeam1WBoard::parseIntArg(const char* text, int& value) {
|
||||
if (!text || !*text) return false;
|
||||
errno = 0;
|
||||
char* end = nullptr;
|
||||
long parsed = strtol(text, &end, 10);
|
||||
if (errno == ERANGE || end == text || *end != '\0' || parsed < INT_MIN || parsed > INT_MAX) {
|
||||
return false;
|
||||
}
|
||||
value = (int)parsed;
|
||||
return true;
|
||||
}
|
||||
|
||||
void TBeam1WBoard::loadFanPrefs() {
|
||||
if (!_prefs) return;
|
||||
|
||||
FanMode mode = FAN_AUTO;
|
||||
char buf[12];
|
||||
buf[0] = 0;
|
||||
if (_prefs->getByKey("fan", buf, 11)) {
|
||||
if (strcmp(buf, "off") == 0) mode = FAN_OFF;
|
||||
else if (strcmp(buf, "on") == 0) mode = FAN_ON;
|
||||
}
|
||||
|
||||
int lo = FAN_DEFAULT_LO_C;
|
||||
int hi = FAN_DEFAULT_HI_C;
|
||||
buf[0] = 0;
|
||||
if (_prefs->getByKey("fan_lo", buf, 11)) lo = atoi(buf);
|
||||
buf[0] = 0;
|
||||
if (_prefs->getByKey("fan_hi", buf, 11)) hi = atoi(buf);
|
||||
|
||||
portENTER_CRITICAL(&_fan_mux);
|
||||
_mode = mode;
|
||||
if (lo >= 0 && hi <= 120 && lo < hi) {
|
||||
_lo_c = lo;
|
||||
_hi_c = hi;
|
||||
}
|
||||
portEXIT_CRITICAL(&_fan_mux);
|
||||
}
|
||||
|
||||
void TBeam1WBoard::attachDynamicPrefs(KeyValueStore* prefs) {
|
||||
_prefs = prefs;
|
||||
loadFanPrefs();
|
||||
updateFan();
|
||||
}
|
||||
|
||||
const char* TBeam1WBoard::modeNameLocked() const {
|
||||
if (_mode == FAN_AUTO) return "auto";
|
||||
if (_mode == FAN_OFF) return "off";
|
||||
return "on";
|
||||
}
|
||||
|
||||
bool TBeam1WBoard::handleCommand(const char* command, uint32_t sender_timestamp, char* reply) {
|
||||
(void)sender_timestamp;
|
||||
|
||||
if (strcmp(command, "get fan") == 0) {
|
||||
portENTER_CRITICAL(&_fan_mux);
|
||||
int cd = cooldownSecsLocked();
|
||||
float temp_c = _temp_c;
|
||||
bool enabled = _fan_on;
|
||||
const char* mode = modeNameLocked();
|
||||
portEXIT_CRITICAL(&_fan_mux);
|
||||
if (ntcImplausible(temp_c)) {
|
||||
sprintf(reply, "> %s n/a fan=%s cd=%ds", mode, enabled ? "on" : "off", cd);
|
||||
} else {
|
||||
sprintf(reply, "> %s %.1fC fan=%s cd=%ds",
|
||||
mode, (double)temp_c, enabled ? "on" : "off", cd);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strncmp(command, "set fan.lo ", 11) == 0) {
|
||||
int lo;
|
||||
portENTER_CRITICAL(&_fan_mux);
|
||||
int hi_limit = _hi_c;
|
||||
portEXIT_CRITICAL(&_fan_mux);
|
||||
if (!parseIntArg(&command[11], lo) || lo < 0 || lo >= hi_limit || lo > 100) {
|
||||
strcpy(reply, "Error: fan.lo must be 0..100 and < fan.hi");
|
||||
} else if (!persistKey("fan_lo", &command[11])) {
|
||||
strcpy(reply, "Error: failed to save fan.lo");
|
||||
} else {
|
||||
portENTER_CRITICAL(&_fan_mux);
|
||||
_lo_c = lo;
|
||||
portEXIT_CRITICAL(&_fan_mux);
|
||||
sprintf(reply, "OK - fan.lo %d", lo);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strncmp(command, "set fan.hi ", 11) == 0) {
|
||||
int hi;
|
||||
portENTER_CRITICAL(&_fan_mux);
|
||||
int lo_limit = _lo_c;
|
||||
portEXIT_CRITICAL(&_fan_mux);
|
||||
if (!parseIntArg(&command[11], hi) || hi <= lo_limit || hi > 120) {
|
||||
strcpy(reply, "Error: fan.hi must be > fan.lo and <= 120");
|
||||
} else if (!persistKey("fan_hi", &command[11])) {
|
||||
strcpy(reply, "Error: failed to save fan.hi");
|
||||
} else {
|
||||
portENTER_CRITICAL(&_fan_mux);
|
||||
_hi_c = hi;
|
||||
portEXIT_CRITICAL(&_fan_mux);
|
||||
sprintf(reply, "OK - fan.hi %d", hi);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strncmp(command, "set fan ", 8) == 0) {
|
||||
const char* arg = &command[8];
|
||||
if (strcmp(arg, "on") == 0) {
|
||||
if (!persistKey("fan", "on")) {
|
||||
strcpy(reply, "Error: failed to save fan mode");
|
||||
} else {
|
||||
portENTER_CRITICAL(&_fan_mux);
|
||||
_mode = FAN_ON;
|
||||
if (!_stopped) setFanOutputLocked(true);
|
||||
portEXIT_CRITICAL(&_fan_mux);
|
||||
strcpy(reply, "OK - fan on");
|
||||
}
|
||||
} else if (strcmp(arg, "off") == 0) {
|
||||
if (!persistKey("fan", "off")) {
|
||||
strcpy(reply, "Error: failed to save fan mode");
|
||||
} else {
|
||||
portENTER_CRITICAL(&_fan_mux);
|
||||
_mode = FAN_OFF;
|
||||
setFanOutputLocked(false);
|
||||
portEXIT_CRITICAL(&_fan_mux);
|
||||
strcpy(reply, "OK - fan off");
|
||||
}
|
||||
} else if (strcmp(arg, "auto") == 0) {
|
||||
if (!persistKey("fan", "auto")) {
|
||||
strcpy(reply, "Error: failed to save fan mode");
|
||||
} else {
|
||||
portENTER_CRITICAL(&_fan_mux);
|
||||
_mode = FAN_AUTO;
|
||||
portEXIT_CRITICAL(&_fan_mux);
|
||||
updateFan();
|
||||
strcpy(reply, "OK - fan auto");
|
||||
}
|
||||
} else {
|
||||
strcpy(reply, "Error: fan must be on, off, or auto");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <helpers/ESP32Board.h>
|
||||
#include "variant.h"
|
||||
|
||||
@@ -28,18 +30,48 @@
|
||||
// - Battery must support 2A+ discharge for high-power TX
|
||||
|
||||
class TBeam1WBoard : public ESP32Board {
|
||||
public:
|
||||
enum FanMode { FAN_ON, FAN_OFF, FAN_AUTO };
|
||||
|
||||
private:
|
||||
bool radio_powered = false;
|
||||
bool _stopped = false;
|
||||
KeyValueStore* _prefs = nullptr;
|
||||
FanMode _mode = FAN_AUTO;
|
||||
int _lo_c = FAN_DEFAULT_LO_C;
|
||||
int _hi_c = FAN_DEFAULT_HI_C;
|
||||
bool _thermal_on = false; // onoff hysteresis; TX boost must not latch this
|
||||
bool _fan_on = true;
|
||||
float _temp_c = NAN;
|
||||
bool _tx_active = false;
|
||||
bool _tx_cooldown_active = false;
|
||||
uint32_t _tx_until_ms = 0;
|
||||
TaskHandle_t _fan_task = nullptr;
|
||||
mutable portMUX_TYPE _fan_mux = portMUX_INITIALIZER_UNLOCKED;
|
||||
|
||||
void startFanTask();
|
||||
void updateFan();
|
||||
void setFanOutputLocked(bool enabled);
|
||||
float readNtcTempC();
|
||||
int cooldownSecsLocked();
|
||||
bool isTxCoolingLocked(uint32_t now);
|
||||
bool ntcImplausible(float temp_c) const;
|
||||
bool persistKey(const char* key, const char* value);
|
||||
static bool parseIntArg(const char* text, int& value);
|
||||
void loadFanPrefs();
|
||||
const char* modeNameLocked() const;
|
||||
static void fanTaskThunk(void* arg);
|
||||
|
||||
public:
|
||||
void begin();
|
||||
void attachDynamicPrefs(KeyValueStore* prefs);
|
||||
bool handleCommand(const char* command, uint32_t sender_timestamp, char* reply) override;
|
||||
void onBeforeTransmit() override;
|
||||
void onAfterTransmit() override;
|
||||
uint16_t getBattMilliVolts() override;
|
||||
const char* getManufacturerName() const override;
|
||||
void powerOff() override;
|
||||
|
||||
// Fan control methods
|
||||
void setFanEnabled(bool enabled);
|
||||
bool isFanEnabled() const;
|
||||
};
|
||||
@@ -153,6 +153,7 @@ extends = LilyGo_TBeam_1W
|
||||
build_flags =
|
||||
${LilyGo_TBeam_1W.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D ENABLE_WIFI_INTERFACE
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
@@ -165,6 +166,7 @@ build_flags =
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${LilyGo_TBeam_1W.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/wifi/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
|
||||
@@ -77,11 +77,21 @@
|
||||
#define BATTERY_SENSE_SAMPLES 30
|
||||
#define ADC_MULTIPLIER 3.0
|
||||
|
||||
// NTC temperature sensor
|
||||
// NTC thermistor (Murata NCP18XH103F03RB, 10k, B25/50=3380K)
|
||||
// Divider: 3.3V -> NTC -> GPIO14 -> 10k pull-down -> GND (Vadc rises with temp)
|
||||
#define NTC_PIN 14
|
||||
#define NTC_B 3380.0f
|
||||
#define NTC_R25 10000.0f
|
||||
#define NTC_R_FIXED 10000.0f
|
||||
#define NTC_VCC_MV 3300.0f
|
||||
|
||||
// Fan control
|
||||
// Fan control (GPIO41). NTC is PA-adjacent PCB temp, not die temp, so trip
|
||||
// well below the SX1262/ESP32 85C operating limit. Hardware testing confirmed
|
||||
// that this fan/MOSFET path is on/off; PWM below 100% does not spin the fan.
|
||||
#define FAN_CTRL_PIN 41
|
||||
#define FAN_TX_COOLDOWN_MS 15000
|
||||
#define FAN_DEFAULT_LO_C 30 // off below typical indoor idle (~86F)
|
||||
#define FAN_DEFAULT_HI_C 36 // on at ~97F PCB; still far below 85C chip ratings
|
||||
|
||||
// PA Ramp Time - T-Beam 1W requires >800us stabilization (default is 200us)
|
||||
// Value 0x05 = RADIOLIB_SX126X_PA_RAMP_800U
|
||||
|
||||
@@ -227,6 +227,7 @@ extends = T_Beam_S3_Supreme_SX1262
|
||||
build_flags =
|
||||
${T_Beam_S3_Supreme_SX1262.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D ENABLE_WIFI_INTERFACE
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
@@ -239,6 +240,7 @@ build_flags =
|
||||
; -D CORE_DEBUG_LEVEL=4
|
||||
build_src_filter = ${T_Beam_S3_Supreme_SX1262.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/wifi/*.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
|
||||
@@ -242,6 +242,7 @@ extends = LilyGo_TLora_V2_1_1_6
|
||||
build_flags =
|
||||
${LilyGo_TLora_V2_1_1_6.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D ENABLE_WIFI_INTERFACE
|
||||
-D MAX_CONTACTS=160
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D WIFI_SSID='"ssid"'
|
||||
@@ -250,6 +251,7 @@ build_flags =
|
||||
-D OFFLINE_QUEUE_SIZE=128
|
||||
build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/wifi/*.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
|
||||
@@ -27,8 +27,8 @@ build_flags =
|
||||
-D PIN_USER_BTN=0
|
||||
-D PIN_VEXT_EN=45
|
||||
-D PIN_VEXT_EN_ACTIVE=HIGH
|
||||
-D LORA_TX_POWER=4
|
||||
-D MAX_LORA_TX_POWER=4 ; GC1109 datasheet says max input at TX port is +5dbm, confirmed full saturation seems to occur at around 3-4dbm
|
||||
-D LORA_TX_POWER=5 ; default to 5, which gives ~22dbm
|
||||
-D MAX_LORA_TX_POWER=13 ; tested with tinySA, 13 gives ~28dbm
|
||||
-D PIN_GPS_RX=38
|
||||
-D PIN_GPS_TX=39
|
||||
-D PIN_GPS_RESET=42
|
||||
@@ -171,6 +171,7 @@ extends = meshnology_w12
|
||||
build_flags =
|
||||
${meshnology_w12.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D ENABLE_WIFI_INTERFACE
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
@@ -184,6 +185,7 @@ build_src_filter = ${meshnology_w12.build_src_filter}
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/wifi/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
|
||||
@@ -143,6 +143,7 @@ extends = nibble_screen_connect_base
|
||||
build_flags =
|
||||
${nibble_screen_connect_base.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D ENABLE_WIFI_INTERFACE
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
@@ -154,6 +155,7 @@ build_src_filter = ${nibble_screen_connect_base.build_src_filter}
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/wifi/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
|
||||
@@ -140,6 +140,7 @@ extends = nibble_zero_connect_base
|
||||
build_flags =
|
||||
${nibble_zero_connect_base.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D ENABLE_WIFI_INTERFACE
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
@@ -150,6 +151,7 @@ build_src_filter = ${nibble_zero_connect_base.build_src_filter}
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/wifi/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
|
||||
@@ -267,6 +267,7 @@ extends = rak3112
|
||||
build_flags =
|
||||
${rak3112.build_flags}
|
||||
-I examples/companion_radio/ui-orig
|
||||
-D ENABLE_WIFI_INTERFACE
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D WIFI_DEBUG_LOGGING=1
|
||||
@@ -277,6 +278,7 @@ build_flags =
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${rak3112.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/wifi/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-orig/*.cpp>
|
||||
lib_deps =
|
||||
|
||||
@@ -67,34 +67,60 @@ lib_deps = ${rpi_picow.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
lib_ignore = BLE
|
||||
|
||||
; [env:PicoW_companion_radio_ble]
|
||||
; extends = rpi_picow
|
||||
; build_flags = ${rpi_picow.build_flags}
|
||||
; -D MAX_CONTACTS=100
|
||||
; -D MAX_GROUP_CHANNELS=8
|
||||
; -D BLE_PIN_CODE=123456
|
||||
; -D BLE_DEBUG_LOGGING=1
|
||||
; ; -D MESH_PACKET_LOGGING=1
|
||||
; ; -D MESH_DEBUG=1
|
||||
; build_src_filter = ${rpi_picow.build_src_filter}
|
||||
; +<../examples/companion_radio/*.cpp>
|
||||
; lib_deps = ${rpi_picow.lib_deps}
|
||||
; densaugeo/base64 @ ~1.4.0
|
||||
[env:PicoW_companion_radio_ble]
|
||||
extends = rpi_picow
|
||||
build_flags = ${rpi_picow.build_flags}
|
||||
-D MAX_CONTACTS=100
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D PIO_FRAMEWORK_ARDUINO_ENABLE_BLUETOOTH
|
||||
-D BLE_PIN_CODE=123456
|
||||
-D BLE_DEBUG_LOGGING=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${rpi_picow.build_src_filter}
|
||||
+<helpers/rp2040/SerialBLEInterface.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
lib_deps = ${rpi_picow.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
; [env:PicoW_companion_radio_wifi]
|
||||
; extends = rpi_picow
|
||||
; build_flags = ${rpi_picow.build_flags}
|
||||
; -D MAX_CONTACTS=100
|
||||
; -D MAX_GROUP_CHANNELS=8
|
||||
; -D WIFI_DEBUG_LOGGING=1
|
||||
; -D WIFI_SSID='"myssid"'
|
||||
; -D WIFI_PWD='"mypwd"'
|
||||
; ; -D MESH_PACKET_LOGGING=1
|
||||
; ; -D MESH_DEBUG=1
|
||||
; build_src_filter = ${rpi_picow.build_src_filter}
|
||||
; +<../examples/companion_radio/*.cpp>
|
||||
; lib_deps = ${rpi_picow.lib_deps}
|
||||
; densaugeo/base64 @ ~1.4.0
|
||||
; USB + WiFi + BLE together; the interface manager fans frames out to all of them
|
||||
[env:PicoW_companion_radio]
|
||||
extends = rpi_picow
|
||||
build_flags = ${rpi_picow.build_flags}
|
||||
-D MAX_CONTACTS=100
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D ENABLE_USB_INTERFACE
|
||||
-D ENABLE_WIFI_INTERFACE
|
||||
-D PIO_FRAMEWORK_ARDUINO_ENABLE_BLUETOOTH
|
||||
-D BLE_PIN_CODE=123456
|
||||
; NOTE: DO NOT ENABLE --> -D BLE_DEBUG_LOGGING=1 (shares Serial with the USB interface)
|
||||
; NOTE: DO NOT ENABLE --> -D WIFI_DEBUG_LOGGING=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${rpi_picow.build_src_filter}
|
||||
+<helpers/rp2040/SerialBLEInterface.cpp>
|
||||
+<helpers/wifi/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
lib_deps = ${rpi_picow.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:PicoW_companion_radio_wifi]
|
||||
extends = rpi_picow
|
||||
build_flags = ${rpi_picow.build_flags}
|
||||
-D ENABLE_WIFI_INTERFACE
|
||||
-D MAX_CONTACTS=100
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D WIFI_DEBUG_LOGGING=1
|
||||
-D WIFI_SSID='""' ; no default network, configure with 'set wifi.ssid' / 'set wifi.pwd'
|
||||
-D WIFI_PWD='""'
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${rpi_picow.build_src_filter}
|
||||
+<helpers/wifi/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
lib_deps = ${rpi_picow.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
lib_ignore = BLE
|
||||
|
||||
[env:PicoW_terminal_chat]
|
||||
extends = rpi_picow
|
||||
|
||||
@@ -272,6 +272,7 @@ extends = Station_G2
|
||||
build_flags =
|
||||
${Station_G2.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D ENABLE_WIFI_INTERFACE
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D WIFI_DEBUG_LOGGING=1
|
||||
@@ -282,6 +283,7 @@ build_flags =
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Station_G2.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/wifi/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
|
||||
@@ -175,6 +175,7 @@ extends = Station_G3_ESP32
|
||||
build_flags =
|
||||
${Station_G3_ESP32.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D ENABLE_WIFI_INTERFACE
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D WIFI_DEBUG_LOGGING=1
|
||||
@@ -185,6 +186,7 @@ build_flags =
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Station_G3_ESP32.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/wifi/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
|
||||
@@ -176,6 +176,7 @@ extends = ThinkNode_M2
|
||||
build_flags =
|
||||
${ThinkNode_M2.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D ENABLE_WIFI_INTERFACE
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
@@ -184,6 +185,7 @@ build_flags =
|
||||
-D WIFI_PWD='"mypwd"'
|
||||
build_src_filter = ${ThinkNode_M2.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/wifi/*.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<helpers/ui/buzzer.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
|
||||
@@ -190,6 +190,7 @@ extends = ThinkNode_M5
|
||||
build_flags =
|
||||
${ThinkNode_M5.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D ENABLE_WIFI_INTERFACE
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
@@ -198,6 +199,7 @@ build_flags =
|
||||
-D WIFI_PWD='"mypwd"'
|
||||
build_src_filter = ${ThinkNode_M5.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/wifi/*.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
|
||||
@@ -136,6 +136,7 @@ extends = ThinkNode_M7
|
||||
build_flags =
|
||||
${ThinkNode_M7.build_flags}
|
||||
-I examples/companion_radio/ui-orig
|
||||
-D ENABLE_WIFI_INTERFACE
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=NullDisplayDriver
|
||||
@@ -146,6 +147,7 @@ build_flags =
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
build_src_filter = ${ThinkNode_M7.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/wifi/*.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<helpers/ui/NullDisplayDriver.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
|
||||
@@ -22,7 +22,6 @@ build_flags = ${nrf52_base.build_flags}
|
||||
-D UI_HAS_ROTARY_INPUT=1
|
||||
-D UI_HAS_NAV_INPUT=1
|
||||
-D UI_RECENT_LIST_SIZE=9
|
||||
-D UI_TZ_OFFSET=-4 # GMT-4
|
||||
-D UI_MSG_PREVIEW_SIZE=165
|
||||
-D EINK_MAX_PARTIAL_REFRESH=60
|
||||
-D EINK_DISPLAY_MODEL=GxEPD2_154_D67
|
||||
|
||||
@@ -137,6 +137,7 @@ extends = ThinkNode_M9
|
||||
build_flags =
|
||||
${ThinkNode_M9.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D ENABLE_WIFI_INTERFACE
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D WIFI_DEBUG_LOGGING=1
|
||||
@@ -146,6 +147,7 @@ build_flags =
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
build_src_filter = ${ThinkNode_M9.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/wifi/*.cpp>
|
||||
+<helpers/ui/buzzer.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
|
||||
#include "WioTrackerL1Board.h"
|
||||
|
||||
void WioTrackerL1Board::begin() {
|
||||
NRF52BoardDCDC::begin();
|
||||
btn_prev_state = HIGH;
|
||||
|
||||
pinMode(PIN_VBAT_READ, INPUT); // VBAT ADC input
|
||||
// Set all button pins to INPUT_PULLUP
|
||||
pinMode(PIN_BUTTON1, INPUT_PULLUP);
|
||||
pinMode(PIN_BUTTON2, INPUT_PULLUP);
|
||||
pinMode(PIN_BUTTON3, INPUT_PULLUP);
|
||||
pinMode(PIN_BUTTON4, INPUT_PULLUP);
|
||||
pinMode(PIN_BUTTON5, INPUT_PULLUP);
|
||||
pinMode(PIN_BUTTON6, INPUT_PULLUP);
|
||||
|
||||
#if defined(PIN_WIRE_SDA) && defined(PIN_WIRE_SCL)
|
||||
Wire.setPins(PIN_WIRE_SDA, PIN_WIRE_SCL);
|
||||
#endif
|
||||
|
||||
Wire.begin();
|
||||
|
||||
pinMode(SX126X_POWER_EN, OUTPUT);
|
||||
|
||||
#ifdef P_LORA_TX_LED
|
||||
pinMode(P_LORA_TX_LED, OUTPUT);
|
||||
digitalWrite(P_LORA_TX_LED, LOW);
|
||||
#endif
|
||||
|
||||
delay(10); // give sx1262 some time to power up
|
||||
}
|
||||
|
||||
bool WioTrackerL1Board::setLoRaFemPaGainEnabled(bool enable) {
|
||||
_is_pa_enabled = enable;
|
||||
digitalWrite(SX126X_POWER_EN, enable ? HIGH : LOW); // enable/disable the 1W PA
|
||||
return true;
|
||||
}
|
||||
|
||||
void WioTrackerL1Board::attachDynamicPrefs(KeyValueStore* prefs) {
|
||||
_prefs = prefs;
|
||||
|
||||
char gain[8];
|
||||
|
||||
gain[0] = 0;
|
||||
_prefs->getByKey("fem_txgain", gain, 7); // get initial values
|
||||
setLoRaFemPaGainEnabled(strcmp(gain, "1") == 0);
|
||||
}
|
||||
|
||||
bool WioTrackerL1Board::handleCommand(const char* command, uint32_t sender_timestamp, char* reply) {
|
||||
if (strcmp(command, "get radio.fem.rxgain") == 0) {
|
||||
strcpy(reply, "Error: unsupported");
|
||||
return true;
|
||||
}
|
||||
if (memcmp(command, "set radio.fem.rxgain ", 21) == 0) {
|
||||
strcpy(reply, "Error: unsupported");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strcmp(command, "get radio.fem.txgain") == 0) {
|
||||
sprintf(reply, "> %s", isLoRaFemPaGainEnabled() ? "on" : "off");
|
||||
return true;
|
||||
}
|
||||
if (memcmp(command, "set radio.fem.txgain ", 21) == 0) {
|
||||
if (memcmp(&command[21], "on", 2) == 0) {
|
||||
if (setLoRaFemPaGainEnabled(true)) {
|
||||
_prefs->setByKey("fem_txgain", "1");
|
||||
strcpy(reply, "OK - LoRa FEM TX gain on");
|
||||
} else {
|
||||
strcpy(reply, "Error: failed to apply LoRa FEM TX gain");
|
||||
}
|
||||
} else if (memcmp(&command[21], "off", 3) == 0) {
|
||||
if (setLoRaFemPaGainEnabled(false)) {
|
||||
_prefs->setByKey("fem_txgain", "0");
|
||||
strcpy(reply, "OK - LoRa FEM TX gain off");
|
||||
} else {
|
||||
strcpy(reply, "Error: failed to apply LoRa FEM TX gain");
|
||||
}
|
||||
} else {
|
||||
strcpy(reply, "Error: state must be on or off");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false; // not handled
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include <MeshCore.h>
|
||||
#include <Arduino.h>
|
||||
#include <helpers/NRF52Board.h>
|
||||
|
||||
class WioTrackerL1Board : public NRF52BoardDCDC {
|
||||
protected:
|
||||
uint8_t btn_prev_state;
|
||||
bool _is_pa_enabled = false;
|
||||
KeyValueStore* _prefs = NULL;
|
||||
|
||||
bool setLoRaFemPaGainEnabled(bool enable);
|
||||
bool isLoRaFemPaGainEnabled() const { return _is_pa_enabled; }
|
||||
|
||||
public:
|
||||
WioTrackerL1Board() : NRF52Board("WioTrackerL1 OTA") {}
|
||||
void begin();
|
||||
void attachDynamicPrefs(KeyValueStore* prefs);
|
||||
bool handleCommand(const char* command, uint32_t sender_timestamp, char* reply) override;
|
||||
|
||||
#if defined(P_LORA_TX_LED)
|
||||
void onBeforeTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
}
|
||||
void onAfterTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off
|
||||
}
|
||||
#endif
|
||||
|
||||
uint16_t getBattMilliVolts() override {
|
||||
int adcvalue = 0;
|
||||
analogReadResolution(12);
|
||||
analogReference(AR_INTERNAL);
|
||||
delay(10);
|
||||
adcvalue = analogRead(PIN_VBAT_READ);
|
||||
return (adcvalue * ADC_MULTIPLIER * AREF_VOLTAGE) / 4.096;
|
||||
}
|
||||
|
||||
const char* getManufacturerName() const override {
|
||||
return "Seeed Wio Tracker L1";
|
||||
}
|
||||
|
||||
void powerOff() override {
|
||||
setLoRaFemPaGainEnabled(false); // turn Off PA
|
||||
NRF52Board::powerOff();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,121 @@
|
||||
[WioTrackerL1-1W]
|
||||
extends = nrf52_base
|
||||
board = seeed-wio-tracker-l1
|
||||
board_build.ldscript = boards/nrf52840_s140_v7.ld
|
||||
build_flags = ${nrf52_base.build_flags}
|
||||
${sensor_base.build_flags}
|
||||
-I lib/nrf52/s140_nrf52_7.3.0_API/include
|
||||
-I lib/nrf52/s140_nrf52_7.3.0_API/include/nrf52
|
||||
-I variants/wio-tracker-l1-1w
|
||||
-D WIO_TRACKER_L1
|
||||
-D USE_SX1262
|
||||
-D RADIO_CLASS=CustomSX1262
|
||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||
-D LORA_TX_POWER=22
|
||||
-D SX126X_CURRENT_LIMIT=140
|
||||
-D SX126X_RX_BOOSTED_GAIN=1
|
||||
-D PIN_OLED_RESET=-1
|
||||
-D GPS_BAUD_RATE=9600
|
||||
build_src_filter = ${nrf52_base.build_src_filter}
|
||||
+<WioTrackerL1Board.cpp>
|
||||
+<../variants/wio-tracker-l1-1w>
|
||||
+<helpers/ui/SH1106Display.cpp>
|
||||
+<helpers/sensors>
|
||||
lib_deps= ${nrf52_base.lib_deps}
|
||||
${sensor_base.lib_deps}
|
||||
adafruit/Adafruit SH110X @ ^2.1.13
|
||||
adafruit/Adafruit GFX Library @ ^1.12.1
|
||||
|
||||
[env:WioTrackerL1-1W_repeater]
|
||||
extends = WioTrackerL1-1W
|
||||
build_src_filter = ${WioTrackerL1-1W.build_src_filter}
|
||||
+<../examples/simple_repeater>
|
||||
build_flags =
|
||||
${WioTrackerL1-1W.build_flags}
|
||||
-D ADVERT_NAME='"WioTrackerL1 Repeater"'
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
-D DISPLAY_CLASS=SH1106Display
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
lib_deps = ${WioTrackerL1-1W.lib_deps}
|
||||
adafruit/RTClib @ ^2.1.3
|
||||
|
||||
[env:WioTrackerL1-1W_room_server]
|
||||
extends = WioTrackerL1-1W
|
||||
build_src_filter = ${WioTrackerL1-1W.build_src_filter}
|
||||
+<../examples/simple_room_server>
|
||||
build_flags = ${WioTrackerL1-1W.build_flags}
|
||||
-D ADVERT_NAME='"WioTrackerL1 Room"'
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D ROOM_PASSWORD='"hello"'
|
||||
-D DISPLAY_CLASS=SH1106Display
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
lib_deps = ${WioTrackerL1-1W.lib_deps}
|
||||
adafruit/RTClib @ ^2.1.3
|
||||
|
||||
[env:WioTrackerL1-1W_companion_radio_usb]
|
||||
extends = WioTrackerL1-1W
|
||||
board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld
|
||||
board_upload.maximum_size = 708608
|
||||
build_flags = ${WioTrackerL1-1W.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=SH1106Display
|
||||
-D UI_HAS_JOYSTICK=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
-D PIN_BUZZER=12
|
||||
-D QSPIFLASH=1
|
||||
-D RADIO_FEM_TXGAIN=1
|
||||
-D ENABLE_USB_INTERFACE
|
||||
; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1
|
||||
; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1
|
||||
build_src_filter = ${WioTrackerL1-1W.build_src_filter}
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
+<helpers/ui/SH1106Display.cpp>
|
||||
+<helpers/ui/buzzer.cpp>
|
||||
lib_deps = ${WioTrackerL1-1W.lib_deps}
|
||||
adafruit/RTClib @ ^2.1.3
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
end2endzone/NonBlockingRTTTL@^1.3.0
|
||||
|
||||
[env:WioTrackerL1-1W_companion_radio_ble]
|
||||
extends = WioTrackerL1-1W
|
||||
board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld
|
||||
board_upload.maximum_size = 708608
|
||||
build_flags = ${WioTrackerL1-1W.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D BLE_PIN_CODE=123456
|
||||
-D BLE_DEBUG_LOGGING=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
-D DISPLAY_CLASS=SH1106Display
|
||||
-D UI_HAS_JOYSTICK=1
|
||||
-D PIN_BUZZER=12
|
||||
-D QSPIFLASH=1
|
||||
-D RADIO_FEM_TXGAIN=1
|
||||
-D ADVERT_NAME='"@@MAC"'
|
||||
-D ENV_PIN_SDA=PIN_WIRE1_SDA
|
||||
-D ENV_PIN_SCL=PIN_WIRE1_SCL
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${WioTrackerL1-1W.build_src_filter}
|
||||
+<helpers/nrf52/SerialBLEInterface.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<helpers/ui/buzzer.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps = ${WioTrackerL1-1W.lib_deps}
|
||||
adafruit/RTClib @ ^2.1.3
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
end2endzone/NonBlockingRTTTL@^1.3.0
|
||||
|
||||
[env:WioTrackerL1-1W_kiss_modem]
|
||||
extends = WioTrackerL1-1W
|
||||
build_src_filter = ${WioTrackerL1-1W.build_src_filter}
|
||||
+<../examples/kiss_modem/>
|
||||
@@ -0,0 +1,40 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
#include <helpers/sensors/MicroNMEALocationProvider.h>
|
||||
|
||||
WioTrackerL1Board board;
|
||||
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
VolatileRTCClock fallback_clock;
|
||||
AutoDiscoverRTCClock rtc_clock(fallback_clock);
|
||||
|
||||
#ifdef ENV_INCLUDE_GPS
|
||||
MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock);
|
||||
EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea);
|
||||
#else
|
||||
EnvironmentSensorManager sensors = EnvironmentSensorManager();
|
||||
#endif
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
DISPLAY_CLASS display;
|
||||
MomentaryButton user_btn(PIN_USER_BTN, 1000, true, false, false);
|
||||
MomentaryButton joystick_left(JOYSTICK_LEFT, 1000, true, false, false);
|
||||
MomentaryButton joystick_right(JOYSTICK_RIGHT, 1000, true, false, false);
|
||||
MomentaryButton back_btn(PIN_BACK_BTN, 1000, true, false, true);
|
||||
#endif
|
||||
|
||||
bool radio_init() {
|
||||
rtc_clock.begin(Wire);
|
||||
|
||||
return radio.std_init(&SPI);
|
||||
}
|
||||
|
||||
mesh::LocalIdentity radio_new_identity() {
|
||||
RadioNoiseListener rng(radio);
|
||||
return mesh::LocalIdentity(&rng); // create new random identity
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#define RADIOLIB_STATIC_ONLY 1
|
||||
#include <RadioLib.h>
|
||||
#include <helpers/radiolib/RadioLibWrappers.h>
|
||||
#include <WioTrackerL1Board.h>
|
||||
#include <helpers/radiolib/CustomSX1262Wrapper.h>
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
#include <helpers/sensors/EnvironmentSensorManager.h>
|
||||
#ifdef DISPLAY_CLASS
|
||||
#if defined(WIO_TRACKER_L1_EINK)
|
||||
#include <helpers/ui/GxEPDDisplay.h>
|
||||
#else
|
||||
#include <helpers/ui/SH1106Display.h>
|
||||
#endif
|
||||
#include <helpers/ui/MomentaryButton.h>
|
||||
#endif
|
||||
#include <helpers/sensors/EnvironmentSensorManager.h>
|
||||
|
||||
extern WioTrackerL1Board board;
|
||||
extern WRAPPER_CLASS radio_driver;
|
||||
extern AutoDiscoverRTCClock rtc_clock;
|
||||
extern EnvironmentSensorManager sensors;
|
||||
#ifdef DISPLAY_CLASS
|
||||
extern DISPLAY_CLASS display;
|
||||
extern MomentaryButton user_btn;
|
||||
extern MomentaryButton joystick_left;
|
||||
extern MomentaryButton joystick_right;
|
||||
extern MomentaryButton back_btn;
|
||||
#endif
|
||||
|
||||
bool radio_init();
|
||||
mesh::LocalIdentity radio_new_identity();
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
#include "variant.h"
|
||||
#include "wiring_constants.h"
|
||||
#include "wiring_digital.h"
|
||||
#include "nrf.h"
|
||||
|
||||
const uint32_t g_ADigitalPinMap[] = {
|
||||
// D0 .. D10 - Peripheral control pins
|
||||
41, // D0 P1.09 GNSS_WAKEUP
|
||||
7, // D1 P0.07 LORA_DIO1
|
||||
39, // D2 P1,07 LORA_RESET
|
||||
42, // D3 P1.10 LORA_BUSY
|
||||
46, // D4 P1.14 (A4/SDA) LORA_CS
|
||||
29, // D5 P0.29 (AIN5) LORA_VDET 鈥?Pro 1W: P0.29 (not P1.08)
|
||||
27, // D6 P0.27 (UART_TX) GNSS_TX
|
||||
26, // D7 P0.26 (UART_RX) GNSS_RX
|
||||
30, // D8 P0.30 (SPI_SCK) LORA_SCK
|
||||
3, // D9 P0.3 (SPI_MISO) LORA_MISO
|
||||
28, // D10 P0.28 (SPI_MOSI) LORA_MOSI
|
||||
|
||||
// D11-D12 - LED outputs
|
||||
33, // D11 P1.01 Mesh_LED (orange) 鈥?Pro 1W: P1.01 (not P1.15)
|
||||
// Buzzzer
|
||||
32, // D12 P1.0 Buzzer
|
||||
|
||||
// D13 - User input
|
||||
8, // D13 P0.08 User Button
|
||||
|
||||
// D14-D15 - OLED
|
||||
6, // D14 P0.06 OLED SDA
|
||||
5, // D15 P0.05 OLED SCL
|
||||
|
||||
// D16 - Battery voltage ADC input
|
||||
31, // D16 P0.31 VBAT_ADC
|
||||
// GROVE
|
||||
43, // D17 P0.00 GROVE SDA
|
||||
44, // D18 P0.01 GROVE SCL
|
||||
|
||||
// FLASH
|
||||
21, // D19 P0.21 (QSPI_SCK)
|
||||
25, // D20 P0.25 (QSPI_CSN)
|
||||
20, // D21 P0.20 (QSPI_SIO_0 DI)
|
||||
24, // D22 P0.24 (QSPI_SIO_1 DO)
|
||||
22, // D23 P0.22 (QSPI_SIO_2 WP)
|
||||
23, // D24 P0.23 (QSPI_SIO_3 HOLD)
|
||||
|
||||
// JOYSTICK
|
||||
36, // D25 TB_UP
|
||||
12, // D26 TB_DOWN
|
||||
11, // D27 TB_LEFT
|
||||
35, // D28 TB_RIGHT
|
||||
37, // D29 TB_PRESS
|
||||
|
||||
// VBAT ENABLE
|
||||
4, // D30 BAT_CTL
|
||||
|
||||
// D31-D33 - Pro 1W only
|
||||
13, // D31 P0.13 BOOST_EN (Grove 5V Boost)
|
||||
47, // D32 P1.15 nRF_Sig_Charge_State (BQ25616 STAT)
|
||||
14, // D33 P0.14 LORA_PWR_EN (SX1262 + 1 W PA LDO)
|
||||
};
|
||||
|
||||
void initVariant() {
|
||||
pinMode(PIN_QSPI_CS, OUTPUT);
|
||||
digitalWrite(PIN_QSPI_CS, HIGH);
|
||||
|
||||
// VBAT_ENABLE
|
||||
pinMode(VBAT_ENABLE, OUTPUT);
|
||||
digitalWrite(VBAT_ENABLE, HIGH);
|
||||
|
||||
// set LED pin as output and set it low
|
||||
pinMode(PIN_LED, OUTPUT);
|
||||
digitalWrite(PIN_LED, LOW);
|
||||
|
||||
// set buzzer pin as output and set it low
|
||||
pinMode(12, OUTPUT);
|
||||
digitalWrite(12, LOW);
|
||||
pinMode(12, OUTPUT);
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
#ifndef _SEEED_WIO_TRACKER_L1_H_
|
||||
#define _SEEED_WIO_TRACKER_L1_H_
|
||||
|
||||
/** Master clock frequency */
|
||||
#define VARIANT_MCK (64000000ul)
|
||||
|
||||
#define USE_LFXO // Board uses 32khz crystal for LF
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
* Headers
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "WVariant.h"
|
||||
|
||||
#define PINS_COUNT (34)
|
||||
#define NUM_DIGITAL_PINS (34)
|
||||
#define NUM_ANALOG_INPUTS (8)
|
||||
#define NUM_ANALOG_OUTPUTS (0)
|
||||
|
||||
// LEDs
|
||||
#define PIN_LED (11)
|
||||
#define LED_BLUE (-1) // Disable annoying flashing caused by Bluefruit
|
||||
#define LED_BUILTIN PIN_LED
|
||||
#define P_LORA_TX_LED PIN_LED
|
||||
#define LED_STATE_ON 1
|
||||
|
||||
// Buttons
|
||||
#define PIN_BUTTON1 (13) // Menu / User Button
|
||||
#define PIN_BUTTON2 (25) // Joystick Up
|
||||
#define PIN_BUTTON3 (26) // Joystick Down
|
||||
#define PIN_BUTTON4 (27) // Joystick Left
|
||||
#define PIN_BUTTON5 (28) // Joystick Right
|
||||
#define PIN_BUTTON6 (29) // Joystick Press
|
||||
#define PIN_BACK_BTN PIN_BUTTON1
|
||||
#define JOYSTICK_UP PIN_BUTTON2
|
||||
#define JOYSTICK_DOWN PIN_BUTTON3
|
||||
#define JOYSTICK_LEFT PIN_BUTTON4
|
||||
#define JOYSTICK_RIGHT PIN_BUTTON5
|
||||
#define JOYSTICK_PRESS PIN_BUTTON6
|
||||
#define PIN_USER_BTN PIN_BUTTON6
|
||||
|
||||
// Buzzer
|
||||
// #define PIN_BUZZER (12) // Buzzer pin (defined per firmware type)
|
||||
|
||||
#define VBAT_ENABLE (30)
|
||||
|
||||
// Analog pins
|
||||
#define PIN_VBAT_READ (16)
|
||||
#define AREF_VOLTAGE (3.6F)
|
||||
#define ADC_MULTIPLIER (2.0F)
|
||||
#define ADC_RESOLUTION (12)
|
||||
|
||||
// Serial interfaces
|
||||
#define PIN_SERIAL1_RX (7)
|
||||
#define PIN_SERIAL1_TX (6)
|
||||
|
||||
// SPI Interfaces
|
||||
#define SPI_INTERFACES_COUNT (2)
|
||||
|
||||
#define PIN_SPI_MISO (9)
|
||||
#define PIN_SPI_MOSI (10)
|
||||
#define PIN_SPI_SCK (8)
|
||||
|
||||
// BQ25616 single-wire charge status (Pro 1W)
|
||||
#define PIN_BOOST_EN (31) // D31 / P0.13 鈥?Grove 5V Boost enable
|
||||
#define EXT_CHRG_DETECT (32) // D32 / P1.15 鈥?BQ25616 STAT
|
||||
#define EXT_CHRG_DETECT_VALUE LOW // 0 = charging, 1 = full / charger sleep
|
||||
#define BOOST_EN_ACTIVE HIGH // HIGH enables Grove 5V Boost
|
||||
|
||||
// Lora Pins
|
||||
#define P_LORA_SCLK PIN_SPI_SCK
|
||||
#define P_LORA_MISO PIN_SPI_MISO
|
||||
#define P_LORA_MOSI PIN_SPI_MOSI
|
||||
#define P_LORA_DIO_1 (1)
|
||||
#define P_LORA_RESET (2)
|
||||
#define P_LORA_BUSY (3)
|
||||
#define P_LORA_NSS (4)
|
||||
#define SX126X_RXEN (5)
|
||||
#define SX126X_TXEN RADIOLIB_NC
|
||||
#define SX126X_DIO2_AS_RF_SWITCH true
|
||||
#define SX126X_DIO3_TCXO_VOLTAGE (1.8f)
|
||||
|
||||
// LORA_PWR_EN (P0.14): external LDO enable for the SX1262 + 1 W PA on the Pro 1W board.
|
||||
#define LORA_PWR_EN (33)
|
||||
#define SX126X_POWER_EN LORA_PWR_EN
|
||||
|
||||
// Wire Interfaces
|
||||
#define WIRE_INTERFACES_COUNT (2)
|
||||
|
||||
#define PIN_WIRE_SDA (14)
|
||||
#define PIN_WIRE_SCL (15)
|
||||
#define PIN_WIRE1_SDA (18)
|
||||
#define PIN_WIRE1_SCL (17)
|
||||
#define I2C_NO_RESCAN
|
||||
#define DISPLAY_ADDRESS 0x3D // SH1106 OLED I2C address
|
||||
|
||||
// GPS L76KB
|
||||
#define GPS_BAUDRATE 9600
|
||||
#define PIN_GPS_TX PIN_SERIAL1_RX
|
||||
#define PIN_GPS_RX PIN_SERIAL1_TX
|
||||
#define PIN_GPS_STANDBY (0)
|
||||
#define PIN_GPS_EN (PIN_GPS_STANDBY)
|
||||
|
||||
// QSPI Pins
|
||||
#define PIN_QSPI_SCK (19)
|
||||
#define PIN_QSPI_CS (20)
|
||||
#define PIN_QSPI_IO0 (21)
|
||||
#define PIN_QSPI_IO1 (22)
|
||||
#define PIN_QSPI_IO2 (23)
|
||||
#define PIN_QSPI_IO3 (24)
|
||||
|
||||
#define EXTERNAL_FLASH_DEVICES P25Q16H
|
||||
#define EXTERNAL_FLASH_USE_QSPI
|
||||
|
||||
// // EInk on SPI1
|
||||
// #define PIN_DISPLAY_CS (36)
|
||||
// #define PIN_DISPLAY_BUSY (35)
|
||||
// #define PIN_DISPLAY_DC (34)
|
||||
// #define PIN_DISPLAY_RST (32)
|
||||
|
||||
#define PIN_SPI1_MISO (37)
|
||||
#define PIN_SPI1_MOSI (33)
|
||||
#define PIN_SPI1_SCK (31)
|
||||
|
||||
// GxEPD2 needs that for a panel that is not even used !
|
||||
extern const int MISO;
|
||||
extern const int MOSI;
|
||||
extern const int SCK;
|
||||
|
||||
#endif
|
||||
@@ -115,8 +115,10 @@ extends = Xiao_esp32_C3
|
||||
build_src_filter = ${Xiao_esp32_C3.build_src_filter}
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/wifi/*.cpp>
|
||||
build_flags =
|
||||
${Xiao_esp32_C3.build_flags}
|
||||
-D ENABLE_WIFI_INTERFACE
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
|
||||
@@ -280,11 +280,13 @@ extends = Xiao_S3_WIO
|
||||
build_src_filter = ${Xiao_S3_WIO.build_src_filter}
|
||||
+<helpers/ui/NullDisplayDriver.cpp>
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/wifi/*.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
build_flags =
|
||||
${Xiao_S3_WIO.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D ENABLE_WIFI_INTERFACE
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
|
||||
Reference in new issue
Block a user