mirror of
https://github.com/meshcore-dev/MeshCore.git
synced 2026-03-30 10:39:59 +00:00
Compare commits
198 Commits
room-serve
...
repeater-v
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d072e7b575 | ||
|
|
d8952f3710 | ||
|
|
810fc8b8f0 | ||
|
|
997261a68e | ||
|
|
98f1785104 | ||
|
|
60b7897665 | ||
|
|
7a7f436921 | ||
|
|
0e208f01cd | ||
|
|
eba0daf70a | ||
|
|
94db70d511 | ||
|
|
c2ef0a3f0b | ||
|
|
e076e797e6 | ||
|
|
90b3b1b6fe | ||
|
|
f18a3b78ad | ||
|
|
6962a043e2 | ||
|
|
d04eda9f16 | ||
|
|
941d2d5c13 | ||
|
|
f855523481 | ||
|
|
6dd85880e4 | ||
|
|
dfe3561f39 | ||
|
|
bff90a5102 | ||
|
|
078a60040d | ||
|
|
eaea26267b | ||
|
|
a39c000f5d | ||
|
|
fb5fcae614 | ||
|
|
81863a5995 | ||
|
|
310e6c64d4 | ||
|
|
5780b50a48 | ||
|
|
791da53c7b | ||
|
|
5b27bef485 | ||
|
|
d3a88e9206 | ||
|
|
67d709b3aa | ||
|
|
136f3d1000 | ||
|
|
458f309065 | ||
|
|
af606343a7 | ||
|
|
1f06d22bde | ||
|
|
bcb64d8a4c | ||
|
|
cb80ceee47 | ||
|
|
9d967388f7 | ||
|
|
678f36a57b | ||
|
|
8f32ee61ce | ||
|
|
0bccf29f64 | ||
|
|
e442e94e3d | ||
|
|
cd9691ba81 | ||
|
|
933e7ba847 | ||
|
|
b407f923e0 | ||
|
|
1e031e989d | ||
|
|
26f01e0605 | ||
|
|
99774f10ac | ||
|
|
6aa4df6ca5 | ||
|
|
e1c3dfca92 | ||
|
|
c0870960d6 | ||
|
|
73231b1d22 | ||
|
|
2818749a09 | ||
|
|
77f44f727e | ||
|
|
8f84a5d990 | ||
|
|
9813ec6d96 | ||
|
|
d63775b878 | ||
|
|
8a8e89f282 | ||
|
|
05254bd67b | ||
|
|
f68b9bbfca | ||
|
|
1c67d1cb42 | ||
|
|
056bcf83d9 | ||
|
|
f261599608 | ||
|
|
e6325db72b | ||
|
|
154b5e4014 | ||
|
|
21756d5e1c | ||
|
|
7eebd81cd0 | ||
|
|
2cdb3b501c | ||
|
|
13654347c7 | ||
|
|
4f2aaa47d3 | ||
|
|
b614cef980 | ||
|
|
569ef18b35 | ||
|
|
8f5a2ac832 | ||
|
|
c942aa06f9 | ||
|
|
2f047da3a3 | ||
|
|
f51ab11cf1 | ||
|
|
2a7e105c59 | ||
|
|
0fc4d244ea | ||
|
|
36b981c9eb | ||
|
|
e1092118d9 | ||
|
|
00f0bb7471 | ||
|
|
10df19d3a3 | ||
|
|
da1febdd88 | ||
|
|
70b6e01c49 | ||
|
|
285423ca55 | ||
|
|
8c992d5037 | ||
|
|
977b76c47e | ||
|
|
669597ea4f | ||
|
|
a87b5231cc | ||
|
|
2ba3f42f30 | ||
|
|
26efe2fb19 | ||
|
|
4d9964ff98 | ||
|
|
b1c8963e1e | ||
|
|
99246e6b6f | ||
|
|
76847a7756 | ||
|
|
9d82911e18 | ||
|
|
631f593895 | ||
|
|
933764546a | ||
|
|
1e263cab2b | ||
|
|
a81e8b4b54 | ||
|
|
8f70d48ea1 | ||
|
|
c0eb5bffaa | ||
|
|
1b25a63996 | ||
|
|
7669b97b8b | ||
|
|
7d7692a13b | ||
|
|
c34dd2a40c | ||
|
|
6735960a4e | ||
|
|
2d6c834887 | ||
|
|
052ca9f12f | ||
|
|
512f0900da | ||
|
|
04fe2f567f | ||
|
|
f64470c581 | ||
|
|
50f6e8a089 | ||
|
|
7b1582a0b9 | ||
|
|
21564ae494 | ||
|
|
b17196acb4 | ||
|
|
ea24a12ba3 | ||
|
|
1f1d39d179 | ||
|
|
f9bc3a1ebb | ||
|
|
fbfa8bbe57 | ||
|
|
310ab9710c | ||
|
|
69d1d920bb | ||
|
|
7f7b03e442 | ||
|
|
2a875d9930 | ||
|
|
5848080f58 | ||
|
|
e825e4474f | ||
|
|
04118f01fc | ||
|
|
34faa49685 | ||
|
|
561d289ea5 | ||
|
|
2de87d1875 | ||
|
|
67ca4a1c8e | ||
|
|
cf3d55201f | ||
|
|
b4330e376c | ||
|
|
8ee251a19e | ||
|
|
1d4ae9f3c4 | ||
|
|
82bcd74932 | ||
|
|
4704ea8dae | ||
|
|
ab8cd85d8e | ||
|
|
366461a3a1 | ||
|
|
96d6ffefad | ||
|
|
3c7ff8da29 | ||
|
|
7534c5143f | ||
|
|
a5f210779f | ||
|
|
87ca6e19ae | ||
|
|
c4c175cab8 | ||
|
|
a3c8597747 | ||
|
|
511a935bbf | ||
|
|
1718657829 | ||
|
|
cc5c7b3ab7 | ||
|
|
d4544804b5 | ||
|
|
be64fa7ca0 | ||
|
|
5c2c248f70 | ||
|
|
9b3e7e5a21 | ||
|
|
c4b221f386 | ||
|
|
3eded4581a | ||
|
|
6092f5737c | ||
|
|
329c76629d | ||
|
|
b98668150b | ||
|
|
c4d32eba74 | ||
|
|
bc820ae93e | ||
|
|
4a51cb98c6 | ||
|
|
28aa94b899 | ||
|
|
348db9b82f | ||
|
|
a0d9449e21 | ||
|
|
b2b755c0fb | ||
|
|
d7e6a361b5 | ||
|
|
396a7a24b1 | ||
|
|
9498d2e824 | ||
|
|
019a829121 | ||
|
|
86a3f592b9 | ||
|
|
4d2380d168 | ||
|
|
96faf423e3 | ||
|
|
04ad06b8be | ||
|
|
259f4ec169 | ||
|
|
49d24283f7 | ||
|
|
2bec0f1418 | ||
|
|
c762d88edd | ||
|
|
87f476354f | ||
|
|
accd80db77 | ||
|
|
4d4a0ae4e3 | ||
|
|
7e583d7f98 | ||
|
|
f93a5156bb | ||
|
|
af070af554 | ||
|
|
ec320cb5a8 | ||
|
|
87443ad43f | ||
|
|
0a62ab663f | ||
|
|
7d24c65c4e | ||
|
|
5e553b1fc8 | ||
|
|
3a6b8fda93 | ||
|
|
6e109779f3 | ||
|
|
3a920986ca | ||
|
|
8740528d37 | ||
|
|
5493dbc120 | ||
|
|
2f6427ce21 | ||
|
|
b8f09531c4 | ||
|
|
01e98caea7 | ||
|
|
74e7af3f57 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,6 +1,8 @@
|
||||
.direnv
|
||||
.pio
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
||||
out/
|
||||
.direnv/
|
||||
|
||||
@@ -34,7 +34,8 @@
|
||||
],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"svd_path": "nrf52840.svd"
|
||||
"svd_path": "nrf52840.svd",
|
||||
"openocd_target": "nrf52.cfg"
|
||||
},
|
||||
"frameworks": [
|
||||
"arduino"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [
|
||||
[
|
||||
[
|
||||
"0x239A",
|
||||
"0x00B3"
|
||||
],
|
||||
@@ -51,7 +51,8 @@
|
||||
],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"svd_path": "nrf52840.svd"
|
||||
"svd_path": "nrf52840.svd",
|
||||
"openocd_target": "nrf52.cfg"
|
||||
},
|
||||
"frameworks": [
|
||||
"arduino",
|
||||
|
||||
61
boards/seeed-xiao-afruitnrf52-nrf52840.json
Normal file
61
boards/seeed-xiao-afruitnrf52-nrf52840.json
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v7.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DARDUINO_Seeed_XIAO_nRF52840 -DNRF52840_XXAA -DSEEED_XIAO_NRF52840 ",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [
|
||||
[ "0x2886", "0x8044" ],
|
||||
[ "0x2886", "0x0044" ]
|
||||
],
|
||||
"mcu": "nrf52840",
|
||||
"variant": "Seeed_XIAO_nRF52840",
|
||||
"softdevice": {
|
||||
"sd_flags": "-DS140",
|
||||
"sd_name": "s140",
|
||||
"sd_version": "7.3.0",
|
||||
"sd_fwid": "0x0123"
|
||||
},
|
||||
"bsp": {
|
||||
"name": "adafruit"
|
||||
},
|
||||
"bootloader": {
|
||||
"settings_addr": "0xFF000"
|
||||
},
|
||||
"usb_product": "XIAO nRF52840"
|
||||
},
|
||||
"connectivity": [
|
||||
"bluetooth"
|
||||
],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"openocd_target": "nrf52.cfg",
|
||||
"svd_path": "nrf52840.svd"
|
||||
},
|
||||
"frameworks": [
|
||||
"arduino"
|
||||
],
|
||||
"name": "Seeed Studio XIAO nRF52840",
|
||||
"upload": {
|
||||
"maximum_ram_size": 237568,
|
||||
"maximum_size": 811008,
|
||||
"protocol": "nrfutil",
|
||||
"speed": 115200,
|
||||
"protocols": [
|
||||
"jlink",
|
||||
"nrfjprog",
|
||||
"nrfutil",
|
||||
"cmsis-dap",
|
||||
"sam-ba",
|
||||
"blackmagic"
|
||||
],
|
||||
"use_1200bps_touch": true,
|
||||
"require_upload_port": true,
|
||||
"wait_for_upload_port": true
|
||||
},
|
||||
"url": "https://wiki.seeedstudio.com/XIAO_BLE",
|
||||
"vendor": "Seeed Studio"
|
||||
}
|
||||
@@ -1,32 +1,32 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v6.ld"
|
||||
"ldscript": "nrf52840_s140_v6.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DARDUINO_NRF52840_PCA10056 -DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [
|
||||
[
|
||||
"0x239A",
|
||||
"0x8029"
|
||||
]
|
||||
[
|
||||
"0x239A",
|
||||
"0x8029"
|
||||
]
|
||||
],
|
||||
"usb_product": "NRF52 DK",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "pca10056",
|
||||
"bsp": {
|
||||
"name": "adafruit"
|
||||
"name": "adafruit"
|
||||
},
|
||||
"softdevice": {
|
||||
"sd_flags": "-DS140",
|
||||
"sd_name": "s140",
|
||||
"sd_version": "6.1.1",
|
||||
"sd_fwid": "0x00B6"
|
||||
"sd_flags": "-DS140",
|
||||
"sd_name": "s140",
|
||||
"sd_version": "6.1.1",
|
||||
"sd_fwid": "0x00B6"
|
||||
},
|
||||
"bootloader": {
|
||||
"settings_addr": "0xFF000"
|
||||
"settings_addr": "0xFF000"
|
||||
}
|
||||
},
|
||||
"connectivity": [
|
||||
@@ -35,9 +35,10 @@
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"onboard_tools": [
|
||||
"jlink"
|
||||
"jlink"
|
||||
],
|
||||
"svd_path": "nrf52840.svd"
|
||||
"svd_path": "nrf52840.svd",
|
||||
"openocd_target": "nrf52.cfg"
|
||||
},
|
||||
"frameworks": [
|
||||
"arduino"
|
||||
@@ -50,13 +51,13 @@
|
||||
"speed": 115200,
|
||||
"protocol": "jlink",
|
||||
"protocols": [
|
||||
"jlink",
|
||||
"nrfjprog",
|
||||
"stlink",
|
||||
"cmsis-dap",
|
||||
"blackmagic"
|
||||
"jlink",
|
||||
"nrfjprog",
|
||||
"stlink",
|
||||
"cmsis-dap",
|
||||
"blackmagic"
|
||||
]
|
||||
},
|
||||
"url": "https://os.mbed.com/platforms/Nordic-nRF52840-DK/",
|
||||
"vendor": "Nordic"
|
||||
}
|
||||
}
|
||||
51
boards/t_beams3_supreme.json
Normal file
51
boards/t_beams3_supreme.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino":{
|
||||
"ldscript": "esp32s3_out.ld",
|
||||
"partitions": "default.csv",
|
||||
"memory_type": "qio_qspi"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-DARDUINO_USB_MODE=1",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "qio",
|
||||
"hwids": [
|
||||
[
|
||||
"0x303A",
|
||||
"0x1001"
|
||||
]
|
||||
],
|
||||
"mcu": "esp32s3",
|
||||
"variant": "esp32s3"
|
||||
},
|
||||
"connectivity": [
|
||||
"wifi"
|
||||
],
|
||||
"debug": {
|
||||
"default_tool": "esp-builtin",
|
||||
"onboard_tools": [
|
||||
"esp-builtin"
|
||||
],
|
||||
"openocd_target": "esp32s3.cfg"
|
||||
},
|
||||
"frameworks": [
|
||||
"arduino",
|
||||
"espidf"
|
||||
],
|
||||
"name": "LilyGo T-Beam supreme (8MB Flash 8MB PSRAM)",
|
||||
"upload": {
|
||||
"flash_size": "8MB",
|
||||
"maximum_ram_size": 327680,
|
||||
"maximum_size": 8388608,
|
||||
"require_upload_port": true,
|
||||
"speed": 460800
|
||||
},
|
||||
"url": "https://www.lilygo.cc/products/t-beamsupreme-m",
|
||||
"vendor": "LilyGo"
|
||||
}
|
||||
72
boards/thinknode_m1.json
Normal file
72
boards/thinknode_m1.json
Normal file
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v6.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DARDUINO_NRF52840_TTGO_EINK -DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [
|
||||
[
|
||||
"0x239A",
|
||||
"0x4405"
|
||||
],
|
||||
[
|
||||
"0x239A",
|
||||
"0x0029"
|
||||
],
|
||||
[
|
||||
"0x239A",
|
||||
"0x002A"
|
||||
]
|
||||
],
|
||||
"usb_product": "elecrow_eink",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "ELECROW-ThinkNode-M1",
|
||||
"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",
|
||||
"onboard_tools": [
|
||||
"jlink"
|
||||
],
|
||||
"svd_path": "nrf52840.svd",
|
||||
"openocd_target": "nrf52.cfg"
|
||||
},
|
||||
"frameworks": [
|
||||
"arduino"
|
||||
],
|
||||
"name": "elecrow eink",
|
||||
"upload": {
|
||||
"maximum_ram_size": 248832,
|
||||
"maximum_size": 815104,
|
||||
"speed": 115200,
|
||||
"use_1200bps_touch": true,
|
||||
"require_upload_port": true,
|
||||
"wait_for_upload_port": true,
|
||||
"protocol": "nrfutil",
|
||||
"protocols": [
|
||||
"jlink",
|
||||
"nrfjprog",
|
||||
"nrfutil",
|
||||
"stlink"
|
||||
]
|
||||
},
|
||||
"url": "https://github.com/Elecrow-RD",
|
||||
"vendor": "ELECROW"
|
||||
}
|
||||
@@ -32,7 +32,8 @@
|
||||
"connectivity": ["bluetooth"],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"svd_path": "nrf52840.svd"
|
||||
"svd_path": "nrf52840.svd",
|
||||
"openocd_target": "nrf52.cfg"
|
||||
},
|
||||
"frameworks": ["arduino"],
|
||||
"name": "Seeed T1000-E",
|
||||
|
||||
2
build.sh
Normal file → Executable file
2
build.sh
Normal file → Executable file
@@ -58,7 +58,7 @@ build_firmware() {
|
||||
fi
|
||||
|
||||
# build .uf2 for nrf52 boards
|
||||
if [[ $1 == *"RAK_4631"* || $1 == *"t1000e"* || $1 == *"t114"* || $1 == *"T-Echo"* || $1 == *"Faketec"* || $1 == *"ProMicro"* ]]; then
|
||||
if [[ -f .pio/build/$1/firmware.zip && -f .pio/build/$1/firmware.hex ]]; then
|
||||
python bin/uf2conv/uf2conv.py .pio/build/$1/firmware.hex -c -o .pio/build/$1/firmware.uf2 -f 0xADA52840
|
||||
fi
|
||||
|
||||
|
||||
10
default.nix
Normal file
10
default.nix
Normal file
@@ -0,0 +1,10 @@
|
||||
{ pkgs ? import <nixpkgs> {} }:
|
||||
let
|
||||
in
|
||||
pkgs.mkShell {
|
||||
buildInputs = [
|
||||
pkgs.platformio
|
||||
# optional: needed as a programmer i.e. for esp32
|
||||
pkgs.avrdude
|
||||
];
|
||||
}
|
||||
@@ -372,4 +372,4 @@ You can update repeater and room server firmware with a bluetooth connection bet
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
26
examples/companion_radio/NodePrefs.h
Normal file
26
examples/companion_radio/NodePrefs.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#ifndef NODE_PREFS_H
|
||||
#define NODE_PREFS_H
|
||||
|
||||
#include <cstdint> // For uint8_t, uint32_t
|
||||
|
||||
#define TELEM_MODE_DENY 0
|
||||
#define TELEM_MODE_ALLOW_FLAGS 1 // use contact.flags
|
||||
#define TELEM_MODE_ALLOW_ALL 2
|
||||
|
||||
struct NodePrefs { // persisted to file
|
||||
float airtime_factor;
|
||||
char node_name[32];
|
||||
float freq;
|
||||
uint8_t sf;
|
||||
uint8_t cr;
|
||||
uint8_t reserved1;
|
||||
uint8_t manual_add_contacts;
|
||||
float bw;
|
||||
uint8_t tx_power_dbm;
|
||||
uint8_t telemetry_mode_base;
|
||||
uint8_t telemetry_mode_loc;
|
||||
float rx_delay_base;
|
||||
uint32_t ble_pin;
|
||||
};
|
||||
|
||||
#endif // NODE_PREFS_H
|
||||
@@ -1,8 +1,16 @@
|
||||
#include "UITask.h"
|
||||
#include <Arduino.h>
|
||||
#include <helpers/TxtDataHelpers.h>
|
||||
#include "NodePrefs.h"
|
||||
|
||||
#define AUTO_OFF_MILLIS 15000 // 15 seconds
|
||||
#define AUTO_OFF_MILLIS 15000 // 15 seconds
|
||||
#define BOOT_SCREEN_MILLIS 4000 // 4 seconds
|
||||
|
||||
#ifdef PIN_STATUS_LED
|
||||
#define LED_ON_MILLIS 20
|
||||
#define LED_ON_MSG_MILLIS 200
|
||||
#define LED_CYCLE_MILLIS 4000
|
||||
#endif
|
||||
|
||||
#ifndef USER_BTN_PRESSED
|
||||
#define USER_BTN_PRESSED LOW
|
||||
@@ -25,16 +33,26 @@ static const uint8_t meshcore_logo [] PROGMEM = {
|
||||
0xe3, 0xe3, 0x8f, 0xff, 0x1f, 0xfc, 0x3c, 0x0e, 0x1f, 0xf8, 0xff, 0xf8, 0x70, 0x3c, 0x7f, 0xf8,
|
||||
};
|
||||
|
||||
void UITask::begin(DisplayDriver* display, const char* node_name, const char* build_date, uint32_t pin_code) {
|
||||
void UITask::begin(DisplayDriver* display, NodePrefs* node_prefs, const char* build_date, const char* firmware_version, uint32_t pin_code) {
|
||||
_display = display;
|
||||
_auto_off = millis() + AUTO_OFF_MILLIS;
|
||||
clearMsgPreview();
|
||||
_node_name = node_name;
|
||||
_build_date = build_date;
|
||||
_node_prefs = node_prefs;
|
||||
_pin_code = pin_code;
|
||||
if (_display != NULL) {
|
||||
_display->turnOn();
|
||||
}
|
||||
|
||||
// strip off dash and commit hash by changing dash to null terminator
|
||||
// e.g: v1.2.3-abcdef -> v1.2.3
|
||||
char *version = strdup(firmware_version);
|
||||
char *dash = strchr(version, '-');
|
||||
if(dash){
|
||||
*dash = 0;
|
||||
}
|
||||
|
||||
// v1.2.3 (1 Jan 2025)
|
||||
sprintf(_version_info, "%s (%s)", version, build_date);
|
||||
}
|
||||
|
||||
void UITask::msgRead(int msgcount) {
|
||||
@@ -47,6 +65,7 @@ void UITask::msgRead(int msgcount) {
|
||||
void UITask::clearMsgPreview() {
|
||||
_origin[0] = 0;
|
||||
_msg[0] = 0;
|
||||
_need_refresh = true;
|
||||
}
|
||||
|
||||
void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) {
|
||||
@@ -62,71 +81,127 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i
|
||||
if (_display != NULL) {
|
||||
if (!_display->isOn()) _display->turnOn();
|
||||
_auto_off = millis() + AUTO_OFF_MILLIS; // extend the auto-off timer
|
||||
_need_refresh = true;
|
||||
}
|
||||
}
|
||||
|
||||
void renderBatteryIndicator(DisplayDriver* _display, uint16_t batteryMilliVolts) {
|
||||
// Convert millivolts to percentage
|
||||
const int minMilliVolts = 3000; // Minimum voltage (e.g., 3.0V)
|
||||
const int maxMilliVolts = 4200; // Maximum voltage (e.g., 4.2V)
|
||||
int batteryPercentage = ((batteryMilliVolts - minMilliVolts) * 100) / (maxMilliVolts - minMilliVolts);
|
||||
if (batteryPercentage < 0) batteryPercentage = 0; // Clamp to 0%
|
||||
if (batteryPercentage > 100) batteryPercentage = 100; // Clamp to 100%
|
||||
|
||||
// battery icon
|
||||
int iconWidth = 24;
|
||||
int iconHeight = 12;
|
||||
int iconX = _display->width() - iconWidth - 5; // Position the icon near the top-right corner
|
||||
int iconY = 0;
|
||||
_display->setColor(DisplayDriver::GREEN);
|
||||
|
||||
// battery outline
|
||||
_display->drawRect(iconX, iconY, iconWidth, iconHeight);
|
||||
|
||||
// battery "cap"
|
||||
_display->fillRect(iconX + iconWidth, iconY + (iconHeight / 4), 3, iconHeight / 2);
|
||||
|
||||
// fill the battery based on the percentage
|
||||
int fillWidth = (batteryPercentage * (iconWidth - 2)) / 100;
|
||||
_display->fillRect(iconX + 1, iconY + 1, fillWidth, iconHeight - 2);
|
||||
}
|
||||
|
||||
void UITask::renderCurrScreen() {
|
||||
if (_display == NULL) return; // assert() ??
|
||||
|
||||
char tmp[80];
|
||||
if (_origin[0] && _msg[0]) {
|
||||
if (_origin[0] && _msg[0]) { // message preview
|
||||
// render message preview
|
||||
_display->setCursor(0, 0);
|
||||
_display->setTextSize(1);
|
||||
_display->print(_node_name);
|
||||
_display->setColor(DisplayDriver::GREEN);
|
||||
_display->print(_node_prefs->node_name);
|
||||
|
||||
_display->setCursor(0, 12);
|
||||
_display->setColor(DisplayDriver::YELLOW);
|
||||
_display->print(_origin);
|
||||
_display->setCursor(0, 24);
|
||||
_display->setColor(DisplayDriver::LIGHT);
|
||||
_display->print(_msg);
|
||||
|
||||
_display->setCursor(100, 9);
|
||||
_display->setCursor(_display->width() - 28, 9);
|
||||
_display->setTextSize(2);
|
||||
_display->setColor(DisplayDriver::ORANGE);
|
||||
sprintf(tmp, "%d", _msgcount);
|
||||
_display->print(tmp);
|
||||
} else {
|
||||
// render 'home' screen
|
||||
_display->drawXbm(0, 0, meshcore_logo, 128, 13);
|
||||
_display->setCursor(0, 20);
|
||||
_display->setTextSize(1);
|
||||
_display->print(_node_name);
|
||||
_display->setColor(DisplayDriver::YELLOW); // last color will be kept on T114
|
||||
} else if (millis() < BOOT_SCREEN_MILLIS) { // boot screen
|
||||
// meshcore logo
|
||||
_display->setColor(DisplayDriver::BLUE);
|
||||
int logoWidth = 128;
|
||||
_display->drawXbm((_display->width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13);
|
||||
|
||||
sprintf(tmp, "Build: %s", _build_date);
|
||||
_display->setCursor(0, 32);
|
||||
// version info
|
||||
_display->setColor(DisplayDriver::LIGHT);
|
||||
_display->setTextSize(1);
|
||||
uint16_t textWidth = _display->getTextWidth(_version_info);
|
||||
_display->setCursor((_display->width() - textWidth) / 2, 22);
|
||||
_display->print(_version_info);
|
||||
} else { // home screen
|
||||
// node name
|
||||
_display->setCursor(0, 0);
|
||||
_display->setTextSize(1);
|
||||
_display->setColor(DisplayDriver::GREEN);
|
||||
_display->print(_node_prefs->node_name);
|
||||
|
||||
// battery voltage
|
||||
renderBatteryIndicator(_display, _board->getBattMilliVolts());
|
||||
|
||||
// freq / sf
|
||||
_display->setCursor(0, 20);
|
||||
_display->setColor(DisplayDriver::YELLOW);
|
||||
sprintf(tmp, "FREQ: %06.3f SF%d", _node_prefs->freq, _node_prefs->sf);
|
||||
_display->print(tmp);
|
||||
|
||||
if (_connected) {
|
||||
//_display->printf("freq : %03.2f sf %d\n", _prefs.freq, _prefs.sf);
|
||||
//_display->printf("bw : %03.2f cr %d\n", _prefs.bw, _prefs.cr);
|
||||
} else if (_pin_code != 0) {
|
||||
// bw / cr
|
||||
_display->setCursor(0, 30);
|
||||
sprintf(tmp, "BW: %03.2f CR: %d", _node_prefs->bw, _node_prefs->cr);
|
||||
_display->print(tmp);
|
||||
|
||||
// BT pin
|
||||
if (!_connected && _pin_code != 0) {
|
||||
_display->setColor(DisplayDriver::RED);
|
||||
_display->setTextSize(2);
|
||||
_display->setCursor(0, 43);
|
||||
sprintf(tmp, "Pin:%d", _pin_code);
|
||||
_display->print(tmp);
|
||||
_display->setColor(DisplayDriver::GREEN);
|
||||
} else {
|
||||
_display->setColor(DisplayDriver::LIGHT);
|
||||
}
|
||||
}
|
||||
_need_refresh = false;
|
||||
}
|
||||
|
||||
void UITask::userLedHandler() {
|
||||
#ifdef PIN_STATUS_LED
|
||||
static int state = 0;
|
||||
static int next_change = 0;
|
||||
static int last_increment = 0;
|
||||
|
||||
int cur_time = millis();
|
||||
if (cur_time > next_change) {
|
||||
if (state == 0) {
|
||||
state = 1; // led on, short = unread msg
|
||||
state = 1;
|
||||
if (_msgcount > 0) {
|
||||
next_change = cur_time + 500;
|
||||
last_increment = LED_ON_MSG_MILLIS;
|
||||
} else {
|
||||
next_change = cur_time + 2000;
|
||||
last_increment = LED_ON_MILLIS;
|
||||
}
|
||||
next_change = cur_time + last_increment;
|
||||
} else {
|
||||
state = 0;
|
||||
if (_board->getBattMilliVolts() > 3800) {
|
||||
next_change = cur_time + 2000;
|
||||
} else {
|
||||
next_change = cur_time + 4000; // 4s blank if bat level low
|
||||
}
|
||||
next_change = cur_time + LED_CYCLE_MILLIS - last_increment;
|
||||
}
|
||||
digitalWrite(PIN_STATUS_LED, state);
|
||||
}
|
||||
@@ -148,6 +223,7 @@ void UITask::buttonHandler() {
|
||||
clearMsgPreview();
|
||||
} else {
|
||||
_display->turnOn();
|
||||
_need_refresh = true;
|
||||
}
|
||||
_auto_off = cur_time + AUTO_OFF_MILLIS; // extend auto-off timer
|
||||
}
|
||||
@@ -173,7 +249,12 @@ void UITask::loop() {
|
||||
userLedHandler();
|
||||
|
||||
if (_display != NULL && _display->isOn()) {
|
||||
if (millis() >= _next_refresh) {
|
||||
static bool _firstBoot = true;
|
||||
if(_firstBoot && millis() >= BOOT_SCREEN_MILLIS) {
|
||||
_need_refresh = true;
|
||||
_firstBoot = false;
|
||||
}
|
||||
if (millis() >= _next_refresh && _need_refresh) {
|
||||
_display->startFrame();
|
||||
renderCurrScreen();
|
||||
_display->endFrame();
|
||||
|
||||
@@ -4,17 +4,20 @@
|
||||
#include <helpers/ui/DisplayDriver.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "NodePrefs.h"
|
||||
|
||||
class UITask {
|
||||
DisplayDriver* _display;
|
||||
mesh::MainBoard* _board;
|
||||
unsigned long _next_refresh, _auto_off;
|
||||
bool _connected;
|
||||
uint32_t _pin_code;
|
||||
const char* _node_name;
|
||||
const char* _build_date;
|
||||
NodePrefs* _node_prefs;
|
||||
char _version_info[32];
|
||||
char _origin[62];
|
||||
char _msg[80];
|
||||
int _msgcount;
|
||||
bool _need_refresh = true;
|
||||
|
||||
void renderCurrScreen();
|
||||
void buttonHandler();
|
||||
@@ -26,7 +29,7 @@ public:
|
||||
_next_refresh = 0;
|
||||
_connected = false;
|
||||
}
|
||||
void begin(DisplayDriver* display, const char* node_name, const char* build_date, uint32_t pin_code);
|
||||
void begin(DisplayDriver* display, NodePrefs* node_prefs, const char* build_date, const char* firmware_version, uint32_t pin_code);
|
||||
|
||||
void setHasConnection(bool connected) { _connected = connected; }
|
||||
bool hasDisplay() const { return _display != NULL; }
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#if defined(NRF52_PLATFORM)
|
||||
#include <InternalFileSystem.h>
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
#include <LittleFS.h>
|
||||
#elif defined(ESP32)
|
||||
#include <SPIFFS.h>
|
||||
#endif
|
||||
@@ -12,6 +14,7 @@
|
||||
#include <helpers/SimpleMeshTables.h>
|
||||
#include <helpers/IdentityStore.h>
|
||||
#include <helpers/BaseSerialInterface.h>
|
||||
#include "NodePrefs.h"
|
||||
#include <RTClib.h>
|
||||
#include <target.h>
|
||||
|
||||
@@ -57,11 +60,24 @@
|
||||
|
||||
#define PUBLIC_GROUP_PSK "izOH6cXN6mrJ5e26oRXNcg=="
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
#ifdef DISPLAY_CLASS // TODO: refactor this -- move to variants/*/target
|
||||
#include "UITask.h"
|
||||
#include <helpers/ui/SSD1306Display.h>
|
||||
#ifdef ST7735
|
||||
#include <helpers/ui/ST7735Display.h>
|
||||
#elif ST7789
|
||||
#include <helpers/ui/ST7789Display.h>
|
||||
#elif defined(HAS_GxEPD)
|
||||
#include <helpers/ui/GxEPDDisplay.h>
|
||||
#else
|
||||
#include <helpers/ui/SSD1306Display.h>
|
||||
#endif
|
||||
|
||||
#if defined(HELTEC_LORA_V3) && defined(ST7735)
|
||||
static DISPLAY_CLASS display(&board.periph_power); // peripheral power pin is shared
|
||||
#else
|
||||
static DISPLAY_CLASS display;
|
||||
#endif
|
||||
|
||||
static DISPLAY_CLASS display;
|
||||
#define HAS_UI
|
||||
#endif
|
||||
|
||||
@@ -83,14 +99,14 @@ static uint32_t _atoi(const char* sp) {
|
||||
|
||||
/*------------ Frame Protocol --------------*/
|
||||
|
||||
#define FIRMWARE_VER_CODE 3
|
||||
#define FIRMWARE_VER_CODE 5
|
||||
|
||||
#ifndef FIRMWARE_BUILD_DATE
|
||||
#define FIRMWARE_BUILD_DATE "30 Mar 2025"
|
||||
#define FIRMWARE_BUILD_DATE "9 May 2025"
|
||||
#endif
|
||||
|
||||
#ifndef FIRMWARE_VERSION
|
||||
#define FIRMWARE_VERSION "v1.4.2"
|
||||
#define FIRMWARE_VERSION "v1.6.0"
|
||||
#endif
|
||||
|
||||
#define CMD_APP_START 1
|
||||
@@ -130,6 +146,10 @@ static uint32_t _atoi(const char* sp) {
|
||||
#define CMD_SIGN_FINISH 35
|
||||
#define CMD_SEND_TRACE_PATH 36
|
||||
#define CMD_SET_DEVICE_PIN 37
|
||||
#define CMD_SET_OTHER_PARAMS 38
|
||||
#define CMD_SEND_TELEMETRY_REQ 39
|
||||
#define CMD_GET_CUSTOM_VARS 40
|
||||
#define CMD_SET_CUSTOM_VAR 41
|
||||
|
||||
#define RESP_CODE_OK 0
|
||||
#define RESP_CODE_ERR 1
|
||||
@@ -152,6 +172,7 @@ static uint32_t _atoi(const char* sp) {
|
||||
#define RESP_CODE_CHANNEL_INFO 18 // a reply to CMD_GET_CHANNEL
|
||||
#define RESP_CODE_SIGN_START 19
|
||||
#define RESP_CODE_SIGNATURE 20
|
||||
#define RESP_CODE_CUSTOM_VARS 21
|
||||
|
||||
// these are _pushed_ to client app at any time
|
||||
#define PUSH_CODE_ADVERT 0x80
|
||||
@@ -164,6 +185,8 @@ static uint32_t _atoi(const char* sp) {
|
||||
#define PUSH_CODE_STATUS_RESPONSE 0x87
|
||||
#define PUSH_CODE_LOG_RX_DATA 0x88
|
||||
#define PUSH_CODE_TRACE_DATA 0x89
|
||||
#define PUSH_CODE_NEW_ADVERT 0x8A
|
||||
#define PUSH_CODE_TELEMETRY_RESPONSE 0x8B
|
||||
|
||||
#define ERR_CODE_UNSUPPORTED_CMD 1
|
||||
#define ERR_CODE_NOT_FOUND 2
|
||||
@@ -174,23 +197,11 @@ static uint32_t _atoi(const char* sp) {
|
||||
|
||||
/* -------------------------------------------------------------------------------------- */
|
||||
|
||||
#define MAX_SIGN_DATA_LEN (8*1024) // 8K
|
||||
#define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS
|
||||
#define REQ_TYPE_KEEP_ALIVE 0x02
|
||||
#define REQ_TYPE_GET_TELEMETRY_DATA 0x03
|
||||
|
||||
struct NodePrefs { // persisted to file
|
||||
float airtime_factor;
|
||||
char node_name[32];
|
||||
double node_lat, node_lon;
|
||||
float freq;
|
||||
uint8_t sf;
|
||||
uint8_t cr;
|
||||
uint8_t reserved1;
|
||||
uint8_t reserved2;
|
||||
float bw;
|
||||
uint8_t tx_power_dbm;
|
||||
uint8_t unused[3];
|
||||
float rx_delay_base;
|
||||
uint32_t ble_pin;
|
||||
};
|
||||
#define MAX_SIGN_DATA_LEN (8*1024) // 8K
|
||||
|
||||
class MyMesh : public BaseChatMesh {
|
||||
FILESYSTEM* _fs;
|
||||
@@ -198,6 +209,7 @@ class MyMesh : public BaseChatMesh {
|
||||
NodePrefs _prefs;
|
||||
uint32_t pending_login;
|
||||
uint32_t pending_status;
|
||||
uint32_t pending_telemetry;
|
||||
BaseSerialInterface* _serial;
|
||||
ContactsIterator _iter;
|
||||
uint32_t _iter_filter_since;
|
||||
@@ -209,6 +221,7 @@ class MyMesh : public BaseChatMesh {
|
||||
uint32_t sign_data_len;
|
||||
uint8_t cmd_frame[MAX_FRAME_SIZE+1];
|
||||
uint8_t out_frame[MAX_FRAME_SIZE+1];
|
||||
CayenneLPP telemetry;
|
||||
|
||||
struct Frame {
|
||||
uint8_t len;
|
||||
@@ -228,6 +241,10 @@ class MyMesh : public BaseChatMesh {
|
||||
void loadMainIdentity() {
|
||||
if (!_identity_store->load("_main", self_id)) {
|
||||
self_id = radio_new_identity(); // create new random identity
|
||||
int count = 0;
|
||||
while (count < 10 && (self_id.pub_key[0] == 0x00 || self_id.pub_key[0] == 0xFF)) { // reserved id hashes
|
||||
self_id = radio_new_identity(); count++;
|
||||
}
|
||||
saveMainIdentity(self_id);
|
||||
}
|
||||
}
|
||||
@@ -238,7 +255,11 @@ class MyMesh : public BaseChatMesh {
|
||||
|
||||
void loadContacts() {
|
||||
if (_fs->exists("/contacts3")) {
|
||||
#if defined(RP2040_PLATFORM)
|
||||
File file = _fs->open("/contacts3", "r");
|
||||
#else
|
||||
File file = _fs->open("/contacts3");
|
||||
#endif
|
||||
if (file) {
|
||||
bool full = false;
|
||||
while (!full) {
|
||||
@@ -273,6 +294,8 @@ class MyMesh : public BaseChatMesh {
|
||||
#if defined(NRF52_PLATFORM)
|
||||
File file = _fs->open("/contacts3", FILE_O_WRITE);
|
||||
if (file) { file.seek(0); file.truncate(); }
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
File file = _fs->open("/contacts3", "w");
|
||||
#else
|
||||
File file = _fs->open("/contacts3", "w", true);
|
||||
#endif
|
||||
@@ -303,7 +326,11 @@ class MyMesh : public BaseChatMesh {
|
||||
|
||||
void loadChannels() {
|
||||
if (_fs->exists("/channels2")) {
|
||||
#if defined(RP2040_PLATFORM)
|
||||
File file = _fs->open("/channels2", "r");
|
||||
#else
|
||||
File file = _fs->open("/channels2");
|
||||
#endif
|
||||
if (file) {
|
||||
bool full = false;
|
||||
uint8_t channel_idx = 0;
|
||||
@@ -332,6 +359,8 @@ class MyMesh : public BaseChatMesh {
|
||||
#if defined(NRF52_PLATFORM)
|
||||
File file = _fs->open("/channels2", FILE_O_WRITE);
|
||||
if (file) { file.seek(0); file.truncate(); }
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
File file = _fs->open("/channels2", "w");
|
||||
#else
|
||||
File file = _fs->open("/channels2", "w", true);
|
||||
#endif
|
||||
@@ -362,7 +391,11 @@ class MyMesh : public BaseChatMesh {
|
||||
sprintf(path, "/bl/%s", fname);
|
||||
|
||||
if (_fs->exists(path)) {
|
||||
#if defined(RP2040_PLATFORM)
|
||||
File f = _fs->open(path, "r");
|
||||
#else
|
||||
File f = _fs->open(path);
|
||||
#endif
|
||||
if (f) {
|
||||
int len = f.read(dest_buf, 255); // currently MAX 255 byte blob len supported!!
|
||||
f.close();
|
||||
@@ -383,6 +416,8 @@ class MyMesh : public BaseChatMesh {
|
||||
#if defined(NRF52_PLATFORM)
|
||||
File f = _fs->open(path, FILE_O_WRITE);
|
||||
if (f) { f.seek(0); f.truncate(); }
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
File f = _fs->open(path, "w");
|
||||
#else
|
||||
File f = _fs->open(path, "w", true);
|
||||
#endif
|
||||
@@ -495,11 +530,19 @@ protected:
|
||||
}
|
||||
}
|
||||
|
||||
bool isAutoAddEnabled() const override {
|
||||
return (_prefs.manual_add_contacts & 1) == 0;
|
||||
}
|
||||
|
||||
void onDiscoveredContact(ContactInfo& contact, bool is_new) override {
|
||||
if (_serial->isConnected()) {
|
||||
out_frame[0] = PUSH_CODE_ADVERT;
|
||||
memcpy(&out_frame[1], contact.id.pub_key, PUB_KEY_SIZE);
|
||||
_serial->writeFrame(out_frame, 1 + PUB_KEY_SIZE);
|
||||
if (!isAutoAddEnabled() && is_new) {
|
||||
writeContactRespFrame(PUSH_CODE_NEW_ADVERT, contact);
|
||||
} else {
|
||||
out_frame[0] = PUSH_CODE_ADVERT;
|
||||
memcpy(&out_frame[1], contact.id.pub_key, PUB_KEY_SIZE);
|
||||
_serial->writeFrame(out_frame, 1 + PUB_KEY_SIZE);
|
||||
}
|
||||
} else {
|
||||
soundBuzzer();
|
||||
}
|
||||
@@ -620,9 +663,42 @@ protected:
|
||||
#endif
|
||||
}
|
||||
|
||||
uint8_t onContactRequest(const ContactInfo& contact, uint32_t sender_timestamp, const uint8_t* data, uint8_t len, uint8_t* reply) override {
|
||||
if (data[0] == REQ_TYPE_GET_TELEMETRY_DATA) {
|
||||
uint8_t permissions = 0;
|
||||
uint8_t cp = contact.flags >> 1; // LSB used as 'favourite' bit (so only use upper bits)
|
||||
|
||||
if (_prefs.telemetry_mode_base == TELEM_MODE_ALLOW_ALL) {
|
||||
permissions = TELEM_PERM_BASE;
|
||||
} else if (_prefs.telemetry_mode_base == TELEM_MODE_ALLOW_FLAGS) {
|
||||
permissions = cp & TELEM_PERM_BASE;
|
||||
}
|
||||
|
||||
if (_prefs.telemetry_mode_loc == TELEM_MODE_ALLOW_ALL) {
|
||||
permissions |= TELEM_PERM_LOCATION;
|
||||
} else if (_prefs.telemetry_mode_loc == TELEM_MODE_ALLOW_FLAGS) {
|
||||
permissions |= cp & TELEM_PERM_LOCATION;
|
||||
}
|
||||
|
||||
if (permissions & TELEM_PERM_BASE) { // only respond if base permission bit is set
|
||||
telemetry.reset();
|
||||
telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f);
|
||||
// query other sensors -- target specific
|
||||
sensors.querySensors(permissions, telemetry);
|
||||
|
||||
memcpy(reply, &sender_timestamp, 4); // reflect sender_timestamp back in response packet (kind of like a 'tag')
|
||||
|
||||
uint8_t tlen = telemetry.getSize();
|
||||
memcpy(&reply[4], telemetry.getBuffer(), tlen);
|
||||
return 4 + tlen;
|
||||
}
|
||||
}
|
||||
return 0; // unknown
|
||||
}
|
||||
|
||||
void onContactResponse(const ContactInfo& contact, const uint8_t* data, uint8_t len) override {
|
||||
uint32_t sender_timestamp;
|
||||
memcpy(&sender_timestamp, data, 4);
|
||||
uint32_t tag;
|
||||
memcpy(&tag, data, 4);
|
||||
|
||||
if (pending_login && memcmp(&pending_login, contact.id.pub_key, 4) == 0) { // check for login response
|
||||
// yes, is response to pending sendLogin()
|
||||
@@ -645,8 +721,10 @@ protected:
|
||||
}
|
||||
memcpy(&out_frame[i], contact.id.pub_key, 6); i += 6; // pub_key_prefix
|
||||
_serial->writeFrame(out_frame, i);
|
||||
} else if (len > 4 && pending_status && memcmp(&pending_status, contact.id.pub_key, 4) == 0) { // check for status response
|
||||
// yes, is response to pending sendStatusRequest()
|
||||
} else if (len > 4 && // check for status response
|
||||
pending_status && memcmp(&pending_status, contact.id.pub_key, 4) == 0 // legacy matching scheme
|
||||
// FUTURE: tag == pending_status
|
||||
) {
|
||||
pending_status = 0;
|
||||
|
||||
int i = 0;
|
||||
@@ -655,10 +733,23 @@ protected:
|
||||
memcpy(&out_frame[i], contact.id.pub_key, 6); i += 6; // pub_key_prefix
|
||||
memcpy(&out_frame[i], &data[4], len - 4); i += (len - 4);
|
||||
_serial->writeFrame(out_frame, i);
|
||||
} else if (len > 4 && tag == pending_telemetry) { // check for telemetry response
|
||||
pending_telemetry = 0;
|
||||
|
||||
int i = 0;
|
||||
out_frame[i++] = PUSH_CODE_TELEMETRY_RESPONSE;
|
||||
out_frame[i++] = 0; // reserved
|
||||
memcpy(&out_frame[i], contact.id.pub_key, 6); i += 6; // pub_key_prefix
|
||||
memcpy(&out_frame[i], &data[4], len - 4); i += (len - 4);
|
||||
_serial->writeFrame(out_frame, i);
|
||||
}
|
||||
}
|
||||
|
||||
void onRawDataRecv(mesh::Packet* packet) override {
|
||||
if (packet->payload_len + 4 > sizeof(out_frame)) {
|
||||
MESH_DEBUG_PRINTLN("onRawDataRecv(), payload_len too long: %d", packet->payload_len);
|
||||
return;
|
||||
}
|
||||
int i = 0;
|
||||
out_frame[i++] = PUSH_CODE_RAW_DATA;
|
||||
out_frame[i++] = (int8_t)(_radio->getLastSNR() * 4);
|
||||
@@ -706,13 +797,14 @@ protected:
|
||||
public:
|
||||
|
||||
MyMesh(mesh::Radio& radio, mesh::RNG& rng, mesh::RTCClock& rtc, SimpleMeshTables& tables)
|
||||
: BaseChatMesh(radio, *new ArduinoMillis(), rng, rtc, *new StaticPoolPacketManager(16), tables), _serial(NULL)
|
||||
: BaseChatMesh(radio, *new ArduinoMillis(), rng, rtc, *new StaticPoolPacketManager(16), tables), _serial(NULL),
|
||||
telemetry(MAX_PACKET_PAYLOAD - 4)
|
||||
{
|
||||
_iter_started = false;
|
||||
offline_queue_len = 0;
|
||||
app_target_ver = 0;
|
||||
_identity_store = NULL;
|
||||
pending_login = pending_status = 0;
|
||||
pending_login = pending_status = pending_telemetry = 0;
|
||||
next_ack_idx = 0;
|
||||
sign_data = NULL;
|
||||
|
||||
@@ -729,23 +821,29 @@ public:
|
||||
}
|
||||
|
||||
void loadPrefsInt(const char* filename) {
|
||||
#if defined(RP2040_PLATFORM)
|
||||
File file = _fs->open(filename, "r");
|
||||
#else
|
||||
File file = _fs->open(filename);
|
||||
#endif
|
||||
if (file) {
|
||||
uint8_t pad[8];
|
||||
|
||||
file.read((uint8_t *) &_prefs.airtime_factor, sizeof(float)); // 0
|
||||
file.read((uint8_t *) _prefs.node_name, sizeof(_prefs.node_name)); // 4
|
||||
file.read(pad, 4); // 36
|
||||
file.read((uint8_t *) &_prefs.node_lat, sizeof(_prefs.node_lat)); // 40
|
||||
file.read((uint8_t *) &_prefs.node_lon, sizeof(_prefs.node_lon)); // 48
|
||||
file.read((uint8_t *) &sensors.node_lat, sizeof(sensors.node_lat)); // 40
|
||||
file.read((uint8_t *) &sensors.node_lon, sizeof(sensors.node_lon)); // 48
|
||||
file.read((uint8_t *) &_prefs.freq, sizeof(_prefs.freq)); // 56
|
||||
file.read((uint8_t *) &_prefs.sf, sizeof(_prefs.sf)); // 60
|
||||
file.read((uint8_t *) &_prefs.cr, sizeof(_prefs.cr)); // 61
|
||||
file.read((uint8_t *) &_prefs.reserved1, sizeof(_prefs.reserved1)); // 62
|
||||
file.read((uint8_t *) &_prefs.reserved2, sizeof(_prefs.reserved2)); // 63
|
||||
file.read((uint8_t *) &_prefs.manual_add_contacts, sizeof(_prefs.manual_add_contacts)); // 63
|
||||
file.read((uint8_t *) &_prefs.bw, sizeof(_prefs.bw)); // 64
|
||||
file.read((uint8_t *) &_prefs.tx_power_dbm, sizeof(_prefs.tx_power_dbm)); // 68
|
||||
file.read((uint8_t *) _prefs.unused, sizeof(_prefs.unused)); // 69
|
||||
file.read((uint8_t *) &_prefs.telemetry_mode_base, sizeof(_prefs.telemetry_mode_base)); // 69
|
||||
file.read((uint8_t *) &_prefs.telemetry_mode_loc, sizeof(_prefs.telemetry_mode_loc)); // 70
|
||||
file.read(pad, 1); // 71
|
||||
file.read((uint8_t *) &_prefs.rx_delay_base, sizeof(_prefs.rx_delay_base)); // 72
|
||||
file.read(pad, 4); // 76
|
||||
file.read((uint8_t *) &_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80
|
||||
@@ -770,12 +868,20 @@ public:
|
||||
|
||||
#if defined(NRF52_PLATFORM)
|
||||
_identity_store = new IdentityStore(fs, "");
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
_identity_store = new IdentityStore(fs, "/identity");
|
||||
_identity_store->begin();
|
||||
#else
|
||||
_identity_store = new IdentityStore(fs, "/identity");
|
||||
#endif
|
||||
|
||||
loadMainIdentity();
|
||||
|
||||
// use hex of first 4 bytes of identity public key as default node name
|
||||
char pub_key_hex[10];
|
||||
mesh::Utils::toHex(pub_key_hex, self_id.pub_key, 4);
|
||||
strcpy(_prefs.node_name, pub_key_hex);
|
||||
|
||||
// load persisted prefs
|
||||
if (_fs->exists("/new_prefs")) {
|
||||
loadPrefsInt("/new_prefs"); // new filename
|
||||
@@ -816,6 +922,9 @@ public:
|
||||
}
|
||||
|
||||
const char* getNodeName() { return _prefs.node_name; }
|
||||
NodePrefs* getNodePrefs() {
|
||||
return &_prefs;
|
||||
}
|
||||
uint32_t getBLEPin() { return _active_ble_pin; }
|
||||
|
||||
void startInterface(BaseSerialInterface& serial) {
|
||||
@@ -827,6 +936,8 @@ public:
|
||||
#if defined(NRF52_PLATFORM)
|
||||
File file = _fs->open("/new_prefs", FILE_O_WRITE);
|
||||
if (file) { file.seek(0); file.truncate(); }
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
File file = _fs->open("/new_prefs", "w");
|
||||
#else
|
||||
File file = _fs->open("/new_prefs", "w", true);
|
||||
#endif
|
||||
@@ -837,16 +948,18 @@ public:
|
||||
file.write((uint8_t *) &_prefs.airtime_factor, sizeof(float)); // 0
|
||||
file.write((uint8_t *) _prefs.node_name, sizeof(_prefs.node_name)); // 4
|
||||
file.write(pad, 4); // 36
|
||||
file.write((uint8_t *) &_prefs.node_lat, sizeof(_prefs.node_lat)); // 40
|
||||
file.write((uint8_t *) &_prefs.node_lon, sizeof(_prefs.node_lon)); // 48
|
||||
file.write((uint8_t *) &sensors.node_lat, sizeof(sensors.node_lat)); // 40
|
||||
file.write((uint8_t *) &sensors.node_lon, sizeof(sensors.node_lon)); // 48
|
||||
file.write((uint8_t *) &_prefs.freq, sizeof(_prefs.freq)); // 56
|
||||
file.write((uint8_t *) &_prefs.sf, sizeof(_prefs.sf)); // 60
|
||||
file.write((uint8_t *) &_prefs.cr, sizeof(_prefs.cr)); // 61
|
||||
file.write((uint8_t *) &_prefs.reserved1, sizeof(_prefs.reserved1)); // 62
|
||||
file.write((uint8_t *) &_prefs.reserved2, sizeof(_prefs.reserved2)); // 63
|
||||
file.write((uint8_t *) &_prefs.manual_add_contacts, sizeof(_prefs.manual_add_contacts)); // 63
|
||||
file.write((uint8_t *) &_prefs.bw, sizeof(_prefs.bw)); // 64
|
||||
file.write((uint8_t *) &_prefs.tx_power_dbm, sizeof(_prefs.tx_power_dbm)); // 68
|
||||
file.write((uint8_t *) _prefs.unused, sizeof(_prefs.unused)); // 69
|
||||
file.write((uint8_t *) &_prefs.telemetry_mode_base, sizeof(_prefs.telemetry_mode_base)); // 69
|
||||
file.write((uint8_t *) &_prefs.telemetry_mode_loc, sizeof(_prefs.telemetry_mode_loc)); // 70
|
||||
file.write(pad, 1); // 71
|
||||
file.write((uint8_t *) &_prefs.rx_delay_base, sizeof(_prefs.rx_delay_base)); // 72
|
||||
file.write(pad, 4); // 76
|
||||
file.write((uint8_t *) &_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80
|
||||
@@ -884,12 +997,15 @@ public:
|
||||
out_frame[i++] = MAX_LORA_TX_POWER;
|
||||
memcpy(&out_frame[i], self_id.pub_key, PUB_KEY_SIZE); i += PUB_KEY_SIZE;
|
||||
|
||||
int32_t lat, lon, alt = 0;
|
||||
lat = (_prefs.node_lat * 1000000.0);
|
||||
lon = (_prefs.node_lon * 1000000.0);
|
||||
int32_t lat, lon;
|
||||
lat = (sensors.node_lat * 1000000.0);
|
||||
lon = (sensors.node_lon * 1000000.0);
|
||||
memcpy(&out_frame[i], &lat, 4); i += 4;
|
||||
memcpy(&out_frame[i], &lon, 4); i += 4;
|
||||
memcpy(&out_frame[i], &alt, 4); i += 4;
|
||||
out_frame[i++] = 0; // reserved
|
||||
out_frame[i++] = 0; // reserved
|
||||
out_frame[i++] = (_prefs.telemetry_mode_loc << 2) | (_prefs.telemetry_mode_base); // v5+
|
||||
out_frame[i++] = _prefs.manual_add_contacts;
|
||||
|
||||
uint32_t freq = _prefs.freq * 1000;
|
||||
memcpy(&out_frame[i], &freq, 4); i += 4;
|
||||
@@ -996,8 +1112,8 @@ public:
|
||||
memcpy(&alt, &cmd_frame[9], 4); // for FUTURE support
|
||||
}
|
||||
if (lat <= 90*1E6 && lat >= -90*1E6 && lon <= 180*1E6 && lon >= -180*1E6) {
|
||||
_prefs.node_lat = ((double)lat) / 1000000.0;
|
||||
_prefs.node_lon = ((double)lon) / 1000000.0;
|
||||
sensors.node_lat = ((double)lat) / 1000000.0;
|
||||
sensors.node_lon = ((double)lon) / 1000000.0;
|
||||
savePrefs();
|
||||
writeOKFrame();
|
||||
} else {
|
||||
@@ -1020,7 +1136,7 @@ public:
|
||||
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
|
||||
}
|
||||
} else if (cmd_frame[0] == CMD_SEND_SELF_ADVERT) {
|
||||
auto pkt = createSelfAdvert(_prefs.node_name, _prefs.node_lat, _prefs.node_lon);
|
||||
auto pkt = createSelfAdvert(_prefs.node_name, sensors.node_lat, sensors.node_lon);
|
||||
if (pkt) {
|
||||
if (len >= 2 && cmd_frame[1] == 1) { // optional param (1 = flood, 0 = zero hop)
|
||||
sendFlood(pkt);
|
||||
@@ -1094,8 +1210,10 @@ public:
|
||||
} else if (cmd_frame[0] == CMD_EXPORT_CONTACT) {
|
||||
if (len < 1 + PUB_KEY_SIZE) {
|
||||
// export SELF
|
||||
auto pkt = createSelfAdvert(_prefs.node_name, _prefs.node_lat, _prefs.node_lon);
|
||||
auto pkt = createSelfAdvert(_prefs.node_name, sensors.node_lat, sensors.node_lon);
|
||||
if (pkt) {
|
||||
pkt->header |= ROUTE_TYPE_FLOOD; // would normally be sent in this mode
|
||||
|
||||
out_frame[0] = RESP_CODE_EXPORT_CONTACT;
|
||||
uint8_t out_len = pkt->writeTo(&out_frame[1]);
|
||||
releasePacket(pkt); // undo the obtainNewPacket()
|
||||
@@ -1173,6 +1291,14 @@ public:
|
||||
_prefs.airtime_factor = ((float)af) / 1000.0f;
|
||||
savePrefs();
|
||||
writeOKFrame();
|
||||
} else if (cmd_frame[0] == CMD_SET_OTHER_PARAMS) {
|
||||
_prefs.manual_add_contacts = cmd_frame[1];
|
||||
if (len >= 3) {
|
||||
_prefs.telemetry_mode_base = cmd_frame[2] & 0x03; // v5+
|
||||
_prefs.telemetry_mode_loc = (cmd_frame[2] >> 2) & 0x03;
|
||||
}
|
||||
savePrefs();
|
||||
writeOKFrame();
|
||||
} else if (cmd_frame[0] == CMD_REBOOT && memcmp(&cmd_frame[1], "reboot", 6) == 0) {
|
||||
board.reboot();
|
||||
} else if (cmd_frame[0] == CMD_GET_BATTERY_VOLTAGE) {
|
||||
@@ -1229,7 +1355,7 @@ public:
|
||||
if (result == MSG_SEND_FAILED) {
|
||||
writeErrFrame(ERR_CODE_TABLE_FULL);
|
||||
} else {
|
||||
pending_status = 0;
|
||||
pending_telemetry = pending_status = 0;
|
||||
memcpy(&pending_login, recipient->id.pub_key, 4); // match this to onContactResponse()
|
||||
out_frame[0] = RESP_CODE_SENT;
|
||||
out_frame[1] = (result == MSG_SEND_SENT_FLOOD) ? 1 : 0;
|
||||
@@ -1244,16 +1370,37 @@ public:
|
||||
uint8_t* pub_key = &cmd_frame[1];
|
||||
ContactInfo* recipient = lookupContactByPubKey(pub_key, PUB_KEY_SIZE);
|
||||
if (recipient) {
|
||||
uint32_t est_timeout;
|
||||
int result = sendStatusRequest(*recipient, est_timeout);
|
||||
uint32_t tag, est_timeout;
|
||||
int result = sendRequest(*recipient, REQ_TYPE_GET_STATUS, tag, est_timeout);
|
||||
if (result == MSG_SEND_FAILED) {
|
||||
writeErrFrame(ERR_CODE_TABLE_FULL);
|
||||
} else {
|
||||
pending_login = 0;
|
||||
memcpy(&pending_status, recipient->id.pub_key, 4); // match this to onContactResponse()
|
||||
pending_telemetry = pending_login = 0;
|
||||
// FUTURE: pending_status = tag; // match this in onContactResponse()
|
||||
memcpy(&pending_status, recipient->id.pub_key, 4); // legacy matching scheme
|
||||
out_frame[0] = RESP_CODE_SENT;
|
||||
out_frame[1] = (result == MSG_SEND_SENT_FLOOD) ? 1 : 0;
|
||||
memcpy(&out_frame[2], &pending_status, 4);
|
||||
memcpy(&out_frame[2], &tag, 4);
|
||||
memcpy(&out_frame[6], &est_timeout, 4);
|
||||
_serial->writeFrame(out_frame, 10);
|
||||
}
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_NOT_FOUND); // contact not found
|
||||
}
|
||||
} else if (cmd_frame[0] == CMD_SEND_TELEMETRY_REQ && len >= 4+PUB_KEY_SIZE) {
|
||||
uint8_t* pub_key = &cmd_frame[4];
|
||||
ContactInfo* recipient = lookupContactByPubKey(pub_key, PUB_KEY_SIZE);
|
||||
if (recipient) {
|
||||
uint32_t tag, est_timeout;
|
||||
int result = sendRequest(*recipient, REQ_TYPE_GET_TELEMETRY_DATA, tag, est_timeout);
|
||||
if (result == MSG_SEND_FAILED) {
|
||||
writeErrFrame(ERR_CODE_TABLE_FULL);
|
||||
} else {
|
||||
pending_status = pending_login = 0;
|
||||
pending_telemetry = tag; // match this in onContactResponse()
|
||||
out_frame[0] = RESP_CODE_SENT;
|
||||
out_frame[1] = (result == MSG_SEND_SENT_FLOOD) ? 1 : 0;
|
||||
memcpy(&out_frame[2], &tag, 4);
|
||||
memcpy(&out_frame[6], &est_timeout, 4);
|
||||
_serial->writeFrame(out_frame, 10);
|
||||
}
|
||||
@@ -1351,9 +1498,45 @@ public:
|
||||
writeErrFrame(ERR_CODE_TABLE_FULL);
|
||||
}
|
||||
} else if (cmd_frame[0] == CMD_SET_DEVICE_PIN && len >= 5) {
|
||||
memcpy(&_prefs.ble_pin, &cmd_frame[1], 4);
|
||||
savePrefs();
|
||||
writeOKFrame();
|
||||
|
||||
// get pin from command frame
|
||||
uint32_t pin;
|
||||
memcpy(&pin, &cmd_frame[1], 4);
|
||||
|
||||
// ensure pin is zero, or a valid 6 digit pin
|
||||
if(pin == 0 || (pin >= 100000 && pin <= 999999)){
|
||||
_prefs.ble_pin = pin;
|
||||
savePrefs();
|
||||
writeOKFrame();
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
|
||||
}
|
||||
|
||||
} else if (cmd_frame[0] == CMD_GET_CUSTOM_VARS) {
|
||||
out_frame[0] = RESP_CODE_CUSTOM_VARS;
|
||||
char* dp = (char *) &out_frame[1];
|
||||
for (int i = 0; i < sensors.getNumSettings() && dp - (char *) &out_frame[1] < 140; i++) {
|
||||
if (i > 0) { *dp++ = ','; }
|
||||
strcpy(dp, sensors.getSettingName(i)); dp = strchr(dp, 0);
|
||||
*dp++ = ':';
|
||||
strcpy(dp, sensors.getSettingValue(i)); dp = strchr(dp, 0);
|
||||
}
|
||||
_serial->writeFrame(out_frame, dp - (char *)out_frame);
|
||||
} else if (cmd_frame[0] == CMD_SET_CUSTOM_VAR && len >= 4) {
|
||||
cmd_frame[len] = 0;
|
||||
char* sp = (char *) &cmd_frame[1];
|
||||
char* np = strchr(sp, ':'); // look for separator char
|
||||
if (np) {
|
||||
*np++ = 0; // modify 'cmd_frame', replace ':' with null
|
||||
bool success = sensors.setSettingValue(sp, np);
|
||||
if (success) {
|
||||
writeOKFrame();
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
|
||||
}
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
|
||||
}
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_UNSUPPORTED_CMD);
|
||||
MESH_DEBUG_PRINTLN("ERROR: unknown command: %02X", cmd_frame[0]);
|
||||
@@ -1404,6 +1587,28 @@ public:
|
||||
#elif defined(BLE_PIN_CODE)
|
||||
#include <helpers/esp32/SerialBLEInterface.h>
|
||||
SerialBLEInterface serial_interface;
|
||||
#elif defined(SERIAL_RX)
|
||||
#include <helpers/ArduinoSerialInterface.h>
|
||||
ArduinoSerialInterface serial_interface;
|
||||
HardwareSerial companion_serial(1);
|
||||
#else
|
||||
#include <helpers/ArduinoSerialInterface.h>
|
||||
ArduinoSerialInterface serial_interface;
|
||||
#endif
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
//#ifdef WIFI_SSID
|
||||
// #include <helpers/rp2040/SerialWifiInterface.h>
|
||||
// SerialWifiInterface serial_interface;
|
||||
// #ifndef TCP_PORT
|
||||
// #define TCP_PORT 5000
|
||||
// #endif
|
||||
// #elif defined(BLE_PIN_CODE)
|
||||
// #include <helpers/rp2040/SerialBLEInterface.h>
|
||||
// SerialBLEInterface serial_interface;
|
||||
#if defined(SERIAL_RX)
|
||||
#include <helpers/ArduinoSerialInterface.h>
|
||||
ArduinoSerialInterface serial_interface;
|
||||
HardwareSerial companion_serial(1);
|
||||
#else
|
||||
#include <helpers/ArduinoSerialInterface.h>
|
||||
ArduinoSerialInterface serial_interface;
|
||||
@@ -1433,19 +1638,22 @@ void setup() {
|
||||
|
||||
board.begin();
|
||||
|
||||
if (!radio_init()) { halt(); }
|
||||
|
||||
fast_rng.begin(radio_get_rng_seed());
|
||||
|
||||
#ifdef HAS_UI
|
||||
DisplayDriver* disp = NULL;
|
||||
#ifdef DISPLAY_CLASS
|
||||
if (display.begin()) {
|
||||
disp = &display;
|
||||
disp->startFrame();
|
||||
disp->print("Please wait...");
|
||||
disp->endFrame();
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
if (!radio_init()) { halt(); }
|
||||
|
||||
fast_rng.begin(radio_get_rng_seed());
|
||||
|
||||
#if defined(NRF52_PLATFORM)
|
||||
InternalFS.begin();
|
||||
the_mesh.begin(InternalFS,
|
||||
@@ -1464,6 +1672,31 @@ void setup() {
|
||||
serial_interface.begin(Serial);
|
||||
#endif
|
||||
the_mesh.startInterface(serial_interface);
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
LittleFS.begin();
|
||||
the_mesh.begin(LittleFS,
|
||||
#ifdef HAS_UI
|
||||
disp != NULL
|
||||
#else
|
||||
false
|
||||
#endif
|
||||
);
|
||||
|
||||
//#ifdef WIFI_SSID
|
||||
// WiFi.begin(WIFI_SSID, WIFI_PWD);
|
||||
// serial_interface.begin(TCP_PORT);
|
||||
// #elif defined(BLE_PIN_CODE)
|
||||
// char dev_name[32+16];
|
||||
// sprintf(dev_name, "%s%s", BLE_NAME_PREFIX, the_mesh.getNodeName());
|
||||
// serial_interface.begin(dev_name, the_mesh.getBLEPin());
|
||||
#if defined(SERIAL_RX)
|
||||
companion_serial.setPins(SERIAL_RX, SERIAL_TX);
|
||||
companion_serial.begin(115200);
|
||||
serial_interface.begin(companion_serial);
|
||||
#else
|
||||
serial_interface.begin(Serial);
|
||||
#endif
|
||||
the_mesh.startInterface(serial_interface);
|
||||
#elif defined(ESP32)
|
||||
SPIFFS.begin(true);
|
||||
the_mesh.begin(SPIFFS,
|
||||
@@ -1481,6 +1714,10 @@ void setup() {
|
||||
char dev_name[32+16];
|
||||
sprintf(dev_name, "%s%s", BLE_NAME_PREFIX, the_mesh.getNodeName());
|
||||
serial_interface.begin(dev_name, the_mesh.getBLEPin());
|
||||
#elif defined(SERIAL_RX)
|
||||
companion_serial.setPins(SERIAL_RX, SERIAL_TX);
|
||||
companion_serial.begin(115200);
|
||||
serial_interface.begin(companion_serial);
|
||||
#else
|
||||
serial_interface.begin(Serial);
|
||||
#endif
|
||||
@@ -1489,11 +1726,14 @@ void setup() {
|
||||
#error "need to define filesystem"
|
||||
#endif
|
||||
|
||||
sensors.begin();
|
||||
|
||||
#ifdef HAS_UI
|
||||
ui_task.begin(disp, the_mesh.getNodeName(), FIRMWARE_BUILD_DATE, the_mesh.getBLEPin());
|
||||
ui_task.begin(disp, the_mesh.getNodePrefs(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION, the_mesh.getBLEPin());
|
||||
#endif
|
||||
}
|
||||
|
||||
void loop() {
|
||||
the_mesh.loop();
|
||||
sensors.loop();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#include "UITask.h"
|
||||
#include <Arduino.h>
|
||||
#include <helpers/CommonCLI.h>
|
||||
|
||||
#define AUTO_OFF_MILLIS 20000 // 20 seconds
|
||||
#define AUTO_OFF_MILLIS 20000 // 20 seconds
|
||||
#define BOOT_SCREEN_MILLIS 4000 // 4 seconds
|
||||
|
||||
// 'meshcore', 128x13px
|
||||
static const uint8_t meshcore_logo [] PROGMEM = {
|
||||
@@ -20,29 +22,62 @@ static const uint8_t meshcore_logo [] PROGMEM = {
|
||||
0xe3, 0xe3, 0x8f, 0xff, 0x1f, 0xfc, 0x3c, 0x0e, 0x1f, 0xf8, 0xff, 0xf8, 0x70, 0x3c, 0x7f, 0xf8,
|
||||
};
|
||||
|
||||
void UITask::begin(const char* node_name, const char* build_date) {
|
||||
void UITask::begin(NodePrefs* node_prefs, const char* build_date, const char* firmware_version) {
|
||||
_prevBtnState = HIGH;
|
||||
_auto_off = millis() + AUTO_OFF_MILLIS;
|
||||
_node_name = node_name;
|
||||
_build_date = build_date;
|
||||
_node_prefs = node_prefs;
|
||||
_display->turnOn();
|
||||
|
||||
// strip off dash and commit hash by changing dash to null terminator
|
||||
// e.g: v1.2.3-abcdef -> v1.2.3
|
||||
char *version = strdup(firmware_version);
|
||||
char *dash = strchr(version, '-');
|
||||
if(dash){
|
||||
*dash = 0;
|
||||
}
|
||||
|
||||
// v1.2.3 (1 Jan 2025)
|
||||
sprintf(_version_info, "%s (%s)", version, build_date);
|
||||
}
|
||||
|
||||
void UITask::renderCurrScreen() {
|
||||
char tmp[80];
|
||||
// render 'home' screen
|
||||
_display->drawXbm(0, 0, meshcore_logo, 128, 13);
|
||||
_display->setCursor(0, 20);
|
||||
_display->setTextSize(1);
|
||||
_display->print(_node_name);
|
||||
if (millis() < BOOT_SCREEN_MILLIS) { // boot screen
|
||||
// meshcore logo
|
||||
_display->setColor(DisplayDriver::BLUE);
|
||||
int logoWidth = 128;
|
||||
_display->drawXbm((_display->width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13);
|
||||
|
||||
sprintf(tmp, "Build: %s", _build_date);
|
||||
_display->setCursor(0, 32);
|
||||
_display->print(tmp);
|
||||
_display->setCursor(0, 43);
|
||||
_display->print("< Repeater >");
|
||||
//_display->printf("freq : %03.2f sf %d\n", _prefs.freq, _prefs.sf);
|
||||
//_display->printf("bw : %03.2f cr %d\n", _prefs.bw, _prefs.cr);
|
||||
// version info
|
||||
_display->setColor(DisplayDriver::LIGHT);
|
||||
_display->setTextSize(1);
|
||||
uint16_t versionWidth = _display->getTextWidth(_version_info);
|
||||
_display->setCursor((_display->width() - versionWidth) / 2, 22);
|
||||
_display->print(_version_info);
|
||||
|
||||
// node type
|
||||
const char* node_type = "< Repeater >";
|
||||
uint16_t typeWidth = _display->getTextWidth(node_type);
|
||||
_display->setCursor((_display->width() - typeWidth) / 2, 35);
|
||||
_display->print(node_type);
|
||||
} else { // home screen
|
||||
// node name
|
||||
_display->setCursor(0, 0);
|
||||
_display->setTextSize(1);
|
||||
_display->setColor(DisplayDriver::GREEN);
|
||||
_display->print(_node_prefs->node_name);
|
||||
|
||||
// freq / sf
|
||||
_display->setCursor(0, 20);
|
||||
_display->setColor(DisplayDriver::YELLOW);
|
||||
sprintf(tmp, "FREQ: %06.3f SF%d", _node_prefs->freq, _node_prefs->sf);
|
||||
_display->print(tmp);
|
||||
|
||||
// bw / cr
|
||||
_display->setCursor(0, 30);
|
||||
sprintf(tmp, "BW: %03.2f CR: %d", _node_prefs->bw, _node_prefs->cr);
|
||||
_display->print(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
void UITask::loop() {
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <helpers/ui/DisplayDriver.h>
|
||||
#include <helpers/CommonCLI.h>
|
||||
|
||||
class UITask {
|
||||
DisplayDriver* _display;
|
||||
unsigned long _next_read, _next_refresh, _auto_off;
|
||||
int _prevBtnState;
|
||||
const char* _node_name;
|
||||
const char* _build_date;
|
||||
NodePrefs* _node_prefs;
|
||||
char _version_info[32];
|
||||
|
||||
void renderCurrScreen();
|
||||
public:
|
||||
UITask(DisplayDriver& display) : _display(&display) { _next_read = _next_refresh = 0; }
|
||||
void begin(const char* node_name, const char* build_date);
|
||||
void begin(NodePrefs* node_prefs, const char* build_date, const char* firmware_version);
|
||||
|
||||
void loop();
|
||||
};
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#if defined(NRF52_PLATFORM)
|
||||
#include <InternalFileSystem.h>
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
#include <LittleFS.h>
|
||||
#elif defined(ESP32)
|
||||
#include <SPIFFS.h>
|
||||
#endif
|
||||
@@ -20,11 +22,11 @@
|
||||
/* ------------------------------ Config -------------------------------- */
|
||||
|
||||
#ifndef FIRMWARE_BUILD_DATE
|
||||
#define FIRMWARE_BUILD_DATE "30 Mar 2025"
|
||||
#define FIRMWARE_BUILD_DATE "9 May 2025"
|
||||
#endif
|
||||
|
||||
#ifndef FIRMWARE_VERSION
|
||||
#define FIRMWARE_VERSION "v1.4.2"
|
||||
#define FIRMWARE_VERSION "v1.6.0"
|
||||
#endif
|
||||
|
||||
#ifndef LORA_FREQ
|
||||
@@ -72,7 +74,9 @@
|
||||
|
||||
/* ------------------------------ Code -------------------------------- */
|
||||
|
||||
#define CMD_GET_STATUS 0x01
|
||||
#define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS
|
||||
#define REQ_TYPE_KEEP_ALIVE 0x02
|
||||
#define REQ_TYPE_GET_TELEMETRY_DATA 0x03
|
||||
|
||||
#define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ
|
||||
|
||||
@@ -103,6 +107,13 @@ struct ClientInfo {
|
||||
|
||||
#define MAX_CLIENTS 4
|
||||
|
||||
struct NeighbourInfo {
|
||||
mesh::Identity id;
|
||||
uint32_t advert_timestamp;
|
||||
uint32_t heard_timestamp;
|
||||
int8_t snr; // multiplied by 4, user should divide to get float value
|
||||
};
|
||||
|
||||
// NOTE: need to space the ACK and the reply text apart (in CLI)
|
||||
#define CLI_REPLY_DELAY_MILLIS 1500
|
||||
|
||||
@@ -114,6 +125,10 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
||||
CommonCLI _cli;
|
||||
uint8_t reply_data[MAX_PACKET_PAYLOAD];
|
||||
ClientInfo known_clients[MAX_CLIENTS];
|
||||
#if MAX_NEIGHBOURS
|
||||
NeighbourInfo neighbours[MAX_NEIGHBOURS];
|
||||
#endif
|
||||
CayenneLPP telemetry;
|
||||
|
||||
ClientInfo* putClient(const mesh::Identity& id) {
|
||||
uint32_t min_time = 0xFFFFFFFF;
|
||||
@@ -133,12 +148,40 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
||||
return oldest;
|
||||
}
|
||||
|
||||
int handleRequest(ClientInfo* sender, uint8_t* payload, size_t payload_len) {
|
||||
uint32_t now = getRTCClock()->getCurrentTimeUnique();
|
||||
memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp
|
||||
void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr) {
|
||||
#if MAX_NEIGHBOURS // check if neighbours enabled
|
||||
// find existing neighbour, else use least recently updated
|
||||
uint32_t oldest_timestamp = 0xFFFFFFFF;
|
||||
NeighbourInfo* neighbour = &neighbours[0];
|
||||
for (int i = 0; i < MAX_NEIGHBOURS; i++) {
|
||||
// if neighbour already known, we should update it
|
||||
if (id.matches(neighbours[i].id)) {
|
||||
neighbour = &neighbours[i];
|
||||
break;
|
||||
}
|
||||
|
||||
// otherwise we should update the least recently updated neighbour
|
||||
if (neighbours[i].heard_timestamp < oldest_timestamp) {
|
||||
neighbour = &neighbours[i];
|
||||
oldest_timestamp = neighbour->heard_timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
// update neighbour info
|
||||
neighbour->id = id;
|
||||
neighbour->advert_timestamp = timestamp;
|
||||
neighbour->heard_timestamp = getRTCClock()->getCurrentTime();
|
||||
neighbour->snr = (int8_t) (snr * 4);
|
||||
#endif
|
||||
}
|
||||
|
||||
int handleRequest(ClientInfo* sender, uint32_t sender_timestamp, uint8_t* payload, size_t payload_len) {
|
||||
// uint32_t now = getRTCClock()->getCurrentTimeUnique();
|
||||
// memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp
|
||||
memcpy(reply_data, &sender_timestamp, 4); // reflect sender_timestamp back in response packet (kind of like a 'tag')
|
||||
|
||||
switch (payload[0]) {
|
||||
case CMD_GET_STATUS: { // guests can also access this now
|
||||
case REQ_TYPE_GET_STATUS: { // guests can also access this now
|
||||
RepeaterStats stats;
|
||||
stats.batt_milli_volts = board.getBattMilliVolts();
|
||||
stats.curr_tx_queue_len = _mgr->getOutboundCount();
|
||||
@@ -161,9 +204,18 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
||||
|
||||
return 4 + sizeof(stats); // reply_len
|
||||
}
|
||||
case REQ_TYPE_GET_TELEMETRY_DATA: {
|
||||
telemetry.reset();
|
||||
telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f);
|
||||
// query other sensors -- target specific
|
||||
sensors.querySensors(sender->is_admin ? 0xFF : 0x00, telemetry);
|
||||
|
||||
uint8_t tlen = telemetry.getSize();
|
||||
memcpy(&reply_data[4], telemetry.getBuffer(), tlen);
|
||||
return 4 + tlen; // reply_len
|
||||
}
|
||||
}
|
||||
// unknown command
|
||||
return 0; // reply_len
|
||||
return 0; // unknown command
|
||||
}
|
||||
|
||||
mesh::Packet* createSelfAdvert() {
|
||||
@@ -180,6 +232,8 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
||||
File openAppend(const char* fname) {
|
||||
#if defined(NRF52_PLATFORM)
|
||||
return _fs->open(fname, FILE_O_WRITE);
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
return _fs->open(fname, "a");
|
||||
#else
|
||||
return _fs->open(fname, "a", true);
|
||||
#endif
|
||||
@@ -357,6 +411,18 @@ protected:
|
||||
}
|
||||
}
|
||||
|
||||
void 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) {
|
||||
AdvertDataParser parser(app_data, app_data_len);
|
||||
if (parser.isValid() && parser.getType() == ADV_TYPE_REPEATER) { // just keep neigbouring Repeaters
|
||||
putNeighbour(id, timestamp, packet->getSNR());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override {
|
||||
int i = matching_peer_indexes[sender_idx];
|
||||
if (i < 0 || i >= MAX_CLIENTS) { // get from our known_clients table (sender SHOULD already be known in this context)
|
||||
@@ -369,7 +435,7 @@ protected:
|
||||
memcpy(×tamp, data, 4);
|
||||
|
||||
if (timestamp > client->last_timestamp) { // prevent replay attacks
|
||||
int reply_len = handleRequest(client, &data[4], len - 4);
|
||||
int reply_len = handleRequest(client, timestamp, &data[4], len - 4);
|
||||
if (reply_len == 0) return; // invalid command
|
||||
|
||||
client->last_timestamp = timestamp;
|
||||
@@ -423,12 +489,14 @@ protected:
|
||||
}
|
||||
|
||||
uint8_t temp[166];
|
||||
const char *command = (const char *) &data[5];
|
||||
char *reply = (char *) &temp[5];
|
||||
if (is_retry) {
|
||||
temp[0] = 0;
|
||||
*reply = 0;
|
||||
} else {
|
||||
_cli.handleCommand(sender_timestamp, (const char *) &data[5], (char *) &temp[5]);
|
||||
_cli.handleCommand(sender_timestamp, command, reply);
|
||||
}
|
||||
int text_len = strlen((char *) &temp[5]);
|
||||
int text_len = strlen(reply);
|
||||
if (text_len > 0) {
|
||||
uint32_t timestamp = getRTCClock()->getCurrentTimeUnique();
|
||||
if (timestamp == sender_timestamp) {
|
||||
@@ -472,12 +540,16 @@ protected:
|
||||
public:
|
||||
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, this, &_prefs, this)
|
||||
_cli(board, rtc, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4)
|
||||
{
|
||||
memset(known_clients, 0, sizeof(known_clients));
|
||||
next_local_advert = next_flood_advert = 0;
|
||||
_logging = false;
|
||||
|
||||
#if MAX_NEIGHBOURS
|
||||
memset(neighbours, 0, sizeof(neighbours));
|
||||
#endif
|
||||
|
||||
// defaults
|
||||
memset(&_prefs, 0, sizeof(_prefs));
|
||||
_prefs.airtime_factor = 1.0; // one half
|
||||
@@ -516,6 +588,9 @@ public:
|
||||
const char* getBuildDate() override { return FIRMWARE_BUILD_DATE; }
|
||||
const char* getRole() override { return FIRMWARE_ROLE; }
|
||||
const char* getNodeName() { return _prefs.node_name; }
|
||||
NodePrefs* getNodePrefs() {
|
||||
return &_prefs;
|
||||
}
|
||||
|
||||
void savePrefs() override {
|
||||
_cli.savePrefs(_fs);
|
||||
@@ -524,10 +599,12 @@ public:
|
||||
bool formatFileSystem() override {
|
||||
#if defined(NRF52_PLATFORM)
|
||||
return InternalFS.format();
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
return LittleFS.format();
|
||||
#elif defined(ESP32)
|
||||
return SPIFFS.format();
|
||||
#else
|
||||
#error "need to implement file system erase"
|
||||
#error "need to implement file system erase"
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
@@ -563,7 +640,11 @@ public:
|
||||
}
|
||||
|
||||
void dumpLogFile() override {
|
||||
#if defined(RP2040_PLATFORM)
|
||||
File f = _fs->open(PACKET_LOG_FILE, "r");
|
||||
#else
|
||||
File f = _fs->open(PACKET_LOG_FILE);
|
||||
#endif
|
||||
if (f) {
|
||||
while (f.available()) {
|
||||
int c = f.read();
|
||||
@@ -578,6 +659,34 @@ public:
|
||||
radio_set_tx_power(power_dbm);
|
||||
}
|
||||
|
||||
void formatNeighborsReply(char *reply) override {
|
||||
char *dp = reply;
|
||||
|
||||
#if MAX_NEIGHBOURS
|
||||
for (int i = 0; i < MAX_NEIGHBOURS && dp - reply < 134; i++) {
|
||||
NeighbourInfo* neighbour = &neighbours[i];
|
||||
if (neighbour->heard_timestamp == 0) continue; // skip empty slots
|
||||
|
||||
// add new line if not first item
|
||||
if (i > 0) *dp++ = '\n';
|
||||
|
||||
char hex[10];
|
||||
// get 4 bytes of neighbour id as hex
|
||||
mesh::Utils::toHex(hex, neighbour->id.pub_key, 4);
|
||||
|
||||
// add next neighbour
|
||||
sprintf(dp, "%s:%d:%d", hex, neighbour->advert_timestamp, neighbour->snr);
|
||||
while (*dp) dp++; // find end of string
|
||||
}
|
||||
#endif
|
||||
if (dp == reply) { // no neighbours, need empty response
|
||||
strcpy(dp, "-none-"); dp += 6;
|
||||
}
|
||||
*dp = 0; // null terminator
|
||||
}
|
||||
|
||||
const uint8_t* getSelfIdPubKey() { return self_id.pub_key; }
|
||||
|
||||
void loop() {
|
||||
mesh::Mesh::loop();
|
||||
|
||||
@@ -616,6 +725,14 @@ void setup() {
|
||||
|
||||
board.begin();
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
if(display.begin()){
|
||||
display.startFrame();
|
||||
display.print("Please wait...");
|
||||
display.endFrame();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!radio_init()) { halt(); }
|
||||
|
||||
fast_rng.begin(radio_get_rng_seed());
|
||||
@@ -629,12 +746,21 @@ void setup() {
|
||||
SPIFFS.begin(true);
|
||||
fs = &SPIFFS;
|
||||
IdentityStore store(SPIFFS, "/identity");
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
LittleFS.begin();
|
||||
fs = &LittleFS;
|
||||
IdentityStore store(LittleFS, "/identity");
|
||||
store.begin();
|
||||
#else
|
||||
#error "need to define filesystem"
|
||||
#endif
|
||||
if (!store.load("_main", the_mesh.self_id)) {
|
||||
MESH_DEBUG_PRINTLN("Generating new keypair");
|
||||
the_mesh.self_id = radio_new_identity(); // create new random identity
|
||||
int count = 0;
|
||||
while (count < 10 && (the_mesh.self_id.pub_key[0] == 0x00 || the_mesh.self_id.pub_key[0] == 0xFF)) { // reserved id hashes
|
||||
the_mesh.self_id = radio_new_identity(); count++;
|
||||
}
|
||||
store.save("_main", the_mesh.self_id);
|
||||
}
|
||||
|
||||
@@ -643,11 +769,12 @@ void setup() {
|
||||
|
||||
command[0] = 0;
|
||||
|
||||
sensors.begin();
|
||||
|
||||
the_mesh.begin(fs);
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
display.begin();
|
||||
ui_task.begin(the_mesh.getNodeName(), FIRMWARE_BUILD_DATE);
|
||||
ui_task.begin(the_mesh.getNodePrefs(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION);
|
||||
#endif
|
||||
|
||||
// send out initial Advertisement to the mesh
|
||||
@@ -680,4 +807,5 @@ void loop() {
|
||||
}
|
||||
|
||||
the_mesh.loop();
|
||||
sensors.loop();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#include "UITask.h"
|
||||
#include <Arduino.h>
|
||||
#include <helpers/CommonCLI.h>
|
||||
|
||||
#define AUTO_OFF_MILLIS 20000 // 20 seconds
|
||||
#define AUTO_OFF_MILLIS 20000 // 20 seconds
|
||||
#define BOOT_SCREEN_MILLIS 4000 // 4 seconds
|
||||
|
||||
// 'meshcore', 128x13px
|
||||
static const uint8_t meshcore_logo [] PROGMEM = {
|
||||
@@ -20,29 +22,62 @@ static const uint8_t meshcore_logo [] PROGMEM = {
|
||||
0xe3, 0xe3, 0x8f, 0xff, 0x1f, 0xfc, 0x3c, 0x0e, 0x1f, 0xf8, 0xff, 0xf8, 0x70, 0x3c, 0x7f, 0xf8,
|
||||
};
|
||||
|
||||
void UITask::begin(const char* node_name, const char* build_date) {
|
||||
void UITask::begin(NodePrefs* node_prefs, const char* build_date, const char* firmware_version) {
|
||||
_prevBtnState = HIGH;
|
||||
_auto_off = millis() + AUTO_OFF_MILLIS;
|
||||
_node_name = node_name;
|
||||
_build_date = build_date;
|
||||
_node_prefs = node_prefs;
|
||||
_display->turnOn();
|
||||
|
||||
// strip off dash and commit hash by changing dash to null terminator
|
||||
// e.g: v1.2.3-abcdef -> v1.2.3
|
||||
char *version = strdup(firmware_version);
|
||||
char *dash = strchr(version, '-');
|
||||
if(dash){
|
||||
*dash = 0;
|
||||
}
|
||||
|
||||
// v1.2.3 (1 Jan 2025)
|
||||
sprintf(_version_info, "%s (%s)", version, build_date);
|
||||
}
|
||||
|
||||
void UITask::renderCurrScreen() {
|
||||
char tmp[80];
|
||||
// render 'home' screen
|
||||
_display->drawXbm(0, 0, meshcore_logo, 128, 13);
|
||||
_display->setCursor(0, 20);
|
||||
_display->setTextSize(1);
|
||||
_display->print(_node_name);
|
||||
if (millis() < BOOT_SCREEN_MILLIS) { // boot screen
|
||||
// meshcore logo
|
||||
_display->setColor(DisplayDriver::BLUE);
|
||||
int logoWidth = 128;
|
||||
_display->drawXbm((_display->width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13);
|
||||
|
||||
sprintf(tmp, "Build: %s", _build_date);
|
||||
_display->setCursor(0, 32);
|
||||
_display->print(tmp);
|
||||
_display->setCursor(0, 43);
|
||||
_display->print("< Room Server >");
|
||||
//_display->printf("freq : %03.2f sf %d\n", _prefs.freq, _prefs.sf);
|
||||
//_display->printf("bw : %03.2f cr %d\n", _prefs.bw, _prefs.cr);
|
||||
// version info
|
||||
_display->setColor(DisplayDriver::LIGHT);
|
||||
_display->setTextSize(1);
|
||||
uint16_t versionWidth = _display->getTextWidth(_version_info);
|
||||
_display->setCursor((_display->width() - versionWidth) / 2, 22);
|
||||
_display->print(_version_info);
|
||||
|
||||
// node type
|
||||
const char* node_type = "< Room Server >";
|
||||
uint16_t typeWidth = _display->getTextWidth(node_type);
|
||||
_display->setCursor((_display->width() - typeWidth) / 2, 35);
|
||||
_display->print(node_type);
|
||||
} else { // home screen
|
||||
// node name
|
||||
_display->setCursor(0, 0);
|
||||
_display->setTextSize(1);
|
||||
_display->setColor(DisplayDriver::GREEN);
|
||||
_display->print(_node_prefs->node_name);
|
||||
|
||||
// freq / sf
|
||||
_display->setCursor(0, 20);
|
||||
_display->setColor(DisplayDriver::YELLOW);
|
||||
sprintf(tmp, "FREQ: %06.3f SF%d", _node_prefs->freq, _node_prefs->sf);
|
||||
_display->print(tmp);
|
||||
|
||||
// bw / cr
|
||||
_display->setCursor(0, 30);
|
||||
sprintf(tmp, "BW: %03.2f CR: %d", _node_prefs->bw, _node_prefs->cr);
|
||||
_display->print(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
void UITask::loop() {
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <helpers/ui/DisplayDriver.h>
|
||||
#include <helpers/CommonCLI.h>
|
||||
|
||||
class UITask {
|
||||
DisplayDriver* _display;
|
||||
unsigned long _next_read, _next_refresh, _auto_off;
|
||||
int _prevBtnState;
|
||||
const char* _node_name;
|
||||
const char* _build_date;
|
||||
NodePrefs* _node_prefs;
|
||||
char _version_info[32];
|
||||
|
||||
void renderCurrScreen();
|
||||
public:
|
||||
UITask(DisplayDriver& display) : _display(&display) { _next_read = _next_refresh = 0; }
|
||||
void begin(const char* node_name, const char* build_date);
|
||||
void begin(NodePrefs* node_prefs, const char* build_date, const char* firmware_version);
|
||||
|
||||
void loop();
|
||||
};
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#if defined(NRF52_PLATFORM)
|
||||
#include <InternalFileSystem.h>
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
#include <LittleFS.h>
|
||||
#elif defined(ESP32)
|
||||
#include <SPIFFS.h>
|
||||
#endif
|
||||
@@ -20,11 +22,11 @@
|
||||
/* ------------------------------ Config -------------------------------- */
|
||||
|
||||
#ifndef FIRMWARE_BUILD_DATE
|
||||
#define FIRMWARE_BUILD_DATE "30 Mar 2025"
|
||||
#define FIRMWARE_BUILD_DATE "9 May 2025"
|
||||
#endif
|
||||
|
||||
#ifndef FIRMWARE_VERSION
|
||||
#define FIRMWARE_VERSION "v1.4.2"
|
||||
#define FIRMWARE_VERSION "v1.6.0"
|
||||
#endif
|
||||
|
||||
#ifndef LORA_FREQ
|
||||
@@ -62,7 +64,7 @@
|
||||
#endif
|
||||
|
||||
#ifndef MAX_UNSYNCED_POSTS
|
||||
#define MAX_UNSYNCED_POSTS 16
|
||||
#define MAX_UNSYNCED_POSTS 32
|
||||
#endif
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
@@ -111,7 +113,7 @@ struct PostInfo {
|
||||
|
||||
#define REPLY_DELAY_MILLIS 1500
|
||||
#define PUSH_NOTIFY_DELAY_MILLIS 2000
|
||||
#define SYNC_PUSH_INTERVAL 2000
|
||||
#define SYNC_PUSH_INTERVAL 1200
|
||||
|
||||
#define PUSH_ACK_TIMEOUT_FLOOD 12000
|
||||
#define PUSH_TIMEOUT_BASE 4000
|
||||
@@ -119,8 +121,9 @@ struct PostInfo {
|
||||
|
||||
#define CLIENT_KEEP_ALIVE_SECS 128
|
||||
|
||||
#define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS
|
||||
#define REQ_TYPE_KEEP_ALIVE 0x02
|
||||
#define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS
|
||||
#define REQ_TYPE_KEEP_ALIVE 0x02
|
||||
#define REQ_TYPE_GET_TELEMETRY_DATA 0x03
|
||||
|
||||
#define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ
|
||||
|
||||
@@ -155,6 +158,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
||||
int next_client_idx; // for round-robin polling
|
||||
int next_post_idx;
|
||||
PostInfo posts[MAX_UNSYNCED_POSTS]; // cyclic queue
|
||||
CayenneLPP telemetry;
|
||||
|
||||
ClientInfo* putClient(const mesh::Identity& id) {
|
||||
for (int i = 0; i < num_clients; i++) {
|
||||
@@ -232,6 +236,17 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t getUnsyncedCount(ClientInfo* client) {
|
||||
uint8_t count = 0;
|
||||
for (int k = 0; k < MAX_UNSYNCED_POSTS; k++) {
|
||||
if (posts[k].post_timestamp > client->sync_since // is new post for this Client?
|
||||
&& !posts[k].author.matches(client->id)) { // don't push posts to the author
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
bool processAck(const uint8_t *data) {
|
||||
for (int i = 0; i < num_clients; i++) {
|
||||
auto client = &known_clients[i];
|
||||
@@ -259,11 +274,58 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
||||
File openAppend(const char* fname) {
|
||||
#if defined(NRF52_PLATFORM)
|
||||
return _fs->open(fname, FILE_O_WRITE);
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
return _fs->open(fname, "a");
|
||||
#else
|
||||
return _fs->open(fname, "a", true);
|
||||
#endif
|
||||
}
|
||||
|
||||
int handleRequest(ClientInfo* sender, uint32_t sender_timestamp, uint8_t* payload, size_t payload_len) {
|
||||
// uint32_t now = getRTCClock()->getCurrentTimeUnique();
|
||||
// memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp
|
||||
memcpy(reply_data, &sender_timestamp, 4); // reflect sender_timestamp back in response packet (kind of like a 'tag')
|
||||
|
||||
switch (payload[0]) {
|
||||
case REQ_TYPE_GET_STATUS: {
|
||||
ServerStats stats;
|
||||
stats.batt_milli_volts = board.getBattMilliVolts();
|
||||
stats.curr_tx_queue_len = _mgr->getOutboundCount();
|
||||
stats.curr_free_queue_len = _mgr->getFreeCount();
|
||||
stats.last_rssi = (int16_t) radio_driver.getLastRSSI();
|
||||
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.n_sent_flood = getNumSentFlood();
|
||||
stats.n_sent_direct = getNumSentDirect();
|
||||
stats.n_recv_flood = getNumRecvFlood();
|
||||
stats.n_recv_direct = getNumRecvDirect();
|
||||
stats.n_full_events = getNumFullEvents();
|
||||
stats.last_snr = (int16_t)(radio_driver.getLastSNR() * 4);
|
||||
stats.n_direct_dups = ((SimpleMeshTables *)getTables())->getNumDirectDups();
|
||||
stats.n_flood_dups = ((SimpleMeshTables *)getTables())->getNumFloodDups();
|
||||
stats.n_posted = _num_posted;
|
||||
stats.n_post_push = _num_post_pushes;
|
||||
|
||||
memcpy(&reply_data[4], &stats, sizeof(stats));
|
||||
return 4 + sizeof(stats);
|
||||
}
|
||||
|
||||
case REQ_TYPE_GET_TELEMETRY_DATA: {
|
||||
telemetry.reset();
|
||||
telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f);
|
||||
// query other sensors -- target specific
|
||||
sensors.querySensors(sender->permission == RoomPermission::ADMIN ? 0xFF : 0x00, telemetry);
|
||||
|
||||
uint8_t tlen = telemetry.getSize();
|
||||
memcpy(&reply_data[4], telemetry.getBuffer(), tlen);
|
||||
return 4 + tlen; // reply_len
|
||||
}
|
||||
}
|
||||
return 0; // unknown command
|
||||
}
|
||||
|
||||
protected:
|
||||
float getAirtimeBudgetFactor() const override {
|
||||
return _prefs.airtime_factor;
|
||||
@@ -398,7 +460,7 @@ protected:
|
||||
reply_data[4] = RESP_SERVER_LOGIN_OK;
|
||||
reply_data[5] = (CLIENT_KEEP_ALIVE_SECS >> 4); // NEW: recommended keep-alive interval (secs / 16)
|
||||
reply_data[6] = (perm == RoomPermission::ADMIN ? 1 : (perm == RoomPermission::GUEST ? 0 : 2));
|
||||
reply_data[7] = 0; // FUTURE: reserved
|
||||
reply_data[7] = getUnsyncedCount(client); // NEW
|
||||
memcpy(&reply_data[8], "OK", 2); // REVISIT: not really needed
|
||||
|
||||
next_push = futureMillis(PUSH_NOTIFY_DELAY_MILLIS); // delay next push, give RESPONSE packet time to arrive first
|
||||
@@ -572,47 +634,26 @@ protected:
|
||||
|
||||
auto reply = createAck(ack_hash);
|
||||
if (reply) {
|
||||
reply->payload[reply->payload_len++] = getUnsyncedCount(client); // NEW: add unsynced counter to end of ACK packet
|
||||
sendDirect(reply, client->out_path, client->out_path_len);
|
||||
}
|
||||
}
|
||||
} else if (data[4] == REQ_TYPE_GET_STATUS) {
|
||||
ServerStats stats;
|
||||
stats.batt_milli_volts = board.getBattMilliVolts();
|
||||
stats.curr_tx_queue_len = _mgr->getOutboundCount();
|
||||
stats.curr_free_queue_len = _mgr->getFreeCount();
|
||||
stats.last_rssi = (int16_t) radio_driver.getLastRSSI();
|
||||
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.n_sent_flood = getNumSentFlood();
|
||||
stats.n_sent_direct = getNumSentDirect();
|
||||
stats.n_recv_flood = getNumRecvFlood();
|
||||
stats.n_recv_direct = getNumRecvDirect();
|
||||
stats.n_full_events = getNumFullEvents();
|
||||
stats.last_snr = (int16_t)(radio_driver.getLastSNR() * 4);
|
||||
stats.n_direct_dups = ((SimpleMeshTables *)getTables())->getNumDirectDups();
|
||||
stats.n_flood_dups = ((SimpleMeshTables *)getTables())->getNumFloodDups();
|
||||
stats.n_posted = _num_posted;
|
||||
stats.n_post_push = _num_post_pushes;
|
||||
|
||||
now = getRTCClock()->getCurrentTimeUnique();
|
||||
memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp
|
||||
memcpy(&reply_data[4], &stats, sizeof(stats));
|
||||
uint8_t reply_len = 4 + sizeof(stats);
|
||||
|
||||
if (packet->isRouteFlood()) {
|
||||
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
|
||||
mesh::Packet* path = createPathReturn(client->id, secret, packet->path, packet->path_len,
|
||||
PAYLOAD_TYPE_RESPONSE, reply_data, reply_len);
|
||||
if (path) sendFlood(path);
|
||||
} else {
|
||||
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len);
|
||||
if (reply) {
|
||||
if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT
|
||||
sendDirect(reply, client->out_path, client->out_path_len);
|
||||
} else {
|
||||
sendFlood(reply);
|
||||
} else {
|
||||
int reply_len = handleRequest(client, sender_timestamp, &data[4], len - 4);
|
||||
if (reply_len > 0) { // valid command
|
||||
if (packet->isRouteFlood()) {
|
||||
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
|
||||
mesh::Packet* path = createPathReturn(client->id, secret, packet->path, packet->path_len,
|
||||
PAYLOAD_TYPE_RESPONSE, reply_data, reply_len);
|
||||
if (path) sendFlood(path);
|
||||
} else {
|
||||
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len);
|
||||
if (reply) {
|
||||
if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT
|
||||
sendDirect(reply, client->out_path, client->out_path_len);
|
||||
} else {
|
||||
sendFlood(reply);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -651,7 +692,7 @@ protected:
|
||||
public:
|
||||
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, this, &_prefs, this)
|
||||
_cli(board, rtc, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4)
|
||||
{
|
||||
next_local_advert = next_flood_advert = 0;
|
||||
_logging = false;
|
||||
@@ -705,6 +746,9 @@ public:
|
||||
const char* getBuildDate() override { return FIRMWARE_BUILD_DATE; }
|
||||
const char* getRole() override { return FIRMWARE_ROLE; }
|
||||
const char* getNodeName() { return _prefs.node_name; }
|
||||
NodePrefs* getNodePrefs() {
|
||||
return &_prefs;
|
||||
}
|
||||
|
||||
void savePrefs() override {
|
||||
_cli.savePrefs(_fs);
|
||||
@@ -713,6 +757,8 @@ public:
|
||||
bool formatFileSystem() override {
|
||||
#if defined(NRF52_PLATFORM)
|
||||
return InternalFS.format();
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
return LittleFS.format();
|
||||
#elif defined(ESP32)
|
||||
return SPIFFS.format();
|
||||
#else
|
||||
@@ -752,7 +798,11 @@ public:
|
||||
}
|
||||
|
||||
void dumpLogFile() override {
|
||||
#if defined(RP2040_PLATFORM)
|
||||
File f = _fs->open(PACKET_LOG_FILE, "r");
|
||||
#else
|
||||
File f = _fs->open(PACKET_LOG_FILE);
|
||||
#endif
|
||||
if (f) {
|
||||
while (f.available()) {
|
||||
int c = f.read();
|
||||
@@ -767,6 +817,12 @@ public:
|
||||
radio_set_tx_power(power_dbm);
|
||||
}
|
||||
|
||||
void formatNeighborsReply(char *reply) override {
|
||||
strcpy(reply, "not supported");
|
||||
}
|
||||
|
||||
const uint8_t* getSelfIdPubKey() { return self_id.pub_key; }
|
||||
|
||||
void loop() {
|
||||
mesh::Mesh::loop();
|
||||
|
||||
@@ -782,6 +838,7 @@ public:
|
||||
}
|
||||
// check next Round-Robin client, and sync next new post
|
||||
auto client = &known_clients[next_client_idx];
|
||||
bool did_push = false;
|
||||
if (client->pending_ack == 0 && client->last_activity != 0 && client->push_failures < 3) { // not already waiting for ACK, AND not evicted, AND retries not max
|
||||
MESH_DEBUG_PRINTLN("loop - checking for client %02X", (uint32_t) client->id.pub_key[0]);
|
||||
for (int k = 0, idx = next_post_idx; k < MAX_UNSYNCED_POSTS; k++) {
|
||||
@@ -789,6 +846,7 @@ public:
|
||||
&& !posts[idx].author.matches(client->id)) { // don't push posts to the author
|
||||
// push this post to Client, then wait for ACK
|
||||
pushPostToClient(client, posts[idx]);
|
||||
did_push = true;
|
||||
MESH_DEBUG_PRINTLN("loop - pushed to client %02X: %s", (uint32_t) client->id.pub_key[0], posts[idx].text);
|
||||
break;
|
||||
}
|
||||
@@ -799,7 +857,12 @@ public:
|
||||
}
|
||||
next_client_idx = (next_client_idx + 1) % num_clients; // round robin polling for each client
|
||||
|
||||
next_push = futureMillis(SYNC_PUSH_INTERVAL);
|
||||
if (did_push) {
|
||||
next_push = futureMillis(SYNC_PUSH_INTERVAL);
|
||||
} else {
|
||||
// were no unsynced posts for curr client, so proccess next client much quicker! (in next loop())
|
||||
next_push = futureMillis(SYNC_PUSH_INTERVAL / 8);
|
||||
}
|
||||
}
|
||||
|
||||
if (next_flood_advert && millisHasNowPassed(next_flood_advert)) {
|
||||
@@ -839,6 +902,14 @@ void setup() {
|
||||
|
||||
board.begin();
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
if(display.begin()){
|
||||
display.startFrame();
|
||||
display.print("Please wait...");
|
||||
display.endFrame();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!radio_init()) { halt(); }
|
||||
|
||||
fast_rng.begin(radio_get_rng_seed());
|
||||
@@ -848,6 +919,11 @@ void setup() {
|
||||
InternalFS.begin();
|
||||
fs = &InternalFS;
|
||||
IdentityStore store(InternalFS, "");
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
LittleFS.begin();
|
||||
fs = &LittleFS;
|
||||
IdentityStore store(LittleFS, "/identity");
|
||||
store.begin();
|
||||
#elif defined(ESP32)
|
||||
SPIFFS.begin(true);
|
||||
fs = &SPIFFS;
|
||||
@@ -857,6 +933,10 @@ void setup() {
|
||||
#endif
|
||||
if (!store.load("_main", the_mesh.self_id)) {
|
||||
the_mesh.self_id = radio_new_identity(); // create new random identity
|
||||
int count = 0;
|
||||
while (count < 10 && (the_mesh.self_id.pub_key[0] == 0x00 || the_mesh.self_id.pub_key[0] == 0xFF)) { // reserved id hashes
|
||||
the_mesh.self_id = radio_new_identity(); count++;
|
||||
}
|
||||
store.save("_main", the_mesh.self_id);
|
||||
}
|
||||
|
||||
@@ -865,11 +945,12 @@ void setup() {
|
||||
|
||||
command[0] = 0;
|
||||
|
||||
sensors.begin();
|
||||
|
||||
the_mesh.begin(fs);
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
display.begin();
|
||||
ui_task.begin(the_mesh.getNodeName(), FIRMWARE_BUILD_DATE);
|
||||
ui_task.begin(the_mesh.getNodePrefs(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION);
|
||||
#endif
|
||||
|
||||
// send out initial Advertisement to the mesh
|
||||
@@ -902,4 +983,5 @@ void loop() {
|
||||
}
|
||||
|
||||
the_mesh.loop();
|
||||
sensors.loop();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#if defined(NRF52_PLATFORM)
|
||||
#include <InternalFileSystem.h>
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
#include <LittleFS.h>
|
||||
#elif defined(ESP32)
|
||||
#include <SPIFFS.h>
|
||||
#endif
|
||||
@@ -88,7 +90,11 @@ class MyMesh : public BaseChatMesh, ContactVisitor {
|
||||
|
||||
void loadContacts() {
|
||||
if (_fs->exists("/contacts")) {
|
||||
#if defined(RP2040_PLATFORM)
|
||||
File file = _fs->open("/contacts", "r");
|
||||
#else
|
||||
File file = _fs->open("/contacts");
|
||||
#endif
|
||||
if (file) {
|
||||
bool full = false;
|
||||
while (!full) {
|
||||
@@ -123,6 +129,8 @@ class MyMesh : public BaseChatMesh, ContactVisitor {
|
||||
#if defined(NRF52_PLATFORM)
|
||||
File file = _fs->open("/contacts", FILE_O_WRITE);
|
||||
if (file) { file.seek(0); file.truncate(); }
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
File file = _fs->open("/contacts", "w");
|
||||
#else
|
||||
File file = _fs->open("/contacts", "w", true);
|
||||
#endif
|
||||
@@ -246,6 +254,10 @@ protected:
|
||||
Serial.printf(" %s\n", text);
|
||||
}
|
||||
|
||||
uint8_t onContactRequest(const ContactInfo& contact, uint32_t sender_timestamp, const uint8_t* data, uint8_t len, uint8_t* reply) override {
|
||||
return 0; // unknown
|
||||
}
|
||||
|
||||
void onContactResponse(const ContactInfo& contact, const uint8_t* data, uint8_t len) override {
|
||||
// not supported
|
||||
}
|
||||
@@ -287,6 +299,9 @@ public:
|
||||
|
||||
#if defined(NRF52_PLATFORM)
|
||||
IdentityStore store(fs, "");
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
IdentityStore store(fs, "/identity");
|
||||
store.begin();
|
||||
#else
|
||||
IdentityStore store(fs, "/identity");
|
||||
#endif
|
||||
@@ -300,12 +315,20 @@ public:
|
||||
((StdRNG *)getRNG())->begin(millis());
|
||||
|
||||
self_id = mesh::LocalIdentity(getRNG()); // create new random identity
|
||||
int count = 0;
|
||||
while (count < 10 && (self_id.pub_key[0] == 0x00 || self_id.pub_key[0] == 0xFF)) { // reserved id hashes
|
||||
self_id = mesh::LocalIdentity(getRNG()); count++;
|
||||
}
|
||||
store.save("_main", self_id);
|
||||
}
|
||||
|
||||
// load persisted prefs
|
||||
if (_fs->exists("/node_prefs")) {
|
||||
#if defined(RP2040_PLATFORM)
|
||||
File file = _fs->open("/node_prefs", "r");
|
||||
#else
|
||||
File file = _fs->open("/node_prefs");
|
||||
#endif
|
||||
if (file) {
|
||||
file.read((uint8_t *) &_prefs, sizeof(_prefs));
|
||||
file.close();
|
||||
@@ -320,6 +343,8 @@ public:
|
||||
#if defined(NRF52_PLATFORM)
|
||||
File file = _fs->open("/node_prefs", FILE_O_WRITE);
|
||||
if (file) { file.seek(0); file.truncate(); }
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
File file = _fs->open("/node_prefs", "w");
|
||||
#else
|
||||
File file = _fs->open("/node_prefs", "w", true);
|
||||
#endif
|
||||
@@ -541,6 +566,9 @@ void setup() {
|
||||
#if defined(NRF52_PLATFORM)
|
||||
InternalFS.begin();
|
||||
the_mesh.begin(InternalFS);
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
LittleFS.begin();
|
||||
the_mesh.begin(LittleFS);
|
||||
#elif defined(ESP32)
|
||||
SPIFFS.begin(true);
|
||||
the_mesh.begin(SPIFFS);
|
||||
|
||||
@@ -22,10 +22,11 @@ lib_deps =
|
||||
rweather/Crypto @ ^0.4.0
|
||||
adafruit/RTClib @ ^2.1.3
|
||||
melopero/Melopero RV3028 @ ^1.1.0
|
||||
electroniccats/CayenneLPP @ 1.4.0
|
||||
build_flags = -w -DNDEBUG -DRADIOLIB_STATIC_ONLY=1 -DRADIOLIB_GODMODE=1
|
||||
-D LORA_FREQ=869.525
|
||||
-D LORA_BW=250
|
||||
-D LORA_SF=10
|
||||
-D LORA_SF=11
|
||||
build_src_filter =
|
||||
+<*.cpp>
|
||||
+<helpers/*.cpp>
|
||||
@@ -47,6 +48,7 @@ lib_deps =
|
||||
file://arch/esp32/AsyncElegantOTA
|
||||
|
||||
; ----------------- NRF52 ---------------------
|
||||
|
||||
[nrf52_base]
|
||||
extends = arduino_base
|
||||
platform = nordicnrf52
|
||||
@@ -60,3 +62,10 @@ lib_deps =
|
||||
${nrf52_base.lib_deps}
|
||||
rweather/Crypto @ ^0.4.0
|
||||
https://github.com/adafruit/Adafruit_nRF52_Arduino
|
||||
|
||||
; ----------------- RP2040 ---------------------
|
||||
|
||||
[rp2040_base]
|
||||
extends = arduino_base
|
||||
build_flags = ${arduino_base.build_flags}
|
||||
-D RP2040_PLATFORM
|
||||
|
||||
@@ -101,6 +101,12 @@ void Dispatcher::checkRecv() {
|
||||
#endif
|
||||
|
||||
pkt->header = raw[i++];
|
||||
if (pkt->hasTransportCodes()) {
|
||||
memcpy(&pkt->transport_codes[0], &raw[i], 2); i += 2;
|
||||
memcpy(&pkt->transport_codes[1], &raw[i], 2); i += 2;
|
||||
} else {
|
||||
pkt->transport_codes[0] = pkt->transport_codes[1] = 0;
|
||||
}
|
||||
pkt->path_len = raw[i++];
|
||||
|
||||
if (pkt->path_len > MAX_PATH_SIZE || i + pkt->path_len > len) {
|
||||
@@ -132,7 +138,7 @@ void Dispatcher::checkRecv() {
|
||||
#if MESH_PACKET_LOGGING
|
||||
Serial.print(getLogDateTime());
|
||||
Serial.printf(": RX, len=%d (type=%d, route=%s, payload_len=%d) SNR=%d RSSI=%d score=%d",
|
||||
2 + pkt->path_len + pkt->payload_len, pkt->getPayloadType(), pkt->isRouteDirect() ? "D" : "F", pkt->payload_len,
|
||||
pkt->getRawLength(), pkt->getPayloadType(), pkt->isRouteDirect() ? "D" : "F", pkt->payload_len,
|
||||
(int)pkt->getSNR(), (int)_radio->getLastRSSI(), (int)(score*1000));
|
||||
|
||||
static uint8_t packet_hash[MAX_HASH_SIZE];
|
||||
@@ -147,7 +153,7 @@ void Dispatcher::checkRecv() {
|
||||
Serial.printf("\n");
|
||||
}
|
||||
#endif
|
||||
logRx(pkt, 2 + pkt->path_len + pkt->payload_len, score); // hook for custom logging
|
||||
logRx(pkt, pkt->getRawLength(), score); // hook for custom logging
|
||||
|
||||
if (pkt->isRouteFlood()) {
|
||||
n_recv_flood++;
|
||||
@@ -212,6 +218,10 @@ void Dispatcher::checkSend() {
|
||||
raw[len++] = NODE_ID;
|
||||
#endif
|
||||
raw[len++] = outbound->header;
|
||||
if (outbound->hasTransportCodes()) {
|
||||
memcpy(&raw[len], &outbound->transport_codes[0], 2); len += 2;
|
||||
memcpy(&raw[len], &outbound->transport_codes[1], 2); len += 2;
|
||||
}
|
||||
raw[len++] = outbound->path_len;
|
||||
memcpy(&raw[len], outbound->path, outbound->path_len); len += outbound->path_len;
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ bool Mesh::allowPacketForward(const mesh::Packet* packet) {
|
||||
return false; // by default, Transport NOT enabled
|
||||
}
|
||||
uint32_t Mesh::getRetransmitDelay(const mesh::Packet* packet) {
|
||||
uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * 52 / 50) / 2;
|
||||
uint32_t t = (_radio->getEstAirtimeFor(packet->getRawLength()) * 52 / 50) / 2;
|
||||
|
||||
return _rng->nextInt(0, 5)*t;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,10 @@ Packet::Packet() {
|
||||
payload_len = 0;
|
||||
}
|
||||
|
||||
int Packet::getRawLength() const {
|
||||
return 2 + path_len + payload_len + (hasTransportCodes() ? 4 : 0);
|
||||
}
|
||||
|
||||
void Packet::calculatePacketHash(uint8_t* hash) const {
|
||||
SHA256 sha;
|
||||
uint8_t t = getPayloadType();
|
||||
@@ -24,6 +28,10 @@ void Packet::calculatePacketHash(uint8_t* hash) const {
|
||||
uint8_t Packet::writeTo(uint8_t dest[]) const {
|
||||
uint8_t i = 0;
|
||||
dest[i++] = header;
|
||||
if (hasTransportCodes()) {
|
||||
memcpy(&dest[i], &transport_codes[0], 2); i += 2;
|
||||
memcpy(&dest[i], &transport_codes[1], 2); i += 2;
|
||||
}
|
||||
dest[i++] = path_len;
|
||||
memcpy(&dest[i], path, path_len); i += path_len;
|
||||
memcpy(&dest[i], payload, payload_len); i += payload_len;
|
||||
@@ -33,11 +41,18 @@ uint8_t Packet::writeTo(uint8_t dest[]) const {
|
||||
bool Packet::readFrom(const uint8_t src[], uint8_t len) {
|
||||
uint8_t i = 0;
|
||||
header = src[i++];
|
||||
if (hasTransportCodes()) {
|
||||
memcpy(&transport_codes[0], &src[i], 2); i += 2;
|
||||
memcpy(&transport_codes[1], &src[i], 2); i += 2;
|
||||
} else {
|
||||
transport_codes[0] = transport_codes[1] = 0;
|
||||
}
|
||||
path_len = src[i++];
|
||||
if (path_len > sizeof(path)) return false; // bad encoding
|
||||
memcpy(path, &src[i], path_len); i += path_len;
|
||||
if (i >= len) return false; // bad encoding
|
||||
payload_len = len - i;
|
||||
if (payload_len > sizeof(payload)) return false; // bad encoding
|
||||
memcpy(payload, &src[i], payload_len); //i += payload_len;
|
||||
return true; // success
|
||||
}
|
||||
|
||||
20
src/Packet.h
20
src/Packet.h
@@ -11,10 +11,10 @@ namespace mesh {
|
||||
#define PH_VER_SHIFT 6
|
||||
#define PH_VER_MASK 0x03 // 2-bits
|
||||
|
||||
#define ROUTE_TYPE_RESERVED1 0x00 // FUTURE
|
||||
#define ROUTE_TYPE_FLOOD 0x01 // flood mode, needs 'path' to be built up (max 64 bytes)
|
||||
#define ROUTE_TYPE_DIRECT 0x02 // direct route, 'path' is supplied
|
||||
#define ROUTE_TYPE_RESERVED2 0x03 // FUTURE
|
||||
#define ROUTE_TYPE_TRANSPORT_FLOOD 0x00 // flood mode + transport codes
|
||||
#define ROUTE_TYPE_FLOOD 0x01 // flood mode, needs 'path' to be built up (max 64 bytes)
|
||||
#define ROUTE_TYPE_DIRECT 0x02 // direct route, 'path' is supplied
|
||||
#define ROUTE_TYPE_TRANSPORT_DIRECT 0x03 // direct route + transport codes
|
||||
|
||||
#define PAYLOAD_TYPE_REQ 0x00 // request (prefixed with dest/src hashes, MAC) (enc data: timestamp, blob)
|
||||
#define PAYLOAD_TYPE_RESPONSE 0x01 // response to REQ or ANON_REQ (prefixed with dest/src hashes, MAC) (enc data: timestamp, blob)
|
||||
@@ -43,6 +43,7 @@ public:
|
||||
|
||||
uint8_t header;
|
||||
uint16_t payload_len, path_len;
|
||||
uint16_t transport_codes[2];
|
||||
uint8_t path[MAX_PATH_SIZE];
|
||||
uint8_t payload[MAX_PACKET_PAYLOAD];
|
||||
int8_t _snr;
|
||||
@@ -58,8 +59,10 @@ public:
|
||||
*/
|
||||
uint8_t getRouteType() const { return header & PH_ROUTE_MASK; }
|
||||
|
||||
bool isRouteFlood() const { return getRouteType() == ROUTE_TYPE_FLOOD; }
|
||||
bool isRouteDirect() const { return getRouteType() == ROUTE_TYPE_DIRECT; }
|
||||
bool isRouteFlood() const { return getRouteType() == ROUTE_TYPE_FLOOD || getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD; }
|
||||
bool isRouteDirect() const { return getRouteType() == ROUTE_TYPE_DIRECT || getRouteType() == ROUTE_TYPE_TRANSPORT_DIRECT; }
|
||||
|
||||
bool hasTransportCodes() const { return getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD || getRouteType() == ROUTE_TYPE_TRANSPORT_DIRECT; }
|
||||
|
||||
/**
|
||||
* \returns one of PAYLOAD_TYPE_ values
|
||||
@@ -76,6 +79,11 @@ public:
|
||||
|
||||
float getSNR() const { return ((float)_snr) / 4.0f; }
|
||||
|
||||
/**
|
||||
* \returns the encoded/wire format length of this packet
|
||||
*/
|
||||
int getRawLength() const;
|
||||
|
||||
/**
|
||||
* \brief save entire packet as a blob
|
||||
* \param dest (OUT) destination buffer (assumed to be MAX_MTU_SIZE)
|
||||
|
||||
@@ -8,30 +8,18 @@ class ArduinoSerialInterface : public BaseSerialInterface {
|
||||
uint8_t _state;
|
||||
uint16_t _frame_len;
|
||||
uint16_t rx_len;
|
||||
#ifdef LILYGO_T3S3
|
||||
HWCDC* _serial;
|
||||
#elif defined(NRF52_PLATFORM)
|
||||
Adafruit_USBD_CDC* _serial;
|
||||
#else
|
||||
HardwareSerial* _serial;
|
||||
#endif
|
||||
Stream* _serial;
|
||||
uint8_t rx_buf[MAX_FRAME_SIZE];
|
||||
|
||||
public:
|
||||
ArduinoSerialInterface() { _isEnabled = false; _state = 0; }
|
||||
|
||||
#ifdef LILYGO_T3S3
|
||||
void begin(HWCDC& serial) { _serial = &serial; }
|
||||
#elif defined(NRF52_PLATFORM)
|
||||
void begin(Adafruit_USBD_CDC& serial) {
|
||||
_serial = &serial;
|
||||
void begin(Stream& serial) {
|
||||
_serial = &serial;
|
||||
#ifdef RAK_4631
|
||||
pinMode(WB_IO2, OUTPUT);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
void begin(HardwareSerial& serial) { _serial = &serial; }
|
||||
#endif
|
||||
|
||||
// BaseSerialInterface methods
|
||||
void enable() override;
|
||||
|
||||
@@ -8,8 +8,12 @@ static bool ds3231_success = false;
|
||||
static Melopero_RV3028 rtc_rv3028;
|
||||
static bool rv3028_success = false;
|
||||
|
||||
static RTC_PCF8563 rtc_8563;
|
||||
static bool rtc_8563_success = false;
|
||||
|
||||
#define DS3231_ADDRESS 0x68
|
||||
#define RV3028_ADDRESS 0x52
|
||||
#define PCF8563_ADDRESS 0x51
|
||||
|
||||
bool AutoDiscoverRTCClock::i2c_probe(TwoWire& wire, uint8_t addr) {
|
||||
wire.beginTransmission(addr);
|
||||
@@ -28,6 +32,9 @@ void AutoDiscoverRTCClock::begin(TwoWire& wire) {
|
||||
rtc_rv3028.set24HourMode(); // Set the device to use the 24hour format (default) instead of the 12 hour format
|
||||
rv3028_success = true;
|
||||
}
|
||||
if(i2c_probe(wire,PCF8563_ADDRESS)){
|
||||
rtc_8563_success = rtc_8563.begin(&wire);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t AutoDiscoverRTCClock::getCurrentTime() {
|
||||
@@ -44,6 +51,9 @@ uint32_t AutoDiscoverRTCClock::getCurrentTime() {
|
||||
rtc_rv3028.getSecond()
|
||||
).unixtime();
|
||||
}
|
||||
if(rtc_8563_success){
|
||||
return rtc_8563.now().unixtime();
|
||||
}
|
||||
return _fallback->getCurrentTime();
|
||||
}
|
||||
|
||||
@@ -52,9 +62,10 @@ void AutoDiscoverRTCClock::setCurrentTime(uint32_t time) {
|
||||
rtc_3231.adjust(DateTime(time));
|
||||
} else if (rv3028_success) {
|
||||
auto dt = DateTime(time);
|
||||
uint8_t weekday = (dt.day() + (uint16_t)((2.6 * dt.month()) - 0.2) - (2 * (dt.year() / 100)) + dt.year() + (uint16_t)(dt.year() / 4) + (uint16_t)(dt.year() / 400)) % 7;
|
||||
|
||||
uint8_t weekday = (dt.day() + (uint16_t)((2.6 * dt.month()) - 0.2) - (2 * (dt.year() / 100)) + dt.year() + (uint16_t)(dt.year() / 4) + (uint16_t)(dt.year() / 400)) % 7;
|
||||
rtc_rv3028.setTime(dt.year(), dt.month(), weekday, dt.day(), dt.hour(), dt.minute(), dt.second());
|
||||
} else if (rtc_8563_success) {
|
||||
rtc_8563.adjust(DateTime(time));
|
||||
} else {
|
||||
_fallback->setCurrentTime(time);
|
||||
}
|
||||
|
||||
@@ -31,8 +31,29 @@ 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);
|
||||
putBlobByKey(id.pub_key, PUB_KEY_SIZE, temp_buf, plen);
|
||||
|
||||
bool is_new = false;
|
||||
if (from == NULL) {
|
||||
if (!isAutoAddEnabled()) {
|
||||
ContactInfo ci;
|
||||
memset(&ci, 0, sizeof(ci));
|
||||
ci.id = id;
|
||||
ci.out_path_len = -1; // initially out_path is unknown
|
||||
StrHelper::strncpy(ci.name, parser.getName(), sizeof(ci.name));
|
||||
ci.type = parser.getType();
|
||||
if (parser.hasLatLon()) {
|
||||
ci.gps_lat = parser.getIntLat();
|
||||
ci.gps_lon = parser.getIntLon();
|
||||
}
|
||||
ci.last_advert_timestamp = timestamp;
|
||||
ci.lastmod = getRTCClock()->getCurrentTime();
|
||||
onDiscoveredContact(ci, true); // let UI know
|
||||
return;
|
||||
}
|
||||
|
||||
is_new = true;
|
||||
if (num_contacts < MAX_CONTACTS) {
|
||||
from = &contacts[num_contacts++];
|
||||
@@ -50,10 +71,6 @@ 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);
|
||||
putBlobByKey(id.pub_key, PUB_KEY_SIZE, temp_buf, plen);
|
||||
|
||||
// update
|
||||
StrHelper::strncpy(from->name, parser.getName(), sizeof(from->name));
|
||||
from->type = parser.getType();
|
||||
@@ -161,6 +178,27 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender
|
||||
} else {
|
||||
MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported message type: %u", (uint32_t) flags);
|
||||
}
|
||||
} else if (type == PAYLOAD_TYPE_REQ && len > 4) {
|
||||
uint32_t sender_timestamp;
|
||||
memcpy(&sender_timestamp, data, 4);
|
||||
uint8_t reply_len = onContactRequest(from, sender_timestamp, &data[4], len - 4, temp_buf);
|
||||
if (reply_len > 0) {
|
||||
if (packet->isRouteFlood()) {
|
||||
// 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);
|
||||
} 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);
|
||||
} else {
|
||||
sendFlood(reply);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (type == PAYLOAD_TYPE_RESPONSE && len > 0) {
|
||||
onContactResponse(from, data, len);
|
||||
}
|
||||
@@ -252,7 +290,7 @@ int BaseChatMesh::sendMessage(const ContactInfo& recipient, uint32_t timestamp,
|
||||
mesh::Packet* pkt = composeMsgPacket(recipient, timestamp, attempt, text, expected_ack);
|
||||
if (pkt == NULL) return MSG_SEND_FAILED;
|
||||
|
||||
uint32_t t = _radio->getEstAirtimeFor(pkt->payload_len + pkt->path_len + 2);
|
||||
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
|
||||
|
||||
int rc;
|
||||
if (recipient.out_path_len < 0) {
|
||||
@@ -279,7 +317,7 @@ int BaseChatMesh::sendCommandData(const ContactInfo& recipient, uint32_t timest
|
||||
auto pkt = createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.shared_secret, temp, 5 + text_len);
|
||||
if (pkt == NULL) return MSG_SEND_FAILED;
|
||||
|
||||
uint32_t t = _radio->getEstAirtimeFor(pkt->payload_len + pkt->path_len + 2);
|
||||
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
|
||||
int rc;
|
||||
if (recipient.out_path_len < 0) {
|
||||
sendFlood(pkt);
|
||||
@@ -362,7 +400,7 @@ int BaseChatMesh::sendLogin(const ContactInfo& recipient, const char* password,
|
||||
|
||||
auto pkt = createAnonDatagram(PAYLOAD_TYPE_ANON_REQ, self_id, recipient.id, recipient.shared_secret, temp, tlen);
|
||||
if (pkt) {
|
||||
uint32_t t = _radio->getEstAirtimeFor(pkt->payload_len + pkt->path_len + 2);
|
||||
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
|
||||
if (recipient.out_path_len < 0) {
|
||||
sendFlood(pkt);
|
||||
est_timeout = calcFloodTimeoutMillisFor(t);
|
||||
@@ -376,17 +414,17 @@ int BaseChatMesh::sendLogin(const ContactInfo& recipient, const char* password,
|
||||
return MSG_SEND_FAILED;
|
||||
}
|
||||
|
||||
int BaseChatMesh::sendStatusRequest(const ContactInfo& recipient, uint32_t& est_timeout) {
|
||||
int BaseChatMesh::sendRequest(const ContactInfo& recipient, uint8_t req_type, uint32_t& tag, uint32_t& est_timeout) {
|
||||
uint8_t temp[13];
|
||||
uint32_t now = getRTCClock()->getCurrentTimeUnique();
|
||||
memcpy(temp, &now, 4); // mostly an extra blob to help make packet_hash unique
|
||||
temp[4] = REQ_TYPE_GET_STATUS;
|
||||
tag = getRTCClock()->getCurrentTimeUnique();
|
||||
memcpy(temp, &tag, 4); // mostly an extra blob to help make packet_hash unique
|
||||
temp[4] = req_type;
|
||||
memset(&temp[5], 0, 4); // reserved (possibly for 'since' param)
|
||||
getRNG()->random(&temp[9], 4); // random blob to help make packet-hash unique
|
||||
|
||||
auto pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.shared_secret, temp, sizeof(temp));
|
||||
if (pkt) {
|
||||
uint32_t t = _radio->getEstAirtimeFor(pkt->payload_len + pkt->path_len + 2);
|
||||
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
|
||||
if (recipient.out_path_len < 0) {
|
||||
sendFlood(pkt);
|
||||
est_timeout = calcFloodTimeoutMillisFor(t);
|
||||
|
||||
@@ -103,6 +103,7 @@ protected:
|
||||
}
|
||||
|
||||
// 'UI' concepts, for sub-classes to implement
|
||||
virtual bool isAutoAddEnabled() const { return true; }
|
||||
virtual void onDiscoveredContact(ContactInfo& contact, bool is_new) = 0;
|
||||
virtual bool processAck(const uint8_t *data) = 0;
|
||||
virtual void onContactPathUpdated(const ContactInfo& contact) = 0;
|
||||
@@ -113,6 +114,7 @@ protected:
|
||||
virtual uint32_t calcDirectTimeoutMillisFor(uint32_t pkt_airtime_millis, uint8_t path_len) const = 0;
|
||||
virtual void onSendTimeout() = 0;
|
||||
virtual void onChannelMessageRecv(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t timestamp, const char *text) = 0;
|
||||
virtual uint8_t onContactRequest(const ContactInfo& contact, uint32_t sender_timestamp, const uint8_t* data, uint8_t len, uint8_t* reply) = 0;
|
||||
virtual void onContactResponse(const ContactInfo& contact, const uint8_t* data, uint8_t len) = 0;
|
||||
|
||||
// storage concepts, for sub-classes to override/implement
|
||||
@@ -145,7 +147,7 @@ public:
|
||||
int sendCommandData(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char* text, uint32_t& est_timeout);
|
||||
bool sendGroupMessage(uint32_t timestamp, mesh::GroupChannel& channel, const char* sender_name, const char* text, int text_len);
|
||||
int sendLogin(const ContactInfo& recipient, const char* password, uint32_t& est_timeout);
|
||||
int sendStatusRequest(const ContactInfo& recipient, uint32_t& est_timeout);
|
||||
int sendRequest(const ContactInfo& recipient, uint8_t req_type, uint32_t& tag, uint32_t& est_timeout);
|
||||
bool shareContactZeroHop(const ContactInfo& contact);
|
||||
uint8_t exportContact(const ContactInfo& contact, uint8_t dest_buf[]);
|
||||
bool importContact(const uint8_t src_buf[], uint8_t len);
|
||||
|
||||
@@ -24,7 +24,11 @@ void CommonCLI::loadPrefs(FILESYSTEM* fs) {
|
||||
}
|
||||
|
||||
void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
#if defined(RP2040_PLATFORM)
|
||||
File file = fs->open(filename, "r");
|
||||
#else
|
||||
File file = fs->open(filename);
|
||||
#endif
|
||||
if (file) {
|
||||
uint8_t pad[8];
|
||||
|
||||
@@ -72,6 +76,8 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
|
||||
#if defined(NRF52_PLATFORM)
|
||||
File file = fs->open("/com_prefs", FILE_O_WRITE);
|
||||
if (file) { file.seek(0); file.truncate(); }
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
File file = fs->open("/com_prefs", "w");
|
||||
#else
|
||||
File file = fs->open("/com_prefs", "w", true);
|
||||
#endif
|
||||
@@ -155,6 +161,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
} else {
|
||||
strcpy(reply, "(ERR: clock cannot go backwards)");
|
||||
}
|
||||
} else if (memcmp(command, "neighbors", 9) == 0) {
|
||||
_callbacks->formatNeighborsReply(reply);
|
||||
} else if (memcmp(command, "password ", 9) == 0) {
|
||||
// change admin password
|
||||
StrHelper::strncpy(_prefs->password, &command[9], sizeof(_prefs->password));
|
||||
@@ -200,7 +208,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->freq));
|
||||
} else if (memcmp(config, "public.key", 10) == 0) {
|
||||
strcpy(reply, "> ");
|
||||
mesh::Utils::toHex(&reply[2], _mesh->self_id.pub_key, PUB_KEY_SIZE);
|
||||
mesh::Utils::toHex(&reply[2], _callbacks->getSelfIdPubKey(), PUB_KEY_SIZE);
|
||||
} else if (memcmp(config, "role", 4) == 0) {
|
||||
sprintf(reply, "> %s", _callbacks->getRole());
|
||||
} else {
|
||||
|
||||
@@ -40,16 +40,18 @@ public:
|
||||
virtual void eraseLogFile() = 0;
|
||||
virtual void dumpLogFile() = 0;
|
||||
virtual void setTxPower(uint8_t power_dbm) = 0;
|
||||
virtual void formatNeighborsReply(char *reply) = 0;
|
||||
virtual const uint8_t* getSelfIdPubKey() = 0;
|
||||
};
|
||||
|
||||
class CommonCLI {
|
||||
mesh::Mesh* _mesh;
|
||||
mesh::RTCClock* _rtc;
|
||||
NodePrefs* _prefs;
|
||||
CommonCLICallbacks* _callbacks;
|
||||
mesh::MainBoard* _board;
|
||||
char tmp[80];
|
||||
|
||||
mesh::RTCClock* getRTCClock() { return _mesh->getRTCClock(); }
|
||||
mesh::RTCClock* getRTCClock() { return _rtc; }
|
||||
void savePrefs() { _callbacks->savePrefs(); }
|
||||
|
||||
void checkAdvertInterval();
|
||||
@@ -57,8 +59,8 @@ class CommonCLI {
|
||||
void loadPrefsInt(FILESYSTEM* _fs, const char* filename);
|
||||
|
||||
public:
|
||||
CommonCLI(mesh::MainBoard& board, mesh::Mesh* mesh, NodePrefs* prefs, CommonCLICallbacks* callbacks)
|
||||
: _board(&board), _mesh(mesh), _prefs(prefs), _callbacks(callbacks) { }
|
||||
CommonCLI(mesh::MainBoard& board, mesh::RTCClock& rtc, NodePrefs* prefs, CommonCLICallbacks* callbacks)
|
||||
: _board(&board), _rtc(&rtc), _prefs(prefs), _callbacks(callbacks) { }
|
||||
|
||||
void loadPrefs(FILESYSTEM* _fs);
|
||||
void savePrefs(FILESYSTEM* _fs);
|
||||
|
||||
@@ -27,4 +27,5 @@ public:
|
||||
|
||||
float getLastRSSI() const override { return ((CustomLR1110 *)_radio)->getRSSI(); }
|
||||
float getLastSNR() const override { return ((CustomLR1110 *)_radio)->getSNR(); }
|
||||
int16_t setRxBoostedGainMode(bool en) { return ((CustomLR1110 *)_radio)->setRxBoostedGainMode(en); };
|
||||
};
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <AsyncElegantOTA.h>
|
||||
|
||||
#include <SPIFFS.h>
|
||||
|
||||
bool ESP32Board::startOTAUpdate(const char* id, char reply[]) {
|
||||
WiFi.softAP("MeshCore-OTA", NULL);
|
||||
|
||||
@@ -24,6 +26,9 @@ bool ESP32Board::startOTAUpdate(const char* id, char reply[]) {
|
||||
server->on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
|
||||
request->send(200, "text/html", home_buf);
|
||||
});
|
||||
server->on("/log", HTTP_GET, [](AsyncWebServerRequest *request) {
|
||||
request->send(SPIFFS, "/packet_log", "text/plain");
|
||||
});
|
||||
|
||||
AsyncElegantOTA.setID(id_buf);
|
||||
AsyncElegantOTA.begin(server); // Start ElegantOTA
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <helpers/RefCountedDigitalPin.h>
|
||||
|
||||
// LoRa radio module pins for Heltec V3
|
||||
// Also for Heltec Wireless Tracker
|
||||
#define P_LORA_DIO_1 14
|
||||
#define P_LORA_NSS 8
|
||||
#define P_LORA_RESET RADIOLIB_NC
|
||||
@@ -13,11 +15,12 @@
|
||||
|
||||
// built-ins
|
||||
#define PIN_VBAT_READ 1
|
||||
#define PIN_ADC_CTRL 37
|
||||
#ifndef PIN_ADC_CTRL // set in platformio.ini for Heltec Wireless Tracker (2)
|
||||
#define PIN_ADC_CTRL 37
|
||||
#endif
|
||||
#define PIN_ADC_CTRL_ACTIVE LOW
|
||||
#define PIN_ADC_CTRL_INACTIVE HIGH
|
||||
#define PIN_LED_BUILTIN 35
|
||||
#define PIN_VEXT_EN 36
|
||||
//#define PIN_LED_BUILTIN 35
|
||||
|
||||
#include "ESP32Board.h"
|
||||
|
||||
@@ -28,6 +31,10 @@ private:
|
||||
bool adc_active_state;
|
||||
|
||||
public:
|
||||
RefCountedDigitalPin periph_power;
|
||||
|
||||
HeltecV3Board() : periph_power(PIN_VEXT_EN) { }
|
||||
|
||||
void begin() {
|
||||
ESP32Board::begin();
|
||||
|
||||
@@ -38,8 +45,7 @@ public:
|
||||
pinMode(PIN_ADC_CTRL, OUTPUT);
|
||||
digitalWrite(PIN_ADC_CTRL, !adc_active_state); // Initially inactive
|
||||
|
||||
pinMode(PIN_VEXT_EN, OUTPUT);
|
||||
digitalWrite(PIN_VEXT_EN, LOW); // for V3.2 boards
|
||||
periph_power.begin();
|
||||
|
||||
esp_reset_reason_t reason = esp_reset_reason();
|
||||
if (reason == ESP_RST_DEEPSLEEP) {
|
||||
|
||||
@@ -5,7 +5,11 @@ bool IdentityStore::load(const char *name, mesh::LocalIdentity& id) {
|
||||
char filename[40];
|
||||
sprintf(filename, "%s/%s.id", _dir, name);
|
||||
if (_fs->exists(filename)) {
|
||||
#if defined(RP2040_PLATFORM)
|
||||
File file = _fs->open(filename, "r");
|
||||
#else
|
||||
File file = _fs->open(filename);
|
||||
#endif
|
||||
if (file) {
|
||||
loaded = id.readFrom(file);
|
||||
file.close();
|
||||
@@ -19,7 +23,11 @@ bool IdentityStore::load(const char *name, mesh::LocalIdentity& id, char display
|
||||
char filename[40];
|
||||
sprintf(filename, "%s/%s.id", _dir, name);
|
||||
if (_fs->exists(filename)) {
|
||||
#if defined(RP2040_PLATFORM)
|
||||
File file = _fs->open(filename, "r");
|
||||
#else
|
||||
File file = _fs->open(filename);
|
||||
#endif
|
||||
if (file) {
|
||||
loaded = id.readFrom(file);
|
||||
|
||||
@@ -41,6 +49,8 @@ bool IdentityStore::save(const char *name, const mesh::LocalIdentity& id) {
|
||||
#if defined(NRF52_PLATFORM)
|
||||
File file = _fs->open(filename, FILE_O_WRITE);
|
||||
if (file) { file.seek(0); file.truncate(); }
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
File file = _fs->open(filename, "w");
|
||||
#else
|
||||
File file = _fs->open(filename, "w", true);
|
||||
#endif
|
||||
@@ -61,6 +71,8 @@ bool IdentityStore::save(const char *name, const mesh::LocalIdentity& id, const
|
||||
#if defined(NRF52_PLATFORM)
|
||||
File file = _fs->open(filename, FILE_O_WRITE);
|
||||
if (file) { file.seek(0); file.truncate(); }
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
File file = _fs->open(filename, "w");
|
||||
#else
|
||||
File file = _fs->open(filename, "w", true);
|
||||
#endif
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(ESP32)
|
||||
#if defined(ESP32) || defined(RP2040_PLATFORM)
|
||||
#include <FS.h>
|
||||
#define FILESYSTEM fs::FS
|
||||
#elif defined(NRF52_PLATFORM)
|
||||
@@ -18,7 +18,7 @@ class IdentityStore {
|
||||
public:
|
||||
IdentityStore(FILESYSTEM& fs, const char* dir): _fs(&fs), _dir(dir) { }
|
||||
|
||||
void begin() { _fs->mkdir(_dir); }
|
||||
void begin() { if (_dir && _dir[0] == '/') { _fs->mkdir(_dir); } }
|
||||
bool load(const char *name, mesh::LocalIdentity& id);
|
||||
bool load(const char *name, mesh::LocalIdentity& id, char display_name[], int max_name_sz);
|
||||
bool save(const char *name, const mesh::LocalIdentity& id);
|
||||
|
||||
29
src/helpers/RefCountedDigitalPin.h
Normal file
29
src/helpers/RefCountedDigitalPin.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
class RefCountedDigitalPin {
|
||||
uint8_t _pin;
|
||||
int8_t _claims = 0;
|
||||
|
||||
public:
|
||||
RefCountedDigitalPin(uint8_t pin): _pin(pin) { }
|
||||
|
||||
void begin() {
|
||||
pinMode(_pin, OUTPUT);
|
||||
digitalWrite(_pin, LOW); // initial state
|
||||
}
|
||||
|
||||
void claim() {
|
||||
_claims++;
|
||||
if (_claims > 0) {
|
||||
digitalWrite(_pin, HIGH);
|
||||
}
|
||||
}
|
||||
void release() {
|
||||
_claims--;
|
||||
if (_claims == 0) {
|
||||
digitalWrite(_pin, LOW);
|
||||
}
|
||||
}
|
||||
};
|
||||
22
src/helpers/SensorManager.h
Normal file
22
src/helpers/SensorManager.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <CayenneLPP.h>
|
||||
|
||||
#define TELEM_PERM_BASE 0x01 // 'base' permission includes battery
|
||||
#define TELEM_PERM_LOCATION 0x02
|
||||
|
||||
#define TELEM_CHANNEL_SELF 1 // LPP data channel for 'self' device
|
||||
|
||||
class SensorManager {
|
||||
public:
|
||||
double node_lat, node_lon; // modify these, if you want to affect Advert location
|
||||
|
||||
SensorManager() { node_lat = 0; node_lon = 0; }
|
||||
virtual bool begin() { return false; }
|
||||
virtual bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { return false; }
|
||||
virtual void loop() { }
|
||||
virtual int getNumSettings() const { return 0; }
|
||||
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; }
|
||||
};
|
||||
@@ -33,11 +33,11 @@ class TBeamBoard : public ESP32Board {
|
||||
public:
|
||||
void begin() {
|
||||
ESP32Board::begin();
|
||||
|
||||
|
||||
power.setALDO2Voltage(3300);
|
||||
power.enableALDO2();
|
||||
|
||||
pinMode(38,INPUT_PULLUP);
|
||||
pinMode(38, INPUT_PULLUP);
|
||||
|
||||
esp_reset_reason_t reason = esp_reset_reason();
|
||||
if (reason == ESP_RST_DEEPSLEEP) {
|
||||
@@ -75,7 +75,7 @@ public:
|
||||
}
|
||||
|
||||
uint16_t getBattMilliVolts() override {
|
||||
return 0;
|
||||
return power.getBattVoltage();
|
||||
}
|
||||
|
||||
const char* getManufacturerName() const override {
|
||||
|
||||
79
src/helpers/TBeamBoardSX1262.h
Normal file
79
src/helpers/TBeamBoardSX1262.h
Normal file
@@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
#include <Wire.h>
|
||||
#include <Arduino.h>
|
||||
#include "XPowersLib.h"
|
||||
|
||||
#define XPOWERS_CHIP_AXP192
|
||||
|
||||
// LoRa radio module pins for TBeam
|
||||
#define P_LORA_DIO_1 33 // SX1262 IRQ pin
|
||||
#define P_LORA_NSS 18
|
||||
#define P_LORA_RESET 23
|
||||
#define P_LORA_BUSY 32 // SX1262 Busy pin
|
||||
#define P_LORA_SCLK 5
|
||||
#define P_LORA_MISO 19
|
||||
#define P_LORA_MOSI 27
|
||||
|
||||
#include "ESP32Board.h"
|
||||
|
||||
#include <driver/rtc_io.h>
|
||||
|
||||
class TBeamBoardSX1262 : public ESP32Board {
|
||||
XPowersAXP192 power;
|
||||
|
||||
public:
|
||||
void begin() {
|
||||
ESP32Board::begin();
|
||||
|
||||
power.setLDO2Voltage(3300);
|
||||
power.enableLDO2();
|
||||
|
||||
power.enableBattVoltageMeasure();
|
||||
|
||||
pinMode(38, INPUT_PULLUP);
|
||||
|
||||
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 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
|
||||
rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY);
|
||||
rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1);
|
||||
|
||||
rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS);
|
||||
|
||||
if (pin_wake_btn < 0) {
|
||||
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet
|
||||
} else {
|
||||
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn
|
||||
}
|
||||
|
||||
if (secs > 0) {
|
||||
esp_sleep_enable_timer_wakeup(secs * 1000000);
|
||||
}
|
||||
|
||||
// Finally set ESP32 into sleep
|
||||
esp_deep_sleep_start(); // CPU halts here and never returns!
|
||||
}
|
||||
|
||||
uint16_t getBattMilliVolts() override {
|
||||
return power.getBattVoltage();
|
||||
}
|
||||
|
||||
const char* getManufacturerName() const override {
|
||||
return "LilyGo T-Beam SX1262";
|
||||
}
|
||||
};
|
||||
106
src/helpers/TBeamS3SupremeBoard.h
Normal file
106
src/helpers/TBeamS3SupremeBoard.h
Normal file
@@ -0,0 +1,106 @@
|
||||
#pragma once
|
||||
|
||||
#include "ESP32Board.h"
|
||||
#include <driver/rtc_io.h>
|
||||
#include <Wire.h>
|
||||
#include <Arduino.h>
|
||||
#include "XPowersLib.h"
|
||||
|
||||
// LoRa radio module pins for TBeam S3 Supreme
|
||||
#define P_LORA_DIO_1 1 //SX1262 IRQ pin
|
||||
#define P_LORA_NSS 10 //SX1262 SS pin
|
||||
#define P_LORA_RESET 5 //SX1262 Rest pin
|
||||
#define P_LORA_BUSY 4 //SX1262 Busy pin
|
||||
#define P_LORA_SCLK 12 //SX1262 SCLK pin
|
||||
#define P_LORA_MISO 13 //SX1262 MISO pin
|
||||
#define P_LORA_MOSI 11 //SX1262 MOSI pin
|
||||
|
||||
#define PIN_BOARD_SDA 17 //SDA for OLED, BME280, and QMC6310U (0x1C)
|
||||
#define PIN_BOARD_SCL 18 //SCL for OLED, BME280, and QMC6310U (0x1C)
|
||||
|
||||
#define PIN_BOARD_SDA1 42 //SDA for PMU and PFC8563 (RTC)
|
||||
#define PIN_BOARD_SCL1 41 //SCL for PMU and PFC8563 (RTC)
|
||||
#define PIN_PMU_IRQ 40 //IRQ pin for PMU
|
||||
|
||||
#define PIN_USER_BTN 0
|
||||
|
||||
#define P_BOARD_SPI_MOSI 35 //SPI for SD Card and QMI8653 (IMU)
|
||||
#define P_BOARD_SPI_MISO 37 //SPI for SD Card and QMI8653 (IMU)
|
||||
#define P_BOARD_SPI_SCK 36 //SPI for SD Card and QMI8653 (IMU)
|
||||
#define P_BPARD_SPI_CS 47 //SPI for SD Card and QMI8653 (IMU)
|
||||
#define P_BOARD_IMU_CS 34 //Pin for QMI8653 (IMU) CS
|
||||
|
||||
#define P_BOARD_IMU_INT 33 //IMU Int pin
|
||||
#define P_BOARD_RTC_INT 14 //RTC Int pin
|
||||
|
||||
#define P_GPS_RX 9 //GPS RX pin
|
||||
#define P_GPS_TX 8 //GPS TX pin
|
||||
#define P_GPS_WAKE 7 //GPS Wakeup pin
|
||||
#define P_GPS_1PPS 6 //GPS 1PPS pin
|
||||
|
||||
//I2C Wire addresses
|
||||
#define I2C_BME280_ADD 0x76 //BME280 sensor I2C address on Wire
|
||||
#define I2C_OLED_ADD 0x3C //SH1106 OLED I2C address on Wire
|
||||
#define I2C_QMC6310U_ADD 0x1C //QMC6310U mag sensor I2C address on Wire
|
||||
|
||||
//I2C Wire1 addresses
|
||||
#define I2C_RTC_ADD 0x51 //RTC I2C address on Wire1
|
||||
#define I2C_PMU_ADD 0x34 //AXP2101 I2C address on Wire1
|
||||
|
||||
|
||||
|
||||
class TBeamS3SupremeBoard : public ESP32Board {
|
||||
|
||||
public:
|
||||
void begin() {
|
||||
|
||||
bool power_init();
|
||||
|
||||
ESP32Board::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 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
|
||||
rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY);
|
||||
rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1);
|
||||
|
||||
rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS);
|
||||
|
||||
if (pin_wake_btn < 0) {
|
||||
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet
|
||||
} else {
|
||||
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn
|
||||
}
|
||||
|
||||
if (secs > 0) {
|
||||
esp_sleep_enable_timer_wakeup(secs * 1000000);
|
||||
}
|
||||
|
||||
// Finally set ESP32 into sleep
|
||||
esp_deep_sleep_start(); // CPU halts here and never returns!
|
||||
}
|
||||
|
||||
uint16_t getBattMilliVolts() override {
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint16_t getBattPercent();
|
||||
|
||||
const char* getManufacturerName() const override {
|
||||
return "LilyGo T-Beam S3 Supreme SX1262";
|
||||
}
|
||||
};
|
||||
@@ -22,7 +22,7 @@ static void OnDataRecv(const uint8_t *mac, const uint8_t *data, int len) {
|
||||
last_rx_len = len;
|
||||
}
|
||||
|
||||
void ESPNOWRadio::begin() {
|
||||
void ESPNOWRadio::init() {
|
||||
// Set device as a Wi-Fi Station
|
||||
WiFi.mode(WIFI_STA);
|
||||
// Long Range mode
|
||||
@@ -72,6 +72,7 @@ void ESPNOWRadio::startSendRaw(const uint8_t* bytes, int len) {
|
||||
is_send_complete = false;
|
||||
esp_err_t result = esp_now_send(broadcastAddress, bytes, len);
|
||||
if (result == ESP_OK) {
|
||||
n_sent++;
|
||||
ESPNOW_DEBUG_PRINTLN("Send success");
|
||||
} else {
|
||||
last_send_result = result;
|
||||
@@ -94,6 +95,7 @@ int ESPNOWRadio::recvRaw(uint8_t* bytes, int sz) {
|
||||
if (last_rx_len > 0) {
|
||||
memcpy(bytes, rx_buf, last_rx_len);
|
||||
last_rx_len = 0;
|
||||
n_recv++;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ protected:
|
||||
public:
|
||||
ESPNOWRadio() { n_recv = n_sent = 0; }
|
||||
|
||||
void begin() override;
|
||||
void init();
|
||||
int recvRaw(uint8_t* bytes, int sz) override;
|
||||
uint32_t getEstAirtimeFor(int len_bytes) override;
|
||||
void startSendRaw(const uint8_t* bytes, int len) override;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#include <Arduino.h>
|
||||
#include "FaketecBoard.h"
|
||||
#include "PromicroBoard.h"
|
||||
|
||||
#include <bluefruit.h>
|
||||
#include <Wire.h>
|
||||
|
||||
static BLEDfu bledfu;
|
||||
|
||||
void FaketecBoard::begin() {
|
||||
void PromicroBoard::begin() {
|
||||
// for future use, sub-classes SHOULD call this from their begin()
|
||||
startup_reason = BD_STARTUP_NORMAL;
|
||||
btn_prev_state = HIGH;
|
||||
@@ -14,7 +14,7 @@ void FaketecBoard::begin() {
|
||||
pinMode(PIN_VBAT_READ, INPUT);
|
||||
|
||||
#ifdef BUTTON_PIN
|
||||
pinMode(BUTTON_PIN, INPUT);
|
||||
pinMode(BUTTON_PIN, INPUT_PULLUP);
|
||||
#endif
|
||||
|
||||
#if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL)
|
||||
@@ -39,7 +39,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) {
|
||||
MESH_DEBUG_PRINTLN("BLE client disconnected");
|
||||
}
|
||||
|
||||
bool FaketecBoard::startOTAUpdate(const char* id, char reply[]) {
|
||||
bool PromicroBoard::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()
|
||||
@@ -50,7 +50,7 @@ bool FaketecBoard::startOTAUpdate(const char* id, char reply[]) {
|
||||
// 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("Faketec_OTA");
|
||||
Bluefruit.setName("ProMicro_OTA");
|
||||
|
||||
Bluefruit.Periph.setConnectCallback(connect_callback);
|
||||
Bluefruit.Periph.setDisconnectCallback(disconnect_callback);
|
||||
@@ -19,7 +19,7 @@
|
||||
#define PIN_VBAT_READ 17
|
||||
#define ADC_MULTIPLIER (1.815f) // dependent on voltage divider resistors. TODO: more accurate battery tracking
|
||||
|
||||
class FaketecBoard : public mesh::MainBoard {
|
||||
class PromicroBoard : public mesh::MainBoard {
|
||||
protected:
|
||||
uint8_t startup_reason;
|
||||
uint8_t btn_prev_state;
|
||||
@@ -43,7 +43,7 @@ public:
|
||||
}
|
||||
|
||||
const char* getManufacturerName() const override {
|
||||
return "Faketec DIY";
|
||||
return "ProMicro DIY";
|
||||
}
|
||||
|
||||
int buttonStateChanged() {
|
||||
@@ -27,7 +27,7 @@ void RAK4631Board::begin() {
|
||||
#endif
|
||||
|
||||
#if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL)
|
||||
Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL)
|
||||
Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL);
|
||||
#endif
|
||||
|
||||
Wire.begin();
|
||||
|
||||
@@ -18,7 +18,7 @@ void T1000eBoard::begin() {
|
||||
#endif
|
||||
|
||||
#if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL)
|
||||
Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL)
|
||||
Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL);
|
||||
#endif
|
||||
|
||||
Wire.begin();
|
||||
|
||||
@@ -29,9 +29,16 @@ public:
|
||||
|
||||
uint16_t getBattMilliVolts() override {
|
||||
#ifdef BATTERY_PIN
|
||||
#ifdef PIN_3V3_EN
|
||||
digitalWrite(PIN_3V3_EN, HIGH);
|
||||
#endif
|
||||
analogReference(AR_INTERNAL_3_0);
|
||||
analogReadResolution(12);
|
||||
delay(10);
|
||||
float volts = (analogRead(BATTERY_PIN) * ADC_MULTIPLIER * AREF_VOLTAGE) / 4096;
|
||||
#ifdef PIN_3V3_EN
|
||||
digitalWrite(PIN_3V3_EN, LOW);
|
||||
#endif
|
||||
|
||||
analogReference(AR_DEFAULT); // put back to default
|
||||
analogReadResolution(10);
|
||||
|
||||
@@ -27,7 +27,7 @@ void T114Board::begin() {
|
||||
pinMode(PIN_VBAT_READ, INPUT);
|
||||
|
||||
#if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL)
|
||||
Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL)
|
||||
Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL);
|
||||
#endif
|
||||
|
||||
Wire.begin();
|
||||
|
||||
@@ -24,19 +24,6 @@ void TechoBoard::begin() {
|
||||
// for future use, sub-classes SHOULD call this from their begin()
|
||||
startup_reason = BD_STARTUP_NORMAL;
|
||||
|
||||
delay(200);
|
||||
pinMode(PIN_PWR_EN, OUTPUT);
|
||||
digitalWrite(PIN_PWR_EN, HIGH);
|
||||
pinMode(PIN_BUTTON1, INPUT_PULLUP);
|
||||
pinMode(PIN_BUTTON2, INPUT_PULLUP);
|
||||
pinMode(LED_RED, OUTPUT);
|
||||
pinMode(LED_GREEN, OUTPUT);
|
||||
pinMode(LED_BLUE, OUTPUT);
|
||||
delay(200);
|
||||
|
||||
pinMode(PIN_TXCO, OUTPUT);
|
||||
digitalWrite(PIN_TXCO, HIGH);
|
||||
|
||||
Wire.begin();
|
||||
|
||||
pinMode(SX126X_POWER_EN, OUTPUT);
|
||||
|
||||
90
src/helpers/nrf52/ThinkNodeM1Board.cpp
Normal file
90
src/helpers/nrf52/ThinkNodeM1Board.cpp
Normal file
@@ -0,0 +1,90 @@
|
||||
#include <Arduino.h>
|
||||
#include "ThinkNodeM1Board.h"
|
||||
|
||||
#ifdef THINKNODE_M1
|
||||
|
||||
#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 ThinkNodeM1Board::begin() {
|
||||
// for future use, sub-classes SHOULD call this from their begin()
|
||||
startup_reason = BD_STARTUP_NORMAL;
|
||||
|
||||
Wire.begin();
|
||||
|
||||
pinMode(SX126X_POWER_EN, OUTPUT);
|
||||
digitalWrite(SX126X_POWER_EN, HIGH);
|
||||
delay(10); // give sx1262 some time to power up
|
||||
}
|
||||
|
||||
uint16_t ThinkNodeM1Board::getBattMilliVolts() {
|
||||
int adcvalue = 0;
|
||||
|
||||
analogReference(AR_INTERNAL_3_0);
|
||||
analogReadResolution(12);
|
||||
delay(10);
|
||||
|
||||
// ADC range is 0..3000mV and resolution is 12-bit (0..4095)
|
||||
adcvalue = analogRead(PIN_VBAT_READ);
|
||||
// Convert the raw value to compensated mv, taking the resistor-
|
||||
// divider into account (providing the actual LIPO voltage)
|
||||
return (uint16_t)((float)adcvalue * REAL_VBAT_MV_PER_LSB);
|
||||
}
|
||||
|
||||
bool ThinkNodeM1Board::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("THINKNODE_M1_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
|
||||
49
src/helpers/nrf52/ThinkNodeM1Board.h
Normal file
49
src/helpers/nrf52/ThinkNodeM1Board.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include <MeshCore.h>
|
||||
#include <Arduino.h>
|
||||
|
||||
// LoRa radio module pins for Elecrow ThinkNode M1
|
||||
#define P_LORA_DIO_1 20
|
||||
#define P_LORA_NSS 24
|
||||
#define P_LORA_RESET 25
|
||||
#define P_LORA_BUSY 17
|
||||
#define P_LORA_SCLK 19
|
||||
#define P_LORA_MISO 23
|
||||
#define P_LORA_MOSI 22
|
||||
#define SX126X_POWER_EN 37
|
||||
|
||||
#define SX126X_DIO2_AS_RF_SWITCH true
|
||||
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
|
||||
|
||||
// built-ins
|
||||
#define VBAT_MV_PER_LSB (0.73242188F) // 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096
|
||||
|
||||
#define VBAT_DIVIDER (0.5F) // 150K + 150K voltage divider on VBAT
|
||||
#define VBAT_DIVIDER_COMP (2.0F) // Compensation factor for the VBAT divider
|
||||
|
||||
#define PIN_VBAT_READ (4)
|
||||
#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB)
|
||||
|
||||
class ThinkNodeM1Board : public mesh::MainBoard {
|
||||
protected:
|
||||
uint8_t startup_reason;
|
||||
|
||||
public:
|
||||
|
||||
void begin();
|
||||
uint16_t getBattMilliVolts() override;
|
||||
bool startOTAUpdate(const char* id, char reply[]) override;
|
||||
|
||||
uint8_t getStartupReason() const override {
|
||||
return startup_reason;
|
||||
}
|
||||
|
||||
const char* getManufacturerName() const override {
|
||||
return "Elecrow ThinkNode-M1";
|
||||
}
|
||||
|
||||
void reboot() override {
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
};
|
||||
95
src/helpers/nrf52/XiaoNrf52Board.cpp
Normal file
95
src/helpers/nrf52/XiaoNrf52Board.cpp
Normal file
@@ -0,0 +1,95 @@
|
||||
#ifdef XIAO_NRF52
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "XiaoNrf52Board.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 XiaoNrf52Board::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);
|
||||
|
||||
#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 XiaoNrf52Board::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;
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
67
src/helpers/nrf52/XiaoNrf52Board.h
Normal file
67
src/helpers/nrf52/XiaoNrf52Board.h
Normal file
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#include <MeshCore.h>
|
||||
#include <Arduino.h>
|
||||
|
||||
#ifdef XIAO_NRF52
|
||||
|
||||
// LoRa radio module pins for Seeed Xiao-nrf52
|
||||
#ifdef SX1262_XIAO_S3_VARIANT
|
||||
#undef P_LORA_DIO_1
|
||||
#undef P_LORA_BUSY
|
||||
#undef P_LORA_RESET
|
||||
#undef P_LORA_NSS
|
||||
#define P_LORA_DIO_1 D0
|
||||
#define P_LORA_BUSY D1
|
||||
#define P_LORA_RESET D2
|
||||
#define P_LORA_NSS D3
|
||||
#endif
|
||||
//#define SX126X_POWER_EN 37
|
||||
|
||||
|
||||
|
||||
class XiaoNrf52Board : 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
|
||||
}
|
||||
void onAfterTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED off
|
||||
}
|
||||
#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 "Seeed Xiao-nrf52";
|
||||
}
|
||||
|
||||
void reboot() override {
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
|
||||
bool startOTAUpdate(const char* id, char reply[]) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
42
src/helpers/rp2040/PicoWBoard.cpp
Normal file
42
src/helpers/rp2040/PicoWBoard.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
#include <Arduino.h>
|
||||
#include "PicoWBoard.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 PicoWBoard::begin() {
|
||||
// for future use, sub-classes SHOULD call this from their begin()
|
||||
startup_reason = BD_STARTUP_NORMAL;
|
||||
pinMode(PIN_VBAT_READ, INPUT);
|
||||
#ifdef PIN_USER_BTN
|
||||
pinMode(PIN_USER_BTN, INPUT_PULLUP);
|
||||
#endif
|
||||
|
||||
#if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL)
|
||||
Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL);
|
||||
#endif
|
||||
|
||||
Wire.begin();
|
||||
|
||||
//pinMode(SX126X_POWER_EN, OUTPUT);
|
||||
//digitalWrite(SX126X_POWER_EN, HIGH);
|
||||
delay(10); // give sx1262 some time to power up
|
||||
}
|
||||
|
||||
bool PicoWBoard::startOTAUpdate(const char* id, char reply[]) {
|
||||
return false;
|
||||
}
|
||||
64
src/helpers/rp2040/PicoWBoard.h
Normal file
64
src/helpers/rp2040/PicoWBoard.h
Normal file
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include <MeshCore.h>
|
||||
#include <Arduino.h>
|
||||
|
||||
// LoRa radio module pins for PicoW
|
||||
#define P_LORA_DIO_1 20
|
||||
#define P_LORA_NSS 3
|
||||
#define P_LORA_RESET 15
|
||||
#define P_LORA_BUSY 2
|
||||
#define P_LORA_SCLK 10
|
||||
#define P_LORA_MISO 12
|
||||
#define P_LORA_MOSI 11
|
||||
//#define SX126X_POWER_EN ??? // Not Sure
|
||||
|
||||
#define SX126X_DIO2_AS_RF_SWITCH true
|
||||
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
|
||||
|
||||
// built-ins
|
||||
#define PIN_VBAT_READ 26
|
||||
#define ADC_MULTIPLIER (3.1 * 3.3 * 1000) // MT Uses 3.1
|
||||
#define PIN_LED_BUILTIN LED_BUILTIN
|
||||
|
||||
class PicoWBoard : public mesh::MainBoard {
|
||||
protected:
|
||||
uint8_t startup_reason;
|
||||
|
||||
public:
|
||||
void begin();
|
||||
uint8_t getStartupReason() const override { return startup_reason; }
|
||||
|
||||
void onBeforeTransmit() override {
|
||||
digitalWrite(LED_BUILTIN, HIGH); // turn TX LED on
|
||||
}
|
||||
|
||||
void onAfterTransmit() override {
|
||||
digitalWrite(LED_BUILTIN, LOW); // turn TX LED off
|
||||
}
|
||||
|
||||
#define BATTERY_SAMPLES 8
|
||||
|
||||
uint16_t getBattMilliVolts() override {
|
||||
analogReadResolution(12);
|
||||
|
||||
uint32_t raw = 0;
|
||||
for (int i = 0; i < BATTERY_SAMPLES; i++) {
|
||||
raw += analogRead(PIN_VBAT_READ);
|
||||
}
|
||||
raw = raw / BATTERY_SAMPLES;
|
||||
|
||||
return (ADC_MULTIPLIER * raw) / 4096;
|
||||
}
|
||||
|
||||
const char* getManufacturerName() const override {
|
||||
return "Pico W";
|
||||
}
|
||||
|
||||
void reboot() override {
|
||||
//NVIC_SystemReset();
|
||||
rp2040.reboot();
|
||||
}
|
||||
|
||||
bool startOTAUpdate(const char* id, char reply[]) override;
|
||||
};
|
||||
17
src/helpers/sensors/LocationProvider.h
Normal file
17
src/helpers/sensors/LocationProvider.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include "Mesh.h"
|
||||
|
||||
|
||||
class LocationProvider {
|
||||
|
||||
public:
|
||||
virtual long getLatitude() = 0;
|
||||
virtual long getLongitude() = 0;
|
||||
virtual bool isValid() = 0;
|
||||
virtual long getTimestamp() = 0;
|
||||
virtual void reset();
|
||||
virtual void begin();
|
||||
virtual void stop();
|
||||
virtual void loop();
|
||||
};
|
||||
80
src/helpers/sensors/MicroNMEALocationProvider.h
Normal file
80
src/helpers/sensors/MicroNMEALocationProvider.h
Normal file
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
#include "LocationProvider.h"
|
||||
#include <MicroNMEA.h>
|
||||
#include <RTClib.h>
|
||||
|
||||
#ifndef GPS_EN
|
||||
#define GPS_EN (-1)
|
||||
#endif
|
||||
|
||||
#ifndef GPS_RESET
|
||||
#define GPS_RESET (-1)
|
||||
#endif
|
||||
|
||||
#ifndef GPS_RESET_FORCE
|
||||
#define GPS_RESET_FORCE LOW
|
||||
#endif
|
||||
|
||||
class MicroNMEALocationProvider : public LocationProvider {
|
||||
char _nmeaBuffer[100];
|
||||
MicroNMEA nmea;
|
||||
Stream* _gps_serial;
|
||||
int _pin_reset;
|
||||
int _pin_en;
|
||||
|
||||
public :
|
||||
MicroNMEALocationProvider(Stream& ser, 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) {
|
||||
if (_pin_reset != -1) {
|
||||
pinMode(_pin_reset, OUTPUT);
|
||||
digitalWrite(_pin_reset, GPS_RESET_FORCE);
|
||||
}
|
||||
if (_pin_en != -1) {
|
||||
pinMode(_pin_en, OUTPUT);
|
||||
digitalWrite(_pin_en, LOW);
|
||||
}
|
||||
}
|
||||
|
||||
void begin() override {
|
||||
if (_pin_reset != -1) {
|
||||
digitalWrite(_pin_reset, !GPS_RESET_FORCE);
|
||||
}
|
||||
if (_pin_en != -1) {
|
||||
digitalWrite(_pin_en, HIGH);
|
||||
}
|
||||
}
|
||||
|
||||
void reset() override {
|
||||
if (_pin_reset != -1) {
|
||||
digitalWrite(_pin_reset, GPS_RESET_FORCE);
|
||||
delay(100);
|
||||
digitalWrite(_pin_reset, !GPS_RESET_FORCE);
|
||||
}
|
||||
}
|
||||
|
||||
void stop() override {
|
||||
if (_pin_en != -1) {
|
||||
digitalWrite(_pin_en, LOW);
|
||||
}
|
||||
}
|
||||
|
||||
long getLatitude() override { return nmea.getLatitude(); }
|
||||
long getLongitude() override { return nmea.getLongitude(); }
|
||||
bool isValid() override { return nmea.isValid(); }
|
||||
|
||||
long getTimestamp() override {
|
||||
DateTime dt(nmea.getYear(), nmea.getMonth(),nmea.getDay(),nmea.getHour(),nmea.getMinute(),nmea.getSecond());
|
||||
return dt.unixtime();
|
||||
}
|
||||
|
||||
void loop() override {
|
||||
while (_gps_serial->available()) {
|
||||
char c = _gps_serial->read();
|
||||
#ifdef GPS_NMEA_DEBUG
|
||||
Serial.print(c);
|
||||
#endif
|
||||
nmea.process(c);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -7,7 +7,7 @@ class DisplayDriver {
|
||||
protected:
|
||||
DisplayDriver(int w, int h) { _w = w; _h = h; }
|
||||
public:
|
||||
enum Color { DARK, LIGHT };
|
||||
enum Color { DARK=0, LIGHT, RED, GREEN, BLUE, YELLOW, ORANGE }; // on b/w screen, colors will be !=0 synonym of light
|
||||
|
||||
int width() const { return _w; }
|
||||
int height() const { return _h; }
|
||||
@@ -15,6 +15,7 @@ public:
|
||||
virtual bool isOn() = 0;
|
||||
virtual void turnOn() = 0;
|
||||
virtual void turnOff() = 0;
|
||||
virtual void clear() = 0;
|
||||
virtual void startFrame(Color bkg = DARK) = 0;
|
||||
virtual void setTextSize(int sz) = 0;
|
||||
virtual void setColor(Color c) = 0;
|
||||
@@ -23,5 +24,6 @@ public:
|
||||
virtual void fillRect(int x, int y, int w, int h) = 0;
|
||||
virtual void drawRect(int x, int y, int w, int h) = 0;
|
||||
virtual void drawXbm(int x, int y, const uint8_t* bits, int w, int h) = 0;
|
||||
virtual uint16_t getTextWidth(const char* str) = 0;
|
||||
virtual void endFrame() = 0;
|
||||
};
|
||||
|
||||
96
src/helpers/ui/GxEPDDisplay.cpp
Normal file
96
src/helpers/ui/GxEPDDisplay.cpp
Normal file
@@ -0,0 +1,96 @@
|
||||
|
||||
#include "GxEPDDisplay.h"
|
||||
|
||||
#ifndef DISPLAY_ROTATION
|
||||
#define DISPLAY_ROTATION 3
|
||||
#endif
|
||||
|
||||
#ifdef TECHO_ZOOM
|
||||
#define SCALE_X (1.5625f * 1.5f) // 200 / 128 (with 1.5 scale)
|
||||
#define SCALE_Y (1.5625f * 1.5f) // 200 / 128 (with 1.5 scale)
|
||||
#else
|
||||
#define SCALE_X 1.5625f // 200 / 128
|
||||
#define SCALE_Y 1.5625f // 200 / 128
|
||||
#endif
|
||||
|
||||
bool GxEPDDisplay::begin() {
|
||||
display.epd2.selectSPI(SPI1, SPISettings(4000000, MSBFIRST, SPI_MODE0));
|
||||
SPI1.begin();
|
||||
display.init(115200, true, 2, false);
|
||||
display.setRotation(DISPLAY_ROTATION);
|
||||
#ifdef TECHO_ZOOM
|
||||
display.setFont(&FreeMono9pt7b);
|
||||
#endif
|
||||
display.setPartialWindow(0, 0, display.width(), display.height());
|
||||
|
||||
display.fillScreen(GxEPD_WHITE);
|
||||
display.display(true);
|
||||
#if DISP_BACKLIGHT
|
||||
pinMode(DISP_BACKLIGHT, OUTPUT);
|
||||
#endif
|
||||
_init = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void GxEPDDisplay::turnOn() {
|
||||
if (!_init) begin();
|
||||
#if DISP_BACKLIGHT
|
||||
digitalWrite(DISP_BACKLIGHT, HIGH);
|
||||
_isOn = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void GxEPDDisplay::turnOff() {
|
||||
#if DISP_BACKLIGHT
|
||||
digitalWrite(DISP_BACKLIGHT, LOW);
|
||||
#endif
|
||||
_isOn = false;
|
||||
}
|
||||
|
||||
void GxEPDDisplay::clear() {
|
||||
display.fillScreen(GxEPD_WHITE);
|
||||
display.setTextColor(GxEPD_BLACK);
|
||||
}
|
||||
|
||||
void GxEPDDisplay::startFrame(Color bkg) {
|
||||
display.fillScreen(GxEPD_WHITE);
|
||||
}
|
||||
|
||||
void GxEPDDisplay::setTextSize(int sz) {
|
||||
display.setTextSize(sz);
|
||||
}
|
||||
|
||||
void GxEPDDisplay::setColor(Color c) {
|
||||
display.setTextColor(GxEPD_BLACK);
|
||||
}
|
||||
|
||||
void GxEPDDisplay::setCursor(int x, int y) {
|
||||
display.setCursor(x*SCALE_X, (y+10)*SCALE_Y);
|
||||
}
|
||||
|
||||
void GxEPDDisplay::print(const char* str) {
|
||||
display.print(str);
|
||||
}
|
||||
|
||||
void GxEPDDisplay::fillRect(int x, int y, int w, int h) {
|
||||
display.fillRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, GxEPD_BLACK);
|
||||
}
|
||||
|
||||
void GxEPDDisplay::drawRect(int x, int y, int w, int h) {
|
||||
display.drawRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, GxEPD_BLACK);
|
||||
}
|
||||
|
||||
void GxEPDDisplay::drawXbm(int x, int y, const uint8_t* bits, int w, int h) {
|
||||
display.drawBitmap(x*SCALE_X, (y*SCALE_Y) + 10, bits, w, h, GxEPD_BLACK);
|
||||
}
|
||||
|
||||
uint16_t GxEPDDisplay::getTextWidth(const char* str) {
|
||||
int16_t x1, y1;
|
||||
uint16_t w, h;
|
||||
display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h);
|
||||
return w / SCALE_X;
|
||||
}
|
||||
|
||||
void GxEPDDisplay::endFrame() {
|
||||
display.display(true);
|
||||
}
|
||||
51
src/helpers/ui/GxEPDDisplay.h
Normal file
51
src/helpers/ui/GxEPDDisplay.h
Normal file
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include <SPI.h>
|
||||
#include <Wire.h>
|
||||
|
||||
#define ENABLE_GxEPD2_GFX 0
|
||||
|
||||
#include <GxEPD2_BW.h>
|
||||
#include <GxEPD2_3C.h>
|
||||
#include <GxEPD2_4C.h>
|
||||
#include <GxEPD2_7C.h>
|
||||
#include <Fonts/FreeMono9pt7b.h>
|
||||
|
||||
#define GxEPD2_DISPLAY_CLASS GxEPD2_BW
|
||||
#define GxEPD2_DRIVER_CLASS GxEPD2_150_BN // DEPG0150BN 200x200, SSD1681, (FPC8101), TTGO T5 V2.4.1
|
||||
|
||||
#include <epd/GxEPD2_150_BN.h> // 1.54" b/w
|
||||
|
||||
#include "DisplayDriver.h"
|
||||
|
||||
//GxEPD2_BW<GxEPD2_150_BN, 200> display(GxEPD2_150_BN(DISP_CS, DISP_DC, DISP_RST, DISP_BUSY)); // DEPG0150BN 200x200, SSD1681, TTGO T5 V2.4.1
|
||||
|
||||
|
||||
class GxEPDDisplay : public DisplayDriver {
|
||||
|
||||
GxEPD2_BW<GxEPD2_150_BN, 200> display;
|
||||
bool _init = false;
|
||||
bool _isOn = false;
|
||||
|
||||
public:
|
||||
// there is a margin in y...
|
||||
GxEPDDisplay() : DisplayDriver(128, 128), display(GxEPD2_150_BN(DISP_CS, DISP_DC, DISP_RST, DISP_BUSY)) {
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
1176
src/helpers/ui/OLEDDisplay.cpp
Normal file
1176
src/helpers/ui/OLEDDisplay.cpp
Normal file
File diff suppressed because it is too large
Load Diff
395
src/helpers/ui/OLEDDisplay.h
Normal file
395
src/helpers/ui/OLEDDisplay.h
Normal file
@@ -0,0 +1,395 @@
|
||||
/**
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2018 by ThingPulse, Daniel Eichhorn
|
||||
* Copyright (c) 2018 by Fabrice Weinberg
|
||||
* Copyright (c) 2019 by Helmut Tschemernjak - www.radioshuttle.de
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
* ThingPulse invests considerable time and money to develop these open source libraries.
|
||||
* Please support us by buying our products (and not the clones) from
|
||||
* https://thingpulse.com
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef OLEDDISPLAY_h
|
||||
#define OLEDDISPLAY_h
|
||||
|
||||
#include <cstdarg>
|
||||
|
||||
#ifdef ARDUINO
|
||||
#include <Arduino.h>
|
||||
#elif __MBED__
|
||||
#define pgm_read_byte(addr) (*(const unsigned char *)(addr))
|
||||
|
||||
#include <mbed.h>
|
||||
#define delay(x) wait_ms(x)
|
||||
#define yield() void()
|
||||
|
||||
/*
|
||||
* This is a little Arduino String emulation to keep the OLEDDisplay
|
||||
* library code in common between Arduino and mbed-os
|
||||
*/
|
||||
class String {
|
||||
public:
|
||||
String(const char *s) { _str = s; };
|
||||
int length() { return strlen(_str); };
|
||||
const char *c_str() { return _str; };
|
||||
void toCharArray(char *buf, unsigned int bufsize, unsigned int index = 0) const {
|
||||
memcpy(buf, _str + index, std::min(bufsize, strlen(_str)));
|
||||
};
|
||||
private:
|
||||
const char *_str;
|
||||
};
|
||||
|
||||
#else
|
||||
#error "Unkown operating system"
|
||||
#endif
|
||||
|
||||
#include "OLEDDisplayFonts.h"
|
||||
|
||||
//#define DEBUG_OLEDDISPLAY(...) Serial.printf( __VA_ARGS__ )
|
||||
//#define DEBUG_OLEDDISPLAY(...) dprintf("%s", __VA_ARGS__ )
|
||||
|
||||
#ifndef DEBUG_OLEDDISPLAY
|
||||
#define DEBUG_OLEDDISPLAY(...)
|
||||
#endif
|
||||
|
||||
// Use DOUBLE BUFFERING by default
|
||||
#ifndef OLEDDISPLAY_REDUCE_MEMORY
|
||||
#define OLEDDISPLAY_DOUBLE_BUFFER
|
||||
#endif
|
||||
|
||||
// Header Values
|
||||
#define JUMPTABLE_BYTES 4
|
||||
|
||||
#define JUMPTABLE_LSB 1
|
||||
#define JUMPTABLE_SIZE 2
|
||||
#define JUMPTABLE_WIDTH 3
|
||||
#define JUMPTABLE_START 4
|
||||
|
||||
#define WIDTH_POS 0
|
||||
#define HEIGHT_POS 1
|
||||
#define FIRST_CHAR_POS 2
|
||||
#define CHAR_NUM_POS 3
|
||||
|
||||
|
||||
// Display commands
|
||||
#define CHARGEPUMP 0x8D
|
||||
#define COLUMNADDR 0x21
|
||||
#define COMSCANDEC 0xC8
|
||||
#define COMSCANINC 0xC0
|
||||
#define DISPLAYALLON 0xA5
|
||||
#define DISPLAYALLON_RESUME 0xA4
|
||||
#define DISPLAYOFF 0xAE
|
||||
#define DISPLAYON 0xAF
|
||||
#define EXTERNALVCC 0x1
|
||||
#define INVERTDISPLAY 0xA7
|
||||
#define MEMORYMODE 0x20
|
||||
#define NORMALDISPLAY 0xA6
|
||||
#define PAGEADDR 0x22
|
||||
#define SEGREMAP 0xA0
|
||||
#define SETCOMPINS 0xDA
|
||||
#define SETCONTRAST 0x81
|
||||
#define SETDISPLAYCLOCKDIV 0xD5
|
||||
#define SETDISPLAYOFFSET 0xD3
|
||||
#define SETHIGHCOLUMN 0x10
|
||||
#define SETLOWCOLUMN 0x00
|
||||
#define SETMULTIPLEX 0xA8
|
||||
#define SETPRECHARGE 0xD9
|
||||
#define SETSEGMENTREMAP 0xA1
|
||||
#define SETSTARTLINE 0x40
|
||||
#define SETVCOMDETECT 0xDB
|
||||
#define SWITCHCAPVCC 0x2
|
||||
|
||||
#ifndef _swap_int16_t
|
||||
#define _swap_int16_t(a, b) { int16_t t = a; a = b; b = t; }
|
||||
#endif
|
||||
|
||||
enum OLEDDISPLAY_COLOR {
|
||||
BLACK = 0,
|
||||
WHITE = 1,
|
||||
INVERSE = 2
|
||||
};
|
||||
|
||||
enum OLEDDISPLAY_TEXT_ALIGNMENT {
|
||||
TEXT_ALIGN_LEFT = 0,
|
||||
TEXT_ALIGN_RIGHT = 1,
|
||||
TEXT_ALIGN_CENTER = 2,
|
||||
TEXT_ALIGN_CENTER_BOTH = 3
|
||||
};
|
||||
|
||||
|
||||
enum OLEDDISPLAY_GEOMETRY {
|
||||
GEOMETRY_128_64 = 0,
|
||||
GEOMETRY_128_32 = 1,
|
||||
GEOMETRY_64_48 = 2,
|
||||
GEOMETRY_64_32 = 3,
|
||||
GEOMETRY_RAWMODE = 4,
|
||||
GEOMETRY_128_128 = 5
|
||||
};
|
||||
|
||||
enum HW_I2C {
|
||||
I2C_ONE,
|
||||
I2C_TWO
|
||||
};
|
||||
|
||||
typedef char (*FontTableLookupFunction)(const uint8_t ch);
|
||||
char DefaultFontTableLookup(const uint8_t ch);
|
||||
|
||||
|
||||
#ifdef ARDUINO
|
||||
class OLEDDisplay : public Print {
|
||||
#elif __MBED__
|
||||
class OLEDDisplay : public Stream {
|
||||
#else
|
||||
#error "Unkown operating system"
|
||||
#endif
|
||||
|
||||
public:
|
||||
OLEDDisplay();
|
||||
virtual ~OLEDDisplay();
|
||||
|
||||
uint16_t width(void) const { return displayWidth; };
|
||||
uint16_t height(void) const { return displayHeight; };
|
||||
|
||||
// Use this to resume after a deep sleep without resetting the display (what init() would do).
|
||||
// Returns true if connection to the display was established and the buffer allocated, false otherwise.
|
||||
bool allocateBuffer();
|
||||
|
||||
// Allocates the buffer and initializes the driver & display. Resets the display!
|
||||
// Returns false if buffer allocation failed, true otherwise.
|
||||
bool init();
|
||||
|
||||
// Free the memory used by the display
|
||||
void end();
|
||||
|
||||
// Cycle through the initialization
|
||||
void resetDisplay(void);
|
||||
|
||||
/* Drawing functions */
|
||||
// Sets the color of all pixel operations
|
||||
void setColor(OLEDDISPLAY_COLOR color);
|
||||
|
||||
// Returns the current color.
|
||||
OLEDDISPLAY_COLOR getColor();
|
||||
|
||||
// Draw a pixel at given position
|
||||
void setPixel(int16_t x, int16_t y);
|
||||
|
||||
// Draw a pixel at given position and color
|
||||
void setPixelColor(int16_t x, int16_t y, OLEDDISPLAY_COLOR color);
|
||||
|
||||
// Clear a pixel at given position FIXME: INVERSE is untested with this function
|
||||
void clearPixel(int16_t x, int16_t y);
|
||||
|
||||
// Draw a line from position 0 to position 1
|
||||
void drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1);
|
||||
|
||||
// Draw the border of a rectangle at the given location
|
||||
void drawRect(int16_t x, int16_t y, int16_t width, int16_t height);
|
||||
|
||||
// Fill the rectangle
|
||||
void fillRect(int16_t x, int16_t y, int16_t width, int16_t height);
|
||||
|
||||
// Draw the border of a circle
|
||||
void drawCircle(int16_t x, int16_t y, int16_t radius);
|
||||
|
||||
// Draw all Quadrants specified in the quads bit mask
|
||||
void drawCircleQuads(int16_t x0, int16_t y0, int16_t radius, uint8_t quads);
|
||||
|
||||
// Fill circle
|
||||
void fillCircle(int16_t x, int16_t y, int16_t radius);
|
||||
|
||||
// Draw an empty triangle i.e. only the outline
|
||||
void drawTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2);
|
||||
|
||||
// Draw a solid triangle i.e. filled
|
||||
void fillTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2);
|
||||
|
||||
// Draw a line horizontally
|
||||
void drawHorizontalLine(int16_t x, int16_t y, int16_t length);
|
||||
|
||||
// Draw a line vertically
|
||||
void drawVerticalLine(int16_t x, int16_t y, int16_t length);
|
||||
|
||||
// Draws a rounded progress bar with the outer dimensions given by width and height. Progress is
|
||||
// a unsigned byte value between 0 and 100
|
||||
void drawProgressBar(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t progress);
|
||||
|
||||
// Draw a bitmap in the internal image format
|
||||
void drawFastImage(int16_t x, int16_t y, int16_t width, int16_t height, const uint8_t *image);
|
||||
|
||||
// Draw a XBM
|
||||
void drawXbm(int16_t x, int16_t y, int16_t width, int16_t height, const uint8_t *xbm);
|
||||
|
||||
// Draw icon 16x16 xbm format
|
||||
void drawIco16x16(int16_t x, int16_t y, const uint8_t *ico, bool inverse = false);
|
||||
|
||||
/* Text functions */
|
||||
|
||||
// Draws a string at the given location, returns how many chars have been written
|
||||
uint16_t drawString(int16_t x, int16_t y, const String &text);
|
||||
|
||||
// Draws a formatted string (like printf) at the given location
|
||||
void drawStringf(int16_t x, int16_t y, char* buffer, String format, ... );
|
||||
|
||||
// Draws a String with a maximum width at the given location.
|
||||
// If the given String is wider than the specified width
|
||||
// The text will be wrapped to the next line at a space or dash
|
||||
// returns 0 if everything fits on the screen or the numbers of characters in the
|
||||
// first line if not
|
||||
uint16_t drawStringMaxWidth(int16_t x, int16_t y, uint16_t maxLineWidth, const String &text);
|
||||
|
||||
// Returns the width of the const char* with the current
|
||||
// font settings
|
||||
uint16_t getStringWidth(const char* text, uint16_t length, bool utf8 = false);
|
||||
|
||||
// Convencience method for the const char version
|
||||
uint16_t getStringWidth(const String &text);
|
||||
|
||||
// Specifies relative to which anchor point
|
||||
// the text is rendered. Available constants:
|
||||
// TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER_BOTH
|
||||
void setTextAlignment(OLEDDISPLAY_TEXT_ALIGNMENT textAlignment);
|
||||
|
||||
// Sets the current font. Available default fonts
|
||||
// ArialMT_Plain_10, ArialMT_Plain_16, ArialMT_Plain_24
|
||||
void setFont(const uint8_t *fontData);
|
||||
|
||||
// Set the function that will convert utf-8 to font table index
|
||||
void setFontTableLookupFunction(FontTableLookupFunction function);
|
||||
|
||||
/* Display functions */
|
||||
|
||||
// Turn the display on
|
||||
void displayOn(void);
|
||||
|
||||
// Turn the display offs
|
||||
void displayOff(void);
|
||||
|
||||
// Inverted display mode
|
||||
void invertDisplay(void);
|
||||
|
||||
// Normal display mode
|
||||
void normalDisplay(void);
|
||||
|
||||
// Set display contrast
|
||||
// really low brightness & contrast: contrast = 10, precharge = 5, comdetect = 0
|
||||
// normal brightness & contrast: contrast = 100
|
||||
void setContrast(uint8_t contrast, uint8_t precharge = 241, uint8_t comdetect = 64);
|
||||
|
||||
// Convenience method to access
|
||||
virtual void setBrightness(uint8_t);
|
||||
|
||||
// Reset display rotation or mirroring
|
||||
void resetOrientation();
|
||||
|
||||
// Turn the display upside down
|
||||
void flipScreenVertically();
|
||||
|
||||
// Mirror the display (to be used in a mirror or as a projector)
|
||||
void mirrorScreen();
|
||||
|
||||
// Write the buffer to the display memory
|
||||
virtual void display(void) = 0;
|
||||
|
||||
// Clear the local pixel buffer
|
||||
void clear(void);
|
||||
|
||||
// Log buffer implementation
|
||||
|
||||
// This will define the lines and characters you can
|
||||
// print to the screen. When you exeed the buffer size (lines * chars)
|
||||
// the output may be truncated due to the size constraint.
|
||||
bool setLogBuffer(uint16_t lines, uint16_t chars);
|
||||
|
||||
// Draw the log buffer at position (x, y)
|
||||
void drawLogBuffer(uint16_t x, uint16_t y);
|
||||
|
||||
// Get screen geometry
|
||||
uint16_t getWidth(void);
|
||||
uint16_t getHeight(void);
|
||||
|
||||
// Implement needed function to be compatible with Print class
|
||||
size_t write(uint8_t c);
|
||||
size_t write(const char* s);
|
||||
|
||||
// Implement needed function to be compatible with Stream class
|
||||
#ifdef __MBED__
|
||||
int _putc(int c);
|
||||
int _getc() { return -1; };
|
||||
#endif
|
||||
|
||||
|
||||
uint8_t *buffer;
|
||||
|
||||
#ifdef OLEDDISPLAY_DOUBLE_BUFFER
|
||||
uint8_t *buffer_back;
|
||||
#endif
|
||||
|
||||
// Set the correct height, width and buffer for the geometry
|
||||
void setGeometry(OLEDDISPLAY_GEOMETRY g, uint16_t width = 0, uint16_t height = 0);
|
||||
|
||||
protected:
|
||||
|
||||
OLEDDISPLAY_GEOMETRY geometry;
|
||||
|
||||
uint16_t displayWidth;
|
||||
uint16_t displayHeight;
|
||||
uint16_t displayBufferSize;
|
||||
|
||||
OLEDDISPLAY_TEXT_ALIGNMENT textAlignment;
|
||||
OLEDDISPLAY_COLOR color;
|
||||
|
||||
const uint8_t *fontData;
|
||||
|
||||
// State values for logBuffer
|
||||
uint16_t logBufferSize;
|
||||
uint16_t logBufferFilled;
|
||||
uint16_t logBufferLine;
|
||||
uint16_t logBufferMaxLines;
|
||||
char *logBuffer;
|
||||
|
||||
|
||||
// the header size of the buffer used, e.g. for the SPI command header
|
||||
int BufferOffset;
|
||||
virtual int getBufferOffset(void) = 0;
|
||||
|
||||
// Send a command to the display (low level function)
|
||||
virtual void sendCommand(uint8_t com) {(void)com;};
|
||||
|
||||
// Connect to the display
|
||||
virtual bool connect() { return false; };
|
||||
|
||||
// Send all the init commands
|
||||
virtual void sendInitCommands();
|
||||
|
||||
// converts utf8 characters to extended ascii
|
||||
char* utf8ascii(const String &s);
|
||||
|
||||
void inline drawInternal(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const uint8_t *data, uint16_t offset, uint16_t bytesInData) __attribute__((always_inline));
|
||||
|
||||
uint16_t drawStringInternal(int16_t xMove, int16_t yMove, const char* text, uint16_t textLength, uint16_t textWidth, bool utf8);
|
||||
|
||||
FontTableLookupFunction fontTableLookupFunction;
|
||||
};
|
||||
|
||||
#endif
|
||||
1273
src/helpers/ui/OLEDDisplayFonts.cpp
Normal file
1273
src/helpers/ui/OLEDDisplayFonts.cpp
Normal file
File diff suppressed because it is too large
Load Diff
13
src/helpers/ui/OLEDDisplayFonts.h
Normal file
13
src/helpers/ui/OLEDDisplayFonts.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#ifndef OLEDDISPLAYFONTS_h
|
||||
#define OLEDDISPLAYFONTS_h
|
||||
|
||||
#ifdef ARDUINO
|
||||
#include <Arduino.h>
|
||||
#elif __MBED__
|
||||
#define PROGMEM
|
||||
#endif
|
||||
|
||||
extern const uint8_t ArialMT_Plain_10[] PROGMEM;
|
||||
extern const uint8_t ArialMT_Plain_16[] PROGMEM;
|
||||
extern const uint8_t ArialMT_Plain_24[] PROGMEM;
|
||||
#endif
|
||||
@@ -20,6 +20,11 @@ void SSD1306Display::turnOff() {
|
||||
_isOn = false;
|
||||
}
|
||||
|
||||
void SSD1306Display::clear() {
|
||||
display.clearDisplay();
|
||||
display.display();
|
||||
}
|
||||
|
||||
void SSD1306Display::startFrame(Color bkg) {
|
||||
display.clearDisplay(); // TODO: apply 'bkg'
|
||||
_color = SSD1306_WHITE;
|
||||
@@ -33,7 +38,7 @@ void SSD1306Display::setTextSize(int sz) {
|
||||
}
|
||||
|
||||
void SSD1306Display::setColor(Color c) {
|
||||
_color = (c == LIGHT) ? SSD1306_WHITE : SSD1306_BLACK;
|
||||
_color = (c != 0) ? SSD1306_WHITE : SSD1306_BLACK;
|
||||
display.setTextColor(_color);
|
||||
}
|
||||
|
||||
@@ -57,6 +62,13 @@ void SSD1306Display::drawXbm(int x, int y, const uint8_t* bits, int w, int h) {
|
||||
display.drawBitmap(x, y, bits, w, h, SSD1306_WHITE);
|
||||
}
|
||||
|
||||
uint16_t SSD1306Display::getTextWidth(const char* str) {
|
||||
int16_t x1, y1;
|
||||
uint16_t w, h;
|
||||
display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h);
|
||||
return w;
|
||||
}
|
||||
|
||||
void SSD1306Display::endFrame() {
|
||||
display.display();
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ public:
|
||||
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;
|
||||
@@ -35,5 +36,6 @@ public:
|
||||
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;
|
||||
};
|
||||
|
||||
134
src/helpers/ui/ST7735Display.cpp
Normal file
134
src/helpers/ui/ST7735Display.cpp
Normal file
@@ -0,0 +1,134 @@
|
||||
#ifdef ST7735
|
||||
|
||||
#include "ST7735Display.h"
|
||||
|
||||
#ifndef DISPLAY_ROTATION
|
||||
#define DISPLAY_ROTATION 2
|
||||
#endif
|
||||
|
||||
#define SCALE_X 1.25f // 160 / 128
|
||||
#define SCALE_Y 1.25f // 80 / 64
|
||||
|
||||
bool ST7735Display::i2c_probe(TwoWire& wire, uint8_t addr) {
|
||||
return true;
|
||||
/*
|
||||
wire.beginTransmission(addr);
|
||||
uint8_t error = wire.endTransmission();
|
||||
return (error == 0);
|
||||
*/
|
||||
}
|
||||
|
||||
bool ST7735Display::begin() {
|
||||
if (!_isOn) {
|
||||
if (_peripher_power) _peripher_power->claim();
|
||||
|
||||
pinMode(PIN_TFT_LEDA_CTL, OUTPUT);
|
||||
digitalWrite(PIN_TFT_LEDA_CTL, HIGH);
|
||||
digitalWrite(PIN_TFT_RST, HIGH);
|
||||
|
||||
display.initR(INITR_MINI160x80_PLUGIN);
|
||||
display.setRotation(DISPLAY_ROTATION);
|
||||
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;
|
||||
}
|
||||
|
||||
void ST7735Display::turnOn() {
|
||||
ST7735Display::begin();
|
||||
}
|
||||
|
||||
void ST7735Display::turnOff() {
|
||||
if (_isOn) {
|
||||
digitalWrite(PIN_TFT_LEDA_CTL, HIGH);
|
||||
digitalWrite(PIN_TFT_RST, LOW);
|
||||
digitalWrite(PIN_TFT_LEDA_CTL, LOW);
|
||||
_isOn = false;
|
||||
|
||||
if (_peripher_power) _peripher_power->release();
|
||||
}
|
||||
}
|
||||
|
||||
void ST7735Display::clear() {
|
||||
//Serial.println("DBG: display.Clear");
|
||||
display.fillScreen(ST77XX_BLACK);
|
||||
}
|
||||
|
||||
void ST7735Display::startFrame(Color bkg) {
|
||||
display.fillScreen(0x00);
|
||||
display.setTextColor(ST77XX_WHITE);
|
||||
display.setTextSize(1); // This one affects size of Please wait... message
|
||||
display.cp437(true); // Use full 256 char 'Code Page 437' font
|
||||
}
|
||||
|
||||
void ST7735Display::setTextSize(int sz) {
|
||||
display.setTextSize(sz);
|
||||
}
|
||||
|
||||
void ST7735Display::setColor(Color c) {
|
||||
switch (c) {
|
||||
case DisplayDriver::DARK :
|
||||
_color = ST77XX_BLACK;
|
||||
break;
|
||||
case DisplayDriver::LIGHT :
|
||||
_color = ST77XX_WHITE;
|
||||
break;
|
||||
case DisplayDriver::RED :
|
||||
_color = ST77XX_RED;
|
||||
break;
|
||||
case DisplayDriver::GREEN :
|
||||
_color = ST77XX_GREEN;
|
||||
break;
|
||||
case DisplayDriver::BLUE :
|
||||
_color = ST77XX_BLUE;
|
||||
break;
|
||||
case DisplayDriver::YELLOW :
|
||||
_color = ST77XX_YELLOW;
|
||||
break;
|
||||
case DisplayDriver::ORANGE :
|
||||
_color = ST77XX_ORANGE;
|
||||
break;
|
||||
default:
|
||||
_color = ST77XX_WHITE;
|
||||
break;
|
||||
}
|
||||
display.setTextColor(_color);
|
||||
}
|
||||
|
||||
void ST7735Display::setCursor(int x, int y) {
|
||||
display.setCursor(x*SCALE_X, y*SCALE_Y);
|
||||
}
|
||||
|
||||
void ST7735Display::print(const char* str) {
|
||||
display.print(str);
|
||||
}
|
||||
|
||||
void ST7735Display::fillRect(int x, int y, int w, int h) {
|
||||
display.fillRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, _color);
|
||||
}
|
||||
|
||||
void ST7735Display::drawRect(int x, int y, int w, int h) {
|
||||
display.drawRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, _color);
|
||||
}
|
||||
|
||||
void ST7735Display::drawXbm(int x, int y, const uint8_t* bits, int w, int h) {
|
||||
display.drawBitmap(x*SCALE_X, y*SCALE_Y, bits, w, h, _color);
|
||||
}
|
||||
|
||||
uint16_t ST7735Display::getTextWidth(const char* str) {
|
||||
int16_t x1, y1;
|
||||
uint16_t w, h;
|
||||
display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h);
|
||||
return w / SCALE_X;
|
||||
}
|
||||
|
||||
void ST7735Display::endFrame() {
|
||||
// display.display();
|
||||
}
|
||||
|
||||
#endif
|
||||
49
src/helpers/ui/ST7735Display.h
Normal file
49
src/helpers/ui/ST7735Display.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include "DisplayDriver.h"
|
||||
#include <Wire.h>
|
||||
#include <SPI.h>
|
||||
#include <Adafruit_GFX.h>
|
||||
#include <Adafruit_ST7735.h>
|
||||
#include <helpers/RefCountedDigitalPin.h>
|
||||
|
||||
class ST7735Display : public DisplayDriver {
|
||||
Adafruit_ST7735 display;
|
||||
bool _isOn;
|
||||
uint16_t _color;
|
||||
RefCountedDigitalPin* _peripher_power;
|
||||
|
||||
bool i2c_probe(TwoWire& wire, uint8_t addr);
|
||||
public:
|
||||
#ifdef USE_PIN_TFT
|
||||
ST7735Display(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64),
|
||||
display(PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_SDA, PIN_TFT_SCL, PIN_TFT_RST),
|
||||
_peripher_power(peripher_power)
|
||||
{
|
||||
_isOn = false;
|
||||
}
|
||||
#else
|
||||
ST7735Display(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64),
|
||||
display(&SPI1, PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_RST),
|
||||
_peripher_power(peripher_power)
|
||||
{
|
||||
_isOn = false;
|
||||
}
|
||||
#endif
|
||||
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;
|
||||
};
|
||||
157
src/helpers/ui/ST7789Display.cpp
Normal file
157
src/helpers/ui/ST7789Display.cpp
Normal file
@@ -0,0 +1,157 @@
|
||||
#ifdef ST7789
|
||||
|
||||
#include "ST7789Display.h"
|
||||
|
||||
#ifndef X_OFFSET
|
||||
#define X_OFFSET 0 // No offset needed for landscape
|
||||
#endif
|
||||
|
||||
#ifndef Y_OFFSET
|
||||
#define Y_OFFSET 1 // Vertical offset to prevent top row cutoff
|
||||
#endif
|
||||
|
||||
#define SCALE_X 1.875f // 240 / 128
|
||||
#define SCALE_Y 2.109375f // 135 / 64
|
||||
|
||||
bool ST7789Display::begin() {
|
||||
if(!_isOn) {
|
||||
pinMode(PIN_TFT_VDD_CTL, OUTPUT);
|
||||
pinMode(PIN_TFT_LEDA_CTL, OUTPUT);
|
||||
digitalWrite(PIN_TFT_VDD_CTL, LOW);
|
||||
digitalWrite(PIN_TFT_LEDA_CTL, LOW);
|
||||
digitalWrite(PIN_TFT_RST, HIGH);
|
||||
|
||||
display.init();
|
||||
display.landscapeScreen();
|
||||
display.displayOn();
|
||||
setCursor(0,0);
|
||||
|
||||
_isOn = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ST7789Display::turnOn() {
|
||||
ST7789Display::begin();
|
||||
}
|
||||
|
||||
void ST7789Display::turnOff() {
|
||||
digitalWrite(PIN_TFT_VDD_CTL, HIGH);
|
||||
digitalWrite(PIN_TFT_LEDA_CTL, HIGH);
|
||||
digitalWrite(PIN_TFT_RST, LOW);
|
||||
_isOn = false;
|
||||
}
|
||||
|
||||
void ST7789Display::clear() {
|
||||
display.clear();
|
||||
}
|
||||
|
||||
void ST7789Display::startFrame(Color bkg) {
|
||||
display.clear();
|
||||
}
|
||||
|
||||
void ST7789Display::setTextSize(int sz) {
|
||||
switch(sz) {
|
||||
case 1 :
|
||||
display.setFont(ArialMT_Plain_16);
|
||||
break;
|
||||
case 2 :
|
||||
display.setFont(ArialMT_Plain_24);
|
||||
break;
|
||||
default:
|
||||
display.setFont(ArialMT_Plain_16);
|
||||
}
|
||||
}
|
||||
|
||||
void ST7789Display::setColor(Color c) {
|
||||
switch (c) {
|
||||
case DisplayDriver::DARK :
|
||||
_color = ST77XX_BLACK;
|
||||
break;
|
||||
case DisplayDriver::LIGHT :
|
||||
_color = ST77XX_WHITE;
|
||||
break;
|
||||
case DisplayDriver::RED :
|
||||
_color = ST77XX_RED;
|
||||
break;
|
||||
case DisplayDriver::GREEN :
|
||||
_color = ST77XX_GREEN;
|
||||
break;
|
||||
case DisplayDriver::BLUE :
|
||||
_color = ST77XX_BLUE;
|
||||
break;
|
||||
case DisplayDriver::YELLOW :
|
||||
_color = ST77XX_YELLOW;
|
||||
break;
|
||||
case DisplayDriver::ORANGE :
|
||||
_color = ST77XX_ORANGE;
|
||||
break;
|
||||
default:
|
||||
_color = ST77XX_WHITE;
|
||||
break;
|
||||
}
|
||||
display.setRGB(_color);
|
||||
}
|
||||
|
||||
void ST7789Display::setCursor(int x, int y) {
|
||||
_x = x*SCALE_X + X_OFFSET;
|
||||
_y = y*SCALE_Y + Y_OFFSET;
|
||||
}
|
||||
|
||||
void ST7789Display::print(const char* str) {
|
||||
display.drawString(_x, _y, str);
|
||||
}
|
||||
|
||||
void ST7789Display::fillRect(int x, int y, int w, int h) {
|
||||
display.fillRect(x*SCALE_X + X_OFFSET, y*SCALE_Y + Y_OFFSET, w*SCALE_X, h*SCALE_Y);
|
||||
}
|
||||
|
||||
void ST7789Display::drawRect(int x, int y, int w, int h) {
|
||||
display.drawRect(x*SCALE_X + X_OFFSET, y*SCALE_Y + Y_OFFSET, w*SCALE_X, h*SCALE_Y);
|
||||
}
|
||||
|
||||
void ST7789Display::drawXbm(int x, int y, const uint8_t* bits, int w, int h) {
|
||||
// Calculate the base position in display coordinates
|
||||
uint16_t startX = x * SCALE_X + X_OFFSET;
|
||||
uint16_t startY = y * SCALE_Y + Y_OFFSET;
|
||||
|
||||
// Width in bytes for bitmap processing
|
||||
uint16_t widthInBytes = (w + 7) / 8;
|
||||
|
||||
// Process the bitmap row by row
|
||||
for (uint16_t by = 0; by < h; by++) {
|
||||
// Calculate the target y-coordinates for this logical row
|
||||
int y1 = startY + (int)(by * SCALE_Y);
|
||||
int y2 = startY + (int)((by + 1) * SCALE_Y);
|
||||
int block_h = y2 - y1;
|
||||
|
||||
// Scan across the row bit by bit
|
||||
for (uint16_t bx = 0; bx < w; bx++) {
|
||||
// Calculate the target x-coordinates for this logical column
|
||||
int x1 = startX + (int)(bx * SCALE_X);
|
||||
int x2 = startX + (int)((bx + 1) * SCALE_X);
|
||||
int block_w = x2 - x1;
|
||||
|
||||
// Get the current bit
|
||||
uint16_t byteOffset = (by * widthInBytes) + (bx / 8);
|
||||
uint8_t bitMask = 0x80 >> (bx & 7);
|
||||
bool bitSet = pgm_read_byte(bits + byteOffset) & bitMask;
|
||||
|
||||
// If the bit is set, draw a block of pixels
|
||||
if (bitSet) {
|
||||
// Draw the block as a filled rectangle
|
||||
display.fillRect(x1, y1, block_w, block_h);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t ST7789Display::getTextWidth(const char* str) {
|
||||
return display.getStringWidth(str) / SCALE_X;
|
||||
}
|
||||
|
||||
void ST7789Display::endFrame() {
|
||||
display.display();
|
||||
}
|
||||
|
||||
#endif
|
||||
35
src/helpers/ui/ST7789Display.h
Normal file
35
src/helpers/ui/ST7789Display.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include "DisplayDriver.h"
|
||||
#include <Wire.h>
|
||||
#include <SPI.h>
|
||||
#include <Adafruit_GFX.h>
|
||||
#include <ST7789Spi.h>
|
||||
|
||||
class ST7789Display : public DisplayDriver {
|
||||
ST7789Spi display;
|
||||
bool _isOn;
|
||||
uint16_t _color;
|
||||
int _x=0, _y=0;
|
||||
|
||||
bool i2c_probe(TwoWire& wire, uint8_t addr);
|
||||
public:
|
||||
ST7789Display() : DisplayDriver(128, 64), display(&SPI1, PIN_TFT_RST, PIN_TFT_DC, PIN_TFT_CS, GEOMETRY_RAWMODE, 240, 135) {_isOn = false;}
|
||||
|
||||
bool begin();
|
||||
|
||||
bool isOn() override { return _isOn; }
|
||||
void turnOn() override;
|
||||
void turnOff() override;
|
||||
void clear() override;
|
||||
void startFrame(Color bkg = DARK) override;
|
||||
void setTextSize(int sz) override;
|
||||
void setColor(Color c) override;
|
||||
void setCursor(int x, int y) override;
|
||||
void print(const char* str) override;
|
||||
void fillRect(int x, int y, int w, int h) override;
|
||||
void drawRect(int x, int y, int w, int h) override;
|
||||
void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override;
|
||||
uint16_t getTextWidth(const char* str) override;
|
||||
void endFrame() override;
|
||||
};
|
||||
464
src/helpers/ui/ST7789Spi.h
Normal file
464
src/helpers/ui/ST7789Spi.h
Normal file
@@ -0,0 +1,464 @@
|
||||
/**
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2018 by ThingPulse, Daniel Eichhorn
|
||||
* Copyright (c) 2018 by Fabrice Weinberg
|
||||
* Copyright (c) 2024 by Heltec AutoMation
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
* ThingPulse invests considerable time and money to develop these open source libraries.
|
||||
* Please support us by buying our products (and not the clones) from
|
||||
* https://thingpulse.com
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef ST7789Spi_h
|
||||
#define ST7789Spi_h
|
||||
|
||||
#include "OLEDDisplay.h"
|
||||
#include <SPI.h>
|
||||
|
||||
|
||||
#define ST_CMD_DELAY 0x80 // special signifier for command lists
|
||||
|
||||
#define ST77XX_NOP 0x00
|
||||
#define ST77XX_SWRESET 0x01
|
||||
#define ST77XX_RDDID 0x04
|
||||
#define ST77XX_RDDST 0x09
|
||||
|
||||
#define ST77XX_SLPIN 0x10
|
||||
#define ST77XX_SLPOUT 0x11
|
||||
#define ST77XX_PTLON 0x12
|
||||
#define ST77XX_NORON 0x13
|
||||
|
||||
#define ST77XX_INVOFF 0x20
|
||||
#define ST77XX_INVON 0x21
|
||||
#define ST77XX_DISPOFF 0x28
|
||||
#define ST77XX_DISPON 0x29
|
||||
#define ST77XX_CASET 0x2A
|
||||
#define ST77XX_RASET 0x2B
|
||||
#define ST77XX_RAMWR 0x2C
|
||||
#define ST77XX_RAMRD 0x2E
|
||||
|
||||
#define ST77XX_PTLAR 0x30
|
||||
#define ST77XX_TEOFF 0x34
|
||||
#define ST77XX_TEON 0x35
|
||||
#define ST77XX_MADCTL 0x36
|
||||
#define ST77XX_COLMOD 0x3A
|
||||
|
||||
#define ST77XX_MADCTL_MY 0x80
|
||||
#define ST77XX_MADCTL_MX 0x40
|
||||
#define ST77XX_MADCTL_MV 0x20
|
||||
#define ST77XX_MADCTL_ML 0x10
|
||||
#define ST77XX_MADCTL_RGB 0x00
|
||||
|
||||
#define ST77XX_RDID1 0xDA
|
||||
#define ST77XX_RDID2 0xDB
|
||||
#define ST77XX_RDID3 0xDC
|
||||
#define ST77XX_RDID4 0xDD
|
||||
|
||||
// Some ready-made 16-bit ('565') color settings:
|
||||
#define ST77XX_BLACK 0x0000
|
||||
#define ST77XX_WHITE 0xFFFF
|
||||
#define ST77XX_RED 0xF800
|
||||
#define ST77XX_GREEN 0x07E0
|
||||
#define ST77XX_BLUE 0x001F
|
||||
#define ST77XX_CYAN 0x07FF
|
||||
#define ST77XX_MAGENTA 0xF81F
|
||||
#define ST77XX_YELLOW 0xFFE0
|
||||
#define ST77XX_ORANGE 0xFC00
|
||||
|
||||
#define LED_A_ON LOW
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#undef LED_A_ON
|
||||
#define LED_A_ON HIGH
|
||||
#define rtos_free free
|
||||
#define rtos_malloc malloc
|
||||
//SPIClass SPI1(HSPI);
|
||||
#endif
|
||||
class ST7789Spi : public OLEDDisplay {
|
||||
private:
|
||||
uint8_t _rst;
|
||||
uint8_t _dc;
|
||||
uint8_t _cs;
|
||||
uint8_t _ledA;
|
||||
int _miso;
|
||||
int _mosi;
|
||||
int _clk;
|
||||
SPIClass * _spi;
|
||||
SPISettings _spiSettings;
|
||||
uint16_t _RGB=0xFFFF;
|
||||
uint8_t _buffheight;
|
||||
public:
|
||||
/* pass _cs as -1 to indicate "do not use CS pin", for cases where it is hard wired low */
|
||||
ST7789Spi(SPIClass *spiClass,uint8_t _rst, uint8_t _dc, uint8_t _cs, OLEDDISPLAY_GEOMETRY g = GEOMETRY_RAWMODE,uint16_t width=240,uint16_t height=135,int mosi=-1,int miso=-1,int clk=-1) {
|
||||
this->_spi = spiClass;
|
||||
this->_rst = _rst;
|
||||
this->_dc = _dc;
|
||||
this->_cs = _cs;
|
||||
this->_mosi=mosi;
|
||||
this->_miso=miso;
|
||||
this->_clk=clk;
|
||||
//this->_ledA = _ledA;
|
||||
_spiSettings = SPISettings(40000000, MSBFIRST, SPI_MODE0);
|
||||
setGeometry(g,width,height);
|
||||
}
|
||||
|
||||
bool connect(){
|
||||
this->_buffheight=displayHeight / 8;
|
||||
this->_buffheight+=displayHeight % 8 ? 1:0;
|
||||
pinMode(_cs, OUTPUT);
|
||||
pinMode(_dc, OUTPUT);
|
||||
//pinMode(_ledA, OUTPUT);
|
||||
if (_cs != (uint8_t) -1) {
|
||||
pinMode(_cs, OUTPUT);
|
||||
}
|
||||
pinMode(_rst, OUTPUT);
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
_spi->begin(_clk,_miso,_mosi,-1);
|
||||
#else
|
||||
_spi->begin();
|
||||
#endif
|
||||
_spi->setClockDivider (SPI_CLOCK_DIV2);
|
||||
|
||||
// Pulse Reset low for 10ms
|
||||
digitalWrite(_rst, HIGH);
|
||||
delay(1);
|
||||
digitalWrite(_rst, LOW);
|
||||
delay(10);
|
||||
digitalWrite(_rst, HIGH);
|
||||
_spi->begin ();
|
||||
//digitalWrite(_ledA, LED_A_ON);
|
||||
return true;
|
||||
}
|
||||
|
||||
void display(void) {
|
||||
#ifdef OLEDDISPLAY_DOUBLE_BUFFER
|
||||
|
||||
uint16_t minBoundY = UINT16_MAX;
|
||||
uint16_t maxBoundY = 0;
|
||||
|
||||
uint16_t minBoundX = UINT16_MAX;
|
||||
uint16_t maxBoundX = 0;
|
||||
|
||||
uint16_t x, y;
|
||||
|
||||
// Calculate the Y bounding box of changes
|
||||
// and copy buffer[pos] to buffer_back[pos];
|
||||
for (y = 0; y < _buffheight; y++) {
|
||||
for (x = 0; x < displayWidth; x++) {
|
||||
//Serial.printf("x %d y %d\r\n",x,y);
|
||||
uint16_t pos = x + y * displayWidth;
|
||||
if (buffer[pos] != buffer_back[pos]) {
|
||||
minBoundY = min(minBoundY, y);
|
||||
maxBoundY = max(maxBoundY, y);
|
||||
minBoundX = min(minBoundX, x);
|
||||
maxBoundX = max(maxBoundX, x);
|
||||
}
|
||||
buffer_back[pos] = buffer[pos];
|
||||
}
|
||||
yield();
|
||||
}
|
||||
|
||||
// If the minBoundY wasn't updated
|
||||
// we can savely assume that buffer_back[pos] == buffer[pos]
|
||||
// holdes true for all values of pos
|
||||
if (minBoundY == UINT16_MAX) return;
|
||||
|
||||
set_CS(LOW);
|
||||
_spi->beginTransaction(_spiSettings);
|
||||
|
||||
for (y = minBoundY; y <= maxBoundY; y++)
|
||||
{
|
||||
for(int temp = 0; temp<8;temp++)
|
||||
{
|
||||
//setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1);
|
||||
setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1);
|
||||
//setAddrWindow(y*8+temp,minBoundX,1,maxBoundX-minBoundX+1);
|
||||
uint32_t const pixbufcount = maxBoundX-minBoundX+1;
|
||||
uint16_t *pixbuf = (uint16_t *)rtos_malloc(2 * pixbufcount);
|
||||
for (x = minBoundX; x <= maxBoundX; x++)
|
||||
{
|
||||
pixbuf[x-minBoundX] = ((buffer[x + y * displayWidth]>>temp)&0x01)==1?_RGB:0;
|
||||
}
|
||||
#ifdef ESP_PLATFORM
|
||||
_spi->transferBytes((uint8_t *)pixbuf, NULL, 2 * pixbufcount);
|
||||
#else
|
||||
_spi->transfer(pixbuf, NULL, 2 * pixbufcount);
|
||||
#endif
|
||||
rtos_free(pixbuf);
|
||||
}
|
||||
}
|
||||
_spi->endTransaction();
|
||||
set_CS(HIGH);
|
||||
|
||||
#else
|
||||
set_CS(LOW);
|
||||
_spi->beginTransaction(_spiSettings);
|
||||
uint8_t x, y;
|
||||
for (y = 0; y < _buffheight; y++)
|
||||
{
|
||||
for(int temp = 0; temp<8;temp++)
|
||||
{
|
||||
//setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1);
|
||||
//setAddrWindow(minBoundX,y*8+temp,maxBoundX-minBoundX+1,1);
|
||||
setAddrWindow(y*8+temp,0,1,displayWidth);
|
||||
uint32_t const pixbufcount = displayWidth;
|
||||
uint16_t *pixbuf = (uint16_t *)rtos_malloc(2 * pixbufcount);
|
||||
for (x = 0; x < displayWidth; x++)
|
||||
{
|
||||
pixbuf[x] = ((buffer[x + y * displayWidth]>>temp)&0x01)==1?_RGB:0;
|
||||
}
|
||||
#ifdef ESP_PLATFORM
|
||||
_spi->transferBytes((uint8_t *)pixbuf, NULL, 2 * pixbufcount);
|
||||
#else
|
||||
_spi->transfer(pixbuf, NULL, 2 * pixbufcount);
|
||||
#endif
|
||||
rtos_free(pixbuf);
|
||||
}
|
||||
}
|
||||
_spi->endTransaction();
|
||||
set_CS(HIGH);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
virtual void resetOrientation() {
|
||||
uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV;
|
||||
sendCommand(ST77XX_MADCTL);
|
||||
WriteData(madctl);
|
||||
delay(10);
|
||||
}
|
||||
|
||||
virtual void flipScreenVertically() {
|
||||
uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MY;
|
||||
sendCommand(ST77XX_MADCTL);
|
||||
WriteData(madctl);
|
||||
delay(10);
|
||||
}
|
||||
|
||||
virtual void mirrorScreen() {
|
||||
uint8_t madctl = ST77XX_MADCTL_RGB|ST77XX_MADCTL_MV|ST77XX_MADCTL_MX|ST77XX_MADCTL_MY;
|
||||
sendCommand(ST77XX_MADCTL);
|
||||
WriteData(madctl);
|
||||
delay(10);
|
||||
}
|
||||
|
||||
virtual void landscapeScreen() {
|
||||
// For landscape mode rotated 180 degrees with correct text direction
|
||||
// MV swaps rows/columns for landscape orientation
|
||||
// Adding MX (instead of MY) flips X axis and rotates 180 degrees
|
||||
uint8_t madctl = ST77XX_MADCTL_RGB | ST77XX_MADCTL_MV | ST77XX_MADCTL_MX;
|
||||
sendCommand(ST77XX_MADCTL);
|
||||
WriteData(madctl);
|
||||
delay(10);
|
||||
}
|
||||
|
||||
|
||||
void setRGB(uint16_t c)
|
||||
{
|
||||
|
||||
this->_RGB=0x00|c>>8|c<<8&0xFF00;
|
||||
}
|
||||
|
||||
void displayOn(void) {
|
||||
//sendCommand(DISPLAYON);
|
||||
}
|
||||
|
||||
void displayOff(void) {
|
||||
//sendCommand(DISPLAYOFF);
|
||||
}
|
||||
|
||||
void drawBitmap(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const uint8_t *xbm) {
|
||||
int16_t widthInXbm = (width + 7) / 8;
|
||||
uint8_t data = 0;
|
||||
|
||||
for(int16_t y = 0; y < height; y++) {
|
||||
for(int16_t x = 0; x < width; x++ ) {
|
||||
if (x & 7) {
|
||||
data <<= 1; // Move a bit
|
||||
} else { // Read new data every 8 bit
|
||||
data = pgm_read_byte(xbm + (x / 8) + y * widthInXbm);
|
||||
}
|
||||
// if there is a bit draw it
|
||||
if (data & 0x80) {
|
||||
setPixel(xMove + x, yMove + y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//#define ST77XX_MADCTL_MY 0x80
|
||||
//#define ST77XX_MADCTL_MX 0x40
|
||||
//#define ST77XX_MADCTL_MV 0x20
|
||||
//#define ST77XX_MADCTL_ML 0x10
|
||||
protected:
|
||||
// Send all the init commands
|
||||
virtual void sendInitCommands()
|
||||
{
|
||||
sendCommand(ST77XX_SWRESET); // 1: Software reset, no args, w/delay
|
||||
delay(150);
|
||||
|
||||
sendCommand(ST77XX_SLPOUT); // 2: Out of sleep mode, no args, w/delay
|
||||
delay(10);
|
||||
|
||||
sendCommand(ST77XX_COLMOD); // 3: Set color mode, 16-bit color
|
||||
WriteData(0x55);
|
||||
delay(10);
|
||||
|
||||
// Initialize with landscape orientation rotated 180 degrees
|
||||
uint8_t madctl = ST77XX_MADCTL_RGB | ST77XX_MADCTL_MV | ST77XX_MADCTL_MX;
|
||||
sendCommand(ST77XX_MADCTL); // 4: Mem access ctrl (directions)
|
||||
WriteData(madctl);
|
||||
delay(10);
|
||||
|
||||
// Set column address range for landscape orientation (240 pixels wide)
|
||||
sendCommand(ST77XX_CASET); // 5: Column addr set
|
||||
WriteData(0x00);
|
||||
WriteData(0x00); // XSTART = 0
|
||||
WriteData(0x00);
|
||||
WriteData(240); // XEND = 240
|
||||
|
||||
// Set row address range for landscape orientation (135 pixels tall)
|
||||
sendCommand(ST77XX_RASET); // 6: Row addr set
|
||||
WriteData(0x00);
|
||||
WriteData(0x00); // YSTART = 0
|
||||
WriteData(0x00);
|
||||
WriteData(135); // YEND = 135
|
||||
|
||||
sendCommand(ST77XX_SLPOUT); // 7: hack
|
||||
delay(10);
|
||||
|
||||
sendCommand(ST77XX_NORON); // 8: Normal display on, no args, w/delay
|
||||
delay(10);
|
||||
|
||||
sendCommand(ST77XX_DISPON); // 9: Main screen turn on, no args, delay
|
||||
delay(10);
|
||||
|
||||
sendCommand(ST77XX_INVON); // 10: invert
|
||||
delay(10);
|
||||
|
||||
// Use landscape mode instead of portrait
|
||||
landscapeScreen();
|
||||
setRGB(ST77XX_GREEN);
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
|
||||
void setAddrWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
|
||||
x += (320-displayWidth)/2;
|
||||
y += (240-displayHeight)/2;
|
||||
|
||||
uint32_t xa = ((uint32_t)x << 16) | (x + w - 1);
|
||||
uint32_t ya = ((uint32_t)y << 16) | (y + h - 1);
|
||||
|
||||
writeCommand(ST77XX_CASET); // Column addr set
|
||||
SPI_WRITE32(xa);
|
||||
|
||||
writeCommand(ST77XX_RASET); // Row addr set
|
||||
SPI_WRITE32(ya);
|
||||
|
||||
writeCommand(ST77XX_RAMWR); // write to RAM
|
||||
}
|
||||
int getBufferOffset(void) {
|
||||
return 0;
|
||||
}
|
||||
inline void set_CS(bool level) {
|
||||
if (_cs != (uint8_t) -1) {
|
||||
digitalWrite(_cs, level);
|
||||
}
|
||||
};
|
||||
inline void sendCommand(uint8_t com) __attribute__((always_inline)){
|
||||
set_CS(HIGH);
|
||||
digitalWrite(_dc, LOW);
|
||||
set_CS(LOW);
|
||||
_spi->beginTransaction(_spiSettings);
|
||||
_spi->transfer(com);
|
||||
_spi->endTransaction();
|
||||
set_CS(HIGH);
|
||||
digitalWrite(_dc, HIGH);
|
||||
}
|
||||
|
||||
inline void WriteData(uint8_t data) __attribute__((always_inline)){
|
||||
digitalWrite(_cs, LOW);
|
||||
_spi->beginTransaction(_spiSettings);
|
||||
_spi->transfer(data);
|
||||
_spi->endTransaction();
|
||||
digitalWrite(_cs, HIGH);
|
||||
}
|
||||
void SPI_WRITE32(uint32_t l)
|
||||
{
|
||||
_spi->transfer(l >> 24);
|
||||
_spi->transfer(l >> 16);
|
||||
_spi->transfer(l >> 8);
|
||||
_spi->transfer(l);
|
||||
}
|
||||
void writeCommand(uint8_t cmd) {
|
||||
digitalWrite(_dc, LOW);
|
||||
_spi->transfer(cmd);
|
||||
digitalWrite(_dc, HIGH);
|
||||
}
|
||||
|
||||
// Private functions
|
||||
void setGeometry(OLEDDISPLAY_GEOMETRY g, uint16_t width, uint16_t height) {
|
||||
this->geometry = g;
|
||||
|
||||
switch (g) {
|
||||
case GEOMETRY_128_128:
|
||||
this->displayWidth = 128;
|
||||
this->displayHeight = 128;
|
||||
break;
|
||||
case GEOMETRY_128_64:
|
||||
this->displayWidth = 128;
|
||||
this->displayHeight = 64;
|
||||
break;
|
||||
case GEOMETRY_128_32:
|
||||
this->displayWidth = 128;
|
||||
this->displayHeight = 32;
|
||||
break;
|
||||
case GEOMETRY_64_48:
|
||||
this->displayWidth = 64;
|
||||
this->displayHeight = 48;
|
||||
break;
|
||||
case GEOMETRY_64_32:
|
||||
this->displayWidth = 64;
|
||||
this->displayHeight = 32;
|
||||
break;
|
||||
case GEOMETRY_RAWMODE:
|
||||
this->displayWidth = width > 0 ? width : 128;
|
||||
this->displayHeight = height > 0 ? height : 64;
|
||||
break;
|
||||
}
|
||||
uint8_t tmp=displayHeight % 8;
|
||||
uint8_t _buffheight=displayHeight / 8;
|
||||
|
||||
if(tmp!=0)
|
||||
_buffheight++;
|
||||
this->displayBufferSize = displayWidth * _buffheight ;
|
||||
}
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
68
variants/generic-e22/platformio.ini
Normal file
68
variants/generic-e22/platformio.ini
Normal file
@@ -0,0 +1,68 @@
|
||||
[Generic_E22]
|
||||
extends = esp32_base
|
||||
board = esp32doit-devkit-v1
|
||||
build_flags =
|
||||
${esp32_base.build_flags}
|
||||
-I variants/generic-e22
|
||||
-D GENERIC_E22
|
||||
-D P_LORA_TX_LED=2
|
||||
-D PIN_VBAT_READ=35
|
||||
-D P_LORA_DIO_1=33
|
||||
-D P_LORA_NSS=18
|
||||
-D P_LORA_RESET=RADIOLIB_NC
|
||||
-D P_LORA_BUSY=32
|
||||
-D P_LORA_SCLK=5
|
||||
-D P_LORA_MOSI=27
|
||||
-D P_LORA_MISO=19
|
||||
-D SX126X_TXEN=13
|
||||
-D SX126X_RXEN=14
|
||||
-D PIN_BOARD_SDA=21
|
||||
-D PIN_BOARD_SCL=22
|
||||
-D SX126X_DIO2_AS_RF_SWITCH=true
|
||||
-D SX126X_DIO3_TCXO_VOLTAGE=1.8
|
||||
-D SX126X_CURRENT_LIMIT=130.0f ; for best TX power!
|
||||
build_src_filter = ${esp32_base.build_src_filter}
|
||||
+<../variants/generic-e22>
|
||||
lib_deps =
|
||||
${esp32_base.lib_deps}
|
||||
adafruit/Adafruit SSD1306 @ ^2.5.13
|
||||
|
||||
[env:Generic_E22_sx1262_repeater]
|
||||
extends = Generic_E22
|
||||
build_src_filter = ${Generic_E22.build_src_filter}
|
||||
+<../examples/simple_repeater/main.cpp>
|
||||
build_flags =
|
||||
${Generic_E22.build_flags}
|
||||
-D RADIO_CLASS=CustomSX1262
|
||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||
-D LORA_TX_POWER=22
|
||||
-D ADVERT_NAME='"E22 Repeater"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=8
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
lib_deps =
|
||||
${Generic_E22.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
[env:Generic_E22_sx1268_repeater]
|
||||
extends = Generic_E22
|
||||
build_src_filter = ${Generic_E22.build_src_filter}
|
||||
+<../examples/simple_repeater/main.cpp>
|
||||
build_flags =
|
||||
${Generic_E22.build_flags}
|
||||
-D RADIO_CLASS=CustomSX1268
|
||||
-D WRAPPER_CLASS=CustomSX1268Wrapper
|
||||
-D LORA_TX_POWER=22
|
||||
-D ADVERT_NAME='"E22 Repeater"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=8
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
lib_deps =
|
||||
${Generic_E22.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
80
variants/generic-e22/target.cpp
Normal file
80
variants/generic-e22/target.cpp
Normal file
@@ -0,0 +1,80 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
|
||||
ESP32Board 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);
|
||||
SensorManager sensors;
|
||||
|
||||
#ifndef LORA_CR
|
||||
#define LORA_CR 5
|
||||
#endif
|
||||
|
||||
bool radio_init() {
|
||||
fallback_clock.begin();
|
||||
rtc_clock.begin(Wire);
|
||||
|
||||
#ifdef SX126X_DIO3_TCXO_VOLTAGE
|
||||
float tcxo = SX126X_DIO3_TCXO_VOLTAGE;
|
||||
#else
|
||||
float tcxo = 1.6f;
|
||||
#endif
|
||||
|
||||
#if defined(P_LORA_SCLK)
|
||||
spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI);
|
||||
#endif
|
||||
int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 8, tcxo);
|
||||
if (status != RADIOLIB_ERR_NONE) {
|
||||
Serial.print("ERROR: radio init failed: ");
|
||||
Serial.println(status);
|
||||
return false; // fail
|
||||
}
|
||||
|
||||
radio.setCRC(1);
|
||||
|
||||
#if defined(SX126X_RXEN) && defined(SX126X_TXEN)
|
||||
radio.setRfSwitchPins(SX126X_RXEN, SX126X_TXEN);
|
||||
#endif
|
||||
|
||||
#ifdef SX126X_CURRENT_LIMIT
|
||||
radio.setCurrentLimit(SX126X_CURRENT_LIMIT);
|
||||
#endif
|
||||
#ifdef SX126X_DIO2_AS_RF_SWITCH
|
||||
radio.setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH);
|
||||
#endif
|
||||
#ifdef SX126X_RX_BOOSTED_GAIN
|
||||
radio.setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN);
|
||||
#endif
|
||||
|
||||
return true; // success
|
||||
}
|
||||
|
||||
uint32_t radio_get_rng_seed() {
|
||||
return radio.random(0x7FFFFFFF);
|
||||
}
|
||||
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) {
|
||||
radio.setFrequency(freq);
|
||||
radio.setSpreadingFactor(sf);
|
||||
radio.setBandwidth(bw);
|
||||
radio.setCodingRate(cr);
|
||||
}
|
||||
|
||||
void radio_set_tx_power(uint8_t dbm) {
|
||||
radio.setOutputPower(dbm);
|
||||
}
|
||||
|
||||
mesh::LocalIdentity radio_new_identity() {
|
||||
RadioNoiseListener rng(radio);
|
||||
return mesh::LocalIdentity(&rng); // create new random identity
|
||||
}
|
||||
21
variants/generic-e22/target.h
Normal file
21
variants/generic-e22/target.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#define RADIOLIB_STATIC_ONLY 1
|
||||
#include <RadioLib.h>
|
||||
#include <helpers/RadioLibWrappers.h>
|
||||
#include <helpers/ESP32Board.h>
|
||||
#include <helpers/CustomSX1262Wrapper.h>
|
||||
#include <helpers/CustomSX1268Wrapper.h>
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
#include <helpers/SensorManager.h>
|
||||
|
||||
extern ESP32Board board;
|
||||
extern WRAPPER_CLASS radio_driver;
|
||||
extern AutoDiscoverRTCClock rtc_clock;
|
||||
extern SensorManager 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();
|
||||
44
variants/generic-e22/variant.h
Normal file
44
variants/generic-e22/variant.h
Normal file
@@ -0,0 +1,44 @@
|
||||
// For OLED LCD
|
||||
#define I2C_SDA 21
|
||||
#define I2C_SCL 22
|
||||
|
||||
// For GPS, 'undef's not needed
|
||||
#define GPS_TX_PIN 15
|
||||
#define GPS_RX_PIN 12
|
||||
#define PIN_GPS_EN 4
|
||||
#define GPS_POWER_TOGGLE // Moved definition from platformio.ini to here
|
||||
|
||||
#define BUTTON_PIN 39 // The middle button GPIO on the T-Beam
|
||||
#define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage
|
||||
#define ADC_CHANNEL ADC1_GPIO35_CHANNEL
|
||||
#define ADC_MULTIPLIER 1.85 // (R1 = 470k, R2 = 680k)
|
||||
#define EXT_PWR_DETECT 4 // Pin to detect connected external power source for LILYGO® TTGO T-Energy T18 and other DIY boards
|
||||
#define EXT_NOTIFY_OUT 12 // Overridden default pin to use for Ext Notify Module (#975).
|
||||
#define LED_PIN 2 // add status LED (compatible with core-pcb and DIY targets)
|
||||
|
||||
// Radio
|
||||
#define USE_SX1262 // E22-900M30S uses SX1262
|
||||
#define USE_SX1268 // E22-400M30S uses SX1268
|
||||
#define SX126X_MAX_POWER 22 // Outputting 22dBm from SX1262 results in ~30dBm E22-900M30S output (module only uses last stage of the YP2233W PA)
|
||||
#define SX126X_DIO3_TCXO_VOLTAGE 1.8 // E22 series TCXO reference voltage is 1.8V
|
||||
|
||||
#define SX126X_CS 18 // EBYTE module's NSS pin
|
||||
#define SX126X_SCK 5 // EBYTE module's SCK pin
|
||||
#define SX126X_MOSI 27 // EBYTE module's MOSI pin
|
||||
#define SX126X_MISO 19 // EBYTE module's MISO pin
|
||||
#define SX126X_RESET 23 // EBYTE module's NRST pin
|
||||
#define SX126X_BUSY 32 // EBYTE module's BUSY pin
|
||||
#define SX126X_DIO1 33 // EBYTE module's DIO1 pin
|
||||
|
||||
// The E22's TXEN pin is connected to MCU pin, E22's RXEN pin is connected to MCU pin (allows for ramping up PA before transmission
|
||||
// Don't define DIO2_AS_RF_SWITCH because we only use DIO2 or an MCU pin mutually exclusively to connect to E22's TXEN (to prevent
|
||||
// a short if they are both connected at the same time and there's a slight non-neglibible delay and/or voltage difference between
|
||||
// DIO2 and TXEN).
|
||||
#define SX126X_TXEN 13 // Schematic connects EBYTE module's TXEN pin to MCU
|
||||
#define SX126X_RXEN 14 // Schematic connects EBYTE module's RXEN pin to MCU
|
||||
|
||||
#define LORA_CS SX126X_CS // Compatibility with variant file configuration structure
|
||||
#define LORA_SCK SX126X_SCK // Compatibility with variant file configuration structure
|
||||
#define LORA_MOSI SX126X_MOSI // Compatibility with variant file configuration structure
|
||||
#define LORA_MISO SX126X_MISO // Compatibility with variant file configuration structure
|
||||
#define LORA_DIO1 SX126X_DIO1 // Compatibility with variant file configuration structure
|
||||
@@ -19,7 +19,7 @@ build_flags =
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${esp32_base.build_src_filter}
|
||||
+<helpers/esp32/ESPNowRadio.cpp>
|
||||
+<helpers/esp32/ESPNOWRadio.cpp>
|
||||
+<../variants/generic_espnow>
|
||||
|
||||
[env:Generic_ESPNOW_terminal_chat]
|
||||
@@ -39,9 +39,10 @@ extends = Generic_ESPNOW
|
||||
build_flags =
|
||||
${Generic_ESPNOW.build_flags}
|
||||
-D ADVERT_NAME='"ESPNOW Repeater"'
|
||||
-D ADVERT_LAT=-37.0
|
||||
-D ADVERT_LON=145.0
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=8
|
||||
build_src_filter = ${Generic_ESPNOW.build_src_filter}
|
||||
+<../examples/simple_repeater/main.cpp>
|
||||
lib_deps =
|
||||
@@ -71,8 +72,8 @@ extends = Generic_ESPNOW
|
||||
build_flags =
|
||||
${Generic_ESPNOW.build_flags}
|
||||
-D ADVERT_NAME='"Heltec Room"'
|
||||
-D ADVERT_LAT=-37.0
|
||||
-D ADVERT_LON=145.0
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D ROOM_PASSWORD='"hello"'
|
||||
build_src_filter = ${Generic_ESPNOW.build_src_filter}
|
||||
@@ -80,4 +81,3 @@ build_src_filter = ${Generic_ESPNOW.build_src_filter}
|
||||
lib_deps =
|
||||
${Generic_ESPNOW.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
|
||||
@@ -7,11 +7,13 @@ ESP32Board board;
|
||||
ESPNOWRadio radio_driver;
|
||||
|
||||
ESP32RTCClock rtc_clock;
|
||||
SensorManager sensors;
|
||||
|
||||
bool radio_init() {
|
||||
rtc_clock.begin();
|
||||
|
||||
// NOTE: radio_driver.begin() is called by Dispatcher::begin(), so not needed here
|
||||
radio_driver.init();
|
||||
|
||||
return true; // success
|
||||
}
|
||||
|
||||
@@ -27,7 +29,16 @@ void radio_set_tx_power(uint8_t dbm) {
|
||||
radio_driver.setTxPower(dbm);
|
||||
}
|
||||
|
||||
// NOTE: as we are using the WiFi radio, the ESP_IDF will have enabled hardware RNG:
|
||||
// https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/random.html
|
||||
class ESP_RNG : public mesh::RNG {
|
||||
public:
|
||||
void random(uint8_t* dest, size_t sz) override {
|
||||
esp_fill_random(dest, sz);
|
||||
}
|
||||
};
|
||||
|
||||
mesh::LocalIdentity radio_new_identity() {
|
||||
StdRNG rng; // TODO: need stronger True-RNG here
|
||||
ESP_RNG rng;
|
||||
return mesh::LocalIdentity(&rng); // create new random identity
|
||||
}
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
|
||||
#include <helpers/ESP32Board.h>
|
||||
#include <helpers/esp32/ESPNOWRadio.h>
|
||||
#include <helpers/SensorManager.h>
|
||||
|
||||
extern ESP32Board board;
|
||||
extern ESPNOWRadio radio_driver;
|
||||
extern ESP32RTCClock rtc_clock;
|
||||
extern SensorManager sensors;
|
||||
|
||||
bool radio_init();
|
||||
uint32_t radio_get_rng_seed();
|
||||
|
||||
61
variants/heltec_tracker/platformio.ini
Normal file
61
variants/heltec_tracker/platformio.ini
Normal file
@@ -0,0 +1,61 @@
|
||||
[Heltec_tracker_base]
|
||||
extends = esp32_base
|
||||
board = esp32-s3-devkitc-1
|
||||
build_flags =
|
||||
${esp32_base.build_flags}
|
||||
-I variants/heltec_tracker
|
||||
-D HELTEC_LORA_V3
|
||||
-D RADIO_CLASS=CustomSX1262
|
||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||
-D LORA_TX_POWER=22
|
||||
-D P_LORA_TX_LED=18
|
||||
-D PIN_BOARD_SDA=45
|
||||
-D PIN_BOARD_SCL=46
|
||||
-D PIN_USER_BTN=0
|
||||
-D PIN_ADC_CTRL=2
|
||||
-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_TFT_LEDA_CTL=21 ; LEDK (switches on/off via mosfet to create the ground)
|
||||
-D PIN_GPS_RX=33
|
||||
-D PIN_GPS_TX=34
|
||||
-D SX126X_DIO2_AS_RF_SWITCH=true
|
||||
-D SX126X_DIO3_TCXO_VOLTAGE=1.8
|
||||
-D SX126X_CURRENT_LIMIT=130.0f ; for best TX power!
|
||||
-D SX126X_RX_BOOSTED_GAIN=1
|
||||
build_src_filter = ${esp32_base.build_src_filter}
|
||||
+<../variants/heltec_tracker>
|
||||
lib_deps =
|
||||
${esp32_base.lib_deps}
|
||||
|
||||
[env:Heltec_Wireless_Tracker_companion_radio_ble]
|
||||
extends = Heltec_tracker_base
|
||||
build_flags =
|
||||
${Heltec_tracker_base.build_flags}
|
||||
-I src/helpers/ui
|
||||
; -D ARDUINO_USB_CDC_ON_BOOT=1 ; need for debugging
|
||||
-D ST7735
|
||||
-D DISPLAY_ROTATION=1
|
||||
-D DISPLAY_CLASS=ST7735Display
|
||||
-D MAX_CONTACTS=100
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D BLE_PIN_CODE=123456 ; HWT will use display for pin
|
||||
; -D BLE_DEBUG_LOGGING=1
|
||||
; -D ENABLE_PRIVATE_KEY_IMPORT=1
|
||||
; -D ENABLE_PRIVATE_KEY_EXPORT=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_tracker_base.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<../examples/companion_radio>
|
||||
+<helpers/ui/ST7735Display.cpp>
|
||||
lib_deps =
|
||||
${Heltec_tracker_base.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
stevemarple/MicroNMEA @ ^2.0.6
|
||||
adafruit/Adafruit ST7735 and ST7789 Library @ ^1.11.0
|
||||
|
||||
147
variants/heltec_tracker/target.cpp
Normal file
147
variants/heltec_tracker/target.cpp
Normal file
@@ -0,0 +1,147 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
|
||||
#include <helpers/sensors/MicroNMEALocationProvider.h>
|
||||
|
||||
HeltecV3Board 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);
|
||||
MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1);
|
||||
HWTSensorManager sensors = HWTSensorManager(nmea);
|
||||
|
||||
#ifndef LORA_CR
|
||||
#define LORA_CR 5
|
||||
#endif
|
||||
|
||||
bool radio_init() {
|
||||
fallback_clock.begin();
|
||||
rtc_clock.begin(Wire);
|
||||
|
||||
#ifdef SX126X_DIO3_TCXO_VOLTAGE
|
||||
float tcxo = SX126X_DIO3_TCXO_VOLTAGE;
|
||||
#else
|
||||
float tcxo = 1.6f;
|
||||
#endif
|
||||
|
||||
#if defined(P_LORA_SCLK)
|
||||
spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI);
|
||||
#endif
|
||||
int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 8, tcxo);
|
||||
if (status != RADIOLIB_ERR_NONE) {
|
||||
Serial.print("ERROR: radio init failed: ");
|
||||
Serial.println(status);
|
||||
return false; // fail
|
||||
}
|
||||
|
||||
radio.setCRC(1);
|
||||
|
||||
#ifdef SX126X_CURRENT_LIMIT
|
||||
radio.setCurrentLimit(SX126X_CURRENT_LIMIT);
|
||||
#endif
|
||||
#ifdef SX126X_DIO2_AS_RF_SWITCH
|
||||
radio.setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH);
|
||||
#endif
|
||||
#ifdef SX126X_RX_BOOSTED_GAIN
|
||||
radio.setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN);
|
||||
#endif
|
||||
|
||||
return true; // success
|
||||
}
|
||||
|
||||
uint32_t radio_get_rng_seed() {
|
||||
return radio.random(0x7FFFFFFF);
|
||||
}
|
||||
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) {
|
||||
radio.setFrequency(freq);
|
||||
radio.setSpreadingFactor(sf);
|
||||
radio.setBandwidth(bw);
|
||||
radio.setCodingRate(cr);
|
||||
}
|
||||
|
||||
void radio_set_tx_power(uint8_t dbm) {
|
||||
radio.setOutputPower(dbm);
|
||||
}
|
||||
|
||||
mesh::LocalIdentity radio_new_identity() {
|
||||
RadioNoiseListener rng(radio);
|
||||
return mesh::LocalIdentity(&rng); // create new random identity
|
||||
}
|
||||
|
||||
void HWTSensorManager::start_gps() {
|
||||
if (!gps_active) {
|
||||
board.periph_power.claim();
|
||||
|
||||
gps_active = true;
|
||||
Serial1.println("$CFGSYS,h35155*68");
|
||||
}
|
||||
}
|
||||
|
||||
void HWTSensorManager::stop_gps() {
|
||||
if (gps_active) {
|
||||
gps_active = false;
|
||||
|
||||
board.periph_power.release();
|
||||
}
|
||||
}
|
||||
|
||||
bool HWTSensorManager::begin() {
|
||||
// init GPS port
|
||||
Serial1.begin(115200, SERIAL_8N1, PIN_GPS_RX, PIN_GPS_TX);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HWTSensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) {
|
||||
if (requester_permissions & TELEM_PERM_LOCATION) { // does requester have permission?
|
||||
telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, 0.0f);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void HWTSensorManager::loop() {
|
||||
static long next_gps_update = 0;
|
||||
|
||||
_location->loop();
|
||||
|
||||
if (millis() > next_gps_update) {
|
||||
if (gps_active && _location->isValid()) {
|
||||
node_lat = ((double)_location->getLatitude())/1000000.;
|
||||
node_lon = ((double)_location->getLongitude())/1000000.;
|
||||
MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon);
|
||||
}
|
||||
next_gps_update = millis() + 1000;
|
||||
}
|
||||
}
|
||||
|
||||
int HWTSensorManager::getNumSettings() const { return 1; } // just one supported: "gps" (power switch)
|
||||
|
||||
const char* HWTSensorManager::getSettingName(int i) const {
|
||||
return i == 0 ? "gps" : NULL;
|
||||
}
|
||||
const char* HWTSensorManager::getSettingValue(int i) const {
|
||||
if (i == 0) {
|
||||
return gps_active ? "1" : "0";
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
bool HWTSensorManager::setSettingValue(const char* name, const char* value) {
|
||||
if (strcmp(name, "gps") == 0) {
|
||||
if (strcmp(value, "0") == 0) {
|
||||
stop_gps();
|
||||
} else {
|
||||
start_gps();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false; // not supported
|
||||
}
|
||||
38
variants/heltec_tracker/target.h
Normal file
38
variants/heltec_tracker/target.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#define RADIOLIB_STATIC_ONLY 1
|
||||
#include <RadioLib.h>
|
||||
#include <helpers/RadioLibWrappers.h>
|
||||
#include <helpers/HeltecV3Board.h>
|
||||
#include <helpers/CustomSX1262Wrapper.h>
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
#include <helpers/SensorManager.h>
|
||||
#include <helpers/sensors/LocationProvider.h>
|
||||
|
||||
class HWTSensorManager : public SensorManager {
|
||||
bool gps_active = false;
|
||||
LocationProvider * _location;
|
||||
|
||||
void start_gps();
|
||||
void stop_gps();
|
||||
public:
|
||||
HWTSensorManager(LocationProvider &location): _location(&location) { }
|
||||
bool begin() override;
|
||||
bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override;
|
||||
void loop() override;
|
||||
int getNumSettings() const override;
|
||||
const char* getSettingName(int i) const override;
|
||||
const char* getSettingValue(int i) const override;
|
||||
bool setSettingValue(const char* name, const char* value) override;
|
||||
};
|
||||
|
||||
extern HeltecV3Board board;
|
||||
extern WRAPPER_CLASS radio_driver;
|
||||
extern AutoDiscoverRTCClock rtc_clock;
|
||||
extern HWTSensorManager 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();
|
||||
@@ -25,14 +25,15 @@ build_flags =
|
||||
${Heltec_lora32_v2.build_flags}
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D ADVERT_NAME='"Heltec Repeater"'
|
||||
-D ADVERT_LAT=-37.0
|
||||
-D ADVERT_LON=145.0
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=8
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_lora32_v2.build_src_filter}
|
||||
+<../examples/simple_repeater>
|
||||
+<helpers/ui/*.cpp>
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
lib_deps =
|
||||
${Heltec_lora32_v2.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
@@ -43,14 +44,14 @@ build_flags =
|
||||
${Heltec_lora32_v2.build_flags}
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D ADVERT_NAME='"Heltec Room"'
|
||||
-D ADVERT_LAT=-37.0
|
||||
-D ADVERT_LON=145.0
|
||||
-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_lora32_v2.build_src_filter}
|
||||
+<helpers/ui/*.cpp>
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<../examples/simple_room_server>
|
||||
lib_deps =
|
||||
${Heltec_lora32_v2.lib_deps}
|
||||
@@ -81,7 +82,7 @@ build_flags =
|
||||
; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_lora32_v2.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/ui/*.cpp>
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<../examples/companion_radio>
|
||||
lib_deps =
|
||||
${Heltec_lora32_v2.lib_deps}
|
||||
@@ -102,7 +103,7 @@ build_flags =
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_lora32_v2.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/ui/*.cpp>
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<../examples/companion_radio>
|
||||
lib_deps =
|
||||
${Heltec_lora32_v2.lib_deps}
|
||||
|
||||
@@ -14,6 +14,7 @@ WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
ESP32RTCClock fallback_clock;
|
||||
AutoDiscoverRTCClock rtc_clock(fallback_clock);
|
||||
SensorManager sensors;
|
||||
|
||||
#ifndef LORA_CR
|
||||
#define LORA_CR 5
|
||||
|
||||
@@ -6,10 +6,12 @@
|
||||
#include <helpers/HeltecV2Board.h>
|
||||
#include <helpers/CustomSX1276Wrapper.h>
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
#include <helpers/SensorManager.h>
|
||||
|
||||
extern HeltecV2Board board;
|
||||
extern WRAPPER_CLASS radio_driver;
|
||||
extern AutoDiscoverRTCClock rtc_clock;
|
||||
extern SensorManager sensors;
|
||||
|
||||
bool radio_init();
|
||||
uint32_t radio_get_rng_seed();
|
||||
|
||||
@@ -12,6 +12,7 @@ build_flags =
|
||||
-D PIN_BOARD_SDA=17
|
||||
-D PIN_BOARD_SCL=18
|
||||
-D PIN_USER_BTN=0
|
||||
-D PIN_VEXT_EN=36
|
||||
-D SX126X_DIO2_AS_RF_SWITCH=true
|
||||
-D SX126X_DIO3_TCXO_VOLTAGE=1.8
|
||||
-D SX126X_CURRENT_LIMIT=130.0f ; for best TX power!
|
||||
@@ -28,13 +29,14 @@ build_flags =
|
||||
${Heltec_lora32_v3.build_flags}
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D ADVERT_NAME='"Heltec Repeater"'
|
||||
-D ADVERT_LAT=-37.0
|
||||
-D ADVERT_LON=145.0
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MESH_PACKET_LOGGING=1
|
||||
-D MAX_NEIGHBOURS=8
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_lora32_v3.build_src_filter}
|
||||
+<helpers/ui/*.cpp>
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<../examples/simple_repeater>
|
||||
lib_deps =
|
||||
${Heltec_lora32_v3.lib_deps}
|
||||
@@ -46,14 +48,14 @@ build_flags =
|
||||
${Heltec_lora32_v3.build_flags}
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D ADVERT_NAME='"Heltec Room"'
|
||||
-D ADVERT_LAT=-37.0
|
||||
-D ADVERT_LON=145.0
|
||||
-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_lora32_v3.build_src_filter}
|
||||
+<helpers/ui/*.cpp>
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<../examples/simple_room_server>
|
||||
lib_deps =
|
||||
${Heltec_lora32_v3.lib_deps}
|
||||
@@ -85,7 +87,7 @@ build_flags =
|
||||
; 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}
|
||||
+<helpers/ui/*.cpp>
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<../examples/companion_radio>
|
||||
lib_deps =
|
||||
${Heltec_lora32_v3.lib_deps}
|
||||
@@ -105,7 +107,7 @@ build_flags =
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_lora32_v3.build_src_filter}
|
||||
+<helpers/ui/*.cpp>
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<../examples/companion_radio>
|
||||
lib_deps =
|
||||
@@ -127,7 +129,7 @@ build_flags =
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_lora32_v3.build_src_filter}
|
||||
+<helpers/ui/*.cpp>
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<../examples/companion_radio>
|
||||
lib_deps =
|
||||
@@ -139,10 +141,11 @@ extends = Heltec_lora32_v3
|
||||
build_flags =
|
||||
${Heltec_lora32_v3.build_flags}
|
||||
-D ADVERT_NAME='"Heltec Repeater"'
|
||||
-D ADVERT_LAT=-37.0
|
||||
-D ADVERT_LON=145.0
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MESH_PACKET_LOGGING=1
|
||||
-D MAX_NEIGHBOURS=8
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_lora32_v3.build_src_filter}
|
||||
+<../examples/simple_repeater>
|
||||
@@ -157,8 +160,8 @@ build_src_filter = ${Heltec_lora32_v3.build_src_filter}
|
||||
build_flags =
|
||||
${Heltec_lora32_v3.build_flags}
|
||||
-D ADVERT_NAME='"Heltec Room"'
|
||||
-D ADVERT_LAT=-37.0
|
||||
-D ADVERT_LON=145.0
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D ROOM_PASSWORD='"hello"'
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
|
||||
@@ -14,6 +14,7 @@ WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
ESP32RTCClock fallback_clock;
|
||||
AutoDiscoverRTCClock rtc_clock(fallback_clock);
|
||||
SensorManager sensors;
|
||||
|
||||
#ifndef LORA_CR
|
||||
#define LORA_CR 5
|
||||
|
||||
@@ -6,10 +6,12 @@
|
||||
#include <helpers/HeltecV3Board.h>
|
||||
#include <helpers/CustomSX1262Wrapper.h>
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
#include <helpers/SensorManager.h>
|
||||
|
||||
extern HeltecV3Board board;
|
||||
extern WRAPPER_CLASS radio_driver;
|
||||
extern AutoDiscoverRTCClock rtc_clock;
|
||||
extern SensorManager sensors;
|
||||
|
||||
bool radio_init();
|
||||
uint32_t radio_get_rng_seed();
|
||||
|
||||
@@ -39,13 +39,14 @@ build_flags =
|
||||
${LilyGo_T3S3_sx1262.build_flags}
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D ADVERT_NAME='"T3S3-1262 Repeater"'
|
||||
-D ADVERT_LAT=-37.0
|
||||
-D ADVERT_LON=145.0
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=8
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${LilyGo_T3S3_sx1262.build_src_filter}
|
||||
+<helpers/ui/*.cpp>
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<../examples/simple_repeater>
|
||||
lib_deps =
|
||||
${LilyGo_T3S3_sx1262.lib_deps}
|
||||
@@ -71,14 +72,14 @@ build_flags =
|
||||
${LilyGo_T3S3_sx1262.build_flags}
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D ADVERT_NAME='"T3S3-1262 Room"'
|
||||
-D ADVERT_LAT=-37.0
|
||||
-D ADVERT_LON=145.0
|
||||
-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 = ${LilyGo_T3S3_sx1262.build_src_filter}
|
||||
+<helpers/ui/*.cpp>
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<../examples/simple_room_server>
|
||||
lib_deps =
|
||||
${LilyGo_T3S3_sx1262.lib_deps}
|
||||
@@ -96,7 +97,7 @@ build_flags =
|
||||
; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1
|
||||
; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1
|
||||
build_src_filter = ${LilyGo_T3S3_sx1262.build_src_filter}
|
||||
+<helpers/ui/*.cpp>
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<../examples/companion_radio>
|
||||
lib_deps =
|
||||
${LilyGo_T3S3_sx1262.lib_deps}
|
||||
@@ -117,7 +118,7 @@ build_flags =
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${LilyGo_T3S3_sx1262.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/ui/*.cpp>
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<../examples/companion_radio>
|
||||
lib_deps =
|
||||
${LilyGo_T3S3_sx1262.lib_deps}
|
||||
|
||||
@@ -14,6 +14,7 @@ WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
ESP32RTCClock fallback_clock;
|
||||
AutoDiscoverRTCClock rtc_clock(fallback_clock);
|
||||
SensorManager sensors;
|
||||
|
||||
#ifndef LORA_CR
|
||||
#define LORA_CR 5
|
||||
|
||||
@@ -6,10 +6,12 @@
|
||||
#include <helpers/ESP32Board.h>
|
||||
#include <helpers/CustomSX1262Wrapper.h>
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
#include <helpers/SensorManager.h>
|
||||
|
||||
extern ESP32Board board;
|
||||
extern WRAPPER_CLASS radio_driver;
|
||||
extern AutoDiscoverRTCClock rtc_clock;
|
||||
extern SensorManager sensors;
|
||||
|
||||
bool radio_init();
|
||||
uint32_t radio_get_rng_seed();
|
||||
|
||||
@@ -27,20 +27,39 @@ build_flags =
|
||||
${LilyGo_TBeam.build_flags}
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D MAX_CONTACTS=100
|
||||
-D MAX_GROUP_CHANNELS=1
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D BLE_PIN_CODE=123456
|
||||
-D BLE_DEBUG_LOGGING=1
|
||||
-D MESH_PACKET_LOGGING=1
|
||||
-D MESH_DEBUG=1
|
||||
-D RADIOLIB_DEBUG_BASIC=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
; -D RADIOLIB_DEBUG_BASIC=1
|
||||
; -D ENABLE_PRIVATE_KEY_IMPORT=1
|
||||
; -D ENABLE_PRIVATE_KEY_EXPORT=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${LilyGo_TBeam.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/ui/*.cpp>
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<../examples/companion_radio>
|
||||
lib_deps =
|
||||
${LilyGo_TBeam.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Tbeam_repeater]
|
||||
extends = LilyGo_TBeam
|
||||
build_flags =
|
||||
${LilyGo_TBeam.build_flags}
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D ADVERT_NAME='"Tbeam Repeater"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=8
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${LilyGo_TBeam.build_src_filter}
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<../examples/simple_repeater>
|
||||
lib_deps =
|
||||
${LilyGo_TBeam.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
@@ -5,15 +5,16 @@ TBeamBoard 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);
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_0, P_LORA_RESET, P_LORA_DIO_1, spi);
|
||||
#else
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_0, P_LORA_RESET, P_LORA_DIO_1);
|
||||
#endif
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
ESP32RTCClock fallback_clock;
|
||||
AutoDiscoverRTCClock rtc_clock(fallback_clock);
|
||||
SensorManager sensors;
|
||||
|
||||
#ifndef LORA_CR
|
||||
#define LORA_CR 5
|
||||
|
||||
@@ -6,10 +6,12 @@
|
||||
#include <helpers/TBeamBoard.h>
|
||||
#include <helpers/CustomSX1276Wrapper.h>
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
#include <helpers/SensorManager.h>
|
||||
|
||||
extern TBeamBoard board;
|
||||
extern WRAPPER_CLASS radio_driver;
|
||||
extern AutoDiscoverRTCClock rtc_clock;
|
||||
extern SensorManager sensors;
|
||||
|
||||
bool radio_init();
|
||||
uint32_t radio_get_rng_seed();
|
||||
|
||||
69
variants/lilygo_tbeam_SX1262/platformio.ini
Normal file
69
variants/lilygo_tbeam_SX1262/platformio.ini
Normal file
@@ -0,0 +1,69 @@
|
||||
[LilyGo_TBeam_SX1262]
|
||||
extends = esp32_base
|
||||
board = ttgo-t-beam
|
||||
build_flags =
|
||||
${esp32_base.build_flags}
|
||||
-I variants/lilygo_tbeam_SX1262
|
||||
-D LILYGO_TBEAM_SX1262
|
||||
-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 RADIO_CLASS=CustomSX1262
|
||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||
-D LORA_TX_POWER=22
|
||||
-D P_LORA_TX_LED=4
|
||||
-D PIN_BOARD_SDA=21
|
||||
-D PIN_BOARD_SCL=22
|
||||
-D PIN_USER_BTN=38
|
||||
build_src_filter = ${esp32_base.build_src_filter}
|
||||
+<../variants/lilygo_tbeam_SX1262>
|
||||
board_build.partitions = min_spiffs.csv ; get around 4mb flash limit
|
||||
lib_deps =
|
||||
${esp32_base.lib_deps}
|
||||
lewisxhe/XPowersLib@^0.2.7
|
||||
adafruit/Adafruit SSD1306 @ ^2.5.13
|
||||
|
||||
[env:Tbeam_SX1262_companion_radio_ble]
|
||||
extends = LilyGo_TBeam_SX1262
|
||||
board_build.upload.maximum_ram_size=2000000
|
||||
build_flags =
|
||||
${LilyGo_TBeam_SX1262.build_flags}
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D MAX_CONTACTS=100
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D BLE_PIN_CODE=123456
|
||||
; -D BLE_DEBUG_LOGGING=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
; -D RADIOLIB_DEBUG_BASIC=1
|
||||
; -D ENABLE_PRIVATE_KEY_IMPORT=1
|
||||
; -D ENABLE_PRIVATE_KEY_EXPORT=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${LilyGo_TBeam_SX1262.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<../examples/companion_radio>
|
||||
lib_deps =
|
||||
${LilyGo_TBeam_SX1262.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Tbeam_SX1262_repeater]
|
||||
extends = LilyGo_TBeam_SX1262
|
||||
build_flags =
|
||||
${LilyGo_TBeam_SX1262.build_flags}
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D ADVERT_NAME='"Tbeam SX1262 Repeater"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=8
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${LilyGo_TBeam_SX1262.build_src_filter}
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<../examples/simple_repeater>
|
||||
lib_deps =
|
||||
${LilyGo_TBeam_SX1262.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
66
variants/lilygo_tbeam_SX1262/target.cpp
Normal file
66
variants/lilygo_tbeam_SX1262/target.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
|
||||
TBeamBoardSX1262 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);
|
||||
SensorManager sensors;
|
||||
|
||||
#ifndef LORA_CR
|
||||
#define LORA_CR 5
|
||||
#endif
|
||||
|
||||
bool radio_init() {
|
||||
fallback_clock.begin();
|
||||
rtc_clock.begin(Wire);
|
||||
|
||||
#ifdef SX126X_DIO3_TCXO_VOLTAGE
|
||||
float tcxo = SX126X_DIO3_TCXO_VOLTAGE;
|
||||
#else
|
||||
float tcxo = 1.6f;
|
||||
#endif
|
||||
|
||||
#if defined(P_LORA_SCLK)
|
||||
spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI);
|
||||
#endif
|
||||
int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 8, tcxo);
|
||||
if (status != RADIOLIB_ERR_NONE) {
|
||||
Serial.print("ERROR: radio init failed: ");
|
||||
Serial.println(status);
|
||||
return false; // fail
|
||||
}
|
||||
|
||||
radio.setCRC(1);
|
||||
|
||||
return true; // success
|
||||
}
|
||||
|
||||
uint32_t radio_get_rng_seed() {
|
||||
return radio.random(0x7FFFFFFF);
|
||||
}
|
||||
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) {
|
||||
radio.setFrequency(freq);
|
||||
radio.setSpreadingFactor(sf);
|
||||
radio.setBandwidth(bw);
|
||||
radio.setCodingRate(cr);
|
||||
}
|
||||
|
||||
void radio_set_tx_power(uint8_t dbm) {
|
||||
radio.setOutputPower(dbm);
|
||||
}
|
||||
|
||||
mesh::LocalIdentity radio_new_identity() {
|
||||
RadioNoiseListener rng(radio);
|
||||
return mesh::LocalIdentity(&rng); // create new random identity
|
||||
}
|
||||
20
variants/lilygo_tbeam_SX1262/target.h
Normal file
20
variants/lilygo_tbeam_SX1262/target.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#define RADIOLIB_STATIC_ONLY 1
|
||||
#include <RadioLib.h>
|
||||
#include <helpers/RadioLibWrappers.h>
|
||||
#include <helpers/TBeamBoardSX1262.h>
|
||||
#include <helpers/CustomSX1262Wrapper.h>
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
#include <helpers/SensorManager.h>
|
||||
|
||||
extern TBeamBoardSX1262 board;
|
||||
extern WRAPPER_CLASS radio_driver;
|
||||
extern AutoDiscoverRTCClock rtc_clock;
|
||||
extern SensorManager 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();
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user