mirror of
https://github.com/meshcore-dev/MeshCore.git
synced 2026-03-30 21:25:46 +00:00
Compare commits
100 Commits
room-serve
...
repeater-v
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6b469fa47 | ||
|
|
f74819f8db | ||
|
|
fccb3b6c39 | ||
|
|
7947e8a2d8 | ||
|
|
da8bd717a4 | ||
|
|
1930dc347e | ||
|
|
df33321bdc | ||
|
|
2c9a2ee18f | ||
|
|
8c104b8a8f | ||
|
|
9117798a41 | ||
|
|
4a2978736e | ||
|
|
3c92c6aa3b | ||
|
|
f9e595687e | ||
|
|
be68aaed20 | ||
|
|
339ee035aa | ||
|
|
ced14d65db | ||
|
|
854a8dfe2f | ||
|
|
0d1b5b17d3 | ||
|
|
fc541bdf42 | ||
|
|
fe2616d19c | ||
|
|
7958b920fa | ||
|
|
10bb05c31a | ||
|
|
6aa41bd67d | ||
|
|
78cd655789 | ||
|
|
e8b1f317f3 | ||
|
|
cd1cf71f39 | ||
|
|
55a259b0a1 | ||
|
|
75486f5d41 | ||
|
|
90db5f7e39 | ||
|
|
ed7ca6fb60 | ||
|
|
cdd44212a1 | ||
|
|
9d0dd7947f | ||
|
|
5f7bd0fe77 | ||
|
|
781f7e99f6 | ||
|
|
797ab85283 | ||
|
|
91b911320b | ||
|
|
7d47608985 | ||
|
|
541cd8cfd9 | ||
|
|
2715058eb2 | ||
|
|
112b360ef4 | ||
|
|
29435342b0 | ||
|
|
9cecbad2a7 | ||
|
|
ac834922de | ||
|
|
de3e4bc27c | ||
|
|
810b1f8fe7 | ||
|
|
7fb7b69bbc | ||
|
|
d3831821c7 | ||
|
|
1c7c5ecb2b | ||
|
|
58f4db1f19 | ||
|
|
00ebb090e7 | ||
|
|
35374947ba | ||
|
|
d30412bf65 | ||
|
|
02645be9df | ||
|
|
67f9204e88 | ||
|
|
992c8e49d4 | ||
|
|
987c42409a | ||
|
|
71f46ddaea | ||
|
|
0f2f1bc8be | ||
|
|
5ec89dff5b | ||
|
|
62f1ab4b06 | ||
|
|
88cbe3fddc | ||
|
|
e47755c8e9 | ||
|
|
99e6b75743 | ||
|
|
0914056a09 | ||
|
|
7ea6a98513 | ||
|
|
013787556d | ||
|
|
54890421bb | ||
|
|
0ddd3b9ade | ||
|
|
ae5e3588ba | ||
|
|
d32fa5c004 | ||
|
|
aa3c702ffd | ||
|
|
fa481e832b | ||
|
|
ff9699c071 | ||
|
|
2c1f61c03d | ||
|
|
71255e00f1 | ||
|
|
2941388041 | ||
|
|
3d70a0d02c | ||
|
|
3d2404f249 | ||
|
|
cf35daddc2 | ||
|
|
2bb7e6dad4 | ||
|
|
74818d0594 | ||
|
|
484b7b8144 | ||
|
|
cb423bcb71 | ||
|
|
837870169a | ||
|
|
90656e7d06 | ||
|
|
d82b2a28e4 | ||
|
|
eb978f1b50 | ||
|
|
910ec59887 | ||
|
|
d23378cff6 | ||
|
|
ec98d5f8a5 | ||
|
|
ca422bbafb | ||
|
|
70a9990f45 | ||
|
|
6440bcaf48 | ||
|
|
ad2e015a5b | ||
|
|
dcb7ffa92e | ||
|
|
539f99a90f | ||
|
|
3832836eb2 | ||
|
|
af2628bb00 | ||
|
|
6f94c8148a | ||
|
|
8c80c10d2a |
14
README.md
14
README.md
@@ -74,17 +74,7 @@ They can also be managed via LoRa in the mobile app by using the Remote Manageme
|
||||
|
||||
## 🛠 Hardware Compatibility
|
||||
|
||||
MeshCore is designed for use with:
|
||||
* Heltec V3 LoRa Boards
|
||||
* RAK4631
|
||||
* XiaoS3 WIO (sx1262 combo)
|
||||
* XiaoC3 (plus external sx126x module)
|
||||
* LilyGo T3S3
|
||||
* Heltec T114
|
||||
* Station G2
|
||||
* Sensecap T1000e
|
||||
* Heltec V2
|
||||
* LilyGo TLora32 v1.6
|
||||
MeshCore is designed for devices listed in the [MeshCore Flasher](https://flasher.meshcore.co.uk)
|
||||
|
||||
## 📜 License
|
||||
|
||||
@@ -104,7 +94,7 @@ Here are some general principals you should try to adhere to:
|
||||
|
||||
- Report bugs and request features on the [GitHub Issues](https://github.com/ripplebiz/MeshCore/issues) page.
|
||||
- Find additional guides and components on [my site](https://buymeacoffee.com/ripplebiz).
|
||||
- Join [Andy Kirby's Discord](https://discord.gg/GBxVx2JMAy) to chat with the developers and get help from the community.
|
||||
- Join [MeshCore Discord](https://discord.gg/BMwCtwHj5V) to chat with the developers and get help from the community.
|
||||
|
||||
## RAK Wireless Board Support in PlatformIO
|
||||
|
||||
|
||||
59
boards/minewsemi_me25ls01.json
Normal file
59
boards/minewsemi_me25ls01.json
Normal file
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v7.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DARDUINO_WIO_WM1110 -DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [
|
||||
["0x239A", "0x8029"],
|
||||
["0x239A", "0x0029"],
|
||||
["0x239A", "0x002A"],
|
||||
["0x239A", "0x802A"]
|
||||
],
|
||||
"usb_product": "me25ls01-BOOT",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "minewsemi_me25ls01",
|
||||
"bsp": {
|
||||
"name": "adafruit"
|
||||
},
|
||||
"softdevice": {
|
||||
"sd_flags": "-DS140",
|
||||
"sd_name": "s140",
|
||||
"sd_version": "7.3.0",
|
||||
"sd_fwid": "0x0123"
|
||||
},
|
||||
"bootloader": {
|
||||
"settings_addr": "0xFF000"
|
||||
}
|
||||
},
|
||||
"connectivity": ["bluetooth"],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"svd_path": "nrf52840.svd",
|
||||
"openocd_target": "nrf52.cfg"
|
||||
},
|
||||
"frameworks": ["arduino"],
|
||||
"name": "Minewsemi ME25LS01",
|
||||
"upload": {
|
||||
"maximum_ram_size": 248832,
|
||||
"maximum_size": 815104,
|
||||
"speed": 115200,
|
||||
"protocol": "nrfutil",
|
||||
"protocols": [
|
||||
"jlink",
|
||||
"nrfjprog",
|
||||
"nrfutil",
|
||||
"stlink",
|
||||
"cmsis-dap",
|
||||
"blackmagic"
|
||||
],
|
||||
"use_1200bps_touch": true,
|
||||
"require_upload_port": true,
|
||||
"wait_for_upload_port": true
|
||||
},
|
||||
"url": "https://en.minewsemi.com/lora-module/lr1110-nrf52840-me25LS01",
|
||||
"vendor": "MINEWSEMI"
|
||||
}
|
||||
61
boards/seeed-wio-tracker-l1.json
Normal file
61
boards/seeed-wio-tracker-l1.json
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v7.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DARDUINO_SEEED_WIO_TRACKER_L1 -DNRF52840_XXAA -DSEEED_WIO_TRACKER_L1 ",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [
|
||||
[ "0x2886", "0x1667" ],
|
||||
[ "0x2886", "0x1668" ]
|
||||
],
|
||||
"mcu": "nrf52840",
|
||||
"variant": "Seeed_Wio_Tracker_L1",
|
||||
"softdevice": {
|
||||
"sd_flags": "-DS140",
|
||||
"sd_name": "s140",
|
||||
"sd_version": "7.3.0",
|
||||
"sd_fwid": "0x0123"
|
||||
},
|
||||
"bsp": {
|
||||
"name": "adafruit"
|
||||
},
|
||||
"bootloader": {
|
||||
"settings_addr": "0xFF000"
|
||||
},
|
||||
"usb_product": "Seeed Wio Tracker L1"
|
||||
},
|
||||
"connectivity": [
|
||||
"bluetooth"
|
||||
],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"openocd_target": "nrf52.cfg",
|
||||
"svd_path": "nrf52840.svd"
|
||||
},
|
||||
"frameworks": [
|
||||
"arduino"
|
||||
],
|
||||
"name": "Seeed Wio Tracker L1",
|
||||
"upload": {
|
||||
"maximum_ram_size": 248832,
|
||||
"maximum_size": 815104,
|
||||
"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/wio_tracker_l1_node/",
|
||||
"vendor": "Seeed Studio"
|
||||
}
|
||||
60
boards/seeed_sensecap_solar.json
Normal file
60
boards/seeed_sensecap_solar.json
Normal file
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"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", "0x0059" ]
|
||||
],
|
||||
"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": 248832,
|
||||
"maximum_size": 815104,
|
||||
"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/meshtastic_solar_node/",
|
||||
"vendor": "Seeed Studio"
|
||||
}
|
||||
@@ -179,7 +179,7 @@ The T-Deck firmware is free to download and most features are available without
|
||||
|
||||
In UK and EU, 867.5MHz is not allowed to use 250kHz bandwidth and it only allows 2.5% duty cycle for clients. 869.525Mhz allows an airtime of 10%, 250KHz bandwidth, and a higher EIRP, therefore MeshCore nodes can send more often and with more power. That is why this frequency is chosen for UK and EU. This is also why Meshtastic also uses this frequency.
|
||||
|
||||
[Source]([https://](https://discord.com/channels/826570251612323860/1330643963501351004/1356540643853209641))
|
||||
[Source](https://discord.com/channels/826570251612323860/1330643963501351004/1356540643853209641)
|
||||
|
||||
the rest of the radio settings are the same for all frequencies:
|
||||
- Spread Factor (SF): 11
|
||||
@@ -271,7 +271,7 @@ You can get the latitude and longitude from Google Maps by right-clicking the lo
|
||||
|
||||
GPS on T-Deck is always enabled. You can skip the "GPS clock sync" and the T-Deck will continue to try to get a GPS lock. You can go to the `GPS Info` screen; you should see the `Sentences:` counter increasing if the baud rate is correct.
|
||||
|
||||
[Source]([https://](https://discord.com/channels/826570251612323860/1330643963501351004/1356609240302616689))
|
||||
[Source](https://discord.com/channels/826570251612323860/1330643963501351004/1356609240302616689)
|
||||
|
||||
### 4.3. Q: Why is my OG (non-Plus) T-Deck not getting any satellite lock?
|
||||
**A:** The OG (non-Plus) T-Deck doesn't come with a GPS. If you added a GPS to your OG T-Deck, please refer to the manual of your GPS to see what baud rate it requires. Alternatively, you can try to set the baud rate from 9600, 19200, etc., and up to 115200 to see which one works.
|
||||
@@ -396,7 +396,7 @@ The third character is the capital letter 'O', not zero `0`
|
||||
- Firmware repo: <https://github.com/ripplebiz/MeshCore>
|
||||
|
||||
### 5.8. Q: How can I support MeshCore?
|
||||
**A:** Provide your honest feedback on GitHub and on AndyKirby's Discord server <http://discord.com/invite/H62Re4DCeD>. Spread the word of MeshCore to your friends and communities; help them get started with MeshCore. Support Scott's MeshCore development at <https://buymeacoffee.com/ripplebiz>.
|
||||
**A:** Provide your honest feedback on GitHub and on [MeshCore Discord server](https://discord.gg/BMwCtwHj5V). Spread the word of MeshCore to your friends and communities; help them get started with MeshCore. Support Scott's MeshCore development at <https://buymeacoffee.com/ripplebiz>.
|
||||
|
||||
Support Liam Cottle's smartphone client development by unlocking the server administration wait gate with in-app purchase
|
||||
|
||||
|
||||
@@ -45,6 +45,10 @@
|
||||
#define CMD_GET_CUSTOM_VARS 40
|
||||
#define CMD_SET_CUSTOM_VAR 41
|
||||
#define CMD_GET_ADVERT_PATH 42
|
||||
#define CMD_GET_TUNING_PARAMS 43
|
||||
// NOTE: CMD range 44..49 parked, potentially for WiFi operations
|
||||
#define CMD_SEND_BINARY_REQ 50
|
||||
#define CMD_FACTORY_RESET 51
|
||||
|
||||
#define RESP_CODE_OK 0
|
||||
#define RESP_CODE_ERR 1
|
||||
@@ -69,6 +73,7 @@
|
||||
#define RESP_CODE_SIGNATURE 20
|
||||
#define RESP_CODE_CUSTOM_VARS 21
|
||||
#define RESP_CODE_ADVERT_PATH 22
|
||||
#define RESP_CODE_TUNING_PARAMS 23
|
||||
|
||||
#define SEND_TIMEOUT_BASE_MILLIS 500
|
||||
#define FLOOD_SEND_TIMEOUT_FACTOR 16.0f
|
||||
@@ -91,6 +96,7 @@
|
||||
#define PUSH_CODE_TRACE_DATA 0x89
|
||||
#define PUSH_CODE_NEW_ADVERT 0x8A
|
||||
#define PUSH_CODE_TELEMETRY_RESPONSE 0x8B
|
||||
#define PUSH_CODE_BINARY_RESPONSE 0x8C
|
||||
|
||||
#define ERR_CODE_UNSUPPORTED_CMD 1
|
||||
#define ERR_CODE_NOT_FOUND 2
|
||||
@@ -319,13 +325,17 @@ void MyMesh::queueMessage(const ContactInfo &from, uint8_t txt_type, mesh::Packe
|
||||
uint8_t frame[1];
|
||||
frame[0] = PUSH_CODE_MSG_WAITING; // send push 'tickle'
|
||||
_serial->writeFrame(frame, 1);
|
||||
} else {
|
||||
#ifdef DISPLAY_CLASS
|
||||
ui_task.soundBuzzer(UIEventType::contactMessage);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
ui_task.newMsg(path_len, from.name, text, offline_queue_len);
|
||||
// we only want to show text messages on display, not cli data
|
||||
bool should_display = txt_type == TXT_TYPE_PLAIN || txt_type == TXT_TYPE_SIGNED_PLAIN;
|
||||
if (should_display) {
|
||||
ui_task.newMsg(path_len, from.name, text, offline_queue_len);
|
||||
if (!_serial->isConnected()) {
|
||||
ui_task.soundBuzzer(UIEventType::contactMessage);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -462,6 +472,7 @@ void MyMesh::onContactResponse(const ContactInfo &contact, const uint8_t *data,
|
||||
i += 6; // pub_key_prefix
|
||||
memcpy(&out_frame[i], &tag, 4);
|
||||
i += 4; // NEW: include server timestamp
|
||||
out_frame[i++] = data[7]; // NEW (v7): ACL permissions
|
||||
} else {
|
||||
out_frame[i++] = PUSH_CODE_LOGIN_FAIL;
|
||||
out_frame[i++] = 0; // reserved
|
||||
@@ -484,7 +495,7 @@ void MyMesh::onContactResponse(const ContactInfo &contact, const uint8_t *data,
|
||||
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
|
||||
} else if (len > 4 && tag == pending_telemetry) { // check for matching response tag
|
||||
pending_telemetry = 0;
|
||||
|
||||
int i = 0;
|
||||
@@ -495,6 +506,17 @@ void MyMesh::onContactResponse(const ContactInfo &contact, const uint8_t *data,
|
||||
memcpy(&out_frame[i], &data[4], len - 4);
|
||||
i += (len - 4);
|
||||
_serial->writeFrame(out_frame, i);
|
||||
} else if (len > 4 && tag == pending_req) { // check for matching response tag
|
||||
pending_req = 0;
|
||||
|
||||
int i = 0;
|
||||
out_frame[i++] = PUSH_CODE_BINARY_RESPONSE;
|
||||
out_frame[i++] = 0; // reserved
|
||||
memcpy(&out_frame[i], &tag, 4); // app needs to match this to RESP_CODE_SENT.tag
|
||||
i += 4;
|
||||
memcpy(&out_frame[i], &data[4], len - 4);
|
||||
i += (len - 4);
|
||||
_serial->writeFrame(out_frame, i);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -560,7 +582,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
|
||||
_cli_rescue = false;
|
||||
offline_queue_len = 0;
|
||||
app_target_ver = 0;
|
||||
pending_login = pending_status = pending_telemetry = 0;
|
||||
pending_login = pending_status = pending_telemetry = pending_req = 0;
|
||||
next_ack_idx = 0;
|
||||
sign_data = NULL;
|
||||
dirty_contacts_expiry = 0;
|
||||
@@ -1012,6 +1034,13 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
_prefs.airtime_factor = ((float)af) / 1000.0f;
|
||||
savePrefs();
|
||||
writeOKFrame();
|
||||
} else if (cmd_frame[0] == CMD_GET_TUNING_PARAMS) {
|
||||
uint32_t rx = _prefs.rx_delay_base * 1000, af = _prefs.airtime_factor * 1000;
|
||||
int i = 0;
|
||||
out_frame[i++] = RESP_CODE_TUNING_PARAMS;
|
||||
memcpy(&out_frame[i], &rx, 4); i += 4;
|
||||
memcpy(&out_frame[i], &af, 4); i += 4;
|
||||
_serial->writeFrame(out_frame, i);
|
||||
} else if (cmd_frame[0] == CMD_SET_OTHER_PARAMS) {
|
||||
_prefs.manual_add_contacts = cmd_frame[1];
|
||||
if (len >= 3) {
|
||||
@@ -1090,7 +1119,7 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
if (result == MSG_SEND_FAILED) {
|
||||
writeErrFrame(ERR_CODE_TABLE_FULL);
|
||||
} else {
|
||||
pending_telemetry = pending_status = 0;
|
||||
pending_req = 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;
|
||||
@@ -1110,7 +1139,7 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
if (result == MSG_SEND_FAILED) {
|
||||
writeErrFrame(ERR_CODE_TABLE_FULL);
|
||||
} else {
|
||||
pending_telemetry = pending_login = 0;
|
||||
pending_req = 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;
|
||||
@@ -1122,7 +1151,7 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_NOT_FOUND); // contact not found
|
||||
}
|
||||
} else if (cmd_frame[0] == CMD_SEND_TELEMETRY_REQ && len >= 4 + PUB_KEY_SIZE) {
|
||||
} else if (cmd_frame[0] == CMD_SEND_TELEMETRY_REQ && len >= 4 + PUB_KEY_SIZE) { // can deprecate, in favour of CMD_SEND_BINARY_REQ
|
||||
uint8_t *pub_key = &cmd_frame[4];
|
||||
ContactInfo *recipient = lookupContactByPubKey(pub_key, PUB_KEY_SIZE);
|
||||
if (recipient) {
|
||||
@@ -1131,7 +1160,7 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
if (result == MSG_SEND_FAILED) {
|
||||
writeErrFrame(ERR_CODE_TABLE_FULL);
|
||||
} else {
|
||||
pending_status = pending_login = 0;
|
||||
pending_status = pending_login = pending_req = 0;
|
||||
pending_telemetry = tag; // match this in onContactResponse()
|
||||
out_frame[0] = RESP_CODE_SENT;
|
||||
out_frame[1] = (result == MSG_SEND_SENT_FLOOD) ? 1 : 0;
|
||||
@@ -1157,6 +1186,27 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
memcpy(&out_frame[i], telemetry.getBuffer(), tlen);
|
||||
i += tlen;
|
||||
_serial->writeFrame(out_frame, i);
|
||||
} else if (cmd_frame[0] == CMD_SEND_BINARY_REQ && len >= 2 + PUB_KEY_SIZE) {
|
||||
uint8_t *pub_key = &cmd_frame[1];
|
||||
ContactInfo *recipient = lookupContactByPubKey(pub_key, PUB_KEY_SIZE);
|
||||
if (recipient) {
|
||||
uint8_t *req_data = &cmd_frame[1 + PUB_KEY_SIZE];
|
||||
uint32_t tag, est_timeout;
|
||||
int result = sendRequest(*recipient, req_data, len - (1 + PUB_KEY_SIZE), tag, est_timeout);
|
||||
if (result == MSG_SEND_FAILED) {
|
||||
writeErrFrame(ERR_CODE_TABLE_FULL);
|
||||
} else {
|
||||
pending_status = pending_login = pending_telemetry = 0;
|
||||
pending_req = 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);
|
||||
}
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_NOT_FOUND); // contact not found
|
||||
}
|
||||
} else if (cmd_frame[0] == CMD_HAS_CONNECTION && len >= 1 + PUB_KEY_SIZE) {
|
||||
uint8_t *pub_key = &cmd_frame[1];
|
||||
if (hasConnectionTo(pub_key)) {
|
||||
@@ -1312,6 +1362,15 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_NOT_FOUND);
|
||||
}
|
||||
} else if (cmd_frame[0] == CMD_FACTORY_RESET && memcmp(&cmd_frame[1], "reset", 5) == 0) {
|
||||
bool success = _store->formatFileSystem();
|
||||
if (success) {
|
||||
writeOKFrame();
|
||||
delay(1000);
|
||||
board.reboot(); // doesn't return
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_FILE_IO_ERROR);
|
||||
}
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_UNSUPPORTED_CMD);
|
||||
MESH_DEBUG_PRINTLN("ERROR: unknown command: %02X", cmd_frame[0]);
|
||||
|
||||
@@ -7,14 +7,14 @@
|
||||
#endif
|
||||
|
||||
/*------------ Frame Protocol --------------*/
|
||||
#define FIRMWARE_VER_CODE 6
|
||||
#define FIRMWARE_VER_CODE 7
|
||||
|
||||
#ifndef FIRMWARE_BUILD_DATE
|
||||
#define FIRMWARE_BUILD_DATE "2 Jul 2025"
|
||||
#define FIRMWARE_BUILD_DATE "15 Jul 2025"
|
||||
#endif
|
||||
|
||||
#ifndef FIRMWARE_VERSION
|
||||
#define FIRMWARE_VERSION "v1.7.2"
|
||||
#define FIRMWARE_VERSION "v1.7.3"
|
||||
#endif
|
||||
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
@@ -160,7 +160,8 @@ private:
|
||||
NodePrefs _prefs;
|
||||
uint32_t pending_login;
|
||||
uint32_t pending_status;
|
||||
uint32_t pending_telemetry;
|
||||
uint32_t pending_telemetry; // pending _TELEMETRY_REQ
|
||||
uint32_t pending_req; // pending _BINARY_REQ
|
||||
BaseSerialInterface *_serial;
|
||||
|
||||
ContactsIterator _iter;
|
||||
|
||||
@@ -22,11 +22,11 @@
|
||||
/* ------------------------------ Config -------------------------------- */
|
||||
|
||||
#ifndef FIRMWARE_BUILD_DATE
|
||||
#define FIRMWARE_BUILD_DATE "2 Jul 2025"
|
||||
#define FIRMWARE_BUILD_DATE "15 Jul 2025"
|
||||
#endif
|
||||
|
||||
#ifndef FIRMWARE_VERSION
|
||||
#define FIRMWARE_VERSION "v1.7.2"
|
||||
#define FIRMWARE_VERSION "v1.7.3"
|
||||
#endif
|
||||
|
||||
#ifndef LORA_FREQ
|
||||
@@ -149,7 +149,6 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
||||
oldest->id = id;
|
||||
oldest->out_path_len = -1; // initially out_path is unknown
|
||||
oldest->last_timestamp = 0;
|
||||
self_id.calcSharedSecret(oldest->secret, id); // calc ECDH shared secret
|
||||
return oldest;
|
||||
}
|
||||
|
||||
@@ -341,8 +340,8 @@ protected:
|
||||
return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds
|
||||
}
|
||||
|
||||
void onAnonDataRecv(mesh::Packet* packet, uint8_t type, const mesh::Identity& sender, uint8_t* data, size_t len) override {
|
||||
if (type == PAYLOAD_TYPE_ANON_REQ) { // received an initial request by a possible admin client (unknown at this stage)
|
||||
void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override {
|
||||
if (packet->getPayloadType() == PAYLOAD_TYPE_ANON_REQ) { // received an initial request by a possible admin client (unknown at this stage)
|
||||
uint32_t timestamp;
|
||||
memcpy(×tamp, data, 4);
|
||||
|
||||
@@ -369,6 +368,7 @@ protected:
|
||||
client->last_timestamp = timestamp;
|
||||
client->last_activity = getRTCClock()->getCurrentTime();
|
||||
client->is_admin = is_admin;
|
||||
memcpy(client->secret, secret, PUB_KEY_SIZE);
|
||||
|
||||
uint32_t now = getRTCClock()->getCurrentTimeUnique();
|
||||
memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp
|
||||
@@ -500,12 +500,12 @@ protected:
|
||||
}
|
||||
|
||||
uint8_t temp[166];
|
||||
const char *command = (const char *) &data[5];
|
||||
char *command = (char *) &data[5];
|
||||
char *reply = (char *) &temp[5];
|
||||
if (is_retry) {
|
||||
*reply = 0;
|
||||
} else {
|
||||
_cli.handleCommand(sender_timestamp, command, reply);
|
||||
handleCommand(sender_timestamp, command, reply);
|
||||
}
|
||||
int text_len = strlen(reply);
|
||||
if (text_len > 0) {
|
||||
@@ -581,8 +581,6 @@ public:
|
||||
_prefs.interference_threshold = 0; // disabled
|
||||
}
|
||||
|
||||
CommonCLI* getCLI() { return &_cli; }
|
||||
|
||||
void begin(FILESYSTEM* fs) {
|
||||
mesh::Mesh::begin();
|
||||
_fs = fs;
|
||||
@@ -706,6 +704,18 @@ public:
|
||||
((SimpleMeshTables *)getTables())->resetStats();
|
||||
}
|
||||
|
||||
void handleCommand(uint32_t sender_timestamp, char* command, char* reply) {
|
||||
while (*command == ' ') command++; // skip leading spaces
|
||||
|
||||
if (strlen(command) > 4 && command[2] == '|') { // optional prefix (for companion radio CLI)
|
||||
memcpy(reply, command, 3); // reflect the prefix back
|
||||
reply += 3;
|
||||
command += 3;
|
||||
}
|
||||
|
||||
_cli.handleCommand(sender_timestamp, command, reply); // common CLI commands
|
||||
}
|
||||
|
||||
void loop() {
|
||||
mesh::Mesh::loop();
|
||||
|
||||
@@ -817,7 +827,7 @@ void loop() {
|
||||
if (len > 0 && command[len - 1] == '\r') { // received complete line
|
||||
command[len - 1] = 0; // replace newline with C string null terminator
|
||||
char reply[160];
|
||||
the_mesh.getCLI()->handleCommand(0, command, reply); // NOTE: there is no sender_timestamp via serial!
|
||||
the_mesh.handleCommand(0, command, reply); // NOTE: there is no sender_timestamp via serial!
|
||||
if (reply[0]) {
|
||||
Serial.print(" -> "); Serial.println(reply);
|
||||
}
|
||||
|
||||
@@ -22,11 +22,11 @@
|
||||
/* ------------------------------ Config -------------------------------- */
|
||||
|
||||
#ifndef FIRMWARE_BUILD_DATE
|
||||
#define FIRMWARE_BUILD_DATE "2 Jul 2025"
|
||||
#define FIRMWARE_BUILD_DATE "15 Jul 2025"
|
||||
#endif
|
||||
|
||||
#ifndef FIRMWARE_VERSION
|
||||
#define FIRMWARE_VERSION "v1.7.2"
|
||||
#define FIRMWARE_VERSION "v1.7.3"
|
||||
#endif
|
||||
|
||||
#ifndef LORA_FREQ
|
||||
@@ -188,7 +188,6 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
||||
newClient->id = id;
|
||||
newClient->out_path_len = -1; // initially out_path is unknown
|
||||
newClient->last_timestamp = 0;
|
||||
self_id.calcSharedSecret(newClient->secret, id); // calc ECDH shared secret
|
||||
return newClient;
|
||||
}
|
||||
|
||||
@@ -334,7 +333,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
||||
}
|
||||
return 0; // unknown command
|
||||
}
|
||||
|
||||
|
||||
protected:
|
||||
float getAirtimeBudgetFactor() const override {
|
||||
return _prefs.airtime_factor;
|
||||
@@ -432,8 +431,8 @@ protected:
|
||||
return true;
|
||||
}
|
||||
|
||||
void onAnonDataRecv(mesh::Packet* packet, uint8_t type, const mesh::Identity& sender, uint8_t* data, size_t len) override {
|
||||
if (type == PAYLOAD_TYPE_ANON_REQ) { // received an initial request by a possible admin client (unknown at this stage)
|
||||
void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override {
|
||||
if (packet->getPayloadType() == PAYLOAD_TYPE_ANON_REQ) { // received an initial request by a possible admin client (unknown at this stage)
|
||||
uint32_t sender_timestamp, sender_sync_since;
|
||||
memcpy(&sender_timestamp, data, 4);
|
||||
memcpy(&sender_sync_since, &data[4], 4); // sender's "sync messags SINCE x" timestamp
|
||||
@@ -465,6 +464,7 @@ protected:
|
||||
client->sync_since = sender_sync_since;
|
||||
client->pending_ack = 0;
|
||||
client->push_failures = 0;
|
||||
memcpy(client->secret, secret, PUB_KEY_SIZE);
|
||||
|
||||
uint32_t now = getRTCClock()->getCurrentTime();
|
||||
client->last_activity = now;
|
||||
@@ -555,7 +555,7 @@ protected:
|
||||
if (is_retry) {
|
||||
temp[5] = 0; // no reply
|
||||
} else {
|
||||
_cli.handleCommand(sender_timestamp, (const char *) &data[5], (char *) &temp[5]);
|
||||
handleCommand(sender_timestamp, (char *) &data[5], (char *) &temp[5]);
|
||||
temp[4] = (TXT_TYPE_CLI_DATA << 2); // attempt and flags, (NOTE: legacy was: TXT_TYPE_PLAIN)
|
||||
}
|
||||
send_ack = false;
|
||||
@@ -743,8 +743,6 @@ public:
|
||||
_num_posted = _num_post_pushes = 0;
|
||||
}
|
||||
|
||||
CommonCLI* getCLI() { return &_cli; }
|
||||
|
||||
void begin(FILESYSTEM* fs) {
|
||||
mesh::Mesh::begin();
|
||||
_fs = fs;
|
||||
@@ -845,6 +843,18 @@ public:
|
||||
((SimpleMeshTables *)getTables())->resetStats();
|
||||
}
|
||||
|
||||
void handleCommand(uint32_t sender_timestamp, char* command, char* reply) {
|
||||
while (*command == ' ') command++; // skip leading spaces
|
||||
|
||||
if (strlen(command) > 4 && command[2] == '|') { // optional prefix (for companion radio CLI)
|
||||
memcpy(reply, command, 3); // reflect the prefix back
|
||||
reply += 3;
|
||||
command += 3;
|
||||
}
|
||||
|
||||
_cli.handleCommand(sender_timestamp, command, reply); // common CLI commands
|
||||
}
|
||||
|
||||
void loop() {
|
||||
mesh::Mesh::loop();
|
||||
|
||||
@@ -998,7 +1008,7 @@ void loop() {
|
||||
if (len > 0 && command[len - 1] == '\r') { // received complete line
|
||||
command[len - 1] = 0; // replace newline with C string null terminator
|
||||
char reply[160];
|
||||
the_mesh.getCLI()->handleCommand(0, command, reply); // NOTE: there is no sender_timestamp via serial!
|
||||
the_mesh.handleCommand(0, command, reply); // NOTE: there is no sender_timestamp via serial!
|
||||
if (reply[0]) {
|
||||
Serial.print(" -> "); Serial.println(reply);
|
||||
}
|
||||
|
||||
866
examples/simple_sensor/SensorMesh.cpp
Normal file
866
examples/simple_sensor/SensorMesh.cpp
Normal file
@@ -0,0 +1,866 @@
|
||||
#include "SensorMesh.h"
|
||||
|
||||
/* ------------------------------ Config -------------------------------- */
|
||||
|
||||
#ifndef LORA_FREQ
|
||||
#define LORA_FREQ 915.0
|
||||
#endif
|
||||
#ifndef LORA_BW
|
||||
#define LORA_BW 250
|
||||
#endif
|
||||
#ifndef LORA_SF
|
||||
#define LORA_SF 10
|
||||
#endif
|
||||
#ifndef LORA_CR
|
||||
#define LORA_CR 5
|
||||
#endif
|
||||
#ifndef LORA_TX_POWER
|
||||
#define LORA_TX_POWER 20
|
||||
#endif
|
||||
|
||||
#ifndef ADVERT_NAME
|
||||
#define ADVERT_NAME "sensor"
|
||||
#endif
|
||||
#ifndef ADVERT_LAT
|
||||
#define ADVERT_LAT 0.0
|
||||
#endif
|
||||
#ifndef ADVERT_LON
|
||||
#define ADVERT_LON 0.0
|
||||
#endif
|
||||
|
||||
#ifndef ADMIN_PASSWORD
|
||||
#define ADMIN_PASSWORD "password"
|
||||
#endif
|
||||
|
||||
#ifndef SERVER_RESPONSE_DELAY
|
||||
#define SERVER_RESPONSE_DELAY 300
|
||||
#endif
|
||||
|
||||
#ifndef TXT_ACK_DELAY
|
||||
#define TXT_ACK_DELAY 200
|
||||
#endif
|
||||
|
||||
#ifndef SENSOR_READ_INTERVAL_SECS
|
||||
#define SENSOR_READ_INTERVAL_SECS 60
|
||||
#endif
|
||||
|
||||
/* ------------------------------ Code -------------------------------- */
|
||||
|
||||
#define REQ_TYPE_LOGIN 0x00
|
||||
#define REQ_TYPE_GET_STATUS 0x01
|
||||
#define REQ_TYPE_KEEP_ALIVE 0x02
|
||||
#define REQ_TYPE_GET_TELEMETRY_DATA 0x03
|
||||
#define REQ_TYPE_GET_AVG_MIN_MAX 0x04
|
||||
#define REQ_TYPE_GET_ACCESS_LIST 0x05
|
||||
|
||||
#define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ
|
||||
|
||||
#define CLI_REPLY_DELAY_MILLIS 1000
|
||||
|
||||
#define LAZY_CONTACTS_WRITE_DELAY 5000
|
||||
|
||||
#define ALERT_ACK_EXPIRY_MILLIS 8000 // wait 8 secs for ACKs to alert messages
|
||||
|
||||
static File openAppend(FILESYSTEM* _fs, const char* fname) {
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_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
|
||||
}
|
||||
|
||||
static File openWrite(FILESYSTEM* _fs, const char* filename) {
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
_fs->remove(filename);
|
||||
return _fs->open(filename, FILE_O_WRITE);
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
return _fs->open(filename, "w");
|
||||
#else
|
||||
return _fs->open(filename, "w", true);
|
||||
#endif
|
||||
}
|
||||
|
||||
void SensorMesh::loadContacts() {
|
||||
num_contacts = 0;
|
||||
if (_fs->exists("/s_contacts")) {
|
||||
#if defined(RP2040_PLATFORM)
|
||||
File file = _fs->open("/s_contacts", "r");
|
||||
#else
|
||||
File file = _fs->open("/s_contacts");
|
||||
#endif
|
||||
if (file) {
|
||||
bool full = false;
|
||||
while (!full) {
|
||||
ContactInfo c;
|
||||
uint8_t pub_key[32];
|
||||
uint8_t unused[6];
|
||||
|
||||
bool success = (file.read(pub_key, 32) == 32);
|
||||
success = success && (file.read((uint8_t *) &c.permissions, 1) == 1);
|
||||
success = success && (file.read(unused, 6) == 6);
|
||||
success = success && (file.read((uint8_t *)&c.out_path_len, 1) == 1);
|
||||
success = success && (file.read(c.out_path, 64) == 64);
|
||||
success = success && (file.read(c.shared_secret, PUB_KEY_SIZE) == PUB_KEY_SIZE);
|
||||
c.last_timestamp = 0; // transient
|
||||
c.last_activity = 0;
|
||||
|
||||
if (!success) break; // EOF
|
||||
|
||||
c.id = mesh::Identity(pub_key);
|
||||
if (num_contacts < MAX_CONTACTS) {
|
||||
contacts[num_contacts++] = c;
|
||||
} else {
|
||||
full = true;
|
||||
}
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SensorMesh::saveContacts() {
|
||||
File file = openWrite(_fs, "/s_contacts");
|
||||
if (file) {
|
||||
uint8_t unused[5];
|
||||
memset(unused, 0, sizeof(unused));
|
||||
|
||||
for (int i = 0; i < num_contacts; i++) {
|
||||
auto c = &contacts[i];
|
||||
if (c->permissions == 0) continue; // skip deleted entries
|
||||
|
||||
bool success = (file.write(c->id.pub_key, 32) == 32);
|
||||
success = success && (file.write((uint8_t *) &c->permissions, 1) == 1);
|
||||
success = success && (file.write(unused, 6) == 6);
|
||||
success = success && (file.write((uint8_t *)&c->out_path_len, 1) == 1);
|
||||
success = success && (file.write(c->out_path, 64) == 64);
|
||||
success = success && (file.write(c->shared_secret, PUB_KEY_SIZE) == PUB_KEY_SIZE);
|
||||
|
||||
if (!success) break; // write failed
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t getDataSize(uint8_t type) {
|
||||
switch (type) {
|
||||
case LPP_GPS:
|
||||
return 9;
|
||||
case LPP_POLYLINE:
|
||||
return 8; // TODO: this is MINIMIUM
|
||||
case LPP_GYROMETER:
|
||||
case LPP_ACCELEROMETER:
|
||||
return 6;
|
||||
case LPP_GENERIC_SENSOR:
|
||||
case LPP_FREQUENCY:
|
||||
case LPP_DISTANCE:
|
||||
case LPP_ENERGY:
|
||||
case LPP_UNIXTIME:
|
||||
return 4;
|
||||
case LPP_COLOUR:
|
||||
return 3;
|
||||
case LPP_ANALOG_INPUT:
|
||||
case LPP_ANALOG_OUTPUT:
|
||||
case LPP_LUMINOSITY:
|
||||
case LPP_TEMPERATURE:
|
||||
case LPP_CONCENTRATION:
|
||||
case LPP_BAROMETRIC_PRESSURE:
|
||||
case LPP_RELATIVE_HUMIDITY:
|
||||
case LPP_ALTITUDE:
|
||||
case LPP_VOLTAGE:
|
||||
case LPP_CURRENT:
|
||||
case LPP_DIRECTION:
|
||||
case LPP_POWER:
|
||||
return 2;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static uint32_t getMultiplier(uint8_t type) {
|
||||
switch (type) {
|
||||
case LPP_CURRENT:
|
||||
case LPP_DISTANCE:
|
||||
case LPP_ENERGY:
|
||||
return 1000;
|
||||
case LPP_VOLTAGE:
|
||||
case LPP_ANALOG_INPUT:
|
||||
case LPP_ANALOG_OUTPUT:
|
||||
return 100;
|
||||
case LPP_TEMPERATURE:
|
||||
case LPP_BAROMETRIC_PRESSURE:
|
||||
case LPP_RELATIVE_HUMIDITY:
|
||||
return 10;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static bool isSigned(uint8_t type) {
|
||||
return type == LPP_ALTITUDE || type == LPP_TEMPERATURE || type == LPP_GYROMETER ||
|
||||
type == LPP_ANALOG_INPUT || type == LPP_ANALOG_OUTPUT || type == LPP_GPS || type == LPP_ACCELEROMETER;
|
||||
}
|
||||
|
||||
static float getFloat(const uint8_t * buffer, uint8_t size, uint32_t multiplier, bool is_signed) {
|
||||
uint32_t value = 0;
|
||||
for (uint8_t i = 0; i < size; i++) {
|
||||
value = (value << 8) + buffer[i];
|
||||
}
|
||||
|
||||
int sign = 1;
|
||||
if (is_signed) {
|
||||
uint32_t bit = 1ul << ((size * 8) - 1);
|
||||
if ((value & bit) == bit) {
|
||||
value = (bit << 1) - value;
|
||||
sign = -1;
|
||||
}
|
||||
}
|
||||
return sign * ((float) value / multiplier);
|
||||
}
|
||||
|
||||
static uint8_t putFloat(uint8_t * dest, float value, uint8_t size, uint32_t multiplier, bool is_signed) {
|
||||
// check sign
|
||||
bool sign = value < 0;
|
||||
if (sign) value = -value;
|
||||
|
||||
// get value to store
|
||||
uint32_t v = value * multiplier;
|
||||
|
||||
// format an uint32_t as if it was an int32_t
|
||||
if (is_signed & sign) {
|
||||
uint32_t mask = (1 << (size * 8)) - 1;
|
||||
v = v & mask;
|
||||
if (sign) v = mask - v + 1;
|
||||
}
|
||||
|
||||
// add bytes (MSB first)
|
||||
for (uint8_t i=1; i<=size; i++) {
|
||||
dest[size - i] = (v & 0xFF);
|
||||
v >>= 8;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
uint8_t SensorMesh::handleRequest(uint8_t perms, uint32_t sender_timestamp, uint8_t req_type, uint8_t* payload, size_t payload_len) {
|
||||
memcpy(reply_data, &sender_timestamp, 4); // reflect sender_timestamp back in response packet (kind of like a 'tag')
|
||||
|
||||
if (req_type == REQ_TYPE_GET_TELEMETRY_DATA && (perms & PERM_GET_TELEMETRY) != 0) {
|
||||
telemetry.reset();
|
||||
telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f);
|
||||
// query other sensors -- target specific
|
||||
sensors.querySensors(0xFF, telemetry); // allow all telemetry permissions for admin or guest
|
||||
// TODO: let requester know permissions they have: telemetry.addPresence(TELEM_CHANNEL_SELF, perms);
|
||||
|
||||
uint8_t tlen = telemetry.getSize();
|
||||
memcpy(&reply_data[4], telemetry.getBuffer(), tlen);
|
||||
return 4 + tlen; // reply_len
|
||||
}
|
||||
if (req_type == REQ_TYPE_GET_AVG_MIN_MAX && (perms & PERM_GET_OTHER_STATS) != 0) {
|
||||
uint32_t start_secs_ago, end_secs_ago;
|
||||
memcpy(&start_secs_ago, &payload[0], 4);
|
||||
memcpy(&end_secs_ago, &payload[4], 4);
|
||||
uint8_t res1 = payload[8]; // reserved for future (extra query params)
|
||||
uint8_t res2 = payload[9];
|
||||
|
||||
MinMaxAvg data[8];
|
||||
int n;
|
||||
if (res1 == 0 && res2 == 0) {
|
||||
n = querySeriesData(start_secs_ago, end_secs_ago, data, 8);
|
||||
} else {
|
||||
n = 0;
|
||||
}
|
||||
|
||||
uint8_t ofs = 4;
|
||||
{
|
||||
uint32_t now = getRTCClock()->getCurrentTime();
|
||||
memcpy(&reply_data[ofs], &now, 4); ofs += 4;
|
||||
}
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
auto d = &data[i];
|
||||
reply_data[ofs++] = d->_channel;
|
||||
reply_data[ofs++] = d->_lpp_type;
|
||||
uint8_t sz = getDataSize(d->_lpp_type);
|
||||
uint32_t mult = getMultiplier(d->_lpp_type);
|
||||
bool is_signed = isSigned(d->_lpp_type);
|
||||
ofs += putFloat(&reply_data[ofs], d->_min, sz, mult, is_signed);
|
||||
ofs += putFloat(&reply_data[ofs], d->_max, sz, mult, is_signed);
|
||||
ofs += putFloat(&reply_data[ofs], d->_avg, sz, mult, is_signed);
|
||||
}
|
||||
return ofs;
|
||||
}
|
||||
if (req_type == REQ_TYPE_GET_ACCESS_LIST && (perms & PERM_ACL_ROLE_MASK) == PERM_ACL_LEVEL3) {
|
||||
uint8_t res1 = payload[0]; // reserved for future (extra query params)
|
||||
uint8_t res2 = payload[1];
|
||||
if (res1 == 0 && res2 == 0) {
|
||||
uint8_t ofs = 4;
|
||||
for (int i = 0; i < num_contacts && ofs + 7 <= sizeof(reply_data) - 4; i++) {
|
||||
auto c = &contacts[i];
|
||||
memcpy(&reply_data[ofs], c->id.pub_key, 6); ofs += 6; // just 6-byte pub_key prefix
|
||||
reply_data[ofs++] = c->permissions;
|
||||
}
|
||||
return ofs;
|
||||
}
|
||||
}
|
||||
return 0; // unknown command
|
||||
}
|
||||
|
||||
mesh::Packet* SensorMesh::createSelfAdvert() {
|
||||
uint8_t app_data[MAX_ADVERT_DATA_SIZE];
|
||||
uint8_t app_data_len;
|
||||
{
|
||||
AdvertDataBuilder builder(ADV_TYPE_SENSOR, _prefs.node_name, _prefs.node_lat, _prefs.node_lon);
|
||||
app_data_len = builder.encodeTo(app_data);
|
||||
}
|
||||
|
||||
return createAdvert(self_id, app_data, app_data_len);
|
||||
}
|
||||
|
||||
ContactInfo* SensorMesh::putContact(const mesh::Identity& id) {
|
||||
uint32_t min_time = 0xFFFFFFFF;
|
||||
ContactInfo* oldest = &contacts[MAX_CONTACTS - 1];
|
||||
for (int i = 0; i < num_contacts; i++) {
|
||||
if (id.matches(contacts[i].id)) return &contacts[i]; // already known
|
||||
if (!contacts[i].isAdmin() && contacts[i].last_activity < min_time) {
|
||||
oldest = &contacts[i];
|
||||
min_time = oldest->last_activity;
|
||||
}
|
||||
}
|
||||
|
||||
ContactInfo* c;
|
||||
if (num_contacts < MAX_CONTACTS) {
|
||||
c = &contacts[num_contacts++];
|
||||
} else {
|
||||
c = oldest; // evict least active contact
|
||||
}
|
||||
memset(c, 0, sizeof(*c));
|
||||
c->id = id;
|
||||
c->out_path_len = -1; // initially out_path is unknown
|
||||
return c;
|
||||
}
|
||||
|
||||
void SensorMesh::applyContactPermissions(const uint8_t* pubkey, uint8_t perms) {
|
||||
mesh::Identity id(pubkey);
|
||||
auto c = putContact(id);
|
||||
|
||||
if ((perms & PERM_ACL_ROLE_MASK) == PERM_ACL_GUEST) { // guest role is not persisted in contacts
|
||||
memset(c, 0, sizeof(*c));
|
||||
} else {
|
||||
c->permissions = perms; // update their permissions
|
||||
self_id.calcSharedSecret(c->shared_secret, pubkey);
|
||||
}
|
||||
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); // trigger saveContacts()
|
||||
}
|
||||
|
||||
void SensorMesh::sendAlert(ContactInfo* c, Trigger* t) {
|
||||
int text_len = strlen(t->text);
|
||||
|
||||
uint8_t data[MAX_PACKET_PAYLOAD];
|
||||
memcpy(data, &t->timestamp, 4);
|
||||
data[4] = (TXT_TYPE_PLAIN << 2) | t->attempt; // attempt and flags
|
||||
memcpy(&data[5], t->text, text_len);
|
||||
|
||||
// calc expected ACK reply
|
||||
mesh::Utils::sha256((uint8_t *)&t->expected_acks[t->attempt], 4, data, 5 + text_len, self_id.pub_key, PUB_KEY_SIZE);
|
||||
t->attempt++;
|
||||
|
||||
auto pkt = createDatagram(PAYLOAD_TYPE_TXT_MSG, c->id, c->shared_secret, data, 5 + text_len);
|
||||
if (pkt) {
|
||||
if (c->out_path_len >= 0) { // we have an out_path, so send DIRECT
|
||||
sendDirect(pkt, c->out_path, c->out_path_len);
|
||||
} else {
|
||||
sendFlood(pkt);
|
||||
}
|
||||
}
|
||||
t->send_expiry = futureMillis(ALERT_ACK_EXPIRY_MILLIS);
|
||||
}
|
||||
|
||||
void SensorMesh::alertIf(bool condition, Trigger& t, AlertPriority pri, const char* text) {
|
||||
if (condition) {
|
||||
if (!t.isTriggered() && num_alert_tasks < MAX_CONCURRENT_ALERTS) {
|
||||
StrHelper::strncpy(t.text, text, sizeof(t.text));
|
||||
t.pri = pri;
|
||||
t.send_expiry = 0; // signal that initial send is needed
|
||||
t.attempt = 4;
|
||||
t.curr_contact_idx = -1; // start iterating thru contacts[]
|
||||
|
||||
alert_tasks[num_alert_tasks++] = &t; // add to queue
|
||||
}
|
||||
} else {
|
||||
if (t.isTriggered()) {
|
||||
t.text[0] = 0;
|
||||
// remove 't' from alert queue
|
||||
int i = 0;
|
||||
while (i < num_alert_tasks && alert_tasks[i] != &t) i++;
|
||||
|
||||
if (i < num_alert_tasks) { // found, now delete from array
|
||||
num_alert_tasks--;
|
||||
while (i < num_alert_tasks) {
|
||||
alert_tasks[i] = alert_tasks[i + 1];
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float SensorMesh::getAirtimeBudgetFactor() const {
|
||||
return _prefs.airtime_factor;
|
||||
}
|
||||
|
||||
bool SensorMesh::allowPacketForward(const mesh::Packet* packet) {
|
||||
if (_prefs.disable_fwd) return false;
|
||||
if (packet->isRouteFlood() && packet->path_len >= _prefs.flood_max) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
int SensorMesh::calcRxDelay(float score, uint32_t air_time) const {
|
||||
if (_prefs.rx_delay_base <= 0.0f) return 0;
|
||||
return (int) ((pow(_prefs.rx_delay_base, 0.85f - score) - 1.0) * air_time);
|
||||
}
|
||||
|
||||
uint32_t SensorMesh::getRetransmitDelay(const mesh::Packet* packet) {
|
||||
uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.tx_delay_factor);
|
||||
return getRNG()->nextInt(0, 6)*t;
|
||||
}
|
||||
uint32_t SensorMesh::getDirectRetransmitDelay(const mesh::Packet* packet) {
|
||||
uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.direct_tx_delay_factor);
|
||||
return getRNG()->nextInt(0, 6)*t;
|
||||
}
|
||||
int SensorMesh::getInterferenceThreshold() const {
|
||||
return _prefs.interference_threshold;
|
||||
}
|
||||
int SensorMesh::getAGCResetInterval() const {
|
||||
return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds
|
||||
}
|
||||
|
||||
uint8_t SensorMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data) {
|
||||
if (strcmp((char *) data, _prefs.password) != 0) { // check for valid password
|
||||
#if MESH_DEBUG
|
||||
MESH_DEBUG_PRINTLN("Invalid password: %s", &data[4]);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto client = putContact(sender); // add to contacts (if not already known)
|
||||
if (sender_timestamp <= client->last_timestamp) {
|
||||
MESH_DEBUG_PRINTLN("Possible login replay attack!");
|
||||
return 0; // FATAL: client table is full -OR- replay attack
|
||||
}
|
||||
|
||||
MESH_DEBUG_PRINTLN("Login success!");
|
||||
client->last_timestamp = sender_timestamp;
|
||||
client->last_activity = getRTCClock()->getCurrentTime();
|
||||
client->permissions = PERM_ACL_LEVEL3 | PERM_RECV_ALERTS_HI | PERM_RECV_ALERTS_LO; // initially opt-in to receive alerts (can opt out)
|
||||
memcpy(client->shared_secret, secret, PUB_KEY_SIZE);
|
||||
|
||||
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY);
|
||||
|
||||
uint32_t now = getRTCClock()->getCurrentTimeUnique();
|
||||
memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp
|
||||
reply_data[4] = RESP_SERVER_LOGIN_OK;
|
||||
reply_data[5] = 0; // NEW: recommended keep-alive interval (secs / 16)
|
||||
reply_data[6] = 1; // 1 = is admin
|
||||
reply_data[7] = client->permissions;
|
||||
getRNG()->random(&reply_data[8], 4); // random blob to help packet-hash uniqueness
|
||||
|
||||
return 12; // reply length
|
||||
}
|
||||
|
||||
void SensorMesh::handleCommand(uint32_t sender_timestamp, char* command, char* reply) {
|
||||
while (*command == ' ') command++; // skip leading spaces
|
||||
|
||||
if (strlen(command) > 4 && command[2] == '|') { // optional prefix (for companion radio CLI)
|
||||
memcpy(reply, command, 3); // reflect the prefix back
|
||||
reply += 3;
|
||||
command += 3;
|
||||
}
|
||||
|
||||
// first, see if this is a custom-handled CLI command (ie. in main.cpp)
|
||||
if (handleCustomCommand(sender_timestamp, command, reply)) {
|
||||
return; // command has been handled
|
||||
}
|
||||
|
||||
// handle sensor-specific CLI commands
|
||||
if (memcmp(command, "setperm ", 8) == 0) { // format: setperm {pubkey-hex} {permissions-int8}
|
||||
char* hex = &command[8];
|
||||
char* sp = strchr(hex, ' '); // look for separator char
|
||||
if (sp == NULL || sp - hex != PUB_KEY_SIZE*2) {
|
||||
strcpy(reply, "Err - bad pubkey len");
|
||||
} else {
|
||||
*sp++ = 0; // replace space with null terminator
|
||||
|
||||
uint8_t pubkey[PUB_KEY_SIZE];
|
||||
if (mesh::Utils::fromHex(pubkey, PUB_KEY_SIZE, hex)) {
|
||||
uint8_t perms = atoi(sp);
|
||||
applyContactPermissions(pubkey, perms);
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Err - bad pubkey");
|
||||
}
|
||||
}
|
||||
} else if (sender_timestamp == 0 && strcmp(command, "get acl") == 0) {
|
||||
Serial.println("ACL:");
|
||||
for (int i = 0; i < num_contacts; i++) {
|
||||
auto c = &contacts[i];
|
||||
|
||||
Serial.printf("%02X ", c->permissions);
|
||||
mesh::Utils::printHex(Serial, c->id.pub_key, PUB_KEY_SIZE);
|
||||
Serial.printf("\n");
|
||||
}
|
||||
reply[0] = 0;
|
||||
} else {
|
||||
_cli.handleCommand(sender_timestamp, command, reply); // common CLI commands
|
||||
}
|
||||
}
|
||||
|
||||
void SensorMesh::onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) {
|
||||
if (packet->getPayloadType() == PAYLOAD_TYPE_ANON_REQ) { // received an initial request by a possible admin client (unknown at this stage)
|
||||
uint32_t timestamp;
|
||||
memcpy(×tamp, data, 4);
|
||||
|
||||
data[len] = 0; // ensure null terminator
|
||||
uint8_t reply_len = handleLoginReq(sender, secret, timestamp, &data[4]);
|
||||
|
||||
if (reply_len == 0) return; // invalid request
|
||||
|
||||
if (packet->isRouteFlood()) {
|
||||
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response
|
||||
mesh::Packet* path = createPathReturn(sender, secret, packet->path, packet->path_len,
|
||||
PAYLOAD_TYPE_RESPONSE, reply_data, reply_len);
|
||||
if (path) sendFlood(path, SERVER_RESPONSE_DELAY);
|
||||
} else {
|
||||
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len);
|
||||
if (reply) sendFlood(reply, SERVER_RESPONSE_DELAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int SensorMesh::searchPeersByHash(const uint8_t* hash) {
|
||||
int n = 0;
|
||||
for (int i = 0; i < num_contacts && n < MAX_SEARCH_RESULTS; i++) {
|
||||
if (contacts[i].id.isHashMatch(hash)) {
|
||||
matching_peer_indexes[n++] = i; // store the INDEXES of matching contacts (for subsequent 'peer' methods)
|
||||
}
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
void SensorMesh::getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) {
|
||||
int i = matching_peer_indexes[peer_idx];
|
||||
if (i >= 0 && i < num_contacts) {
|
||||
// lookup pre-calculated shared_secret
|
||||
memcpy(dest_secret, contacts[i].shared_secret, PUB_KEY_SIZE);
|
||||
} else {
|
||||
MESH_DEBUG_PRINTLN("getPeerSharedSecret: Invalid peer idx: %d", i);
|
||||
}
|
||||
}
|
||||
|
||||
void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) {
|
||||
int i = matching_peer_indexes[sender_idx];
|
||||
if (i < 0 || i >= num_contacts) {
|
||||
MESH_DEBUG_PRINTLN("onPeerDataRecv: Invalid sender idx: %d", i);
|
||||
return;
|
||||
}
|
||||
|
||||
ContactInfo& from = contacts[i];
|
||||
|
||||
if (type == PAYLOAD_TYPE_REQ) { // request (from a known contact)
|
||||
uint32_t timestamp;
|
||||
memcpy(×tamp, data, 4);
|
||||
|
||||
if (timestamp > from.last_timestamp) { // prevent replay attacks
|
||||
uint8_t reply_len = handleRequest(from.isAdmin() ? 0xFFFF : from.permissions, timestamp, data[4], &data[5], len - 5);
|
||||
if (reply_len == 0) return; // invalid command
|
||||
|
||||
from.last_timestamp = timestamp;
|
||||
from.last_activity = getRTCClock()->getCurrentTime();
|
||||
|
||||
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, reply_data, reply_len);
|
||||
if (path) sendFlood(path, SERVER_RESPONSE_DELAY);
|
||||
} else {
|
||||
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, from.id, secret, reply_data, reply_len);
|
||||
if (reply) {
|
||||
if (from.out_path_len >= 0) { // we have an out_path, so send DIRECT
|
||||
sendDirect(reply, from.out_path, from.out_path_len, SERVER_RESPONSE_DELAY);
|
||||
} else {
|
||||
sendFlood(reply, SERVER_RESPONSE_DELAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
MESH_DEBUG_PRINTLN("onPeerDataRecv: possible replay attack detected");
|
||||
}
|
||||
} else if (type == PAYLOAD_TYPE_TXT_MSG && len > 5 && from.isAdmin()) { // a CLI command
|
||||
uint32_t sender_timestamp;
|
||||
memcpy(&sender_timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong)
|
||||
uint flags = (data[4] >> 2); // message attempt number, and other flags
|
||||
|
||||
if (!(flags == TXT_TYPE_CLI_DATA)) {
|
||||
MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported text type received: flags=%02x", (uint32_t)flags);
|
||||
} else if (sender_timestamp > from.last_timestamp) { // prevent replay attacks
|
||||
from.last_timestamp = sender_timestamp;
|
||||
from.last_activity = getRTCClock()->getCurrentTime();
|
||||
|
||||
// len can be > original length, but 'text' will be padded with zeroes
|
||||
data[len] = 0; // need to make a C string again, with null terminator
|
||||
|
||||
uint8_t temp[166];
|
||||
char *command = (char *) &data[5];
|
||||
char *reply = (char *) &temp[5];
|
||||
handleCommand(sender_timestamp, command, reply);
|
||||
|
||||
int text_len = strlen(reply);
|
||||
if (text_len > 0) {
|
||||
uint32_t timestamp = getRTCClock()->getCurrentTimeUnique();
|
||||
if (timestamp == sender_timestamp) {
|
||||
// WORKAROUND: the two timestamps need to be different, in the CLI view
|
||||
timestamp++;
|
||||
}
|
||||
memcpy(temp, ×tamp, 4); // mostly an extra blob to help make packet_hash unique
|
||||
temp[4] = (TXT_TYPE_CLI_DATA << 2);
|
||||
|
||||
auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, from.id, secret, temp, 5 + text_len);
|
||||
if (reply) {
|
||||
if (from.out_path_len < 0) {
|
||||
sendFlood(reply, CLI_REPLY_DELAY_MILLIS);
|
||||
} else {
|
||||
sendDirect(reply, from.out_path, from.out_path_len, CLI_REPLY_DELAY_MILLIS);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
MESH_DEBUG_PRINTLN("onPeerDataRecv: possible replay attack detected");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SensorMesh::onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) {
|
||||
int i = matching_peer_indexes[sender_idx];
|
||||
if (i < 0 || i >= num_contacts) {
|
||||
MESH_DEBUG_PRINTLN("onPeerPathRecv: Invalid sender idx: %d", i);
|
||||
return false;
|
||||
}
|
||||
|
||||
ContactInfo& from = contacts[i];
|
||||
|
||||
MESH_DEBUG_PRINTLN("PATH to contact, path_len=%d", (uint32_t) path_len);
|
||||
// NOTE: for this impl, we just replace the current 'out_path' regardless, whenever sender sends us a new out_path.
|
||||
// FUTURE: could store multiple out_paths per contact, and try to find which is the 'best'(?)
|
||||
memcpy(from.out_path, path, from.out_path_len = path_len); // store a copy of path, for sendDirect()
|
||||
from.last_activity = getRTCClock()->getCurrentTime();
|
||||
|
||||
// REVISIT: maybe make ALL out_paths non-persisted to minimise flash writes??
|
||||
if (from.isAdmin()) {
|
||||
// only do saveContacts() (of this out_path change) if this is an admin
|
||||
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY);
|
||||
}
|
||||
|
||||
// NOTE: no reciprocal path send!!
|
||||
return false;
|
||||
}
|
||||
|
||||
void SensorMesh::onAckRecv(mesh::Packet* packet, uint32_t ack_crc) {
|
||||
if (num_alert_tasks > 0) {
|
||||
auto t = alert_tasks[0]; // check current alert task
|
||||
for (int i = 0; i < t->attempt; i++) {
|
||||
if (ack_crc == t->expected_acks[i]) { // matching ACK!
|
||||
t->attempt = 4; // signal to move to next contact
|
||||
t->send_expiry = 0;
|
||||
packet->markDoNotRetransmit(); // ACK was for this node, so don't retransmit
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables)
|
||||
: mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables),
|
||||
_cli(board, rtc, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4)
|
||||
{
|
||||
num_contacts = 0;
|
||||
next_local_advert = next_flood_advert = 0;
|
||||
dirty_contacts_expiry = 0;
|
||||
last_read_time = 0;
|
||||
num_alert_tasks = 0;
|
||||
|
||||
// defaults
|
||||
memset(&_prefs, 0, sizeof(_prefs));
|
||||
_prefs.airtime_factor = 1.0; // one half
|
||||
_prefs.rx_delay_base = 0.0f; // turn off by default, was 10.0;
|
||||
_prefs.tx_delay_factor = 0.5f; // was 0.25f
|
||||
StrHelper::strncpy(_prefs.node_name, ADVERT_NAME, sizeof(_prefs.node_name));
|
||||
_prefs.node_lat = ADVERT_LAT;
|
||||
_prefs.node_lon = ADVERT_LON;
|
||||
StrHelper::strncpy(_prefs.password, ADMIN_PASSWORD, sizeof(_prefs.password));
|
||||
_prefs.freq = LORA_FREQ;
|
||||
_prefs.sf = LORA_SF;
|
||||
_prefs.bw = LORA_BW;
|
||||
_prefs.cr = LORA_CR;
|
||||
_prefs.tx_power_dbm = LORA_TX_POWER;
|
||||
_prefs.advert_interval = 1; // default to 2 minutes for NEW installs
|
||||
_prefs.flood_advert_interval = 0; // disabled
|
||||
_prefs.disable_fwd = true;
|
||||
_prefs.flood_max = 64;
|
||||
_prefs.interference_threshold = 0; // disabled
|
||||
}
|
||||
|
||||
void SensorMesh::begin(FILESYSTEM* fs) {
|
||||
mesh::Mesh::begin();
|
||||
_fs = fs;
|
||||
// load persisted prefs
|
||||
_cli.loadPrefs(_fs);
|
||||
|
||||
loadContacts();
|
||||
|
||||
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||
radio_set_tx_power(_prefs.tx_power_dbm);
|
||||
|
||||
updateAdvertTimer();
|
||||
updateFloodAdvertTimer();
|
||||
}
|
||||
|
||||
bool SensorMesh::formatFileSystem() {
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_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"
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void SensorMesh::sendSelfAdvertisement(int delay_millis) {
|
||||
mesh::Packet* pkt = createSelfAdvert();
|
||||
if (pkt) {
|
||||
sendFlood(pkt, delay_millis);
|
||||
} else {
|
||||
MESH_DEBUG_PRINTLN("ERROR: unable to create advertisement packet!");
|
||||
}
|
||||
}
|
||||
|
||||
void SensorMesh::updateAdvertTimer() {
|
||||
if (_prefs.advert_interval > 0) { // schedule local advert timer
|
||||
next_local_advert = futureMillis( ((uint32_t)_prefs.advert_interval) * 2 * 60 * 1000);
|
||||
} else {
|
||||
next_local_advert = 0; // stop the timer
|
||||
}
|
||||
}
|
||||
void SensorMesh::updateFloodAdvertTimer() {
|
||||
if (_prefs.flood_advert_interval > 0) { // schedule flood advert timer
|
||||
next_flood_advert = futureMillis( ((uint32_t)_prefs.flood_advert_interval) * 60 * 60 * 1000);
|
||||
} else {
|
||||
next_flood_advert = 0; // stop the timer
|
||||
}
|
||||
}
|
||||
|
||||
void SensorMesh::setTxPower(uint8_t power_dbm) {
|
||||
radio_set_tx_power(power_dbm);
|
||||
}
|
||||
|
||||
float SensorMesh::getTelemValue(uint8_t channel, uint8_t type) {
|
||||
auto buf = telemetry.getBuffer();
|
||||
uint8_t size = telemetry.getSize();
|
||||
uint8_t i = 0;
|
||||
|
||||
while (i + 2 < size) {
|
||||
// Get channel #
|
||||
uint8_t ch = buf[i++];
|
||||
// Get data type
|
||||
uint8_t t = buf[i++];
|
||||
uint8_t sz = getDataSize(t);
|
||||
|
||||
if (ch == channel && t == type) {
|
||||
return getFloat(&buf[i], sz, getMultiplier(t), isSigned(t));
|
||||
}
|
||||
i += sz; // skip
|
||||
}
|
||||
return 0.0f; // not found
|
||||
}
|
||||
|
||||
bool SensorMesh::getGPS(uint8_t channel, float& lat, float& lon, float& alt) {
|
||||
if (channel == TELEM_CHANNEL_SELF) {
|
||||
lat = sensors.node_lat;
|
||||
lon = sensors.node_lon;
|
||||
alt = sensors.node_altitude;
|
||||
return true;
|
||||
}
|
||||
// REVISIT: custom GPS channels??
|
||||
return false;
|
||||
}
|
||||
|
||||
void SensorMesh::loop() {
|
||||
mesh::Mesh::loop();
|
||||
|
||||
if (next_flood_advert && millisHasNowPassed(next_flood_advert)) {
|
||||
mesh::Packet* pkt = createSelfAdvert();
|
||||
if (pkt) sendFlood(pkt);
|
||||
|
||||
updateFloodAdvertTimer(); // schedule next flood advert
|
||||
updateAdvertTimer(); // also schedule local advert (so they don't overlap)
|
||||
} else if (next_local_advert && millisHasNowPassed(next_local_advert)) {
|
||||
mesh::Packet* pkt = createSelfAdvert();
|
||||
if (pkt) sendZeroHop(pkt);
|
||||
|
||||
updateAdvertTimer(); // schedule next local advert
|
||||
}
|
||||
|
||||
uint32_t curr = getRTCClock()->getCurrentTime();
|
||||
if (curr >= last_read_time + SENSOR_READ_INTERVAL_SECS) {
|
||||
telemetry.reset();
|
||||
telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f);
|
||||
// query other sensors -- target specific
|
||||
sensors.querySensors(0xFF, telemetry); // allow all telemetry permissions
|
||||
|
||||
onSensorDataRead();
|
||||
|
||||
last_read_time = curr;
|
||||
}
|
||||
|
||||
// check the alert send queue
|
||||
if (num_alert_tasks > 0) {
|
||||
auto t = alert_tasks[0]; // process head of queue
|
||||
|
||||
if (millisHasNowPassed(t->send_expiry)) { // next send needed?
|
||||
if (t->attempt >= 4) { // max attempts reached, try next contact
|
||||
t->curr_contact_idx++;
|
||||
if (t->curr_contact_idx >= num_contacts) { // no more contacts to try?
|
||||
num_alert_tasks--; // remove t from queue
|
||||
for (int i = 0; i < num_alert_tasks; i++) {
|
||||
alert_tasks[i] = alert_tasks[i + 1];
|
||||
}
|
||||
} else {
|
||||
auto c = &contacts[t->curr_contact_idx];
|
||||
uint16_t pri_mask = (t->pri == HIGH_PRI_ALERT) ? PERM_RECV_ALERTS_HI : PERM_RECV_ALERTS_LO;
|
||||
|
||||
if (c->permissions & pri_mask) { // contact wants alert
|
||||
// reset attempts
|
||||
t->attempt = (t->pri == LOW_PRI_ALERT) ? 3 : 0; // Low pri alerts, start at attempt #3 (ie. only make ONE attempt)
|
||||
t->timestamp = getRTCClock()->getCurrentTimeUnique(); // need unique timestamp per contact
|
||||
|
||||
sendAlert(c, t); // NOTE: modifies attempt, expected_acks[] and send_expiry
|
||||
} else {
|
||||
// next contact tested in next ::loop()
|
||||
}
|
||||
}
|
||||
} else if (t->curr_contact_idx < num_contacts) {
|
||||
auto c = &contacts[t->curr_contact_idx]; // send next attempt
|
||||
sendAlert(c, t); // NOTE: modifies attempt, expected_acks[] and send_expiry
|
||||
} else {
|
||||
// contact list has likely been modified while waiting for alert ACK, cancel this task
|
||||
t->attempt = 4; // next ::loop() will remove t from queue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// is there are pending dirty contacts write needed?
|
||||
if (dirty_contacts_expiry && millisHasNowPassed(dirty_contacts_expiry)) {
|
||||
saveContacts();
|
||||
dirty_contacts_expiry = 0;
|
||||
}
|
||||
}
|
||||
168
examples/simple_sensor/SensorMesh.h
Normal file
168
examples/simple_sensor/SensorMesh.h
Normal file
@@ -0,0 +1,168 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h> // needed for PlatformIO
|
||||
#include <Mesh.h>
|
||||
|
||||
#include "TimeSeriesData.h"
|
||||
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
#include <InternalFileSystem.h>
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
#include <LittleFS.h>
|
||||
#elif defined(ESP32)
|
||||
#include <SPIFFS.h>
|
||||
#endif
|
||||
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
#include <helpers/StaticPoolPacketManager.h>
|
||||
#include <helpers/SimpleMeshTables.h>
|
||||
#include <helpers/IdentityStore.h>
|
||||
#include <helpers/AdvertDataHelpers.h>
|
||||
#include <helpers/TxtDataHelpers.h>
|
||||
#include <helpers/CommonCLI.h>
|
||||
#include <RTClib.h>
|
||||
#include <target.h>
|
||||
|
||||
#define PERM_ACL_ROLE_MASK 3 // lower 2 bits
|
||||
#define PERM_ACL_GUEST 0
|
||||
#define PERM_ACL_LEVEL1 1
|
||||
#define PERM_ACL_LEVEL2 2
|
||||
#define PERM_ACL_LEVEL3 3 // admin
|
||||
|
||||
#define PERM_GET_TELEMETRY (1 << 2)
|
||||
#define PERM_GET_OTHER_STATS (1 << 3)
|
||||
#define PERM_RESERVED1 (1 << 4)
|
||||
#define PERM_RESERVED2 (1 << 5)
|
||||
#define PERM_RECV_ALERTS_LO (1 << 6) // low priority alerts
|
||||
#define PERM_RECV_ALERTS_HI (1 << 7) // high priority alerts
|
||||
|
||||
struct ContactInfo {
|
||||
mesh::Identity id;
|
||||
uint8_t permissions;
|
||||
int8_t out_path_len;
|
||||
uint8_t out_path[MAX_PATH_SIZE];
|
||||
uint8_t shared_secret[PUB_KEY_SIZE];
|
||||
uint32_t last_timestamp; // by THEIR clock (transient)
|
||||
uint32_t last_activity; // by OUR clock (transient)
|
||||
|
||||
bool isAdmin() const { return (permissions & PERM_ACL_ROLE_MASK) == PERM_ACL_LEVEL3; }
|
||||
};
|
||||
|
||||
#ifndef FIRMWARE_BUILD_DATE
|
||||
#define FIRMWARE_BUILD_DATE "2 Jul 2025"
|
||||
#endif
|
||||
|
||||
#ifndef FIRMWARE_VERSION
|
||||
#define FIRMWARE_VERSION "v1.7.2"
|
||||
#endif
|
||||
|
||||
#define FIRMWARE_ROLE "sensor"
|
||||
|
||||
#define MAX_CONTACTS 20
|
||||
|
||||
#define MAX_SEARCH_RESULTS 8
|
||||
#define MAX_CONCURRENT_ALERTS 4
|
||||
|
||||
class SensorMesh : public mesh::Mesh, public CommonCLICallbacks {
|
||||
public:
|
||||
SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables);
|
||||
void begin(FILESYSTEM* fs);
|
||||
void loop();
|
||||
void handleCommand(uint32_t sender_timestamp, char* command, char* reply);
|
||||
|
||||
// CommonCLI callbacks
|
||||
const char* getFirmwareVer() override { return FIRMWARE_VERSION; }
|
||||
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); }
|
||||
bool formatFileSystem() override;
|
||||
void sendSelfAdvertisement(int delay_millis) override;
|
||||
void updateAdvertTimer() override;
|
||||
void updateFloodAdvertTimer() override;
|
||||
void setLoggingOn(bool enable) override { }
|
||||
void eraseLogFile() override { }
|
||||
void dumpLogFile() override { }
|
||||
void setTxPower(uint8_t power_dbm) override;
|
||||
void formatNeighborsReply(char *reply) override {
|
||||
strcpy(reply, "not supported");
|
||||
}
|
||||
const uint8_t* getSelfIdPubKey() override { return self_id.pub_key; }
|
||||
void clearStats() override { }
|
||||
|
||||
float getTelemValue(uint8_t channel, uint8_t type);
|
||||
|
||||
protected:
|
||||
// current telemetry data queries
|
||||
float getVoltage(uint8_t channel) { return getTelemValue(channel, LPP_VOLTAGE); }
|
||||
float getCurrent(uint8_t channel) { return getTelemValue(channel, LPP_CURRENT); }
|
||||
float getPower(uint8_t channel) { return getTelemValue(channel, LPP_POWER); }
|
||||
float getTemperature(uint8_t channel) { return getTelemValue(channel, LPP_TEMPERATURE); }
|
||||
float getRelativeHumidity(uint8_t channel) { return getTelemValue(channel, LPP_RELATIVE_HUMIDITY); }
|
||||
float getBarometricPressure(uint8_t channel) { return getTelemValue(channel, LPP_BAROMETRIC_PRESSURE); }
|
||||
float getAltitude(uint8_t channel) { return getTelemValue(channel, LPP_ALTITUDE); }
|
||||
bool getGPS(uint8_t channel, float& lat, float& lon, float& alt);
|
||||
|
||||
// alerts
|
||||
enum AlertPriority { LOW_PRI_ALERT, HIGH_PRI_ALERT };
|
||||
|
||||
struct Trigger {
|
||||
uint32_t timestamp;
|
||||
AlertPriority pri;
|
||||
uint32_t expected_acks[4];
|
||||
int8_t curr_contact_idx;
|
||||
uint8_t attempt;
|
||||
unsigned long send_expiry;
|
||||
char text[MAX_PACKET_PAYLOAD];
|
||||
|
||||
Trigger() { text[0] = 0; }
|
||||
bool isTriggered() const { return text[0] != 0; }
|
||||
};
|
||||
void alertIf(bool condition, Trigger& t, AlertPriority pri, const char* text);
|
||||
|
||||
virtual void onSensorDataRead() = 0; // for app to implement
|
||||
virtual int querySeriesData(uint32_t start_secs_ago, uint32_t end_secs_ago, MinMaxAvg dest[], int max_num) = 0; // for app to implement
|
||||
virtual bool handleCustomCommand(uint32_t sender_timestamp, char* command, char* reply) { return false; }
|
||||
|
||||
// Mesh overrides
|
||||
float getAirtimeBudgetFactor() const override;
|
||||
bool allowPacketForward(const mesh::Packet* packet) override;
|
||||
int calcRxDelay(float score, uint32_t air_time) const override;
|
||||
uint32_t getRetransmitDelay(const mesh::Packet* packet) override;
|
||||
uint32_t getDirectRetransmitDelay(const mesh::Packet* packet) override;
|
||||
int getInterferenceThreshold() const override;
|
||||
int getAGCResetInterval() const override;
|
||||
void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override;
|
||||
int searchPeersByHash(const uint8_t* hash) override;
|
||||
void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) override;
|
||||
void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override;
|
||||
bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override;
|
||||
void onAckRecv(mesh::Packet* packet, uint32_t ack_crc) override;
|
||||
|
||||
private:
|
||||
FILESYSTEM* _fs;
|
||||
unsigned long next_local_advert, next_flood_advert;
|
||||
NodePrefs _prefs;
|
||||
CommonCLI _cli;
|
||||
uint8_t reply_data[MAX_PACKET_PAYLOAD];
|
||||
ContactInfo contacts[MAX_CONTACTS];
|
||||
int num_contacts;
|
||||
unsigned long dirty_contacts_expiry;
|
||||
CayenneLPP telemetry;
|
||||
uint32_t last_read_time;
|
||||
int matching_peer_indexes[MAX_SEARCH_RESULTS];
|
||||
int num_alert_tasks;
|
||||
Trigger* alert_tasks[MAX_CONCURRENT_ALERTS];
|
||||
|
||||
void loadContacts();
|
||||
void saveContacts();
|
||||
uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data);
|
||||
uint8_t handleRequest(uint8_t perms, uint32_t sender_timestamp, uint8_t req_type, uint8_t* payload, size_t payload_len);
|
||||
mesh::Packet* createSelfAdvert();
|
||||
ContactInfo* putContact(const mesh::Identity& id);
|
||||
void applyContactPermissions(const uint8_t* pubkey, uint8_t perms);
|
||||
|
||||
void sendAlert(ContactInfo* c, Trigger* t);
|
||||
|
||||
};
|
||||
45
examples/simple_sensor/TimeSeriesData.cpp
Normal file
45
examples/simple_sensor/TimeSeriesData.cpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#include "TimeSeriesData.h"
|
||||
|
||||
void TimeSeriesData::recordData(mesh::RTCClock* clock, float value) {
|
||||
uint32_t now = clock->getCurrentTime();
|
||||
if (now >= last_timestamp + interval_secs) {
|
||||
last_timestamp = now;
|
||||
|
||||
data[next] = value; // append to cycle table
|
||||
next = (next + 1) % num_slots;
|
||||
}
|
||||
}
|
||||
|
||||
void TimeSeriesData::calcMinMaxAvg(mesh::RTCClock* clock, uint32_t start_secs_ago, uint32_t end_secs_ago, MinMaxAvg* dest, uint8_t channel, uint8_t lpp_type) const {
|
||||
int i = next, n = num_slots;
|
||||
uint32_t ago = clock->getCurrentTime() - last_timestamp;
|
||||
int num_values = 0;
|
||||
float total = 0.0f;
|
||||
|
||||
dest->_channel = channel;
|
||||
dest->_lpp_type = lpp_type;
|
||||
|
||||
// start at most recet recording, back-track through to oldest
|
||||
while (n > 0) {
|
||||
n--;
|
||||
i = (i + num_slots - 1) % num_slots; // go back by one
|
||||
if (ago >= end_secs_ago && ago < start_secs_ago) { // filter by the desired time range
|
||||
float v = data[i];
|
||||
num_values++;
|
||||
total += v;
|
||||
if (num_values == 1) {
|
||||
dest->_max = dest->_min = v;
|
||||
} else {
|
||||
if (v < dest->_min) dest->_min = v;
|
||||
if (v > dest->_max) dest->_max = v;
|
||||
}
|
||||
}
|
||||
ago += interval_secs;
|
||||
}
|
||||
// calc average
|
||||
if (num_values > 0) {
|
||||
dest->_avg = total / num_values;
|
||||
} else {
|
||||
dest->_max = dest->_min = dest->_avg = NAN;
|
||||
}
|
||||
}
|
||||
29
examples/simple_sensor/TimeSeriesData.h
Normal file
29
examples/simple_sensor/TimeSeriesData.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Mesh.h>
|
||||
|
||||
struct MinMaxAvg {
|
||||
float _min, _max, _avg;
|
||||
uint8_t _lpp_type, _channel;
|
||||
};
|
||||
|
||||
class TimeSeriesData {
|
||||
float* data;
|
||||
int num_slots, next;
|
||||
uint32_t last_timestamp;
|
||||
uint32_t interval_secs;
|
||||
|
||||
public:
|
||||
TimeSeriesData(float* array, int num, uint32_t secs) : num_slots(num), data(array), last_timestamp(0), next(0), interval_secs(secs) {
|
||||
memset(data, 0, sizeof(float)*num);
|
||||
}
|
||||
TimeSeriesData(int num, uint32_t secs) : num_slots(num), last_timestamp(0), next(0), interval_secs(secs) {
|
||||
data = new float[num];
|
||||
memset(data, 0, sizeof(float)*num);
|
||||
}
|
||||
|
||||
void recordData(mesh::RTCClock* clock, float value);
|
||||
void calcMinMaxAvg(mesh::RTCClock* clock, uint32_t start_secs_ago, uint32_t end_secs_ago, MinMaxAvg* dest, uint8_t channel, uint8_t lpp_type) const;
|
||||
};
|
||||
|
||||
114
examples/simple_sensor/UITask.cpp
Normal file
114
examples/simple_sensor/UITask.cpp
Normal file
@@ -0,0 +1,114 @@
|
||||
#include "UITask.h"
|
||||
#include <Arduino.h>
|
||||
#include <helpers/CommonCLI.h>
|
||||
|
||||
#define AUTO_OFF_MILLIS 20000 // 20 seconds
|
||||
#define BOOT_SCREEN_MILLIS 4000 // 4 seconds
|
||||
|
||||
// 'meshcore', 128x13px
|
||||
static const uint8_t meshcore_logo [] PROGMEM = {
|
||||
0x3c, 0x01, 0xe3, 0xff, 0xc7, 0xff, 0x8f, 0x03, 0x87, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe,
|
||||
0x3c, 0x03, 0xe3, 0xff, 0xc7, 0xff, 0x8e, 0x03, 0x8f, 0xfe, 0x3f, 0xfe, 0x1f, 0xff, 0x1f, 0xfe,
|
||||
0x3e, 0x03, 0xc3, 0xff, 0x8f, 0xff, 0x0e, 0x07, 0x8f, 0xfe, 0x7f, 0xfe, 0x1f, 0xff, 0x1f, 0xfc,
|
||||
0x3e, 0x07, 0xc7, 0x80, 0x0e, 0x00, 0x0e, 0x07, 0x9e, 0x00, 0x78, 0x0e, 0x3c, 0x0f, 0x1c, 0x00,
|
||||
0x3e, 0x0f, 0xc7, 0x80, 0x1e, 0x00, 0x0e, 0x07, 0x1e, 0x00, 0x70, 0x0e, 0x38, 0x0f, 0x3c, 0x00,
|
||||
0x7f, 0x0f, 0xc7, 0xfe, 0x1f, 0xfc, 0x1f, 0xff, 0x1c, 0x00, 0x70, 0x0e, 0x38, 0x0e, 0x3f, 0xf8,
|
||||
0x7f, 0x1f, 0xc7, 0xfe, 0x0f, 0xff, 0x1f, 0xff, 0x1c, 0x00, 0xf0, 0x0e, 0x38, 0x0e, 0x3f, 0xf8,
|
||||
0x7f, 0x3f, 0xc7, 0xfe, 0x0f, 0xff, 0x1f, 0xff, 0x1c, 0x00, 0xf0, 0x1e, 0x3f, 0xfe, 0x3f, 0xf0,
|
||||
0x77, 0x3b, 0x87, 0x00, 0x00, 0x07, 0x1c, 0x0f, 0x3c, 0x00, 0xe0, 0x1c, 0x7f, 0xfc, 0x38, 0x00,
|
||||
0x77, 0xfb, 0x8f, 0x00, 0x00, 0x07, 0x1c, 0x0f, 0x3c, 0x00, 0xe0, 0x1c, 0x7f, 0xf8, 0x38, 0x00,
|
||||
0x73, 0xf3, 0x8f, 0xff, 0x0f, 0xff, 0x1c, 0x0e, 0x3f, 0xf8, 0xff, 0xfc, 0x70, 0x78, 0x7f, 0xf8,
|
||||
0xe3, 0xe3, 0x8f, 0xff, 0x1f, 0xfe, 0x3c, 0x0e, 0x3f, 0xf8, 0xff, 0xfc, 0x70, 0x3c, 0x7f, 0xf8,
|
||||
0xe3, 0xe3, 0x8f, 0xff, 0x1f, 0xfc, 0x3c, 0x0e, 0x1f, 0xf8, 0xff, 0xf8, 0x70, 0x3c, 0x7f, 0xf8,
|
||||
};
|
||||
|
||||
void UITask::begin(NodePrefs* node_prefs, const char* build_date, const char* firmware_version) {
|
||||
_prevBtnState = HIGH;
|
||||
_auto_off = millis() + AUTO_OFF_MILLIS;
|
||||
_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];
|
||||
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);
|
||||
|
||||
// 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 = "< Sensor >";
|
||||
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() {
|
||||
#ifdef PIN_USER_BTN
|
||||
if (millis() >= _next_read) {
|
||||
int btnState = digitalRead(PIN_USER_BTN);
|
||||
if (btnState != _prevBtnState) {
|
||||
if (btnState == LOW) { // pressed?
|
||||
if (_display->isOn()) {
|
||||
// TODO: any action ?
|
||||
} else {
|
||||
_display->turnOn();
|
||||
}
|
||||
_auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer
|
||||
}
|
||||
_prevBtnState = btnState;
|
||||
}
|
||||
_next_read = millis() + 200; // 5 reads per second
|
||||
}
|
||||
#endif
|
||||
|
||||
if (_display->isOn()) {
|
||||
if (millis() >= _next_refresh) {
|
||||
_display->startFrame();
|
||||
renderCurrScreen();
|
||||
_display->endFrame();
|
||||
|
||||
_next_refresh = millis() + 1000; // refresh every second
|
||||
}
|
||||
if (millis() > _auto_off) {
|
||||
_display->turnOff();
|
||||
}
|
||||
}
|
||||
}
|
||||
19
examples/simple_sensor/UITask.h
Normal file
19
examples/simple_sensor/UITask.h
Normal file
@@ -0,0 +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;
|
||||
NodePrefs* _node_prefs;
|
||||
char _version_info[32];
|
||||
|
||||
void renderCurrScreen();
|
||||
public:
|
||||
UITask(DisplayDriver& display) : _display(&display) { _next_read = _next_refresh = 0; }
|
||||
void begin(NodePrefs* node_prefs, const char* build_date, const char* firmware_version);
|
||||
|
||||
void loop();
|
||||
};
|
||||
147
examples/simple_sensor/main.cpp
Normal file
147
examples/simple_sensor/main.cpp
Normal file
@@ -0,0 +1,147 @@
|
||||
#include "SensorMesh.h"
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
#include "UITask.h"
|
||||
static UITask ui_task(display);
|
||||
#endif
|
||||
|
||||
class MyMesh : public SensorMesh {
|
||||
public:
|
||||
MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables)
|
||||
: SensorMesh(board, radio, ms, rng, rtc, tables),
|
||||
battery_data(12*24, 5*60) // 24 hours worth of battery data, every 5 minutes
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
/* ========================== custom logic here ========================== */
|
||||
Trigger low_batt, critical_batt;
|
||||
TimeSeriesData battery_data;
|
||||
|
||||
void onSensorDataRead() override {
|
||||
float batt_voltage = getVoltage(TELEM_CHANNEL_SELF);
|
||||
|
||||
battery_data.recordData(getRTCClock(), batt_voltage); // record battery
|
||||
alertIf(batt_voltage < 3.4f, critical_batt, HIGH_PRI_ALERT, "Battery is critical!");
|
||||
alertIf(batt_voltage < 3.6f, low_batt, LOW_PRI_ALERT, "Battery is low");
|
||||
}
|
||||
|
||||
int querySeriesData(uint32_t start_secs_ago, uint32_t end_secs_ago, MinMaxAvg dest[], int max_num) override {
|
||||
battery_data.calcMinMaxAvg(getRTCClock(), start_secs_ago, end_secs_ago, &dest[0], TELEM_CHANNEL_SELF, LPP_VOLTAGE);
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool handleCustomCommand(uint32_t sender_timestamp, char* command, char* reply) override {
|
||||
if (strcmp(command, "magic") == 0) { // example 'custom' command handling
|
||||
strcpy(reply, "**Magic now done**");
|
||||
return true; // handled
|
||||
}
|
||||
return false; // not handled
|
||||
}
|
||||
/* ======================================================================= */
|
||||
};
|
||||
|
||||
StdRNG fast_rng;
|
||||
SimpleMeshTables tables;
|
||||
|
||||
MyMesh the_mesh(board, radio_driver, *new ArduinoMillis(), fast_rng, rtc_clock, tables);
|
||||
|
||||
void halt() {
|
||||
while (1) ;
|
||||
}
|
||||
|
||||
static char command[120];
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
delay(1000);
|
||||
|
||||
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());
|
||||
|
||||
FILESYSTEM* fs;
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
InternalFS.begin();
|
||||
fs = &InternalFS;
|
||||
IdentityStore store(InternalFS, "");
|
||||
#elif defined(ESP32)
|
||||
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);
|
||||
}
|
||||
|
||||
Serial.print("Sensor ID: ");
|
||||
mesh::Utils::printHex(Serial, the_mesh.self_id.pub_key, PUB_KEY_SIZE); Serial.println();
|
||||
|
||||
command[0] = 0;
|
||||
|
||||
sensors.begin();
|
||||
|
||||
the_mesh.begin(fs);
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
ui_task.begin(the_mesh.getNodePrefs(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION);
|
||||
#endif
|
||||
|
||||
// send out initial Advertisement to the mesh
|
||||
the_mesh.sendSelfAdvertisement(16000);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
int len = strlen(command);
|
||||
while (Serial.available() && len < sizeof(command)-1) {
|
||||
char c = Serial.read();
|
||||
if (c != '\n') {
|
||||
command[len++] = c;
|
||||
command[len] = 0;
|
||||
}
|
||||
Serial.print(c);
|
||||
}
|
||||
if (len == sizeof(command)-1) { // command buffer full
|
||||
command[sizeof(command)-1] = '\r';
|
||||
}
|
||||
|
||||
if (len > 0 && command[len - 1] == '\r') { // received complete line
|
||||
command[len - 1] = 0; // replace newline with C string null terminator
|
||||
char reply[160];
|
||||
the_mesh.handleCommand(0, command, reply); // NOTE: there is no sender_timestamp via serial!
|
||||
if (reply[0]) {
|
||||
Serial.print(" -> "); Serial.println(reply);
|
||||
}
|
||||
|
||||
command[0] = 0; // reset command buffer
|
||||
}
|
||||
|
||||
the_mesh.loop();
|
||||
sensors.loop();
|
||||
#ifdef DISPLAY_CLASS
|
||||
ui_task.loop();
|
||||
#endif
|
||||
}
|
||||
@@ -29,6 +29,20 @@ build_flags = -w -DNDEBUG -DRADIOLIB_STATIC_ONLY=1 -DRADIOLIB_GODMODE=1
|
||||
-D LORA_SF=11
|
||||
-D ENABLE_PRIVATE_KEY_IMPORT=1 ; NOTE: comment these out for more secure firmware
|
||||
-D ENABLE_PRIVATE_KEY_EXPORT=1
|
||||
-D RADIOLIB_EXCLUDE_CC1101=1
|
||||
-D RADIOLIB_EXCLUDE_RF69=1
|
||||
-D RADIOLIB_EXCLUDE_SX1231=1
|
||||
-D RADIOLIB_EXCLUDE_SI443X=1
|
||||
-D RADIOLIB_EXCLUDE_RFM2X=1
|
||||
-D RADIOLIB_EXCLUDE_SX128X=1
|
||||
-D RADIOLIB_EXCLUDE_AFSK=1
|
||||
-D RADIOLIB_EXCLUDE_AX25=1
|
||||
-D RADIOLIB_EXCLUDE_HELLSCHREIBER=1
|
||||
-D RADIOLIB_EXCLUDE_MORSE=1
|
||||
-D RADIOLIB_EXCLUDE_APRS=1
|
||||
-D RADIOLIB_EXCLUDE_BELL=1
|
||||
-D RADIOLIB_EXCLUDE_RTTY=1
|
||||
-D RADIOLIB_EXCLUDE_SSTV=1
|
||||
build_src_filter =
|
||||
+<*.cpp>
|
||||
+<helpers/*.cpp>
|
||||
@@ -63,14 +77,6 @@ build_flags = ${arduino_base.build_flags}
|
||||
-D NRF52_PLATFORM
|
||||
-D LFS_NO_ASSERT=1
|
||||
|
||||
[nrf52840_base]
|
||||
extends = nrf52_base
|
||||
build_flags = ${nrf52_base.build_flags}
|
||||
lib_deps =
|
||||
${nrf52_base.lib_deps}
|
||||
rweather/Crypto @ ^0.4.0
|
||||
https://github.com/adafruit/Adafruit_nRF52_Arduino
|
||||
|
||||
; ----------------- RP2040 ---------------------
|
||||
|
||||
[rp2040_base]
|
||||
|
||||
@@ -181,7 +181,7 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
|
||||
uint8_t data[MAX_PACKET_PAYLOAD];
|
||||
int len = Utils::MACThenDecrypt(secret, data, macAndData, pkt->payload_len - i);
|
||||
if (len > 0) { // success!
|
||||
onAnonDataRecv(pkt, pkt->getPayloadType(), sender, data, len);
|
||||
onAnonDataRecv(pkt, secret, sender, data, len);
|
||||
pkt->markDoNotRetransmit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,10 +107,10 @@ protected:
|
||||
/**
|
||||
* \brief A (now decrypted) data packet has been received.
|
||||
* NOTE: these can be received multiple times (per sender/contents), via different routes
|
||||
* \param type one of: PAYLOAD_TYPE_ANON_REQ
|
||||
* \param secret ECDH shared secret
|
||||
* \param sender public key provided by sender
|
||||
*/
|
||||
virtual void onAnonDataRecv(Packet* packet, uint8_t type, const Identity& sender, uint8_t* data, size_t len) { }
|
||||
virtual void onAnonDataRecv(Packet* packet, const uint8_t* secret, const Identity& sender, uint8_t* data, size_t len) { }
|
||||
|
||||
/**
|
||||
* \brief A path TO 'sender' has been received. (also with optional 'extra' data encoded)
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
#define ADV_TYPE_CHAT 1
|
||||
#define ADV_TYPE_REPEATER 2
|
||||
#define ADV_TYPE_ROOM 3
|
||||
//FUTURE: 4..15
|
||||
#define ADV_TYPE_SENSOR 4
|
||||
//FUTURE: 5..15
|
||||
|
||||
#define ADV_LATLON_MASK 0x10
|
||||
#define ADV_FEAT1_MASK 0x20 // FUTURE
|
||||
|
||||
@@ -403,22 +403,52 @@ bool BaseChatMesh::importContact(const uint8_t src_buf[], uint8_t len) {
|
||||
}
|
||||
|
||||
int BaseChatMesh::sendLogin(const ContactInfo& recipient, const char* password, uint32_t& est_timeout) {
|
||||
int tlen;
|
||||
uint8_t temp[24];
|
||||
uint32_t now = getRTCClock()->getCurrentTimeUnique();
|
||||
memcpy(temp, &now, 4); // mostly an extra blob to help make packet_hash unique
|
||||
if (recipient.type == ADV_TYPE_ROOM) {
|
||||
memcpy(&temp[4], &recipient.sync_since, 4);
|
||||
int len = strlen(password); if (len > 15) len = 15; // max 15 chars currently
|
||||
memcpy(&temp[8], password, len);
|
||||
tlen = 8 + len;
|
||||
} else {
|
||||
int len = strlen(password); if (len > 15) len = 15; // max 15 chars currently
|
||||
memcpy(&temp[4], password, len);
|
||||
tlen = 4 + len;
|
||||
}
|
||||
mesh::Packet* pkt;
|
||||
{
|
||||
int tlen;
|
||||
uint8_t temp[24];
|
||||
uint32_t now = getRTCClock()->getCurrentTimeUnique();
|
||||
memcpy(temp, &now, 4); // mostly an extra blob to help make packet_hash unique
|
||||
if (recipient.type == ADV_TYPE_ROOM) {
|
||||
memcpy(&temp[4], &recipient.sync_since, 4);
|
||||
int len = strlen(password); if (len > 15) len = 15; // max 15 chars currently
|
||||
memcpy(&temp[8], password, len);
|
||||
tlen = 8 + len;
|
||||
} else {
|
||||
int len = strlen(password); if (len > 15) len = 15; // max 15 chars currently
|
||||
memcpy(&temp[4], password, len);
|
||||
tlen = 4 + len;
|
||||
}
|
||||
|
||||
auto pkt = createAnonDatagram(PAYLOAD_TYPE_ANON_REQ, self_id, recipient.id, recipient.shared_secret, temp, tlen);
|
||||
pkt = createAnonDatagram(PAYLOAD_TYPE_ANON_REQ, self_id, recipient.id, recipient.shared_secret, temp, tlen);
|
||||
}
|
||||
if (pkt) {
|
||||
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
|
||||
if (recipient.out_path_len < 0) {
|
||||
sendFlood(pkt);
|
||||
est_timeout = calcFloodTimeoutMillisFor(t);
|
||||
return MSG_SEND_SENT_FLOOD;
|
||||
} else {
|
||||
sendDirect(pkt, recipient.out_path, recipient.out_path_len);
|
||||
est_timeout = calcDirectTimeoutMillisFor(t, recipient.out_path_len);
|
||||
return MSG_SEND_SENT_DIRECT;
|
||||
}
|
||||
}
|
||||
return MSG_SEND_FAILED;
|
||||
}
|
||||
|
||||
int BaseChatMesh::sendRequest(const ContactInfo& recipient, const uint8_t* req_data, uint8_t data_len, uint32_t& tag, uint32_t& est_timeout) {
|
||||
if (data_len > MAX_PACKET_PAYLOAD - 16) return MSG_SEND_FAILED;
|
||||
|
||||
mesh::Packet* pkt;
|
||||
{
|
||||
uint8_t temp[MAX_PACKET_PAYLOAD];
|
||||
tag = getRTCClock()->getCurrentTimeUnique();
|
||||
memcpy(temp, &tag, 4); // mostly an extra blob to help make packet_hash unique
|
||||
memcpy(&temp[4], req_data, data_len);
|
||||
|
||||
pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.shared_secret, temp, 4 + data_len);
|
||||
}
|
||||
if (pkt) {
|
||||
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
|
||||
if (recipient.out_path_len < 0) {
|
||||
@@ -435,14 +465,17 @@ int BaseChatMesh::sendLogin(const ContactInfo& recipient, const char* password,
|
||||
}
|
||||
|
||||
int BaseChatMesh::sendRequest(const ContactInfo& recipient, uint8_t req_type, uint32_t& tag, uint32_t& est_timeout) {
|
||||
uint8_t temp[13];
|
||||
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
|
||||
mesh::Packet* pkt;
|
||||
{
|
||||
uint8_t temp[13];
|
||||
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));
|
||||
pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.shared_secret, temp, sizeof(temp));
|
||||
}
|
||||
if (pkt) {
|
||||
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
|
||||
if (recipient.out_path_len < 0) {
|
||||
|
||||
@@ -134,6 +134,7 @@ public:
|
||||
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 sendRequest(const ContactInfo& recipient, uint8_t req_type, uint32_t& tag, uint32_t& est_timeout);
|
||||
int sendRequest(const ContactInfo& recipient, const uint8_t* req_data, uint8_t data_len, 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);
|
||||
|
||||
@@ -120,21 +120,14 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
|
||||
|
||||
#define MIN_LOCAL_ADVERT_INTERVAL 60
|
||||
|
||||
void CommonCLI::checkAdvertInterval() {
|
||||
void CommonCLI::savePrefs() {
|
||||
if (_prefs->advert_interval * 2 < MIN_LOCAL_ADVERT_INTERVAL) {
|
||||
_prefs->advert_interval = 0; // turn it off, now that device has been manually configured
|
||||
}
|
||||
_callbacks->savePrefs();
|
||||
}
|
||||
|
||||
void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, char* reply) {
|
||||
while (*command == ' ') command++; // skip leading spaces
|
||||
|
||||
if (strlen(command) > 4 && command[2] == '|') { // optional prefix (for companion radio CLI)
|
||||
memcpy(reply, command, 3); // reflect the prefix back
|
||||
reply += 3;
|
||||
command += 3;
|
||||
}
|
||||
|
||||
if (memcmp(command, "reboot", 6) == 0) {
|
||||
_board->reboot(); // doesn't return
|
||||
} else if (memcmp(command, "advert", 6) == 0) {
|
||||
@@ -174,7 +167,6 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
} else if (memcmp(command, "password ", 9) == 0) {
|
||||
// change admin password
|
||||
StrHelper::strncpy(_prefs->password, &command[9], sizeof(_prefs->password));
|
||||
checkAdvertInterval();
|
||||
savePrefs();
|
||||
sprintf(reply, "password now: %s", _prefs->password); // echo back just to let admin know for sure!!
|
||||
} else if (memcmp(command, "clear stats", 11) == 0) {
|
||||
@@ -273,7 +265,6 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
strcpy(reply, "OK");
|
||||
} else if (memcmp(config, "name ", 5) == 0) {
|
||||
StrHelper::strncpy(_prefs->node_name, &config[5], sizeof(_prefs->node_name));
|
||||
checkAdvertInterval();
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else if (memcmp(config, "repeat ", 7) == 0) {
|
||||
@@ -300,12 +291,10 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
}
|
||||
} else if (memcmp(config, "lat ", 4) == 0) {
|
||||
_prefs->node_lat = atof(&config[4]);
|
||||
checkAdvertInterval();
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else if (memcmp(config, "lon ", 4) == 0) {
|
||||
_prefs->node_lon = atof(&config[4]);
|
||||
checkAdvertInterval();
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else if (memcmp(config, "rxdelay ", 8) == 0) {
|
||||
|
||||
@@ -55,10 +55,7 @@ class CommonCLI {
|
||||
char tmp[80];
|
||||
|
||||
mesh::RTCClock* getRTCClock() { return _rtc; }
|
||||
void savePrefs() { _callbacks->savePrefs(); }
|
||||
|
||||
void checkAdvertInterval();
|
||||
|
||||
void savePrefs();
|
||||
void loadPrefsInt(FILESYSTEM* _fs, const char* filename);
|
||||
|
||||
public:
|
||||
|
||||
@@ -9,6 +9,59 @@ class CustomLR1110 : public LR1110 {
|
||||
public:
|
||||
CustomLR1110(Module *mod) : LR1110(mod) { }
|
||||
|
||||
RadioLibTime_t getTimeOnAir(size_t len) override {
|
||||
// calculate number of symbols
|
||||
float N_symbol = 0;
|
||||
if(this->codingRate <= RADIOLIB_LR11X0_LORA_CR_4_8_SHORT) {
|
||||
// legacy coding rate - nice and simple
|
||||
// get SF coefficients
|
||||
float coeff1 = 0;
|
||||
int16_t coeff2 = 0;
|
||||
int16_t coeff3 = 0;
|
||||
if(this->spreadingFactor < 7) {
|
||||
// SF5, SF6
|
||||
coeff1 = 6.25;
|
||||
coeff2 = 4*this->spreadingFactor;
|
||||
coeff3 = 4*this->spreadingFactor;
|
||||
} else if(this->spreadingFactor < 11) {
|
||||
// SF7. SF8, SF9, SF10
|
||||
coeff1 = 4.25;
|
||||
coeff2 = 4*this->spreadingFactor + 8;
|
||||
coeff3 = 4*this->spreadingFactor;
|
||||
} else {
|
||||
// SF11, SF12
|
||||
coeff1 = 4.25;
|
||||
coeff2 = 4*this->spreadingFactor + 8;
|
||||
coeff3 = 4*(this->spreadingFactor - 2);
|
||||
}
|
||||
|
||||
// get CRC length
|
||||
int16_t N_bitCRC = 16;
|
||||
if(this->crcTypeLoRa == RADIOLIB_LR11X0_LORA_CRC_DISABLED) {
|
||||
N_bitCRC = 0;
|
||||
}
|
||||
|
||||
// get header length
|
||||
int16_t N_symbolHeader = 20;
|
||||
if(this->headerType == RADIOLIB_LR11X0_LORA_HEADER_IMPLICIT) {
|
||||
N_symbolHeader = 0;
|
||||
}
|
||||
|
||||
// calculate number of LoRa preamble symbols - NO! Lora preamble is already in symbols
|
||||
// uint32_t N_symbolPreamble = (this->preambleLengthLoRa & 0x0F) * (uint32_t(1) << ((this->preambleLengthLoRa & 0xF0) >> 4));
|
||||
|
||||
// calculate the number of symbols - nope
|
||||
// N_symbol = (float)N_symbolPreamble + coeff1 + 8.0f + ceilf((float)RADIOLIB_MAX((int16_t)(8 * len + N_bitCRC - coeff2 + N_symbolHeader), (int16_t)0) / (float)coeff3) * (float)(this->codingRate + 4);
|
||||
// calculate the number of symbols - using only preamblelora because it's already in symbols
|
||||
N_symbol = (float)preambleLengthLoRa + coeff1 + 8.0f + ceilf((float)RADIOLIB_MAX((int16_t)(8 * len + N_bitCRC - coeff2 + N_symbolHeader), (int16_t)0) / (float)coeff3) * (float)(this->codingRate + 4);
|
||||
} else {
|
||||
// long interleaving - not needed for this modem
|
||||
}
|
||||
|
||||
// get time-on-air in us
|
||||
return(((uint32_t(1) << this->spreadingFactor) / this->bandwidthKhz) * N_symbol * 1000.0f);
|
||||
}
|
||||
|
||||
bool isReceiving() {
|
||||
uint16_t irq = getIrqStatus();
|
||||
bool detected = ((irq & LR1110_IRQ_HEADER_VALID) || (irq & LR1110_IRQ_HAS_PREAMBLE));
|
||||
|
||||
@@ -1,6 +1,27 @@
|
||||
#include "SerialBLEInterface.h"
|
||||
|
||||
static SerialBLEInterface* instance;
|
||||
|
||||
void SerialBLEInterface::onConnect(uint16_t connection_handle) {
|
||||
BLE_DEBUG_PRINTLN("SerialBLEInterface: connected");
|
||||
if(instance){
|
||||
instance->_isDeviceConnected = true;
|
||||
// no need to stop advertising on connect, as the ble stack does this automatically
|
||||
}
|
||||
}
|
||||
|
||||
void SerialBLEInterface::onDisconnect(uint16_t connection_handle, uint8_t reason) {
|
||||
BLE_DEBUG_PRINTLN("SerialBLEInterface: disconnected reason=%d", reason);
|
||||
if(instance){
|
||||
instance->_isDeviceConnected = false;
|
||||
instance->startAdv();
|
||||
}
|
||||
}
|
||||
|
||||
void SerialBLEInterface::begin(const char* device_name, uint32_t pin_code) {
|
||||
|
||||
instance = this;
|
||||
|
||||
char charpin[20];
|
||||
sprintf(charpin, "%d", pin_code);
|
||||
|
||||
@@ -13,11 +34,31 @@ void SerialBLEInterface::begin(const char* device_name, uint32_t pin_code) {
|
||||
Bluefruit.Security.setMITM(true);
|
||||
Bluefruit.Security.setPIN(charpin);
|
||||
|
||||
Bluefruit.Periph.setConnectCallback(onConnect);
|
||||
Bluefruit.Periph.setDisconnectCallback(onDisconnect);
|
||||
|
||||
// To be consistent OTA DFU should be added first if it exists
|
||||
//bledfu.begin();
|
||||
|
||||
// Configure and start the BLE Uart service
|
||||
bleuart.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM);
|
||||
bleuart.begin();
|
||||
|
||||
}
|
||||
|
||||
void SerialBLEInterface::startAdv() {
|
||||
|
||||
BLE_DEBUG_PRINTLN("SerialBLEInterface: starting advertising");
|
||||
|
||||
// clean restart if already advertising
|
||||
if(Bluefruit.Advertising.isRunning()){
|
||||
BLE_DEBUG_PRINTLN("SerialBLEInterface: already advertising, stopping to allow clean restart");
|
||||
Bluefruit.Advertising.stop();
|
||||
}
|
||||
|
||||
Bluefruit.Advertising.clearData(); // clear advertising data
|
||||
Bluefruit.ScanResponse.clearData(); // clear scan response data
|
||||
|
||||
// Advertising packet
|
||||
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
|
||||
Bluefruit.Advertising.addTxPower();
|
||||
@@ -38,10 +79,25 @@ void SerialBLEInterface::startAdv() {
|
||||
* For recommended advertising interval
|
||||
* https://developer.apple.com/library/content/qa/qa1931/_index.html
|
||||
*/
|
||||
Bluefruit.Advertising.restartOnDisconnect(true);
|
||||
Bluefruit.Advertising.restartOnDisconnect(false); // don't restart automatically as we handle it in onDisconnect
|
||||
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
|
||||
Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds
|
||||
|
||||
}
|
||||
|
||||
void SerialBLEInterface::stopAdv() {
|
||||
|
||||
BLE_DEBUG_PRINTLN("SerialBLEInterface: stopping advertising");
|
||||
|
||||
// we only want to stop advertising if it's running, otherwise an invalid state error is logged by ble stack
|
||||
if(!Bluefruit.Advertising.isRunning()){
|
||||
return;
|
||||
}
|
||||
|
||||
// stop advertising
|
||||
Bluefruit.Advertising.stop();
|
||||
|
||||
}
|
||||
|
||||
// ---------- public methods
|
||||
@@ -52,25 +108,14 @@ void SerialBLEInterface::enable() {
|
||||
_isEnabled = true;
|
||||
clearBuffers();
|
||||
|
||||
// Configure and start the BLE Uart service
|
||||
bleuart.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM);
|
||||
bleuart.begin();
|
||||
|
||||
// Start advertising
|
||||
startAdv();
|
||||
|
||||
checkAdvRestart = false;
|
||||
}
|
||||
|
||||
void SerialBLEInterface::disable() {
|
||||
_isEnabled = false;
|
||||
|
||||
BLE_DEBUG_PRINTLN("SerialBLEInterface::disable");
|
||||
|
||||
Bluefruit.Advertising.stop();
|
||||
|
||||
oldDeviceConnected = deviceConnected = false;
|
||||
checkAdvRestart = false;
|
||||
stopAdv();
|
||||
}
|
||||
|
||||
size_t SerialBLEInterface::writeFrame(const uint8_t src[], size_t len) {
|
||||
@@ -79,7 +124,7 @@ size_t SerialBLEInterface::writeFrame(const uint8_t src[], size_t len) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (deviceConnected && len > 0) {
|
||||
if (_isDeviceConnected && len > 0) {
|
||||
if (send_queue_len >= FRAME_QUEUE_SIZE) {
|
||||
BLE_DEBUG_PRINTLN("writeFrame(), send_queue is full!");
|
||||
return 0;
|
||||
@@ -115,44 +160,14 @@ size_t SerialBLEInterface::checkRecvFrame(uint8_t dest[]) {
|
||||
} else {
|
||||
int len = bleuart.available();
|
||||
if (len > 0) {
|
||||
deviceConnected = true; // should probably use the callback to monitor cx
|
||||
bleuart.readBytes(dest, len);
|
||||
BLE_DEBUG_PRINTLN("readBytes: sz=%d, hdr=%d", len, (uint32_t) dest[0]);
|
||||
return len;
|
||||
}
|
||||
}
|
||||
|
||||
if (Bluefruit.connected() == 0) deviceConnected = false;
|
||||
|
||||
if (deviceConnected != oldDeviceConnected) {
|
||||
if (!deviceConnected) { // disconnecting
|
||||
clearBuffers();
|
||||
|
||||
BLE_DEBUG_PRINTLN("SerialBLEInterface -> disconnecting...");
|
||||
delay(500); // give the bluetooth stack the chance to get things ready
|
||||
|
||||
checkAdvRestart = true;
|
||||
} else {
|
||||
BLE_DEBUG_PRINTLN("SerialBLEInterface -> stopping advertising");
|
||||
BLE_DEBUG_PRINTLN("SerialBLEInterface -> connecting...");
|
||||
// connecting
|
||||
// do stuff here on connecting
|
||||
Bluefruit.Advertising.stop();
|
||||
checkAdvRestart = false;
|
||||
}
|
||||
oldDeviceConnected = deviceConnected;
|
||||
}
|
||||
|
||||
if (checkAdvRestart) {
|
||||
if (Bluefruit.connected() == 0) {
|
||||
BLE_DEBUG_PRINTLN("SerialBLEInterface -> re-starting advertising");
|
||||
startAdv();
|
||||
}
|
||||
checkAdvRestart = false;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool SerialBLEInterface::isConnected() const {
|
||||
return deviceConnected; //pServer != NULL && pServer->getConnectedCount() > 0;
|
||||
return _isDeviceConnected;
|
||||
}
|
||||
|
||||
@@ -5,10 +5,8 @@
|
||||
|
||||
class SerialBLEInterface : public BaseSerialInterface {
|
||||
BLEUart bleuart;
|
||||
bool deviceConnected;
|
||||
bool oldDeviceConnected;
|
||||
bool checkAdvRestart;
|
||||
bool _isEnabled;
|
||||
bool _isDeviceConnected;
|
||||
unsigned long _last_write;
|
||||
|
||||
struct Frame {
|
||||
@@ -21,18 +19,19 @@ class SerialBLEInterface : public BaseSerialInterface {
|
||||
Frame send_queue[FRAME_QUEUE_SIZE];
|
||||
|
||||
void clearBuffers() { send_queue_len = 0; }
|
||||
void startAdv();
|
||||
static void onConnect(uint16_t connection_handle);
|
||||
static void onDisconnect(uint16_t connection_handle, uint8_t reason);
|
||||
|
||||
public:
|
||||
SerialBLEInterface() {
|
||||
deviceConnected = false;
|
||||
oldDeviceConnected = false;
|
||||
checkAdvRestart = false;
|
||||
_isEnabled = false;
|
||||
_isDeviceConnected = false;
|
||||
_last_write = 0;
|
||||
send_queue_len = 0;
|
||||
}
|
||||
|
||||
void startAdv();
|
||||
void stopAdv();
|
||||
void begin(const char* device_name, uint32_t pin_code);
|
||||
|
||||
// BaseSerialInterface methods
|
||||
|
||||
30
src/helpers/rp2040/XiaoRP2040Board.cpp
Normal file
30
src/helpers/rp2040/XiaoRP2040Board.cpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#include "XiaoRP2040Board.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
|
||||
void XiaoRP2040Board::begin() {
|
||||
// for future use, sub-classes SHOULD call this from their begin()
|
||||
startup_reason = BD_STARTUP_NORMAL;
|
||||
|
||||
#ifdef P_LORA_TX_LED
|
||||
pinMode(P_LORA_TX_LED, OUTPUT);
|
||||
#endif
|
||||
|
||||
#ifdef PIN_VBAT_READ
|
||||
pinMode(PIN_VBAT_READ, INPUT);
|
||||
#endif
|
||||
|
||||
#if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL)
|
||||
Wire.setSDA(PIN_BOARD_SDA);
|
||||
Wire.setSCL(PIN_BOARD_SCL);
|
||||
#endif
|
||||
|
||||
Wire.begin();
|
||||
|
||||
delay(10); // give sx1262 some time to power up
|
||||
}
|
||||
|
||||
bool XiaoRP2040Board::startOTAUpdate(const char *id, char reply[]) {
|
||||
return false;
|
||||
}
|
||||
75
src/helpers/rp2040/XiaoRP2040Board.h
Normal file
75
src/helpers/rp2040/XiaoRP2040Board.h
Normal file
@@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <MeshCore.h>
|
||||
|
||||
// LoRa radio module pins for the Xiao RP2040
|
||||
// https://wiki.seeedstudio.com/XIAO-RP2040/
|
||||
|
||||
#define P_LORA_DIO_1 27 // D1
|
||||
#define P_LORA_NSS 6 // D4
|
||||
#define P_LORA_RESET 28 // D2
|
||||
#define P_LORA_BUSY 29 // D3
|
||||
#define P_LORA_TX_LED 17
|
||||
|
||||
#define SX126X_RXEN 7 // D5
|
||||
#define SX126X_TXEN -1
|
||||
|
||||
#define SX126X_DIO2_AS_RF_SWITCH true
|
||||
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
|
||||
|
||||
/*
|
||||
* This board has no built-in way to read battery voltage.
|
||||
* Nevertheless it's very easy to make it work, you only require two 1% resistors.
|
||||
* If your using the WIO SX1262 Addon for xaio, make sure you dont connect D0!
|
||||
*
|
||||
* BAT+ -----+
|
||||
* |
|
||||
* VSYS --+ -/\/\/\/\- --+
|
||||
* 200k |
|
||||
* +-- D0
|
||||
* |
|
||||
* GND --+ -/\/\/\/\- --+
|
||||
* | 100k
|
||||
* BAT- -----+
|
||||
*/
|
||||
#define PIN_VBAT_READ 26 // D0
|
||||
#define BATTERY_SAMPLES 8
|
||||
#define ADC_MULTIPLIER (3.0f * 3.3f * 1000)
|
||||
|
||||
class XiaoRP2040Board : public mesh::MainBoard {
|
||||
protected:
|
||||
uint8_t startup_reason;
|
||||
|
||||
public:
|
||||
void begin();
|
||||
uint8_t getStartupReason() const override { return startup_reason; }
|
||||
|
||||
#ifdef P_LORA_TX_LED
|
||||
void onBeforeTransmit() override { digitalWrite(P_LORA_TX_LED, HIGH); }
|
||||
void onAfterTransmit() override { digitalWrite(P_LORA_TX_LED, LOW); }
|
||||
#endif
|
||||
|
||||
|
||||
uint16_t getBattMilliVolts() override {
|
||||
#if defined(PIN_VBAT_READ) && defined(ADC_MULTIPLIER)
|
||||
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;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
const char *getManufacturerName() const override { return "Xiao RP2040"; }
|
||||
|
||||
void reboot() override { rp2040.reboot(); }
|
||||
|
||||
bool startOTAUpdate(const char *id, char reply[]) override;
|
||||
};
|
||||
@@ -24,6 +24,14 @@ static Adafruit_BME280 BME280;
|
||||
static Adafruit_BMP280 BMP280;
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_SHTC3
|
||||
#include <Adafruit_SHTC3.h>
|
||||
static Adafruit_SHTC3 SHTC3;
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_LPS22HB
|
||||
#include <Arduino_LPS22HB.h>
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_INA3221
|
||||
#define TELEM_INA3221_ADDRESS 0x42 // INA3221 3 channel current sensor I2C address
|
||||
@@ -76,28 +84,48 @@ bool EnvironmentSensorManager::begin() {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_SHTC3
|
||||
if (SHTC3.begin()) {
|
||||
MESH_DEBUG_PRINTLN("Found sensor: SHTC3");
|
||||
SHTC3_initialized = true;
|
||||
} else {
|
||||
SHTC3_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("SHTC3 was not found at I2C address %02X", 0x70);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_LPS22HB
|
||||
if (BARO.begin()) {
|
||||
MESH_DEBUG_PRINTLN("Found sensor: LPS22HB");
|
||||
LPS22HB_initialized = true;
|
||||
} else {
|
||||
LPS22HB_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("LPS22HB was not found at I2C address %02X", 0x5C);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_INA3221
|
||||
if (INA3221.begin(TELEM_INA3221_ADDRESS, &Wire)) {
|
||||
MESH_DEBUG_PRINTLN("Found INA3221 at address: %02X", TELEM_INA3221_ADDRESS);
|
||||
MESH_DEBUG_PRINTLN("%04X %04X", INA3221.getDieID(), INA3221.getManufacturerID());
|
||||
MESH_DEBUG_PRINTLN("Found INA3221 at address: %02X", TELEM_INA3221_ADDRESS);
|
||||
MESH_DEBUG_PRINTLN("%04X %04X", INA3221.getDieID(), INA3221.getManufacturerID());
|
||||
|
||||
for(int i = 0; i < 3; i++) {
|
||||
INA3221.setShuntResistance(i, TELEM_INA3221_SHUNT_VALUE);
|
||||
}
|
||||
INA3221_initialized = true;
|
||||
for(int i = 0; i < 3; i++) {
|
||||
INA3221.setShuntResistance(i, TELEM_INA3221_SHUNT_VALUE);
|
||||
}
|
||||
INA3221_initialized = true;
|
||||
} else {
|
||||
INA3221_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("INA3221 was not found at I2C address %02X", TELEM_INA3221_ADDRESS);
|
||||
INA3221_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("INA3221 was not found at I2C address %02X", TELEM_INA3221_ADDRESS);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_INA219
|
||||
if (INA219.begin(&Wire)) {
|
||||
MESH_DEBUG_PRINTLN("Found INA219 at address: %02X", TELEM_INA219_ADDRESS);
|
||||
INA219_initialized = true;
|
||||
MESH_DEBUG_PRINTLN("Found INA219 at address: %02X", TELEM_INA219_ADDRESS);
|
||||
INA219_initialized = true;
|
||||
} else {
|
||||
INA219_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("INA219 was not found at I2C address %02X", TELEM_INA219_ADDRESS);
|
||||
INA219_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("INA219 was not found at I2C address %02X", TELEM_INA219_ADDRESS);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -126,7 +154,7 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen
|
||||
if (BME280_initialized) {
|
||||
telemetry.addTemperature(TELEM_CHANNEL_SELF, BME280.readTemperature());
|
||||
telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, BME280.readHumidity());
|
||||
telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BME280.readPressure());
|
||||
telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BME280.readPressure()/100);
|
||||
telemetry.addAltitude(TELEM_CHANNEL_SELF, BME280.readAltitude(TELEM_BME280_SEALEVELPRESSURE_HPA));
|
||||
}
|
||||
#endif
|
||||
@@ -139,6 +167,23 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_SHTC3
|
||||
if (SHTC3_initialized) {
|
||||
sensors_event_t humidity, temp;
|
||||
SHTC3.getEvent(&humidity, &temp);
|
||||
|
||||
telemetry.addTemperature(TELEM_CHANNEL_SELF, temp.temperature);
|
||||
telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, humidity.relative_humidity);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_LPS22HB
|
||||
if (LPS22HB_initialized) {
|
||||
telemetry.addTemperature(TELEM_CHANNEL_SELF, BARO.readTemperature());
|
||||
telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BARO.readPressure());
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_INA3221
|
||||
if (INA3221_initialized) {
|
||||
for(int i = 0; i < TELEM_INA3221_NUM_CHANNELS; i++) {
|
||||
@@ -157,10 +202,10 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen
|
||||
|
||||
#if ENV_INCLUDE_INA219
|
||||
if (INA219_initialized) {
|
||||
telemetry.addVoltage(next_available_channel, INA219.getBusVoltage_V());
|
||||
telemetry.addCurrent(next_available_channel, INA219.getCurrent_mA() / 1000);
|
||||
telemetry.addPower(next_available_channel, INA219.getPower_mW() / 1000);
|
||||
next_available_channel++;
|
||||
telemetry.addVoltage(next_available_channel, INA219.getBusVoltage_V());
|
||||
telemetry.addCurrent(next_available_channel, INA219.getCurrent_mA() / 1000);
|
||||
telemetry.addPower(next_available_channel, INA219.getPower_mW() / 1000);
|
||||
next_available_channel++;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@ protected:
|
||||
bool BMP280_initialized = false;
|
||||
bool INA3221_initialized = false;
|
||||
bool INA219_initialized = false;
|
||||
bool SHTC3_initialized = false;
|
||||
bool LPS22HB_initialized = false;
|
||||
|
||||
bool gps_detected = false;
|
||||
bool gps_active = false;
|
||||
|
||||
@@ -46,6 +46,13 @@ void genericBuzzer::shutdown() {
|
||||
|
||||
void genericBuzzer::quiet(bool buzzer_state) {
|
||||
_is_quiet = buzzer_state;
|
||||
#ifdef PIN_BUZZER_EN
|
||||
if (_is_quiet) {
|
||||
digitalWrite(PIN_BUZZER_EN, LOW);
|
||||
} else {
|
||||
digitalWrite(PIN_BUZZER_EN, HIGH);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool genericBuzzer::isQuiet() {
|
||||
|
||||
@@ -23,9 +23,9 @@ build_flags =
|
||||
-D ENV_INCLUDE_INA3221=1
|
||||
-D ENV_INCLUDE_INA219=1
|
||||
-D ENV_INCLUDE_GPS=1
|
||||
-D PIN_GPS_RX=45
|
||||
-D PIN_GPS_TX=46
|
||||
-D PIN_GPS_EN=-1
|
||||
-D PIN_GPS_RX=47
|
||||
-D PIN_GPS_TX=48
|
||||
-D PIN_GPS_EN=26
|
||||
build_src_filter = ${esp32_base.build_src_filter}
|
||||
+<../variants/heltec_v3>
|
||||
+<helpers/sensors>
|
||||
@@ -198,3 +198,20 @@ build_src_filter = ${Heltec_lora32_v3.build_src_filter}
|
||||
lib_deps =
|
||||
${Heltec_lora32_v3.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Heltec_WSL3_sensor]
|
||||
extends = Heltec_lora32_v3
|
||||
build_flags =
|
||||
${Heltec_lora32_v3.build_flags}
|
||||
-D ADVERT_NAME='"Heltec Sensor"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_lora32_v3.build_src_filter}
|
||||
+<../examples/simple_sensor>
|
||||
lib_deps =
|
||||
${Heltec_lora32_v3.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ build_flags =
|
||||
-D MAX_CONTACTS=100
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D DISPLAY_CLASS=E213Display
|
||||
-D BLE_PIN_CODE=0 ; dynamic, random PIN
|
||||
-D BLE_PIN_CODE=123456 ; dynamic, random PIN
|
||||
-D BLE_DEBUG_LOGGING=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
build_src_filter = ${Heltec_Wireless_Paper_base.build_src_filter}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
#include "target.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
HeltecV3Board board;
|
||||
|
||||
static SPIClass spi;
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi);
|
||||
#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);
|
||||
|
||||
@@ -21,7 +24,11 @@ DISPLAY_CLASS display;
|
||||
bool radio_init() {
|
||||
fallback_clock.begin();
|
||||
rtc_clock.begin(Wire);
|
||||
#if defined(P_LORA_SCLK)
|
||||
return radio.std_init(&spi);
|
||||
#else
|
||||
return radio.std_init();
|
||||
#endif
|
||||
}
|
||||
|
||||
uint32_t radio_get_rng_seed() {
|
||||
@@ -42,4 +49,4 @@ void radio_set_tx_power(uint8_t dbm) {
|
||||
mesh::LocalIdentity radio_new_identity() {
|
||||
RadioNoiseListener rng(radio);
|
||||
return mesh::LocalIdentity(&rng); // create new random identity
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
#define RADIOLIB_STATIC_ONLY 1
|
||||
#include <RadioLib.h>
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
#include <helpers/CustomSX1262Wrapper.h>
|
||||
#include <helpers/HeltecV3Board.h>
|
||||
#include <helpers/RadioLibWrappers.h>
|
||||
#include <helpers/HeltecV3Board.h>
|
||||
#include <helpers/CustomSX1262Wrapper.h>
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
#include <helpers/SensorManager.h>
|
||||
#ifdef DISPLAY_CLASS
|
||||
#include <helpers/ui/E213Display.h>
|
||||
|
||||
@@ -69,4 +69,21 @@ build_src_filter = ${LilyGo_TBeam_SX1262.build_src_filter}
|
||||
+<../examples/simple_repeater>
|
||||
lib_deps =
|
||||
${LilyGo_TBeam_SX1262.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
[env:Tbeam_SX1262_room_server]
|
||||
extends = LilyGo_TBeam_SX1262
|
||||
build_flags =
|
||||
${LilyGo_TBeam_SX1262.build_flags}
|
||||
-D ADVERT_NAME='"Tbeam SX1262 Room"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D ROOM_PASSWORD='"hello"'
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${LilyGo_TBeam_SX1262.build_src_filter}
|
||||
+<../examples/simple_room_server>
|
||||
lib_deps =
|
||||
${LilyGo_TBeam_SX1262.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
@@ -5,13 +5,8 @@
|
||||
|
||||
MeshadventurerBoard 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
|
||||
|
||||
static SPIClass spi;
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi);
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
ESP32RTCClock fallback_clock;
|
||||
@@ -30,40 +25,12 @@ MASensorManager sensors = MASensorManager(nmea);
|
||||
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);
|
||||
return radio.std_init(&spi);
|
||||
#else
|
||||
return radio.std_init();
|
||||
#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() {
|
||||
|
||||
91
variants/minewsemi_me25ls01/MinewsemiME25LS01Board.cpp
Normal file
91
variants/minewsemi_me25ls01/MinewsemiME25LS01Board.cpp
Normal file
@@ -0,0 +1,91 @@
|
||||
#include <Arduino.h>
|
||||
#include "MinewsemiME25LS01Board.h"
|
||||
#include <Wire.h>
|
||||
|
||||
#include <bluefruit.h>
|
||||
|
||||
void MinewsemiME25LS01Board::begin() {
|
||||
// for future use, sub-classes SHOULD call this from their begin()
|
||||
startup_reason = BD_STARTUP_NORMAL;
|
||||
btn_prev_state = HIGH;
|
||||
|
||||
pinMode(PIN_VBAT_READ, INPUT);
|
||||
|
||||
sd_power_mode_set(NRF_POWER_MODE_LOWPWR);
|
||||
|
||||
#ifdef BUTTON_PIN
|
||||
pinMode(BUTTON_PIN, INPUT);
|
||||
pinMode(LED_PIN, OUTPUT);
|
||||
#endif
|
||||
|
||||
#if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL)
|
||||
Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL);
|
||||
#endif
|
||||
|
||||
Wire.begin();
|
||||
|
||||
#ifdef P_LORA_TX_LED
|
||||
pinMode(P_LORA_TX_LED, OUTPUT);
|
||||
digitalWrite(P_LORA_TX_LED, LOW);
|
||||
#endif
|
||||
|
||||
delay(10); // give sx1262 some time to power up
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
|
||||
bool MinewsemiME25LS01Board::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("Minewsemi_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;
|
||||
}
|
||||
88
variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h
Normal file
88
variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h
Normal file
@@ -0,0 +1,88 @@
|
||||
#pragma once
|
||||
|
||||
#include <MeshCore.h>
|
||||
#include <Arduino.h>
|
||||
|
||||
// LoRa and SPI pins
|
||||
|
||||
#define P_LORA_DIO_1 (32 + 12) // P1.12
|
||||
#define P_LORA_NSS (32 + 13) // P1.13
|
||||
#define P_LORA_RESET (32 + 11) // P1.11
|
||||
#define P_LORA_BUSY (32 + 10) // P1.10
|
||||
#define P_LORA_SCLK (32 + 15) // P1.15
|
||||
#define P_LORA_MISO (0 + 29) // P0.29
|
||||
#define P_LORA_MOSI (0 + 2) // P0.2
|
||||
|
||||
#define LR11X0_DIO_AS_RF_SWITCH true
|
||||
#define LR11X0_DIO3_TCXO_VOLTAGE 1.6
|
||||
|
||||
#define PIN_VBAT_READ BATTERY_PIN
|
||||
#define ADC_MULTIPLIER (1.815f) // dependent on voltage divider resistors. TODO: more accurate battery tracking
|
||||
|
||||
|
||||
class MinewsemiME25LS01Board : public mesh::MainBoard {
|
||||
protected:
|
||||
uint8_t startup_reason;
|
||||
uint8_t btn_prev_state;
|
||||
|
||||
public:
|
||||
void begin();
|
||||
|
||||
#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);
|
||||
}
|
||||
|
||||
uint8_t getStartupReason() const override { return startup_reason; }
|
||||
|
||||
const char* getManufacturerName() const override {
|
||||
return "Minewsemi";
|
||||
}
|
||||
|
||||
void powerOff() override {
|
||||
#ifdef HAS_GPS
|
||||
digitalWrite(GPS_VRTC_EN, LOW);
|
||||
digitalWrite(GPS_RESET, LOW);
|
||||
digitalWrite(GPS_SLEEP_INT, LOW);
|
||||
digitalWrite(GPS_RTC_INT, LOW);
|
||||
pinMode(GPS_RESETB, OUTPUT);
|
||||
digitalWrite(GPS_RESETB, LOW);
|
||||
#endif
|
||||
|
||||
#ifdef BUZZER_EN
|
||||
digitalWrite(BUZZER_EN, LOW);
|
||||
#endif
|
||||
|
||||
#ifdef LED_PIN
|
||||
digitalWrite(LED_PIN, LOW);
|
||||
#endif
|
||||
#ifdef BUTTON_PIN
|
||||
nrf_gpio_cfg_sense_input(digitalPinToInterrupt(BUTTON_PIN), NRF_GPIO_PIN_PULLUP, NRF_GPIO_PIN_SENSE_HIGH);
|
||||
#endif
|
||||
sd_power_system_off();
|
||||
}
|
||||
|
||||
#if defined(P_LORA_TX_LED)
|
||||
void onBeforeTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, HIGH);// turn TX LED on
|
||||
}
|
||||
void onAfterTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
void reboot() override {
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
|
||||
bool startOTAUpdate(const char* id, char reply[]) override;
|
||||
};
|
||||
24
variants/minewsemi_me25ls01/NullDisplayDriver.h
Normal file
24
variants/minewsemi_me25ls01/NullDisplayDriver.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <helpers/ui/DisplayDriver.h>
|
||||
|
||||
class NullDisplayDriver : public DisplayDriver {
|
||||
public:
|
||||
NullDisplayDriver() : DisplayDriver(128, 64) { }
|
||||
bool begin() { return false; } // not present
|
||||
|
||||
bool isOn() override { return false; }
|
||||
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 { return 0; }
|
||||
void endFrame() { }
|
||||
};
|
||||
164
variants/minewsemi_me25ls01/platformio.ini
Normal file
164
variants/minewsemi_me25ls01/platformio.ini
Normal file
@@ -0,0 +1,164 @@
|
||||
; ----------------- NRF52 me25ls01---------------------
|
||||
[nrf52840_me25ls01]
|
||||
extends = nrf52_base
|
||||
platform_packages = framework-arduinoadafruitnrf52
|
||||
build_flags = ${nrf52_base.build_flags}
|
||||
-I src/helpers/nrf52
|
||||
-I lib/nrf52/s140_nrf52_7.3.0_API/include
|
||||
-I lib/nrf52/s140_nrf52_7.3.0_API/include/nrf52
|
||||
lib_ignore =
|
||||
BluetoothOTA
|
||||
lib5b4
|
||||
lib_deps =
|
||||
${nrf52_base.lib_deps}
|
||||
rweather/Crypto @ ^0.4.0
|
||||
|
||||
[me25ls01]
|
||||
extends = nrf52840_me25ls01
|
||||
board = minewsemi_me25ls01
|
||||
board_build.ldscript = boards/nrf52840_s140_v7.ld
|
||||
build_flags = ${nrf52840_me25ls01.build_flags}
|
||||
-I variants/minewsemi_me25ls01
|
||||
-D me25ls01
|
||||
-D PIN_USER_BTN=27
|
||||
-D USER_BTN_PRESSED=HIGH
|
||||
-D PIN_STATUS_LED=39
|
||||
-D P_LORA_TX_LED=22
|
||||
-D RADIO_CLASS=CustomLR1110
|
||||
-D WRAPPER_CLASS=CustomLR1110Wrapper
|
||||
-D LORA_TX_POWER=22
|
||||
-D ENV_INCLUDE_GPS=0
|
||||
-D ENV_INCLUDE_AHTX0=1
|
||||
-D ENV_INCLUDE_BME280=1
|
||||
-D ENV_INCLUDE_INA3221=1
|
||||
-D ENV_INCLUDE_INA219=1
|
||||
build_src_filter = ${nrf52840_me25ls01.build_src_filter}
|
||||
+<helpers/*.cpp>
|
||||
+<../variants/minewsemi_me25ls01>
|
||||
+<helpers/sensors>
|
||||
debug_tool = jlink
|
||||
upload_protocol = nrfutil
|
||||
lib_deps = ${nrf52840_me25ls01.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
stevemarple/MicroNMEA @ ^2.0.6
|
||||
end2endzone/NonBlockingRTTTL@^1.3.0
|
||||
adafruit/Adafruit SSD1306 @ ^2.5.13
|
||||
adafruit/Adafruit INA3221 Library @ ^1.0.1
|
||||
adafruit/Adafruit INA219 @ ^1.2.3
|
||||
adafruit/Adafruit AHTX0 @ ^2.0.5
|
||||
adafruit/Adafruit BME280 Library @ ^2.3.0
|
||||
|
||||
[env:Minewsemi_me25ls01_companion_radio_ble]
|
||||
extends = me25ls01
|
||||
build_flags = ${me25ls01.build_flags}
|
||||
-D MAX_CONTACTS=100
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D BLE_PIN_CODE=123456
|
||||
; -D BLE_DEBUG_LOGGING=1
|
||||
-D MESH_PACKET_LOGGING=1
|
||||
-D MESH_DEBUG=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
-D RX_BOOSTED_GAIN=true
|
||||
-D RF_SWITCH_TABLE
|
||||
-D DISPLAY_CLASS=NullDisplayDriver
|
||||
;-D PIN_BUZZER=25
|
||||
;-D PIN_BUZZER_EN=37
|
||||
build_src_filter = ${me25ls01.build_src_filter}
|
||||
+<helpers/nrf52/SerialBLEInterface.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
lib_deps = ${me25ls01.lib_deps}
|
||||
adafruit/RTClib @ ^2.1.3
|
||||
|
||||
|
||||
[env:Minewsemi_me25ls01_repeater]
|
||||
extends = me25ls01
|
||||
build_flags = ${me25ls01.build_flags}
|
||||
-D MAX_CONTACTS=100
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D BLE_PIN_CODE=123456
|
||||
; -D BLE_DEBUG_LOGGING=1
|
||||
-D MESH_PACKET_LOGGING=1
|
||||
-D MESH_DEBUG=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
-D RX_BOOSTED_GAIN=true
|
||||
-D RF_SWITCH_TABLE
|
||||
-D ADVERT_NAME='"ME25LS01 Repeater"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=8
|
||||
-D DISPLAY_CLASS=NullDisplayDriver
|
||||
build_src_filter = ${me25ls01.build_src_filter}
|
||||
+<../examples/simple_repeater>
|
||||
lib_deps = ${me25ls01.lib_deps}
|
||||
adafruit/RTClib @ ^2.1.3
|
||||
|
||||
|
||||
|
||||
[env:Minewsemi_me25ls01_room_server]
|
||||
extends = me25ls01
|
||||
build_flags = ${me25ls01.build_flags}
|
||||
-D MAX_CONTACTS=100
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
; -D BLE_PIN_CODE=123456
|
||||
; -D BLE_DEBUG_LOGGING=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
-D RX_BOOSTED_GAIN=true
|
||||
-D RF_SWITCH_TABLE
|
||||
-D ADVERT_NAME='"ME25LS01 Room"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D ROOM_PASSWORD='"hello"'
|
||||
-D MAX_NEIGHBOURS=8
|
||||
-D DISPLAY_CLASS=NullDisplayDriver
|
||||
build_src_filter = ${me25ls01.build_src_filter}
|
||||
+<../examples/simple_room_server>
|
||||
lib_deps = ${me25ls01.lib_deps}
|
||||
adafruit/RTClib @ ^2.1.3
|
||||
|
||||
[env:Minewsemi_me25ls01_terminal_chat]
|
||||
extends = me25ls01
|
||||
build_flags = ${me25ls01.build_flags}
|
||||
-D MAX_CONTACTS=100
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D BLE_PIN_CODE=123456
|
||||
; -D BLE_DEBUG_LOGGING=1
|
||||
-D MESH_PACKET_LOGGING=1
|
||||
-D MESH_DEBUG=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
-D RX_BOOSTED_GAIN=true
|
||||
-D RF_SWITCH_TABLE
|
||||
-D ADVERT_NAME='"ME25LS01 Chat"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D ROOM_PASSWORD='"hello"'
|
||||
-D MAX_NEIGHBOURS=8
|
||||
-D DISPLAY_CLASS=NullDisplayDriver
|
||||
build_src_filter = ${me25ls01.build_src_filter}
|
||||
+<../examples/simple_secure_chat/main.cpp>
|
||||
lib_deps = ${me25ls01.lib_deps}
|
||||
adafruit/RTClib @ ^2.1.3
|
||||
|
||||
[env:Minewsemi_me25ls01_companion_radio_usb]
|
||||
extends = me25ls01
|
||||
build_flags = ${me25ls01.build_flags}
|
||||
-D MAX_CONTACTS=100
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
;-D BLE_PIN_CODE=123456
|
||||
; -D BLE_DEBUG_LOGGING=1
|
||||
-D MESH_PACKET_LOGGING=1
|
||||
-D MESH_DEBUG=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
-D RX_BOOSTED_GAIN=true
|
||||
-D RF_SWITCH_TABLE
|
||||
-D DISPLAY_CLASS=NullDisplayDriver
|
||||
build_src_filter = ${me25ls01.build_src_filter}
|
||||
+<helpers/nrf52/*.cpp>
|
||||
+<../examples/companion_radio>
|
||||
lib_deps = ${me25ls01.lib_deps}
|
||||
adafruit/RTClib @ ^2.1.3
|
||||
|
||||
98
variants/minewsemi_me25ls01/target.cpp
Normal file
98
variants/minewsemi_me25ls01/target.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
|
||||
MinewsemiME25LS01Board board;
|
||||
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
VolatileRTCClock rtc_clock;
|
||||
extern EnvironmentSensorManager sensors;
|
||||
#if ENV_INCLUDE_GPS
|
||||
#include <helpers/sensors/MicroNMEALocationProvider.h>
|
||||
MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock);
|
||||
EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea);
|
||||
#else
|
||||
EnvironmentSensorManager sensors;
|
||||
#endif
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
NullDisplayDriver display;
|
||||
#endif
|
||||
|
||||
#ifndef LORA_CR
|
||||
#define LORA_CR 5
|
||||
#endif
|
||||
|
||||
#ifdef RF_SWITCH_TABLE
|
||||
static const uint32_t rfswitch_dios[Module::RFSWITCH_MAX_PINS] = {
|
||||
RADIOLIB_LR11X0_DIO5,
|
||||
RADIOLIB_LR11X0_DIO6,
|
||||
RADIOLIB_LR11X0_DIO7,
|
||||
RADIOLIB_LR11X0_DIO8,
|
||||
RADIOLIB_NC
|
||||
};
|
||||
|
||||
static const Module::RfSwitchMode_t rfswitch_table[] = {
|
||||
// mode DIO5 DIO6 DIO7 DIO8
|
||||
{ LR11x0::MODE_STBY, {LOW, LOW, LOW, LOW }},
|
||||
{ LR11x0::MODE_RX, {HIGH, LOW, LOW, HIGH }},
|
||||
{ LR11x0::MODE_TX, {HIGH, HIGH, LOW, HIGH }},
|
||||
{ LR11x0::MODE_TX_HP, {LOW, HIGH, LOW, HIGH }},
|
||||
{ LR11x0::MODE_TX_HF, {LOW, LOW, LOW, LOW }},
|
||||
{ LR11x0::MODE_GNSS, {LOW, LOW, HIGH, LOW }},
|
||||
{ LR11x0::MODE_WIFI, {LOW, LOW, LOW, LOW }},
|
||||
END_OF_MODE_TABLE,
|
||||
};
|
||||
#endif
|
||||
|
||||
bool radio_init() {
|
||||
//rtc_clock.begin(Wire);
|
||||
|
||||
#ifdef LR11X0_DIO3_TCXO_VOLTAGE
|
||||
float tcxo = LR11X0_DIO3_TCXO_VOLTAGE;
|
||||
#else
|
||||
float tcxo = 1.6f;
|
||||
#endif
|
||||
|
||||
SPI.setPins(P_LORA_MISO, P_LORA_SCLK, P_LORA_MOSI);
|
||||
SPI.begin();
|
||||
int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_LR11X0_LORA_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo);
|
||||
if (status != RADIOLIB_ERR_NONE) {
|
||||
Serial.print("ERROR: radio init failed: ");
|
||||
Serial.println(status);
|
||||
return false; // fail
|
||||
}
|
||||
|
||||
radio.setCRC(1);
|
||||
|
||||
#ifdef RF_SWITCH_TABLE
|
||||
radio.setRfSwitchTable(rfswitch_dios, rfswitch_table);
|
||||
#endif
|
||||
#ifdef RX_BOOSTED_GAIN
|
||||
radio.setRxBoostedGainMode(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
|
||||
}
|
||||
29
variants/minewsemi_me25ls01/target.h
Normal file
29
variants/minewsemi_me25ls01/target.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#define RADIOLIB_STATIC_ONLY 1
|
||||
#include <RadioLib.h>
|
||||
#include <helpers/RadioLibWrappers.h>
|
||||
#include <MinewsemiME25LS01Board.h>
|
||||
#include <helpers/CustomLR1110Wrapper.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
#include <helpers/SensorManager.h>
|
||||
#include <helpers/sensors/LocationProvider.h>
|
||||
#include <helpers/sensors/EnvironmentSensorManager.h>
|
||||
#ifdef DISPLAY_CLASS
|
||||
#include "NullDisplayDriver.h"
|
||||
#endif
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
extern NullDisplayDriver display;
|
||||
#endif
|
||||
|
||||
extern MinewsemiME25LS01Board board;
|
||||
extern WRAPPER_CLASS radio_driver;
|
||||
extern VolatileRTCClock rtc_clock;
|
||||
extern EnvironmentSensorManager sensors;
|
||||
|
||||
bool radio_init();
|
||||
uint32_t radio_get_rng_seed();
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr);
|
||||
void radio_set_tx_power(uint8_t dbm);
|
||||
mesh::LocalIdentity radio_new_identity();
|
||||
70
variants/minewsemi_me25ls01/variant.cpp
Normal file
70
variants/minewsemi_me25ls01/variant.cpp
Normal file
@@ -0,0 +1,70 @@
|
||||
#include "variant.h"
|
||||
#include "wiring_constants.h"
|
||||
#include "wiring_digital.h"
|
||||
|
||||
const uint32_t g_ADigitalPinMap[PINS_COUNT + 1] =
|
||||
{
|
||||
0, // P0.00
|
||||
1, // P0.01
|
||||
2, // P0.02
|
||||
3, // P0.03
|
||||
4, // P0.04
|
||||
5, // P0.05
|
||||
6, // P0.06
|
||||
7, // P0.07
|
||||
8, // P0.08
|
||||
9, // P0.09
|
||||
10, // P0.10
|
||||
11, // P0.11
|
||||
12, // P0.12
|
||||
13, // P0.13, PIN_SERIAL1_TX
|
||||
14, // P0.14, PIN_SERIAL1_RX
|
||||
15, // P0.15, PIN_SERIAL2_RX
|
||||
16, // P0.16, PIN_WIRE_SCL
|
||||
17, // P0.17, PIN_SERIAL2_TX
|
||||
18, // P0.18
|
||||
19, // P0.19
|
||||
20, // P0.20
|
||||
21, // P0.21, PIN_WIRE_SDA
|
||||
22, // P0.22
|
||||
23, // P0.23
|
||||
24, // P0.24,
|
||||
25, // P0.25,
|
||||
26, // P0.26,
|
||||
27, // P0.27,
|
||||
28, // P0.28
|
||||
29, // P0.29,
|
||||
30, // P0.30
|
||||
31, // P0.31, BATTERY_PIN
|
||||
32, // P1.00
|
||||
33, // P1.01, LORA_DIO_1
|
||||
34, // P1.02
|
||||
35, // P1.03,
|
||||
36, // P1.04
|
||||
37, // P1.05, LR1110_EN
|
||||
38, // P1.06,
|
||||
39, // P1.07,
|
||||
40, // P1.08, PIN_SPI_MISO
|
||||
41, // P1.09, PIN_SPI_MOSI
|
||||
42, // P1.10, LORA_RESET
|
||||
43, // P1.11, GPS_EN
|
||||
44, // P1.12, GPS_SLEEP_INT
|
||||
45, // P1.13
|
||||
46, // P1.14, GPS_RESETB
|
||||
47, // P1.15, PIN_GPS_RESET
|
||||
255, // NRFX_SPIM_PIN_NOT_USED
|
||||
};
|
||||
|
||||
void initVariant()
|
||||
{
|
||||
pinMode(BATTERY_PIN, INPUT);
|
||||
pinMode(PIN_BUTTON1, INPUT);
|
||||
|
||||
// pinMode(PIN_3V3_EN, OUTPUT);
|
||||
// pinMode(PIN_3V3_ACC_EN, OUTPUT);
|
||||
pinMode(LED_PIN, OUTPUT);
|
||||
pinMode(P_LORA_TX_LED, OUTPUT);
|
||||
|
||||
digitalWrite(LED_PIN, HIGH);
|
||||
digitalWrite(P_LORA_TX_LED, LOW);
|
||||
}
|
||||
94
variants/minewsemi_me25ls01/variant.h
Normal file
94
variants/minewsemi_me25ls01/variant.h
Normal file
@@ -0,0 +1,94 @@
|
||||
#pragma once
|
||||
|
||||
#include "WVariant.h"
|
||||
|
||||
// Low frequency clock source
|
||||
#define USE_LFXO // 32.768 kHz crystal oscillator
|
||||
#define VARIANT_MCK (64000000ul)
|
||||
// #define USE_LFRC // 32.768 kHz RC oscillator
|
||||
|
||||
// Power
|
||||
#define BATTERY_PIN (31)
|
||||
#define BATTERY_IMMUTABLE
|
||||
#define ADC_MULTIPLIER (2.0F)
|
||||
|
||||
#define ADC_RESOLUTION (14)
|
||||
#define BATTERY_SENSE_RES (12)
|
||||
|
||||
// Number of pins
|
||||
#define PINS_COUNT (48)
|
||||
#define NUM_DIGITAL_PINS (48)
|
||||
#define NUM_ANALOG_INPUTS (6)
|
||||
#define NUM_ANALOG_OUTPUTS (0)
|
||||
|
||||
// UART pin definition
|
||||
#define PIN_SERIAL1_RX (14) // P0.14
|
||||
#define PIN_SERIAL1_TX (13) // P0.13
|
||||
|
||||
#define PIN_SERIAL2_RX (15) // P0.15
|
||||
#define PIN_SERIAL2_TX (17) // P0.17
|
||||
|
||||
// I2C pin definition
|
||||
#define HAS_WIRE (1)
|
||||
#define WIRE_INTERFACES_COUNT (1)
|
||||
|
||||
#define PIN_WIRE_SDA (21) // P0.21
|
||||
#define PIN_WIRE_SCL (16) // P0.16
|
||||
#define I2C_NO_RESCAN
|
||||
|
||||
// SPI pin definition
|
||||
#define SPI_INTERFACES_COUNT (1)
|
||||
|
||||
#define PIN_SPI_MISO (0 + 29) // P0.29
|
||||
#define PIN_SPI_MOSI (0 + 2) // P0.2
|
||||
#define PIN_SPI_SCK (32 + 15) // P1.15
|
||||
#define PIN_SPI_NSS (32 + 13) // P1.13
|
||||
|
||||
// Builtin LEDs
|
||||
#define LED_BUILTIN (-1)
|
||||
#define LED_RED (32 + 5) // P1.5
|
||||
#define LED_BLUE (32 + 7) // P1.7
|
||||
#define LED_PIN LED_BLUE
|
||||
#define P_LORA_TX_LED LED_RED
|
||||
|
||||
#define LED_STATE_ON HIGH
|
||||
|
||||
// Builtin buttons
|
||||
|
||||
#define PIN_BUTTON1 (0 + 27) // P0.6
|
||||
#define BUTTON_PIN PIN_BUTTON1
|
||||
|
||||
// LR1110
|
||||
#define LORA_DIO_1 (32 + 12) // P1.12
|
||||
#define LORA_DIO_2 (32 + 10) // P1.10
|
||||
#define LORA_NSS (PIN_SPI_NSS) // P1.13
|
||||
#define LORA_RESET (32 + 11) // P1.11
|
||||
#define LORA_BUSY (32 + 10) // P1.10
|
||||
#define LORA_SCLK (PIN_SPI_SCK) // P1.15
|
||||
#define LORA_MISO (PIN_SPI_MISO) // P0.29
|
||||
#define LORA_MOSI (PIN_SPI_MOSI) // P0.2
|
||||
#define LORA_CS PIN_SPI_NSS // P1.13
|
||||
|
||||
#define LR11X0_DIO_AS_RF_SWITCH true
|
||||
#define LR11X0_DIO3_TCXO_VOLTAGE 1.6
|
||||
|
||||
#define LR1110_IRQ_PIN LORA_DIO_1
|
||||
#define LR1110_NRESET_PIN LORA_RESET
|
||||
#define LR1110_BUSY_PIN LORA_DIO_2
|
||||
#define LR1110_SPI_NSS_PIN LORA_CS
|
||||
#define LR1110_SPI_SCK_PIN LORA_SCLK
|
||||
#define LR1110_SPI_MOSI_PIN LORA_MOSI
|
||||
#define LR1110_SPI_MISO_PIN LORA_MISO
|
||||
|
||||
// GPS
|
||||
#define HAS_GPS 0
|
||||
#define GPS_RX_PIN PIN_SERIAL1_RX
|
||||
#define GPS_TX_PIN PIN_SERIAL1_TX
|
||||
|
||||
#define GPS_EN (-1) // P1.11
|
||||
#define GPS_RESET (-1) // P1.15
|
||||
|
||||
#define GPS_VRTC_EN (-1) // P0.8
|
||||
#define GPS_SLEEP_INT (-1) // P1.12
|
||||
#define GPS_RTC_INT (-1) // P0.15
|
||||
#define GPS_RESETB (-1) // P1.14
|
||||
@@ -23,7 +23,6 @@ build_flags = ${nrf52_base.build_flags}
|
||||
-D ENV_INCLUDE_INA3221=1
|
||||
-D ENV_INCLUDE_INA219=1
|
||||
build_src_filter = ${nrf52_base.build_src_filter}
|
||||
+<helpers/nrf52/PromicroBoard.cpp>
|
||||
+<helpers/sensors>
|
||||
+<../variants/promicro>
|
||||
lib_deps= ${nrf52_base.lib_deps}
|
||||
@@ -130,7 +129,6 @@ build_flags = ${nrf52_base.build_flags}
|
||||
-D SX126X_RX_BOOSTED_GAIN=1
|
||||
build_src_filter =
|
||||
${nrf52_base.build_src_filter}
|
||||
+<helpers/nrf52/PromicroBoard.cpp>
|
||||
+<helpers/sensors>
|
||||
+<../variants/promicro>
|
||||
lib_deps= ${nrf52_base.lib_deps}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#define RADIOLIB_STATIC_ONLY 1
|
||||
#include <RadioLib.h>
|
||||
#include <helpers/RadioLibWrappers.h>
|
||||
#include <helpers/nrf52/PromicroBoard.h>
|
||||
#include <PromicroBoard.h>
|
||||
#include <helpers/CustomSX1262Wrapper.h>
|
||||
#include <helpers/CustomLLCC68Wrapper.h>
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
|
||||
@@ -38,7 +38,7 @@ bool radio_init() {
|
||||
|
||||
radio.setRfSwitchTable(rfswitch_pins, rfswitch_table);
|
||||
|
||||
int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 8, STM32WL_TCXO_VOLTAGE, 0);
|
||||
int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, STM32WL_TCXO_VOLTAGE, 0);
|
||||
|
||||
if (status != RADIOLIB_ERR_NONE) {
|
||||
Serial.print("ERROR: radio init failed: ");
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
[rak4631]
|
||||
extends = nrf52840_base
|
||||
extends = nrf52_base
|
||||
platform = https://github.com/maxgerhardt/platform-nordicnrf52.git#rak
|
||||
board = wiscore_rak4631
|
||||
board_check = true
|
||||
build_flags = ${nrf52840_base.build_flags}
|
||||
build_flags = ${nrf52_base.build_flags}
|
||||
-I variants/rak4631
|
||||
-D RAK_4631
|
||||
-D PIN_USER_BTN=9
|
||||
-D PIN_USER_BTN_ANA=31
|
||||
-D PIN_BOARD_SCL=14
|
||||
-D PIN_BOARD_SDA=13
|
||||
-D PIN_OLED_RESET=-1
|
||||
@@ -16,11 +14,10 @@ build_flags = ${nrf52840_base.build_flags}
|
||||
-D LORA_TX_POWER=22
|
||||
-D SX126X_CURRENT_LIMIT=140
|
||||
-D SX126X_RX_BOOSTED_GAIN=1
|
||||
build_src_filter = ${nrf52840_base.build_src_filter}
|
||||
+<helpers/nrf52/RAK4631Board.cpp>
|
||||
build_src_filter = ${nrf52_base.build_src_filter}
|
||||
+<../variants/rak4631>
|
||||
lib_deps =
|
||||
${nrf52840_base.lib_deps}
|
||||
${nrf52_base.lib_deps}
|
||||
adafruit/Adafruit SSD1306 @ ^2.5.13
|
||||
stevemarple/MicroNMEA @ ^2.0.6
|
||||
|
||||
@@ -58,7 +55,7 @@ build_flags =
|
||||
build_src_filter = ${rak4631.build_src_filter}
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<../examples/simple_repeater>
|
||||
lib_deps =
|
||||
lib_deps =
|
||||
${rak4631.lib_deps}
|
||||
sparkfun/SparkFun u-blox GNSS Arduino Library @ ^2.2.27
|
||||
https://github.com/boschsensortec/Bosch-BSEC2-Library
|
||||
@@ -84,6 +81,8 @@ build_src_filter = ${rak4631.build_src_filter}
|
||||
extends = rak4631
|
||||
build_flags =
|
||||
${rak4631.build_flags}
|
||||
-D PIN_USER_BTN=9
|
||||
-D PIN_USER_BTN_ANA=31
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D MAX_CONTACTS=100
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
@@ -100,6 +99,8 @@ lib_deps =
|
||||
extends = rak4631
|
||||
build_flags =
|
||||
${rak4631.build_flags}
|
||||
-D PIN_USER_BTN=9
|
||||
-D PIN_USER_BTN_ANA=31
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D MAX_CONTACTS=100
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
@@ -120,6 +121,8 @@ lib_deps =
|
||||
extends = rak4631
|
||||
build_flags =
|
||||
${rak4631.build_flags}
|
||||
-D PIN_USER_BTN=9
|
||||
-D PIN_USER_BTN_ANA=31
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D MAX_CONTACTS=100
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
@@ -145,6 +148,8 @@ lib_deps =
|
||||
extends = rak4631
|
||||
build_flags =
|
||||
${rak4631.build_flags}
|
||||
-D PIN_USER_BTN=9
|
||||
-D PIN_USER_BTN_ANA=31
|
||||
-D MAX_CONTACTS=100
|
||||
-D MAX_GROUP_CHANNELS=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#define RADIOLIB_STATIC_ONLY 1
|
||||
#include <RadioLib.h>
|
||||
#include <helpers/RadioLibWrappers.h>
|
||||
#include <helpers/nrf52/RAK4631Board.h>
|
||||
#include <RAK4631Board.h>
|
||||
#include <helpers/CustomSX1262Wrapper.h>
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
#include <helpers/SensorManager.h>
|
||||
|
||||
81
variants/sensecap_solar/SenseCapSolarBoard.cpp
Normal file
81
variants/sensecap_solar/SenseCapSolarBoard.cpp
Normal file
@@ -0,0 +1,81 @@
|
||||
#include <Arduino.h>
|
||||
#include "SenseCapSolarBoard.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 SenseCapSolarBoard::begin() {
|
||||
// for future use, sub-classes SHOULD call this from their begin()
|
||||
startup_reason = BD_STARTUP_NORMAL;
|
||||
|
||||
#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, LOW);
|
||||
#endif
|
||||
|
||||
delay(10); // give sx1262 some time to power up
|
||||
}
|
||||
|
||||
bool SenseCapSolarBoard::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("SENSECAP_SOLAR_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;
|
||||
}
|
||||
42
variants/sensecap_solar/SenseCapSolarBoard.h
Normal file
42
variants/sensecap_solar/SenseCapSolarBoard.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <MeshCore.h>
|
||||
#include <Arduino.h>
|
||||
|
||||
class SenseCapSolarBoard : 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, HIGH); // turn TX LED on
|
||||
}
|
||||
void onAfterTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off
|
||||
}
|
||||
#endif
|
||||
|
||||
uint16_t getBattMilliVolts() override {
|
||||
digitalWrite(VBAT_ENABLE, LOW);
|
||||
int adcvalue = 0;
|
||||
analogReadResolution(12);
|
||||
analogReference(AR_INTERNAL_3_0);
|
||||
delay(10);
|
||||
adcvalue = analogRead(BATTERY_PIN);
|
||||
return (adcvalue * ADC_MULTIPLIER * AREF_VOLTAGE) / 4.096;
|
||||
}
|
||||
|
||||
const char* getManufacturerName() const override {
|
||||
return "Seeed SenseCap Solar";
|
||||
}
|
||||
|
||||
void reboot() override {
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
|
||||
bool startOTAUpdate(const char* id, char reply[]) override;
|
||||
};
|
||||
75
variants/sensecap_solar/platformio.ini
Normal file
75
variants/sensecap_solar/platformio.ini
Normal file
@@ -0,0 +1,75 @@
|
||||
[SenseCap_Solar]
|
||||
extends = nrf52_base
|
||||
board = seeed_sensecap_solar
|
||||
board_build.ldscript = boards/nrf52840_s140_v7.ld
|
||||
build_flags = ${nrf52_base.build_flags}
|
||||
-I lib/nrf52/s140_nrf52_7.3.0_API/include
|
||||
-I lib/nrf52/s140_nrf52_7.3.0_API/include/nrf52
|
||||
-I variants/sensecap_solar
|
||||
-I src/helpers/nrf52
|
||||
-D NRF52_PLATFORM=1
|
||||
-D RADIO_CLASS=CustomSX1262
|
||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||
-D P_LORA_TX_LED=12
|
||||
-D P_LORA_DIO_1=1
|
||||
-D P_LORA_RESET=2
|
||||
-D P_LORA_BUSY=3
|
||||
-D P_LORA_NSS=4
|
||||
-D LORA_TX_POWER=22
|
||||
-D SX126X_RXEN=5
|
||||
-D SX126X_TXEN=RADIOLIB_NC
|
||||
-D SX126X_DIO2_AS_RF_SWITCH=1
|
||||
-D SX126X_DIO3_TCXO_VOLTAGE=1.8
|
||||
-D SX126X_CURRENT_LIMIT=140
|
||||
-D SX126X_RX_BOOSTED_GAIN=1
|
||||
-D ENV_INCLUDE_AHTX0=1
|
||||
-D ENV_INCLUDE_BME280=1
|
||||
-D ENV_INCLUDE_BMP280=1
|
||||
-D ENV_INCLUDE_SHTC3=1
|
||||
-D ENV_INCLUDE_LPS22HB=1
|
||||
-D ENV_INCLUDE_INA3221=1
|
||||
-D ENV_INCLUDE_INA219=1
|
||||
build_src_filter = ${nrf52_base.build_src_filter}
|
||||
+<helpers/*.cpp>
|
||||
+<helpers/sensors>
|
||||
+<helpers/nrf52/SenseCapSolarBoard.cpp>
|
||||
+<../variants/SenseCap_Solar>
|
||||
debug_tool = jlink
|
||||
upload_protocol = nrfutil
|
||||
lib_deps =
|
||||
${nrf52_base.lib_deps}
|
||||
rweather/Crypto @ ^0.4.0
|
||||
adafruit/Adafruit INA3221 Library @ ^1.0.1
|
||||
adafruit/Adafruit INA219 @ ^1.2.3
|
||||
adafruit/Adafruit AHTX0 @ ^2.0.5
|
||||
adafruit/Adafruit BME280 Library @ ^2.3.0
|
||||
adafruit/Adafruit BMP280 Library @ ^2.6.8
|
||||
adafruit/Adafruit SHTC3 Library @ ^1.0.1
|
||||
arduino-libraries/Arduino_LPS22HB @ ^1.0.2
|
||||
|
||||
[env:SenseCap_Solar_repeater]
|
||||
extends = SenseCap_Solar
|
||||
build_flags =
|
||||
${SenseCap_Solar.build_flags}
|
||||
-D ADVERT_NAME='"SenseCap_Solar 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 = ${SenseCap_Solar.build_src_filter}
|
||||
+<../examples/simple_repeater/main.cpp>
|
||||
|
||||
[env:SenseCap_Solar_room_server]
|
||||
extends = SenseCap_Solar
|
||||
build_flags =
|
||||
${SenseCap_Solar.build_flags}
|
||||
-D ADVERT_NAME='"SenseCap_Solar Room"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${SenseCap_Solar.build_src_filter}
|
||||
+<../examples/simple_room_server/main.cpp>
|
||||
39
variants/sensecap_solar/target.cpp
Normal file
39
variants/sensecap_solar/target.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
|
||||
SenseCapSolarBoard board;
|
||||
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
VolatileRTCClock fallback_clock;
|
||||
AutoDiscoverRTCClock rtc_clock(fallback_clock);
|
||||
EnvironmentSensorManager sensors;
|
||||
|
||||
bool radio_init() {
|
||||
rtc_clock.begin(Wire);
|
||||
|
||||
return radio.std_init(&SPI);
|
||||
}
|
||||
|
||||
uint32_t radio_get_rng_seed() {
|
||||
return radio.random(0x7FFFFFFF);
|
||||
}
|
||||
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) {
|
||||
radio.setFrequency(freq);
|
||||
radio.setSpreadingFactor(sf);
|
||||
radio.setBandwidth(bw);
|
||||
radio.setCodingRate(cr);
|
||||
}
|
||||
|
||||
void radio_set_tx_power(uint8_t dbm) {
|
||||
radio.setOutputPower(dbm);
|
||||
}
|
||||
|
||||
mesh::LocalIdentity radio_new_identity() {
|
||||
RadioNoiseListener rng(radio);
|
||||
return mesh::LocalIdentity(&rng); // create new random identity
|
||||
}
|
||||
21
variants/sensecap_solar/target.h
Normal file
21
variants/sensecap_solar/target.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#define RADIOLIB_STATIC_ONLY 1
|
||||
#include <RadioLib.h>
|
||||
#include <helpers/RadioLibWrappers.h>
|
||||
#include <SenseCapSolarBoard.h>
|
||||
#include <helpers/CustomSX1262Wrapper.h>
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
#include <helpers/sensors/EnvironmentSensorManager.h>
|
||||
|
||||
extern SenseCapSolarBoard board;
|
||||
extern WRAPPER_CLASS radio_driver;
|
||||
extern AutoDiscoverRTCClock rtc_clock;
|
||||
extern EnvironmentSensorManager sensors;
|
||||
|
||||
bool radio_init();
|
||||
uint32_t radio_get_rng_seed();
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr);
|
||||
void radio_set_tx_power(uint8_t dbm);
|
||||
mesh::LocalIdentity radio_new_identity();
|
||||
71
variants/sensecap_solar/variant.cpp
Normal file
71
variants/sensecap_solar/variant.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
#include "variant.h"
|
||||
#include "wiring_constants.h"
|
||||
#include "wiring_digital.h"
|
||||
#include "nrf.h"
|
||||
|
||||
const uint32_t g_ADigitalPinMap[] = {
|
||||
// D0 .. D10 - Peripheral control pins
|
||||
2, // D0 P0.02 (A0) GNSS_WAKEUP
|
||||
3, // D1 P0.03 (A1) LORA_DIO1
|
||||
28, // D2 P0.28 (A2) LORA_RESET
|
||||
29, // D3 P0.29 (A3) LORA_BUSY
|
||||
4, // D4 P0.04 (A4/SDA) LORA_CS
|
||||
5, // D5 P0.05 (A5/SCL) LORA_SW
|
||||
43, // D6 P1.11 (UART_TX) GNSS_TX
|
||||
44, // D7 P1.12 (UART_RX) GNSS_RX
|
||||
45, // D8 P1.13 (SPI_SCK) LORA_SCK
|
||||
46, // D9 P1.14 (SPI_MISO) LORA_MISO
|
||||
47, // D10 P1.15 (SPI_MOSI) LORA_MOSI
|
||||
|
||||
// D11-D12 - LED outputs
|
||||
15, // D11 P0.15 User LED
|
||||
19, // D12 P0.19 Breathing LED
|
||||
|
||||
// D13 - User input
|
||||
33, // D13 P1.01 User Button
|
||||
|
||||
// D14-D15 - Grove/NFC interface
|
||||
9, // D14 P0.09 NFC1/GROVE_D1
|
||||
10, // D15 P0.10 NFC2/GROVE_D0
|
||||
|
||||
// D16 - Power management
|
||||
// 31, // D16 P0.31 VBAT_ADC (Battery voltage)
|
||||
31, // D16 P0.31 VBAT_ADC (Battery voltage)
|
||||
// D17 - GNSS control
|
||||
35, // D17 P1.03 GNSS_RESET
|
||||
|
||||
37, // D18 P1.05 GNSS_ENABLE
|
||||
14, // D19 P0.14 BAT_READ
|
||||
39, // D20 P1.07 USER_BUTTON
|
||||
|
||||
//
|
||||
21, // D21 P0.21 (QSPI_SCK)
|
||||
25, // D22 P0.25 (QSPI_CSN)
|
||||
20, // D23 P0.20 (QSPI_SIO_0 DI)
|
||||
24, // D24 P0.24 (QSPI_SIO_1 DO)
|
||||
22, // D25 P0.22 (QSPI_SIO_2 WP)
|
||||
23, // D26 P0.23 (QSPI_SIO_3 HOLD)
|
||||
};
|
||||
|
||||
void initVariant() {
|
||||
pinMode(GPS_EN, OUTPUT);
|
||||
digitalWrite(GPS_EN, LOW);
|
||||
|
||||
pinMode(BATTERY_PIN, INPUT);
|
||||
pinMode(VBAT_ENABLE, OUTPUT);
|
||||
digitalWrite(VBAT_ENABLE, LOW);
|
||||
|
||||
pinMode(PIN_QSPI_CS, OUTPUT);
|
||||
digitalWrite(PIN_QSPI_CS, HIGH);
|
||||
|
||||
pinMode(LED_GREEN, OUTPUT);
|
||||
digitalWrite(LED_GREEN, LOW);
|
||||
|
||||
pinMode(LED_BLUE, OUTPUT);
|
||||
digitalWrite(LED_BLUE, LOW);
|
||||
|
||||
/* disable gps until we actually support it.
|
||||
pinMode(GPS_EN, OUTPUT);
|
||||
digitalWrite(GPS_EN, HIGH);
|
||||
*/
|
||||
}
|
||||
85
variants/sensecap_solar/variant.h
Normal file
85
variants/sensecap_solar/variant.h
Normal file
@@ -0,0 +1,85 @@
|
||||
#ifndef _SEEED_SENSECAP_SOLAR_H_
|
||||
#define _SEEED_SENSECAP_SOLAR_H_
|
||||
|
||||
/** Master clock frequency */
|
||||
#define VARIANT_MCK (64000000ul)
|
||||
|
||||
#define USE_LFXO // Board uses 32khz crystal for LF
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
* Headers
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "WVariant.h"
|
||||
|
||||
#define PINS_COUNT (33)
|
||||
#define NUM_DIGITAL_PINS (33)
|
||||
#define NUM_ANALOG_INPUTS (8)
|
||||
#define NUM_ANALOG_OUTPUTS (0)
|
||||
|
||||
// LEDs
|
||||
#define PIN_LED (12)
|
||||
#define LED_PWR (PINS_COUNT)
|
||||
|
||||
#define LED_BUILTIN (PIN_LED)
|
||||
|
||||
#define LED_RED (PINS_COUNT)
|
||||
#define LED_GREEN (12)
|
||||
#define LED_BLUE (11)
|
||||
|
||||
#define LED_STATE_ON (1) // State when LED is litted
|
||||
|
||||
// Buttons
|
||||
#define PIN_BUTTON1 (13)
|
||||
#define PIN_BUTTON2 (20)
|
||||
|
||||
#define VBAT_ENABLE (19) // Output LOW to enable reading of the BAT voltage.
|
||||
|
||||
// Analog pins
|
||||
#define BATTERY_PIN (16) // Read the BAT voltage.
|
||||
#define AREF_VOLTAGE (3.0F)
|
||||
#define ADC_MULTIPLIER (3.0F) // 1M, 512k divider bridge
|
||||
#define ADC_RESOLUTION (12)
|
||||
|
||||
// Serial interfaces
|
||||
#define PIN_SERIAL1_RX (7)
|
||||
#define PIN_SERIAL1_TX (6)
|
||||
|
||||
// SPI Interfaces
|
||||
#define SPI_INTERFACES_COUNT (1)
|
||||
|
||||
#define PIN_SPI_MISO (9)
|
||||
#define PIN_SPI_MOSI (10)
|
||||
#define PIN_SPI_SCK (8)
|
||||
|
||||
// Lora SPI is on SPI0
|
||||
#define P_LORA_SCLK PIN_SPI_SCK
|
||||
#define P_LORA_MISO PIN_SPI_MISO
|
||||
#define P_LORA_MOSI PIN_SPI_MOSI
|
||||
|
||||
// Wire Interfaces
|
||||
#define WIRE_INTERFACES_COUNT (1)
|
||||
|
||||
#define PIN_WIRE_SDA (14)
|
||||
#define PIN_WIRE_SCL (15)
|
||||
|
||||
// GPS L76KB
|
||||
#define GPS_BAUDRATE 9600
|
||||
#define GPS_THREAD_INTERVAL 50
|
||||
#define PIN_GPS_TX PIN_SERIAL1_RX
|
||||
#define PIN_GPS_RX PIN_SERIAL1_TX
|
||||
#define PIN_GPS_STANDBY (0)
|
||||
#define GPS_EN (18)
|
||||
|
||||
// QSPI Pins
|
||||
#define PIN_QSPI_SCK (21)
|
||||
#define PIN_QSPI_CS (22)
|
||||
#define PIN_QSPI_IO0 (23)
|
||||
#define PIN_QSPI_IO1 (24)
|
||||
#define PIN_QSPI_IO2 (25)
|
||||
#define PIN_QSPI_IO3 (26)
|
||||
|
||||
#define EXTERNAL_FLASH_DEVICES P25Q16H
|
||||
#define EXTERNAL_FLASH_USE_QSPI
|
||||
|
||||
#endif
|
||||
@@ -61,7 +61,8 @@ bool radio_init() {
|
||||
return false; // fail
|
||||
}
|
||||
|
||||
radio.setCRC(1);
|
||||
radio.setCRC(2);
|
||||
radio.explicitHeader();
|
||||
|
||||
#ifdef RF_SWITCH_TABLE
|
||||
radio.setRfSwitchTable(rfswitch_dios, rfswitch_table);
|
||||
|
||||
@@ -35,7 +35,7 @@ bool radio_init() {
|
||||
|
||||
radio.setRfSwitchTable(rfswitch_pins, rfswitch_table);
|
||||
|
||||
int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 8, 1.7, 0);
|
||||
int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, 1.7, 0);
|
||||
|
||||
if (status != RADIOLIB_ERR_NONE) {
|
||||
Serial.print("ERROR: radio init failed: ");
|
||||
|
||||
@@ -25,6 +25,15 @@ build_flags = ${lora_e5_mini.build_flags}
|
||||
build_src_filter = ${lora_e5_mini.build_src_filter}
|
||||
+<../examples/simple_repeater/main.cpp>
|
||||
|
||||
[env:wio-e5-mini-sensor]
|
||||
extends = lora_e5_mini
|
||||
build_flags = ${lora_e5_mini.build_flags}
|
||||
-D LORA_TX_POWER=22
|
||||
-D ADVERT_NAME='"wio-e5-mini Sensor"'
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
build_src_filter = ${lora_e5_mini.build_src_filter}
|
||||
+<../examples/simple_sensor>
|
||||
|
||||
[env:wio-e5-mini_companion_radio_usb]
|
||||
extends = lora_e5_mini
|
||||
build_flags = ${lora_e5_mini.build_flags}
|
||||
|
||||
@@ -33,7 +33,7 @@ bool radio_init() {
|
||||
|
||||
radio.setRfSwitchTable(rfswitch_pins, rfswitch_table);
|
||||
|
||||
int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 8, 1.7, 0);
|
||||
int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, 1.7, 0);
|
||||
|
||||
if (status != RADIOLIB_ERR_NONE) {
|
||||
Serial.print("ERROR: radio init failed: ");
|
||||
|
||||
96
variants/wio-tracker-l1/WioTrackerL1Board.cpp
Normal file
96
variants/wio-tracker-l1/WioTrackerL1Board.cpp
Normal file
@@ -0,0 +1,96 @@
|
||||
#include <Arduino.h>
|
||||
#include "WioTrackerL1Board.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 WioTrackerL1Board::begin() {
|
||||
// for future use, sub-classes SHOULD call this from their begin()
|
||||
startup_reason = BD_STARTUP_NORMAL;
|
||||
btn_prev_state = HIGH;
|
||||
|
||||
pinMode(PIN_VBAT_READ, INPUT); // VBAT ADC input
|
||||
// Set all button pins to INPUT_PULLUP
|
||||
pinMode(PIN_BUTTON1, INPUT_PULLUP);
|
||||
pinMode(PIN_BUTTON2, INPUT_PULLUP);
|
||||
pinMode(PIN_BUTTON3, INPUT_PULLUP);
|
||||
pinMode(PIN_BUTTON4, INPUT_PULLUP);
|
||||
pinMode(PIN_BUTTON5, INPUT_PULLUP);
|
||||
pinMode(PIN_BUTTON6, INPUT_PULLUP);
|
||||
|
||||
|
||||
#if defined(PIN_WIRE_SDA) && defined(PIN_WIRE_SCL)
|
||||
Wire.setPins(PIN_WIRE_SDA, PIN_WIRE_SCL);
|
||||
#endif
|
||||
|
||||
Wire.begin();
|
||||
|
||||
#ifdef P_LORA_TX_LED
|
||||
pinMode(P_LORA_TX_LED, OUTPUT);
|
||||
digitalWrite(P_LORA_TX_LED, LOW);
|
||||
#endif
|
||||
|
||||
delay(10); // give sx1262 some time to power up
|
||||
}
|
||||
|
||||
bool WioTrackerL1Board::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("WioTrackerL1 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
|
||||
|
||||
uint8_t mac_addr[6];
|
||||
memset(mac_addr, 0, sizeof(mac_addr));
|
||||
Bluefruit.getAddr(mac_addr);
|
||||
sprintf(reply, "OK - mac: %02X:%02X:%02X:%02X:%02X:%02X",
|
||||
mac_addr[5], mac_addr[4], mac_addr[3], mac_addr[2], mac_addr[1], mac_addr[0]);
|
||||
|
||||
return true;
|
||||
}
|
||||
42
variants/wio-tracker-l1/WioTrackerL1Board.h
Normal file
42
variants/wio-tracker-l1/WioTrackerL1Board.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <MeshCore.h>
|
||||
#include <Arduino.h>
|
||||
|
||||
class WioTrackerL1Board : public mesh::MainBoard {
|
||||
protected:
|
||||
uint8_t startup_reason;
|
||||
uint8_t btn_prev_state;
|
||||
|
||||
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, HIGH); // turn TX LED on
|
||||
}
|
||||
void onAfterTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off
|
||||
}
|
||||
#endif
|
||||
|
||||
uint16_t getBattMilliVolts() override {
|
||||
int adcvalue = 0;
|
||||
analogReadResolution(12);
|
||||
analogReference(AR_INTERNAL);
|
||||
delay(10);
|
||||
adcvalue = analogRead(PIN_VBAT_READ);
|
||||
return (adcvalue * ADC_MULTIPLIER * AREF_VOLTAGE) / 4.096;
|
||||
}
|
||||
|
||||
const char* getManufacturerName() const override {
|
||||
return "Seeed Wio Tracker L1";
|
||||
}
|
||||
|
||||
void reboot() override {
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
|
||||
bool startOTAUpdate(const char* id, char reply[]) override;
|
||||
};
|
||||
92
variants/wio-tracker-l1/platformio.ini
Normal file
92
variants/wio-tracker-l1/platformio.ini
Normal file
@@ -0,0 +1,92 @@
|
||||
[WioTrackerL1]
|
||||
extends = nrf52_base
|
||||
board = seeed-wio-tracker-l1
|
||||
board_build.ldscript = boards/nrf52840_s140_v7.ld
|
||||
build_flags = ${nrf52_base.build_flags}
|
||||
-I lib/nrf52/s140_nrf52_7.3.0_API/include
|
||||
-I lib/nrf52/s140_nrf52_7.3.0_API/include/nrf52
|
||||
-I variants/wio-tracker-l1
|
||||
-D WIO_TRACKER_L1
|
||||
-D RADIO_CLASS=CustomSX1262
|
||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||
-D LORA_TX_POWER=22
|
||||
-D SX126X_CURRENT_LIMIT=140
|
||||
-D SX126X_RX_BOOSTED_GAIN=1
|
||||
-D PIN_OLED_RESET=-1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${nrf52_base.build_src_filter}
|
||||
+<WioTrackerL1Board.cpp>
|
||||
+<../variants/wio-tracker-l1>
|
||||
+<helpers/ui/SH1106Display.cpp>
|
||||
+<helpers/sensors>
|
||||
lib_deps= ${nrf52_base.lib_deps}
|
||||
adafruit/Adafruit SH110X @ ^2.1.13
|
||||
adafruit/Adafruit GFX Library @ ^1.12.1
|
||||
stevemarple/MicroNMEA @ ^2.0.6
|
||||
|
||||
[env:WioTrackerL1_Repeater]
|
||||
extends = WioTrackerL1
|
||||
build_src_filter = ${WioTrackerL1.build_src_filter}
|
||||
+<../examples/simple_repeater>
|
||||
build_flags =
|
||||
${WioTrackerL1.build_flags}
|
||||
-D ADVERT_NAME='"WioTrackerL1 Repeater"'
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=8
|
||||
-D DISPLAY_CLASS=SH1106Display
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
lib_deps = ${WioTrackerL1.lib_deps}
|
||||
adafruit/RTClib @ ^2.1.3
|
||||
|
||||
[env:WioTrackerL1_room_server]
|
||||
extends = WioTrackerL1
|
||||
build_src_filter = ${WioTrackerL1.build_src_filter}
|
||||
+<../examples/simple_room_server>
|
||||
build_flags = ${WioTrackerL1.build_flags}
|
||||
-D ADVERT_NAME='"WioTrackerL1 Room"'
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D ROOM_PASSWORD='"hello"'
|
||||
-D DISPLAY_CLASS=SH1106Display
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
lib_deps = ${WioTrackerL1.lib_deps}
|
||||
adafruit/RTClib @ ^2.1.3
|
||||
|
||||
[env:WioTrackerL1_companion_radio_usb]
|
||||
extends = WioTrackerL1
|
||||
build_flags = ${WioTrackerL1.build_flags}
|
||||
-D MAX_CONTACTS=100
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D DISPLAY_CLASS=SH1106Display
|
||||
; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1
|
||||
; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1
|
||||
build_src_filter = ${WioTrackerL1.build_src_filter}
|
||||
+<../examples/companion_radio>
|
||||
+<helpers/ui/SH1106Display.cpp>
|
||||
+<helpers/ui/buzzer.cpp>
|
||||
lib_deps = ${WioTrackerL1.lib_deps}
|
||||
adafruit/RTClib @ ^2.1.3
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
end2endzone/NonBlockingRTTTL@^1.3.0
|
||||
|
||||
[env:WioTrackerL1_companion_radio_ble]
|
||||
extends = WioTrackerL1
|
||||
build_flags = ${WioTrackerL1.build_flags}
|
||||
-D MAX_CONTACTS=100
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D BLE_PIN_CODE=123456
|
||||
-D BLE_DEBUG_LOGGING=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
-D DISPLAY_CLASS=SH1106Display
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
-D MESH_DEBUG=1
|
||||
-D PIN_BUZZER=12
|
||||
build_src_filter = ${WioTrackerL1.build_src_filter}
|
||||
+<helpers/nrf52/SerialBLEInterface.cpp>
|
||||
+<../examples/companion_radio>
|
||||
+<helpers/ui/buzzer.cpp>
|
||||
lib_deps = ${WioTrackerL1.lib_deps}
|
||||
adafruit/RTClib @ ^2.1.3
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
end2endzone/NonBlockingRTTTL@^1.3.0
|
||||
146
variants/wio-tracker-l1/target.cpp
Normal file
146
variants/wio-tracker-l1/target.cpp
Normal file
@@ -0,0 +1,146 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
#include <helpers/sensors/MicroNMEALocationProvider.h>
|
||||
|
||||
WioTrackerL1Board board;
|
||||
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
VolatileRTCClock fallback_clock;
|
||||
AutoDiscoverRTCClock rtc_clock(fallback_clock);
|
||||
MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1);
|
||||
WioTrackerL1SensorManager sensors = WioTrackerL1SensorManager(nmea);
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
DISPLAY_CLASS display;
|
||||
#endif
|
||||
|
||||
bool radio_init() {
|
||||
rtc_clock.begin(Wire);
|
||||
|
||||
return radio.std_init(&SPI);
|
||||
}
|
||||
|
||||
uint32_t radio_get_rng_seed() {
|
||||
return radio.random(0x7FFFFFFF);
|
||||
}
|
||||
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) {
|
||||
radio.setFrequency(freq);
|
||||
radio.setSpreadingFactor(sf);
|
||||
radio.setBandwidth(bw);
|
||||
radio.setCodingRate(cr);
|
||||
}
|
||||
|
||||
void radio_set_tx_power(uint8_t dbm) {
|
||||
radio.setOutputPower(dbm);
|
||||
}
|
||||
|
||||
void WioTrackerL1SensorManager::start_gps()
|
||||
{
|
||||
if (!gps_active)
|
||||
{
|
||||
MESH_DEBUG_PRINTLN("starting GPS");
|
||||
digitalWrite(PIN_GPS_STANDBY, HIGH);
|
||||
gps_active = true;
|
||||
}
|
||||
}
|
||||
|
||||
void WioTrackerL1SensorManager::stop_gps()
|
||||
{
|
||||
if (gps_active)
|
||||
{
|
||||
MESH_DEBUG_PRINTLN("stopping GPS");
|
||||
digitalWrite(PIN_GPS_STANDBY, LOW);
|
||||
gps_active = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool WioTrackerL1SensorManager::begin()
|
||||
{
|
||||
Serial1.setPins(PIN_GPS_TX, PIN_GPS_RX); // be sure to tx into rx and rx into tx
|
||||
Serial1.begin(GPS_BAUDRATE);
|
||||
|
||||
pinMode(PIN_GPS_STANDBY, OUTPUT);
|
||||
digitalWrite(PIN_GPS_STANDBY, HIGH); // Wake GPS from standby
|
||||
delay(500);
|
||||
|
||||
// We'll consider GPS detected if we see any data on Serial1
|
||||
if (Serial1.available() > 0)
|
||||
{
|
||||
MESH_DEBUG_PRINTLN("GPS detected");
|
||||
}
|
||||
else
|
||||
{
|
||||
MESH_DEBUG_PRINTLN("No GPS detected");
|
||||
}
|
||||
digitalWrite(PIN_GPS_STANDBY, LOW); // Put GPS back into standby mode
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WioTrackerL1SensorManager::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, node_altitude);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void WioTrackerL1SensorManager::loop()
|
||||
{
|
||||
static long next_gps_update = 0;
|
||||
_location->loop();
|
||||
if (millis() > next_gps_update && gps_active) // don't bother if gps position is not enabled
|
||||
{
|
||||
if (_location->isValid())
|
||||
{
|
||||
node_lat = ((double)_location->getLatitude()) / 1000000.;
|
||||
node_lon = ((double)_location->getLongitude()) / 1000000.;
|
||||
node_altitude = ((double)_location->getAltitude()) / 1000.0;
|
||||
MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon);
|
||||
}
|
||||
next_gps_update = millis() + (1000 * 60); // after initial update, only check every minute TODO: should be configurable
|
||||
}
|
||||
}
|
||||
|
||||
int WioTrackerL1SensorManager::getNumSettings() const { return 1; } // just one supported: "gps" (power switch)
|
||||
|
||||
const char *WioTrackerL1SensorManager::getSettingName(int i) const
|
||||
{
|
||||
return i == 0 ? "gps" : NULL;
|
||||
}
|
||||
|
||||
const char *WioTrackerL1SensorManager::getSettingValue(int i) const
|
||||
{
|
||||
if (i == 0)
|
||||
{
|
||||
return gps_active ? "1" : "0";
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool WioTrackerL1SensorManager::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
|
||||
}
|
||||
|
||||
mesh::LocalIdentity radio_new_identity() {
|
||||
RadioNoiseListener rng(radio);
|
||||
return mesh::LocalIdentity(&rng); // create new random identity
|
||||
}
|
||||
47
variants/wio-tracker-l1/target.h
Normal file
47
variants/wio-tracker-l1/target.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#define RADIOLIB_STATIC_ONLY 1
|
||||
#include <RadioLib.h>
|
||||
#include <helpers/RadioLibWrappers.h>
|
||||
#include <WioTrackerL1Board.h>
|
||||
#include <helpers/CustomSX1262Wrapper.h>
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
#ifdef DISPLAY_CLASS
|
||||
#include <helpers/ui/SH1106Display.h>
|
||||
#endif
|
||||
#include <helpers/sensors/EnvironmentSensorManager.h>
|
||||
|
||||
class WioTrackerL1SensorManager : public SensorManager
|
||||
{
|
||||
bool gps_active = false;
|
||||
LocationProvider *_location;
|
||||
|
||||
void start_gps();
|
||||
void stop_gps();
|
||||
|
||||
public:
|
||||
WioTrackerL1SensorManager(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 WioTrackerL1Board board;
|
||||
extern WRAPPER_CLASS radio_driver;
|
||||
extern AutoDiscoverRTCClock rtc_clock;
|
||||
extern WioTrackerL1SensorManager sensors;
|
||||
#ifdef DISPLAY_CLASS
|
||||
extern DISPLAY_CLASS display;
|
||||
#endif
|
||||
|
||||
bool radio_init();
|
||||
uint32_t radio_get_rng_seed();
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr);
|
||||
void radio_set_tx_power(uint8_t dbm);
|
||||
mesh::LocalIdentity radio_new_identity();
|
||||
73
variants/wio-tracker-l1/variant.cpp
Normal file
73
variants/wio-tracker-l1/variant.cpp
Normal file
@@ -0,0 +1,73 @@
|
||||
#include "variant.h"
|
||||
#include "wiring_constants.h"
|
||||
#include "wiring_digital.h"
|
||||
#include "nrf.h"
|
||||
|
||||
const uint32_t g_ADigitalPinMap[] = {
|
||||
// D0 .. D10 - Peripheral control pins
|
||||
41, // D0 P1.09 GNSS_WAKEUP
|
||||
7, // D1 P0.07 LORA_DIO1
|
||||
39, // D2 P1,07 LORA_RESET
|
||||
42, // D3 P1.10 LORA_BUSY
|
||||
46, // D4 P1.14 (A4/SDA) LORA_CS
|
||||
40, // D5 P1.08 (A5/SCL) LORA_SW
|
||||
27, // D6 P0.27 (UART_TX) GNSS_TX
|
||||
26, // D7 P0.26 (UART_RX) GNSS_RX
|
||||
30, // D8 P0.30 (SPI_SCK) LORA_SCK
|
||||
3, // D9 P0.3 (SPI_MISO) LORA_MISO
|
||||
28, // D10 P0.28 (SPI_MOSI) LORA_MOSI
|
||||
|
||||
// D11-D12 - LED outputs
|
||||
33, // D11 P1.1 User LED
|
||||
// Buzzzer
|
||||
32, // D12 P1.0 Buzzer
|
||||
|
||||
// D13 - User input
|
||||
8, // D13 P0.08 User Button
|
||||
|
||||
// D14-D15 - OLED
|
||||
6, // D14 P0.06 OLED SDA
|
||||
5, // D15 P0.05 OLED SCL
|
||||
|
||||
// D16 - Battery voltage ADC input
|
||||
31, // D16 P0.31 VBAT_ADC
|
||||
// GROVE
|
||||
43, // D17 P0.00 GROVE SDA
|
||||
44, // D18 P0.01 GROVE SCL
|
||||
|
||||
// FLASH
|
||||
21, // D19 P0.21 (QSPI_SCK)
|
||||
25, // D20 P0.25 (QSPI_CSN)
|
||||
20, // D21 P0.20 (QSPI_SIO_0 DI)
|
||||
24, // D22 P0.24 (QSPI_SIO_1 DO)
|
||||
22, // D23 P0.22 (QSPI_SIO_2 WP)
|
||||
23, // D24 P0.23 (QSPI_SIO_3 HOLD)
|
||||
|
||||
// JOYSTICK
|
||||
36, // D25 TB_UP
|
||||
12, // D26 TB_DOWN
|
||||
11, // D27 TB_LEFT
|
||||
35, // D28 TB_RIGHT
|
||||
37, // D29 TB_PRESS
|
||||
|
||||
// VBAT ENABLE
|
||||
4, // D30 BAT_CTL
|
||||
};
|
||||
|
||||
void initVariant() {
|
||||
pinMode(PIN_QSPI_CS, OUTPUT);
|
||||
digitalWrite(PIN_QSPI_CS, HIGH);
|
||||
|
||||
// VBAT_ENABLE
|
||||
pinMode(VBAT_ENABLE, OUTPUT);
|
||||
digitalWrite(VBAT_ENABLE, HIGH);
|
||||
|
||||
// set LED pin as output and set it low
|
||||
pinMode(PIN_LED, OUTPUT);
|
||||
digitalWrite(PIN_LED, LOW);
|
||||
|
||||
// set buzzer pin as output and set it low
|
||||
pinMode(12, OUTPUT);
|
||||
digitalWrite(12, LOW);
|
||||
pinMode(12, OUTPUT);
|
||||
}
|
||||
102
variants/wio-tracker-l1/variant.h
Normal file
102
variants/wio-tracker-l1/variant.h
Normal file
@@ -0,0 +1,102 @@
|
||||
#ifndef _SEEED_WIO_TRACKER_L1_H_
|
||||
#define _SEEED_WIO_TRACKER_L1_H_
|
||||
|
||||
/** Master clock frequency */
|
||||
#define VARIANT_MCK (64000000ul)
|
||||
|
||||
#define USE_LFXO // Board uses 32khz crystal for LF
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
* Headers
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
#include "WVariant.h"
|
||||
|
||||
#define PINS_COUNT (33)
|
||||
#define NUM_DIGITAL_PINS (33)
|
||||
#define NUM_ANALOG_INPUTS (8)
|
||||
#define NUM_ANALOG_OUTPUTS (0)
|
||||
|
||||
// LEDs
|
||||
#define PIN_LED (11)
|
||||
#define LED_BLUE (-1) // Disable annoying flashing caused by Bluefruit
|
||||
#define LED_BUILTIN PIN_LED
|
||||
#define P_LORA_TX_LED PIN_LED
|
||||
#define LED_STATE_ON 1
|
||||
|
||||
// Buttons
|
||||
#define PIN_BUTTON1 (13) // Menu / User Button
|
||||
#define PIN_BUTTON2 (25) // Joystick Up
|
||||
#define PIN_BUTTON3 (26) // Joystick Down
|
||||
#define PIN_BUTTON4 (27) // Joystick Left
|
||||
#define PIN_BUTTON5 (28) // Joystick Right
|
||||
#define PIN_BUTTON6 (28) // Joystick Press
|
||||
#define PIN_USER_BTN PIN_BUTTON1
|
||||
#define JOYSTICK_UP PIN_BUTTON2
|
||||
#define JOYSTICK_DOWN PIN_BUTTON3
|
||||
#define JOYSTICK_LEFT PIN_BUTTON4
|
||||
#define JOYSTICK_RIGHT PIN_BUTTON5
|
||||
#define JOYSTICK_PRESS PIN_BUTTON6
|
||||
|
||||
// Buzzer
|
||||
// #define PIN_BUZZER (12) // Buzzer pin (defined per firmware type)
|
||||
|
||||
#define VBAT_ENABLE (30)
|
||||
|
||||
// Analog pins
|
||||
#define PIN_VBAT_READ (16)
|
||||
#define AREF_VOLTAGE (3.6F)
|
||||
#define ADC_MULTIPLIER (2.0F)
|
||||
#define ADC_RESOLUTION (12)
|
||||
|
||||
// Serial interfaces
|
||||
#define PIN_SERIAL1_RX (7)
|
||||
#define PIN_SERIAL1_TX (6)
|
||||
|
||||
// SPI Interfaces
|
||||
#define SPI_INTERFACES_COUNT (1)
|
||||
|
||||
#define PIN_SPI_MISO (9)
|
||||
#define PIN_SPI_MOSI (10)
|
||||
#define PIN_SPI_SCK (8)
|
||||
|
||||
// Lora Pins
|
||||
#define P_LORA_SCLK PIN_SPI_SCK
|
||||
#define P_LORA_MISO PIN_SPI_MISO
|
||||
#define P_LORA_MOSI PIN_SPI_MOSI
|
||||
#define P_LORA_DIO_1 (1)
|
||||
#define P_LORA_RESET (2)
|
||||
#define P_LORA_BUSY (3)
|
||||
#define P_LORA_NSS (4)
|
||||
#define SX126X_RXEN (5)
|
||||
#define SX126X_TXEN RADIOLIB_NC
|
||||
|
||||
// Wire Interfaces
|
||||
#define WIRE_INTERFACES_COUNT (2)
|
||||
|
||||
#define PIN_WIRE_SDA (14)
|
||||
#define PIN_WIRE_SCL (15)
|
||||
#define PIN_WIRE1_SDA (17)
|
||||
#define PIN_WIRE1_SCL (18)
|
||||
#define I2C_NO_RESCAN
|
||||
#define DISPLAY_ADDRESS 0x3D // SH1106 OLED I2C address
|
||||
|
||||
// GPS L76KB
|
||||
#define GPS_BAUDRATE 9600
|
||||
#define PIN_GPS_TX PIN_SERIAL1_RX
|
||||
#define PIN_GPS_RX PIN_SERIAL1_TX
|
||||
#define PIN_GPS_STANDBY (0)
|
||||
#define PIN_GPS_EN (18)
|
||||
|
||||
// QSPI Pins
|
||||
#define PIN_QSPI_SCK (21)
|
||||
#define PIN_QSPI_CS (22)
|
||||
#define PIN_QSPI_IO0 (23)
|
||||
#define PIN_QSPI_IO1 (24)
|
||||
#define PIN_QSPI_IO2 (25)
|
||||
#define PIN_QSPI_IO3 (26)
|
||||
|
||||
#define EXTERNAL_FLASH_DEVICES P25Q16H
|
||||
#define EXTERNAL_FLASH_USE_QSPI
|
||||
|
||||
#endif
|
||||
@@ -1,6 +1,27 @@
|
||||
[Xiao_esp32_C3]
|
||||
extends = esp32_base
|
||||
board = seeed_xiao_esp32c3
|
||||
build_flags =
|
||||
${esp32_base.build_flags}
|
||||
-I variants/xiao_c3
|
||||
-D ESP32_CPU_FREQ=80
|
||||
-D PIN_VBAT_READ=D0
|
||||
-D P_LORA_DIO_1=D1
|
||||
-D P_LORA_NSS=D4
|
||||
-D P_LORA_RESET=D2
|
||||
-D P_LORA_BUSY=D3
|
||||
-D PIN_BOARD_SDA=D6
|
||||
-D PIN_BOARD_SCL=D7
|
||||
-D SX126X_RXEN=D5
|
||||
-D SX126X_DIO2_AS_RF_SWITCH=true
|
||||
-D SX126X_DIO3_TCXO_VOLTAGE=1.8
|
||||
-D SX126X_CURRENT_LIMIT=140
|
||||
build_src_filter = ${esp32_base.build_src_filter}
|
||||
+<../variants/xiao_c3>
|
||||
|
||||
[Xiao_esp32_C3_custom]
|
||||
extends = esp32_base
|
||||
board = seeed_xiao_esp32c3
|
||||
build_flags =
|
||||
${esp32_base.build_flags}
|
||||
-I variants/xiao_c3
|
||||
@@ -30,7 +51,7 @@ build_flags =
|
||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||
-D SX126X_RX_BOOSTED_GAIN=1
|
||||
-D LORA_TX_POWER=22
|
||||
-D ADVERT_NAME='"Xiao Repeater"'
|
||||
-D ADVERT_NAME='"Xiao C3 Repeater"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
@@ -41,12 +62,78 @@ lib_deps =
|
||||
${Xiao_esp32_C3.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
[env:Xiao_C3_Repeater_sx1268]
|
||||
[env:Xiao_C3_companion_radio_ble]
|
||||
extends = Xiao_esp32_C3
|
||||
build_src_filter = ${Xiao_esp32_C3.build_src_filter}
|
||||
+<../examples/simple_repeater/main.cpp>
|
||||
+<../examples/companion_radio>
|
||||
+<helpers/esp32/*.cpp>
|
||||
build_flags =
|
||||
${Xiao_esp32_C3.build_flags}
|
||||
-D RADIO_CLASS=CustomSX1262
|
||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||
-D SX126X_RX_BOOSTED_GAIN=1
|
||||
-D LORA_TX_POWER=22
|
||||
-D MAX_CONTACTS=100
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D BLE_PIN_CODE=123456
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
; -D BLE_DEBUG_LOGGING=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
lib_deps =
|
||||
${Xiao_esp32_C3.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Xiao_C3_companion_radio_usb]
|
||||
extends = Xiao_esp32_C3
|
||||
build_src_filter = ${Xiao_esp32_C3.build_src_filter}
|
||||
+<../examples/companion_radio>
|
||||
+<helpers/esp32/*.cpp>
|
||||
build_flags =
|
||||
${Xiao_esp32_C3.build_flags}
|
||||
-D RADIO_CLASS=CustomSX1262
|
||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||
-D SX126X_RX_BOOSTED_GAIN=1
|
||||
-D LORA_TX_POWER=22
|
||||
-D MAX_CONTACTS=100
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
; -D BLE_DEBUG_LOGGING=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
lib_deps =
|
||||
${Xiao_esp32_C3.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Xiao_C3_Repeater_sx1262_custom]
|
||||
extends = Xiao_esp32_C3_custom
|
||||
build_src_filter = ${Xiao_esp32_C3_custom.build_src_filter}
|
||||
+<../examples/simple_repeater/main.cpp>
|
||||
build_flags =
|
||||
${Xiao_esp32_C3_custom.build_flags}
|
||||
-D RADIO_CLASS=CustomSX1262
|
||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||
-D SX126X_RX_BOOSTED_GAIN=1
|
||||
-D LORA_TX_POWER=22
|
||||
-D ADVERT_NAME='"Xiao 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 =
|
||||
${Xiao_esp32_C3_custom.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
[env:Xiao_C3_Repeater_sx1268_custom]
|
||||
extends = Xiao_esp32_C3_custom
|
||||
build_src_filter = ${Xiao_esp32_C3_custom.build_src_filter}
|
||||
+<../examples/simple_repeater/main.cpp>
|
||||
build_flags =
|
||||
${Xiao_esp32_C3_custom.build_flags}
|
||||
-D RADIO_CLASS=CustomSX1268
|
||||
-D WRAPPER_CLASS=CustomSX1268Wrapper
|
||||
-D LORA_TX_POWER=22
|
||||
@@ -58,5 +145,5 @@ build_flags =
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
lib_deps =
|
||||
${Xiao_esp32_C3.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
${Xiao_esp32_C3_custom.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
@@ -5,20 +5,19 @@
|
||||
|
||||
#ifdef XIAO_NRF52
|
||||
|
||||
// LoRa radio module pins for Seeed Xiao-nrf52
|
||||
// redefine lora pins if using the S3 variant of SX1262 board
|
||||
#ifdef SX1262_XIAO_S3_VARIANT
|
||||
#undef P_LORA_DIO_1
|
||||
#undef P_LORA_BUSY
|
||||
#undef P_LORA_RESET
|
||||
#undef P_LORA_NSS
|
||||
#undef SX126X_RXEN
|
||||
#define P_LORA_DIO_1 D0
|
||||
#define P_LORA_BUSY D1
|
||||
#define P_LORA_RESET D2
|
||||
#define P_LORA_NSS D3
|
||||
#define SX126X_RXEN D4
|
||||
#endif
|
||||
//#define SX126X_POWER_EN 37
|
||||
|
||||
|
||||
|
||||
class XiaoNrf52Board : public mesh::MainBoard {
|
||||
protected:
|
||||
@@ -50,8 +50,7 @@ build_flags = ${nrf52840_xiao.build_flags}
|
||||
-D ENV_INCLUDE_INA219=1
|
||||
build_src_filter = ${nrf52840_xiao.build_src_filter}
|
||||
+<helpers/*.cpp>
|
||||
+<helpers/sensors>
|
||||
+<helpers/nrf52/XiaoNrf52Board.cpp>
|
||||
+<helpers/sensors>
|
||||
+<../variants/xiao_nrf52>
|
||||
debug_tool = jlink
|
||||
upload_protocol = nrfutil
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#define RADIOLIB_STATIC_ONLY 1
|
||||
#include <RadioLib.h>
|
||||
#include <helpers/RadioLibWrappers.h>
|
||||
#include <helpers/nrf52/XiaoNrf52Board.h>
|
||||
#include <XiaoNrf52Board.h>
|
||||
#include <helpers/CustomSX1262Wrapper.h>
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
|
||||
104
variants/xiao_rp2040/platformio.ini
Normal file
104
variants/xiao_rp2040/platformio.ini
Normal file
@@ -0,0 +1,104 @@
|
||||
[Xiao_rp2040]
|
||||
extends = rp2040_base
|
||||
|
||||
board = seeed_xiao_rp2040
|
||||
board_build.filesystem_size = 0.5m
|
||||
|
||||
build_flags = ${rp2040_base.build_flags}
|
||||
-I variants/xiao_rp2040
|
||||
-D SX126X_CURRENT_LIMIT=140
|
||||
-D RADIO_CLASS=CustomSX1262
|
||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||
-D LORA_TX_POWER=22
|
||||
-D SX126X_RX_BOOSTED_GAIN=1
|
||||
; Debug options
|
||||
; -D DEBUG_RP2040_WIRE=1
|
||||
; -D DEBUG_RP2040_SPI=1
|
||||
; -D DEBUG_RP2040_CORE=1
|
||||
; -D RADIOLIB_DEBUG_SPI=1
|
||||
; -D DEBUG_RP2040_PORT=Serial
|
||||
|
||||
build_src_filter = ${rp2040_base.build_src_filter}
|
||||
+<helpers/rp2040/XiaoRP2040Board.cpp>
|
||||
+<../variants/xiao_rp2040>
|
||||
|
||||
lib_deps = ${rp2040_base.lib_deps}
|
||||
|
||||
[env:Xiao_rp2040_Repeater]
|
||||
extends = Xiao_rp2040
|
||||
build_flags = ${Xiao_rp2040.build_flags}
|
||||
-D ADVERT_NAME='"Xiao 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 = ${Xiao_rp2040.build_src_filter}
|
||||
+<../examples/simple_repeater>
|
||||
|
||||
[env:Xiao_rp2040_room_server]
|
||||
extends = Xiao_rp2040
|
||||
build_flags = ${Xiao_rp2040.build_flags}
|
||||
-D ADVERT_NAME='"Xiao Room"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D ROOM_PASSWORD='"hello"'
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Xiao_rp2040.build_src_filter}
|
||||
+<../examples/simple_room_server>
|
||||
|
||||
[env:Xiao_rp2040_companion_radio_usb]
|
||||
extends = Xiao_rp2040
|
||||
build_flags = ${Xiao_rp2040.build_flags}
|
||||
-D MAX_CONTACTS=100
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1
|
||||
; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1
|
||||
build_src_filter = ${Xiao_rp2040.build_src_filter}
|
||||
+<../examples/companion_radio>
|
||||
lib_deps = ${Xiao_rp2040.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
; [env:Xiao_rp2040_companion_radio_ble]
|
||||
; extends = Xiao_rp2040
|
||||
; build_flags = ${Xiao_rp2040.build_flags}
|
||||
; -D MAX_CONTACTS=100
|
||||
; -D MAX_GROUP_CHANNELS=8
|
||||
; -D BLE_PIN_CODE=123456
|
||||
; -D BLE_DEBUG_LOGGING=1
|
||||
; ; -D MESH_PACKET_LOGGING=1
|
||||
; ; -D MESH_DEBUG=1
|
||||
; build_src_filter = ${Xiao_rp2040.build_src_filter}
|
||||
; +<../examples/companion_radio>
|
||||
; lib_deps = ${Xiao_rp2040.lib_deps}
|
||||
; densaugeo/base64 @ ~1.4.0
|
||||
|
||||
; [env:Xiao_rp2040_companion_radio_wifi]
|
||||
; extends = Xiao_rp2040
|
||||
; build_flags = ${Xiao_rp2040.build_flags}
|
||||
; -D MAX_CONTACTS=100
|
||||
; -D MAX_GROUP_CHANNELS=8
|
||||
; -D WIFI_DEBUG_LOGGING=1
|
||||
; -D WIFI_SSID='"myssid"'
|
||||
; -D WIFI_PWD='"mypwd"'
|
||||
; ; -D MESH_PACKET_LOGGING=1
|
||||
; ; -D MESH_DEBUG=1
|
||||
; build_src_filter = ${Xiao_rp2040.build_src_filter}
|
||||
; +<../examples/companion_radio>
|
||||
; lib_deps = ${Xiao_rp2040.lib_deps}
|
||||
; densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Xiao_rp2040_terminal_chat]
|
||||
extends = Xiao_rp2040
|
||||
build_flags = ${Xiao_rp2040.build_flags}
|
||||
-D MAX_CONTACTS=100
|
||||
-D MAX_GROUP_CHANNELS=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Xiao_rp2040.build_src_filter}
|
||||
+<../examples/simple_secure_chat/main.cpp>
|
||||
lib_deps = ${Xiao_rp2040.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
71
variants/xiao_rp2040/target.cpp
Normal file
71
variants/xiao_rp2040/target.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
#include "target.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
|
||||
XiaoRP2040Board board;
|
||||
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
VolatileRTCClock fallback_clock;
|
||||
AutoDiscoverRTCClock rtc_clock(fallback_clock);
|
||||
SensorManager sensors;
|
||||
|
||||
#ifndef LORA_CR
|
||||
#define LORA_CR 5
|
||||
#endif
|
||||
|
||||
bool radio_init() {
|
||||
rtc_clock.begin(Wire);
|
||||
|
||||
#ifdef SX126X_DIO3_TCXO_VOLTAGE
|
||||
float tcxo = SX126X_DIO3_TCXO_VOLTAGE;
|
||||
#else
|
||||
float tcxo = 1.6f;
|
||||
#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
|
||||
}
|
||||
21
variants/xiao_rp2040/target.h
Normal file
21
variants/xiao_rp2040/target.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#define RADIOLIB_STATIC_ONLY 1
|
||||
|
||||
#include <RadioLib.h>
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
#include <helpers/CustomSX1262Wrapper.h>
|
||||
#include <helpers/RadioLibWrappers.h>
|
||||
#include <helpers/SensorManager.h>
|
||||
#include <helpers/rp2040/XiaoRP2040Board.h>
|
||||
|
||||
extern XiaoRP2040Board 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();
|
||||
Reference in New Issue
Block a user