mirror of
https://github.com/meshcore-dev/MeshCore.git
synced 2026-04-25 08:52:24 +00:00
Merge branch 'dev'
# Conflicts: # docs/payloads.md
This commit is contained in:
39
boards/ESP32-S3-WROOM-1-N4.json
Normal file
39
boards/ESP32-S3-WROOM-1-N4.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "esp32s3_out.ld"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-D ARDUINO_USB_CDC_ON_BOOT=0",
|
||||
"-D ARDUINO_USB_MSC_ON_BOOT=0",
|
||||
"-D ARDUINO_USB_DFU_ON_BOOT=0",
|
||||
"-D ARDUINO_USB_MODE=0",
|
||||
"-D ARDUINO_RUNNING_CORE=1",
|
||||
"-D ARDUINO_EVENT_RUNNING_CORE=1"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "qio",
|
||||
"hwids": [["0x303A", "0x1001"]],
|
||||
"mcu": "esp32s3",
|
||||
"variant": "ESP32-S3-WROOM-1-N4"
|
||||
},
|
||||
"connectivity": ["wifi", "bluetooth"],
|
||||
"debug": {
|
||||
"default_tool": "esp-builtin",
|
||||
"onboard_tools": ["esp-builtin"],
|
||||
"openocd_target": "esp32s3.cfg"
|
||||
},
|
||||
"frameworks": ["arduino", "espidf"],
|
||||
"name": "ESP32-S3-WROOM-1-N4 (4 MB Flash, No PSRAM)",
|
||||
"upload": {
|
||||
"flash_size": "4MB",
|
||||
"maximum_ram_size": 524288,
|
||||
"maximum_size": 4194304,
|
||||
"require_upload_port": true,
|
||||
"speed": 921600
|
||||
},
|
||||
"url": "https://www.espressif.com/sites/default/files/documentation/esp32-s3-wroom-1_wroom-1u_datasheet_en.pdf",
|
||||
"vendor": "Espressif"
|
||||
}
|
||||
40
boards/heltec_tracker_v2.json
Normal file
40
boards/heltec_tracker_v2.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "esp32s3_out.ld",
|
||||
"partitions": "default_8MB.csv"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_USB_MODE=0",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "qio",
|
||||
"hwids": [["0x303A", "0x1001"]],
|
||||
"mcu": "esp32s3",
|
||||
"variant": "heltec_tracker_v2"
|
||||
},
|
||||
"connectivity": ["wifi", "bluetooth", "lora"],
|
||||
"debug": {
|
||||
"default_tool": "esp-builtin",
|
||||
"onboard_tools": ["esp-builtin"],
|
||||
"openocd_target": "esp32s3.cfg"
|
||||
},
|
||||
"frameworks": ["arduino", "espidf"],
|
||||
"name": "heltec_tracker v2",
|
||||
"upload": {
|
||||
"flash_size": "8MB",
|
||||
"maximum_ram_size": 327680,
|
||||
"maximum_size": 8388608,
|
||||
"use_1200bps_touch": true,
|
||||
"wait_for_upload_port": true,
|
||||
"require_upload_port": true,
|
||||
"speed": 921600
|
||||
},
|
||||
"url": "https://heltec.org/",
|
||||
"vendor": "heltec"
|
||||
}
|
||||
72
boards/rak4631.json
Normal file
72
boards/rak4631.json
Normal file
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v6.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [
|
||||
[
|
||||
"0x239A",
|
||||
"0x8029"
|
||||
],
|
||||
[
|
||||
"0x239A",
|
||||
"0x0029"
|
||||
],
|
||||
[
|
||||
"0x239A",
|
||||
"0x002A"
|
||||
],
|
||||
[
|
||||
"0x239A",
|
||||
"0x802A"
|
||||
]
|
||||
],
|
||||
"usb_product": "WisCore RAK4631 Board",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "WisCore_RAK4631_Board",
|
||||
"bsp": {
|
||||
"name": "adafruit"
|
||||
},
|
||||
"softdevice": {
|
||||
"sd_flags": "-DS140",
|
||||
"sd_name": "s140",
|
||||
"sd_version": "6.1.1",
|
||||
"sd_fwid": "0x00B6"
|
||||
},
|
||||
"bootloader": {
|
||||
"settings_addr": "0xFF000"
|
||||
}
|
||||
},
|
||||
"connectivity": [
|
||||
"bluetooth"
|
||||
],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"svd_path": "nrf52840.svd"
|
||||
},
|
||||
"frameworks": [
|
||||
"arduino"
|
||||
],
|
||||
"name": "WisCore RAK4631 Board",
|
||||
"upload": {
|
||||
"maximum_ram_size": 248832,
|
||||
"maximum_size": 815104,
|
||||
"speed": 115200,
|
||||
"protocol": "nrfutil",
|
||||
"protocols": [
|
||||
"jlink",
|
||||
"nrfjprog",
|
||||
"nrfutil",
|
||||
"stlink"
|
||||
],
|
||||
"use_1200bps_touch": true,
|
||||
"require_upload_port": true,
|
||||
"wait_for_upload_port": true
|
||||
},
|
||||
"url": "https://www.rakwireless.com",
|
||||
"vendor": "RAKwireless"
|
||||
}
|
||||
47
build.sh
47
build.sh
@@ -1,12 +1,45 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# usage
|
||||
# sh build.sh build-firmware RAK_4631_Repeater
|
||||
# sh build.sh build-firmwares
|
||||
# sh build.sh build-matching-firmwares RAK_4631
|
||||
# sh build.sh build-companion-firmwares
|
||||
# sh build.sh build-repeater-firmwares
|
||||
# sh build.sh build-room-server-firmwares
|
||||
global_usage() {
|
||||
cat - <<EOF
|
||||
Usage:
|
||||
sh build.sh <command> [target]
|
||||
|
||||
Commands:
|
||||
help|usage|-h|--help: Shows this message.
|
||||
build-firmware <target>: Build the firmware for the given build target.
|
||||
build-firmwares: Build all firmwares for all targets.
|
||||
build-matching-firmwares <build-match-spec>: Build all firmwares for build targets containing the string given for <build-match-spec>.
|
||||
build-companion-firmwares: Build all companion firmwares for all build targets.
|
||||
build-repeater-firmwares: Build all repeater firmwares for all build targets.
|
||||
build-room-server-firmwares: Build all chat room server firmwares for all build targets.
|
||||
|
||||
Examples:
|
||||
Build firmware for the "RAK_4631_repeater" device target
|
||||
$ sh build.sh build-firmware RAK_4631_repeater
|
||||
|
||||
Build all firmwares for device targets containing the string "RAK_4631"
|
||||
$ sh build.sh build-matching-firmwares <build-match-spec>
|
||||
|
||||
Build all companion firmwares
|
||||
$ sh build.sh build-companion-firmwares
|
||||
|
||||
Build all repeater firmwares
|
||||
$ sh build.sh build-repeater-firmwares
|
||||
|
||||
Build all chat room server firmwares
|
||||
$ sh build.sh build-room-server-firmwares
|
||||
EOF
|
||||
}
|
||||
|
||||
# Catch cries for help before doing anything else.
|
||||
case $1 in
|
||||
help|usage|-h|--help)
|
||||
global_usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
|
||||
# get a list of pio env names that start with "env:"
|
||||
get_pio_envs() {
|
||||
|
||||
31
create-uf2.py
Normal file
31
create-uf2.py
Normal file
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
# Adds PlatformIO post-processing to convert hex files to uf2 files
|
||||
|
||||
import os
|
||||
|
||||
Import("env")
|
||||
|
||||
firmware_hex = "${BUILD_DIR}/${PROGNAME}.hex"
|
||||
uf2_file = os.environ.get("UF2_FILE_PATH", "${BUILD_DIR}/${PROGNAME}.uf2")
|
||||
|
||||
def create_uf2_action(source, target, env):
|
||||
uf2_cmd = " ".join(
|
||||
[
|
||||
'"$PYTHONEXE"',
|
||||
'"$PROJECT_DIR/bin/uf2conv/uf2conv.py"',
|
||||
'-f', '0xADA52840',
|
||||
'-c', firmware_hex,
|
||||
'-o', uf2_file,
|
||||
]
|
||||
)
|
||||
env.Execute(uf2_cmd)
|
||||
|
||||
env.AddCustomTarget(
|
||||
name="create_uf2",
|
||||
dependencies=firmware_hex,
|
||||
actions=create_uf2_action,
|
||||
title="Create UF2 file",
|
||||
description="Use uf2conv to convert hex binary into uf2",
|
||||
always_build=True,
|
||||
)
|
||||
@@ -44,6 +44,10 @@ bit 0 means the lowest bit (1s place)
|
||||
| `0x08` | `PAYLOAD_TYPE_PATH` | Returned path. |
|
||||
| `0x09` | `PAYLOAD_TYPE_TRACE` | trace a path, collecting SNI for each hop. |
|
||||
| `0x0A` | `PAYLOAD_TYPE_MULTIPART` | packet is part of a sequence of packets. |
|
||||
| `0x0B` | `PAYLOAD_TYPE_CONTROL` | control packet data (unencrypted) |
|
||||
| `0x0C` | . | reserved |
|
||||
| `0x0D` | . | reserved |
|
||||
| `0x0E` | . | reserved |
|
||||
| `0x0F` | `PAYLOAD_TYPE_RAW_CUSTOM` | Custom packet (raw bytes, custom encryption). |
|
||||
|
||||
## Payload Version Values
|
||||
|
||||
@@ -11,6 +11,7 @@ Inside of each [meshcore packet](./packet_structure.md) is a payload, identified
|
||||
* Group text message (unverified).
|
||||
* Group datagram (unverified).
|
||||
* Multi-part packet
|
||||
* Control data packet
|
||||
* Custom packet (raw bytes, custom encryption).
|
||||
|
||||
This document defines the structure of each of these payload types.
|
||||
@@ -57,7 +58,7 @@ Appdata Flags
|
||||
|
||||
# Acknowledgement
|
||||
|
||||
An acknowledgement that a message was received. Note that for returned path messages, an acknowledgement will be sent in the "extra" payload (see [Returned Path](#returned-path)) and not as a discrete acknowledgement. CLI commands do not require an acknowledgement, neither discrete nor extra.
|
||||
An acknowledgement that a message was received. Note that for returned path messages, an acknowledgement can be sent in the "extra" payload (see [Returned Path](#returned-path)) instead of as a separate ackowledgement packet. CLI commands do not cause acknowledgement responses, neither discrete nor extra.
|
||||
|
||||
| Field | Size (bytes) | Description |
|
||||
|----------|--------------|------------------------------------------------------------|
|
||||
@@ -140,13 +141,13 @@ Request data about sensors on the node, including battery level.
|
||||
|
||||
## Plain text message
|
||||
|
||||
| Field | Size (bytes) | Description |
|
||||
|-----------------|-----------------|--------------------------------------------------------------|
|
||||
| timestamp | 4 | send time (unix timestamp) |
|
||||
| flags + attempt | 1 | upper six bits are flags (see below), lower two bits are attempt number (0..3) |
|
||||
| message | rest of payload | the message content, see next table |
|
||||
| Field | Size (bytes) | Description |
|
||||
|--------------------|-----------------|--------------------------------------------------------------|
|
||||
| timestamp | 4 | send time (unix timestamp) |
|
||||
| txt_type + attempt | 1 | upper six bits are txt_type (see below), lower two bits are attempt number (0..3) |
|
||||
| message | rest of payload | the message content, see next table |
|
||||
|
||||
Flags
|
||||
txt_type
|
||||
|
||||
| Value | Description | Message content |
|
||||
|--------|---------------------------|------------------------------------------------------------|
|
||||
@@ -163,13 +164,20 @@ Flags
|
||||
| cipher MAC | 2 | MAC for encrypted data in next field |
|
||||
| ciphertext | rest of payload | encrypted message, see below for details |
|
||||
|
||||
Plaintext message
|
||||
## Room server login
|
||||
|
||||
| Field | Size (bytes) | Description |
|
||||
|----------------|-----------------|-------------------------------------------------------------------------------|
|
||||
| timestamp | 4 | send time (unix timestamp) |
|
||||
| sync timestamp | 4 | NOTE: room server only! - sender's "sync messages SINCE x" timestamp |
|
||||
| password | rest of message | password for repeater/room |
|
||||
| timestamp | 4 | sender time (unix timestamp) |
|
||||
| sync timestamp | 4 | sender's "sync messages SINCE x" timestamp |
|
||||
| password | rest of message | password for room |
|
||||
|
||||
## Repeater/Sensor login
|
||||
|
||||
| Field | Size (bytes) | Description |
|
||||
|----------------|-----------------|-------------------------------------------------------------------------------|
|
||||
| timestamp | 4 | sender time (unix timestamp) |
|
||||
| password | rest of message | password for repeater/sensor |
|
||||
|
||||
# Group text message / datagram
|
||||
|
||||
@@ -182,7 +190,31 @@ Plaintext message
|
||||
The plaintext contained in the ciphertext matches the format described in [plain text message](#plain-text-message). Specifically, it consists of a four byte timestamp, a flags byte, and the message. The flags byte will generally be `0x00` because it is a "plain text message". The message will be of the form `<sender name>: <message body>` (eg., `user123: I'm on my way`).
|
||||
|
||||
|
||||
TODO: describe what datagram looks like
|
||||
# Control data
|
||||
|
||||
| Field | Size (bytes) | Description |
|
||||
|--------------|-----------------|--------------------------------------------|
|
||||
| flags | 1 | upper 4 bits is sub_type |
|
||||
| data | rest of payload | typically unencrypted data |
|
||||
|
||||
## DISCOVER_REQ (sub_type)
|
||||
|
||||
| Field | Size (bytes) | Description |
|
||||
|--------------|-----------------|----------------------------------------------|
|
||||
| flags | 1 | 0x8 (upper 4 bits), prefix_only (lowest bit) |
|
||||
| type_filter | 1 | bit for each ADV_TYPE_* |
|
||||
| tag | 4 | randomly generate by sender |
|
||||
| since | 4 | (optional) epoch timestamp (0 by default) |
|
||||
|
||||
## DISCOVER_RESP (sub_type)
|
||||
|
||||
| Field | Size (bytes) | Description |
|
||||
|--------------|-----------------|--------------------------------------------|
|
||||
| flags | 1 | 0x9 (upper 4 bits), node_type (lower 4) |
|
||||
| snr | 1 | signed, SNR*4 |
|
||||
| tag | 4 | reflected back from DISCOVER_REQ |
|
||||
| pubkey | 8 or 32 | node's ID (or prefix) |
|
||||
|
||||
|
||||
# Custom packet
|
||||
|
||||
|
||||
@@ -197,11 +197,7 @@ void DataStore::loadPrefs(NodePrefs& prefs, double& node_lat, double& node_lon)
|
||||
}
|
||||
|
||||
void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& node_lat, double& node_lon) {
|
||||
#if defined(RP2040_PLATFORM)
|
||||
File file = _fs->open(filename, "r");
|
||||
#else
|
||||
File file = _fs->open(filename);
|
||||
#endif
|
||||
File file = openRead(_fs, filename);
|
||||
if (file) {
|
||||
uint8_t pad[8];
|
||||
|
||||
@@ -262,16 +258,7 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_
|
||||
}
|
||||
|
||||
void DataStore::loadContacts(DataStoreHost* host) {
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
if (_getContactsChannelsFS()->exists("/contacts3")) {
|
||||
File file = _getContactsChannelsFS()->open("/contacts3");
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
if (_fs->exists("/contacts3")) {
|
||||
File file = _fs->open("/contacts3", "r");
|
||||
#else
|
||||
if (_fs->exists("/contacts3")) {
|
||||
File file = _fs->open("/contacts3", "r", false);
|
||||
#endif
|
||||
File file = openRead(_getContactsChannelsFS(), "/contacts3");
|
||||
if (file) {
|
||||
bool full = false;
|
||||
while (!full) {
|
||||
@@ -299,7 +286,6 @@ void DataStore::loadContacts(DataStoreHost* host) {
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DataStore::saveContacts(DataStoreHost* host) {
|
||||
@@ -332,16 +318,7 @@ void DataStore::saveContacts(DataStoreHost* host) {
|
||||
}
|
||||
|
||||
void DataStore::loadChannels(DataStoreHost* host) {
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
if (_getContactsChannelsFS()->exists("/channels2")) {
|
||||
File file = _getContactsChannelsFS()->open("/channels2");
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
if (_fs->exists("/channels2")) {
|
||||
File file = _fs->open("/channels2", "r");
|
||||
#else
|
||||
if (_fs->exists("/channels2")) {
|
||||
File file = _fs->open("/channels2", "r", false);
|
||||
#endif
|
||||
File file = openRead(_getContactsChannelsFS(), "/channels2");
|
||||
if (file) {
|
||||
bool full = false;
|
||||
uint8_t channel_idx = 0;
|
||||
@@ -363,7 +340,6 @@ void DataStore::loadChannels(DataStoreHost* host) {
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DataStore::saveChannels(DataStoreHost* host) {
|
||||
@@ -520,7 +496,7 @@ void DataStore::migrateToSecondaryFS() {
|
||||
}
|
||||
|
||||
uint8_t DataStore::getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]) {
|
||||
File file = _getContactsChannelsFS()->open("/adv_blobs");
|
||||
File file = openRead(_getContactsChannelsFS(), "/adv_blobs");
|
||||
uint8_t len = 0; // 0 = not found
|
||||
if (file) {
|
||||
BlobRec tmp;
|
||||
@@ -583,11 +559,7 @@ uint8_t DataStore::getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_b
|
||||
sprintf(path, "/bl/%s", fname);
|
||||
|
||||
if (_fs->exists(path)) {
|
||||
#if defined(RP2040_PLATFORM)
|
||||
File f = _fs->open(path, "r");
|
||||
#else
|
||||
File f = _fs->open(path);
|
||||
#endif
|
||||
File f = openRead(_fs, path);
|
||||
if (f) {
|
||||
int len = f.read(dest_buf, 255); // currently MAX 255 byte blob len supported!!
|
||||
f.close();
|
||||
|
||||
@@ -50,6 +50,8 @@
|
||||
#define CMD_SEND_BINARY_REQ 50
|
||||
#define CMD_FACTORY_RESET 51
|
||||
#define CMD_SEND_PATH_DISCOVERY_REQ 52
|
||||
#define CMD_SET_FLOOD_SCOPE 54 // v8+
|
||||
#define CMD_SEND_CONTROL_DATA 55 // v8+
|
||||
|
||||
#define RESP_CODE_OK 0
|
||||
#define RESP_CODE_ERR 1
|
||||
@@ -99,6 +101,7 @@
|
||||
#define PUSH_CODE_TELEMETRY_RESPONSE 0x8B
|
||||
#define PUSH_CODE_BINARY_RESPONSE 0x8C
|
||||
#define PUSH_CODE_PATH_DISCOVERY_RESPONSE 0x8D
|
||||
#define PUSH_CODE_CONTROL_DATA 0x8E // v8+
|
||||
|
||||
#define ERR_CODE_UNSUPPORTED_CMD 1
|
||||
#define ERR_CODE_NOT_FOUND 2
|
||||
@@ -378,6 +381,35 @@ void MyMesh::queueMessage(const ContactInfo &from, uint8_t txt_type, mesh::Packe
|
||||
#endif
|
||||
}
|
||||
|
||||
bool MyMesh::filterRecvFloodPacket(mesh::Packet* packet) {
|
||||
// REVISIT: try to determine which Region (from transport_codes[1]) that Sender is indicating for replies/responses
|
||||
// if unknown, fallback to finding Region from transport_codes[0], the 'scope' used by Sender
|
||||
return false;
|
||||
}
|
||||
|
||||
void MyMesh::sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, uint32_t delay_millis) {
|
||||
// TODO: dynamic send_scope, depending on recipient and current 'home' Region
|
||||
if (send_scope.isNull()) {
|
||||
sendFlood(pkt, delay_millis);
|
||||
} else {
|
||||
uint16_t codes[2];
|
||||
codes[0] = send_scope.calcTransportCode(pkt);
|
||||
codes[1] = 0; // REVISIT: set to 'home' Region, for sender/return region?
|
||||
sendFlood(pkt, codes, delay_millis);
|
||||
}
|
||||
}
|
||||
void MyMesh::sendFloodScoped(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t delay_millis) {
|
||||
// TODO: have per-channel send_scope
|
||||
if (send_scope.isNull()) {
|
||||
sendFlood(pkt, delay_millis);
|
||||
} else {
|
||||
uint16_t codes[2];
|
||||
codes[0] = send_scope.calcTransportCode(pkt);
|
||||
codes[1] = 0; // REVISIT: set to 'home' Region, for sender/return region?
|
||||
sendFlood(pkt, codes, delay_millis);
|
||||
}
|
||||
}
|
||||
|
||||
void MyMesh::onMessageRecv(const ContactInfo &from, mesh::Packet *pkt, uint32_t sender_timestamp,
|
||||
const char *text) {
|
||||
markConnectionActive(from); // in case this is from a server, and we have a connection
|
||||
@@ -596,6 +628,26 @@ bool MyMesh::onContactPathRecv(ContactInfo& contact, uint8_t* in_path, uint8_t i
|
||||
return BaseChatMesh::onContactPathRecv(contact, in_path, in_path_len, out_path, out_path_len, extra_type, extra, extra_len);
|
||||
}
|
||||
|
||||
void MyMesh::onControlDataRecv(mesh::Packet *packet) {
|
||||
if (packet->payload_len + 4 > sizeof(out_frame)) {
|
||||
MESH_DEBUG_PRINTLN("onControlDataRecv(), payload_len too long: %d", packet->payload_len);
|
||||
return;
|
||||
}
|
||||
int i = 0;
|
||||
out_frame[i++] = PUSH_CODE_CONTROL_DATA;
|
||||
out_frame[i++] = (int8_t)(_radio->getLastSNR() * 4);
|
||||
out_frame[i++] = (int8_t)(_radio->getLastRSSI());
|
||||
out_frame[i++] = packet->path_len;
|
||||
memcpy(&out_frame[i], packet->payload, packet->payload_len);
|
||||
i += packet->payload_len;
|
||||
|
||||
if (_serial->isConnected()) {
|
||||
_serial->writeFrame(out_frame, i);
|
||||
} else {
|
||||
MESH_DEBUG_PRINTLN("onControlDataRecv(), data received while app offline");
|
||||
}
|
||||
}
|
||||
|
||||
void MyMesh::onRawDataRecv(mesh::Packet *packet) {
|
||||
if (packet->payload_len + 4 > sizeof(out_frame)) {
|
||||
MESH_DEBUG_PRINTLN("onRawDataRecv(), payload_len too long: %d", packet->payload_len);
|
||||
@@ -663,6 +715,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
|
||||
sign_data = NULL;
|
||||
dirty_contacts_expiry = 0;
|
||||
memset(advert_paths, 0, sizeof(advert_paths));
|
||||
memset(send_scope.key, 0, sizeof(send_scope.key));
|
||||
|
||||
// defaults
|
||||
memset(&_prefs, 0, sizeof(_prefs));
|
||||
@@ -706,8 +759,8 @@ void MyMesh::begin(bool has_display) {
|
||||
_prefs.rx_delay_base = constrain(_prefs.rx_delay_base, 0, 20.0f);
|
||||
_prefs.airtime_factor = constrain(_prefs.airtime_factor, 0, 9.0f);
|
||||
_prefs.freq = constrain(_prefs.freq, 400.0f, 2500.0f);
|
||||
_prefs.bw = constrain(_prefs.bw, 62.5f, 500.0f);
|
||||
_prefs.sf = constrain(_prefs.sf, 7, 12);
|
||||
_prefs.bw = constrain(_prefs.bw, 7.8f, 500.0f);
|
||||
_prefs.sf = constrain(_prefs.sf, 5, 12);
|
||||
_prefs.cr = constrain(_prefs.cr, 5, 8);
|
||||
_prefs.tx_power_dbm = constrain(_prefs.tx_power_dbm, 1, MAX_LORA_TX_POWER);
|
||||
|
||||
@@ -1485,6 +1538,21 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_FILE_IO_ERROR);
|
||||
}
|
||||
} else if (cmd_frame[0] == CMD_SET_FLOOD_SCOPE && len >= 2 && cmd_frame[1] == 0) {
|
||||
if (len >= 2 + 16) {
|
||||
memcpy(send_scope.key, &cmd_frame[2], sizeof(send_scope.key)); // set curr scope TransportKey
|
||||
} else {
|
||||
memset(send_scope.key, 0, sizeof(send_scope.key)); // set scope to null
|
||||
}
|
||||
writeOKFrame();
|
||||
} else if (cmd_frame[0] == CMD_SEND_CONTROL_DATA && len >= 2 && (cmd_frame[1] & 0x80) != 0) {
|
||||
auto resp = createControlData(&cmd_frame[1], len - 1);
|
||||
if (resp) {
|
||||
sendZeroHop(resp);
|
||||
writeOKFrame();
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_TABLE_FULL);
|
||||
}
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_UNSUPPORTED_CMD);
|
||||
MESH_DEBUG_PRINTLN("ERROR: unknown command: %02X", cmd_frame[0]);
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
#include "AbstractUITask.h"
|
||||
|
||||
/*------------ Frame Protocol --------------*/
|
||||
#define FIRMWARE_VER_CODE 7
|
||||
#define FIRMWARE_VER_CODE 8
|
||||
|
||||
#ifndef FIRMWARE_BUILD_DATE
|
||||
#define FIRMWARE_BUILD_DATE "2 Oct 2025"
|
||||
#define FIRMWARE_BUILD_DATE "13 Nov 2025"
|
||||
#endif
|
||||
|
||||
#ifndef FIRMWARE_VERSION
|
||||
#define FIRMWARE_VERSION "v1.9.1"
|
||||
#define FIRMWARE_VERSION "v1.10.0"
|
||||
#endif
|
||||
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
@@ -68,6 +68,7 @@
|
||||
#endif
|
||||
|
||||
#include <helpers/BaseChatMesh.h>
|
||||
#include <helpers/TransportKeyStore.h>
|
||||
|
||||
/* -------------------------------------------------------------------------------------- */
|
||||
|
||||
@@ -106,6 +107,10 @@ protected:
|
||||
int getInterferenceThreshold() const override;
|
||||
int calcRxDelay(float score, uint32_t air_time) const override;
|
||||
uint8_t getExtraAckTransmitCount() const override;
|
||||
bool filterRecvFloodPacket(mesh::Packet* packet) override;
|
||||
|
||||
void sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, uint32_t delay_millis=0) override;
|
||||
void sendFloodScoped(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t delay_millis=0) override;
|
||||
|
||||
void logRxRaw(float snr, float rssi, const uint8_t raw[], int len) override;
|
||||
bool isAutoAddEnabled() const override;
|
||||
@@ -128,6 +133,7 @@ protected:
|
||||
uint8_t onContactRequest(const ContactInfo &contact, uint32_t sender_timestamp, const uint8_t *data,
|
||||
uint8_t len, uint8_t *reply) override;
|
||||
void onContactResponse(const ContactInfo &contact, const uint8_t *data, uint8_t len) override;
|
||||
void onControlDataRecv(mesh::Packet *packet) override;
|
||||
void onRawDataRecv(mesh::Packet *packet) override;
|
||||
void onTraceRecv(mesh::Packet *packet, uint32_t tag, uint32_t auth_code, uint8_t flags,
|
||||
const uint8_t *path_snrs, const uint8_t *path_hashes, uint8_t path_len) override;
|
||||
@@ -191,6 +197,8 @@ private:
|
||||
uint32_t sign_data_len;
|
||||
unsigned long dirty_contacts_expiry;
|
||||
|
||||
TransportKey send_scope;
|
||||
|
||||
uint8_t cmd_frame[MAX_FRAME_SIZE + 1];
|
||||
uint8_t out_frame[MAX_FRAME_SIZE + 1];
|
||||
CayenneLPP telemetry;
|
||||
|
||||
@@ -227,4 +227,5 @@ void loop() {
|
||||
#ifdef DISPLAY_CLASS
|
||||
ui_task.loop();
|
||||
#endif
|
||||
rtc_clock.tick();
|
||||
}
|
||||
|
||||
@@ -20,7 +20,11 @@
|
||||
#define UI_RECENT_LIST_SIZE 4
|
||||
#endif
|
||||
|
||||
#define PRESS_LABEL "long press"
|
||||
#if UI_HAS_JOYSTICK
|
||||
#define PRESS_LABEL "press Enter"
|
||||
#else
|
||||
#define PRESS_LABEL "long press"
|
||||
#endif
|
||||
|
||||
#include "icons.h"
|
||||
|
||||
@@ -75,6 +79,9 @@ class HomeScreen : public UIScreen {
|
||||
RADIO,
|
||||
BLUETOOTH,
|
||||
ADVERT,
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
GPS,
|
||||
#endif
|
||||
#if UI_SENSORS_PAGE == 1
|
||||
SENSORS,
|
||||
#endif
|
||||
@@ -170,7 +177,7 @@ public:
|
||||
|
||||
// curr page indicator
|
||||
int y = 14;
|
||||
int x = display.width() / 2 - 25;
|
||||
int x = display.width() / 2 - 5 * (HomePage::Count-1);
|
||||
for (uint8_t i = 0; i < HomePage::Count; i++, x += 10) {
|
||||
if (i == _page) {
|
||||
display.fillRect(x-1, y-1, 3, 3);
|
||||
@@ -250,6 +257,34 @@ public:
|
||||
display.setColor(DisplayDriver::GREEN);
|
||||
display.drawXbm((display.width() - 32) / 2, 18, advert_icon, 32, 32);
|
||||
display.drawTextCentered(display.width() / 2, 64 - 11, "advert: " PRESS_LABEL);
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
} else if (_page == HomePage::GPS) {
|
||||
LocationProvider* nmea = sensors.getLocationProvider();
|
||||
int y = 18;
|
||||
display.drawTextLeftAlign(0, y, _task->getGPSState() ? "gps on" : "gps off");
|
||||
if (nmea == NULL) {
|
||||
y = y + 12;
|
||||
display.drawTextLeftAlign(0, y, "Can't access GPS");
|
||||
} else {
|
||||
char buf[50];
|
||||
strcpy(buf, nmea->isValid()?"fix":"no fix");
|
||||
display.drawTextRightAlign(display.width()-1, y, buf);
|
||||
y = y + 12;
|
||||
display.drawTextLeftAlign(0, y, "sat");
|
||||
sprintf(buf, "%d", nmea->satellitesCount());
|
||||
display.drawTextRightAlign(display.width()-1, y, buf);
|
||||
y = y + 12;
|
||||
display.drawTextLeftAlign(0, y, "pos");
|
||||
sprintf(buf, "%.4f %.4f",
|
||||
nmea->getLatitude()/1000000., nmea->getLongitude()/1000000.);
|
||||
display.drawTextRightAlign(display.width()-1, y, buf);
|
||||
y = y + 12;
|
||||
display.drawTextLeftAlign(0, y, "alt");
|
||||
sprintf(buf, "%.2f", nmea->getAltitude()/1000.);
|
||||
display.drawTextRightAlign(display.width()-1, y, buf);
|
||||
y = y + 12;
|
||||
}
|
||||
#endif
|
||||
#if UI_SENSORS_PAGE == 1
|
||||
} else if (_page == HomePage::SENSORS) {
|
||||
int y = 18;
|
||||
@@ -329,7 +364,7 @@ public:
|
||||
display.drawTextCentered(display.width() / 2, 34, "hibernating...");
|
||||
} else {
|
||||
display.drawXbm((display.width() - 32) / 2, 18, power_icon, 32, 32);
|
||||
display.drawTextCentered(display.width() / 2, 64 - 11, "hibernate: " PRESS_LABEL);
|
||||
display.drawTextCentered(display.width() / 2, 64 - 11, "hibernate:" PRESS_LABEL);
|
||||
}
|
||||
}
|
||||
return 5000; // next render after 5000 ms
|
||||
@@ -364,6 +399,12 @@ public:
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
if (c == KEY_ENTER && _page == HomePage::GPS) {
|
||||
_task->toggleGPS();
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#if UI_SENSORS_PAGE == 1
|
||||
if (c == KEY_ENTER && _page == HomePage::SENSORS) {
|
||||
_task->toggleGPS();
|
||||
@@ -623,19 +664,13 @@ bool UITask::isButtonPressed() const {
|
||||
|
||||
void UITask::loop() {
|
||||
char c = 0;
|
||||
#if defined(PIN_USER_BTN)
|
||||
#if UI_HAS_JOYSTICK
|
||||
int ev = user_btn.check();
|
||||
if (ev == BUTTON_EVENT_CLICK) {
|
||||
c = checkDisplayOn(KEY_NEXT);
|
||||
c = checkDisplayOn(KEY_ENTER);
|
||||
} else if (ev == BUTTON_EVENT_LONG_PRESS) {
|
||||
c = handleLongPress(KEY_ENTER);
|
||||
} else if (ev == BUTTON_EVENT_DOUBLE_CLICK) {
|
||||
c = handleDoubleClick(KEY_PREV);
|
||||
} else if (ev == BUTTON_EVENT_TRIPLE_CLICK) {
|
||||
c = handleTripleClick(KEY_SELECT);
|
||||
c = handleLongPress(KEY_ENTER); // REVISIT: could be mapped to different key code
|
||||
}
|
||||
#endif
|
||||
#if defined(WIO_TRACKER_L1)
|
||||
ev = joystick_left.check();
|
||||
if (ev == BUTTON_EVENT_CLICK) {
|
||||
c = checkDisplayOn(KEY_LEFT);
|
||||
@@ -648,9 +683,12 @@ void UITask::loop() {
|
||||
} else if (ev == BUTTON_EVENT_LONG_PRESS) {
|
||||
c = handleLongPress(KEY_RIGHT);
|
||||
}
|
||||
#endif
|
||||
#if defined(PIN_USER_BTN_ANA)
|
||||
ev = analog_btn.check();
|
||||
ev = back_btn.check();
|
||||
if (ev == BUTTON_EVENT_TRIPLE_CLICK) {
|
||||
c = handleTripleClick(KEY_SELECT);
|
||||
}
|
||||
#elif defined(PIN_USER_BTN)
|
||||
int ev = user_btn.check();
|
||||
if (ev == BUTTON_EVENT_CLICK) {
|
||||
c = checkDisplayOn(KEY_NEXT);
|
||||
} else if (ev == BUTTON_EVENT_LONG_PRESS) {
|
||||
@@ -661,6 +699,21 @@ void UITask::loop() {
|
||||
c = handleTripleClick(KEY_SELECT);
|
||||
}
|
||||
#endif
|
||||
#if defined(PIN_USER_BTN_ANA)
|
||||
if (abs(millis() - _analogue_pin_read_millis) > 10) {
|
||||
ev = analog_btn.check();
|
||||
if (ev == BUTTON_EVENT_CLICK) {
|
||||
c = checkDisplayOn(KEY_NEXT);
|
||||
} else if (ev == BUTTON_EVENT_LONG_PRESS) {
|
||||
c = handleLongPress(KEY_ENTER);
|
||||
} else if (ev == BUTTON_EVENT_DOUBLE_CLICK) {
|
||||
c = handleDoubleClick(KEY_PREV);
|
||||
} else if (ev == BUTTON_EVENT_TRIPLE_CLICK) {
|
||||
c = handleTripleClick(KEY_SELECT);
|
||||
}
|
||||
_analogue_pin_read_millis = millis();
|
||||
}
|
||||
#endif
|
||||
#if defined(DISP_BACKLIGHT) && defined(BACKLIGHT_BTN)
|
||||
if (millis() > next_backlight_btn_check) {
|
||||
bool touch_state = digitalRead(PIN_BUTTON2);
|
||||
@@ -773,6 +826,18 @@ char UITask::handleTripleClick(char c) {
|
||||
return c;
|
||||
}
|
||||
|
||||
bool UITask::getGPSState() {
|
||||
if (_sensors != NULL) {
|
||||
int num = _sensors->getNumSettings();
|
||||
for (int i = 0; i < num; i++) {
|
||||
if (strcmp(_sensors->getSettingName(i), "gps") == 0) {
|
||||
return !strcmp(_sensors->getSettingValue(i), "1");
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void UITask::toggleGPS() {
|
||||
if (_sensors != NULL) {
|
||||
// toggle GPS on/off
|
||||
|
||||
@@ -40,6 +40,10 @@ class UITask : public AbstractUITask {
|
||||
int last_led_increment = 0;
|
||||
#endif
|
||||
|
||||
#ifdef PIN_USER_BTN_ANA
|
||||
unsigned long _analogue_pin_read_millis = millis();
|
||||
#endif
|
||||
|
||||
UIScreen* splash;
|
||||
UIScreen* home;
|
||||
UIScreen* msg_preview;
|
||||
@@ -71,6 +75,7 @@ public:
|
||||
bool isButtonPressed() const;
|
||||
|
||||
void toggleBuzzer();
|
||||
bool getGPSState();
|
||||
void toggleGPS();
|
||||
|
||||
|
||||
|
||||
@@ -114,6 +114,7 @@ uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secr
|
||||
MESH_DEBUG_PRINTLN("Login success!");
|
||||
client->last_timestamp = sender_timestamp;
|
||||
client->last_activity = getRTCClock()->getCurrentTime();
|
||||
client->permissions &= ~0x03;
|
||||
client->permissions |= perms;
|
||||
memcpy(client->shared_secret, secret, PUB_KEY_SIZE);
|
||||
|
||||
@@ -148,7 +149,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t
|
||||
stats.n_packets_recv = radio_driver.getPacketsRecv();
|
||||
stats.n_packets_sent = radio_driver.getPacketsSent();
|
||||
stats.total_air_time_secs = getTotalAirTime() / 1000;
|
||||
stats.total_up_time_secs = _ms->getMillis() / 1000;
|
||||
stats.total_up_time_secs = uptime_millis / 1000;
|
||||
stats.n_sent_flood = getNumSentFlood();
|
||||
stats.n_sent_direct = getNumSentDirect();
|
||||
stats.n_recv_flood = getNumRecvFlood();
|
||||
@@ -169,7 +170,10 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t
|
||||
telemetry.reset();
|
||||
telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f);
|
||||
// query other sensors -- target specific
|
||||
sensors.querySensors((sender->isAdmin() ? 0xFF : 0x00) & perm_mask, telemetry);
|
||||
if ((sender->permissions & PERM_ACL_ROLE_MASK) == PERM_ACL_GUEST) {
|
||||
perm_mask = 0x00; // just base telemetry allowed
|
||||
}
|
||||
sensors.querySensors(perm_mask, telemetry);
|
||||
|
||||
uint8_t tlen = telemetry.getSize();
|
||||
memcpy(&reply_data[4], telemetry.getBuffer(), tlen);
|
||||
@@ -287,11 +291,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t
|
||||
|
||||
mesh::Packet *MyMesh::createSelfAdvert() {
|
||||
uint8_t app_data[MAX_ADVERT_DATA_SIZE];
|
||||
uint8_t app_data_len;
|
||||
{
|
||||
AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name, _prefs.node_lat, _prefs.node_lon);
|
||||
app_data_len = builder.encodeTo(app_data);
|
||||
}
|
||||
uint8_t app_data_len = _cli.buildAdvertData(ADV_TYPE_REPEATER, app_data);
|
||||
|
||||
return createAdvert(self_id, app_data, app_data_len);
|
||||
}
|
||||
@@ -309,6 +309,10 @@ File MyMesh::openAppend(const char *fname) {
|
||||
bool MyMesh::allowPacketForward(const mesh::Packet *packet) {
|
||||
if (_prefs.disable_fwd) return false;
|
||||
if (packet->isRouteFlood() && packet->path_len >= _prefs.flood_max) return false;
|
||||
if (packet->isRouteFlood() && recv_pkt_region == NULL) {
|
||||
MESH_DEBUG_PRINTLN("allowPacketForward: unknown transport code, or wildcard not allowed for FLOOD packet");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -331,6 +335,12 @@ void MyMesh::logRxRaw(float snr, float rssi, const uint8_t raw[], int len) {
|
||||
}
|
||||
|
||||
void MyMesh::logRx(mesh::Packet *pkt, int len, float score) {
|
||||
#ifdef WITH_BRIDGE
|
||||
if (_prefs.bridge_pkt_src == 1) {
|
||||
bridge.sendPacket(pkt);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (_logging) {
|
||||
File f = openAppend(PACKET_LOG_FILE);
|
||||
if (f) {
|
||||
@@ -352,8 +362,11 @@ void MyMesh::logRx(mesh::Packet *pkt, int len, float score) {
|
||||
|
||||
void MyMesh::logTx(mesh::Packet *pkt, int len) {
|
||||
#ifdef WITH_BRIDGE
|
||||
bridge.onPacketTransmitted(pkt);
|
||||
if (_prefs.bridge_pkt_src == 0) {
|
||||
bridge.sendPacket(pkt);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (_logging) {
|
||||
File f = openAppend(PACKET_LOG_FILE);
|
||||
if (f) {
|
||||
@@ -391,11 +404,28 @@ int MyMesh::calcRxDelay(float score, uint32_t air_time) const {
|
||||
|
||||
uint32_t MyMesh::getRetransmitDelay(const mesh::Packet *packet) {
|
||||
uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.tx_delay_factor);
|
||||
return getRNG()->nextInt(0, 6) * t;
|
||||
return getRNG()->nextInt(0, 5*t + 1);
|
||||
}
|
||||
uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) {
|
||||
uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.direct_tx_delay_factor);
|
||||
return getRNG()->nextInt(0, 6) * t;
|
||||
return getRNG()->nextInt(0, 5*t + 1);
|
||||
}
|
||||
|
||||
bool MyMesh::filterRecvFloodPacket(mesh::Packet* pkt) {
|
||||
// just try to determine region for packet (apply later in allowPacketForward())
|
||||
if (pkt->getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD) {
|
||||
recv_pkt_region = region_map.findMatch(pkt, REGION_DENY_FLOOD);
|
||||
} else if (pkt->getRouteType() == ROUTE_TYPE_FLOOD) {
|
||||
if (region_map.getWildcard().flags & REGION_DENY_FLOOD) {
|
||||
recv_pkt_region = NULL;
|
||||
} else {
|
||||
recv_pkt_region = ®ion_map.getWildcard();
|
||||
}
|
||||
} else {
|
||||
recv_pkt_region = NULL;
|
||||
}
|
||||
// do normal processing
|
||||
return false;
|
||||
}
|
||||
|
||||
void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const mesh::Identity &sender,
|
||||
@@ -406,7 +436,14 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m
|
||||
memcpy(×tamp, data, 4);
|
||||
|
||||
data[len] = 0; // ensure null terminator
|
||||
uint8_t reply_len = handleLoginReq(sender, secret, timestamp, &data[4]);
|
||||
uint8_t reply_len;
|
||||
if (data[4] == 0 || data[4] >= ' ') { // is password, ie. a login request
|
||||
reply_len = handleLoginReq(sender, secret, timestamp, &data[4]);
|
||||
//} else if (data[4] == ANON_REQ_TYPE_*) { // future type codes
|
||||
// TODO
|
||||
} else {
|
||||
reply_len = 0; // unknown request type
|
||||
}
|
||||
|
||||
if (reply_len == 0) return; // invalid request
|
||||
|
||||
@@ -442,12 +479,19 @@ void MyMesh::getPeerSharedSecret(uint8_t *dest_secret, int peer_idx) {
|
||||
}
|
||||
}
|
||||
|
||||
static bool isShare(const mesh::Packet *packet) {
|
||||
if (packet->hasTransportCodes()) {
|
||||
return packet->transport_codes[0] == 0 && packet->transport_codes[1] == 0; // codes { 0, 0 } means 'send to nowhere'
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void MyMesh::onAdvertRecv(mesh::Packet *packet, const mesh::Identity &id, uint32_t timestamp,
|
||||
const uint8_t *app_data, size_t app_data_len) {
|
||||
mesh::Mesh::onAdvertRecv(packet, id, timestamp, app_data, app_data_len); // chain to super impl
|
||||
|
||||
// if this a zero hop advert, add it to neighbours
|
||||
if (packet->path_len == 0) {
|
||||
// if this a zero hop advert (and not via 'Share'), add it to neighbours
|
||||
if (packet->path_len == 0 && !isShare(packet)) {
|
||||
AdvertDataParser parser(app_data, app_data_len);
|
||||
if (parser.isValid() && parser.getType() == ADV_TYPE_REPEATER) { // just keep neigbouring Repeaters
|
||||
putNeighbour(id, timestamp, packet->getSNR());
|
||||
@@ -577,20 +621,57 @@ bool MyMesh::onPeerPathRecv(mesh::Packet *packet, int sender_idx, const uint8_t
|
||||
return false;
|
||||
}
|
||||
|
||||
#define CTL_TYPE_NODE_DISCOVER_REQ 0x80
|
||||
#define CTL_TYPE_NODE_DISCOVER_RESP 0x90
|
||||
|
||||
void MyMesh::onControlDataRecv(mesh::Packet* packet) {
|
||||
uint8_t type = packet->payload[0] & 0xF0; // just test upper 4 bits
|
||||
if (type == CTL_TYPE_NODE_DISCOVER_REQ && packet->payload_len >= 6 && discover_limiter.allow(rtc_clock.getCurrentTime())) {
|
||||
int i = 1;
|
||||
uint8_t filter = packet->payload[i++];
|
||||
uint32_t tag;
|
||||
memcpy(&tag, &packet->payload[i], 4); i += 4;
|
||||
uint32_t since;
|
||||
if (packet->payload_len >= i+4) { // optional since field
|
||||
memcpy(&since, &packet->payload[i], 4); i += 4;
|
||||
} else {
|
||||
since = 0;
|
||||
}
|
||||
|
||||
if ((filter & (1 << ADV_TYPE_REPEATER)) != 0 && _prefs.discovery_mod_timestamp >= since) {
|
||||
bool prefix_only = packet->payload[0] & 1;
|
||||
uint8_t data[6 + PUB_KEY_SIZE];
|
||||
data[0] = CTL_TYPE_NODE_DISCOVER_RESP | ADV_TYPE_REPEATER; // low 4-bits for node type
|
||||
data[1] = packet->_snr; // let sender know the inbound SNR ( x 4)
|
||||
memcpy(&data[2], &tag, 4); // include tag from request, for client to match to
|
||||
memcpy(&data[6], self_id.pub_key, PUB_KEY_SIZE);
|
||||
auto resp = createControlData(data, prefix_only ? 6 + 8 : 6 + PUB_KEY_SIZE);
|
||||
if (resp) {
|
||||
sendZeroHop(resp, getRetransmitDelay(resp)*4); // apply random delay (widened x4), as multiple nodes can respond to this
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondClock &ms, mesh::RNG &rng,
|
||||
mesh::RTCClock &rtc, mesh::MeshTables &tables)
|
||||
: mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables),
|
||||
_cli(board, rtc, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4)
|
||||
_cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4), region_map(key_store), temp_map(key_store),
|
||||
discover_limiter(4, 120) // max 4 every 2 minutes
|
||||
#if defined(WITH_RS232_BRIDGE)
|
||||
, bridge(WITH_RS232_BRIDGE, _mgr, &rtc)
|
||||
#elif defined(WITH_ESPNOW_BRIDGE)
|
||||
, bridge(_mgr, &rtc)
|
||||
, bridge(&_prefs, WITH_RS232_BRIDGE, _mgr, &rtc)
|
||||
#endif
|
||||
#if defined(WITH_ESPNOW_BRIDGE)
|
||||
, bridge(&_prefs, _mgr, &rtc)
|
||||
#endif
|
||||
{
|
||||
last_millis = 0;
|
||||
uptime_millis = 0;
|
||||
next_local_advert = next_flood_advert = 0;
|
||||
dirty_contacts_expiry = 0;
|
||||
set_radio_at = revert_radio_at = 0;
|
||||
_logging = false;
|
||||
region_load_active = false;
|
||||
|
||||
#if MAX_NEIGHBOURS
|
||||
memset(neighbours, 0, sizeof(neighbours));
|
||||
@@ -601,6 +682,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
_prefs.airtime_factor = 1.0; // one half
|
||||
_prefs.rx_delay_base = 0.0f; // turn off by default, was 10.0;
|
||||
_prefs.tx_delay_factor = 0.5f; // was 0.25f
|
||||
_prefs.direct_tx_delay_factor = 0.2f; // was zero
|
||||
StrHelper::strncpy(_prefs.node_name, ADVERT_NAME, sizeof(_prefs.node_name));
|
||||
_prefs.node_lat = ADVERT_LAT;
|
||||
_prefs.node_lon = ADVERT_LON;
|
||||
@@ -614,6 +696,20 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
_prefs.flood_advert_interval = 12; // 12 hours
|
||||
_prefs.flood_max = 64;
|
||||
_prefs.interference_threshold = 0; // disabled
|
||||
|
||||
// bridge defaults
|
||||
_prefs.bridge_enabled = 1; // enabled
|
||||
_prefs.bridge_delay = 500; // milliseconds
|
||||
_prefs.bridge_pkt_src = 0; // logTx
|
||||
_prefs.bridge_baud = 115200; // baud rate
|
||||
_prefs.bridge_channel = 1; // channel 1
|
||||
|
||||
StrHelper::strncpy(_prefs.bridge_secret, "LVSITANOS", sizeof(_prefs.bridge_secret));
|
||||
|
||||
// GPS defaults
|
||||
_prefs.gps_enabled = 0;
|
||||
_prefs.gps_interval = 0;
|
||||
_prefs.advert_loc_policy = ADVERT_LOC_PREFS;
|
||||
}
|
||||
|
||||
void MyMesh::begin(FILESYSTEM *fs) {
|
||||
@@ -621,11 +717,14 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
||||
_fs = fs;
|
||||
// load persisted prefs
|
||||
_cli.loadPrefs(_fs);
|
||||
|
||||
acl.load(_fs);
|
||||
// TODO: key_store.begin();
|
||||
region_map.load(_fs);
|
||||
|
||||
#ifdef WITH_BRIDGE
|
||||
bridge.begin();
|
||||
#if defined(WITH_BRIDGE)
|
||||
if (_prefs.bridge_enabled) {
|
||||
bridge.begin();
|
||||
}
|
||||
#endif
|
||||
|
||||
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||
@@ -633,6 +732,10 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
||||
|
||||
updateAdvertTimer();
|
||||
updateFloodAdvertTimer();
|
||||
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
applyGpsPrefs();
|
||||
#endif
|
||||
}
|
||||
|
||||
void MyMesh::applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) {
|
||||
@@ -758,6 +861,19 @@ void MyMesh::removeNeighbor(const uint8_t *pubkey, int key_len) {
|
||||
#endif
|
||||
}
|
||||
|
||||
void MyMesh::formatStatsReply(char *reply) {
|
||||
StatsFormatHelper::formatCoreStats(reply, board, *_ms, _err_flags, _mgr);
|
||||
}
|
||||
|
||||
void MyMesh::formatRadioStatsReply(char *reply) {
|
||||
StatsFormatHelper::formatRadioStats(reply, _radio, radio_driver, getTotalAirTime(), getReceiveAirTime());
|
||||
}
|
||||
|
||||
void MyMesh::formatPacketStatsReply(char *reply) {
|
||||
StatsFormatHelper::formatPacketStats(reply, radio_driver, getNumSentFlood(), getNumSentDirect(),
|
||||
getNumRecvFlood(), getNumRecvDirect());
|
||||
}
|
||||
|
||||
void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id) {
|
||||
self_id = new_id;
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
@@ -779,8 +895,41 @@ void MyMesh::clearStats() {
|
||||
}
|
||||
|
||||
void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply) {
|
||||
while (*command == ' ')
|
||||
command++; // skip leading spaces
|
||||
if (region_load_active) {
|
||||
if (StrHelper::isBlank(command)) { // empty/blank line, signal to terminate 'load' operation
|
||||
region_map = temp_map; // copy over the temp instance as new current map
|
||||
region_load_active = false;
|
||||
|
||||
sprintf(reply, "OK - loaded %d regions", region_map.getCount());
|
||||
} else {
|
||||
char *np = command;
|
||||
while (*np == ' ') np++; // skip indent
|
||||
int indent = np - command;
|
||||
|
||||
char *ep = np;
|
||||
while (RegionMap::is_name_char(*ep)) ep++;
|
||||
if (*ep) { *ep++ = 0; } // set null terminator for end of name
|
||||
|
||||
while (*ep && *ep != 'F') ep++; // look for (optional) flags
|
||||
|
||||
if (indent > 0 && indent < 8 && strlen(np) > 0) {
|
||||
auto parent = load_stack[indent - 1];
|
||||
if (parent) {
|
||||
auto old = region_map.findByName(np);
|
||||
auto nw = temp_map.putRegion(np, parent->id, old ? old->id : 0); // carry-over the current ID (if name already exists)
|
||||
if (nw) {
|
||||
nw->flags = old ? old->flags : (*ep == 'F' ? 0 : REGION_DENY_FLOOD); // carry-over flags from curr
|
||||
|
||||
load_stack[indent] = nw; // keep pointers to parent regions, to resolve parent_id's
|
||||
}
|
||||
}
|
||||
}
|
||||
reply[0] = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
while (*command == ' ') command++; // skip leading spaces
|
||||
|
||||
if (strlen(command) > 4 && command[2] == '|') { // optional prefix (for companion radio CLI)
|
||||
memcpy(reply, command, 3); // reflect the prefix back
|
||||
@@ -822,6 +971,88 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply
|
||||
Serial.printf("\n");
|
||||
}
|
||||
reply[0] = 0;
|
||||
} else if (memcmp(command, "region", 6) == 0) {
|
||||
reply[0] = 0;
|
||||
|
||||
const char* parts[4];
|
||||
int n = mesh::Utils::parseTextParts(command, parts, 4, ' ');
|
||||
if (n == 1 && sender_timestamp == 0) {
|
||||
region_map.exportTo(Serial);
|
||||
} else if (n >= 2 && strcmp(parts[1], "load") == 0) {
|
||||
temp_map.resetFrom(region_map); // rebuild regions in a temp instance
|
||||
memset(load_stack, 0, sizeof(load_stack));
|
||||
load_stack[0] = &temp_map.getWildcard();
|
||||
region_load_active = true;
|
||||
} else if (n >= 2 && strcmp(parts[1], "save") == 0) {
|
||||
_prefs.discovery_mod_timestamp = rtc_clock.getCurrentTime(); // this node is now 'modified' (for discovery info)
|
||||
savePrefs();
|
||||
bool success = region_map.save(_fs);
|
||||
strcpy(reply, success ? "OK" : "Err - save failed");
|
||||
} else if (n >= 3 && strcmp(parts[1], "allowf") == 0) {
|
||||
auto region = region_map.findByNamePrefix(parts[2]);
|
||||
if (region) {
|
||||
region->flags &= ~REGION_DENY_FLOOD;
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Err - unknown region");
|
||||
}
|
||||
} else if (n >= 3 && strcmp(parts[1], "denyf") == 0) {
|
||||
auto region = region_map.findByNamePrefix(parts[2]);
|
||||
if (region) {
|
||||
region->flags |= REGION_DENY_FLOOD;
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Err - unknown region");
|
||||
}
|
||||
} else if (n >= 3 && strcmp(parts[1], "get") == 0) {
|
||||
auto region = region_map.findByNamePrefix(parts[2]);
|
||||
if (region) {
|
||||
auto parent = region_map.findById(region->parent);
|
||||
if (parent && parent->id != 0) {
|
||||
sprintf(reply, " %s (%s) %s", region->name, parent->name, (region->flags & REGION_DENY_FLOOD) ? "" : "F");
|
||||
} else {
|
||||
sprintf(reply, " %s %s", region->name, (region->flags & REGION_DENY_FLOOD) ? "" : "F");
|
||||
}
|
||||
} else {
|
||||
strcpy(reply, "Err - unknown region");
|
||||
}
|
||||
} else if (n >= 3 && strcmp(parts[1], "home") == 0) {
|
||||
auto home = region_map.findByNamePrefix(parts[2]);
|
||||
if (home) {
|
||||
region_map.setHomeRegion(home);
|
||||
sprintf(reply, " home is now %s", home->name);
|
||||
} else {
|
||||
strcpy(reply, "Err - unknown region");
|
||||
}
|
||||
} else if (n == 2 && strcmp(parts[1], "home") == 0) {
|
||||
auto home = region_map.getHomeRegion();
|
||||
sprintf(reply, " home is %s", home ? home->name : "*");
|
||||
} else if (n >= 3 && strcmp(parts[1], "put") == 0) {
|
||||
auto parent = n >= 4 ? region_map.findByNamePrefix(parts[3]) : ®ion_map.getWildcard();
|
||||
if (parent == NULL) {
|
||||
strcpy(reply, "Err - unknown parent");
|
||||
} else {
|
||||
auto region = region_map.putRegion(parts[2], parent->id);
|
||||
if (region == NULL) {
|
||||
strcpy(reply, "Err - unable to put");
|
||||
} else {
|
||||
strcpy(reply, "OK");
|
||||
}
|
||||
}
|
||||
} else if (n >= 3 && strcmp(parts[1], "remove") == 0) {
|
||||
auto region = region_map.findByName(parts[2]);
|
||||
if (region) {
|
||||
if (region_map.removeRegion(*region)) {
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Err - not empty");
|
||||
}
|
||||
} else {
|
||||
strcpy(reply, "Err - not found");
|
||||
}
|
||||
} else {
|
||||
strcpy(reply, "Err - ??");
|
||||
}
|
||||
} else{
|
||||
_cli.handleCommand(sender_timestamp, command, reply); // common CLI commands
|
||||
}
|
||||
@@ -864,4 +1095,9 @@ void MyMesh::loop() {
|
||||
acl.save(_fs);
|
||||
dirty_contacts_expiry = 0;
|
||||
}
|
||||
|
||||
// update uptime
|
||||
uint32_t now = millis();
|
||||
uptime_millis += now - last_millis;
|
||||
last_millis = now;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Mesh.h>
|
||||
#include <helpers/CommonCLI.h>
|
||||
#include <RTClib.h>
|
||||
#include <target.h>
|
||||
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
#include <InternalFileSystem.h>
|
||||
@@ -12,16 +13,6 @@
|
||||
#include <SPIFFS.h>
|
||||
#endif
|
||||
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
#include <helpers/StaticPoolPacketManager.h>
|
||||
#include <helpers/SimpleMeshTables.h>
|
||||
#include <helpers/IdentityStore.h>
|
||||
#include <helpers/AdvertDataHelpers.h>
|
||||
#include <helpers/TxtDataHelpers.h>
|
||||
#include <helpers/ClientACL.h>
|
||||
#include <RTClib.h>
|
||||
#include <target.h>
|
||||
|
||||
#ifdef WITH_RS232_BRIDGE
|
||||
#include "helpers/bridges/RS232Bridge.h"
|
||||
#define WITH_BRIDGE
|
||||
@@ -32,6 +23,18 @@
|
||||
#define WITH_BRIDGE
|
||||
#endif
|
||||
|
||||
#include <helpers/AdvertDataHelpers.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
#include <helpers/ClientACL.h>
|
||||
#include <helpers/CommonCLI.h>
|
||||
#include <helpers/IdentityStore.h>
|
||||
#include <helpers/SimpleMeshTables.h>
|
||||
#include <helpers/StaticPoolPacketManager.h>
|
||||
#include <helpers/StatsFormatHelper.h>
|
||||
#include <helpers/TxtDataHelpers.h>
|
||||
#include <helpers/RegionMap.h>
|
||||
#include "RateLimiter.h"
|
||||
|
||||
#ifdef WITH_BRIDGE
|
||||
extern AbstractBridge* bridge;
|
||||
#endif
|
||||
@@ -65,11 +68,11 @@ struct NeighbourInfo {
|
||||
};
|
||||
|
||||
#ifndef FIRMWARE_BUILD_DATE
|
||||
#define FIRMWARE_BUILD_DATE "2 Oct 2025"
|
||||
#define FIRMWARE_BUILD_DATE "13 Nov 2025"
|
||||
#endif
|
||||
|
||||
#ifndef FIRMWARE_VERSION
|
||||
#define FIRMWARE_VERSION "v1.9.1"
|
||||
#define FIRMWARE_VERSION "v1.10.0"
|
||||
#endif
|
||||
|
||||
#define FIRMWARE_ROLE "repeater"
|
||||
@@ -78,12 +81,20 @@ struct NeighbourInfo {
|
||||
|
||||
class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
||||
FILESYSTEM* _fs;
|
||||
uint32_t last_millis;
|
||||
uint64_t uptime_millis;
|
||||
unsigned long next_local_advert, next_flood_advert;
|
||||
bool _logging;
|
||||
NodePrefs _prefs;
|
||||
CommonCLI _cli;
|
||||
uint8_t reply_data[MAX_PACKET_PAYLOAD];
|
||||
ClientACL acl;
|
||||
TransportKeyStore key_store;
|
||||
RegionMap region_map, temp_map;
|
||||
RegionEntry* load_stack[8];
|
||||
RegionEntry* recv_pkt_region;
|
||||
RateLimiter discover_limiter;
|
||||
bool region_load_active;
|
||||
unsigned long dirty_contacts_expiry;
|
||||
#if MAX_NEIGHBOURS
|
||||
NeighbourInfo neighbours[MAX_NEIGHBOURS];
|
||||
@@ -135,12 +146,21 @@ protected:
|
||||
return _prefs.multi_acks;
|
||||
}
|
||||
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
void applyGpsPrefs() {
|
||||
sensors.setSettingValue("gps", _prefs.gps_enabled?"1":"0");
|
||||
}
|
||||
#endif
|
||||
|
||||
bool filterRecvFloodPacket(mesh::Packet* pkt) override;
|
||||
|
||||
void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override;
|
||||
int searchPeersByHash(const uint8_t* hash) override;
|
||||
void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) override;
|
||||
void onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len);
|
||||
void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override;
|
||||
bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override;
|
||||
void onControlDataRecv(mesh::Packet* packet) override;
|
||||
|
||||
public:
|
||||
MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables);
|
||||
@@ -175,6 +195,9 @@ public:
|
||||
void setTxPower(uint8_t power_dbm) override;
|
||||
void formatNeighborsReply(char *reply) override;
|
||||
void removeNeighbor(const uint8_t* pubkey, int key_len) override;
|
||||
void formatStatsReply(char *reply) override;
|
||||
void formatRadioStatsReply(char *reply) override;
|
||||
void formatPacketStatsReply(char *reply) override;
|
||||
|
||||
mesh::LocalIdentity& getSelfId() override { return self_id; }
|
||||
|
||||
@@ -182,4 +205,24 @@ public:
|
||||
void clearStats() override;
|
||||
void handleCommand(uint32_t sender_timestamp, char* command, char* reply);
|
||||
void loop();
|
||||
|
||||
#if defined(WITH_BRIDGE)
|
||||
void setBridgeState(bool enable) override {
|
||||
if (enable == bridge.isRunning()) return;
|
||||
if (enable)
|
||||
{
|
||||
bridge.begin();
|
||||
}
|
||||
else
|
||||
{
|
||||
bridge.end();
|
||||
}
|
||||
}
|
||||
|
||||
void restartBridge() override {
|
||||
if (!bridge.isRunning()) return;
|
||||
bridge.end();
|
||||
bridge.begin();
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
23
examples/simple_repeater/RateLimiter.h
Normal file
23
examples/simple_repeater/RateLimiter.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
class RateLimiter {
|
||||
uint32_t _start_timestamp;
|
||||
uint32_t _secs;
|
||||
uint16_t _maximum, _count;
|
||||
|
||||
public:
|
||||
RateLimiter(uint16_t maximum, uint32_t secs): _maximum(maximum), _secs(secs), _start_timestamp(0), _count(0) { }
|
||||
|
||||
bool allow(uint32_t now) {
|
||||
if (now < _start_timestamp + _secs) {
|
||||
_count++;
|
||||
if (_count > _maximum) return false; // deny
|
||||
} else { // time window now expired
|
||||
_start_timestamp = now;
|
||||
_count = 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
@@ -91,14 +91,16 @@ void loop() {
|
||||
if (c != '\n') {
|
||||
command[len++] = c;
|
||||
command[len] = 0;
|
||||
Serial.print(c);
|
||||
}
|
||||
Serial.print(c);
|
||||
if (c == '\r') break;
|
||||
}
|
||||
if (len == sizeof(command)-1) { // command buffer full
|
||||
command[sizeof(command)-1] = '\r';
|
||||
}
|
||||
|
||||
if (len > 0 && command[len - 1] == '\r') { // received complete line
|
||||
Serial.print('\n');
|
||||
command[len - 1] = 0; // replace newline with C string null terminator
|
||||
char reply[160];
|
||||
the_mesh.handleCommand(0, command, reply); // NOTE: there is no sender_timestamp via serial!
|
||||
@@ -114,4 +116,5 @@ void loop() {
|
||||
#ifdef DISPLAY_CLASS
|
||||
ui_task.loop();
|
||||
#endif
|
||||
rtc_clock.tick();
|
||||
}
|
||||
|
||||
@@ -114,11 +114,7 @@ bool MyMesh::processAck(const uint8_t *data) {
|
||||
|
||||
mesh::Packet *MyMesh::createSelfAdvert() {
|
||||
uint8_t app_data[MAX_ADVERT_DATA_SIZE];
|
||||
uint8_t app_data_len;
|
||||
{
|
||||
AdvertDataBuilder builder(ADV_TYPE_ROOM, _prefs.node_name, _prefs.node_lat, _prefs.node_lon);
|
||||
app_data_len = builder.encodeTo(app_data);
|
||||
}
|
||||
uint8_t app_data_len = _cli.buildAdvertData(ADV_TYPE_ROOM, app_data);
|
||||
|
||||
return createAdvert(self_id, app_data, app_data_len);
|
||||
}
|
||||
@@ -148,7 +144,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t
|
||||
stats.n_packets_recv = radio_driver.getPacketsRecv();
|
||||
stats.n_packets_sent = radio_driver.getPacketsSent();
|
||||
stats.total_air_time_secs = getTotalAirTime() / 1000;
|
||||
stats.total_up_time_secs = _ms->getMillis() / 1000;
|
||||
stats.total_up_time_secs = uptime_millis / 1000;
|
||||
stats.n_sent_flood = getNumSentFlood();
|
||||
stats.n_sent_direct = getNumSentDirect();
|
||||
stats.n_recv_flood = getNumRecvFlood();
|
||||
@@ -169,7 +165,10 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t
|
||||
telemetry.reset();
|
||||
telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f);
|
||||
// query other sensors -- target specific
|
||||
sensors.querySensors((sender->isAdmin() ? 0xFF : 0x00) & perm_mask, telemetry);
|
||||
if ((sender->permissions & PERM_ACL_ROLE_MASK) == PERM_ACL_GUEST) {
|
||||
perm_mask = 0x00; // just base telemetry allowed
|
||||
}
|
||||
sensors.querySensors(perm_mask, telemetry);
|
||||
|
||||
uint8_t tlen = telemetry.getSize();
|
||||
memcpy(&reply_data[4], telemetry.getBuffer(), tlen);
|
||||
@@ -266,11 +265,11 @@ const char *MyMesh::getLogDateTime() {
|
||||
|
||||
uint32_t MyMesh::getRetransmitDelay(const mesh::Packet *packet) {
|
||||
uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.tx_delay_factor);
|
||||
return getRNG()->nextInt(0, 6) * t;
|
||||
return getRNG()->nextInt(0, 5*t + 1);
|
||||
}
|
||||
uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) {
|
||||
uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.direct_tx_delay_factor);
|
||||
return getRNG()->nextInt(0, 6) * t;
|
||||
return getRNG()->nextInt(0, 5*t + 1);
|
||||
}
|
||||
|
||||
bool MyMesh::allowPacketForward(const mesh::Packet *packet) {
|
||||
@@ -290,7 +289,7 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m
|
||||
data[len] = 0; // ensure null terminator
|
||||
|
||||
ClientInfo* client = NULL;
|
||||
if (data[8] == 0 && !_prefs.allow_read_only) { // blank password, just check if sender is in ACL
|
||||
if (data[8] == 0) { // blank password, just check if sender is in ACL
|
||||
client = acl.getClient(sender.pub_key, PUB_KEY_SIZE);
|
||||
if (client == NULL) {
|
||||
#if MESH_DEBUG
|
||||
@@ -326,6 +325,7 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m
|
||||
client->extra.room.push_failures = 0;
|
||||
|
||||
client->last_activity = getRTCClock()->getCurrentTime();
|
||||
client->permissions &= ~0x03;
|
||||
client->permissions |= perm;
|
||||
memcpy(client->shared_secret, secret, PUB_KEY_SIZE);
|
||||
|
||||
@@ -583,7 +583,9 @@ void MyMesh::onAckRecv(mesh::Packet *packet, uint32_t ack_crc) {
|
||||
MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondClock &ms, mesh::RNG &rng,
|
||||
mesh::RTCClock &rtc, mesh::MeshTables &tables)
|
||||
: mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables),
|
||||
_cli(board, rtc, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) {
|
||||
_cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) {
|
||||
last_millis = 0;
|
||||
uptime_millis = 0;
|
||||
next_local_advert = next_flood_advert = 0;
|
||||
dirty_contacts_expiry = 0;
|
||||
_logging = false;
|
||||
@@ -594,6 +596,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
_prefs.airtime_factor = 1.0; // one half
|
||||
_prefs.rx_delay_base = 0.0f; // off by default, was 10.0
|
||||
_prefs.tx_delay_factor = 0.5f; // was 0.25f;
|
||||
_prefs.direct_tx_delay_factor = 0.2f; // was zero
|
||||
StrHelper::strncpy(_prefs.node_name, ADVERT_NAME, sizeof(_prefs.node_name));
|
||||
_prefs.node_lat = ADVERT_LAT;
|
||||
_prefs.node_lon = ADVERT_LON;
|
||||
@@ -612,6 +615,11 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
StrHelper::strncpy(_prefs.guest_password, ROOM_PASSWORD, sizeof(_prefs.guest_password));
|
||||
#endif
|
||||
|
||||
// GPS defaults
|
||||
_prefs.gps_enabled = 0;
|
||||
_prefs.gps_interval = 0;
|
||||
_prefs.advert_loc_policy = ADVERT_LOC_PREFS;
|
||||
|
||||
next_post_idx = 0;
|
||||
next_client_idx = 0;
|
||||
next_push = 0;
|
||||
@@ -632,6 +640,10 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
||||
|
||||
updateAdvertTimer();
|
||||
updateFloodAdvertTimer();
|
||||
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
applyGpsPrefs();
|
||||
#endif
|
||||
}
|
||||
|
||||
void MyMesh::applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) {
|
||||
@@ -721,6 +733,19 @@ void MyMesh::clearStats() {
|
||||
((SimpleMeshTables *)getTables())->resetStats();
|
||||
}
|
||||
|
||||
void MyMesh::formatStatsReply(char *reply) {
|
||||
StatsFormatHelper::formatCoreStats(reply, board, *_ms, _err_flags, _mgr);
|
||||
}
|
||||
|
||||
void MyMesh::formatRadioStatsReply(char *reply) {
|
||||
StatsFormatHelper::formatRadioStats(reply, _radio, radio_driver, getTotalAirTime(), getReceiveAirTime());
|
||||
}
|
||||
|
||||
void MyMesh::formatPacketStatsReply(char *reply) {
|
||||
StatsFormatHelper::formatPacketStats(reply, radio_driver, getNumSentFlood(), getNumSentDirect(),
|
||||
getNumRecvFlood(), getNumRecvDirect());
|
||||
}
|
||||
|
||||
void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply) {
|
||||
while (*command == ' ')
|
||||
command++; // skip leading spaces
|
||||
@@ -852,4 +877,9 @@ void MyMesh::loop() {
|
||||
}
|
||||
|
||||
// TODO: periodically check for OLD/inactive entries in known_clients[], and evict
|
||||
|
||||
// update uptime
|
||||
uint32_t now = millis();
|
||||
uptime_millis += now - last_millis;
|
||||
last_millis = now;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <helpers/AdvertDataHelpers.h>
|
||||
#include <helpers/TxtDataHelpers.h>
|
||||
#include <helpers/CommonCLI.h>
|
||||
#include <helpers/StatsFormatHelper.h>
|
||||
#include <helpers/ClientACL.h>
|
||||
#include <RTClib.h>
|
||||
#include <target.h>
|
||||
@@ -25,11 +26,11 @@
|
||||
/* ------------------------------ Config -------------------------------- */
|
||||
|
||||
#ifndef FIRMWARE_BUILD_DATE
|
||||
#define FIRMWARE_BUILD_DATE "2 Oct 2025"
|
||||
#define FIRMWARE_BUILD_DATE "13 Nov 2025"
|
||||
#endif
|
||||
|
||||
#ifndef FIRMWARE_VERSION
|
||||
#define FIRMWARE_VERSION "v1.9.1"
|
||||
#define FIRMWARE_VERSION "v1.10.0"
|
||||
#endif
|
||||
|
||||
#ifndef LORA_FREQ
|
||||
@@ -88,6 +89,8 @@ struct PostInfo {
|
||||
|
||||
class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
||||
FILESYSTEM* _fs;
|
||||
uint32_t last_millis;
|
||||
uint64_t uptime_millis;
|
||||
unsigned long next_local_advert, next_flood_advert;
|
||||
bool _logging;
|
||||
NodePrefs _prefs;
|
||||
@@ -149,6 +152,12 @@ protected:
|
||||
bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override;
|
||||
void onAckRecv(mesh::Packet* packet, uint32_t ack_crc) override;
|
||||
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
void applyGpsPrefs() {
|
||||
sensors.setSettingValue("gps", _prefs.gps_enabled?"1":"0");
|
||||
}
|
||||
#endif
|
||||
|
||||
public:
|
||||
MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables);
|
||||
|
||||
@@ -184,6 +193,9 @@ public:
|
||||
void formatNeighborsReply(char *reply) override {
|
||||
strcpy(reply, "not supported");
|
||||
}
|
||||
void formatStatsReply(char *reply) override;
|
||||
void formatRadioStatsReply(char *reply) override;
|
||||
void formatPacketStatsReply(char *reply) override;
|
||||
|
||||
mesh::LocalIdentity& getSelfId() override { return self_id; }
|
||||
|
||||
|
||||
@@ -110,4 +110,5 @@ void loop() {
|
||||
#ifdef DISPLAY_CLASS
|
||||
ui_task.loop();
|
||||
#endif
|
||||
rtc_clock.tick();
|
||||
}
|
||||
|
||||
@@ -548,7 +548,7 @@ public:
|
||||
|
||||
StdRNG fast_rng;
|
||||
SimpleMeshTables tables;
|
||||
MyMesh the_mesh(radio_driver, fast_rng, *new VolatileRTCClock(), tables); // TODO: test with 'rtc_clock' in target.cpp
|
||||
MyMesh the_mesh(radio_driver, fast_rng, rtc_clock, tables);
|
||||
|
||||
void halt() {
|
||||
while (1) ;
|
||||
@@ -587,4 +587,5 @@ void setup() {
|
||||
|
||||
void loop() {
|
||||
the_mesh.loop();
|
||||
rtc_clock.tick();
|
||||
}
|
||||
|
||||
@@ -239,11 +239,7 @@ uint8_t SensorMesh::handleRequest(uint8_t perms, uint32_t sender_timestamp, uint
|
||||
|
||||
mesh::Packet* SensorMesh::createSelfAdvert() {
|
||||
uint8_t app_data[MAX_ADVERT_DATA_SIZE];
|
||||
uint8_t app_data_len;
|
||||
{
|
||||
AdvertDataBuilder builder(ADV_TYPE_SENSOR, _prefs.node_name, _prefs.node_lat, _prefs.node_lon);
|
||||
app_data_len = builder.encodeTo(app_data);
|
||||
}
|
||||
uint8_t app_data_len = _cli.buildAdvertData(ADV_TYPE_SENSOR, app_data);
|
||||
|
||||
return createAdvert(self_id, app_data, app_data_len);
|
||||
}
|
||||
@@ -453,7 +449,14 @@ void SensorMesh::onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, con
|
||||
memcpy(×tamp, data, 4);
|
||||
|
||||
data[len] = 0; // ensure null terminator
|
||||
uint8_t reply_len = handleLoginReq(sender, secret, timestamp, &data[4]);
|
||||
uint8_t reply_len;
|
||||
if (data[4] == 0 || data[4] >= ' ') { // is password, ie. a login request
|
||||
reply_len = handleLoginReq(sender, secret, timestamp, &data[4]);
|
||||
//} else if (data[4] == ANON_REQ_TYPE_*) { // future type codes
|
||||
// TODO
|
||||
} else {
|
||||
reply_len = 0; // unknown request type
|
||||
}
|
||||
|
||||
if (reply_len == 0) return; // invalid request
|
||||
|
||||
@@ -614,6 +617,39 @@ bool SensorMesh::handleIncomingMsg(ClientInfo& from, uint32_t timestamp, uint8_t
|
||||
return false;
|
||||
}
|
||||
|
||||
#define CTL_TYPE_NODE_DISCOVER_REQ 0x80
|
||||
#define CTL_TYPE_NODE_DISCOVER_RESP 0x90
|
||||
|
||||
void SensorMesh::onControlDataRecv(mesh::Packet* packet) {
|
||||
uint8_t type = packet->payload[0] & 0xF0; // just test upper 4 bits
|
||||
if (type == CTL_TYPE_NODE_DISCOVER_REQ && packet->payload_len >= 6) {
|
||||
// TODO: apply rate limiting to these!
|
||||
int i = 1;
|
||||
uint8_t filter = packet->payload[i++];
|
||||
uint32_t tag;
|
||||
memcpy(&tag, &packet->payload[i], 4); i += 4;
|
||||
uint32_t since;
|
||||
if (packet->payload_len >= i+4) { // optional since field
|
||||
memcpy(&since, &packet->payload[i], 4); i += 4;
|
||||
} else {
|
||||
since = 0;
|
||||
}
|
||||
|
||||
if ((filter & (1 << ADV_TYPE_SENSOR)) != 0 && _prefs.discovery_mod_timestamp >= since) {
|
||||
bool prefix_only = packet->payload[0] & 1;
|
||||
uint8_t data[6 + PUB_KEY_SIZE];
|
||||
data[0] = CTL_TYPE_NODE_DISCOVER_RESP | ADV_TYPE_SENSOR; // low 4-bits for node type
|
||||
data[1] = packet->_snr; // let sender know the inbound SNR ( x 4)
|
||||
memcpy(&data[2], &tag, 4); // include tag from request, for client to match to
|
||||
memcpy(&data[6], self_id.pub_key, PUB_KEY_SIZE);
|
||||
auto resp = createControlData(data, prefix_only ? 6 + 8 : 6 + PUB_KEY_SIZE);
|
||||
if (resp) {
|
||||
sendZeroHop(resp, getRetransmitDelay(resp)*4); // apply random delay (widened x4), as multiple nodes can respond to this
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SensorMesh::onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) {
|
||||
int i = matching_peer_indexes[sender_idx];
|
||||
if (i < 0 || i >= acl.getNumClients()) {
|
||||
@@ -655,7 +691,7 @@ void SensorMesh::onAckRecv(mesh::Packet* packet, uint32_t ack_crc) {
|
||||
|
||||
SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables)
|
||||
: mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables),
|
||||
_cli(board, rtc, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4)
|
||||
_cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4)
|
||||
{
|
||||
next_local_advert = next_flood_advert = 0;
|
||||
dirty_contacts_expiry = 0;
|
||||
@@ -668,6 +704,7 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise
|
||||
_prefs.airtime_factor = 1.0; // one half
|
||||
_prefs.rx_delay_base = 0.0f; // turn off by default, was 10.0;
|
||||
_prefs.tx_delay_factor = 0.5f; // was 0.25f
|
||||
_prefs.direct_tx_delay_factor = 0.2f; // was zero
|
||||
StrHelper::strncpy(_prefs.node_name, ADVERT_NAME, sizeof(_prefs.node_name));
|
||||
_prefs.node_lat = ADVERT_LAT;
|
||||
_prefs.node_lon = ADVERT_LON;
|
||||
@@ -682,6 +719,11 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise
|
||||
_prefs.disable_fwd = true;
|
||||
_prefs.flood_max = 64;
|
||||
_prefs.interference_threshold = 0; // disabled
|
||||
|
||||
// GPS defaults
|
||||
_prefs.gps_enabled = 0;
|
||||
_prefs.gps_interval = 0;
|
||||
_prefs.advert_loc_policy = ADVERT_LOC_PREFS;
|
||||
}
|
||||
|
||||
void SensorMesh::begin(FILESYSTEM* fs) {
|
||||
@@ -697,6 +739,10 @@ void SensorMesh::begin(FILESYSTEM* fs) {
|
||||
|
||||
updateAdvertTimer();
|
||||
updateFloodAdvertTimer();
|
||||
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
applyGpsPrefs();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SensorMesh::formatFileSystem() {
|
||||
@@ -764,6 +810,19 @@ void SensorMesh::setTxPower(uint8_t power_dbm) {
|
||||
radio_set_tx_power(power_dbm);
|
||||
}
|
||||
|
||||
void SensorMesh::formatStatsReply(char *reply) {
|
||||
StatsFormatHelper::formatCoreStats(reply, board, *_ms, _err_flags, _mgr);
|
||||
}
|
||||
|
||||
void SensorMesh::formatRadioStatsReply(char *reply) {
|
||||
StatsFormatHelper::formatRadioStats(reply, _radio, radio_driver, getTotalAirTime(), getReceiveAirTime());
|
||||
}
|
||||
|
||||
void SensorMesh::formatPacketStatsReply(char *reply) {
|
||||
StatsFormatHelper::formatPacketStats(reply, radio_driver, getNumSentFlood(), getNumSentDirect(),
|
||||
getNumRecvFlood(), getNumRecvDirect());
|
||||
}
|
||||
|
||||
float SensorMesh::getTelemValue(uint8_t channel, uint8_t type) {
|
||||
auto buf = telemetry.getBuffer();
|
||||
uint8_t size = telemetry.getSize();
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <helpers/AdvertDataHelpers.h>
|
||||
#include <helpers/TxtDataHelpers.h>
|
||||
#include <helpers/CommonCLI.h>
|
||||
#include <helpers/StatsFormatHelper.h>
|
||||
#include <helpers/ClientACL.h>
|
||||
#include <RTClib.h>
|
||||
#include <target.h>
|
||||
@@ -32,11 +33,11 @@
|
||||
#define PERM_RECV_ALERTS_HI (1 << 7) // high priority alerts
|
||||
|
||||
#ifndef FIRMWARE_BUILD_DATE
|
||||
#define FIRMWARE_BUILD_DATE "2 Oct 2025"
|
||||
#define FIRMWARE_BUILD_DATE "13 Nov 2025"
|
||||
#endif
|
||||
|
||||
#ifndef FIRMWARE_VERSION
|
||||
#define FIRMWARE_VERSION "v1.9.1"
|
||||
#define FIRMWARE_VERSION "v1.10.0"
|
||||
#endif
|
||||
|
||||
#define FIRMWARE_ROLE "sensor"
|
||||
@@ -69,6 +70,9 @@ public:
|
||||
void formatNeighborsReply(char *reply) override {
|
||||
strcpy(reply, "not supported");
|
||||
}
|
||||
void formatStatsReply(char *reply) override;
|
||||
void formatRadioStatsReply(char *reply) override;
|
||||
void formatPacketStatsReply(char *reply) override;
|
||||
mesh::LocalIdentity& getSelfId() override { return self_id; }
|
||||
void saveIdentity(const mesh::LocalIdentity& new_id) override;
|
||||
void clearStats() override { }
|
||||
@@ -121,6 +125,7 @@ protected:
|
||||
void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) override;
|
||||
void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override;
|
||||
bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override;
|
||||
void onControlDataRecv(mesh::Packet* packet) override;
|
||||
void onAckRecv(mesh::Packet* packet, uint32_t ack_crc) override;
|
||||
virtual bool handleIncomingMsg(ClientInfo& from, uint32_t timestamp, uint8_t* data, uint flags, size_t len);
|
||||
void sendAckTo(const ClientInfo& dest, uint32_t ack_hash);
|
||||
@@ -149,4 +154,9 @@ private:
|
||||
|
||||
void sendAlert(const ClientInfo* c, Trigger* t);
|
||||
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
void applyGpsPrefs() {
|
||||
sensors.setSettingValue("gps", _prefs.gps_enabled?"1":"0");
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -144,4 +144,5 @@ void loop() {
|
||||
#ifdef DISPLAY_CLASS
|
||||
ui_task.loop();
|
||||
#endif
|
||||
rtc_clock.tick();
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "MeshCore",
|
||||
"version" : "1.8.0",
|
||||
"version" : "1.10.0",
|
||||
"dependencies": {
|
||||
"SPI": "*",
|
||||
"Wire": "*",
|
||||
"jgromes/RadioLib": "^7.1.2",
|
||||
"jgromes/RadioLib": "^7.3.0",
|
||||
"rweather/Crypto": "^0.4.0",
|
||||
"adafruit/RTClib": "^2.1.3",
|
||||
"melopero/Melopero RV3028": "^1.1.0",
|
||||
"electroniccats/CayenneLPP": "1.4.0"
|
||||
"electroniccats/CayenneLPP": "1.6.1"
|
||||
},
|
||||
"build": {
|
||||
"extraScript": "build_as_lib.py"
|
||||
|
||||
@@ -18,11 +18,11 @@ monitor_speed = 115200
|
||||
lib_deps =
|
||||
SPI
|
||||
Wire
|
||||
jgromes/RadioLib @ ^7.1.2
|
||||
jgromes/RadioLib @ ^7.3.0
|
||||
rweather/Crypto @ ^0.4.0
|
||||
adafruit/RTClib @ ^2.1.3
|
||||
melopero/Melopero RV3028 @ ^1.1.0
|
||||
electroniccats/CayenneLPP @ 1.4.0
|
||||
electroniccats/CayenneLPP @ 1.6.1
|
||||
build_flags = -w -DNDEBUG -DRADIOLIB_STATIC_ONLY=1 -DRADIOLIB_GODMODE=1
|
||||
-D LORA_FREQ=869.525
|
||||
-D LORA_BW=250
|
||||
@@ -67,6 +67,7 @@ lib_deps =
|
||||
file://arch/esp32/AsyncElegantOTA
|
||||
|
||||
; esp32c6 uses arduino framework 3.x
|
||||
; WARNING: experimental. pioarduino on esp32c6 needs work - it's not considered stable and has issues.
|
||||
[esp32c6_base]
|
||||
extends = esp32_base
|
||||
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.12/platform-espressif32.zip
|
||||
@@ -76,6 +77,9 @@ platform = https://github.com/pioarduino/platform-espressif32/releases/download/
|
||||
[nrf52_base]
|
||||
extends = arduino_base
|
||||
platform = nordicnrf52
|
||||
platform_packages =
|
||||
framework-arduinoadafruitnrf52 @ 1.10700.0
|
||||
extra_scripts = create-uf2.py
|
||||
build_flags = ${arduino_base.build_flags}
|
||||
-D NRF52_PLATFORM
|
||||
-D LFS_NO_ASSERT=1
|
||||
@@ -124,6 +128,8 @@ build_flags =
|
||||
-D ENV_INCLUDE_INA260=1
|
||||
-D ENV_INCLUDE_MLX90614=1
|
||||
-D ENV_INCLUDE_VL53L0X=1
|
||||
-D ENV_INCLUDE_BME680=1
|
||||
-D ENV_INCLUDE_BMP085=1
|
||||
lib_deps =
|
||||
adafruit/Adafruit INA3221 Library @ ^1.0.1
|
||||
adafruit/Adafruit INA219 @ ^1.2.3
|
||||
@@ -138,3 +144,5 @@ lib_deps =
|
||||
adafruit/Adafruit MLX90614 Library @ ^2.1.5
|
||||
adafruit/Adafruit_VL53L0X @ ^1.2.4
|
||||
stevemarple/MicroNMEA @ ^2.0.6
|
||||
adafruit/Adafruit BME680 Library @ ^2.0.4
|
||||
adafruit/Adafruit BMP085 Library @ ^1.2.4
|
||||
|
||||
70
src/Mesh.cpp
70
src/Mesh.cpp
@@ -68,6 +68,14 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
|
||||
return ACTION_RELEASE;
|
||||
}
|
||||
|
||||
if (pkt->isRouteDirect() && pkt->getPayloadType() == PAYLOAD_TYPE_CONTROL && (pkt->payload[0] & 0x80) != 0) {
|
||||
if (pkt->path_len == 0) {
|
||||
onControlDataRecv(pkt);
|
||||
}
|
||||
// just zero-hop control packets allowed (for this subset of payloads)
|
||||
return ACTION_RELEASE;
|
||||
}
|
||||
|
||||
if (pkt->isRouteDirect() && pkt->path_len >= PATH_HASH_SIZE) {
|
||||
if (self_id.isHashMatch(pkt->path) && allowPacketForward(pkt)) {
|
||||
if (pkt->getPayloadType() == PAYLOAD_TYPE_MULTIPART) {
|
||||
@@ -90,6 +98,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
|
||||
return ACTION_RELEASE; // this node is NOT the next hop (OR this packet has already been forwarded), so discard.
|
||||
}
|
||||
|
||||
if (pkt->isRouteFlood() && filterRecvFloodPacket(pkt)) return ACTION_RELEASE;
|
||||
|
||||
DispatcherAction action = ACTION_RELEASE;
|
||||
|
||||
switch (pkt->getPayloadType()) {
|
||||
@@ -201,9 +211,9 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
|
||||
if (i + 2 >= pkt->payload_len) {
|
||||
MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete data packet", getLogDateTime());
|
||||
} else if (!_tables->hasSeen(pkt)) {
|
||||
// scan channels DB, for all matching hashes of 'channel_hash' (max 2 matches supported ATM)
|
||||
GroupChannel channels[2];
|
||||
int num = searchChannelsByHash(&channel_hash, channels, 2);
|
||||
// scan channels DB, for all matching hashes of 'channel_hash' (max 4 matches supported ATM)
|
||||
GroupChannel channels[4];
|
||||
int num = searchChannelsByHash(&channel_hash, channels, 4);
|
||||
// for each matching channel, try to decrypt data
|
||||
for (int j = 0; j < num; j++) {
|
||||
// decrypt, checking MAC is valid
|
||||
@@ -587,6 +597,22 @@ Packet* Mesh::createTrace(uint32_t tag, uint32_t auth_code, uint8_t flags) {
|
||||
return packet;
|
||||
}
|
||||
|
||||
Packet* Mesh::createControlData(const uint8_t* data, size_t len) {
|
||||
if (len > sizeof(Packet::payload)) return NULL; // invalid arg
|
||||
|
||||
Packet* packet = obtainNewPacket();
|
||||
if (packet == NULL) {
|
||||
MESH_DEBUG_PRINTLN("%s Mesh::createControlData(): error, packet pool empty", getLogDateTime());
|
||||
return NULL;
|
||||
}
|
||||
packet->header = (PAYLOAD_TYPE_CONTROL << PH_TYPE_SHIFT); // ROUTE_TYPE_* set later
|
||||
|
||||
memcpy(packet->payload, data, len);
|
||||
packet->payload_len = len;
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
void Mesh::sendFlood(Packet* packet, uint32_t delay_millis) {
|
||||
if (packet->getPayloadType() == PAYLOAD_TYPE_TRACE) {
|
||||
MESH_DEBUG_PRINTLN("%s Mesh::sendFlood(): TRACE type not suspported", getLogDateTime());
|
||||
@@ -610,6 +636,31 @@ void Mesh::sendFlood(Packet* packet, uint32_t delay_millis) {
|
||||
sendPacket(packet, pri, delay_millis);
|
||||
}
|
||||
|
||||
void Mesh::sendFlood(Packet* packet, uint16_t* transport_codes, uint32_t delay_millis) {
|
||||
if (packet->getPayloadType() == PAYLOAD_TYPE_TRACE) {
|
||||
MESH_DEBUG_PRINTLN("%s Mesh::sendFlood(): TRACE type not suspported", getLogDateTime());
|
||||
return;
|
||||
}
|
||||
|
||||
packet->header &= ~PH_ROUTE_MASK;
|
||||
packet->header |= ROUTE_TYPE_TRANSPORT_FLOOD;
|
||||
packet->transport_codes[0] = transport_codes[0];
|
||||
packet->transport_codes[1] = transport_codes[1];
|
||||
packet->path_len = 0;
|
||||
|
||||
_tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us
|
||||
|
||||
uint8_t pri;
|
||||
if (packet->getPayloadType() == PAYLOAD_TYPE_PATH) {
|
||||
pri = 2;
|
||||
} else if (packet->getPayloadType() == PAYLOAD_TYPE_ADVERT) {
|
||||
pri = 3; // de-prioritie these
|
||||
} else {
|
||||
pri = 1;
|
||||
}
|
||||
sendPacket(packet, pri, delay_millis);
|
||||
}
|
||||
|
||||
void Mesh::sendDirect(Packet* packet, const uint8_t* path, uint8_t path_len, uint32_t delay_millis) {
|
||||
packet->header &= ~PH_ROUTE_MASK;
|
||||
packet->header |= ROUTE_TYPE_DIRECT;
|
||||
@@ -645,4 +696,17 @@ void Mesh::sendZeroHop(Packet* packet, uint32_t delay_millis) {
|
||||
sendPacket(packet, 0, delay_millis);
|
||||
}
|
||||
|
||||
void Mesh::sendZeroHop(Packet* packet, uint16_t* transport_codes, uint32_t delay_millis) {
|
||||
packet->header &= ~PH_ROUTE_MASK;
|
||||
packet->header |= ROUTE_TYPE_TRANSPORT_DIRECT;
|
||||
packet->transport_codes[0] = transport_codes[0];
|
||||
packet->transport_codes[1] = transport_codes[1];
|
||||
|
||||
packet->path_len = 0; // path_len of zero means Zero Hop
|
||||
|
||||
_tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us
|
||||
|
||||
sendPacket(packet, 0, delay_millis);
|
||||
}
|
||||
|
||||
}
|
||||
24
src/Mesh.h
24
src/Mesh.h
@@ -43,6 +43,12 @@ protected:
|
||||
*/
|
||||
DispatcherAction routeRecvPacket(Packet* packet);
|
||||
|
||||
/**
|
||||
* \brief Called _before_ the packet is dispatched to the on..Recv() methods.
|
||||
* \returns true, if given packet should be NOT be processed.
|
||||
*/
|
||||
virtual bool filterRecvFloodPacket(Packet* packet) { return false; }
|
||||
|
||||
/**
|
||||
* \brief Check whether this packet should be forwarded (re-transmitted) or not.
|
||||
* Is sub-classes responsibility to make sure given packet is only transmitted ONCE (by this node)
|
||||
@@ -128,6 +134,11 @@ protected:
|
||||
*/
|
||||
virtual void onPathRecv(Packet* packet, Identity& sender, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) { }
|
||||
|
||||
/**
|
||||
* \brief A control packet has been received.
|
||||
*/
|
||||
virtual void onControlDataRecv(Packet* packet) { }
|
||||
|
||||
/**
|
||||
* \brief A packet with PAYLOAD_TYPE_RAW_CUSTOM has been received.
|
||||
*/
|
||||
@@ -180,12 +191,19 @@ public:
|
||||
Packet* createPathReturn(const Identity& dest, const uint8_t* secret, const uint8_t* path, uint8_t path_len, uint8_t extra_type, const uint8_t*extra, size_t extra_len);
|
||||
Packet* createRawData(const uint8_t* data, size_t len);
|
||||
Packet* createTrace(uint32_t tag, uint32_t auth_code, uint8_t flags = 0);
|
||||
Packet* createControlData(const uint8_t* data, size_t len);
|
||||
|
||||
/**
|
||||
* \brief send a locally-generated Packet with flood routing
|
||||
*/
|
||||
void sendFlood(Packet* packet, uint32_t delay_millis=0);
|
||||
|
||||
/**
|
||||
* \brief send a locally-generated Packet with flood routing
|
||||
* \param transport_codes array of 2 codes to attach to packet
|
||||
*/
|
||||
void sendFlood(Packet* packet, uint16_t* transport_codes, uint32_t delay_millis=0);
|
||||
|
||||
/**
|
||||
* \brief send a locally-generated Packet with Direct routing
|
||||
*/
|
||||
@@ -196,6 +214,12 @@ public:
|
||||
*/
|
||||
void sendZeroHop(Packet* packet, uint32_t delay_millis=0);
|
||||
|
||||
/**
|
||||
* \brief send a locally-generated Packet to just neigbor nodes (zero hops), with specific transort codes
|
||||
* \param transport_codes array of 2 codes to attach to packet
|
||||
*/
|
||||
void sendZeroHop(Packet* packet, uint16_t* transport_codes, uint32_t delay_millis=0);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -28,6 +28,12 @@
|
||||
#define MESH_DEBUG_PRINTLN(...) {}
|
||||
#endif
|
||||
|
||||
#if BRIDGE_DEBUG && ARDUINO
|
||||
#define BRIDGE_DEBUG_PRINTLN(F, ...) Serial.printf("%s BRIDGE: " F, getLogDateTime(), ##__VA_ARGS__)
|
||||
#else
|
||||
#define BRIDGE_DEBUG_PRINTLN(...) {}
|
||||
#endif
|
||||
|
||||
namespace mesh {
|
||||
|
||||
#define BD_STARTUP_NORMAL 0 // getStartupReason() codes
|
||||
@@ -66,6 +72,11 @@ public:
|
||||
*/
|
||||
virtual void setCurrentTime(uint32_t time) = 0;
|
||||
|
||||
/**
|
||||
* override in classes that need to periodically update internal state
|
||||
*/
|
||||
virtual void tick() { /* no op */}
|
||||
|
||||
uint32_t getCurrentTimeUnique() {
|
||||
uint32_t t = getCurrentTime();
|
||||
if (t <= last_unique) {
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace mesh {
|
||||
#define PAYLOAD_TYPE_PATH 0x08 // returned path (prefixed with dest/src hashes, MAC) (enc data: path, extra)
|
||||
#define PAYLOAD_TYPE_TRACE 0x09 // trace a path, collecting SNI for each hop
|
||||
#define PAYLOAD_TYPE_MULTIPART 0x0A // packet is one of a set of packets
|
||||
#define PAYLOAD_TYPE_CONTROL 0x0B // a control/discovery packet
|
||||
//...
|
||||
#define PAYLOAD_TYPE_RAW_CUSTOM 0x0F // custom packet as raw bytes, for applications with custom encryption, payloads, etc
|
||||
|
||||
|
||||
@@ -11,6 +11,18 @@ public:
|
||||
*/
|
||||
virtual void begin() = 0;
|
||||
|
||||
/**
|
||||
* @brief Stops the bridge.
|
||||
*/
|
||||
virtual void end() = 0;
|
||||
|
||||
/**
|
||||
* @brief Gets the current state of the bridge.
|
||||
*
|
||||
* @return true if the bridge is initialized and running, false otherwise.
|
||||
*/
|
||||
virtual bool isRunning() const = 0;
|
||||
|
||||
/**
|
||||
* @brief A method to be called on every main loop iteration.
|
||||
* Used for tasks like checking for incoming data.
|
||||
@@ -20,14 +32,14 @@ public:
|
||||
/**
|
||||
* @brief A callback that is triggered when the mesh transmits a packet.
|
||||
* The bridge can use this to forward the packet.
|
||||
*
|
||||
*
|
||||
* @param packet The packet that was transmitted.
|
||||
*/
|
||||
virtual void onPacketTransmitted(mesh::Packet* packet) = 0;
|
||||
virtual void sendPacket(mesh::Packet* packet) = 0;
|
||||
|
||||
/**
|
||||
* @brief Processes a received packet from the bridge's medium.
|
||||
*
|
||||
*
|
||||
* @param packet The packet that was received.
|
||||
*/
|
||||
virtual void onPacketReceived(mesh::Packet* packet) = 0;
|
||||
|
||||
@@ -4,11 +4,19 @@
|
||||
#include <Arduino.h>
|
||||
|
||||
class VolatileRTCClock : public mesh::RTCClock {
|
||||
long millis_offset;
|
||||
uint32_t base_time;
|
||||
uint64_t accumulator;
|
||||
unsigned long prev_millis;
|
||||
public:
|
||||
VolatileRTCClock() { millis_offset = 1715770351; } // 15 May 2024, 8:50pm
|
||||
uint32_t getCurrentTime() override { return (millis()/1000 + millis_offset); }
|
||||
void setCurrentTime(uint32_t time) override { millis_offset = time - millis()/1000; }
|
||||
VolatileRTCClock() { base_time = 1715770351; accumulator = 0; prev_millis = millis(); } // 15 May 2024, 8:50pm
|
||||
uint32_t getCurrentTime() override { return base_time + accumulator/1000; }
|
||||
void setCurrentTime(uint32_t time) override { base_time = time; accumulator = 0; prev_millis = millis(); }
|
||||
|
||||
void tick() override {
|
||||
unsigned long now = millis();
|
||||
accumulator += (now - prev_millis);
|
||||
prev_millis = now;
|
||||
}
|
||||
};
|
||||
|
||||
class ArduinoMillis : public mesh::MillisecondClock {
|
||||
|
||||
@@ -14,4 +14,8 @@ public:
|
||||
void begin(TwoWire& wire);
|
||||
uint32_t getCurrentTime() override;
|
||||
void setCurrentTime(uint32_t time) override;
|
||||
|
||||
void tick() override {
|
||||
_fallback->tick(); // is typically VolatileRTCClock, which now needs tick()
|
||||
}
|
||||
};
|
||||
|
||||
@@ -9,6 +9,13 @@
|
||||
#define TXT_ACK_DELAY 200
|
||||
#endif
|
||||
|
||||
void BaseChatMesh::sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, uint32_t delay_millis) {
|
||||
sendFlood(pkt, delay_millis);
|
||||
}
|
||||
void BaseChatMesh::sendFloodScoped(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t delay_millis) {
|
||||
sendFlood(pkt, delay_millis);
|
||||
}
|
||||
|
||||
mesh::Packet* BaseChatMesh::createSelfAdvert(const char* name) {
|
||||
uint8_t app_data[MAX_ADVERT_DATA_SIZE];
|
||||
uint8_t app_data_len;
|
||||
@@ -34,7 +41,7 @@ mesh::Packet* BaseChatMesh::createSelfAdvert(const char* name, double lat, doubl
|
||||
void BaseChatMesh::sendAckTo(const ContactInfo& dest, uint32_t ack_hash) {
|
||||
if (dest.out_path_len < 0) {
|
||||
mesh::Packet* ack = createAck(ack_hash);
|
||||
if (ack) sendFlood(ack, TXT_ACK_DELAY);
|
||||
if (ack) sendFloodScoped(dest, ack, TXT_ACK_DELAY);
|
||||
} else {
|
||||
uint32_t d = TXT_ACK_DELAY;
|
||||
if (getExtraAckTransmitCount() > 0) {
|
||||
@@ -68,9 +75,16 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id,
|
||||
}
|
||||
|
||||
// save a copy of raw advert packet (to support "Share..." function)
|
||||
int plen = packet->writeTo(temp_buf);
|
||||
int plen;
|
||||
{
|
||||
uint8_t save = packet->header;
|
||||
packet->header &= ~PH_ROUTE_MASK;
|
||||
packet->header |= ROUTE_TYPE_FLOOD; // make sure transport codes are NOT saved
|
||||
plen = packet->writeTo(temp_buf);
|
||||
packet->header = save;
|
||||
}
|
||||
putBlobByKey(id.pub_key, PUB_KEY_SIZE, temp_buf, plen);
|
||||
|
||||
|
||||
bool is_new = false;
|
||||
if (from == NULL) {
|
||||
if (!isAutoAddEnabled()) {
|
||||
@@ -168,7 +182,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender
|
||||
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the ACK
|
||||
mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len,
|
||||
PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4);
|
||||
if (path) sendFlood(path, TXT_ACK_DELAY);
|
||||
if (path) sendFloodScoped(from, path, TXT_ACK_DELAY);
|
||||
} else {
|
||||
sendAckTo(from, ack_hash);
|
||||
}
|
||||
@@ -179,7 +193,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender
|
||||
if (packet->isRouteFlood()) {
|
||||
// let this sender know path TO here, so they can use sendDirect() (NOTE: no ACK as extra)
|
||||
mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len, 0, NULL, 0);
|
||||
if (path) sendFlood(path);
|
||||
if (path) sendFloodScoped(from, path);
|
||||
}
|
||||
} else if (flags == TXT_TYPE_SIGNED_PLAIN) {
|
||||
if (timestamp > from.sync_since) { // make sure 'sync_since' is up-to-date
|
||||
@@ -195,7 +209,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender
|
||||
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the ACK
|
||||
mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len,
|
||||
PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4);
|
||||
if (path) sendFlood(path, TXT_ACK_DELAY);
|
||||
if (path) sendFloodScoped(from, path, TXT_ACK_DELAY);
|
||||
} else {
|
||||
sendAckTo(from, ack_hash);
|
||||
}
|
||||
@@ -211,14 +225,14 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender
|
||||
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
|
||||
mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len,
|
||||
PAYLOAD_TYPE_RESPONSE, temp_buf, reply_len);
|
||||
if (path) sendFlood(path, SERVER_RESPONSE_DELAY);
|
||||
if (path) sendFloodScoped(from, path, SERVER_RESPONSE_DELAY);
|
||||
} else {
|
||||
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, from.id, secret, temp_buf, reply_len);
|
||||
if (reply) {
|
||||
if (from.out_path_len >= 0) { // we have an out_path, so send DIRECT
|
||||
sendDirect(reply, from.out_path, from.out_path_len, SERVER_RESPONSE_DELAY);
|
||||
} else {
|
||||
sendFlood(reply, SERVER_RESPONSE_DELAY);
|
||||
sendFloodScoped(from, reply, SERVER_RESPONSE_DELAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -339,7 +353,7 @@ int BaseChatMesh::sendMessage(const ContactInfo& recipient, uint32_t timestamp,
|
||||
|
||||
int rc;
|
||||
if (recipient.out_path_len < 0) {
|
||||
sendFlood(pkt);
|
||||
sendFloodScoped(recipient, pkt);
|
||||
txt_send_timeout = futureMillis(est_timeout = calcFloodTimeoutMillisFor(t));
|
||||
rc = MSG_SEND_SENT_FLOOD;
|
||||
} else {
|
||||
@@ -365,7 +379,7 @@ int BaseChatMesh::sendCommandData(const ContactInfo& recipient, uint32_t timest
|
||||
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
|
||||
int rc;
|
||||
if (recipient.out_path_len < 0) {
|
||||
sendFlood(pkt);
|
||||
sendFloodScoped(recipient, pkt);
|
||||
txt_send_timeout = futureMillis(est_timeout = calcFloodTimeoutMillisFor(t));
|
||||
rc = MSG_SEND_SENT_FLOOD;
|
||||
} else {
|
||||
@@ -391,7 +405,7 @@ bool BaseChatMesh::sendGroupMessage(uint32_t timestamp, mesh::GroupChannel& chan
|
||||
|
||||
auto pkt = createGroupDatagram(PAYLOAD_TYPE_GRP_TXT, channel, temp, 5 + prefix_len + text_len);
|
||||
if (pkt) {
|
||||
sendFlood(pkt);
|
||||
sendFloodScoped(channel, pkt);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -405,7 +419,9 @@ bool BaseChatMesh::shareContactZeroHop(const ContactInfo& contact) {
|
||||
if (packet == NULL) return false; // no Packets available
|
||||
|
||||
packet->readFrom(temp_buf, plen); // restore Packet from 'blob'
|
||||
sendZeroHop(packet);
|
||||
uint16_t codes[2];
|
||||
codes[0] = codes[1] = 0; // { 0, 0 } means 'send this nowhere'
|
||||
sendZeroHop(packet, codes);
|
||||
return true; // success
|
||||
}
|
||||
|
||||
@@ -451,7 +467,7 @@ int BaseChatMesh::sendLogin(const ContactInfo& recipient, const char* password,
|
||||
if (pkt) {
|
||||
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
|
||||
if (recipient.out_path_len < 0) {
|
||||
sendFlood(pkt);
|
||||
sendFloodScoped(recipient, pkt);
|
||||
est_timeout = calcFloodTimeoutMillisFor(t);
|
||||
return MSG_SEND_SENT_FLOOD;
|
||||
} else {
|
||||
@@ -478,7 +494,7 @@ int BaseChatMesh::sendRequest(const ContactInfo& recipient, const uint8_t* req_
|
||||
if (pkt) {
|
||||
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
|
||||
if (recipient.out_path_len < 0) {
|
||||
sendFlood(pkt);
|
||||
sendFloodScoped(recipient, pkt);
|
||||
est_timeout = calcFloodTimeoutMillisFor(t);
|
||||
return MSG_SEND_SENT_FLOOD;
|
||||
} else {
|
||||
@@ -505,7 +521,7 @@ int BaseChatMesh::sendRequest(const ContactInfo& recipient, uint8_t req_type, u
|
||||
if (pkt) {
|
||||
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
|
||||
if (recipient.out_path_len < 0) {
|
||||
sendFlood(pkt);
|
||||
sendFloodScoped(recipient, pkt);
|
||||
est_timeout = calcFloodTimeoutMillisFor(t);
|
||||
return MSG_SEND_SENT_FLOOD;
|
||||
} else {
|
||||
|
||||
@@ -107,6 +107,9 @@ protected:
|
||||
virtual void onContactResponse(const ContactInfo& contact, const uint8_t* data, uint8_t len) = 0;
|
||||
virtual void handleReturnPathRetry(const ContactInfo& contact, const uint8_t* path, uint8_t path_len);
|
||||
|
||||
virtual void sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, uint32_t delay_millis=0);
|
||||
virtual void sendFloodScoped(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t delay_millis=0);
|
||||
|
||||
// storage concepts, for sub-classes to override/implement
|
||||
virtual int getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]) { return 0; } // not implemented
|
||||
virtual bool putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], int len) { return false; }
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <Arduino.h>
|
||||
#include "CommonCLI.h"
|
||||
#include "TxtDataHelpers.h"
|
||||
#include "AdvertDataHelpers.h"
|
||||
#include <RTClib.h>
|
||||
|
||||
// Believe it or not, this std C function is busted on some platforms!
|
||||
@@ -32,32 +33,44 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
if (file) {
|
||||
uint8_t pad[8];
|
||||
|
||||
file.read((uint8_t *) &_prefs->airtime_factor, sizeof(_prefs->airtime_factor)); // 0
|
||||
file.read((uint8_t *) &_prefs->node_name, sizeof(_prefs->node_name)); // 4
|
||||
file.read(pad, 4); // 36
|
||||
file.read((uint8_t *) &_prefs->node_lat, sizeof(_prefs->node_lat)); // 40
|
||||
file.read((uint8_t *) &_prefs->node_lon, sizeof(_prefs->node_lon)); // 48
|
||||
file.read((uint8_t *) &_prefs->password[0], sizeof(_prefs->password)); // 56
|
||||
file.read((uint8_t *) &_prefs->freq, sizeof(_prefs->freq)); // 72
|
||||
file.read((uint8_t *) &_prefs->tx_power_dbm, sizeof(_prefs->tx_power_dbm)); // 76
|
||||
file.read((uint8_t *) &_prefs->disable_fwd, sizeof(_prefs->disable_fwd)); // 77
|
||||
file.read((uint8_t *) &_prefs->advert_interval, sizeof(_prefs->advert_interval)); // 78
|
||||
file.read((uint8_t *) pad, 1); // 79 was 'unused'
|
||||
file.read((uint8_t *) &_prefs->rx_delay_base, sizeof(_prefs->rx_delay_base)); // 80
|
||||
file.read((uint8_t *) &_prefs->tx_delay_factor, sizeof(_prefs->tx_delay_factor)); // 84
|
||||
file.read((uint8_t *) &_prefs->guest_password[0], sizeof(_prefs->guest_password)); // 88
|
||||
file.read((uint8_t *) &_prefs->direct_tx_delay_factor, sizeof(_prefs->direct_tx_delay_factor)); // 104
|
||||
file.read(pad, 4); // 108
|
||||
file.read((uint8_t *) &_prefs->sf, sizeof(_prefs->sf)); // 112
|
||||
file.read((uint8_t *) &_prefs->cr, sizeof(_prefs->cr)); // 113
|
||||
file.read((uint8_t *) &_prefs->allow_read_only, sizeof(_prefs->allow_read_only)); // 114
|
||||
file.read((uint8_t *) &_prefs->multi_acks, sizeof(_prefs->multi_acks)); // 115
|
||||
file.read((uint8_t *) &_prefs->bw, sizeof(_prefs->bw)); // 116
|
||||
file.read((uint8_t *) &_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval)); // 120
|
||||
file.read(pad, 3); // 121
|
||||
file.read((uint8_t *) &_prefs->flood_max, sizeof(_prefs->flood_max)); // 124
|
||||
file.read((uint8_t *) &_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); // 125
|
||||
file.read((uint8_t *) &_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); // 126
|
||||
file.read((uint8_t *)&_prefs->airtime_factor, sizeof(_prefs->airtime_factor)); // 0
|
||||
file.read((uint8_t *)&_prefs->node_name, sizeof(_prefs->node_name)); // 4
|
||||
file.read(pad, 4); // 36
|
||||
file.read((uint8_t *)&_prefs->node_lat, sizeof(_prefs->node_lat)); // 40
|
||||
file.read((uint8_t *)&_prefs->node_lon, sizeof(_prefs->node_lon)); // 48
|
||||
file.read((uint8_t *)&_prefs->password[0], sizeof(_prefs->password)); // 56
|
||||
file.read((uint8_t *)&_prefs->freq, sizeof(_prefs->freq)); // 72
|
||||
file.read((uint8_t *)&_prefs->tx_power_dbm, sizeof(_prefs->tx_power_dbm)); // 76
|
||||
file.read((uint8_t *)&_prefs->disable_fwd, sizeof(_prefs->disable_fwd)); // 77
|
||||
file.read((uint8_t *)&_prefs->advert_interval, sizeof(_prefs->advert_interval)); // 78
|
||||
file.read((uint8_t *)pad, 1); // 79 was 'unused'
|
||||
file.read((uint8_t *)&_prefs->rx_delay_base, sizeof(_prefs->rx_delay_base)); // 80
|
||||
file.read((uint8_t *)&_prefs->tx_delay_factor, sizeof(_prefs->tx_delay_factor)); // 84
|
||||
file.read((uint8_t *)&_prefs->guest_password[0], sizeof(_prefs->guest_password)); // 88
|
||||
file.read((uint8_t *)&_prefs->direct_tx_delay_factor, sizeof(_prefs->direct_tx_delay_factor)); // 104
|
||||
file.read(pad, 4); // 108
|
||||
file.read((uint8_t *)&_prefs->sf, sizeof(_prefs->sf)); // 112
|
||||
file.read((uint8_t *)&_prefs->cr, sizeof(_prefs->cr)); // 113
|
||||
file.read((uint8_t *)&_prefs->allow_read_only, sizeof(_prefs->allow_read_only)); // 114
|
||||
file.read((uint8_t *)&_prefs->multi_acks, sizeof(_prefs->multi_acks)); // 115
|
||||
file.read((uint8_t *)&_prefs->bw, sizeof(_prefs->bw)); // 116
|
||||
file.read((uint8_t *)&_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval)); // 120
|
||||
file.read(pad, 3); // 121
|
||||
file.read((uint8_t *)&_prefs->flood_max, sizeof(_prefs->flood_max)); // 124
|
||||
file.read((uint8_t *)&_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); // 125
|
||||
file.read((uint8_t *)&_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); // 126
|
||||
file.read((uint8_t *)&_prefs->bridge_enabled, sizeof(_prefs->bridge_enabled)); // 127
|
||||
file.read((uint8_t *)&_prefs->bridge_delay, sizeof(_prefs->bridge_delay)); // 128
|
||||
file.read((uint8_t *)&_prefs->bridge_pkt_src, sizeof(_prefs->bridge_pkt_src)); // 130
|
||||
file.read((uint8_t *)&_prefs->bridge_baud, sizeof(_prefs->bridge_baud)); // 131
|
||||
file.read((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 135
|
||||
file.read((uint8_t *)&_prefs->bridge_secret, sizeof(_prefs->bridge_secret)); // 136
|
||||
file.read(pad, 4); // 152
|
||||
file.read((uint8_t *)&_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156
|
||||
file.read((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157
|
||||
file.read((uint8_t *)&_prefs->advert_loc_policy, sizeof (_prefs->advert_loc_policy)); // 161
|
||||
file.read((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162
|
||||
// 166
|
||||
|
||||
// sanitise bad pref values
|
||||
_prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f);
|
||||
@@ -65,12 +78,22 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
_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, 400.0f, 2500.0f);
|
||||
_prefs->bw = constrain(_prefs->bw, 62.5f, 500.0f);
|
||||
_prefs->sf = constrain(_prefs->sf, 7, 12);
|
||||
_prefs->bw = constrain(_prefs->bw, 7.8f, 500.0f);
|
||||
_prefs->sf = constrain(_prefs->sf, 5, 12);
|
||||
_prefs->cr = constrain(_prefs->cr, 5, 8);
|
||||
_prefs->tx_power_dbm = constrain(_prefs->tx_power_dbm, 1, 30);
|
||||
_prefs->multi_acks = constrain(_prefs->multi_acks, 0, 1);
|
||||
|
||||
// sanitise bad bridge pref values
|
||||
_prefs->bridge_enabled = constrain(_prefs->bridge_enabled, 0, 1);
|
||||
_prefs->bridge_delay = constrain(_prefs->bridge_delay, 0, 10000);
|
||||
_prefs->bridge_pkt_src = constrain(_prefs->bridge_pkt_src, 0, 1);
|
||||
_prefs->bridge_baud = constrain(_prefs->bridge_baud, 9600, 115200);
|
||||
_prefs->bridge_channel = constrain(_prefs->bridge_channel, 0, 14);
|
||||
|
||||
_prefs->gps_enabled = constrain(_prefs->gps_enabled, 0, 1);
|
||||
_prefs->advert_loc_policy = constrain(_prefs->advert_loc_policy, 0, 2);
|
||||
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
@@ -88,32 +111,44 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
|
||||
uint8_t pad[8];
|
||||
memset(pad, 0, sizeof(pad));
|
||||
|
||||
file.write((uint8_t *) &_prefs->airtime_factor, sizeof(_prefs->airtime_factor)); // 0
|
||||
file.write((uint8_t *) &_prefs->node_name, sizeof(_prefs->node_name)); // 4
|
||||
file.write(pad, 4); // 36
|
||||
file.write((uint8_t *) &_prefs->node_lat, sizeof(_prefs->node_lat)); // 40
|
||||
file.write((uint8_t *) &_prefs->node_lon, sizeof(_prefs->node_lon)); // 48
|
||||
file.write((uint8_t *) &_prefs->password[0], sizeof(_prefs->password)); // 56
|
||||
file.write((uint8_t *) &_prefs->freq, sizeof(_prefs->freq)); // 72
|
||||
file.write((uint8_t *) &_prefs->tx_power_dbm, sizeof(_prefs->tx_power_dbm)); // 76
|
||||
file.write((uint8_t *) &_prefs->disable_fwd, sizeof(_prefs->disable_fwd)); // 77
|
||||
file.write((uint8_t *) &_prefs->advert_interval, sizeof(_prefs->advert_interval)); // 78
|
||||
file.write((uint8_t *) pad, 1); // 79 was 'unused'
|
||||
file.write((uint8_t *) &_prefs->rx_delay_base, sizeof(_prefs->rx_delay_base)); // 80
|
||||
file.write((uint8_t *) &_prefs->tx_delay_factor, sizeof(_prefs->tx_delay_factor)); // 84
|
||||
file.write((uint8_t *) &_prefs->guest_password[0], sizeof(_prefs->guest_password)); // 88
|
||||
file.write((uint8_t *) &_prefs->direct_tx_delay_factor, sizeof(_prefs->direct_tx_delay_factor)); // 104
|
||||
file.write(pad, 4); // 108
|
||||
file.write((uint8_t *) &_prefs->sf, sizeof(_prefs->sf)); // 112
|
||||
file.write((uint8_t *) &_prefs->cr, sizeof(_prefs->cr)); // 113
|
||||
file.write((uint8_t *) &_prefs->allow_read_only, sizeof(_prefs->allow_read_only)); // 114
|
||||
file.write((uint8_t *) &_prefs->multi_acks, sizeof(_prefs->multi_acks)); // 115
|
||||
file.write((uint8_t *) &_prefs->bw, sizeof(_prefs->bw)); // 116
|
||||
file.write((uint8_t *) &_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval)); // 120
|
||||
file.write(pad, 3); // 121
|
||||
file.write((uint8_t *) &_prefs->flood_max, sizeof(_prefs->flood_max)); // 124
|
||||
file.write((uint8_t *) &_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); // 125
|
||||
file.write((uint8_t *) &_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); // 126
|
||||
file.write((uint8_t *)&_prefs->airtime_factor, sizeof(_prefs->airtime_factor)); // 0
|
||||
file.write((uint8_t *)&_prefs->node_name, sizeof(_prefs->node_name)); // 4
|
||||
file.write(pad, 4); // 36
|
||||
file.write((uint8_t *)&_prefs->node_lat, sizeof(_prefs->node_lat)); // 40
|
||||
file.write((uint8_t *)&_prefs->node_lon, sizeof(_prefs->node_lon)); // 48
|
||||
file.write((uint8_t *)&_prefs->password[0], sizeof(_prefs->password)); // 56
|
||||
file.write((uint8_t *)&_prefs->freq, sizeof(_prefs->freq)); // 72
|
||||
file.write((uint8_t *)&_prefs->tx_power_dbm, sizeof(_prefs->tx_power_dbm)); // 76
|
||||
file.write((uint8_t *)&_prefs->disable_fwd, sizeof(_prefs->disable_fwd)); // 77
|
||||
file.write((uint8_t *)&_prefs->advert_interval, sizeof(_prefs->advert_interval)); // 78
|
||||
file.write((uint8_t *)pad, 1); // 79 was 'unused'
|
||||
file.write((uint8_t *)&_prefs->rx_delay_base, sizeof(_prefs->rx_delay_base)); // 80
|
||||
file.write((uint8_t *)&_prefs->tx_delay_factor, sizeof(_prefs->tx_delay_factor)); // 84
|
||||
file.write((uint8_t *)&_prefs->guest_password[0], sizeof(_prefs->guest_password)); // 88
|
||||
file.write((uint8_t *)&_prefs->direct_tx_delay_factor, sizeof(_prefs->direct_tx_delay_factor)); // 104
|
||||
file.write(pad, 4); // 108
|
||||
file.write((uint8_t *)&_prefs->sf, sizeof(_prefs->sf)); // 112
|
||||
file.write((uint8_t *)&_prefs->cr, sizeof(_prefs->cr)); // 113
|
||||
file.write((uint8_t *)&_prefs->allow_read_only, sizeof(_prefs->allow_read_only)); // 114
|
||||
file.write((uint8_t *)&_prefs->multi_acks, sizeof(_prefs->multi_acks)); // 115
|
||||
file.write((uint8_t *)&_prefs->bw, sizeof(_prefs->bw)); // 116
|
||||
file.write((uint8_t *)&_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval)); // 120
|
||||
file.write(pad, 3); // 121
|
||||
file.write((uint8_t *)&_prefs->flood_max, sizeof(_prefs->flood_max)); // 124
|
||||
file.write((uint8_t *)&_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); // 125
|
||||
file.write((uint8_t *)&_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); // 126
|
||||
file.write((uint8_t *)&_prefs->bridge_enabled, sizeof(_prefs->bridge_enabled)); // 127
|
||||
file.write((uint8_t *)&_prefs->bridge_delay, sizeof(_prefs->bridge_delay)); // 128
|
||||
file.write((uint8_t *)&_prefs->bridge_pkt_src, sizeof(_prefs->bridge_pkt_src)); // 130
|
||||
file.write((uint8_t *)&_prefs->bridge_baud, sizeof(_prefs->bridge_baud)); // 131
|
||||
file.write((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 135
|
||||
file.write((uint8_t *)&_prefs->bridge_secret, sizeof(_prefs->bridge_secret)); // 136
|
||||
file.write(pad, 4); // 152
|
||||
file.write((uint8_t *)&_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156
|
||||
file.write((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157
|
||||
file.write((uint8_t *)&_prefs->advert_loc_policy, sizeof(_prefs->advert_loc_policy)); // 161
|
||||
file.write((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162
|
||||
// 166
|
||||
|
||||
file.close();
|
||||
}
|
||||
@@ -128,6 +163,19 @@ void CommonCLI::savePrefs() {
|
||||
_callbacks->savePrefs();
|
||||
}
|
||||
|
||||
uint8_t CommonCLI::buildAdvertData(uint8_t node_type, uint8_t* app_data) {
|
||||
if (_prefs->advert_loc_policy == ADVERT_LOC_NONE) {
|
||||
AdvertDataBuilder builder(node_type, _prefs->node_name);
|
||||
return builder.encodeTo(app_data);
|
||||
} else if (_prefs->advert_loc_policy == ADVERT_LOC_SHARE) {
|
||||
AdvertDataBuilder builder(node_type, _prefs->node_name, _sensors->node_lat, _sensors->node_lon);
|
||||
return builder.encodeTo(app_data);
|
||||
} else {
|
||||
AdvertDataBuilder builder(node_type, _prefs->node_name, _prefs->node_lat, _prefs->node_lon);
|
||||
return builder.encodeTo(app_data);
|
||||
}
|
||||
}
|
||||
|
||||
void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, char* reply) {
|
||||
if (memcmp(command, "reboot", 6) == 0) {
|
||||
_board->reboot(); // doesn't return
|
||||
@@ -199,6 +247,9 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
} else if (memcmp(command, "clear stats", 11) == 0) {
|
||||
_callbacks->clearStats();
|
||||
strcpy(reply, "(OK - stats reset)");
|
||||
/*
|
||||
* GET commands
|
||||
*/
|
||||
} else if (memcmp(command, "get ", 4) == 0) {
|
||||
const char* config = &command[4];
|
||||
if (memcmp(config, "af", 2) == 0) {
|
||||
@@ -252,9 +303,40 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
mesh::Utils::toHex(&reply[2], _callbacks->getSelfId().pub_key, PUB_KEY_SIZE);
|
||||
} else if (memcmp(config, "role", 4) == 0) {
|
||||
sprintf(reply, "> %s", _callbacks->getRole());
|
||||
} else if (memcmp(config, "bridge.type", 11) == 0) {
|
||||
sprintf(reply, "> %s",
|
||||
#ifdef WITH_RS232_BRIDGE
|
||||
"rs232"
|
||||
#elif WITH_ESPNOW_BRIDGE
|
||||
"espnow"
|
||||
#else
|
||||
"none"
|
||||
#endif
|
||||
);
|
||||
#ifdef WITH_BRIDGE
|
||||
} else if (memcmp(config, "bridge.enabled", 14) == 0) {
|
||||
sprintf(reply, "> %s", _prefs->bridge_enabled ? "on" : "off");
|
||||
} else if (memcmp(config, "bridge.delay", 12) == 0) {
|
||||
sprintf(reply, "> %d", (uint32_t)_prefs->bridge_delay);
|
||||
} else if (memcmp(config, "bridge.source", 13) == 0) {
|
||||
sprintf(reply, "> %s", _prefs->bridge_pkt_src ? "logRx" : "logTx");
|
||||
#endif
|
||||
#ifdef WITH_RS232_BRIDGE
|
||||
} else if (memcmp(config, "bridge.baud", 11) == 0) {
|
||||
sprintf(reply, "> %d", (uint32_t)_prefs->bridge_baud);
|
||||
#endif
|
||||
#ifdef WITH_ESPNOW_BRIDGE
|
||||
} else if (memcmp(config, "bridge.channel", 14) == 0) {
|
||||
sprintf(reply, "> %d", (uint32_t)_prefs->bridge_channel);
|
||||
} else if (memcmp(config, "bridge.secret", 13) == 0) {
|
||||
sprintf(reply, "> %s", _prefs->bridge_secret);
|
||||
#endif
|
||||
} else {
|
||||
sprintf(reply, "??: %s", config);
|
||||
}
|
||||
/*
|
||||
* SET commands
|
||||
*/
|
||||
} else if (memcmp(command, "set ", 4) == 0) {
|
||||
const char* config = &command[4];
|
||||
if (memcmp(config, "af ", 3) == 0) {
|
||||
@@ -301,7 +383,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
StrHelper::strncpy(_prefs->guest_password, &config[15], sizeof(_prefs->guest_password));
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else if (sender_timestamp == 0 && memcmp(config, "prv.key ", 8) == 0) { // from serial command line only
|
||||
} else if (sender_timestamp == 0 &&
|
||||
memcmp(config, "prv.key ", 8) == 0) { // from serial command line only
|
||||
uint8_t prv_key[PRV_KEY_SIZE];
|
||||
bool success = mesh::Utils::fromHex(prv_key, PRV_KEY_SIZE, &config[8]);
|
||||
if (success) {
|
||||
@@ -391,6 +474,55 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
_prefs->freq = atof(&config[5]);
|
||||
savePrefs();
|
||||
strcpy(reply, "OK - reboot to apply");
|
||||
#ifdef WITH_BRIDGE
|
||||
} else if (memcmp(config, "bridge.enabled ", 15) == 0) {
|
||||
_prefs->bridge_enabled = memcmp(&config[15], "on", 2) == 0;
|
||||
_callbacks->setBridgeState(_prefs->bridge_enabled);
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else if (memcmp(config, "bridge.delay ", 13) == 0) {
|
||||
int delay = _atoi(&config[13]);
|
||||
if (delay >= 0 && delay <= 10000) {
|
||||
_prefs->bridge_delay = (uint16_t)delay;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Error: delay must be between 0-10000 ms");
|
||||
}
|
||||
} else if (memcmp(config, "bridge.source ", 14) == 0) {
|
||||
_prefs->bridge_pkt_src = memcmp(&config[14], "rx", 2) == 0;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
#endif
|
||||
#ifdef WITH_RS232_BRIDGE
|
||||
} else if (memcmp(config, "bridge.baud ", 12) == 0) {
|
||||
uint32_t baud = atoi(&config[12]);
|
||||
if (baud >= 9600 && baud <= 115200) {
|
||||
_prefs->bridge_baud = (uint32_t)baud;
|
||||
_callbacks->restartBridge();
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Error: baud rate must be between 9600-115200");
|
||||
}
|
||||
#endif
|
||||
#ifdef WITH_ESPNOW_BRIDGE
|
||||
} else if (memcmp(config, "bridge.channel ", 15) == 0) {
|
||||
int ch = atoi(&config[15]);
|
||||
if (ch > 0 && ch < 15) {
|
||||
_prefs->bridge_channel = (uint8_t)ch;
|
||||
_callbacks->restartBridge();
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Error: channel must be between 1-14");
|
||||
}
|
||||
} else if (memcmp(config, "bridge.secret ", 14) == 0) {
|
||||
StrHelper::strncpy(_prefs->bridge_secret, &config[14], sizeof(_prefs->bridge_secret));
|
||||
_callbacks->restartBridge();
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
#endif
|
||||
} else {
|
||||
sprintf(reply, "unknown config: %s", config);
|
||||
}
|
||||
@@ -401,6 +533,126 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
sprintf(reply, "%s (Build: %s)", _callbacks->getFirmwareVer(), _callbacks->getBuildDate());
|
||||
} else if (memcmp(command, "board", 5) == 0) {
|
||||
sprintf(reply, "%s", _board->getManufacturerName());
|
||||
} else if (memcmp(command, "sensor get ", 11) == 0) {
|
||||
const char* key = command + 11;
|
||||
const char* val = _sensors->getSettingByKey(key);
|
||||
if (val != NULL) {
|
||||
sprintf(reply, "> %s", val);
|
||||
} else {
|
||||
strcpy(reply, "null");
|
||||
}
|
||||
} else if (memcmp(command, "sensor set ", 11) == 0) {
|
||||
strcpy(tmp, &command[11]);
|
||||
const char *parts[2];
|
||||
int num = mesh::Utils::parseTextParts(tmp, parts, 2, ' ');
|
||||
const char *key = (num > 0) ? parts[0] : "";
|
||||
const char *value = (num > 1) ? parts[1] : "null";
|
||||
if (_sensors->setSettingValue(key, value)) {
|
||||
strcpy(reply, "ok");
|
||||
} else {
|
||||
strcpy(reply, "can't find custom var");
|
||||
}
|
||||
} else if (memcmp(command, "sensor list", 11) == 0) {
|
||||
char* dp = reply;
|
||||
int start = 0;
|
||||
int end = _sensors->getNumSettings();
|
||||
if (strlen(command) > 11) {
|
||||
start = _atoi(command+12);
|
||||
}
|
||||
if (start >= end) {
|
||||
strcpy(reply, "no custom var");
|
||||
} else {
|
||||
sprintf(dp, "%d vars\n", end);
|
||||
dp = strchr(dp, 0);
|
||||
int i;
|
||||
for (i = start; i < end && (dp-reply < 134); i++) {
|
||||
sprintf(dp, "%s=%s\n",
|
||||
_sensors->getSettingName(i),
|
||||
_sensors->getSettingValue(i));
|
||||
dp = strchr(dp, 0);
|
||||
}
|
||||
if (i < end) {
|
||||
sprintf(dp, "... next:%d", i);
|
||||
} else {
|
||||
*(dp-1) = 0; // remove last CR
|
||||
}
|
||||
}
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
} else if (memcmp(command, "gps on", 6) == 0) {
|
||||
if (_sensors->setSettingValue("gps", "1")) {
|
||||
_prefs->gps_enabled = 1;
|
||||
savePrefs();
|
||||
strcpy(reply, "ok");
|
||||
} else {
|
||||
strcpy(reply, "gps toggle not found");
|
||||
}
|
||||
} else if (memcmp(command, "gps off", 7) == 0) {
|
||||
if (_sensors->setSettingValue("gps", "0")) {
|
||||
_prefs->gps_enabled = 0;
|
||||
savePrefs();
|
||||
strcpy(reply, "ok");
|
||||
} else {
|
||||
strcpy(reply, "gps toggle not found");
|
||||
}
|
||||
} else if (memcmp(command, "gps sync", 8) == 0) {
|
||||
LocationProvider * l = _sensors->getLocationProvider();
|
||||
if (l != NULL) {
|
||||
l->syncTime();
|
||||
}
|
||||
} else if (memcmp(command, "gps setloc", 10) == 0) {
|
||||
_prefs->node_lat = _sensors->node_lat;
|
||||
_prefs->node_lon = _sensors->node_lon;
|
||||
savePrefs();
|
||||
strcpy(reply, "ok");
|
||||
} else if (memcmp(command, "gps advert", 10) == 0) {
|
||||
if (strlen(command) == 10) {
|
||||
switch (_prefs->advert_loc_policy) {
|
||||
case ADVERT_LOC_NONE:
|
||||
strcpy(reply, "> none");
|
||||
break;
|
||||
case ADVERT_LOC_PREFS:
|
||||
strcpy(reply, "> prefs");
|
||||
break;
|
||||
case ADVERT_LOC_SHARE:
|
||||
strcpy(reply, "> share");
|
||||
break;
|
||||
default:
|
||||
strcpy(reply, "error");
|
||||
}
|
||||
} else if (memcmp(command+11, "none", 4) == 0) {
|
||||
_prefs->advert_loc_policy = ADVERT_LOC_NONE;
|
||||
savePrefs();
|
||||
strcpy(reply, "ok");
|
||||
} else if (memcmp(command+11, "share", 5) == 0) {
|
||||
_prefs->advert_loc_policy = ADVERT_LOC_SHARE;
|
||||
savePrefs();
|
||||
strcpy(reply, "ok");
|
||||
} else if (memcmp(command+11, "prefs", 4) == 0) {
|
||||
_prefs->advert_loc_policy = ADVERT_LOC_PREFS;
|
||||
savePrefs();
|
||||
strcpy(reply, "ok");
|
||||
} else {
|
||||
strcpy(reply, "error");
|
||||
}
|
||||
} else if (memcmp(command, "gps", 3) == 0) {
|
||||
LocationProvider * l = _sensors->getLocationProvider();
|
||||
if (l != NULL) {
|
||||
bool enabled = l->isEnabled(); // is EN pin on ?
|
||||
bool fix = l->isValid(); // has fix ?
|
||||
int sats = l->satellitesCount();
|
||||
bool active = !strcmp(_sensors->getSettingByKey("gps"), "1");
|
||||
if (enabled) {
|
||||
sprintf(reply, "on, %s, %s, %d sats",
|
||||
active?"active":"deactivated",
|
||||
fix?"fix":"no fix",
|
||||
sats);
|
||||
} else {
|
||||
strcpy(reply, "off");
|
||||
}
|
||||
} else {
|
||||
strcpy(reply, "Can't find GPS");
|
||||
}
|
||||
#endif
|
||||
} else if (memcmp(command, "log start", 9) == 0) {
|
||||
_callbacks->setLoggingOn(true);
|
||||
strcpy(reply, " logging on");
|
||||
@@ -413,6 +665,12 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
} else if (sender_timestamp == 0 && memcmp(command, "log", 3) == 0) {
|
||||
_callbacks->dumpLogFile();
|
||||
strcpy(reply, " EOF");
|
||||
} else if (sender_timestamp == 0 && memcmp(command, "stats-packets", 13) == 0 && (command[13] == 0 || command[13] == ' ')) {
|
||||
_callbacks->formatPacketStatsReply(reply);
|
||||
} else if (sender_timestamp == 0 && memcmp(command, "stats-radio", 11) == 0 && (command[11] == 0 || command[11] == ' ')) {
|
||||
_callbacks->formatRadioStatsReply(reply);
|
||||
} else if (sender_timestamp == 0 && memcmp(command, "stats-core", 10) == 0 && (command[10] == 0 || command[10] == ' ')) {
|
||||
_callbacks->formatStatsReply(reply);
|
||||
} else {
|
||||
strcpy(reply, "Unknown command");
|
||||
}
|
||||
|
||||
@@ -2,30 +2,51 @@
|
||||
|
||||
#include "Mesh.h"
|
||||
#include <helpers/IdentityStore.h>
|
||||
#include <helpers/SensorManager.h>
|
||||
|
||||
struct NodePrefs { // persisted to file
|
||||
float airtime_factor;
|
||||
char node_name[32];
|
||||
double node_lat, node_lon;
|
||||
char password[16];
|
||||
float freq;
|
||||
uint8_t tx_power_dbm;
|
||||
uint8_t disable_fwd;
|
||||
uint8_t advert_interval; // minutes / 2
|
||||
uint8_t flood_advert_interval; // hours
|
||||
float rx_delay_base;
|
||||
float tx_delay_factor;
|
||||
char guest_password[16];
|
||||
float direct_tx_delay_factor;
|
||||
uint32_t guard;
|
||||
uint8_t sf;
|
||||
uint8_t cr;
|
||||
uint8_t allow_read_only;
|
||||
uint8_t multi_acks;
|
||||
float bw;
|
||||
uint8_t flood_max;
|
||||
uint8_t interference_threshold;
|
||||
uint8_t agc_reset_interval; // secs / 4
|
||||
#if defined(WITH_RS232_BRIDGE) || defined(WITH_ESPNOW_BRIDGE)
|
||||
#define WITH_BRIDGE
|
||||
#endif
|
||||
|
||||
#define ADVERT_LOC_NONE 0
|
||||
#define ADVERT_LOC_SHARE 1
|
||||
#define ADVERT_LOC_PREFS 2
|
||||
|
||||
struct NodePrefs { // persisted to file
|
||||
float airtime_factor;
|
||||
char node_name[32];
|
||||
double node_lat, node_lon;
|
||||
char password[16];
|
||||
float freq;
|
||||
uint8_t tx_power_dbm;
|
||||
uint8_t disable_fwd;
|
||||
uint8_t advert_interval; // minutes / 2
|
||||
uint8_t flood_advert_interval; // hours
|
||||
float rx_delay_base;
|
||||
float tx_delay_factor;
|
||||
char guest_password[16];
|
||||
float direct_tx_delay_factor;
|
||||
uint32_t guard;
|
||||
uint8_t sf;
|
||||
uint8_t cr;
|
||||
uint8_t allow_read_only;
|
||||
uint8_t multi_acks;
|
||||
float bw;
|
||||
uint8_t flood_max;
|
||||
uint8_t interference_threshold;
|
||||
uint8_t agc_reset_interval; // secs / 4
|
||||
// Bridge settings
|
||||
uint8_t bridge_enabled; // boolean
|
||||
uint16_t bridge_delay; // milliseconds (default 500 ms)
|
||||
uint8_t bridge_pkt_src; // 0 = logTx, 1 = logRx (default logTx)
|
||||
uint32_t bridge_baud; // 9600, 19200, 38400, 57600, 115200 (default 115200)
|
||||
uint8_t bridge_channel; // 1-14 (ESP-NOW only)
|
||||
char bridge_secret[16]; // for XOR encryption of bridge packets (ESP-NOW only)
|
||||
// Gps settings
|
||||
uint8_t gps_enabled;
|
||||
uint32_t gps_interval; // in seconds
|
||||
uint8_t advert_loc_policy;
|
||||
uint32_t discovery_mod_timestamp;
|
||||
};
|
||||
|
||||
class CommonCLICallbacks {
|
||||
@@ -46,10 +67,21 @@ public:
|
||||
virtual void removeNeighbor(const uint8_t* pubkey, int key_len) {
|
||||
// no op by default
|
||||
};
|
||||
virtual void formatStatsReply(char *reply) = 0;
|
||||
virtual void formatRadioStatsReply(char *reply) = 0;
|
||||
virtual void formatPacketStatsReply(char *reply) = 0;
|
||||
virtual mesh::LocalIdentity& getSelfId() = 0;
|
||||
virtual void saveIdentity(const mesh::LocalIdentity& new_id) = 0;
|
||||
virtual void clearStats() = 0;
|
||||
virtual void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) = 0;
|
||||
|
||||
virtual void setBridgeState(bool enable) {
|
||||
// no op by default
|
||||
};
|
||||
|
||||
virtual void restartBridge() {
|
||||
// no op by default
|
||||
};
|
||||
};
|
||||
|
||||
class CommonCLI {
|
||||
@@ -57,6 +89,7 @@ class CommonCLI {
|
||||
NodePrefs* _prefs;
|
||||
CommonCLICallbacks* _callbacks;
|
||||
mesh::MainBoard* _board;
|
||||
SensorManager* _sensors;
|
||||
char tmp[PRV_KEY_SIZE*2 + 4];
|
||||
|
||||
mesh::RTCClock* getRTCClock() { return _rtc; }
|
||||
@@ -64,10 +97,11 @@ class CommonCLI {
|
||||
void loadPrefsInt(FILESYSTEM* _fs, const char* filename);
|
||||
|
||||
public:
|
||||
CommonCLI(mesh::MainBoard& board, mesh::RTCClock& rtc, NodePrefs* prefs, CommonCLICallbacks* callbacks)
|
||||
: _board(&board), _rtc(&rtc), _prefs(prefs), _callbacks(callbacks) { }
|
||||
CommonCLI(mesh::MainBoard& board, mesh::RTCClock& rtc, SensorManager& sensors, NodePrefs* prefs, CommonCLICallbacks* callbacks)
|
||||
: _board(&board), _rtc(&rtc), _sensors(&sensors), _prefs(prefs), _callbacks(callbacks) { }
|
||||
|
||||
void loadPrefs(FILESYSTEM* _fs);
|
||||
void savePrefs(FILESYSTEM* _fs);
|
||||
void handleCommand(uint32_t sender_timestamp, const char* command, char* reply);
|
||||
uint8_t buildAdvertData(uint8_t node_type, uint8_t* app_data);
|
||||
};
|
||||
|
||||
237
src/helpers/RegionMap.cpp
Normal file
237
src/helpers/RegionMap.cpp
Normal file
@@ -0,0 +1,237 @@
|
||||
#include "RegionMap.h"
|
||||
#include <helpers/TxtDataHelpers.h>
|
||||
#include <SHA256.h>
|
||||
|
||||
RegionMap::RegionMap(TransportKeyStore& store) : _store(&store) {
|
||||
next_id = 1; num_regions = 0; home_id = 0;
|
||||
wildcard.id = wildcard.parent = 0;
|
||||
wildcard.flags = 0; // default behaviour, allow flood and direct
|
||||
strcpy(wildcard.name, "*");
|
||||
}
|
||||
|
||||
bool RegionMap::is_name_char(char c) {
|
||||
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '.' || c == '_' || c == '#';
|
||||
}
|
||||
|
||||
static File openWrite(FILESYSTEM* _fs, const char* filename) {
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
_fs->remove(filename);
|
||||
return _fs->open(filename, FILE_O_WRITE);
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
return _fs->open(filename, "w");
|
||||
#else
|
||||
return _fs->open(filename, "w", true);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool RegionMap::load(FILESYSTEM* _fs) {
|
||||
if (_fs->exists("/regions2")) {
|
||||
#if defined(RP2040_PLATFORM)
|
||||
File file = _fs->open("/regions2", "r");
|
||||
#else
|
||||
File file = _fs->open("/regions2");
|
||||
#endif
|
||||
|
||||
if (file) {
|
||||
uint8_t pad[128];
|
||||
|
||||
num_regions = 0; next_id = 1; home_id = 0;
|
||||
|
||||
bool success = file.read(pad, 5) == 5; // reserved header
|
||||
success = success && file.read((uint8_t *) &home_id, sizeof(home_id)) == sizeof(home_id);
|
||||
success = success && file.read((uint8_t *) &wildcard.flags, sizeof(wildcard.flags)) == sizeof(wildcard.flags);
|
||||
success = success && file.read((uint8_t *) &next_id, sizeof(next_id)) == sizeof(next_id);
|
||||
|
||||
if (success) {
|
||||
while (num_regions < MAX_REGION_ENTRIES) {
|
||||
auto r = ®ions[num_regions];
|
||||
|
||||
success = file.read((uint8_t *) &r->id, sizeof(r->id)) == sizeof(r->id);
|
||||
success = success && file.read((uint8_t *) &r->parent, sizeof(r->parent)) == sizeof(r->parent);
|
||||
success = success && file.read((uint8_t *) r->name, sizeof(r->name)) == sizeof(r->name);
|
||||
success = success && file.read((uint8_t *) &r->flags, sizeof(r->flags)) == sizeof(r->flags);
|
||||
success = success && file.read(pad, sizeof(pad)) == sizeof(pad);
|
||||
|
||||
if (!success) break; // EOF
|
||||
|
||||
if (r->id >= next_id) { // make sure next_id is valid
|
||||
next_id = r->id + 1;
|
||||
}
|
||||
num_regions++;
|
||||
}
|
||||
}
|
||||
file.close();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false; // failed
|
||||
}
|
||||
|
||||
bool RegionMap::save(FILESYSTEM* _fs) {
|
||||
File file = openWrite(_fs, "/regions2");
|
||||
if (file) {
|
||||
uint8_t pad[128];
|
||||
memset(pad, 0, sizeof(pad));
|
||||
|
||||
bool success = file.write(pad, 5) == 5; // reserved header
|
||||
success = success && file.write((uint8_t *) &home_id, sizeof(home_id)) == sizeof(home_id);
|
||||
success = success && file.write((uint8_t *) &wildcard.flags, sizeof(wildcard.flags)) == sizeof(wildcard.flags);
|
||||
success = success && file.write((uint8_t *) &next_id, sizeof(next_id)) == sizeof(next_id);
|
||||
|
||||
if (success) {
|
||||
for (int i = 0; i < num_regions; i++) {
|
||||
auto r = ®ions[i];
|
||||
|
||||
success = file.write((uint8_t *) &r->id, sizeof(r->id)) == sizeof(r->id);
|
||||
success = success && file.write((uint8_t *) &r->parent, sizeof(r->parent)) == sizeof(r->parent);
|
||||
success = success && file.write((uint8_t *) r->name, sizeof(r->name)) == sizeof(r->name);
|
||||
success = success && file.write((uint8_t *) &r->flags, sizeof(r->flags)) == sizeof(r->flags);
|
||||
success = success && file.write(pad, sizeof(pad)) == sizeof(pad);
|
||||
if (!success) break; // write failed
|
||||
}
|
||||
}
|
||||
file.close();
|
||||
return true;
|
||||
}
|
||||
return false; // failed
|
||||
}
|
||||
|
||||
RegionEntry* RegionMap::putRegion(const char* name, uint16_t parent_id, uint16_t id) {
|
||||
const char* sp = name; // check for illegal name chars
|
||||
while (*sp) {
|
||||
if (!is_name_char(*sp)) return NULL; // error
|
||||
sp++;
|
||||
}
|
||||
|
||||
auto region = findByName(name);
|
||||
if (region) {
|
||||
if (region->id == parent_id) return NULL; // ERROR: invalid parent!
|
||||
|
||||
region->parent = parent_id; // re-parent / move this region in the hierarchy
|
||||
} else {
|
||||
if (id == 0 && num_regions >= MAX_REGION_ENTRIES) return NULL; // full!
|
||||
|
||||
region = ®ions[num_regions++]; // alloc new RegionEntry
|
||||
region->flags = REGION_DENY_FLOOD; // DENY by default
|
||||
region->id = id == 0 ? next_id++ : id;
|
||||
StrHelper::strncpy(region->name, name, sizeof(region->name));
|
||||
region->parent = parent_id;
|
||||
}
|
||||
return region;
|
||||
}
|
||||
|
||||
RegionEntry* RegionMap::findMatch(mesh::Packet* packet, uint8_t mask) {
|
||||
for (int i = 0; i < num_regions; i++) {
|
||||
auto region = ®ions[i];
|
||||
if ((region->flags & mask) == 0) { // does region allow this? (per 'mask' param)
|
||||
TransportKey keys[4];
|
||||
int num;
|
||||
if (region->name[0] == '#') { // auto hashtag region
|
||||
_store->getAutoKeyFor(region->id, region->name, keys[0]);
|
||||
num = 1;
|
||||
} else {
|
||||
num = _store->loadKeysFor(region->id, keys, 4);
|
||||
}
|
||||
for (int j = 0; j < num; j++) {
|
||||
uint16_t code = keys[j].calcTransportCode(packet);
|
||||
if (packet->transport_codes[0] == code) { // a match!!
|
||||
return region;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL; // no matches
|
||||
}
|
||||
|
||||
RegionEntry* RegionMap::findByName(const char* name) {
|
||||
if (strcmp(name, "*") == 0) return &wildcard;
|
||||
|
||||
for (int i = 0; i < num_regions; i++) {
|
||||
auto region = ®ions[i];
|
||||
if (strcmp(name, region->name) == 0) return region;
|
||||
}
|
||||
return NULL; // not found
|
||||
}
|
||||
|
||||
RegionEntry* RegionMap::findByNamePrefix(const char* prefix) {
|
||||
if (strcmp(prefix, "*") == 0) return &wildcard;
|
||||
|
||||
RegionEntry* partial = NULL;
|
||||
for (int i = 0; i < num_regions; i++) {
|
||||
auto region = ®ions[i];
|
||||
if (strcmp(prefix, region->name) == 0) return region; // is a complete match, preference this one
|
||||
if (memcmp(prefix, region->name, strlen(prefix)) == 0) {
|
||||
partial = region;
|
||||
}
|
||||
}
|
||||
return partial;
|
||||
}
|
||||
|
||||
RegionEntry* RegionMap::findById(uint16_t id) {
|
||||
if (id == 0) return &wildcard; // special root Region
|
||||
|
||||
for (int i = 0; i < num_regions; i++) {
|
||||
auto region = ®ions[i];
|
||||
if (region->id == id) return region;
|
||||
}
|
||||
return NULL; // not found
|
||||
}
|
||||
|
||||
RegionEntry* RegionMap::getHomeRegion() {
|
||||
return findById(home_id);
|
||||
}
|
||||
|
||||
void RegionMap::setHomeRegion(const RegionEntry* home) {
|
||||
home_id = home ? home->id : 0;
|
||||
}
|
||||
|
||||
bool RegionMap::removeRegion(const RegionEntry& region) {
|
||||
if (region.id == 0) return false; // failed (cannot remove the wildcard Region)
|
||||
|
||||
int i; // first check region has no child regions
|
||||
for (i = 0; i < num_regions; i++) {
|
||||
if (regions[i].parent == region.id) return false; // failed (must remove child Regions first)
|
||||
}
|
||||
|
||||
i = 0;
|
||||
while (i < num_regions) {
|
||||
if (region.id == regions[i].id) break;
|
||||
i++;
|
||||
}
|
||||
if (i >= num_regions) return false; // failed (not found)
|
||||
|
||||
num_regions--; // remove from regions array
|
||||
while (i < num_regions) {
|
||||
regions[i] = regions[i + 1];
|
||||
i++;
|
||||
}
|
||||
return true; // success
|
||||
}
|
||||
|
||||
bool RegionMap::clear() {
|
||||
num_regions = 0;
|
||||
return true; // success
|
||||
}
|
||||
|
||||
void RegionMap::printChildRegions(int indent, const RegionEntry* parent, Stream& out) const {
|
||||
for (int i = 0; i < indent; i++) {
|
||||
out.print(' ');
|
||||
}
|
||||
|
||||
if (parent->flags & REGION_DENY_FLOOD) {
|
||||
out.printf("%s%s\n", parent->name, parent->id == home_id ? "^" : "");
|
||||
} else {
|
||||
out.printf("%s%s F\n", parent->name, parent->id == home_id ? "^" : "");
|
||||
}
|
||||
|
||||
for (int i = 0; i < num_regions; i++) {
|
||||
auto r = ®ions[i];
|
||||
if (r->parent == parent->id) {
|
||||
printChildRegions(indent + 1, r, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RegionMap::exportTo(Stream& out) const {
|
||||
printChildRegions(0, &wildcard, out); // recursive
|
||||
}
|
||||
52
src/helpers/RegionMap.h
Normal file
52
src/helpers/RegionMap.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h> // needed for PlatformIO
|
||||
#include <Packet.h>
|
||||
#include "TransportKeyStore.h"
|
||||
|
||||
#ifndef MAX_REGION_ENTRIES
|
||||
#define MAX_REGION_ENTRIES 32
|
||||
#endif
|
||||
|
||||
#define REGION_DENY_FLOOD 0x01
|
||||
#define REGION_DENY_DIRECT 0x02 // reserved for future
|
||||
|
||||
struct RegionEntry {
|
||||
uint16_t id;
|
||||
uint16_t parent;
|
||||
uint8_t flags;
|
||||
char name[31];
|
||||
};
|
||||
|
||||
class RegionMap {
|
||||
TransportKeyStore* _store;
|
||||
uint16_t next_id, home_id;
|
||||
uint16_t num_regions;
|
||||
RegionEntry regions[MAX_REGION_ENTRIES];
|
||||
RegionEntry wildcard;
|
||||
|
||||
void printChildRegions(int indent, const RegionEntry* parent, Stream& out) const;
|
||||
|
||||
public:
|
||||
RegionMap(TransportKeyStore& store);
|
||||
|
||||
static bool is_name_char(char c);
|
||||
|
||||
bool load(FILESYSTEM* _fs);
|
||||
bool save(FILESYSTEM* _fs);
|
||||
|
||||
RegionEntry* putRegion(const char* name, uint16_t parent_id, uint16_t id = 0);
|
||||
RegionEntry* findMatch(mesh::Packet* packet, uint8_t mask);
|
||||
RegionEntry& getWildcard() { return wildcard; }
|
||||
RegionEntry* findByName(const char* name);
|
||||
RegionEntry* findByNamePrefix(const char* prefix);
|
||||
RegionEntry* findById(uint16_t id);
|
||||
RegionEntry* getHomeRegion(); // NOTE: can be NULL
|
||||
void setHomeRegion(const RegionEntry* home);
|
||||
bool removeRegion(const RegionEntry& region);
|
||||
bool clear();
|
||||
void resetFrom(const RegionMap& src) { num_regions = 0; next_id = src.next_id; }
|
||||
int getCount() const { return num_regions; }
|
||||
|
||||
void exportTo(Stream& out) const;
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <CayenneLPP.h>
|
||||
#include "sensors/LocationProvider.h"
|
||||
|
||||
#define TELEM_PERM_BASE 0x01 // 'base' permission includes battery
|
||||
#define TELEM_PERM_LOCATION 0x02
|
||||
@@ -21,4 +22,16 @@ public:
|
||||
virtual const char* getSettingName(int i) const { return NULL; }
|
||||
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; }
|
||||
|
||||
// Helper functions to manage setting by keys (useful in many places ...)
|
||||
const char* getSettingByKey(const char* key) {
|
||||
int num = getNumSettings();
|
||||
for (int i = 0; i < num; i++) {
|
||||
if (strcmp(getSettingName(i), key) == 0) {
|
||||
return getSettingValue(i);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
};
|
||||
|
||||
54
src/helpers/StatsFormatHelper.h
Normal file
54
src/helpers/StatsFormatHelper.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include "Mesh.h"
|
||||
|
||||
class StatsFormatHelper {
|
||||
public:
|
||||
static void formatCoreStats(char* reply,
|
||||
mesh::MainBoard& board,
|
||||
mesh::MillisecondClock& ms,
|
||||
uint16_t err_flags,
|
||||
mesh::PacketManager* mgr) {
|
||||
sprintf(reply,
|
||||
"{\"battery_mv\":%u,\"uptime_secs\":%u,\"errors\":%u,\"queue_len\":%u}",
|
||||
board.getBattMilliVolts(),
|
||||
ms.getMillis() / 1000,
|
||||
err_flags,
|
||||
mgr->getOutboundCount(0xFFFFFFFF)
|
||||
);
|
||||
}
|
||||
|
||||
template<typename RadioDriverType>
|
||||
static void formatRadioStats(char* reply,
|
||||
mesh::Radio* radio,
|
||||
RadioDriverType& driver,
|
||||
uint32_t total_air_time_ms,
|
||||
uint32_t total_rx_air_time_ms) {
|
||||
sprintf(reply,
|
||||
"{\"noise_floor\":%d,\"last_rssi\":%d,\"last_snr\":%.2f,\"tx_air_secs\":%u,\"rx_air_secs\":%u}",
|
||||
(int16_t)radio->getNoiseFloor(),
|
||||
(int16_t)driver.getLastRSSI(),
|
||||
driver.getLastSNR(),
|
||||
total_air_time_ms / 1000,
|
||||
total_rx_air_time_ms / 1000
|
||||
);
|
||||
}
|
||||
|
||||
template<typename RadioDriverType>
|
||||
static void formatPacketStats(char* reply,
|
||||
RadioDriverType& driver,
|
||||
uint32_t n_sent_flood,
|
||||
uint32_t n_sent_direct,
|
||||
uint32_t n_recv_flood,
|
||||
uint32_t n_recv_direct) {
|
||||
sprintf(reply,
|
||||
"{\"recv\":%u,\"sent\":%u,\"flood_tx\":%u,\"direct_tx\":%u,\"flood_rx\":%u,\"direct_rx\":%u}",
|
||||
driver.getPacketsRecv(),
|
||||
driver.getPacketsSent(),
|
||||
n_sent_flood,
|
||||
n_sent_direct,
|
||||
n_recv_flood,
|
||||
n_recv_direct
|
||||
);
|
||||
}
|
||||
};
|
||||
92
src/helpers/TransportKeyStore.cpp
Normal file
92
src/helpers/TransportKeyStore.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
#include "TransportKeyStore.h"
|
||||
#include <SHA256.h>
|
||||
|
||||
uint16_t TransportKey::calcTransportCode(const mesh::Packet* packet) const {
|
||||
uint16_t code;
|
||||
SHA256 sha;
|
||||
sha.resetHMAC(key, sizeof(key));
|
||||
uint8_t type = packet->getPayloadType();
|
||||
sha.update(&type, 1);
|
||||
sha.update(packet->payload, packet->payload_len);
|
||||
sha.finalizeHMAC(key, sizeof(key), &code, 2);
|
||||
if (code == 0) { // reserve codes 0000 and FFFF
|
||||
code++;
|
||||
} else if (code == 0xFFFF) {
|
||||
code--;
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
bool TransportKey::isNull() const {
|
||||
for (int i = 0; i < sizeof(key); i++) {
|
||||
if (key[i]) return false;
|
||||
}
|
||||
return true; // key is all zeroes
|
||||
}
|
||||
|
||||
void TransportKeyStore::putCache(uint16_t id, const TransportKey& key) {
|
||||
if (num_cache < MAX_TKS_ENTRIES) {
|
||||
cache_ids[num_cache] = id;
|
||||
cache_keys[num_cache] = key;
|
||||
num_cache++;
|
||||
} else {
|
||||
// TODO: evict oldest cache entry
|
||||
}
|
||||
}
|
||||
|
||||
void TransportKeyStore::getAutoKeyFor(uint16_t id, const char* name, TransportKey& dest) {
|
||||
for (int i = 0; i < num_cache; i++) { // first, check cache
|
||||
if (cache_ids[i] == id) { // cache hit!
|
||||
dest = cache_keys[i];
|
||||
return;
|
||||
}
|
||||
}
|
||||
// calc key for publicly-known hashtag region name
|
||||
SHA256 sha;
|
||||
sha.update(name, strlen(name));
|
||||
sha.finalize(&dest.key, sizeof(dest.key));
|
||||
|
||||
putCache(id, dest);
|
||||
}
|
||||
|
||||
int TransportKeyStore::loadKeysFor(uint16_t id, TransportKey keys[], int max_num) {
|
||||
int n = 0;
|
||||
for (int i = 0; i < num_cache && n < max_num; i++) { // first, check cache
|
||||
if (cache_ids[i] == id) {
|
||||
keys[n++] = cache_keys[i];
|
||||
}
|
||||
}
|
||||
if (n > 0) return n; // cache hit!
|
||||
|
||||
// TODO: retrieve from difficult-to-copy keystore
|
||||
|
||||
// store in cache (if room)
|
||||
for (int i = 0; i < n; i++) {
|
||||
putCache(id, keys[i]);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
bool TransportKeyStore::saveKeysFor(uint16_t id, const TransportKey keys[], int num) {
|
||||
invalidateCache();
|
||||
|
||||
// TODO: update hardware keystore
|
||||
|
||||
return false; // failed
|
||||
}
|
||||
|
||||
bool TransportKeyStore::removeKeys(uint16_t id) {
|
||||
invalidateCache();
|
||||
|
||||
// TODO: remove from hardware keystore
|
||||
|
||||
return false; // failed
|
||||
}
|
||||
|
||||
bool TransportKeyStore::clear() {
|
||||
invalidateCache();
|
||||
|
||||
// TODO: clear hardware keystore
|
||||
|
||||
return false; // failed
|
||||
}
|
||||
31
src/helpers/TransportKeyStore.h
Normal file
31
src/helpers/TransportKeyStore.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h> // needed for PlatformIO
|
||||
#include <Packet.h>
|
||||
#include <helpers/IdentityStore.h>
|
||||
|
||||
struct TransportKey {
|
||||
uint8_t key[16];
|
||||
|
||||
uint16_t calcTransportCode(const mesh::Packet* packet) const;
|
||||
bool isNull() const;
|
||||
};
|
||||
|
||||
#define MAX_TKS_ENTRIES 16
|
||||
|
||||
class TransportKeyStore {
|
||||
uint16_t cache_ids[MAX_TKS_ENTRIES];
|
||||
TransportKey cache_keys[MAX_TKS_ENTRIES];
|
||||
int num_cache;
|
||||
|
||||
void putCache(uint16_t id, const TransportKey& key);
|
||||
void invalidateCache() { num_cache = 0; }
|
||||
|
||||
public:
|
||||
TransportKeyStore() { num_cache = 0; }
|
||||
void getAutoKeyFor(uint16_t id, const char* name, TransportKey& dest);
|
||||
int loadKeysFor(uint16_t id, TransportKey keys[], int max_num);
|
||||
bool saveKeysFor(uint16_t id, const TransportKey keys[], int num);
|
||||
bool removeKeys(uint16_t id);
|
||||
bool clear();
|
||||
};
|
||||
@@ -19,6 +19,13 @@ void StrHelper::strzcpy(char* dest, const char* src, size_t buf_sz) {
|
||||
}
|
||||
}
|
||||
|
||||
bool StrHelper::isBlank(const char* str) {
|
||||
while (*str) {
|
||||
if (*str++ != ' ') return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
union int32_Float_t
|
||||
@@ -132,3 +139,23 @@ const char* StrHelper::ftoa(float f) {
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
uint32_t StrHelper::fromHex(const char* src) {
|
||||
uint32_t n = 0;
|
||||
while (*src) {
|
||||
if (*src >= '0' && *src <= '9') {
|
||||
n <<= 4;
|
||||
n |= (*src - '0');
|
||||
} else if (*src >= 'A' && *src <= 'F') {
|
||||
n <<= 4;
|
||||
n |= (*src - 'A' + 10);
|
||||
} else if (*src >= 'a' && *src <= 'f') {
|
||||
n <<= 4;
|
||||
n |= (*src - 'a' + 10);
|
||||
} else {
|
||||
break; // non-hex char encountered, stop parsing
|
||||
}
|
||||
src++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
@@ -12,4 +12,6 @@ public:
|
||||
static void strncpy(char* dest, const char* src, size_t buf_sz);
|
||||
static void strzcpy(char* dest, const char* src, size_t buf_sz); // pads with trailing nulls
|
||||
static const char* ftoa(float f);
|
||||
static bool isBlank(const char* str);
|
||||
static uint32_t fromHex(const char* src);
|
||||
};
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
bool BridgeBase::isRunning() const {
|
||||
return _initialized;
|
||||
}
|
||||
|
||||
const char *BridgeBase::getLogDateTime() {
|
||||
static char tmp[32];
|
||||
uint32_t now = _rtc->getCurrentTime();
|
||||
@@ -28,8 +32,16 @@ bool BridgeBase::validateChecksum(const uint8_t *data, size_t len, uint16_t rece
|
||||
}
|
||||
|
||||
void BridgeBase::handleReceivedPacket(mesh::Packet *packet) {
|
||||
// Guard against uninitialized state
|
||||
if (_initialized == false) {
|
||||
BRIDGE_DEBUG_PRINTLN("RX packet received before initialization\n");
|
||||
_mgr->free(packet);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_seen_packets.hasSeen(packet)) {
|
||||
_mgr->queueInbound(packet, millis() + BRIDGE_DELAY);
|
||||
// bridge_delay provides a buffer to prevent immediate processing conflicts in the mesh network.
|
||||
_mgr->queueInbound(packet, millis() + _prefs->bridge_delay);
|
||||
} else {
|
||||
_mgr->free(packet);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "helpers/AbstractBridge.h"
|
||||
#include "helpers/CommonCLI.h"
|
||||
#include "helpers/SimpleMeshTables.h"
|
||||
|
||||
#include <RTClib.h>
|
||||
@@ -21,6 +22,13 @@ class BridgeBase : public AbstractBridge {
|
||||
public:
|
||||
virtual ~BridgeBase() = default;
|
||||
|
||||
/**
|
||||
* @brief Gets the current state of the bridge.
|
||||
*
|
||||
* @return true if the bridge is initialized and running, false otherwise.
|
||||
*/
|
||||
bool isRunning() const override;
|
||||
|
||||
/**
|
||||
* @brief Common magic number used by all bridge implementations for packet identification
|
||||
*
|
||||
@@ -41,31 +49,31 @@ public:
|
||||
static constexpr uint16_t BRIDGE_LENGTH_SIZE = sizeof(uint16_t);
|
||||
static constexpr uint16_t BRIDGE_CHECKSUM_SIZE = sizeof(uint16_t);
|
||||
|
||||
/**
|
||||
* @brief Default delay in milliseconds for scheduling inbound packet processing
|
||||
*
|
||||
* It provides a buffer to prevent immediate processing conflicts in the mesh network.
|
||||
* Used in handleReceivedPacket() as: millis() + BRIDGE_DELAY
|
||||
*/
|
||||
static constexpr uint16_t BRIDGE_DELAY = 500; // TODO: maybe too high ?
|
||||
|
||||
protected:
|
||||
/** Tracks bridge state */
|
||||
bool _initialized = false;
|
||||
|
||||
/** Packet manager for allocating and queuing mesh packets */
|
||||
mesh::PacketManager *_mgr;
|
||||
|
||||
/** RTC clock for timestamping debug messages */
|
||||
mesh::RTCClock *_rtc;
|
||||
|
||||
/** Node preferences for configuration settings */
|
||||
NodePrefs *_prefs;
|
||||
|
||||
/** Tracks seen packets to prevent loops in broadcast communications */
|
||||
SimpleMeshTables _seen_packets;
|
||||
|
||||
/**
|
||||
* @brief Constructs a BridgeBase instance
|
||||
*
|
||||
* @param prefs Node preferences for configuration settings
|
||||
* @param mgr PacketManager for allocating and queuing packets
|
||||
* @param rtc RTCClock for timestamping debug messages
|
||||
*/
|
||||
BridgeBase(mesh::PacketManager *mgr, mesh::RTCClock *rtc) : _mgr(mgr), _rtc(rtc) {}
|
||||
BridgeBase(NodePrefs *prefs, mesh::PacketManager *mgr, mesh::RTCClock *rtc)
|
||||
: _prefs(prefs), _mgr(mgr), _rtc(rtc) {}
|
||||
|
||||
/**
|
||||
* @brief Gets formatted date/time string for logging
|
||||
|
||||
@@ -21,18 +21,26 @@ void ESPNowBridge::send_cb(const uint8_t *mac, esp_now_send_status_t status) {
|
||||
}
|
||||
}
|
||||
|
||||
ESPNowBridge::ESPNowBridge(mesh::PacketManager *mgr, mesh::RTCClock *rtc)
|
||||
: BridgeBase(mgr, rtc), _rx_buffer_pos(0) {
|
||||
ESPNowBridge::ESPNowBridge(NodePrefs *prefs, mesh::PacketManager *mgr, mesh::RTCClock *rtc)
|
||||
: BridgeBase(prefs, mgr, rtc), _rx_buffer_pos(0) {
|
||||
_instance = this;
|
||||
}
|
||||
|
||||
void ESPNowBridge::begin() {
|
||||
BRIDGE_DEBUG_PRINTLN("Initializing...\n");
|
||||
|
||||
// Initialize WiFi in station mode
|
||||
WiFi.mode(WIFI_STA);
|
||||
|
||||
// Set wifi channel
|
||||
if (esp_wifi_set_channel(_prefs->bridge_channel, WIFI_SECOND_CHAN_NONE) != ESP_OK) {
|
||||
BRIDGE_DEBUG_PRINTLN("Error setting WIFI channel to %d\n", _prefs->bridge_channel);
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize ESP-NOW
|
||||
if (esp_now_init() != ESP_OK) {
|
||||
Serial.printf("%s: ESPNOW BRIDGE: Error initializing ESP-NOW\n", getLogDateTime());
|
||||
BRIDGE_DEBUG_PRINTLN("Error initializing ESP-NOW\n");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -44,13 +52,41 @@ void ESPNowBridge::begin() {
|
||||
esp_now_peer_info_t peerInfo = {};
|
||||
memset(&peerInfo, 0, sizeof(peerInfo));
|
||||
memset(peerInfo.peer_addr, 0xFF, ESP_NOW_ETH_ALEN); // Broadcast address
|
||||
peerInfo.channel = 0;
|
||||
peerInfo.channel = _prefs->bridge_channel;
|
||||
peerInfo.encrypt = false;
|
||||
|
||||
if (esp_now_add_peer(&peerInfo) != ESP_OK) {
|
||||
Serial.printf("%s: ESPNOW BRIDGE: Failed to add broadcast peer\n", getLogDateTime());
|
||||
BRIDGE_DEBUG_PRINTLN("Failed to add broadcast peer\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Update bridge state
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
void ESPNowBridge::end() {
|
||||
BRIDGE_DEBUG_PRINTLN("Stopping...\n");
|
||||
|
||||
// Remove broadcast peer
|
||||
uint8_t broadcastAddress[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
|
||||
if (esp_now_del_peer(broadcastAddress) != ESP_OK) {
|
||||
BRIDGE_DEBUG_PRINTLN("Error removing broadcast peer\n");
|
||||
}
|
||||
|
||||
// Unregister callbacks
|
||||
esp_now_register_recv_cb(nullptr);
|
||||
esp_now_register_send_cb(nullptr);
|
||||
|
||||
// Deinitialize ESP-NOW
|
||||
if (esp_now_deinit() != ESP_OK) {
|
||||
BRIDGE_DEBUG_PRINTLN("Error deinitializing ESP-NOW\n");
|
||||
}
|
||||
|
||||
// Turn off WiFi
|
||||
WiFi.mode(WIFI_OFF);
|
||||
|
||||
// Update bridge state
|
||||
_initialized = false;
|
||||
}
|
||||
|
||||
void ESPNowBridge::loop() {
|
||||
@@ -58,35 +94,29 @@ void ESPNowBridge::loop() {
|
||||
}
|
||||
|
||||
void ESPNowBridge::xorCrypt(uint8_t *data, size_t len) {
|
||||
size_t keyLen = strlen(_secret);
|
||||
size_t keyLen = strlen(_prefs->bridge_secret);
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
data[i] ^= _secret[i % keyLen];
|
||||
data[i] ^= _prefs->bridge_secret[i % keyLen];
|
||||
}
|
||||
}
|
||||
|
||||
void ESPNowBridge::onDataRecv(const uint8_t *mac, const uint8_t *data, int32_t len) {
|
||||
// Ignore packets that are too small to contain header + checksum
|
||||
if (len < (BRIDGE_MAGIC_SIZE + BRIDGE_CHECKSUM_SIZE)) {
|
||||
#if MESH_PACKET_LOGGING
|
||||
Serial.printf("%s: ESPNOW BRIDGE: RX packet too small, len=%d\n", getLogDateTime(), len);
|
||||
#endif
|
||||
BRIDGE_DEBUG_PRINTLN("RX packet too small, len=%d\n", len);
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate total packet size
|
||||
if (len > MAX_ESPNOW_PACKET_SIZE) {
|
||||
#if MESH_PACKET_LOGGING
|
||||
Serial.printf("%s: ESPNOW BRIDGE: RX packet too large, len=%d\n", getLogDateTime(), len);
|
||||
#endif
|
||||
BRIDGE_DEBUG_PRINTLN("RX packet too large, len=%d\n", len);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check packet header magic
|
||||
uint16_t received_magic = (data[0] << 8) | data[1];
|
||||
if (received_magic != BRIDGE_PACKET_MAGIC) {
|
||||
#if MESH_PACKET_LOGGING
|
||||
Serial.printf("%s: ESPNOW BRIDGE: RX invalid magic 0x%04X\n", getLogDateTime(), received_magic);
|
||||
#endif
|
||||
BRIDGE_DEBUG_PRINTLN("RX invalid magic 0x%04X\n", received_magic);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -104,16 +134,11 @@ void ESPNowBridge::onDataRecv(const uint8_t *mac, const uint8_t *data, int32_t l
|
||||
|
||||
if (!validateChecksum(decrypted + BRIDGE_CHECKSUM_SIZE, payloadLen, received_checksum)) {
|
||||
// Failed to decrypt - likely from a different network
|
||||
#if MESH_PACKET_LOGGING
|
||||
Serial.printf("%s: ESPNOW BRIDGE: RX checksum mismatch, rcv=0x%04X\n", getLogDateTime(),
|
||||
received_checksum);
|
||||
#endif
|
||||
BRIDGE_DEBUG_PRINTLN("RX checksum mismatch, rcv=0x%04X\n", received_checksum);
|
||||
return;
|
||||
}
|
||||
|
||||
#if MESH_PACKET_LOGGING
|
||||
Serial.printf("%s: ESPNOW BRIDGE: RX, payload_len=%d\n", getLogDateTime(), payloadLen);
|
||||
#endif
|
||||
BRIDGE_DEBUG_PRINTLN("RX, payload_len=%d\n", payloadLen);
|
||||
|
||||
// Create mesh packet
|
||||
mesh::Packet *pkt = _instance->_mgr->allocNew();
|
||||
@@ -130,31 +155,27 @@ void ESPNowBridge::onDataSent(const uint8_t *mac_addr, esp_now_send_status_t sta
|
||||
// Could add transmission error handling here if needed
|
||||
}
|
||||
|
||||
void ESPNowBridge::onPacketReceived(mesh::Packet *packet) {
|
||||
handleReceivedPacket(packet);
|
||||
}
|
||||
void ESPNowBridge::sendPacket(mesh::Packet *packet) {
|
||||
// Guard against uninitialized state
|
||||
if (_initialized == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
void ESPNowBridge::onPacketTransmitted(mesh::Packet *packet) {
|
||||
// First validate the packet pointer
|
||||
if (!packet) {
|
||||
#if MESH_PACKET_LOGGING
|
||||
Serial.printf("%s: ESPNOW BRIDGE: TX invalid packet pointer\n", getLogDateTime());
|
||||
#endif
|
||||
BRIDGE_DEBUG_PRINTLN("TX invalid packet pointer\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_seen_packets.hasSeen(packet)) {
|
||||
|
||||
// Create a temporary buffer just for size calculation and reuse for actual writing
|
||||
uint8_t sizingBuffer[MAX_PAYLOAD_SIZE];
|
||||
uint16_t meshPacketLen = packet->writeTo(sizingBuffer);
|
||||
|
||||
// Check if packet fits within our maximum payload size
|
||||
if (meshPacketLen > MAX_PAYLOAD_SIZE) {
|
||||
#if MESH_PACKET_LOGGING
|
||||
Serial.printf("%s: ESPNOW BRIDGE: TX packet too large (payload=%d, max=%d)\n", getLogDateTime(),
|
||||
meshPacketLen, MAX_PAYLOAD_SIZE);
|
||||
#endif
|
||||
BRIDGE_DEBUG_PRINTLN("TX packet too large (payload=%d, max=%d)\n", meshPacketLen,
|
||||
MAX_PAYLOAD_SIZE);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -183,14 +204,16 @@ void ESPNowBridge::onPacketTransmitted(mesh::Packet *packet) {
|
||||
uint8_t broadcastAddress[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
|
||||
esp_err_t result = esp_now_send(broadcastAddress, buffer, totalPacketSize);
|
||||
|
||||
#if MESH_PACKET_LOGGING
|
||||
if (result == ESP_OK) {
|
||||
Serial.printf("%s: ESPNOW BRIDGE: TX, len=%d\n", getLogDateTime(), meshPacketLen);
|
||||
BRIDGE_DEBUG_PRINTLN("TX, len=%d\n", meshPacketLen);
|
||||
} else {
|
||||
Serial.printf("%s: ESPNOW BRIDGE: TX FAILED!\n", getLogDateTime());
|
||||
BRIDGE_DEBUG_PRINTLN("TX FAILED!\n");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void ESPNowBridge::onPacketReceived(mesh::Packet *packet) {
|
||||
handleReceivedPacket(packet);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -6,10 +6,6 @@
|
||||
|
||||
#ifdef WITH_ESPNOW_BRIDGE
|
||||
|
||||
#ifndef WITH_ESPNOW_BRIDGE_SECRET
|
||||
#error WITH_ESPNOW_BRIDGE_SECRET must be defined to use ESPNowBridge
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Bridge implementation using ESP-NOW protocol for packet transport
|
||||
*
|
||||
@@ -36,11 +32,11 @@
|
||||
*
|
||||
* Configuration:
|
||||
* - Define WITH_ESPNOW_BRIDGE to enable this bridge
|
||||
* - Define WITH_ESPNOW_BRIDGE_SECRET with a string to set the network encryption key
|
||||
* - Define _prefs->bridge_secret with a string to set the network encryption key
|
||||
*
|
||||
* Network Isolation:
|
||||
* Multiple independent mesh networks can coexist by using different
|
||||
* WITH_ESPNOW_BRIDGE_SECRET values. Packets encrypted with a different key will
|
||||
* _prefs->bridge_secret values. Packets encrypted with a different key will
|
||||
* fail the checksum validation and be discarded.
|
||||
*/
|
||||
class ESPNowBridge : public BridgeBase {
|
||||
@@ -73,17 +69,11 @@ private:
|
||||
/** Current position in receive buffer */
|
||||
size_t _rx_buffer_pos;
|
||||
|
||||
/**
|
||||
* Network encryption key from build define
|
||||
* Must be defined with WITH_ESPNOW_BRIDGE_SECRET
|
||||
* Used for XOR encryption to isolate different mesh networks
|
||||
*/
|
||||
const char *_secret = WITH_ESPNOW_BRIDGE_SECRET;
|
||||
|
||||
/**
|
||||
* Performs XOR encryption/decryption of data
|
||||
* Used to isolate different mesh networks
|
||||
*
|
||||
* Uses WITH_ESPNOW_BRIDGE_SECRET as the key in a simple XOR operation.
|
||||
* Uses _prefs->bridge_secret as the key in a simple XOR operation.
|
||||
* The same operation is used for both encryption and decryption.
|
||||
* While not cryptographically secure, it provides basic network isolation.
|
||||
*
|
||||
@@ -115,10 +105,11 @@ public:
|
||||
/**
|
||||
* Constructs an ESPNowBridge instance
|
||||
*
|
||||
* @param prefs Node preferences for configuration settings
|
||||
* @param mgr PacketManager for allocating and queuing packets
|
||||
* @param rtc RTCClock for timestamping debug messages
|
||||
*/
|
||||
ESPNowBridge(mesh::PacketManager *mgr, mesh::RTCClock *rtc);
|
||||
ESPNowBridge(NodePrefs *prefs, mesh::PacketManager *mgr, mesh::RTCClock *rtc);
|
||||
|
||||
/**
|
||||
* Initializes the ESP-NOW bridge
|
||||
@@ -130,6 +121,16 @@ public:
|
||||
*/
|
||||
void begin() override;
|
||||
|
||||
/**
|
||||
* Stops the ESP-NOW bridge
|
||||
*
|
||||
* - Removes broadcast peer
|
||||
* - Unregisters callbacks
|
||||
* - Deinitializes ESP-NOW protocol
|
||||
* - Turns off WiFi to release radio resources
|
||||
*/
|
||||
void end() override;
|
||||
|
||||
/**
|
||||
* Main loop handler
|
||||
* ESP-NOW is callback-based, so this is currently empty
|
||||
@@ -150,7 +151,7 @@ public:
|
||||
*
|
||||
* @param packet The mesh packet to transmit
|
||||
*/
|
||||
void onPacketTransmitted(mesh::Packet *packet) override;
|
||||
void sendPacket(mesh::Packet *packet) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
|
||||
#ifdef WITH_RS232_BRIDGE
|
||||
|
||||
RS232Bridge::RS232Bridge(Stream &serial, mesh::PacketManager *mgr, mesh::RTCClock *rtc)
|
||||
: BridgeBase(mgr, rtc), _serial(&serial) {}
|
||||
RS232Bridge::RS232Bridge(NodePrefs *prefs, Stream &serial, mesh::PacketManager *mgr, mesh::RTCClock *rtc)
|
||||
: BridgeBase(prefs, mgr, rtc), _serial(&serial) {}
|
||||
|
||||
void RS232Bridge::begin() {
|
||||
BRIDGE_DEBUG_PRINTLN("Initializing at %d baud...\n", _prefs->bridge_baud);
|
||||
#if !defined(WITH_RS232_BRIDGE_RX) || !defined(WITH_RS232_BRIDGE_TX)
|
||||
#error "WITH_RS232_BRIDGE_RX and WITH_RS232_BRIDGE_TX must be defined"
|
||||
#endif
|
||||
@@ -25,53 +26,26 @@ void RS232Bridge::begin() {
|
||||
#else
|
||||
#error RS232Bridge was not tested on the current platform
|
||||
#endif
|
||||
((HardwareSerial *)_serial)->begin(115200);
|
||||
((HardwareSerial *)_serial)->begin(_prefs->bridge_baud);
|
||||
|
||||
// Update bridge state
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
void RS232Bridge::onPacketTransmitted(mesh::Packet *packet) {
|
||||
// First validate the packet pointer
|
||||
if (!packet) {
|
||||
#if MESH_PACKET_LOGGING
|
||||
Serial.printf("%s: RS232 BRIDGE: TX invalid packet pointer\n", getLogDateTime());
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
void RS232Bridge::end() {
|
||||
BRIDGE_DEBUG_PRINTLN("Stopping...\n");
|
||||
((HardwareSerial *)_serial)->end();
|
||||
|
||||
if (!_seen_packets.hasSeen(packet)) {
|
||||
|
||||
uint8_t buffer[MAX_SERIAL_PACKET_SIZE];
|
||||
uint16_t len = packet->writeTo(buffer + 4);
|
||||
|
||||
// Check if packet fits within our maximum payload size
|
||||
if (len > (MAX_TRANS_UNIT + 1)) {
|
||||
#if MESH_PACKET_LOGGING
|
||||
Serial.printf("%s: RS232 BRIDGE: TX packet too large (payload=%d, max=%d)\n", getLogDateTime(), len,
|
||||
MAX_TRANS_UNIT + 1);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
// Build packet header
|
||||
buffer[0] = (BRIDGE_PACKET_MAGIC >> 8) & 0xFF; // Magic high byte
|
||||
buffer[1] = BRIDGE_PACKET_MAGIC & 0xFF; // Magic low byte
|
||||
buffer[2] = (len >> 8) & 0xFF; // Length high byte
|
||||
buffer[3] = len & 0xFF; // Length low byte
|
||||
|
||||
// Calculate checksum over the payload
|
||||
uint16_t checksum = fletcher16(buffer + 4, len);
|
||||
buffer[4 + len] = (checksum >> 8) & 0xFF; // Checksum high byte
|
||||
buffer[5 + len] = checksum & 0xFF; // Checksum low byte
|
||||
|
||||
// Send complete packet
|
||||
_serial->write(buffer, len + SERIAL_OVERHEAD);
|
||||
|
||||
#if MESH_PACKET_LOGGING
|
||||
Serial.printf("%s: RS232 BRIDGE: TX, len=%d crc=0x%04x\n", getLogDateTime(), len, checksum);
|
||||
#endif
|
||||
}
|
||||
// Update bridge state
|
||||
_initialized = false;
|
||||
}
|
||||
|
||||
void RS232Bridge::loop() {
|
||||
// Guard against uninitialized state
|
||||
if (_initialized == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (_serial->available()) {
|
||||
uint8_t b = _serial->read();
|
||||
|
||||
@@ -97,9 +71,7 @@ void RS232Bridge::loop() {
|
||||
|
||||
// Validate length field
|
||||
if (len > (MAX_TRANS_UNIT + 1)) {
|
||||
#if MESH_PACKET_LOGGING
|
||||
Serial.printf("%s: RS232 BRIDGE: RX invalid length %d, resetting\n", getLogDateTime(), len);
|
||||
#endif
|
||||
BRIDGE_DEBUG_PRINTLN("RX invalid length %d, resetting\n", len);
|
||||
_rx_buffer_pos = 0; // Invalid length, reset
|
||||
continue;
|
||||
}
|
||||
@@ -108,30 +80,20 @@ void RS232Bridge::loop() {
|
||||
uint16_t received_checksum = (_rx_buffer[4 + len] << 8) | _rx_buffer[5 + len];
|
||||
|
||||
if (validateChecksum(_rx_buffer + 4, len, received_checksum)) {
|
||||
#if MESH_PACKET_LOGGING
|
||||
Serial.printf("%s: RS232 BRIDGE: RX, len=%d crc=0x%04x\n", getLogDateTime(), len,
|
||||
received_checksum);
|
||||
#endif
|
||||
BRIDGE_DEBUG_PRINTLN("RX, len=%d crc=0x%04x\n", len, received_checksum);
|
||||
mesh::Packet *pkt = _mgr->allocNew();
|
||||
if (pkt) {
|
||||
if (pkt->readFrom(_rx_buffer + 4, len)) {
|
||||
onPacketReceived(pkt);
|
||||
} else {
|
||||
#if MESH_PACKET_LOGGING
|
||||
Serial.printf("%s: RS232 BRIDGE: RX failed to parse packet\n", getLogDateTime());
|
||||
#endif
|
||||
BRIDGE_DEBUG_PRINTLN("RX failed to parse packet\n");
|
||||
_mgr->free(pkt);
|
||||
}
|
||||
} else {
|
||||
#if MESH_PACKET_LOGGING
|
||||
Serial.printf("%s: RS232 BRIDGE: RX failed to allocate packet\n", getLogDateTime());
|
||||
#endif
|
||||
BRIDGE_DEBUG_PRINTLN("RX failed to allocate packet\n");
|
||||
}
|
||||
} else {
|
||||
#if MESH_PACKET_LOGGING
|
||||
Serial.printf("%s: RS232 BRIDGE: RX checksum mismatch, rcv=0x%04x\n", getLogDateTime(),
|
||||
received_checksum);
|
||||
#endif
|
||||
BRIDGE_DEBUG_PRINTLN("RX checksum mismatch, rcv=0x%04x\n", received_checksum);
|
||||
}
|
||||
_rx_buffer_pos = 0; // Reset for next packet
|
||||
}
|
||||
@@ -140,6 +102,48 @@ void RS232Bridge::loop() {
|
||||
}
|
||||
}
|
||||
|
||||
void RS232Bridge::sendPacket(mesh::Packet *packet) {
|
||||
// Guard against uninitialized state
|
||||
if (_initialized == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// First validate the packet pointer
|
||||
if (!packet) {
|
||||
BRIDGE_DEBUG_PRINTLN("TX invalid packet pointer\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_seen_packets.hasSeen(packet)) {
|
||||
|
||||
uint8_t buffer[MAX_SERIAL_PACKET_SIZE];
|
||||
uint16_t len = packet->writeTo(buffer + 4);
|
||||
|
||||
// Check if packet fits within our maximum payload size
|
||||
if (len > (MAX_TRANS_UNIT + 1)) {
|
||||
BRIDGE_DEBUG_PRINTLN("TX packet too large (payload=%d, max=%d)\n", len,
|
||||
MAX_TRANS_UNIT + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Build packet header
|
||||
buffer[0] = (BRIDGE_PACKET_MAGIC >> 8) & 0xFF; // Magic high byte
|
||||
buffer[1] = BRIDGE_PACKET_MAGIC & 0xFF; // Magic low byte
|
||||
buffer[2] = (len >> 8) & 0xFF; // Length high byte
|
||||
buffer[3] = len & 0xFF; // Length low byte
|
||||
|
||||
// Calculate checksum over the payload
|
||||
uint16_t checksum = fletcher16(buffer + 4, len);
|
||||
buffer[4 + len] = (checksum >> 8) & 0xFF; // Checksum high byte
|
||||
buffer[5 + len] = checksum & 0xFF; // Checksum low byte
|
||||
|
||||
// Send complete packet
|
||||
_serial->write(buffer, len + SERIAL_OVERHEAD);
|
||||
|
||||
BRIDGE_DEBUG_PRINTLN("TX, len=%d crc=0x%04x\n", len, checksum);
|
||||
}
|
||||
}
|
||||
|
||||
void RS232Bridge::onPacketReceived(mesh::Packet *packet) {
|
||||
handleReceivedPacket(packet);
|
||||
}
|
||||
|
||||
@@ -49,11 +49,12 @@ public:
|
||||
/**
|
||||
* @brief Constructs an RS232Bridge instance
|
||||
*
|
||||
* @param prefs Node preferences for configuration settings
|
||||
* @param serial The hardware serial port to use
|
||||
* @param mgr PacketManager for allocating and queuing packets
|
||||
* @param rtc RTCClock for timestamping debug messages
|
||||
*/
|
||||
RS232Bridge(Stream &serial, mesh::PacketManager *mgr, mesh::RTCClock *rtc);
|
||||
RS232Bridge(NodePrefs *prefs, Stream &serial, mesh::PacketManager *mgr, mesh::RTCClock *rtc);
|
||||
|
||||
/**
|
||||
* Initializes the RS232 bridge
|
||||
@@ -65,6 +66,12 @@ public:
|
||||
*/
|
||||
void begin() override;
|
||||
|
||||
/**
|
||||
* Stops the RS232 bridge
|
||||
*
|
||||
*/
|
||||
void end() override;
|
||||
|
||||
/**
|
||||
* @brief Main loop handler for processing incoming serial data
|
||||
*
|
||||
@@ -90,7 +97,7 @@ public:
|
||||
*
|
||||
* @param packet The mesh packet to transmit
|
||||
*/
|
||||
void onPacketTransmitted(mesh::Packet *packet) override;
|
||||
void sendPacket(mesh::Packet *packet) override;
|
||||
|
||||
/**
|
||||
* @brief Called when a complete valid packet has been received from serial
|
||||
|
||||
@@ -1,70 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <RadioLib.h>
|
||||
|
||||
#define LR1110_IRQ_HAS_PREAMBLE 0b0000000100 // 4 4 valid LoRa header received
|
||||
#define LR1110_IRQ_HEADER_VALID 0b0000010000 // 4 4 valid LoRa header received
|
||||
#include "MeshCore.h"
|
||||
|
||||
class CustomLR1110 : public LR1110 {
|
||||
public:
|
||||
CustomLR1110(Module *mod) : LR1110(mod) { }
|
||||
|
||||
RadioLibTime_t getTimeOnAir(size_t len) override {
|
||||
// calculate number of symbols
|
||||
float N_symbol = 0;
|
||||
if(this->codingRate <= RADIOLIB_LR11X0_LORA_CR_4_8_SHORT) {
|
||||
// legacy coding rate - nice and simple
|
||||
// get SF coefficients
|
||||
float coeff1 = 0;
|
||||
int16_t coeff2 = 0;
|
||||
int16_t coeff3 = 0;
|
||||
if(this->spreadingFactor < 7) {
|
||||
// SF5, SF6
|
||||
coeff1 = 6.25;
|
||||
coeff2 = 4*this->spreadingFactor;
|
||||
coeff3 = 4*this->spreadingFactor;
|
||||
} else if(this->spreadingFactor < 11) {
|
||||
// SF7. SF8, SF9, SF10
|
||||
coeff1 = 4.25;
|
||||
coeff2 = 4*this->spreadingFactor + 8;
|
||||
coeff3 = 4*this->spreadingFactor;
|
||||
} else {
|
||||
// SF11, SF12
|
||||
coeff1 = 4.25;
|
||||
coeff2 = 4*this->spreadingFactor + 8;
|
||||
coeff3 = 4*(this->spreadingFactor - 2);
|
||||
size_t getPacketLength(bool update) override {
|
||||
size_t len = LR1110::getPacketLength(update);
|
||||
if (len == 0 && getIrqStatus() & RADIOLIB_LR11X0_IRQ_HEADER_ERR) {
|
||||
// we've just recieved a corrupted packet
|
||||
// this may have triggered a bug causing subsequent packets to be shifted
|
||||
// call standby() to return radio to known-good state
|
||||
// recvRaw will call startReceive() to restart rx
|
||||
MESH_DEBUG_PRINTLN("LR1110: got header err, calling standby()");
|
||||
standby();
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
// get CRC length
|
||||
int16_t N_bitCRC = 16;
|
||||
if(this->crcTypeLoRa == RADIOLIB_LR11X0_LORA_CRC_DISABLED) {
|
||||
N_bitCRC = 0;
|
||||
}
|
||||
|
||||
// get header length
|
||||
int16_t N_symbolHeader = 20;
|
||||
if(this->headerType == RADIOLIB_LR11X0_LORA_HEADER_IMPLICIT) {
|
||||
N_symbolHeader = 0;
|
||||
}
|
||||
|
||||
// calculate number of LoRa preamble symbols - NO! Lora preamble is already in symbols
|
||||
// uint32_t N_symbolPreamble = (this->preambleLengthLoRa & 0x0F) * (uint32_t(1) << ((this->preambleLengthLoRa & 0xF0) >> 4));
|
||||
|
||||
// calculate the number of symbols - nope
|
||||
// N_symbol = (float)N_symbolPreamble + coeff1 + 8.0f + ceilf((float)RADIOLIB_MAX((int16_t)(8 * len + N_bitCRC - coeff2 + N_symbolHeader), (int16_t)0) / (float)coeff3) * (float)(this->codingRate + 4);
|
||||
// calculate the number of symbols - using only preamblelora because it's already in symbols
|
||||
N_symbol = (float)preambleLengthLoRa + coeff1 + 8.0f + ceilf((float)RADIOLIB_MAX((int16_t)(8 * len + N_bitCRC - coeff2 + N_symbolHeader), (int16_t)0) / (float)coeff3) * (float)(this->codingRate + 4);
|
||||
} else {
|
||||
// long interleaving - not needed for this modem
|
||||
}
|
||||
|
||||
// get time-on-air in us
|
||||
return(((uint32_t(1) << this->spreadingFactor) / this->bandwidthKhz) * N_symbol * 1000.0f);
|
||||
}
|
||||
|
||||
|
||||
bool isReceiving() {
|
||||
uint16_t irq = getIrqStatus();
|
||||
bool detected = ((irq & LR1110_IRQ_HEADER_VALID) || (irq & LR1110_IRQ_HAS_PREAMBLE));
|
||||
bool detected = ((irq & RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID) || (irq & RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED));
|
||||
return detected;
|
||||
}
|
||||
};
|
||||
@@ -6,6 +6,21 @@
|
||||
#define TELEM_WIRE &Wire // Use default I2C bus for Environment Sensors
|
||||
#endif
|
||||
|
||||
#ifdef ENV_INCLUDE_BME680
|
||||
#ifndef TELEM_BME680_ADDRESS
|
||||
#define TELEM_BME680_ADDRESS 0x76
|
||||
#endif
|
||||
#define TELEM_BME680_SEALEVELPRESSURE_HPA (1013.25)
|
||||
#include <Adafruit_BME680.h>
|
||||
static Adafruit_BME680 BME680;
|
||||
#endif
|
||||
|
||||
#ifdef ENV_INCLUDE_BMP085
|
||||
#define TELEM_BMP085_SEALEVELPRESSURE_HPA (1013.25)
|
||||
#include <Adafruit_BMP085.h>
|
||||
static Adafruit_BMP085 BMP085;
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_AHTX0
|
||||
#define TELEM_AHTX_ADDRESS 0x38 // AHT10, AHT20 temperature and humidity sensor I2C address
|
||||
#include <Adafruit_AHTX0.h>
|
||||
@@ -96,6 +111,41 @@ static bool serialGPSFlag = false;
|
||||
#define TELEM_RAK12500_ADDRESS 0x42 //RAK12500 Ublox GPS via i2c
|
||||
#include <SparkFun_u-blox_GNSS_Arduino_Library.h>
|
||||
static SFE_UBLOX_GNSS ublox_GNSS;
|
||||
|
||||
class RAK12500LocationProvider : public LocationProvider {
|
||||
long _lat = 0;
|
||||
long _lng = 0;
|
||||
long _alt = 0;
|
||||
int _sats = 0;
|
||||
long _epoch = 0;
|
||||
bool _fix = false;
|
||||
public:
|
||||
long getLatitude() override { return _lat; }
|
||||
long getLongitude() override { return _lng; }
|
||||
long getAltitude() override { return _alt; }
|
||||
long satellitesCount() override { return _sats; }
|
||||
bool isValid() override { return _fix; }
|
||||
long getTimestamp() override { return _epoch; }
|
||||
void sendSentence(const char * sentence) override { }
|
||||
void reset() override { }
|
||||
void begin() override { }
|
||||
void stop() override { }
|
||||
void loop() override {
|
||||
if (ublox_GNSS.getGnssFixOk(8)) {
|
||||
_fix = true;
|
||||
_lat = ublox_GNSS.getLatitude(2) / 10;
|
||||
_lng = ublox_GNSS.getLongitude(2) / 10;
|
||||
_alt = ublox_GNSS.getAltitude(2);
|
||||
_sats = ublox_GNSS.getSIV(2);
|
||||
} else {
|
||||
_fix = false;
|
||||
}
|
||||
_epoch = ublox_GNSS.getUnixEpoch(2);
|
||||
}
|
||||
bool isEnabled() override { return true; }
|
||||
};
|
||||
|
||||
static RAK12500LocationProvider RAK12500_provider;
|
||||
#endif
|
||||
|
||||
bool EnvironmentSensorManager::begin() {
|
||||
@@ -251,6 +301,28 @@ bool EnvironmentSensorManager::begin() {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_BME680
|
||||
if (BME680.begin(TELEM_BME680_ADDRESS, TELEM_WIRE)) {
|
||||
MESH_DEBUG_PRINTLN("Found BME680 at address: %02X", TELEM_BME680_ADDRESS);
|
||||
BME680_initialized = true;
|
||||
} else {
|
||||
BME680_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("BME680 was not found at I2C address %02X", TELEM_BME680_ADDRESS);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_BMP085
|
||||
// First argument is MODE (aka oversampling)
|
||||
// choose ULTRALOWPOWER
|
||||
if (BMP085.begin(0, TELEM_WIRE)) {
|
||||
MESH_DEBUG_PRINTLN("Found sensor BMP085");
|
||||
BMP085_initialized = true;
|
||||
} else {
|
||||
BMP085_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("BMP085 was not found at I2C address %02X", 0x77);
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -380,6 +452,27 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_BME680
|
||||
if (BME680_initialized) {
|
||||
if (BME680.performReading()) {
|
||||
telemetry.addTemperature(TELEM_CHANNEL_SELF, BME680.temperature);
|
||||
telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, BME680.humidity);
|
||||
telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BME680.pressure / 100);
|
||||
telemetry.addAltitude(TELEM_CHANNEL_SELF, 44330.0 * (1.0 - pow((BME680.pressure / 100) / TELEM_BME680_SEALEVELPRESSURE_HPA, 0.1903)));
|
||||
telemetry.addAnalogInput(next_available_channel, BME680.gas_resistance);
|
||||
next_available_channel++;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_BMP085
|
||||
if (BMP085_initialized) {
|
||||
telemetry.addTemperature(TELEM_CHANNEL_SELF, BMP085.readTemperature());
|
||||
telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BMP085.readPressure() / 100);
|
||||
telemetry.addAltitude(TELEM_CHANNEL_SELF, BMP085.readAltitude(TELEM_BMP085_SEALEVELPRESSURE_HPA * 100));
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -387,27 +480,34 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen
|
||||
|
||||
|
||||
int EnvironmentSensorManager::getNumSettings() const {
|
||||
int settings = 0;
|
||||
#if ENV_INCLUDE_GPS
|
||||
return gps_detected ? 1 : 0; // only show GPS setting if GPS is detected
|
||||
#else
|
||||
return 0;
|
||||
if (gps_detected) settings++; // only show GPS setting if GPS is detected
|
||||
#endif
|
||||
return settings;
|
||||
}
|
||||
|
||||
const char* EnvironmentSensorManager::getSettingName(int i) const {
|
||||
int settings = 0;
|
||||
#if ENV_INCLUDE_GPS
|
||||
return (gps_detected && i == 0) ? "gps" : NULL;
|
||||
#else
|
||||
return NULL;
|
||||
if (gps_detected && i == settings++) {
|
||||
return "gps";
|
||||
}
|
||||
#endif
|
||||
// convenient way to add params (needed for some tests)
|
||||
// if (i == settings++) return "param.2";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char* EnvironmentSensorManager::getSettingValue(int i) const {
|
||||
int settings = 0;
|
||||
#if ENV_INCLUDE_GPS
|
||||
if (gps_detected && i == 0) {
|
||||
return gps_active ? "1" : "0";
|
||||
}
|
||||
if (gps_detected && i == settings++) {
|
||||
return gps_active ? "1" : "0";
|
||||
}
|
||||
#endif
|
||||
// convenient way to add params ...
|
||||
// if (i == settings++) return "2";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -514,12 +614,22 @@ bool EnvironmentSensorManager::gpsIsAwake(uint8_t ioPin){
|
||||
//Try to init RAK12500 on I2C
|
||||
if (ublox_GNSS.begin(Wire) == true){
|
||||
MESH_DEBUG_PRINTLN("RAK12500 GPS init correctly with pin %i",ioPin);
|
||||
ublox_GNSS.setI2COutput(COM_TYPE_NMEA);
|
||||
ublox_GNSS.setI2COutput(COM_TYPE_UBX);
|
||||
ublox_GNSS.enableGNSS(true, SFE_UBLOX_GNSS_ID_GPS);
|
||||
ublox_GNSS.enableGNSS(true, SFE_UBLOX_GNSS_ID_GALILEO);
|
||||
ublox_GNSS.enableGNSS(true, SFE_UBLOX_GNSS_ID_GLONASS);
|
||||
ublox_GNSS.enableGNSS(true, SFE_UBLOX_GNSS_ID_SBAS);
|
||||
ublox_GNSS.enableGNSS(true, SFE_UBLOX_GNSS_ID_BEIDOU);
|
||||
ublox_GNSS.enableGNSS(true, SFE_UBLOX_GNSS_ID_IMES);
|
||||
ublox_GNSS.enableGNSS(true, SFE_UBLOX_GNSS_ID_QZSS);
|
||||
ublox_GNSS.setMeasurementRate(1000);
|
||||
ublox_GNSS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT);
|
||||
gpsResetPin = ioPin;
|
||||
i2cGPSFlag = true;
|
||||
gps_active = true;
|
||||
gps_detected = true;
|
||||
|
||||
_location = &RAK12500_provider;
|
||||
return true;
|
||||
}
|
||||
else if(Serial1){
|
||||
@@ -571,19 +681,13 @@ void EnvironmentSensorManager::stop_gps() {
|
||||
void EnvironmentSensorManager::loop() {
|
||||
static long next_gps_update = 0;
|
||||
|
||||
#if ENV_INCLUDE_GPS
|
||||
_location->loop();
|
||||
|
||||
if (millis() > next_gps_update) {
|
||||
if(gps_active){
|
||||
#ifdef RAK_WISBLOCK_GPS
|
||||
if(i2cGPSFlag){
|
||||
node_lat = ((double)ublox_GNSS.getLatitude())/10000000.;
|
||||
node_lon = ((double)ublox_GNSS.getLongitude())/10000000.;
|
||||
MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon);
|
||||
node_altitude = ((double)ublox_GNSS.getAltitude()) / 1000.0;
|
||||
MESH_DEBUG_PRINTLN("lat %f lon %f alt %f", node_lat, node_lon, node_altitude);
|
||||
}
|
||||
else if (serialGPSFlag && _location->isValid()) {
|
||||
if ((i2cGPSFlag || serialGPSFlag) && _location->isValid()) {
|
||||
node_lat = ((double)_location->getLatitude())/1000000.;
|
||||
node_lon = ((double)_location->getLongitude())/1000000.;
|
||||
MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon);
|
||||
@@ -602,5 +706,6 @@ void EnvironmentSensorManager::loop() {
|
||||
}
|
||||
next_gps_update = millis() + 1000;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -20,6 +20,8 @@ protected:
|
||||
bool MLX90614_initialized = false;
|
||||
bool VL53L0X_initialized = false;
|
||||
bool SHT4X_initialized = false;
|
||||
bool BME680_initialized = false;
|
||||
bool BMP085_initialized = false;
|
||||
|
||||
bool gps_detected = false;
|
||||
bool gps_active = false;
|
||||
@@ -39,6 +41,7 @@ protected:
|
||||
public:
|
||||
#if ENV_INCLUDE_GPS
|
||||
EnvironmentSensorManager(LocationProvider &location): _location(&location){};
|
||||
LocationProvider* getLocationProvider() { return _location; }
|
||||
#else
|
||||
EnvironmentSensorManager(){};
|
||||
#endif
|
||||
|
||||
@@ -17,8 +17,9 @@ public:
|
||||
virtual bool isValid() = 0;
|
||||
virtual long getTimestamp() = 0;
|
||||
virtual void sendSentence(const char * sentence);
|
||||
virtual void reset();
|
||||
virtual void begin();
|
||||
virtual void stop();
|
||||
virtual void loop();
|
||||
virtual void reset() = 0;
|
||||
virtual void begin() = 0;
|
||||
virtual void stop() = 0;
|
||||
virtual void loop() = 0;
|
||||
virtual bool isEnabled() = 0;
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "LocationProvider.h"
|
||||
#include <MicroNMEA.h>
|
||||
#include <RTClib.h>
|
||||
#include <helpers/RefCountedDigitalPin.h>
|
||||
|
||||
#ifndef GPS_EN
|
||||
#ifdef PIN_GPS_EN
|
||||
@@ -37,14 +38,15 @@ class MicroNMEALocationProvider : public LocationProvider {
|
||||
MicroNMEA nmea;
|
||||
mesh::RTCClock* _clock;
|
||||
Stream* _gps_serial;
|
||||
RefCountedDigitalPin* _peripher_power;
|
||||
int _pin_reset;
|
||||
int _pin_en;
|
||||
long next_check = 0;
|
||||
long time_valid = 0;
|
||||
|
||||
public :
|
||||
MicroNMEALocationProvider(Stream& ser, mesh::RTCClock* clock = NULL, int pin_reset = GPS_RESET, int pin_en = GPS_EN) :
|
||||
_gps_serial(&ser), nmea(_nmeaBuffer, sizeof(_nmeaBuffer)), _pin_reset(pin_reset), _pin_en(pin_en), _clock(clock) {
|
||||
MicroNMEALocationProvider(Stream& ser, mesh::RTCClock* clock = NULL, int pin_reset = GPS_RESET, int pin_en = GPS_EN,RefCountedDigitalPin* peripher_power=NULL) :
|
||||
_gps_serial(&ser), nmea(_nmeaBuffer, sizeof(_nmeaBuffer)), _pin_reset(pin_reset), _pin_en(pin_en), _clock(clock), _peripher_power(peripher_power) {
|
||||
if (_pin_reset != -1) {
|
||||
pinMode(_pin_reset, OUTPUT);
|
||||
digitalWrite(_pin_reset, GPS_RESET_FORCE);
|
||||
@@ -56,6 +58,7 @@ public :
|
||||
}
|
||||
|
||||
void begin() override {
|
||||
if (_peripher_power) _peripher_power->claim();
|
||||
if (_pin_en != -1) {
|
||||
digitalWrite(_pin_en, PIN_GPS_EN_ACTIVE);
|
||||
}
|
||||
@@ -75,7 +78,18 @@ public :
|
||||
void stop() override {
|
||||
if (_pin_en != -1) {
|
||||
digitalWrite(_pin_en, !PIN_GPS_EN_ACTIVE);
|
||||
}
|
||||
}
|
||||
if (_peripher_power) _peripher_power->release();
|
||||
}
|
||||
|
||||
bool isEnabled() override {
|
||||
// directly read the enable pin if present as gps can be
|
||||
// activated/deactivated outside of here ...
|
||||
if (_pin_en != -1) {
|
||||
return digitalRead(_pin_en) == PIN_GPS_EN_ACTIVE;
|
||||
} else {
|
||||
return true; // no enable so must be active
|
||||
}
|
||||
}
|
||||
|
||||
void syncTime() override { nmea.clear(); LocationProvider::syncTime(); }
|
||||
|
||||
@@ -32,6 +32,15 @@ public:
|
||||
setCursor(mid_x - w/2, y);
|
||||
print(str);
|
||||
}
|
||||
virtual void drawTextRightAlign(int x_anch, int y, const char* str) {
|
||||
int w = getTextWidth(str);
|
||||
setCursor(x_anch - w, y);
|
||||
print(str);
|
||||
}
|
||||
virtual void drawTextLeftAlign(int x_anch, int y, const char* str) {
|
||||
setCursor(x_anch, y);
|
||||
print(str);
|
||||
}
|
||||
|
||||
// convert UTF-8 characters to displayable block characters for compatibility
|
||||
virtual void translateUTF8ToBlocks(char* dest, const char* src, size_t dest_size) {
|
||||
|
||||
125
src/helpers/ui/LGFXDisplay.cpp
Normal file
125
src/helpers/ui/LGFXDisplay.cpp
Normal file
@@ -0,0 +1,125 @@
|
||||
#include "LGFXDisplay.h"
|
||||
|
||||
bool LGFXDisplay::begin() {
|
||||
turnOn();
|
||||
display->init();
|
||||
display->setRotation(1);
|
||||
display->setBrightness(64);
|
||||
display->setColorDepth(8);
|
||||
display->setTextColor(TFT_WHITE);
|
||||
|
||||
buffer.setColorDepth(8);
|
||||
buffer.setPsram(true);
|
||||
buffer.createSprite(width(), height());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void LGFXDisplay::turnOn() {
|
||||
// display->wakeup();
|
||||
if (!_isOn) {
|
||||
display->wakeup();
|
||||
}
|
||||
_isOn = true;
|
||||
}
|
||||
|
||||
void LGFXDisplay::turnOff() {
|
||||
if (_isOn) {
|
||||
display->sleep();
|
||||
}
|
||||
_isOn = false;
|
||||
}
|
||||
|
||||
void LGFXDisplay::clear() {
|
||||
// display->clearDisplay();
|
||||
buffer.clearDisplay();
|
||||
}
|
||||
|
||||
void LGFXDisplay::startFrame(Color bkg) {
|
||||
// display->startWrite();
|
||||
// display->getScanLine();
|
||||
buffer.clearDisplay();
|
||||
buffer.setTextColor(TFT_WHITE);
|
||||
}
|
||||
|
||||
void LGFXDisplay::setTextSize(int sz) {
|
||||
buffer.setTextSize(sz);
|
||||
}
|
||||
|
||||
void LGFXDisplay::setColor(Color c) {
|
||||
// _color = (c != 0) ? ILI9342_WHITE : ILI9342_BLACK;
|
||||
switch (c) {
|
||||
case DARK:
|
||||
_color = TFT_BLACK;
|
||||
break;
|
||||
case LIGHT:
|
||||
_color = TFT_WHITE;
|
||||
break;
|
||||
case RED:
|
||||
_color = TFT_RED;
|
||||
break;
|
||||
case GREEN:
|
||||
_color = TFT_GREEN;
|
||||
break;
|
||||
case BLUE:
|
||||
_color = TFT_BLUE;
|
||||
break;
|
||||
case YELLOW:
|
||||
_color = TFT_YELLOW;
|
||||
break;
|
||||
case ORANGE:
|
||||
_color = TFT_ORANGE;
|
||||
break;
|
||||
default:
|
||||
_color = TFT_WHITE;
|
||||
}
|
||||
buffer.setTextColor(_color);
|
||||
}
|
||||
|
||||
void LGFXDisplay::setCursor(int x, int y) {
|
||||
buffer.setCursor(x, y);
|
||||
}
|
||||
|
||||
void LGFXDisplay::print(const char* str) {
|
||||
buffer.println(str);
|
||||
// Serial.println(str);
|
||||
}
|
||||
|
||||
void LGFXDisplay::fillRect(int x, int y, int w, int h) {
|
||||
buffer.fillRect(x, y, w, h, _color);
|
||||
}
|
||||
|
||||
void LGFXDisplay::drawRect(int x, int y, int w, int h) {
|
||||
buffer.drawRect(x, y, w, h, _color);
|
||||
}
|
||||
|
||||
void LGFXDisplay::drawXbm(int x, int y, const uint8_t* bits, int w, int h) {
|
||||
buffer.drawBitmap(x, y, bits, w, h, _color);
|
||||
}
|
||||
|
||||
uint16_t LGFXDisplay::getTextWidth(const char* str) {
|
||||
return buffer.textWidth(str);
|
||||
}
|
||||
|
||||
void LGFXDisplay::endFrame() {
|
||||
display->startWrite();
|
||||
if (UI_ZOOM != 1) {
|
||||
buffer.pushRotateZoom(display, display->width()/2, display->height()/2 , 0, UI_ZOOM, UI_ZOOM);
|
||||
} else {
|
||||
buffer.pushSprite(display, 0, 0);
|
||||
}
|
||||
display->endWrite();
|
||||
}
|
||||
|
||||
bool LGFXDisplay::getTouch(int *x, int *y) {
|
||||
lgfx::v1::touch_point_t point;
|
||||
display->getTouch(&point);
|
||||
if (UI_ZOOM != 1) {
|
||||
*x = point.x / UI_ZOOM;
|
||||
*y = point.y / UI_ZOOM;
|
||||
} else {
|
||||
*x = point.x;
|
||||
*y = point.y;
|
||||
}
|
||||
return (*x >= 0) && (*y >= 0);
|
||||
}
|
||||
39
src/helpers/ui/LGFXDisplay.h
Normal file
39
src/helpers/ui/LGFXDisplay.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <helpers/ui/DisplayDriver.h>
|
||||
|
||||
#define LGFX_USE_V1
|
||||
#include <LovyanGFX.hpp>
|
||||
|
||||
#ifndef UI_ZOOM
|
||||
#define UI_ZOOM 1
|
||||
#endif
|
||||
|
||||
class LGFXDisplay : public DisplayDriver {
|
||||
protected:
|
||||
LGFX_Device* display;
|
||||
LGFX_Sprite buffer;
|
||||
|
||||
bool _isOn = false;
|
||||
int _color = TFT_WHITE;
|
||||
|
||||
public:
|
||||
LGFXDisplay(int w, int h, LGFX_Device &disp)
|
||||
: DisplayDriver(w/UI_ZOOM, h/UI_ZOOM), display(&disp) {}
|
||||
bool begin();
|
||||
bool isOn() override { return _isOn; }
|
||||
void turnOn() override;
|
||||
void turnOff() override;
|
||||
void clear() override;
|
||||
void startFrame(Color bkg = DARK) override;
|
||||
void setTextSize(int sz) override;
|
||||
void setColor(Color c) override;
|
||||
void setCursor(int x, int y) override;
|
||||
void print(const char* str) override;
|
||||
void fillRect(int x, int y, int w, int h) override;
|
||||
void drawRect(int x, int y, int w, int h) override;
|
||||
void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override;
|
||||
uint16_t getTextWidth(const char* str) override;
|
||||
void endFrame() override;
|
||||
virtual bool getTouch(int *x, int *y);
|
||||
};
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#define MULTI_CLICK_WINDOW_MS 280
|
||||
|
||||
MomentaryButton::MomentaryButton(int8_t pin, int long_press_millis, bool reverse, bool pulldownup) {
|
||||
MomentaryButton::MomentaryButton(int8_t pin, int long_press_millis, bool reverse, bool pulldownup, bool multiclick) {
|
||||
_pin = pin;
|
||||
_reverse = reverse;
|
||||
_pull = pulldownup;
|
||||
@@ -13,7 +13,7 @@ MomentaryButton::MomentaryButton(int8_t pin, int long_press_millis, bool reverse
|
||||
_threshold = 0;
|
||||
_click_count = 0;
|
||||
_last_click_time = 0;
|
||||
_multi_click_window = MULTI_CLICK_WINDOW_MS;
|
||||
_multi_click_window = multiclick ? MULTI_CLICK_WINDOW_MS : 0;
|
||||
_pending_click = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ class MomentaryButton {
|
||||
bool isPressed(int level) const;
|
||||
|
||||
public:
|
||||
MomentaryButton(int8_t pin, int long_press_mills=0, bool reverse=false, bool pulldownup=false);
|
||||
MomentaryButton(int8_t pin, int long_press_mills=0, bool reverse=false, bool pulldownup=false, bool multiclick=true);
|
||||
MomentaryButton(int8_t pin, int long_press_mills, int analog_threshold);
|
||||
void begin();
|
||||
int check(bool repeat_click=false); // returns one of BUTTON_EVENT_*
|
||||
|
||||
@@ -24,14 +24,21 @@ bool ST7735Display::begin() {
|
||||
digitalWrite(PIN_TFT_LEDA_CTL, HIGH);
|
||||
digitalWrite(PIN_TFT_RST, HIGH);
|
||||
|
||||
#if defined(HELTEC_TRACKER_V2)
|
||||
display.initR(INITR_MINI160x80);
|
||||
display.setRotation(DISPLAY_ROTATION);
|
||||
uint8_t madctl = ST77XX_MADCTL_MY | ST77XX_MADCTL_MV |ST7735_MADCTL_BGR;//Adjust color to BGR
|
||||
display.sendCommand(ST77XX_MADCTL, &madctl, 1);
|
||||
#else
|
||||
display.initR(INITR_MINI160x80_PLUGIN);
|
||||
display.setRotation(DISPLAY_ROTATION);
|
||||
#endif
|
||||
display.setSPISpeed(40000000);
|
||||
display.fillScreen(ST77XX_BLACK);
|
||||
display.setTextColor(ST77XX_WHITE);
|
||||
display.setTextSize(2);
|
||||
display.cp437(true); // Use full 256 char 'Code Page 437' font
|
||||
|
||||
|
||||
_isOn = true;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -39,7 +39,7 @@ bool ST7789LCDDisplay::begin() {
|
||||
|
||||
display.fillScreen(ST77XX_BLACK);
|
||||
display.setTextColor(ST77XX_WHITE);
|
||||
display.setTextSize(2);
|
||||
display.setTextSize(2 * DISPLAY_SCALE_X);
|
||||
display.cp437(true); // Use full 256 char 'Code Page 437' font
|
||||
|
||||
_isOn = true;
|
||||
@@ -70,12 +70,12 @@ void ST7789LCDDisplay::clear() {
|
||||
void ST7789LCDDisplay::startFrame(Color bkg) {
|
||||
display.fillScreen(ST77XX_BLACK);
|
||||
display.setTextColor(ST77XX_WHITE);
|
||||
display.setTextSize(1); // This one affects size of Please wait... message
|
||||
display.setTextSize(1 * DISPLAY_SCALE_X); // This one affects size of Please wait... message
|
||||
display.cp437(true); // Use full 256 char 'Code Page 437' font
|
||||
}
|
||||
|
||||
void ST7789LCDDisplay::setTextSize(int sz) {
|
||||
display.setTextSize(sz);
|
||||
display.setTextSize(sz * DISPLAY_SCALE_X);
|
||||
}
|
||||
|
||||
void ST7789LCDDisplay::setColor(Color c) {
|
||||
@@ -125,7 +125,22 @@ void ST7789LCDDisplay::drawRect(int x, int y, int w, int h) {
|
||||
}
|
||||
|
||||
void ST7789LCDDisplay::drawXbm(int x, int y, const uint8_t* bits, int w, int h) {
|
||||
display.drawBitmap(x * DISPLAY_SCALE_X, y * DISPLAY_SCALE_Y, bits, w, h, _color);
|
||||
uint8_t byteWidth = (w + 7) / 8;
|
||||
|
||||
for (int j = 0; j < h; j++) {
|
||||
for (int i = 0; i < w; i++) {
|
||||
uint8_t byte = bits[j * byteWidth + i / 8];
|
||||
bool pixelOn = byte & (0x80 >> (i & 7));
|
||||
|
||||
if (pixelOn) {
|
||||
for (int dy = 0; dy < DISPLAY_SCALE_X; dy++) {
|
||||
for (int dx = 0; dx < DISPLAY_SCALE_X; dx++) {
|
||||
display.drawPixel(x * DISPLAY_SCALE_X + i * DISPLAY_SCALE_X + dx, y * DISPLAY_SCALE_Y + j * DISPLAY_SCALE_X + dy, _color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t ST7789LCDDisplay::getTextWidth(const char* str) {
|
||||
@@ -138,4 +153,4 @@ uint16_t ST7789LCDDisplay::getTextWidth(const char* str) {
|
||||
|
||||
void ST7789LCDDisplay::endFrame() {
|
||||
// display.display();
|
||||
}
|
||||
}
|
||||
@@ -64,7 +64,7 @@ lib_deps =
|
||||
extends = Ebyte_EoRa-S3
|
||||
build_flags =
|
||||
${Ebyte_EoRa-S3.build_flags}
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
@@ -99,8 +99,8 @@ build_flags =
|
||||
${Ebyte_EoRa-S3.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1
|
||||
; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1
|
||||
build_src_filter = ${Ebyte_EoRa-S3.build_src_filter}
|
||||
@@ -118,8 +118,8 @@ build_flags =
|
||||
${Ebyte_EoRa-S3.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D BLE_PIN_CODE=123456
|
||||
-D BLE_DEBUG_LOGGING=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
|
||||
@@ -65,6 +65,7 @@ lib_deps =
|
||||
; -D WITH_RS232_BRIDGE=Serial2
|
||||
; -D WITH_RS232_BRIDGE_RX=5
|
||||
; -D WITH_RS232_BRIDGE_TX=6
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; ; -D MESH_PACKET_LOGGING=1
|
||||
; ; -D MESH_DEBUG=1
|
||||
; lib_deps =
|
||||
@@ -87,7 +88,7 @@ build_flags =
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
-D WITH_ESPNOW_BRIDGE=1
|
||||
-D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"'
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
lib_deps =
|
||||
@@ -132,6 +133,7 @@ lib_deps =
|
||||
; -D WITH_RS232_BRIDGE=Serial2
|
||||
; -D WITH_RS232_BRIDGE_RX=5
|
||||
; -D WITH_RS232_BRIDGE_TX=6
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; ; -D MESH_PACKET_LOGGING=1
|
||||
; ; -D MESH_DEBUG=1
|
||||
; lib_deps =
|
||||
@@ -154,7 +156,7 @@ build_flags =
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
-D WITH_ESPNOW_BRIDGE=1
|
||||
-D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"'
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
lib_deps =
|
||||
|
||||
@@ -26,7 +26,7 @@ build_src_filter = ${esp32_base.build_src_filter}
|
||||
extends = Generic_ESPNOW
|
||||
build_flags =
|
||||
${Generic_ESPNOW.build_flags}
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=1
|
||||
build_src_filter = ${Generic_ESPNOW.build_src_filter}
|
||||
+<../examples/simple_secure_chat/main.cpp>
|
||||
@@ -54,7 +54,7 @@ lib_deps =
|
||||
extends = Generic_ESPNOW
|
||||
build_flags =
|
||||
${Generic_ESPNOW.build_flags}
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1
|
||||
; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1
|
||||
|
||||
@@ -61,6 +61,7 @@ lib_deps =
|
||||
; -D WITH_RS232_BRIDGE=Serial2
|
||||
; -D WITH_RS232_BRIDGE_RX=5
|
||||
; -D WITH_RS232_BRIDGE_TX=6
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; ; -D MESH_PACKET_LOGGING=1
|
||||
; ; -D MESH_DEBUG=1
|
||||
; build_src_filter = ${Heltec_ct62.build_src_filter}
|
||||
@@ -80,7 +81,7 @@ build_flags =
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
-D WITH_ESPNOW_BRIDGE=1
|
||||
-D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"'
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_ct62.build_src_filter}
|
||||
@@ -96,8 +97,8 @@ build_flags =
|
||||
${Heltec_ct62.build_flags}
|
||||
; -D ARDUINO_USB_MODE=1
|
||||
; -D ARDUINO_USB_CDC_ON_BOOT=1
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
@@ -114,8 +115,8 @@ build_flags =
|
||||
${Heltec_ct62.build_flags}
|
||||
; -D ARDUINO_USB_MODE=1
|
||||
; -D ARDUINO_USB_CDC_ON_BOOT=1
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
-D BLE_PIN_CODE=123456
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
|
||||
@@ -40,13 +40,13 @@ lib_deps =
|
||||
${esp32_base.lib_deps}
|
||||
https://github.com/Quency-D/heltec-eink-modules/archive/563dd41fd850a1bc3039b8723da4f3a20fe1c800.zip
|
||||
|
||||
[env:Heltec_E213_companion_radio_ble]
|
||||
[env:Heltec_E213_companion_radio_ble_]
|
||||
extends = Heltec_E213_base
|
||||
build_flags =
|
||||
${Heltec_E213_base.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=E213Display
|
||||
-D BLE_PIN_CODE=123456 ; dynamic, random PIN
|
||||
-D BLE_DEBUG_LOGGING=1
|
||||
@@ -60,13 +60,13 @@ lib_deps =
|
||||
${Heltec_E213_base.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Heltec_E213_companion_radio_usb]
|
||||
[env:Heltec_E213_companion_radio_usb_]
|
||||
extends = Heltec_E213_base
|
||||
build_flags =
|
||||
${Heltec_E213_base.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=E213Display
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
build_src_filter = ${Heltec_E213_base.build_src_filter}
|
||||
@@ -78,7 +78,7 @@ lib_deps =
|
||||
${Heltec_E213_base.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Heltec_E213_repeater]
|
||||
[env:Heltec_E213_repeater_]
|
||||
extends = Heltec_E213_base
|
||||
build_flags =
|
||||
${Heltec_E213_base.build_flags}
|
||||
@@ -95,7 +95,7 @@ lib_deps =
|
||||
${Heltec_E213_base.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
; [env:Heltec_E213_repeater_bridge_rs232]
|
||||
; [env:Heltec_E213_repeater_bridge_rs232_]
|
||||
; extends = Heltec_E213_base
|
||||
; build_flags =
|
||||
; ${Heltec_E213_base.build_flags}
|
||||
@@ -108,6 +108,7 @@ lib_deps =
|
||||
; -D WITH_RS232_BRIDGE=Serial2
|
||||
; -D WITH_RS232_BRIDGE_RX=5
|
||||
; -D WITH_RS232_BRIDGE_TX=6
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; ; -D MESH_PACKET_LOGGING=1
|
||||
; ; -D MESH_DEBUG=1
|
||||
; build_src_filter = ${Heltec_E213_base.build_src_filter}
|
||||
@@ -118,7 +119,7 @@ lib_deps =
|
||||
; ${Heltec_E213_base.lib_deps}
|
||||
; ${esp32_ota.lib_deps}
|
||||
|
||||
[env:Heltec_E213_repeater_bridge_espnow]
|
||||
[env:Heltec_E213_repeater_bridge_espnow_]
|
||||
extends = Heltec_E213_base
|
||||
build_flags =
|
||||
${Heltec_E213_base.build_flags}
|
||||
@@ -129,7 +130,7 @@ build_flags =
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
-D WITH_ESPNOW_BRIDGE=1
|
||||
-D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"'
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_E213_base.build_src_filter}
|
||||
@@ -140,7 +141,7 @@ lib_deps =
|
||||
${Heltec_E213_base.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
[env:Heltec_E213_room_server]
|
||||
[env:Heltec_E213_room_server_]
|
||||
extends = Heltec_E213_base
|
||||
build_flags =
|
||||
${Heltec_E213_base.build_flags}
|
||||
|
||||
@@ -34,13 +34,13 @@ lib_deps =
|
||||
${esp32_base.lib_deps}
|
||||
https://github.com/Quency-D/heltec-eink-modules/archive/563dd41fd850a1bc3039b8723da4f3a20fe1c800.zip
|
||||
|
||||
[env:Heltec_E290_companion_radio_ble]
|
||||
[env:Heltec_E290_companion_ble_]
|
||||
extends = Heltec_E290_base
|
||||
build_flags =
|
||||
${Heltec_E290_base.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=E290Display
|
||||
-D BLE_PIN_CODE=123456 ; dynamic, random PIN
|
||||
-D BLE_DEBUG_LOGGING=1
|
||||
@@ -54,13 +54,13 @@ lib_deps =
|
||||
${Heltec_E290_base.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Heltec_E290_companion_radio_usb]
|
||||
[env:Heltec_E290_companion_usb_]
|
||||
extends = Heltec_E290_base
|
||||
build_flags =
|
||||
${Heltec_E290_base.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=E290Display
|
||||
-D BLE_PIN_CODE=123456 ; dynamic, random PIN
|
||||
-D BLE_DEBUG_LOGGING=1
|
||||
@@ -74,7 +74,7 @@ lib_deps =
|
||||
${Heltec_E290_base.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Heltec_E290_repeater]
|
||||
[env:Heltec_E290_repeater_]
|
||||
extends = Heltec_E290_base
|
||||
build_flags =
|
||||
${Heltec_E290_base.build_flags}
|
||||
@@ -91,7 +91,7 @@ lib_deps =
|
||||
${Heltec_E290_base.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
; [env:Heltec_E290_repeater_bridge_rs232]
|
||||
; [env:Heltec_E290_repeater_bridge_rs232_]
|
||||
; extends = Heltec_E290_base
|
||||
; build_flags =
|
||||
; ${Heltec_E290_base.build_flags}
|
||||
@@ -104,6 +104,7 @@ lib_deps =
|
||||
; -D WITH_RS232_BRIDGE=Serial2
|
||||
; -D WITH_RS232_BRIDGE_RX=5
|
||||
; -D WITH_RS232_BRIDGE_TX=6
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; ; -D MESH_PACKET_LOGGING=1
|
||||
; ; -D MESH_DEBUG=1
|
||||
; build_src_filter = ${Heltec_E290_base.build_src_filter}
|
||||
@@ -114,7 +115,7 @@ lib_deps =
|
||||
; ${Heltec_E290_base.lib_deps}
|
||||
; ${esp32_ota.lib_deps}
|
||||
|
||||
[env:Heltec_E290_repeater_bridge_espnow]
|
||||
[env:Heltec_E290_repeater_bridge_espnow_]
|
||||
extends = Heltec_E290_base
|
||||
build_flags =
|
||||
${Heltec_E290_base.build_flags}
|
||||
@@ -125,7 +126,7 @@ build_flags =
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
-D WITH_ESPNOW_BRIDGE=1
|
||||
-D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"'
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_E290_base.build_src_filter}
|
||||
@@ -136,7 +137,7 @@ lib_deps =
|
||||
${Heltec_E290_base.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
[env:Heltec_E290_room_server]
|
||||
[env:Heltec_E290_room_server_]
|
||||
extends = Heltec_E290_base
|
||||
build_flags =
|
||||
${Heltec_E290_base.build_flags}
|
||||
|
||||
@@ -24,45 +24,6 @@ void T114Board::begin() {
|
||||
|
||||
pinMode(PIN_VBAT_READ, INPUT);
|
||||
|
||||
// Enable SoftDevice low-power mode
|
||||
sd_power_mode_set(NRF_POWER_MODE_LOWPWR);
|
||||
|
||||
// Enable DC/DC converter for better efficiency (REG1 stage)
|
||||
NRF_POWER->DCDCEN = 1;
|
||||
|
||||
// Power down unused communication peripherals
|
||||
// UART1 - Not used on T114
|
||||
NRF_UARTE1->ENABLE = 0;
|
||||
|
||||
// SPIM2/SPIS2 - Not used (SPI is on SPIM0)
|
||||
NRF_SPIM2->ENABLE = 0;
|
||||
NRF_SPIS2->ENABLE = 0;
|
||||
|
||||
// TWI1 (I2C1) - Not used (I2C is on TWI0)
|
||||
NRF_TWIM1->ENABLE = 0;
|
||||
NRF_TWIS1->ENABLE = 0;
|
||||
|
||||
// PWM modules - Not used for standard T114 functions
|
||||
NRF_PWM1->ENABLE = 0;
|
||||
NRF_PWM2->ENABLE = 0;
|
||||
NRF_PWM3->ENABLE = 0;
|
||||
|
||||
// PDM (Digital Microphone Interface) - Not used
|
||||
NRF_PDM->ENABLE = 0;
|
||||
|
||||
// I2S - Not used
|
||||
NRF_I2S->ENABLE = 0;
|
||||
|
||||
// QSPI - Not used (no external flash)
|
||||
NRF_QSPI->ENABLE = 0;
|
||||
|
||||
// Disable unused analog peripherals
|
||||
// SAADC channels - only keep what's needed for battery monitoring
|
||||
NRF_SAADC->ENABLE = 0; // Re-enable only when needed for measurements
|
||||
|
||||
// COMP - Comparator not used
|
||||
NRF_COMP->ENABLE = 0;
|
||||
|
||||
#if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL)
|
||||
Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL);
|
||||
#endif
|
||||
|
||||
@@ -27,9 +27,6 @@ public:
|
||||
|
||||
uint16_t getBattMilliVolts() override {
|
||||
int adcvalue = 0;
|
||||
|
||||
NRF_SAADC->ENABLE = 1;
|
||||
|
||||
analogReadResolution(12);
|
||||
analogReference(AR_INTERNAL_3_0);
|
||||
pinMode(PIN_BAT_CTL, OUTPUT); // battery adc can be read only ctrl pin 6 set to high
|
||||
@@ -39,8 +36,6 @@ public:
|
||||
adcvalue = analogRead(PIN_VBAT_READ);
|
||||
digitalWrite(6, 0);
|
||||
|
||||
NRF_SAADC->ENABLE = 0;
|
||||
|
||||
return (uint16_t)((float)adcvalue * MV_LSB * 4.9);
|
||||
}
|
||||
|
||||
|
||||
@@ -170,6 +170,7 @@ build_flags =
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D BLE_PIN_CODE=123456
|
||||
-D ENV_INCLUDE_GPS=1 ; enable the GPS page in UI
|
||||
; -D BLE_DEBUG_LOGGING=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
|
||||
@@ -74,11 +74,10 @@ bool T114SensorManager::begin() {
|
||||
|
||||
if (gps_detected) {
|
||||
MESH_DEBUG_PRINTLN("GPS detected");
|
||||
digitalWrite(GPS_EN, LOW); // Power off GPS until the setting is changed
|
||||
} else {
|
||||
MESH_DEBUG_PRINTLN("No GPS detected");
|
||||
digitalWrite(GPS_EN, LOW);
|
||||
}
|
||||
digitalWrite(GPS_EN, LOW); // Power off GPS until the setting is changed
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ public:
|
||||
bool begin() override;
|
||||
bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override;
|
||||
void loop() override;
|
||||
LocationProvider* getLocationProvider() override { return gps_detected ? _location : NULL; }
|
||||
int getNumSettings() const override;
|
||||
const char* getSettingName(int i) const override;
|
||||
const char* getSettingValue(int i) const override;
|
||||
|
||||
@@ -47,13 +47,13 @@ lib_deps =
|
||||
${esp32_base.lib_deps}
|
||||
adafruit/Adafruit GFX Library @ ^1.12.1
|
||||
|
||||
[env:Heltec_T190_companion_radio_ble]
|
||||
[env:Heltec_T190_companion_radio_ble_]
|
||||
extends = Heltec_T190_base
|
||||
build_flags =
|
||||
${Heltec_T190_base.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D BLE_PIN_CODE=123456 ; dynamic, random PIN
|
||||
-D BLE_DEBUG_LOGGING=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
@@ -65,13 +65,13 @@ lib_deps =
|
||||
${Heltec_T190_base.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Heltec_T190_companion_radio_usb]
|
||||
[env:Heltec_T190_companion_radio_usb_]
|
||||
extends = Heltec_T190_base
|
||||
build_flags =
|
||||
${Heltec_T190_base.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
build_src_filter = ${Heltec_T190_base.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
@@ -81,7 +81,7 @@ lib_deps =
|
||||
${Heltec_T190_base.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Heltec_T190_repeater]
|
||||
[env:Heltec_T190_repeater_]
|
||||
extends = Heltec_T190_base
|
||||
build_flags =
|
||||
${Heltec_T190_base.build_flags}
|
||||
@@ -96,7 +96,7 @@ lib_deps =
|
||||
${Heltec_T190_base.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
; [env:Heltec_T190_repeater_bridge_rs232]
|
||||
; [env:Heltec_T190_repeater_bridge_rs232_]
|
||||
; extends = Heltec_T190_base
|
||||
; build_flags =
|
||||
; ${Heltec_T190_base.build_flags}
|
||||
@@ -108,6 +108,7 @@ lib_deps =
|
||||
; -D WITH_RS232_BRIDGE=Serial2
|
||||
; -D WITH_RS232_BRIDGE_RX=5
|
||||
; -D WITH_RS232_BRIDGE_TX=6
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; ; -D MESH_PACKET_LOGGING=1
|
||||
; ; -D MESH_DEBUG=1
|
||||
; build_src_filter = ${Heltec_T190_base.build_src_filter}
|
||||
@@ -117,7 +118,7 @@ lib_deps =
|
||||
; ${Heltec_T190_base.lib_deps}
|
||||
; ${esp32_ota.lib_deps}
|
||||
|
||||
[env:Heltec_T190_repeater_bridge_espnow]
|
||||
[env:Heltec_T190_repeater_bridge_espnow_]
|
||||
extends = Heltec_T190_base
|
||||
build_flags =
|
||||
${Heltec_T190_base.build_flags}
|
||||
@@ -127,7 +128,7 @@ build_flags =
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
-D WITH_ESPNOW_BRIDGE=1
|
||||
-D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"'
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_T190_base.build_src_filter}
|
||||
@@ -137,7 +138,7 @@ lib_deps =
|
||||
${Heltec_T190_base.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
[env:Heltec_T190_room_server]
|
||||
[env:Heltec_T190_room_server_]
|
||||
extends = Heltec_T190_base
|
||||
build_flags =
|
||||
${Heltec_T190_base.build_flags}
|
||||
|
||||
@@ -5,6 +5,7 @@ build_flags =
|
||||
${esp32_base.build_flags}
|
||||
-I variants/heltec_tracker
|
||||
-D HELTEC_LORA_V3
|
||||
-D ARDUINO_USB_CDC_ON_BOOT=1 ; need for Serial
|
||||
-D RADIO_CLASS=CustomSX1262
|
||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||
-D LORA_TX_POWER=22
|
||||
@@ -40,11 +41,10 @@ build_flags =
|
||||
${Heltec_tracker_base.build_flags}
|
||||
-I src/helpers/ui
|
||||
-I examples/companion_radio/ui-new
|
||||
-D ARDUINO_USB_CDC_ON_BOOT=1 ; need for Serial
|
||||
-D DISPLAY_ROTATION=1
|
||||
-D DISPLAY_CLASS=ST7735Display
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D BLE_PIN_CODE=123456 ; HWT will use display for pin
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
; -D BLE_DEBUG_LOGGING=1
|
||||
@@ -94,6 +94,7 @@ lib_deps =
|
||||
; -D WITH_RS232_BRIDGE=Serial2
|
||||
; -D WITH_RS232_BRIDGE_RX=5
|
||||
; -D WITH_RS232_BRIDGE_TX=6
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; ; -D MESH_PACKET_LOGGING=1
|
||||
; ; -D MESH_DEBUG=1
|
||||
; build_src_filter = ${Heltec_tracker_base.build_src_filter}
|
||||
@@ -116,7 +117,7 @@ build_flags =
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
-D WITH_ESPNOW_BRIDGE=1
|
||||
-D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"'
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_tracker_base.build_src_filter}
|
||||
|
||||
88
variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp
Normal file
88
variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
#include "HeltecTrackerV2Board.h"
|
||||
|
||||
void HeltecTrackerV2Board::begin() {
|
||||
ESP32Board::begin();
|
||||
|
||||
pinMode(PIN_ADC_CTRL, OUTPUT);
|
||||
digitalWrite(PIN_ADC_CTRL, LOW); // Initially inactive
|
||||
|
||||
pinMode(P_LORA_PA_POWER, OUTPUT);
|
||||
digitalWrite(P_LORA_PA_POWER,HIGH);
|
||||
|
||||
rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_EN);
|
||||
pinMode(P_LORA_PA_EN, OUTPUT);
|
||||
digitalWrite(P_LORA_PA_EN,HIGH);
|
||||
pinMode(P_LORA_PA_TX_EN, OUTPUT);
|
||||
digitalWrite(P_LORA_PA_TX_EN,LOW);
|
||||
|
||||
periph_power.begin();
|
||||
|
||||
esp_reset_reason_t reason = esp_reset_reason();
|
||||
if (reason == ESP_RST_DEEPSLEEP) {
|
||||
long wakeup_source = esp_sleep_get_ext1_wakeup_status();
|
||||
if (wakeup_source & (1 << P_LORA_DIO_1)) { // received a LoRa packet (while in deep sleep)
|
||||
startup_reason = BD_STARTUP_RX_PACKET;
|
||||
}
|
||||
|
||||
rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS);
|
||||
rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1);
|
||||
}
|
||||
}
|
||||
|
||||
void HeltecTrackerV2Board::onBeforeTransmit(void) {
|
||||
digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on
|
||||
digitalWrite(P_LORA_PA_TX_EN,HIGH);
|
||||
}
|
||||
|
||||
void HeltecTrackerV2Board::onAfterTransmit(void) {
|
||||
digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off
|
||||
digitalWrite(P_LORA_PA_TX_EN,LOW);
|
||||
}
|
||||
|
||||
void HeltecTrackerV2Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) {
|
||||
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
|
||||
|
||||
// Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep
|
||||
rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY);
|
||||
rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1);
|
||||
|
||||
rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS);
|
||||
|
||||
rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_EN); //It also needs to be enabled in receive mode
|
||||
|
||||
if (pin_wake_btn < 0) {
|
||||
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet
|
||||
} else {
|
||||
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn
|
||||
}
|
||||
|
||||
if (secs > 0) {
|
||||
esp_sleep_enable_timer_wakeup(secs * 1000000);
|
||||
}
|
||||
|
||||
// Finally set ESP32 into sleep
|
||||
esp_deep_sleep_start(); // CPU halts here and never returns!
|
||||
}
|
||||
|
||||
void HeltecTrackerV2Board::powerOff() {
|
||||
enterDeepSleep(0);
|
||||
}
|
||||
|
||||
uint16_t HeltecTrackerV2Board::getBattMilliVolts() {
|
||||
analogReadResolution(10);
|
||||
digitalWrite(PIN_ADC_CTRL, HIGH);
|
||||
delay(10);
|
||||
uint32_t raw = 0;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
raw += analogRead(PIN_VBAT_READ);
|
||||
}
|
||||
raw = raw / 8;
|
||||
|
||||
digitalWrite(PIN_ADC_CTRL, LOW);
|
||||
|
||||
return (5.42 * (3.3 / 1024.0) * raw) * 1000;
|
||||
}
|
||||
|
||||
const char* HeltecTrackerV2Board::getManufacturerName() const {
|
||||
return "Heltec Tracker V2";
|
||||
}
|
||||
23
variants/heltec_tracker_v2/HeltecTrackerV2Board.h
Normal file
23
variants/heltec_tracker_v2/HeltecTrackerV2Board.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <helpers/RefCountedDigitalPin.h>
|
||||
#include <helpers/ESP32Board.h>
|
||||
#include <driver/rtc_io.h>
|
||||
|
||||
class HeltecTrackerV2Board : public ESP32Board {
|
||||
|
||||
public:
|
||||
RefCountedDigitalPin periph_power;
|
||||
|
||||
HeltecTrackerV2Board() : periph_power(PIN_VEXT_EN,PIN_VEXT_EN_ACTIVE) { }
|
||||
|
||||
void begin();
|
||||
void onBeforeTransmit(void) override;
|
||||
void onAfterTransmit(void) override;
|
||||
void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1);
|
||||
void powerOff() override;
|
||||
uint16_t getBattMilliVolts() override;
|
||||
const char* getManufacturerName() const override ;
|
||||
|
||||
};
|
||||
60
variants/heltec_tracker_v2/pins_arduino.h
Normal file
60
variants/heltec_tracker_v2/pins_arduino.h
Normal file
@@ -0,0 +1,60 @@
|
||||
#ifndef Pins_Arduino_h
|
||||
#define Pins_Arduino_h
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
static const uint8_t LED_BUILTIN = 18;
|
||||
#define BUILTIN_LED LED_BUILTIN // backward compatibility
|
||||
#define LED_BUILTIN LED_BUILTIN // allow testing #ifdef LED_BUILTIN
|
||||
|
||||
static const uint8_t TX = 43;
|
||||
static const uint8_t RX = 44;
|
||||
|
||||
static const uint8_t SDA = 5;
|
||||
static const uint8_t SCL = 6;
|
||||
|
||||
static const uint8_t SS = 8;
|
||||
static const uint8_t MOSI = 10;
|
||||
static const uint8_t MISO = 11;
|
||||
static const uint8_t SCK = 9;
|
||||
|
||||
static const uint8_t A0 = 1;
|
||||
static const uint8_t A1 = 2;
|
||||
static const uint8_t A2 = 3;
|
||||
static const uint8_t A3 = 4;
|
||||
static const uint8_t A4 = 5;
|
||||
static const uint8_t A5 = 6;
|
||||
static const uint8_t A6 = 7;
|
||||
static const uint8_t A7 = 8;
|
||||
static const uint8_t A8 = 9;
|
||||
static const uint8_t A9 = 10;
|
||||
static const uint8_t A10 = 11;
|
||||
static const uint8_t A11 = 12;
|
||||
static const uint8_t A12 = 13;
|
||||
static const uint8_t A13 = 14;
|
||||
static const uint8_t A14 = 15;
|
||||
static const uint8_t A15 = 16;
|
||||
static const uint8_t A16 = 17;
|
||||
static const uint8_t A17 = 18;
|
||||
static const uint8_t A18 = 19;
|
||||
static const uint8_t A19 = 20;
|
||||
|
||||
static const uint8_t T1 = 1;
|
||||
static const uint8_t T2 = 2;
|
||||
static const uint8_t T3 = 3;
|
||||
static const uint8_t T4 = 4;
|
||||
static const uint8_t T5 = 5;
|
||||
static const uint8_t T6 = 6;
|
||||
static const uint8_t T7 = 7;
|
||||
static const uint8_t T8 = 8;
|
||||
static const uint8_t T9 = 9;
|
||||
static const uint8_t T10 = 10;
|
||||
static const uint8_t T11 = 11;
|
||||
static const uint8_t T12 = 12;
|
||||
static const uint8_t T13 = 13;
|
||||
static const uint8_t T14 = 14;
|
||||
|
||||
static const uint8_t Vext = 3;
|
||||
static const uint8_t LED = 18;
|
||||
|
||||
#endif /* Pins_Arduino_h */
|
||||
218
variants/heltec_tracker_v2/platformio.ini
Normal file
218
variants/heltec_tracker_v2/platformio.ini
Normal file
@@ -0,0 +1,218 @@
|
||||
[Heltec_tracker_v2]
|
||||
extends = esp32_base
|
||||
board = heltec_tracker_v2
|
||||
build_flags =
|
||||
${esp32_base.build_flags}
|
||||
${sensor_base.build_flags}
|
||||
-I variants/heltec_tracker_v2
|
||||
-D HELTEC_TRACKER_V2
|
||||
-D ESP32_CPU_FREQ=160
|
||||
-D RADIO_CLASS=CustomSX1262
|
||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||
-D P_LORA_TX_LED=18
|
||||
-D P_LORA_DIO_1=14
|
||||
-D P_LORA_NSS=8
|
||||
-D P_LORA_RESET=12
|
||||
-D P_LORA_BUSY=13
|
||||
-D P_LORA_SCLK=9
|
||||
-D P_LORA_MISO=11
|
||||
-D P_LORA_MOSI=10
|
||||
-D P_LORA_PA_POWER=7 ;power en
|
||||
-D P_LORA_PA_EN=4
|
||||
-D P_LORA_PA_TX_EN=46 ;enable tx
|
||||
-D LORA_TX_POWER=10 ;If it is configured as 10 here, the final output will be 22 dbm.
|
||||
-D MAX_LORA_TX_POWER=22 ;Max SX1262 output
|
||||
-D SX126X_DIO2_AS_RF_SWITCH=true
|
||||
-D SX126X_DIO3_TCXO_VOLTAGE=1.8
|
||||
-D SX126X_CURRENT_LIMIT=140
|
||||
-D SX126X_RX_BOOSTED_GAIN=1
|
||||
-D PIN_BOARD_SDA=5
|
||||
-D PIN_BOARD_SCL=6
|
||||
-D PIN_USER_BTN=0
|
||||
-D PIN_TFT_SDA=42 ; SDIN
|
||||
-D PIN_TFT_SCL=41 ; SCLK
|
||||
-D PIN_TFT_DC=40 ; RS (register select)
|
||||
-D PIN_TFT_RST=39 ; RES
|
||||
-D PIN_TFT_CS=38
|
||||
-D USE_PIN_TFT=1
|
||||
-D PIN_VEXT_EN=3 ; Vext is connected to VDD which is also connected to OLED & GPS
|
||||
-D PIN_VEXT_EN_ACTIVE=HIGH
|
||||
-D PIN_TFT_LEDA_CTL=21 ; LEDK (switches on/off via mosfet to create the ground)
|
||||
-D DISPLAY_ROTATION=1
|
||||
-D PIN_GPS_RX=34
|
||||
-D PIN_GPS_TX=33
|
||||
-D PIN_GPS_RESET=35
|
||||
-D PIN_GPS_RESET_ACTIVE=LOW
|
||||
-D GPS_BAUD_RATE=115200
|
||||
-D ENV_INCLUDE_GPS=1
|
||||
-D PIN_ADC_CTRL=2
|
||||
-D PIN_VBAT_READ=1
|
||||
build_src_filter = ${esp32_base.build_src_filter}
|
||||
+<../variants/heltec_tracker_v2>
|
||||
+<helpers/sensors>
|
||||
lib_deps =
|
||||
${esp32_base.lib_deps}
|
||||
${sensor_base.lib_deps}
|
||||
adafruit/Adafruit ST7735 and ST7789 Library @ ^1.11.0
|
||||
|
||||
[env:heltec_tracker_v2_repeater]
|
||||
extends = Heltec_tracker_v2
|
||||
build_flags =
|
||||
${Heltec_tracker_v2.build_flags}
|
||||
-D DISPLAY_CLASS=ST7735Display
|
||||
-D ADVERT_NAME='"Heltec Repeater"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_tracker_v2.build_src_filter}
|
||||
+<helpers/ui/ST7735Display.cpp>
|
||||
+<../examples/simple_repeater>
|
||||
lib_deps =
|
||||
${Heltec_tracker_v2.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
bakercp/CRC32 @ ^2.0.0
|
||||
|
||||
[env:heltec_tracker_v2_repeater_bridge_espnow]
|
||||
extends = Heltec_tracker_v2
|
||||
build_flags =
|
||||
${Heltec_tracker_v2.build_flags}
|
||||
-D DISPLAY_CLASS=ST7735Display
|
||||
-D ADVERT_NAME='"ESPNow Bridge"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
-D WITH_ESPNOW_BRIDGE=1
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_tracker_v2.build_src_filter}
|
||||
+<helpers/bridges/ESPNowBridge.cpp>
|
||||
+<helpers/ui/ST7735Display.cpp>
|
||||
+<../examples/simple_repeater>
|
||||
lib_deps =
|
||||
${Heltec_tracker_v2.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
[env:heltec_tracker_v2_room_server]
|
||||
extends = Heltec_tracker_v2
|
||||
build_flags =
|
||||
${Heltec_tracker_v2.build_flags}
|
||||
-D DISPLAY_CLASS=ST7735Display
|
||||
-D ADVERT_NAME='"Heltec Room"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D ROOM_PASSWORD='"hello"'
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_tracker_v2.build_src_filter}
|
||||
+<helpers/ui/ST7735Display.cpp>
|
||||
+<../examples/simple_room_server>
|
||||
lib_deps =
|
||||
${Heltec_tracker_v2.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
[env:heltec_tracker_v2_terminal_chat]
|
||||
extends = Heltec_tracker_v2
|
||||
build_flags =
|
||||
${Heltec_tracker_v2.build_flags}
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_tracker_v2.build_src_filter}
|
||||
+<../examples/simple_secure_chat/main.cpp>
|
||||
lib_deps =
|
||||
${Heltec_tracker_v2.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:heltec_tracker_v2_companion_radio_usb]
|
||||
extends = Heltec_tracker_v2
|
||||
build_flags =
|
||||
${Heltec_tracker_v2.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=ST7735Display
|
||||
; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1
|
||||
; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_tracker_v2.build_src_filter}
|
||||
+<helpers/ui/ST7735Display.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${Heltec_tracker_v2.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:heltec_tracker_v2_companion_radio_ble]
|
||||
extends = Heltec_tracker_v2
|
||||
build_flags =
|
||||
${Heltec_tracker_v2.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=ST7735Display
|
||||
-D BLE_PIN_CODE=123456 ; dynamic, random PIN
|
||||
-D AUTO_SHUTDOWN_MILLIVOLTS=3400
|
||||
-D BLE_DEBUG_LOGGING=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
; -D BLE_DEBUG_LOGGING=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_tracker_v2.build_src_filter}
|
||||
+<helpers/ui/ST7735Display.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${Heltec_tracker_v2.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:heltec_tracker_v2_companion_radio_wifi]
|
||||
extends = Heltec_tracker_v2
|
||||
build_flags =
|
||||
${Heltec_tracker_v2.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=ST7735Display
|
||||
-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 = ${Heltec_tracker_v2.build_src_filter}
|
||||
+<helpers/ui/ST7735Display.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${Heltec_tracker_v2.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:heltec_tracker_v2_sensor]
|
||||
extends = Heltec_tracker_v2
|
||||
build_flags =
|
||||
${Heltec_tracker_v2.build_flags}
|
||||
-D ADVERT_NAME='"Heltec Tracker V2 Sensor"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D ENV_PIN_SDA=3
|
||||
-D ENV_PIN_SCL=4
|
||||
-D DISPLAY_CLASS=ST7735Display
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_tracker_v2.build_src_filter}
|
||||
+<helpers/ui/ST7735Display.cpp>
|
||||
+<../examples/simple_sensor>
|
||||
lib_deps =
|
||||
${Heltec_tracker_v2.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
60
variants/heltec_tracker_v2/target.cpp
Normal file
60
variants/heltec_tracker_v2/target.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
|
||||
HeltecTrackerV2Board board;
|
||||
|
||||
#if defined(P_LORA_SCLK)
|
||||
static SPIClass spi;
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi);
|
||||
#else
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
#endif
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
ESP32RTCClock fallback_clock;
|
||||
AutoDiscoverRTCClock rtc_clock(fallback_clock);
|
||||
|
||||
#if ENV_INCLUDE_GPS
|
||||
#include <helpers/sensors/MicroNMEALocationProvider.h>
|
||||
MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, NULL, GPS_RESET, GPS_EN, &board.periph_power);
|
||||
EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea);
|
||||
#else
|
||||
EnvironmentSensorManager sensors;
|
||||
#endif
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
DISPLAY_CLASS display(&board.periph_power); // peripheral power pin is shared
|
||||
MomentaryButton user_btn(PIN_USER_BTN, 1000, true);
|
||||
#endif
|
||||
|
||||
bool radio_init() {
|
||||
fallback_clock.begin();
|
||||
rtc_clock.begin(Wire);
|
||||
|
||||
#if defined(P_LORA_SCLK)
|
||||
return radio.std_init(&spi);
|
||||
#else
|
||||
return radio.std_init();
|
||||
#endif
|
||||
}
|
||||
|
||||
uint32_t radio_get_rng_seed() {
|
||||
return radio.random(0x7FFFFFFF);
|
||||
}
|
||||
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) {
|
||||
radio.setFrequency(freq);
|
||||
radio.setSpreadingFactor(sf);
|
||||
radio.setBandwidth(bw);
|
||||
radio.setCodingRate(cr);
|
||||
}
|
||||
|
||||
void radio_set_tx_power(uint8_t dbm) {
|
||||
radio.setOutputPower(dbm);
|
||||
}
|
||||
|
||||
mesh::LocalIdentity radio_new_identity() {
|
||||
RadioNoiseListener rng(radio);
|
||||
return mesh::LocalIdentity(&rng); // create new random identity
|
||||
}
|
||||
30
variants/heltec_tracker_v2/target.h
Normal file
30
variants/heltec_tracker_v2/target.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#define RADIOLIB_STATIC_ONLY 1
|
||||
#include <RadioLib.h>
|
||||
#include <helpers/radiolib/RadioLibWrappers.h>
|
||||
#include <HeltecTrackerV2Board.h>
|
||||
#include <helpers/radiolib/CustomSX1262Wrapper.h>
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
#include <helpers/SensorManager.h>
|
||||
#include <helpers/sensors/EnvironmentSensorManager.h>
|
||||
#ifdef DISPLAY_CLASS
|
||||
#include <helpers/ui/ST7735Display.h>
|
||||
#include <helpers/ui/MomentaryButton.h>
|
||||
#endif
|
||||
|
||||
extern HeltecTrackerV2Board board;
|
||||
extern WRAPPER_CLASS radio_driver;
|
||||
extern AutoDiscoverRTCClock rtc_clock;
|
||||
extern EnvironmentSensorManager sensors;
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
extern DISPLAY_CLASS display;
|
||||
extern MomentaryButton user_btn;
|
||||
#endif
|
||||
|
||||
bool radio_init();
|
||||
uint32_t radio_get_rng_seed();
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr);
|
||||
void radio_set_tx_power(uint8_t dbm);
|
||||
mesh::LocalIdentity radio_new_identity();
|
||||
@@ -1,22 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
// LoRa radio module pins for Heltec V2
|
||||
#define P_LORA_DIO_1 26 // DIO0
|
||||
#define P_LORA_NSS 18
|
||||
#define P_LORA_RESET RADIOLIB_NC // 14
|
||||
#define P_LORA_BUSY RADIOLIB_NC
|
||||
#define P_LORA_SCLK 5
|
||||
#define P_LORA_MISO 19
|
||||
#define P_LORA_MOSI 27
|
||||
#include <helpers/ESP32Board.h>
|
||||
|
||||
// built-ins
|
||||
#define PIN_VBAT_READ 37
|
||||
#define PIN_LED_BUILTIN 25
|
||||
|
||||
#include "ESP32Board.h"
|
||||
|
||||
#include <driver/rtc_io.h>
|
||||
|
||||
class HeltecV2Board : public ESP32Board {
|
||||
@@ -39,7 +29,7 @@ public:
|
||||
void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) {
|
||||
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
|
||||
|
||||
// Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep
|
||||
// Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep
|
||||
rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY);
|
||||
rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1);
|
||||
|
||||
@@ -7,13 +7,20 @@ build_flags =
|
||||
-D HELTEC_LORA_V2
|
||||
-D RADIO_CLASS=CustomSX1276
|
||||
-D WRAPPER_CLASS=CustomSX1276Wrapper
|
||||
-D P_LORA_DIO_1=26
|
||||
-D P_LORA_NSS=18
|
||||
-D P_LORA_RESET=RADIOLIB_NC
|
||||
-D P_LORA_BUSY=RADIOLIB_NC
|
||||
-D P_LORA_SCLK=5
|
||||
-D P_LORA_MISO=19
|
||||
-D P_LORA_MOSI=27
|
||||
-D P_LORA_TX_LED=25
|
||||
-D SX127X_CURRENT_LIMIT=120
|
||||
-D LORA_TX_POWER=20
|
||||
-D PIN_BOARD_SDA=4
|
||||
-D PIN_BOARD_SCL=15
|
||||
-D PIN_USER_BTN=0
|
||||
-D PIN_OLED_RESET=16
|
||||
-D P_LORA_TX_LED=25
|
||||
build_src_filter = ${esp32_base.build_src_filter}
|
||||
+<../variants/heltec_v2>
|
||||
lib_deps =
|
||||
@@ -53,6 +60,7 @@ lib_deps =
|
||||
; -D WITH_RS232_BRIDGE=Serial2
|
||||
; -D WITH_RS232_BRIDGE_RX=5
|
||||
; -D WITH_RS232_BRIDGE_TX=6
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; ; -D MESH_PACKET_LOGGING=1
|
||||
; ; -D MESH_DEBUG=1
|
||||
; build_src_filter = ${Heltec_lora32_v2.build_src_filter}
|
||||
@@ -75,7 +83,7 @@ build_flags =
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
-D WITH_ESPNOW_BRIDGE=1
|
||||
-D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"'
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_lora32_v2.build_src_filter}
|
||||
@@ -111,7 +119,7 @@ lib_deps =
|
||||
extends = Heltec_lora32_v2
|
||||
build_flags =
|
||||
${Heltec_lora32_v2.build_flags}
|
||||
-D MAX_CONTACTS=170
|
||||
-D MAX_CONTACTS=160
|
||||
-D MAX_GROUP_CHANNELS=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
@@ -127,7 +135,7 @@ build_flags =
|
||||
${Heltec_lora32_v2.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D MAX_CONTACTS=170
|
||||
-D MAX_CONTACTS=160
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1
|
||||
; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1
|
||||
@@ -147,7 +155,7 @@ build_flags =
|
||||
${Heltec_lora32_v2.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D MAX_CONTACTS=170
|
||||
-D MAX_CONTACTS=160
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D BLE_PIN_CODE=123456
|
||||
-D BLE_DEBUG_LOGGING=1
|
||||
@@ -163,3 +171,26 @@ build_src_filter = ${Heltec_lora32_v2.build_src_filter}
|
||||
lib_deps =
|
||||
${Heltec_lora32_v2.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Heltec_v2_companion_radio_wifi]
|
||||
extends = Heltec_lora32_v2
|
||||
build_flags =
|
||||
${Heltec_lora32_v2.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D MAX_CONTACTS=160
|
||||
-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 = ${Heltec_lora32_v2.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${Heltec_lora32_v2.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#define RADIOLIB_STATIC_ONLY 1
|
||||
#include <RadioLib.h>
|
||||
#include <helpers/radiolib/RadioLibWrappers.h>
|
||||
#include <helpers/HeltecV2Board.h>
|
||||
#include <HeltecV2Board.h>
|
||||
#include <helpers/radiolib/CustomSX1276Wrapper.h>
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
#include <helpers/SensorManager.h>
|
||||
|
||||
@@ -2,16 +2,7 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <helpers/RefCountedDigitalPin.h>
|
||||
|
||||
// LoRa radio module pins for Heltec V3
|
||||
// Also for Heltec Wireless Tracker/Paper
|
||||
#define P_LORA_DIO_1 14
|
||||
#define P_LORA_NSS 8
|
||||
#define P_LORA_RESET RADIOLIB_NC
|
||||
#define P_LORA_BUSY 13
|
||||
#define P_LORA_SCLK 9
|
||||
#define P_LORA_MISO 11
|
||||
#define P_LORA_MOSI 10
|
||||
#include <helpers/ESP32Board.h>
|
||||
|
||||
// built-ins
|
||||
#ifndef PIN_VBAT_READ // set in platformio.ini for boards like Heltec Wireless Paper (20)
|
||||
@@ -22,9 +13,6 @@
|
||||
#endif
|
||||
#define PIN_ADC_CTRL_ACTIVE LOW
|
||||
#define PIN_ADC_CTRL_INACTIVE HIGH
|
||||
//#define PIN_LED_BUILTIN 35
|
||||
|
||||
#include "ESP32Board.h"
|
||||
|
||||
#include <driver/rtc_io.h>
|
||||
|
||||
@@ -43,7 +31,7 @@ public:
|
||||
// Auto-detect correct ADC_CTRL pin polarity (different for boards >3.2)
|
||||
pinMode(PIN_ADC_CTRL, INPUT);
|
||||
adc_active_state = !digitalRead(PIN_ADC_CTRL);
|
||||
|
||||
|
||||
pinMode(PIN_ADC_CTRL, OUTPUT);
|
||||
digitalWrite(PIN_ADC_CTRL, !adc_active_state); // Initially inactive
|
||||
|
||||
@@ -64,7 +52,7 @@ public:
|
||||
void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) {
|
||||
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
|
||||
|
||||
// Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep
|
||||
// Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep
|
||||
rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY);
|
||||
rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1);
|
||||
|
||||
@@ -7,6 +7,13 @@ build_flags =
|
||||
-I variants/heltec_v3
|
||||
-D HELTEC_LORA_V3
|
||||
-D ESP32_CPU_FREQ=80
|
||||
-D P_LORA_DIO_1=14
|
||||
-D P_LORA_NSS=8
|
||||
-D P_LORA_RESET=RADIOLIB_NC
|
||||
-D P_LORA_BUSY=13
|
||||
-D P_LORA_SCLK=9
|
||||
-D P_LORA_MISO=11
|
||||
-D P_LORA_MOSI=10
|
||||
-D RADIO_CLASS=CustomSX1262
|
||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||
-D LORA_TX_POWER=22
|
||||
@@ -62,6 +69,7 @@ build_flags =
|
||||
-D WITH_RS232_BRIDGE=Serial2
|
||||
-D WITH_RS232_BRIDGE_RX=5
|
||||
-D WITH_RS232_BRIDGE_TX=6
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_lora32_v3.build_src_filter}
|
||||
@@ -83,7 +91,7 @@ build_flags =
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
-D WITH_ESPNOW_BRIDGE=1
|
||||
-D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"'
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_lora32_v3.build_src_filter}
|
||||
@@ -117,7 +125,7 @@ lib_deps =
|
||||
extends = Heltec_lora32_v3
|
||||
build_flags =
|
||||
${Heltec_lora32_v3.build_flags}
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
@@ -132,8 +140,8 @@ extends = Heltec_lora32_v3
|
||||
build_flags =
|
||||
${Heltec_lora32_v3.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1
|
||||
; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1
|
||||
@@ -151,8 +159,8 @@ extends = Heltec_lora32_v3
|
||||
build_flags =
|
||||
${Heltec_lora32_v3.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D BLE_PIN_CODE=123456 ; dynamic, random PIN
|
||||
-D AUTO_SHUTDOWN_MILLIVOLTS=3400
|
||||
@@ -175,8 +183,8 @@ extends = Heltec_lora32_v3
|
||||
build_flags =
|
||||
${Heltec_lora32_v3.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D WIFI_DEBUG_LOGGING=1
|
||||
-D WIFI_SSID='"myssid"'
|
||||
@@ -243,6 +251,7 @@ build_flags =
|
||||
-D WITH_RS232_BRIDGE=Serial2
|
||||
-D WITH_RS232_BRIDGE_RX=5
|
||||
-D WITH_RS232_BRIDGE_TX=6
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_lora32_v3.build_src_filter}
|
||||
@@ -263,7 +272,7 @@ build_flags =
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
-D WITH_ESPNOW_BRIDGE=1
|
||||
-D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"'
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_lora32_v3.build_src_filter}
|
||||
@@ -295,8 +304,8 @@ lib_deps =
|
||||
extends = Heltec_lora32_v3
|
||||
build_flags =
|
||||
${Heltec_lora32_v3.build_flags}
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D BLE_PIN_CODE=123456
|
||||
-D BLE_DEBUG_LOGGING=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
@@ -314,7 +323,7 @@ extends = Heltec_lora32_v3
|
||||
build_flags =
|
||||
${Heltec_lora32_v3.build_flags}
|
||||
-D MAX_CONTACTS=140
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1
|
||||
; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_lora32_v3.build_src_filter}
|
||||
@@ -323,6 +332,24 @@ lib_deps =
|
||||
${Heltec_lora32_v3.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Heltec_WSL3_companion_radio_wifi]
|
||||
extends = Heltec_lora32_v3
|
||||
build_flags =
|
||||
${Heltec_lora32_v3.build_flags}
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-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 = ${Heltec_lora32_v3.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
lib_deps =
|
||||
${Heltec_lora32_v3.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Heltec_WSL3_sensor]
|
||||
extends = Heltec_lora32_v3
|
||||
build_flags =
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#define RADIOLIB_STATIC_ONLY 1
|
||||
#include <RadioLib.h>
|
||||
#include <helpers/radiolib/RadioLibWrappers.h>
|
||||
#include <helpers/HeltecV3Board.h>
|
||||
#include <HeltecV3Board.h>
|
||||
#include <helpers/radiolib/CustomSX1262Wrapper.h>
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
#include <helpers/SensorManager.h>
|
||||
|
||||
@@ -26,6 +26,7 @@ build_flags =
|
||||
-D PIN_VEXT_EN=36
|
||||
-D PIN_VEXT_EN_ACTIVE=HIGH
|
||||
-D LORA_TX_POWER=10 ;If it is configured as 10 here, the final output will be 22 dbm.
|
||||
-D MAX_LORA_TX_POWER=22 ; Max SX1262 output
|
||||
-D SX126X_DIO2_AS_RF_SWITCH=true
|
||||
-D SX126X_DIO3_TCXO_VOLTAGE=1.8
|
||||
-D SX126X_CURRENT_LIMIT=140
|
||||
@@ -77,7 +78,7 @@ build_flags =
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
-D WITH_ESPNOW_BRIDGE=1
|
||||
-D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"'
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_lora32_v4.build_src_filter}
|
||||
@@ -111,7 +112,7 @@ lib_deps =
|
||||
extends = Heltec_lora32_v4
|
||||
build_flags =
|
||||
${Heltec_lora32_v4.build_flags}
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
@@ -126,8 +127,8 @@ extends = Heltec_lora32_v4
|
||||
build_flags =
|
||||
${Heltec_lora32_v4.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1
|
||||
; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1
|
||||
@@ -145,8 +146,8 @@ extends = Heltec_lora32_v4
|
||||
build_flags =
|
||||
${Heltec_lora32_v4.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D BLE_PIN_CODE=123456 ; dynamic, random PIN
|
||||
-D AUTO_SHUTDOWN_MILLIVOLTS=3400
|
||||
@@ -169,8 +170,8 @@ extends = Heltec_lora32_v4
|
||||
build_flags =
|
||||
${Heltec_lora32_v4.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D WIFI_DEBUG_LOGGING=1
|
||||
-D WIFI_SSID='"myssid"'
|
||||
|
||||
@@ -38,8 +38,8 @@ extends = Heltec_Wireless_Paper_base
|
||||
build_flags =
|
||||
${Heltec_Wireless_Paper_base.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=300
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=E213Display
|
||||
-D BLE_PIN_CODE=123456 ; dynamic, random PIN
|
||||
-D BLE_DEBUG_LOGGING=1
|
||||
@@ -83,6 +83,7 @@ lib_deps =
|
||||
; -D WITH_RS232_BRIDGE=Serial2
|
||||
; -D WITH_RS232_BRIDGE_RX=5
|
||||
; -D WITH_RS232_BRIDGE_TX=6
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; ; -D MESH_PACKET_LOGGING=1
|
||||
; ; -D MESH_DEBUG=1
|
||||
; build_src_filter = ${Heltec_Wireless_Paper_base.build_src_filter}
|
||||
@@ -104,7 +105,7 @@ build_flags =
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
-D WITH_ESPNOW_BRIDGE=1
|
||||
-D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"'
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_Wireless_Paper_base.build_src_filter}
|
||||
|
||||
@@ -1,28 +1,26 @@
|
||||
#ifdef XIAO_NRF52
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "ikoka_stick_nrf_board.h"
|
||||
#include "IkokaNanoNRFBoard.h"
|
||||
|
||||
#include <bluefruit.h>
|
||||
#include <Wire.h>
|
||||
|
||||
static BLEDfu bledfu;
|
||||
|
||||
static void connect_callback(uint16_t conn_handle)
|
||||
{
|
||||
static void connect_callback(uint16_t conn_handle) {
|
||||
(void)conn_handle;
|
||||
MESH_DEBUG_PRINTLN("BLE client connected");
|
||||
}
|
||||
|
||||
static void disconnect_callback(uint16_t conn_handle, uint8_t reason)
|
||||
{
|
||||
static void disconnect_callback(uint16_t conn_handle, uint8_t reason) {
|
||||
(void)conn_handle;
|
||||
(void)reason;
|
||||
|
||||
MESH_DEBUG_PRINTLN("BLE client disconnected");
|
||||
}
|
||||
|
||||
void ikoka_stick_nrf_board::begin() {
|
||||
void IkokaNanoNRFBoard::begin() {
|
||||
// for future use, sub-classes SHOULD call this from their begin()
|
||||
startup_reason = BD_STARTUP_NORMAL;
|
||||
|
||||
@@ -50,7 +48,7 @@ void ikoka_stick_nrf_board::begin() {
|
||||
delay(10); // give sx1262 some time to power up
|
||||
}
|
||||
|
||||
bool ikoka_stick_nrf_board::startOTAUpdate(const char* id, char reply[]) {
|
||||
bool IkokaNanoNRFBoard::startOTAUpdate(const char *id, char reply[]) {
|
||||
// Config the peripheral connection with maximum bandwidth
|
||||
// more SRAM required by SoftDevice
|
||||
// Note: All config***() function must be called before begin()
|
||||
@@ -91,9 +89,6 @@ bool ikoka_stick_nrf_board::startOTAUpdate(const char* id, char reply[]) {
|
||||
|
||||
strcpy(reply, "OK - started");
|
||||
return true;
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
#ifdef XIAO_NRF52
|
||||
|
||||
class ikoka_stick_nrf_board : public mesh::MainBoard {
|
||||
class IkokaNanoNRFBoard : public mesh::MainBoard {
|
||||
protected:
|
||||
uint8_t startup_reason;
|
||||
|
||||
@@ -16,9 +16,17 @@ public:
|
||||
#if defined(P_LORA_TX_LED)
|
||||
void onBeforeTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED on
|
||||
#if defined(LED_BLUE)
|
||||
// turn off that annoying blue LED before transmitting
|
||||
digitalWrite(LED_BLUE, HIGH);
|
||||
#endif
|
||||
}
|
||||
void onAfterTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED off
|
||||
#if defined(LED_BLUE)
|
||||
// do it after transmitting too, just in case
|
||||
digitalWrite(LED_BLUE, HIGH);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -38,15 +46,15 @@ public:
|
||||
return (adcvalue * ADC_MULTIPLIER * AREF_VOLTAGE) / 4.096;
|
||||
}
|
||||
|
||||
const char* getManufacturerName() const override {
|
||||
return "Ikoka Stick (Xiao-nrf52)";
|
||||
const char *getManufacturerName() const override {
|
||||
return MANUFACTURER_STRING;
|
||||
}
|
||||
|
||||
void reboot() override {
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
|
||||
bool startOTAUpdate(const char* id, char reply[]) override;
|
||||
bool startOTAUpdate(const char *id, char reply[]) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
312
variants/ikoka_nano_nrf/platformio.ini
Normal file
312
variants/ikoka_nano_nrf/platformio.ini
Normal file
@@ -0,0 +1,312 @@
|
||||
[nrf52840_xiao]
|
||||
extends = nrf52_base
|
||||
platform_packages =
|
||||
toolchain-gccarmnoneeabi@~1.100301.0
|
||||
framework-arduinoadafruitnrf52
|
||||
board = seeed-xiao-afruitnrf52-nrf52840
|
||||
board_build.ldscript = boards/nrf52840_s140_v7.ld
|
||||
build_flags = ${nrf52_base.build_flags}
|
||||
-D NRF52_PLATFORM -D XIAO_NRF52
|
||||
-I lib/nrf52/s140_nrf52_7.3.0_API/include
|
||||
-I lib/nrf52/s140_nrf52_7.3.0_API/include/nrf52
|
||||
lib_ignore =
|
||||
BluetoothOTA
|
||||
lvgl
|
||||
lib5b4
|
||||
lib_deps =
|
||||
${nrf52_base.lib_deps}
|
||||
rweather/Crypto @ ^0.4.0
|
||||
adafruit/Adafruit INA3221 Library @ ^1.0.1
|
||||
adafruit/Adafruit INA219 @ ^1.2.3
|
||||
adafruit/Adafruit AHTX0 @ ^2.0.5
|
||||
adafruit/Adafruit BME280 Library @ ^2.3.0
|
||||
adafruit/Adafruit SSD1306 @ ^2.5.13
|
||||
|
||||
[ikoka_nano_nrf_baseboard]
|
||||
extends = nrf52840_xiao
|
||||
;board_build.ldscript = boards/nrf52840_s140_v7.ld
|
||||
build_flags = ${nrf52840_xiao.build_flags}
|
||||
-D P_LORA_TX_LED=11
|
||||
-I variants/ikoka_nano_nrf
|
||||
-I src/helpers/nrf52
|
||||
-D DISPLAY_CLASS=NullDisplayDriver
|
||||
-D RADIO_CLASS=CustomSX1262
|
||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||
-D P_LORA_DIO_1=D1
|
||||
; -D P_LORA_BUSY=D3
|
||||
-D P_LORA_BUSY=D2 ; specific to ikoka nano variant.
|
||||
; -D P_LORA_RESET=D2
|
||||
-D P_LORA_RESET=D3 ; specific to ikoka nano variant.
|
||||
; -D P_LORA_NSS=D4
|
||||
-D P_LORA_NSS=D0 ; specific to ikoka nano variant.
|
||||
; -D SX126X_RXEN=D5
|
||||
-D SX126X_RXEN=D7
|
||||
-D SX126X_TXEN=RADIOLIB_NC
|
||||
-D SX126X_DIO2_AS_RF_SWITCH=1
|
||||
-D SX126X_DIO3_TCXO_VOLTAGE=1.8
|
||||
-D SX126X_CURRENT_LIMIT=140
|
||||
-D SX126X_RX_BOOSTED_GAIN=1
|
||||
-D PIN_WIRE_SCL=5 ; specific to ikoka nano variant.
|
||||
-D PIN_WIRE_SDA=4 ; specific to ikoka nano variant.
|
||||
-D ENV_INCLUDE_AHTX0=1
|
||||
-D ENV_INCLUDE_BME280=1
|
||||
-D ENV_INCLUDE_INA3221=1
|
||||
-D ENV_INCLUDE_INA219=1
|
||||
debug_tool = jlink
|
||||
upload_protocol = nrfutil
|
||||
|
||||
|
||||
;;; abstracted hardware variants
|
||||
|
||||
[ikoka_nano_nrf_e22_22dbm]
|
||||
extends = ikoka_nano_nrf_baseboard
|
||||
; No PA in this model, full 22dBm
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_baseboard.build_flags}
|
||||
-D MANUFACTURER_STRING='"Ikoka Nano-E22-22dBm (Xiao_nrf52)"'
|
||||
-D LORA_TX_POWER=22
|
||||
build_src_filter = ${nrf52840_xiao.build_src_filter}
|
||||
+<helpers/*.cpp>
|
||||
+<helpers/sensors>
|
||||
+<helpers/ui/NullDisplayDriver.cpp>
|
||||
+<../variants/ikoka_nano_nrf>
|
||||
|
||||
[ikoka_nano_nrf_e22_30dbm]
|
||||
extends = ikoka_nano_nrf_baseboard
|
||||
; limit txpower to 20dBm on E22-900M30S. Anything higher will
|
||||
; cause distortion in the PA output. 20dBm in -> 30dBm out
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_baseboard.build_flags}
|
||||
-D MANUFACTURER_STRING='"Ikoka Nano-E22-30dBm (Xiao_nrf52)"'
|
||||
-D LORA_TX_POWER=20
|
||||
build_src_filter = ${nrf52840_xiao.build_src_filter}
|
||||
+<helpers/*.cpp>
|
||||
+<helpers/sensors>
|
||||
+<helpers/ui/NullDisplayDriver.cpp>
|
||||
+<../variants/ikoka_nano_nrf>
|
||||
|
||||
[ikoka_nano_nrf_e22_33dbm]
|
||||
extends = ikoka_nano_nrf_baseboard
|
||||
; limit txpower to 9dBm on E22-900M33S to avoid hardware damage
|
||||
; to the rf amplifier frontend. 9dBm in -> 33dBm out
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_baseboard.build_flags}
|
||||
-D MANUFACTURER_STRING='"Ikoka Nano-E22-33dBm (Xiao_nrf52)"'
|
||||
-D LORA_TX_POWER=9
|
||||
build_src_filter = ${nrf52840_xiao.build_src_filter}
|
||||
+<helpers/*.cpp>
|
||||
+<helpers/sensors>
|
||||
+<helpers/ui/NullDisplayDriver.cpp>
|
||||
+<../variants/ikoka_nano_nrf>
|
||||
|
||||
;;; abstracted firmware roles
|
||||
|
||||
[ikoka_nano_nrf_companion_radio_ble]
|
||||
extends = ikoka_nano_nrf_baseboard
|
||||
board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld
|
||||
board_upload.maximum_size = 708608
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_baseboard.build_flags}
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D BLE_PIN_CODE=123456
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
-I examples/companion_radio/ui-new
|
||||
; -D BLE_DEBUG_LOGGING=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${ikoka_nano_nrf_baseboard.build_src_filter}
|
||||
+<helpers/nrf52/SerialBLEInterface.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${ikoka_nano_nrf_baseboard.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[ikoka_nano_nrf_companion_radio_usb]
|
||||
extends = ikoka_nano_nrf_baseboard
|
||||
board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld
|
||||
board_upload.maximum_size = 708608
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_baseboard.build_flags}
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-I examples/companion_radio/ui-new
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${ikoka_nano_nrf_baseboard.build_src_filter}
|
||||
+<helpers/nrf52/SerialBLEInterface.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${ikoka_nano_nrf_baseboard.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[ikoka_nano_nrf_repeater]
|
||||
extends = ikoka_nano_nrf_baseboard
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_baseboard.build_flags}
|
||||
-D ADVERT_NAME='"Ikoka Nano Repeater"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${ikoka_nano_nrf_baseboard.build_src_filter}
|
||||
+<../examples/simple_repeater/*.cpp>
|
||||
|
||||
[ikoka_nano_nrf_room_server]
|
||||
extends = ikoka_nano_nrf_baseboard
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_baseboard.build_flags}
|
||||
-D ADVERT_NAME='"Ikoka Nano Room"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${ikoka_nano_nrf_baseboard.build_src_filter}
|
||||
+<../examples/simple_room_server/*.cpp>
|
||||
|
||||
;;; hardware + firmware variants
|
||||
|
||||
;;; 22dBm EBYTE E22-900M22 variants
|
||||
|
||||
[env:ikoka_nano_nrf_22dbm_companion_radio_usb]
|
||||
extends =
|
||||
ikoka_nano_nrf_e22_22dbm
|
||||
ikoka_nano_nrf_companion_radio_usb
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_companion_radio_usb.build_flags}
|
||||
${ikoka_nano_nrf_e22_22dbm.build_flags}
|
||||
build_src_filter =
|
||||
${ikoka_nano_nrf_companion_radio_usb.build_src_filter}
|
||||
${ikoka_nano_nrf_e22_22dbm.build_src_filter}
|
||||
|
||||
[env:ikoka_nano_nrf_22dbm_companion_radio_ble]
|
||||
extends =
|
||||
ikoka_nano_nrf_e22_22dbm
|
||||
ikoka_nano_nrf_companion_radio_ble
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_companion_radio_ble.build_flags}
|
||||
${ikoka_nano_nrf_e22_22dbm.build_flags}
|
||||
build_src_filter =
|
||||
${ikoka_nano_nrf_companion_radio_ble.build_src_filter}
|
||||
${ikoka_nano_nrf_e22_22dbm.build_src_filter}
|
||||
|
||||
[env:ikoka_nano_nrf_22dbm_repeater]
|
||||
extends =
|
||||
ikoka_nano_nrf_e22_22dbm
|
||||
ikoka_nano_nrf_repeater
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_repeater.build_flags}
|
||||
${ikoka_nano_nrf_e22_22dbm.build_flags}
|
||||
build_src_filter =
|
||||
${ikoka_nano_nrf_repeater.build_src_filter}
|
||||
${ikoka_nano_nrf_e22_22dbm.build_src_filter}
|
||||
|
||||
[env:ikoka_nano_nrf_22dbm_room_server]
|
||||
extends =
|
||||
ikoka_nano_nrf_e22_22dbm
|
||||
ikoka_nano_nrf_room_server
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_room_server.build_flags}
|
||||
${ikoka_nano_nrf_e22_22dbm.build_flags}
|
||||
build_src_filter =
|
||||
${ikoka_nano_nrf_room_server.build_src_filter}
|
||||
${ikoka_nano_nrf_e22_22dbm.build_src_filter}
|
||||
|
||||
|
||||
;;; 30dBm EBYTE E22-900M30 variants
|
||||
|
||||
[env:ikoka_nano_nrf_30dbm_companion_radio_usb]
|
||||
extends =
|
||||
ikoka_nano_nrf_e22_30dbm
|
||||
ikoka_nano_nrf_companion_radio_usb
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_companion_radio_usb.build_flags}
|
||||
${ikoka_nano_nrf_e22_30dbm.build_flags}
|
||||
build_src_filter =
|
||||
${ikoka_nano_nrf_companion_radio_usb.build_src_filter}
|
||||
${ikoka_nano_nrf_e22_30dbm.build_src_filter}
|
||||
|
||||
[env:ikoka_nano_nrf_30dbm_companion_radio_ble]
|
||||
extends =
|
||||
ikoka_nano_nrf_e22_30dbm
|
||||
ikoka_nano_nrf_companion_radio_ble
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_companion_radio_ble.build_flags}
|
||||
${ikoka_nano_nrf_e22_30dbm.build_flags}
|
||||
build_src_filter =
|
||||
${ikoka_nano_nrf_companion_radio_ble.build_src_filter}
|
||||
${ikoka_nano_nrf_e22_30dbm.build_src_filter}
|
||||
|
||||
[env:ikoka_nano_nrf_30dbm_repeater]
|
||||
extends =
|
||||
ikoka_nano_nrf_e22_30dbm
|
||||
ikoka_nano_nrf_repeater
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_repeater.build_flags}
|
||||
${ikoka_nano_nrf_e22_30dbm.build_flags}
|
||||
build_src_filter =
|
||||
${ikoka_nano_nrf_repeater.build_src_filter}
|
||||
${ikoka_nano_nrf_e22_30dbm.build_src_filter}
|
||||
|
||||
[env:ikoka_nano_nrf_30dbm_room_server]
|
||||
extends =
|
||||
ikoka_nano_nrf_e22_30dbm
|
||||
ikoka_nano_nrf_room_server
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_room_server.build_flags}
|
||||
${ikoka_nano_nrf_e22_30dbm.build_flags}
|
||||
build_src_filter =
|
||||
${ikoka_nano_nrf_room_server.build_src_filter}
|
||||
${ikoka_nano_nrf_e22_30dbm.build_src_filter}
|
||||
|
||||
|
||||
;;; 33dBm EBYTE E22-900M33 variants
|
||||
|
||||
[env:ikoka_nano_nrf_33dbm_companion_radio_usb]
|
||||
extends =
|
||||
ikoka_nano_nrf_e22_33dbm
|
||||
ikoka_nano_nrf_companion_radio_usb
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_companion_radio_usb.build_flags}
|
||||
${ikoka_nano_nrf_e22_33dbm.build_flags}
|
||||
build_src_filter =
|
||||
${ikoka_nano_nrf_companion_radio_usb.build_src_filter}
|
||||
${ikoka_nano_nrf_e22_33dbm.build_src_filter}
|
||||
|
||||
[env:ikoka_nano_nrf_33dbm_companion_radio_ble]
|
||||
extends =
|
||||
ikoka_nano_nrf_e22_33dbm
|
||||
ikoka_nano_nrf_companion_radio_ble
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_companion_radio_ble.build_flags}
|
||||
${ikoka_nano_nrf_e22_33dbm.build_flags}
|
||||
build_src_filter =
|
||||
${ikoka_nano_nrf_companion_radio_ble.build_src_filter}
|
||||
${ikoka_nano_nrf_e22_33dbm.build_src_filter}
|
||||
|
||||
[env:ikoka_nano_nrf_33dbm_repeater]
|
||||
extends =
|
||||
ikoka_nano_nrf_e22_33dbm
|
||||
ikoka_nano_nrf_repeater
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_repeater.build_flags}
|
||||
${ikoka_nano_nrf_e22_33dbm.build_flags}
|
||||
build_src_filter =
|
||||
${ikoka_nano_nrf_repeater.build_src_filter}
|
||||
${ikoka_nano_nrf_e22_33dbm.build_src_filter}
|
||||
|
||||
[env:ikoka_nano_nrf_33dbm_room_server]
|
||||
extends =
|
||||
ikoka_nano_nrf_e22_33dbm
|
||||
ikoka_nano_nrf_room_server
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_room_server.build_flags}
|
||||
${ikoka_nano_nrf_e22_33dbm.build_flags}
|
||||
build_src_filter =
|
||||
${ikoka_nano_nrf_room_server.build_src_filter}
|
||||
${ikoka_nano_nrf_e22_33dbm.build_src_filter}
|
||||
44
variants/ikoka_nano_nrf/target.cpp
Normal file
44
variants/ikoka_nano_nrf/target.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
|
||||
IkokaNanoNRFBoard board;
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
DISPLAY_CLASS display;
|
||||
// MomentaryButton user_btn(PIN_USER_BTN, 1000, true);
|
||||
#endif
|
||||
|
||||
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);
|
||||
EnvironmentSensorManager sensors;
|
||||
|
||||
bool radio_init() {
|
||||
rtc_clock.begin(Wire);
|
||||
|
||||
return radio.std_init(&SPI);
|
||||
}
|
||||
|
||||
uint32_t radio_get_rng_seed() {
|
||||
return radio.random(0x7FFFFFFF);
|
||||
}
|
||||
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) {
|
||||
radio.setFrequency(freq);
|
||||
radio.setSpreadingFactor(sf);
|
||||
radio.setBandwidth(bw);
|
||||
radio.setCodingRate(cr);
|
||||
}
|
||||
|
||||
void radio_set_tx_power(uint8_t dbm) {
|
||||
radio.setOutputPower(dbm);
|
||||
}
|
||||
|
||||
mesh::LocalIdentity radio_new_identity() {
|
||||
RadioNoiseListener rng(radio);
|
||||
return mesh::LocalIdentity(&rng); // create new random identity
|
||||
}
|
||||
28
variants/ikoka_nano_nrf/target.h
Normal file
28
variants/ikoka_nano_nrf/target.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#define RADIOLIB_STATIC_ONLY 1
|
||||
#include <RadioLib.h>
|
||||
#include <helpers/radiolib/RadioLibWrappers.h>
|
||||
#include <IkokaNanoNRFBoard.h>
|
||||
#include <helpers/radiolib/CustomSX1262Wrapper.h>
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
#include <helpers/sensors/EnvironmentSensorManager.h>
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
#include <helpers/ui/NullDisplayDriver.h>
|
||||
#include <helpers/ui/MomentaryButton.h>
|
||||
extern DISPLAY_CLASS display;
|
||||
// extern MomentaryButton user_btn;
|
||||
#endif
|
||||
|
||||
extern IkokaNanoNRFBoard board;
|
||||
extern WRAPPER_CLASS radio_driver;
|
||||
extern AutoDiscoverRTCClock rtc_clock;
|
||||
extern EnvironmentSensorManager sensors;
|
||||
|
||||
bool radio_init();
|
||||
uint32_t radio_get_rng_seed();
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr);
|
||||
void radio_set_tx_power(uint8_t dbm);
|
||||
mesh::LocalIdentity radio_new_identity();
|
||||
86
variants/ikoka_nano_nrf/variant.cpp
Normal file
86
variants/ikoka_nano_nrf/variant.cpp
Normal file
@@ -0,0 +1,86 @@
|
||||
#include "variant.h"
|
||||
#include "wiring_constants.h"
|
||||
#include "wiring_digital.h"
|
||||
#include "nrf.h"
|
||||
|
||||
const uint32_t g_ADigitalPinMap[] =
|
||||
{
|
||||
// D0 .. D10
|
||||
2, // D0 is P0.02 (A0)
|
||||
3, // D1 is P0.03 (A1)
|
||||
28, // D2 is P0.28 (A2)
|
||||
29, // D3 is P0.29 (A3)
|
||||
4, // D4 is P0.04 (A4,SDA)
|
||||
5, // D5 is P0.05 (A5,SCL)
|
||||
43, // D6 is P1.11 (TX)
|
||||
44, // D7 is P1.12 (RX)
|
||||
45, // D8 is P1.13 (SCK)
|
||||
46, // D9 is P1.14 (MISO)
|
||||
47, // D10 is P1.15 (MOSI)
|
||||
|
||||
// LEDs
|
||||
26, // D11 is P0.26 (LED RED)
|
||||
6, // D12 is P0.06 (LED BLUE)
|
||||
30, // D13 is P0.30 (LED GREEN)
|
||||
14, // D14 is P0.14 (READ_BAT)
|
||||
|
||||
// LSM6DS3TR
|
||||
40, // D15 is P1.08 (6D_PWR)
|
||||
27, // D16 is P0.27 (6D_I2C_SCL)
|
||||
7, // D17 is P0.07 (6D_I2C_SDA)
|
||||
11, // D18 is P0.11 (6D_INT1)
|
||||
|
||||
// MIC
|
||||
42, // D19 is P1.10 (MIC_PWR)
|
||||
32, // D20 is P1.00 (PDM_CLK)
|
||||
16, // D21 is P0.16 (PDM_DATA)
|
||||
|
||||
// BQ25100
|
||||
13, // D22 is P0.13 (HICHG)
|
||||
17, // D23 is P0.17 (~CHG)
|
||||
|
||||
//
|
||||
21, // D24 is P0.21 (QSPI_SCK)
|
||||
25, // D25 is P0.25 (QSPI_CSN)
|
||||
20, // D26 is P0.20 (QSPI_SIO_0 DI)
|
||||
24, // D27 is P0.24 (QSPI_SIO_1 DO)
|
||||
22, // D28 is P0.22 (QSPI_SIO_2 WP)
|
||||
23, // D29 is P0.23 (QSPI_SIO_3 HOLD)
|
||||
|
||||
// NFC
|
||||
9, // D30 is P0.09 (NFC1)
|
||||
10, // D31 is P0.10 (NFC2)
|
||||
|
||||
// VBAT
|
||||
31, // D32 is P0.31 (VBAT)
|
||||
};
|
||||
|
||||
void initVariant()
|
||||
{
|
||||
// Disable reading of the BAT voltage.
|
||||
// https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging
|
||||
pinMode(VBAT_ENABLE, OUTPUT);
|
||||
//digitalWrite(VBAT_ENABLE, HIGH);
|
||||
// This was taken from Seeed github butis not coherent with the doc,
|
||||
// VBAT_ENABLE should be kept to LOW to protect P0.14, (1500/500)*(4.2-3.3)+3.3 = 3.9V > 3.6V
|
||||
// This induces a 3mA current in the resistors :( but it's better than burning the nrf
|
||||
digitalWrite(VBAT_ENABLE, LOW);
|
||||
|
||||
// Low charging current (50mA)
|
||||
// https://wiki.seeedstudio.com/XIAO_BLE#battery-charging-current
|
||||
//pinMode(PIN_CHARGING_CURRENT, INPUT);
|
||||
|
||||
// High charging current (100mA)
|
||||
pinMode(PIN_CHARGING_CURRENT, OUTPUT);
|
||||
digitalWrite(PIN_CHARGING_CURRENT, LOW);
|
||||
|
||||
pinMode(PIN_QSPI_CS, OUTPUT);
|
||||
digitalWrite(PIN_QSPI_CS, HIGH);
|
||||
|
||||
pinMode(LED_RED, OUTPUT);
|
||||
digitalWrite(LED_RED, HIGH);
|
||||
pinMode(LED_GREEN, OUTPUT);
|
||||
digitalWrite(LED_GREEN, HIGH);
|
||||
pinMode(LED_BLUE, OUTPUT);
|
||||
digitalWrite(LED_BLUE, HIGH);
|
||||
}
|
||||
149
variants/ikoka_nano_nrf/variant.h
Normal file
149
variants/ikoka_nano_nrf/variant.h
Normal file
@@ -0,0 +1,149 @@
|
||||
#ifndef _IKOKA_NANO_NRF_H_
|
||||
#define _IKOKA_NANO_NRF_H_
|
||||
|
||||
/** Master clock frequency */
|
||||
#define VARIANT_MCK (64000000ul)
|
||||
|
||||
#define USE_LFXO // Board uses 32khz crystal for LF
|
||||
//#define USE_LFRC // Board uses RC for LF
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
* Headers
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "WVariant.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif // __cplusplus
|
||||
|
||||
#define PINS_COUNT (33)
|
||||
#define NUM_DIGITAL_PINS (33)
|
||||
#define NUM_ANALOG_INPUTS (8)
|
||||
#define NUM_ANALOG_OUTPUTS (0)
|
||||
|
||||
// LEDs
|
||||
#define PIN_LED (LED_RED)
|
||||
#define LED_PWR (PINS_COUNT)
|
||||
#define PIN_NEOPIXEL (PINS_COUNT)
|
||||
#define NEOPIXEL_NUM (0)
|
||||
|
||||
#define LED_BUILTIN (PIN_LED)
|
||||
|
||||
#define LED_RED (11)
|
||||
#define LED_GREEN (13)
|
||||
#define LED_BLUE (12)
|
||||
|
||||
#define LED_STATE_ON (0) // State when LED is litted
|
||||
|
||||
// Buttons
|
||||
// #define PIN_BUTTON1 (PINS_COUNT)
|
||||
|
||||
// Digital PINs
|
||||
static const uint8_t D0 = 0 ;
|
||||
static const uint8_t D1 = 1 ;
|
||||
static const uint8_t D2 = 2 ;
|
||||
static const uint8_t D3 = 3 ;
|
||||
static const uint8_t D4 = 4 ;
|
||||
static const uint8_t D5 = 5 ;
|
||||
static const uint8_t D6 = 6 ;
|
||||
static const uint8_t D7 = 7 ;
|
||||
static const uint8_t D8 = 8 ;
|
||||
static const uint8_t D9 = 9 ;
|
||||
static const uint8_t D10 = 10;
|
||||
|
||||
#define VBAT_ENABLE (14) // Output LOW to enable reading of the BAT voltage.
|
||||
// https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging
|
||||
|
||||
#define PIN_CHARGING_CURRENT (22) // Battery Charging current
|
||||
// https://wiki.seeedstudio.com/XIAO_BLE#battery-charging-current
|
||||
|
||||
// Analog pins
|
||||
#define PIN_A0 (0)
|
||||
#define PIN_A1 (1)
|
||||
#define PIN_A2 (2)
|
||||
#define PIN_A3 (3)
|
||||
#define PIN_A4 (4)
|
||||
#define PIN_A5 (5)
|
||||
#define PIN_VBAT (32) // Read the BAT voltage.
|
||||
// https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging
|
||||
|
||||
#define BAT_NOT_CHARGING (23) // LOW when charging
|
||||
|
||||
#define AREF_VOLTAGE (3.0)
|
||||
#define ADC_MULTIPLIER (3.0F) // 1M, 512k divider bridge
|
||||
|
||||
static const uint8_t A0 = PIN_A0;
|
||||
static const uint8_t A1 = PIN_A1;
|
||||
static const uint8_t A2 = PIN_A2;
|
||||
static const uint8_t A3 = PIN_A3;
|
||||
static const uint8_t A4 = PIN_A4;
|
||||
static const uint8_t A5 = PIN_A5;
|
||||
|
||||
#define ADC_RESOLUTION (12)
|
||||
|
||||
// Other pins
|
||||
#define PIN_NFC1 (30)
|
||||
#define PIN_NFC2 (31)
|
||||
|
||||
// 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)
|
||||
|
||||
#define PIN_SPI1_MISO (25)
|
||||
#define PIN_SPI1_MOSI (26)
|
||||
#define PIN_SPI1_SCK (29)
|
||||
|
||||
// Lora SPI is on SPI0
|
||||
#define P_LORA_SCLK PIN_SPI_SCK
|
||||
#define P_LORA_MISO PIN_SPI_MISO
|
||||
#define P_LORA_MOSI PIN_SPI_MOSI
|
||||
|
||||
// Wire Interfaces
|
||||
#define WIRE_INTERFACES_COUNT (1)
|
||||
|
||||
// #define PIN_WIRE_SDA (17) // 4 and 5 are used for the sx1262 !
|
||||
// #define PIN_WIRE_SCL (16) // use WIRE1_SDA
|
||||
|
||||
static const uint8_t SDA = PIN_WIRE_SDA;
|
||||
static const uint8_t SCL = PIN_WIRE_SCL;
|
||||
|
||||
//#define PIN_WIRE1_SDA (17)
|
||||
//#define PIN_WIRE1_SCL (16)
|
||||
#define PIN_LSM6DS3TR_C_POWER (15)
|
||||
#define PIN_LSM6DS3TR_C_INT1 (18)
|
||||
|
||||
// PDM Interfaces
|
||||
#define PIN_PDM_PWR (19)
|
||||
#define PIN_PDM_CLK (20)
|
||||
#define PIN_PDM_DIN (21)
|
||||
|
||||
// QSPI Pins
|
||||
#define PIN_QSPI_SCK (24)
|
||||
#define PIN_QSPI_CS (25)
|
||||
#define PIN_QSPI_IO0 (26)
|
||||
#define PIN_QSPI_IO1 (27)
|
||||
#define PIN_QSPI_IO2 (28)
|
||||
#define PIN_QSPI_IO3 (29)
|
||||
|
||||
// On-board QSPI Flash
|
||||
#define EXTERNAL_FLASH_DEVICES (P25Q16H)
|
||||
#define EXTERNAL_FLASH_USE_QSPI
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
* Arduino objects - C++ only
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
#endif
|
||||
94
variants/ikoka_stick_nrf/IkokaStickNRFBoard.cpp
Normal file
94
variants/ikoka_stick_nrf/IkokaStickNRFBoard.cpp
Normal file
@@ -0,0 +1,94 @@
|
||||
#ifdef XIAO_NRF52
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "IkokaStickNRFBoard.h"
|
||||
|
||||
#include <bluefruit.h>
|
||||
#include <Wire.h>
|
||||
|
||||
static BLEDfu bledfu;
|
||||
|
||||
static void connect_callback(uint16_t conn_handle) {
|
||||
(void)conn_handle;
|
||||
MESH_DEBUG_PRINTLN("BLE client connected");
|
||||
}
|
||||
|
||||
static void disconnect_callback(uint16_t conn_handle, uint8_t reason) {
|
||||
(void)conn_handle;
|
||||
(void)reason;
|
||||
|
||||
MESH_DEBUG_PRINTLN("BLE client disconnected");
|
||||
}
|
||||
|
||||
void IkokaStickNRFBoard::begin() {
|
||||
// for future use, sub-classes SHOULD call this from their begin()
|
||||
startup_reason = BD_STARTUP_NORMAL;
|
||||
|
||||
pinMode(PIN_VBAT, INPUT);
|
||||
pinMode(VBAT_ENABLE, OUTPUT);
|
||||
digitalWrite(VBAT_ENABLE, HIGH);
|
||||
|
||||
#ifdef PIN_USER_BTN
|
||||
pinMode(PIN_USER_BTN, INPUT_PULLUP);
|
||||
#endif
|
||||
|
||||
#if defined(PIN_WIRE_SDA) && defined(PIN_WIRE_SCL)
|
||||
Wire.setPins(PIN_WIRE_SDA, PIN_WIRE_SCL);
|
||||
#endif
|
||||
|
||||
Wire.begin();
|
||||
|
||||
#ifdef P_LORA_TX_LED
|
||||
pinMode(P_LORA_TX_LED, OUTPUT);
|
||||
digitalWrite(P_LORA_TX_LED, HIGH);
|
||||
#endif
|
||||
|
||||
// pinMode(SX126X_POWER_EN, OUTPUT);
|
||||
// digitalWrite(SX126X_POWER_EN, HIGH);
|
||||
delay(10); // give sx1262 some time to power up
|
||||
}
|
||||
|
||||
bool IkokaStickNRFBoard::startOTAUpdate(const char *id, char reply[]) {
|
||||
// Config the peripheral connection with maximum bandwidth
|
||||
// more SRAM required by SoftDevice
|
||||
// Note: All config***() function must be called before begin()
|
||||
Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
|
||||
Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16);
|
||||
|
||||
Bluefruit.begin(1, 0);
|
||||
// Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4
|
||||
Bluefruit.setTxPower(4);
|
||||
// Set the BLE device name
|
||||
Bluefruit.setName("XIAO_NRF52_OTA");
|
||||
|
||||
Bluefruit.Periph.setConnectCallback(connect_callback);
|
||||
Bluefruit.Periph.setDisconnectCallback(disconnect_callback);
|
||||
|
||||
// To be consistent OTA DFU should be added first if it exists
|
||||
bledfu.begin();
|
||||
|
||||
// Set up and start advertising
|
||||
// Advertising packet
|
||||
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
|
||||
Bluefruit.Advertising.addTxPower();
|
||||
Bluefruit.Advertising.addName();
|
||||
|
||||
/* Start Advertising
|
||||
- Enable auto advertising if disconnected
|
||||
- Interval: fast mode = 20 ms, slow mode = 152.5 ms
|
||||
- Timeout for fast mode is 30 seconds
|
||||
- Start(timeout) with timeout = 0 will advertise forever (until connected)
|
||||
|
||||
For recommended advertising interval
|
||||
https://developer.apple.com/library/content/qa/qa1931/_index.html
|
||||
*/
|
||||
Bluefruit.Advertising.restartOnDisconnect(true);
|
||||
Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms
|
||||
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
|
||||
Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds
|
||||
|
||||
strcpy(reply, "OK - started");
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
60
variants/ikoka_stick_nrf/IkokaStickNRFBoard.h
Normal file
60
variants/ikoka_stick_nrf/IkokaStickNRFBoard.h
Normal file
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include <MeshCore.h>
|
||||
#include <Arduino.h>
|
||||
|
||||
#ifdef XIAO_NRF52
|
||||
|
||||
class IkokaStickNRFBoard : public mesh::MainBoard {
|
||||
protected:
|
||||
uint8_t startup_reason;
|
||||
|
||||
public:
|
||||
void begin();
|
||||
uint8_t getStartupReason() const override { return startup_reason; }
|
||||
|
||||
#if defined(P_LORA_TX_LED)
|
||||
void onBeforeTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED on
|
||||
#if defined(LED_BLUE)
|
||||
// turn off that annoying blue LED before transmitting
|
||||
digitalWrite(LED_BLUE, HIGH);
|
||||
#endif
|
||||
}
|
||||
void onAfterTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED off
|
||||
#if defined(LED_BLUE)
|
||||
// do it after transmitting too, just in case
|
||||
digitalWrite(LED_BLUE, HIGH);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
uint16_t getBattMilliVolts() override {
|
||||
// Please read befor going further ;)
|
||||
// https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging
|
||||
|
||||
// We can't drive VBAT_ENABLE to HIGH as long
|
||||
// as we don't know wether we are charging or not ...
|
||||
// this is a 3mA loss (4/1500)
|
||||
digitalWrite(VBAT_ENABLE, LOW);
|
||||
int adcvalue = 0;
|
||||
analogReadResolution(12);
|
||||
analogReference(AR_INTERNAL_3_0);
|
||||
delay(10);
|
||||
adcvalue = analogRead(PIN_VBAT);
|
||||
return (adcvalue * ADC_MULTIPLIER * AREF_VOLTAGE) / 4.096;
|
||||
}
|
||||
|
||||
const char *getManufacturerName() const override {
|
||||
return MANUFACTURER_STRING;
|
||||
}
|
||||
|
||||
void reboot() override {
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
|
||||
bool startOTAUpdate(const char *id, char reply[]) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -61,6 +61,7 @@ extends = ikoka_stick_nrf_baseboard
|
||||
; No PA in this model, full 22dBm
|
||||
build_flags =
|
||||
${ikoka_stick_nrf_baseboard.build_flags}
|
||||
-D MANUFACTURER_STRING='"Ikoka Stick-E22-22dBm (Xiao_nrf52)"'
|
||||
-D LORA_TX_POWER=22
|
||||
build_src_filter = ${nrf52840_xiao.build_src_filter}
|
||||
+<helpers/*.cpp>
|
||||
@@ -75,6 +76,7 @@ extends = ikoka_stick_nrf_baseboard
|
||||
; cause distortion in the PA output. 20dBm in -> 30dBm out
|
||||
build_flags =
|
||||
${ikoka_stick_nrf_baseboard.build_flags}
|
||||
-D MANUFACTURER_STRING='"Ikoka Stick-E22-30dBm (Xiao_nrf52)"'
|
||||
-D LORA_TX_POWER=20
|
||||
build_src_filter = ${nrf52840_xiao.build_src_filter}
|
||||
+<helpers/*.cpp>
|
||||
@@ -89,6 +91,7 @@ extends = ikoka_stick_nrf_baseboard
|
||||
; to the rf amplifier frontend. 9dBm in -> 33dBm out
|
||||
build_flags =
|
||||
${ikoka_stick_nrf_baseboard.build_flags}
|
||||
-D MANUFACTURER_STRING='"Ikoka Stick-E22-33dBm (Xiao_nrf52)"'
|
||||
-D LORA_TX_POWER=9
|
||||
build_src_filter = ${nrf52840_xiao.build_src_filter}
|
||||
+<helpers/*.cpp>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user