mirror of
https://github.com/meshcore-dev/MeshCore.git
synced 2026-07-20 09:12:00 +00:00
@@ -37,7 +37,8 @@
|
||||
"onboard_tools": [
|
||||
"jlink"
|
||||
],
|
||||
"svd_path": "nrf52840.svd"
|
||||
"svd_path": "nrf52840.svd",
|
||||
"openocd_target": "nrf52.cfg"
|
||||
},
|
||||
"frameworks": [
|
||||
"arduino"
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "esp32s3_out.ld",
|
||||
"memory_type": "qio_opi"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-DBOARD_HAS_PSRAM",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_USB_MODE=0",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=0"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "qio",
|
||||
"hwids": [["0x303A", "0x1001"]],
|
||||
"mcu": "esp32s3",
|
||||
"variant": "esp32s3"
|
||||
},
|
||||
"connectivity": [
|
||||
"wifi"
|
||||
],
|
||||
"debug": {
|
||||
"default_tool": "esp-builtin",
|
||||
"onboard_tools": ["esp-builtin"],
|
||||
"openocd_target": "esp32s3.cfg"
|
||||
},
|
||||
"frameworks": ["arduino", "espidf"],
|
||||
"name": "Station G3 ESP32",
|
||||
"upload": {
|
||||
"flash_size": "16MB",
|
||||
"maximum_ram_size": 327680,
|
||||
"maximum_size": 16777216,
|
||||
"use_1200bps_touch": true,
|
||||
"wait_for_upload_port": true,
|
||||
"require_upload_port": true,
|
||||
"speed": 921600
|
||||
},
|
||||
"url": "https://wiki.bqvoy.com/en/devkits/station-g3",
|
||||
"vendor": "BQ Consulting"
|
||||
}
|
||||
@@ -756,6 +756,47 @@ This document provides an overview of CLI commands that can be sent to MeshCore
|
||||
|
||||
---
|
||||
|
||||
#### Define region hierarchy (single line)
|
||||
**Usage:**
|
||||
- `region def <token> [<token> ...]`
|
||||
|
||||
**Parameters (tokens):** Space-separated. A logical **cursor** starts at the wildcard `*`.
|
||||
|
||||
- **`name`** — Create `name` as a child of the current cursor (equivalent to `region put name` with the cursor as parent). Cursor moves to `name`.
|
||||
- **`name|jump`** *(or `name,jump`)* — Create `name` as a child of the current cursor, then move the cursor to `jump` (must already exist on the node, or have been created earlier in this command). `jump` is **not** the parent of `name`; use this form to pop back up and start another branch.
|
||||
|
||||
**Behavior:** Each created region defaults to flood-allowed (same as `region put`). The reply is the resulting region tree (same format as bare `region`); review it before running `region save` to persist. On error, the reply is `Err - ...` and any regions placed before the failure remain on the node, just like a partial chain of `region put`.
|
||||
|
||||
**Existing regions:** `region def` does not clear the existing tree — if a name already exists, its parent is updated to the current cursor; otherwise a new region is created. To start from scratch, `region remove` the unwanted regions first.
|
||||
|
||||
**Limits:** Repeater serial accepts one line up to **160 characters**. For larger trees, split across multiple `region def` commands; the cursor resets to `*` between commands, so lead the next command with `child|ancestor` to reposition. Each token splits at most once on `|` — `region def a|b|c|d` is not a flat-list shorthand; see the flat-list example below.
|
||||
|
||||
**Example — linear chain** (each token becomes a child of the previous):
|
||||
```
|
||||
region def a b c d e
|
||||
region save
|
||||
```
|
||||
|
||||
**Example — branched tree** (equivalent to `region put a`, `region put b a`, `region put c b`, `region put d c`, `region put e b`, `region put f e`):
|
||||
```
|
||||
region def a b c d|b e f
|
||||
region save
|
||||
```
|
||||
|
||||
**Example — error and partial state:**
|
||||
```
|
||||
region def a b c|nope d
|
||||
```
|
||||
The reply is `Err - unknown jump: nope`. `a`, `b`, and `c` were placed before the failure; `d` was not. Run `region` to inspect, then re-run with a corrected jump or repair with `region remove` / `region put`.
|
||||
|
||||
**Example — flat list** (each region a child of `*`). Use `|*` after each token to pop the cursor back to the root before the next token:
|
||||
```
|
||||
region def a|* b|* c|* d|* e|* f
|
||||
region save
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### Remove a region
|
||||
**Usage:**
|
||||
- `region remove <name>`
|
||||
|
||||
@@ -156,6 +156,7 @@ Response codes use the high-bit convention: `response = command | 0x80`. Generic
|
||||
| MacFailed | `0x04` | MAC verification failed |
|
||||
| UnknownCmd | `0x05` | Unknown sub-command |
|
||||
| EncryptFailed | `0x06` | Encryption failed |
|
||||
| TxBusy | `0x07` | Transmit busy |
|
||||
|
||||
### Unsolicited Events
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
#define CMD_SEND_CHANNEL_DATA 62
|
||||
#define CMD_SET_DEFAULT_FLOOD_SCOPE 63
|
||||
#define CMD_GET_DEFAULT_FLOOD_SCOPE 64
|
||||
#define CMD_SEND_RAW_PACKET 65
|
||||
|
||||
// Stats sub-types for CMD_GET_STATS
|
||||
#define STATS_TYPE_CORE 0
|
||||
@@ -986,7 +987,7 @@ static FreqRange repeat_freq_ranges[] = {
|
||||
ALLOWED_REPEAT_FREQ_RANGE
|
||||
#else
|
||||
{ 433000, 433000 },
|
||||
{ 869000, 869000 },
|
||||
{ 869495, 869495 },
|
||||
{ 918000, 918000 }
|
||||
#endif
|
||||
};
|
||||
@@ -1963,6 +1964,19 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
memcpy(&out_frame[i], &r->upper_freq, 4); i += 4;
|
||||
}
|
||||
_serial->writeFrame(out_frame, i);
|
||||
} else if (cmd_frame[0] == CMD_SEND_RAW_PACKET && len >= 4) {
|
||||
auto pkt = obtainNewPacket();
|
||||
if (pkt) {
|
||||
uint8_t priority = cmd_frame[1];
|
||||
if (tryParsePacket(pkt, &cmd_frame[2], len - 2)) {
|
||||
sendPacket(pkt, priority, 0);
|
||||
writeOKFrame();
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
|
||||
}
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_TABLE_FULL);
|
||||
}
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_UNSUPPORTED_CMD);
|
||||
MESH_DEBUG_PRINTLN("ERROR: unknown command: %02X", cmd_frame[0]);
|
||||
|
||||
@@ -105,6 +105,12 @@ void halt() {
|
||||
while (1) ;
|
||||
}
|
||||
|
||||
/* WIFI RECONNECT TRACKERS */
|
||||
#if defined(ESP32) && defined(WIFI_SSID)
|
||||
bool wifi_needs_reconnect = false;
|
||||
unsigned long last_wifi_reconnect_attempt = 0;
|
||||
#endif
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
@@ -195,6 +201,18 @@ void setup() {
|
||||
|
||||
#ifdef WIFI_SSID
|
||||
board.setInhibitSleep(true); // prevent sleep when WiFi is active
|
||||
WiFi.setAutoReconnect(true);
|
||||
|
||||
WiFi.onEvent([](WiFiEvent_t event, WiFiEventInfo_t info){
|
||||
if (event == ARDUINO_EVENT_WIFI_STA_DISCONNECTED) {
|
||||
WIFI_DEBUG_PRINTLN("WiFi disconnected. Flagging for reconnect...");
|
||||
wifi_needs_reconnect = true;
|
||||
} else if (event == ARDUINO_EVENT_WIFI_STA_GOT_IP) {
|
||||
WIFI_DEBUG_PRINTLN("WiFi connected successfully!");
|
||||
wifi_needs_reconnect = false;
|
||||
}
|
||||
});
|
||||
|
||||
WiFi.begin(WIFI_SSID, WIFI_PWD);
|
||||
serial_interface.begin(TCP_PORT);
|
||||
#elif defined(BLE_PIN_CODE)
|
||||
@@ -220,6 +238,8 @@ void setup() {
|
||||
#ifdef DISPLAY_CLASS
|
||||
ui_task.begin(disp, &sensors, the_mesh.getNodePrefs()); // still want to pass this in as dependency, as prefs might be moved
|
||||
#endif
|
||||
|
||||
board.onBootComplete();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
@@ -229,4 +249,14 @@ void loop() {
|
||||
ui_task.loop();
|
||||
#endif
|
||||
rtc_clock.tick();
|
||||
|
||||
#if defined(ESP32) && defined(WIFI_SSID)
|
||||
// Safely attempt to reconnect every 10 seconds if flagged
|
||||
if (wifi_needs_reconnect && (millis() - last_wifi_reconnect_attempt > 10000)) {
|
||||
WIFI_DEBUG_PRINTLN("Attempting manual WiFi reconnect...");
|
||||
WiFi.disconnect();
|
||||
WiFi.reconnect();
|
||||
last_wifi_reconnect_attempt = millis();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
#pragma once
|
||||
|
||||
#include <helpers/ui/DisplayDriver.h>
|
||||
|
||||
|
||||
#ifndef STATUS_BAR_SCROLL_MS
|
||||
#define STATUS_BAR_SCROLL_MS 80
|
||||
#endif
|
||||
|
||||
#ifndef STATUS_BAR_SEPARATOR
|
||||
#define STATUS_BAR_SEPARATOR " | "
|
||||
#endif
|
||||
|
||||
#ifndef STATUS_BAR_UPDATE_MS
|
||||
#define STATUS_BAR_UPDATE_MS 2000 // rebuild status string every 2s
|
||||
#endif
|
||||
|
||||
class ScrollingStatusBar {
|
||||
char _status[160];
|
||||
int _text_width;
|
||||
int _scroll_x;
|
||||
int _display_width;
|
||||
unsigned long _next_scroll;
|
||||
unsigned long _next_update;
|
||||
bool _needs_redraw;
|
||||
|
||||
// cached state for change detection
|
||||
char _last_name[32];
|
||||
uint16_t _last_batt_mv;
|
||||
bool _last_buzzer_quiet;
|
||||
bool _last_gps_on;
|
||||
bool _last_ble_on;
|
||||
|
||||
public:
|
||||
ScrollingStatusBar() : _text_width(0), _scroll_x(0), _display_width(72),
|
||||
_next_scroll(0), _next_update(0), _needs_redraw(true),
|
||||
_last_batt_mv(0), _last_buzzer_quiet(false),
|
||||
_last_gps_on(false), _last_ble_on(false) {
|
||||
_status[0] = 0;
|
||||
_last_name[0] = 0;
|
||||
}
|
||||
|
||||
void begin(int display_width) {
|
||||
_display_width = display_width;
|
||||
_scroll_x = 0;
|
||||
_next_scroll = 0;
|
||||
_next_update = 0;
|
||||
}
|
||||
|
||||
// Call periodically to update the status string content.
|
||||
// Only rebuilds if values have changed or update interval has elapsed.
|
||||
void update(DisplayDriver& display, const char* node_name, uint16_t batt_millivolts,
|
||||
bool buzzer_quiet, bool gps_on, bool ble_on) {
|
||||
|
||||
bool changed = (batt_millivolts != _last_batt_mv)
|
||||
|| (buzzer_quiet != _last_buzzer_quiet)
|
||||
|| (gps_on != _last_gps_on)
|
||||
|| (ble_on != _last_ble_on)
|
||||
|| (strcmp(node_name, _last_name) != 0);
|
||||
|
||||
if (!changed) return;
|
||||
|
||||
// cache current values
|
||||
strncpy(_last_name, node_name, sizeof(_last_name) - 1);
|
||||
_last_name[sizeof(_last_name) - 1] = 0;
|
||||
_last_batt_mv = batt_millivolts;
|
||||
_last_buzzer_quiet = buzzer_quiet;
|
||||
_last_gps_on = gps_on;
|
||||
_last_ble_on = ble_on;
|
||||
|
||||
float volts = batt_millivolts / 1000.0f;
|
||||
|
||||
snprintf(_status, sizeof(_status),
|
||||
"%s" STATUS_BAR_SEPARATOR
|
||||
"%.2fV" STATUS_BAR_SEPARATOR
|
||||
"BUZ:%s" STATUS_BAR_SEPARATOR
|
||||
"GPS:%s" STATUS_BAR_SEPARATOR
|
||||
"BLE:%s"
|
||||
" - ", // trailing gap before the text loops
|
||||
node_name,
|
||||
volts,
|
||||
buzzer_quiet ? "OFF" : "ON",
|
||||
gps_on ? "ON" : "OFF",
|
||||
ble_on ? "ON" : "OFF"
|
||||
);
|
||||
|
||||
display.setTextSize(1);
|
||||
_text_width = display.getTextWidth(_status);
|
||||
_next_update = millis() + STATUS_BAR_UPDATE_MS;
|
||||
_needs_redraw = true;
|
||||
}
|
||||
|
||||
// Returns true if the status bar needs a redraw this frame.
|
||||
bool needsRedraw() {
|
||||
if (_text_width <= _display_width) return _needs_redraw; // static, no scrolling
|
||||
return millis() >= _next_scroll;
|
||||
}
|
||||
|
||||
// Render the status bar via DisplayDriver.
|
||||
// U8g2 full-buffer mode clips to display bounds automatically,
|
||||
// and the font height stays within STATUS_BAR_HEIGHT, so no
|
||||
// explicit clip window is needed.
|
||||
void render(DisplayDriver& display) {
|
||||
if (_status[0] == 0) return;
|
||||
|
||||
display.setTextSize(1);
|
||||
display.setColor(DisplayDriver::GREEN);
|
||||
|
||||
// if (_needs_redraw) {
|
||||
// _text_width = display.getTextWidth(_status);
|
||||
// }
|
||||
|
||||
// static text: no scrolling needed
|
||||
if (_text_width <= _display_width) {
|
||||
display.setCursor(0, 0);
|
||||
display.print(_status);
|
||||
_needs_redraw = false;
|
||||
return;
|
||||
}
|
||||
|
||||
int x = _scroll_x;
|
||||
do {
|
||||
display.setCursor(x, 0);
|
||||
display.print(_status);
|
||||
x += _text_width;
|
||||
} while (x < _display_width);
|
||||
|
||||
|
||||
// advance scroll position
|
||||
_scroll_x--;
|
||||
if (_scroll_x <= -_text_width) _scroll_x = 0;
|
||||
|
||||
_next_scroll = millis() + STATUS_BAR_SCROLL_MS;
|
||||
_needs_redraw = false;
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,819 @@
|
||||
#include "UITask.h"
|
||||
#include <helpers/TxtDataHelpers.h>
|
||||
#include "../MyMesh.h"
|
||||
#include "target.h"
|
||||
#include "u8g2_icons.h"
|
||||
|
||||
#ifdef WIFI_SSID
|
||||
#include <WiFi.h>
|
||||
#endif
|
||||
|
||||
#ifndef AUTO_OFF_MILLIS
|
||||
#define AUTO_OFF_MILLIS 15000 // 15 seconds
|
||||
#endif
|
||||
#define BOOT_SCREEN_MILLIS 4000 // 4 seconds
|
||||
|
||||
#ifdef PIN_STATUS_LED
|
||||
#define LED_ON_MILLIS 20
|
||||
#define LED_ON_MSG_MILLIS 200
|
||||
#define LED_CYCLE_MILLIS 4000
|
||||
#endif
|
||||
|
||||
#define LONG_PRESS_MILLIS 1200
|
||||
|
||||
#ifndef UI_RECENT_LIST_SIZE
|
||||
#define UI_RECENT_LIST_SIZE 4
|
||||
#endif
|
||||
|
||||
#if UI_HAS_JOYSTICK
|
||||
#define PRESS_LABEL "press Enter"
|
||||
#else
|
||||
#define PRESS_LABEL "long press"
|
||||
#endif
|
||||
|
||||
class SplashScreen : public UIScreen {
|
||||
UITask* _task;
|
||||
unsigned long dismiss_after;
|
||||
unsigned long version_after;
|
||||
char _version_info[12];
|
||||
|
||||
public:
|
||||
SplashScreen(UITask* task) : _task(task) {
|
||||
// strip off dash and commit hash by changing dash to null terminator
|
||||
// e.g: v1.2.3-abcdef -> v1.2.3
|
||||
const char *ver = FIRMWARE_VERSION;
|
||||
const char *dash = strchr(ver, '-');
|
||||
|
||||
int len = dash ? dash - ver : strlen(ver);
|
||||
if (len >= sizeof(_version_info)) len = sizeof(_version_info) - 1;
|
||||
memcpy(_version_info, ver, len);
|
||||
_version_info[len] = 0;
|
||||
|
||||
version_after = millis() + BOOT_SCREEN_MILLIS / 2;
|
||||
dismiss_after = millis() + BOOT_SCREEN_MILLIS;
|
||||
}
|
||||
|
||||
int render(DisplayDriver& display) override {
|
||||
if (millis() < version_after) {
|
||||
// meshcore logo
|
||||
display.setColor(DisplayDriver::BLUE);
|
||||
int logoWidth = 72;
|
||||
display.drawXbm(0, 0, meshcore_logo, 72, 36);
|
||||
} else {
|
||||
|
||||
// meshcore website
|
||||
const char* website = "meshcore.io";
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
display.setTextSize(1);
|
||||
uint16_t websiteWidth = display.getTextWidth(website);
|
||||
display.setCursor((display.width() - websiteWidth) / 2, 9);
|
||||
display.print(website);
|
||||
|
||||
// version info
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
display.setTextSize(1);
|
||||
display.drawTextCentered(display.width()/2, 18, _version_info);
|
||||
|
||||
display.setTextSize(1);
|
||||
display.drawTextCentered(display.width()/2, 27, FIRMWARE_BUILD_DATE);
|
||||
}
|
||||
return 1000;
|
||||
}
|
||||
|
||||
void poll() override {
|
||||
if (millis() >= dismiss_after) {
|
||||
_task->gotoHomeScreen();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class HomeScreen : public UIScreen {
|
||||
enum HomePage {
|
||||
FIRST,
|
||||
RECENT,
|
||||
RADIO,
|
||||
BLUETOOTH,
|
||||
ADVERT,
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
GPS,
|
||||
#endif
|
||||
#if UI_SENSORS_PAGE == 1
|
||||
SENSORS,
|
||||
#endif
|
||||
SHUTDOWN,
|
||||
Count // keep as last
|
||||
};
|
||||
|
||||
UITask* _task;
|
||||
mesh::RTCClock* _rtc;
|
||||
SensorManager* _sensors;
|
||||
NodePrefs* _node_prefs;
|
||||
uint8_t _page;
|
||||
bool _shutdown_init;
|
||||
AdvertPath recent[UI_RECENT_LIST_SIZE];
|
||||
|
||||
CayenneLPP sensors_lpp;
|
||||
int sensors_nb = 0;
|
||||
bool sensors_scroll = false;
|
||||
int sensors_scroll_offset = 0;
|
||||
int next_sensors_refresh = 0;
|
||||
|
||||
void refresh_sensors() {
|
||||
if (millis() > next_sensors_refresh) {
|
||||
sensors_lpp.reset();
|
||||
sensors_nb = 0;
|
||||
sensors_lpp.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f);
|
||||
sensors.querySensors(0xFF, sensors_lpp);
|
||||
LPPReader reader (sensors_lpp.getBuffer(), sensors_lpp.getSize());
|
||||
uint8_t channel, type;
|
||||
while(reader.readHeader(channel, type)) {
|
||||
reader.skipData(type);
|
||||
sensors_nb ++;
|
||||
}
|
||||
sensors_scroll = sensors_nb > UI_RECENT_LIST_SIZE;
|
||||
#if AUTO_OFF_MILLIS > 0
|
||||
next_sensors_refresh = millis() + 5000; // refresh sensor values every 5 sec
|
||||
#else
|
||||
next_sensors_refresh = millis() + 60000; // refresh sensor values every 1 min
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
HomeScreen(UITask* task, mesh::RTCClock* rtc, SensorManager* sensors, NodePrefs* node_prefs)
|
||||
: _task(task), _rtc(rtc), _sensors(sensors), _node_prefs(node_prefs), _page(0),
|
||||
_shutdown_init(false), sensors_lpp(200) { }
|
||||
|
||||
void poll() override {
|
||||
if (_shutdown_init && !_task->isButtonPressed()) { // must wait for USR button to be released
|
||||
_task->shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
int render(DisplayDriver& display) override {
|
||||
char tmp[80];
|
||||
|
||||
if (_page == HomePage::FIRST) {
|
||||
// // node name
|
||||
// display.setTextSize(1);
|
||||
// display.setColor(DisplayDriver::GREEN);
|
||||
// char filtered_name[sizeof(_node_prefs->node_name)];
|
||||
// display.translateUTF8ToBlocks(filtered_name, _node_prefs->node_name, sizeof(filtered_name));
|
||||
// display.setCursor(0, 0);
|
||||
// display.print(filtered_name);
|
||||
|
||||
|
||||
display.setColor(DisplayDriver::YELLOW);
|
||||
display.setTextSize(2);
|
||||
sprintf(tmp, "MSG: %d", _task->getMsgCount());
|
||||
display.setCursor(0, 10);
|
||||
display.print(tmp);
|
||||
|
||||
sprintf(tmp, "BATT: %.2fV", _task->getCachedBattMV() / 1000.0f);
|
||||
display.setCursor(0, 19);
|
||||
display.print(tmp);
|
||||
|
||||
#ifdef WIFI_SSID
|
||||
IPAddress ip = WiFi.localIP();
|
||||
snprintf(tmp, sizeof(tmp), "IP: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
|
||||
display.setTextSize(1);
|
||||
display.drawTextCentered(display.width() / 2, 54, tmp);
|
||||
#endif
|
||||
if (_task->hasConnection()) {
|
||||
display.setColor(DisplayDriver::GREEN);
|
||||
display.setTextSize(1);
|
||||
display.drawTextCentered(display.width() / 2, display.height()-8, "< Connected >");
|
||||
|
||||
} else if (the_mesh.getBLEPin() != 0) { // BT pin
|
||||
display.setColor(DisplayDriver::RED);
|
||||
display.setTextSize(2);
|
||||
sprintf(tmp, "Pin:%d", the_mesh.getBLEPin());
|
||||
display.drawTextCentered(display.width() / 2, display.height()-8, tmp);
|
||||
}
|
||||
} else if (_page == HomePage::RECENT) {
|
||||
the_mesh.getRecentlyHeard(recent, UI_RECENT_LIST_SIZE);
|
||||
display.setColor(DisplayDriver::GREEN);
|
||||
int y = 8;
|
||||
for (int i = 0; i < UI_RECENT_LIST_SIZE; i++, y += 11) {
|
||||
auto a = &recent[i];
|
||||
if (a->name[0] == 0) continue; // empty slot
|
||||
int secs = _rtc->getCurrentTime() - a->recv_timestamp;
|
||||
if (secs < 60) {
|
||||
sprintf(tmp, "%ds", secs);
|
||||
} else if (secs < 60*60) {
|
||||
sprintf(tmp, "%dm", secs / 60);
|
||||
} else {
|
||||
sprintf(tmp, "%dh", secs / (60*60));
|
||||
}
|
||||
|
||||
int timestamp_width = display.getTextWidth(tmp);
|
||||
int max_name_width = display.width() - timestamp_width - 1;
|
||||
|
||||
char filtered_recent_name[sizeof(a->name)];
|
||||
display.translateUTF8ToBlocks(filtered_recent_name, a->name, sizeof(filtered_recent_name));
|
||||
display.drawTextEllipsized(0, y, max_name_width, filtered_recent_name);
|
||||
display.setCursor(display.width() - timestamp_width - 1, y);
|
||||
display.print(tmp);
|
||||
}
|
||||
} else if (_page == HomePage::RADIO) {
|
||||
display.setColor(DisplayDriver::YELLOW);
|
||||
display.setTextSize(1);
|
||||
// frequency and spreading factor
|
||||
display.setCursor(0, 8);
|
||||
sprintf(tmp, "FQ %06.3f", _node_prefs->freq);
|
||||
display.print(tmp);
|
||||
sprintf(tmp, "SF%d", _node_prefs->sf);
|
||||
display.drawTextRightAlign(display.width(), 8, tmp);
|
||||
// bandwidth and coding rate
|
||||
display.setCursor(0, 17);
|
||||
sprintf(tmp, "BW %03.2f", _node_prefs->bw);
|
||||
display.print(tmp);
|
||||
sprintf(tmp, "CR%d", _node_prefs->cr);
|
||||
display.drawTextRightAlign(display.width(), 17, tmp);
|
||||
// tx power and noise floor
|
||||
display.setCursor(0, 26);
|
||||
sprintf(tmp, "NF %ddB", radio_driver.getNoiseFloor());
|
||||
display.print(tmp);
|
||||
sprintf(tmp, "TX%d", _node_prefs->tx_power_dbm);
|
||||
display.drawTextRightAlign(display.width(), 26, tmp);
|
||||
|
||||
} else if (_page == HomePage::BLUETOOTH) {
|
||||
display.setColor(DisplayDriver::GREEN);
|
||||
display.drawXbm((display.width() - 32) / 2, 8,
|
||||
_task->isSerialEnabled() ? bluetooth_on : bluetooth_off,
|
||||
32, 32);
|
||||
display.setTextSize(1);
|
||||
// display.drawTextCentered(display.width() / 2, 40 - 11, "toggle: " PRESS_LABEL);
|
||||
} else if (_page == HomePage::ADVERT) {
|
||||
display.setColor(DisplayDriver::GREEN);
|
||||
display.drawXbm((display.width() - 32) / 2, 8, advert_icon, 32, 32);
|
||||
// display.drawTextCentered(display.width() / 2, 40 - 11, "advert: " PRESS_LABEL);
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
} else if (_page == HomePage::GPS) {
|
||||
LocationProvider* nmea = sensors.getLocationProvider();
|
||||
char buf[50];
|
||||
int y = 8;
|
||||
bool gps_state = _task->getGPSState();
|
||||
#ifdef PIN_GPS_SWITCH
|
||||
bool hw_gps_state = digitalRead(PIN_GPS_SWITCH);
|
||||
if (gps_state != hw_gps_state) {
|
||||
strcpy(buf, gps_state ? "gps off(hw)" : "gps off(sw)");
|
||||
} else {
|
||||
strcpy(buf, gps_state ? "gps on" : "gps off");
|
||||
}
|
||||
#else
|
||||
strcpy(buf, gps_state ? "gps on" : "gps off");
|
||||
#endif
|
||||
display.drawTextLeftAlign(0, y, buf);
|
||||
if (nmea == NULL) {
|
||||
// y = y + 8;
|
||||
display.drawTextLeftAlign(0, y, "Can't access GPS");
|
||||
} else {
|
||||
if (!gps_state || !nmea->isValid()) {
|
||||
strcpy(buf, "no fix");
|
||||
} else {
|
||||
sprintf(buf, "%d sat", nmea->satellitesCount());
|
||||
}
|
||||
display.drawTextRightAlign(display.width()-1, y, buf);
|
||||
y = y + 8;
|
||||
sprintf(buf, "lat %.4f",
|
||||
nmea->getLatitude()/1000000.);
|
||||
display.drawTextLeftAlign(0, y, buf);
|
||||
y = y + 8;
|
||||
sprintf(buf, "lon %.4f",
|
||||
nmea->getLongitude()/1000000.);
|
||||
display.drawTextLeftAlign(0, y, buf);
|
||||
y = y + 8;
|
||||
sprintf(buf, "alt %.1f", nmea->getAltitude()/1000.);
|
||||
display.drawTextLeftAlign(0, y, buf);
|
||||
}
|
||||
#endif
|
||||
#if UI_SENSORS_PAGE == 1
|
||||
} else if (_page == HomePage::SENSORS) {
|
||||
int y = 8;
|
||||
refresh_sensors();
|
||||
char buf[30];
|
||||
char name[30];
|
||||
LPPReader r(sensors_lpp.getBuffer(), sensors_lpp.getSize());
|
||||
|
||||
for (int i = 0; i < sensors_scroll_offset; i++) {
|
||||
uint8_t channel, type;
|
||||
r.readHeader(channel, type);
|
||||
r.skipData(type);
|
||||
}
|
||||
|
||||
for (int i = 0; i < (sensors_scroll?UI_RECENT_LIST_SIZE:sensors_nb); i++) {
|
||||
uint8_t channel, type;
|
||||
if (!r.readHeader(channel, type)) { // reached end, reset
|
||||
r.reset();
|
||||
r.readHeader(channel, type);
|
||||
}
|
||||
|
||||
display.setCursor(0, y);
|
||||
float v;
|
||||
switch (type) {
|
||||
case LPP_GPS: // GPS
|
||||
float lat, lon, alt;
|
||||
r.readGPS(lat, lon, alt);
|
||||
strcpy(name, "gps"); sprintf(buf, "%.4f %.4f", lat, lon);
|
||||
break;
|
||||
case LPP_VOLTAGE:
|
||||
r.readVoltage(v);
|
||||
strcpy(name, "voltage"); sprintf(buf, "%6.2f", v);
|
||||
break;
|
||||
case LPP_CURRENT:
|
||||
r.readCurrent(v);
|
||||
strcpy(name, "current"); sprintf(buf, "%.3f", v);
|
||||
break;
|
||||
case LPP_TEMPERATURE:
|
||||
r.readTemperature(v);
|
||||
strcpy(name, "temperature"); sprintf(buf, "%.2f", v);
|
||||
break;
|
||||
case LPP_RELATIVE_HUMIDITY:
|
||||
r.readRelativeHumidity(v);
|
||||
strcpy(name, "humidity"); sprintf(buf, "%.2f", v);
|
||||
break;
|
||||
case LPP_BAROMETRIC_PRESSURE:
|
||||
r.readPressure(v);
|
||||
strcpy(name, "pressure"); sprintf(buf, "%.2f", v);
|
||||
break;
|
||||
case LPP_ALTITUDE:
|
||||
r.readAltitude(v);
|
||||
strcpy(name, "altitude"); sprintf(buf, "%.0f", v);
|
||||
break;
|
||||
case LPP_POWER:
|
||||
r.readPower(v);
|
||||
strcpy(name, "power"); sprintf(buf, "%6.2f", v);
|
||||
break;
|
||||
default:
|
||||
r.skipData(type);
|
||||
strcpy(name, "unk"); sprintf(buf, "");
|
||||
}
|
||||
display.setCursor(0, y);
|
||||
display.print(name);
|
||||
display.setCursor(
|
||||
display.width()-display.getTextWidth(buf)-1, y
|
||||
);
|
||||
display.print(buf);
|
||||
y = y + 12;
|
||||
}
|
||||
if (sensors_scroll) sensors_scroll_offset = (sensors_scroll_offset+1)%sensors_nb;
|
||||
else sensors_scroll_offset = 0;
|
||||
#endif
|
||||
} else if (_page == HomePage::SHUTDOWN) {
|
||||
display.setColor(DisplayDriver::GREEN);
|
||||
display.setTextSize(1);
|
||||
if (_shutdown_init) {
|
||||
display.drawTextCentered(display.width() / 2, 20, "hibernating...");
|
||||
} else {
|
||||
display.drawXbm((display.width() - 32) / 2, 8, power_icon, 32, 32);
|
||||
// display.drawTextCentered(display.width() / 2, 40 - 11, "hibernate:" PRESS_LABEL);
|
||||
}
|
||||
}
|
||||
return 5000; // next render after 5000 ms
|
||||
}
|
||||
|
||||
bool handleInput(char c) override {
|
||||
if (c == KEY_LEFT || c == KEY_PREV) {
|
||||
_page = (_page + HomePage::Count - 1) % HomePage::Count;
|
||||
return true;
|
||||
}
|
||||
if (c == KEY_NEXT || c == KEY_RIGHT) {
|
||||
_page = (_page + 1) % HomePage::Count;
|
||||
if (_page == HomePage::RECENT) {
|
||||
_task->showAlert("Recent adverts", 800);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (c == KEY_ENTER && _page == HomePage::BLUETOOTH) {
|
||||
if (_task->isSerialEnabled()) { // toggle Bluetooth on/off
|
||||
_task->disableSerial();
|
||||
} else {
|
||||
_task->enableSerial();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (c == KEY_ENTER && _page == HomePage::ADVERT) {
|
||||
_task->notify(UIEventType::ack);
|
||||
if (the_mesh.advert()) {
|
||||
_task->showAlert("Advert sent!", 1000);
|
||||
} else {
|
||||
_task->showAlert("Advert failed..", 1000);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
if (c == KEY_ENTER && _page == HomePage::GPS) {
|
||||
_task->toggleGPS();
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#if UI_SENSORS_PAGE == 1
|
||||
if (c == KEY_ENTER && _page == HomePage::SENSORS) {
|
||||
_task->toggleGPS();
|
||||
next_sensors_refresh=0;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
if (c == KEY_ENTER && _page == HomePage::SHUTDOWN) {
|
||||
_shutdown_init = true; // need to wait for button to be released
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* node_prefs) {
|
||||
_display = display;
|
||||
_sensors = sensors;
|
||||
_auto_off = millis() + AUTO_OFF_MILLIS;
|
||||
_cached_batt_mv = getBattMilliVolts();
|
||||
|
||||
#if defined(PIN_USER_BTN)
|
||||
user_btn.begin();
|
||||
#endif
|
||||
#if defined(PIN_USER_BTN_ANA)
|
||||
analog_btn.begin();
|
||||
#endif
|
||||
|
||||
_node_prefs = node_prefs;
|
||||
|
||||
if (_display != NULL) {
|
||||
_display->turnOn();
|
||||
}
|
||||
_statusBar.begin(_display->width());
|
||||
|
||||
|
||||
#ifdef PIN_BUZZER
|
||||
buzzer.begin();
|
||||
buzzer.quiet(_node_prefs->buzzer_quiet);
|
||||
buzzer.startup();
|
||||
#endif
|
||||
|
||||
#ifdef PIN_VIBRATION
|
||||
vibration.begin();
|
||||
#endif
|
||||
|
||||
ui_started_at = millis();
|
||||
_alert_expiry = 0;
|
||||
|
||||
splash = new SplashScreen(this);
|
||||
home = new HomeScreen(this, &rtc_clock, sensors, node_prefs);
|
||||
setCurrScreen(splash);
|
||||
}
|
||||
|
||||
void UITask::showAlert(const char* text, int duration_millis) {
|
||||
strcpy(_alert, text);
|
||||
_alert_expiry = millis() + duration_millis;
|
||||
}
|
||||
|
||||
void UITask::notify(UIEventType t) {
|
||||
#if defined(PIN_BUZZER)
|
||||
switch(t){
|
||||
case UIEventType::contactMessage:
|
||||
// gemini's pick
|
||||
buzzer.play("MsgRcv3:d=4,o=6,b=200:32e,32g,32b,16c7");
|
||||
break;
|
||||
case UIEventType::channelMessage:
|
||||
buzzer.play("kerplop:d=16,o=6,b=120:32g#,32c#");
|
||||
break;
|
||||
case UIEventType::ack:
|
||||
buzzer.play("ack:d=32,o=8,b=120:c");
|
||||
break;
|
||||
case UIEventType::roomMessage:
|
||||
case UIEventType::newContactMessage:
|
||||
case UIEventType::none:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef PIN_VIBRATION
|
||||
// Trigger vibration for all UI events except none
|
||||
if (t != UIEventType::none) {
|
||||
vibration.trigger();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void UITask::msgRead(int msgcount) {
|
||||
_msgcount = msgcount;
|
||||
if (msgcount == 0) {
|
||||
gotoHomeScreen();
|
||||
}
|
||||
}
|
||||
|
||||
void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) {
|
||||
_msgcount = msgcount;
|
||||
|
||||
if (_display != NULL) {
|
||||
if (!_display->isOn() && !hasConnection()) {
|
||||
_display->turnOn();
|
||||
}
|
||||
if (_display->isOn()) {
|
||||
_auto_off = millis() + AUTO_OFF_MILLIS; // extend the auto-off timer
|
||||
_next_refresh = 100; // trigger refresh
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UITask::userLedHandler() {
|
||||
#ifdef PIN_STATUS_LED
|
||||
int cur_time = millis();
|
||||
if (cur_time > next_led_change) {
|
||||
if (led_state == 0) {
|
||||
led_state = 1;
|
||||
if (_msgcount > 0) {
|
||||
last_led_increment = LED_ON_MSG_MILLIS;
|
||||
} else {
|
||||
last_led_increment = LED_ON_MILLIS;
|
||||
}
|
||||
next_led_change = cur_time + last_led_increment;
|
||||
} else {
|
||||
led_state = 0;
|
||||
next_led_change = cur_time + LED_CYCLE_MILLIS - last_led_increment;
|
||||
}
|
||||
digitalWrite(PIN_STATUS_LED, led_state == LED_STATE_ON);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void UITask::setCurrScreen(UIScreen* c) {
|
||||
curr = c;
|
||||
_next_refresh = 100;
|
||||
}
|
||||
|
||||
/*
|
||||
hardware-agnostic pre-shutdown activity should be done here
|
||||
*/
|
||||
void UITask::shutdown(bool restart){
|
||||
|
||||
#ifdef PIN_BUZZER
|
||||
/* note: we have a choice here -
|
||||
we can do a blocking buzzer.loop() with non-deterministic consequences
|
||||
or we can set a flag and delay the shutdown for a couple of seconds
|
||||
while a non-blocking buzzer.loop() plays out in UITask::loop()
|
||||
*/
|
||||
buzzer.shutdown();
|
||||
uint32_t buzzer_timer = millis(); // fail-safe shutdown
|
||||
while (buzzer.isPlaying() && (millis() - 2500) < buzzer_timer)
|
||||
buzzer.loop();
|
||||
|
||||
#endif // PIN_BUZZER
|
||||
|
||||
if (restart) {
|
||||
_board->reboot();
|
||||
} else {
|
||||
_display->turnOff();
|
||||
radio_driver.powerOff();
|
||||
_board->powerOff();
|
||||
}
|
||||
}
|
||||
|
||||
bool UITask::isButtonPressed() const {
|
||||
#ifdef PIN_USER_BTN
|
||||
return user_btn.isPressed();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void UITask::loop() {
|
||||
char c = 0;
|
||||
#if UI_HAS_JOYSTICK
|
||||
int ev = user_btn.check();
|
||||
if (ev == BUTTON_EVENT_CLICK) {
|
||||
c = checkDisplayOn(KEY_ENTER);
|
||||
} else if (ev == BUTTON_EVENT_LONG_PRESS) {
|
||||
c = handleLongPress(KEY_ENTER); // REVISIT: could be mapped to different key code
|
||||
}
|
||||
ev = joystick_left.check();
|
||||
if (ev == BUTTON_EVENT_CLICK) {
|
||||
c = checkDisplayOn(KEY_LEFT);
|
||||
} else if (ev == BUTTON_EVENT_LONG_PRESS) {
|
||||
c = handleLongPress(KEY_LEFT);
|
||||
}
|
||||
ev = joystick_right.check();
|
||||
if (ev == BUTTON_EVENT_CLICK) {
|
||||
c = checkDisplayOn(KEY_RIGHT);
|
||||
} else if (ev == BUTTON_EVENT_LONG_PRESS) {
|
||||
c = handleLongPress(KEY_RIGHT);
|
||||
}
|
||||
ev = back_btn.check();
|
||||
if (ev == BUTTON_EVENT_TRIPLE_CLICK) {
|
||||
c = handleTripleClick(KEY_SELECT);
|
||||
}
|
||||
#elif defined(PIN_USER_BTN)
|
||||
int ev = user_btn.check();
|
||||
if (ev == BUTTON_EVENT_CLICK) {
|
||||
c = checkDisplayOn(KEY_NEXT);
|
||||
} else if (ev == BUTTON_EVENT_LONG_PRESS) {
|
||||
c = handleLongPress(KEY_ENTER);
|
||||
} else if (ev == BUTTON_EVENT_DOUBLE_CLICK) {
|
||||
c = handleDoubleClick(KEY_PREV);
|
||||
} else if (ev == BUTTON_EVENT_TRIPLE_CLICK) {
|
||||
c = handleTripleClick(KEY_SELECT);
|
||||
}
|
||||
#endif
|
||||
#if defined(PIN_USER_BTN_ANA)
|
||||
if (abs(millis() - _analogue_pin_read_millis) > 10) {
|
||||
int ev = analog_btn.check();
|
||||
if (ev == BUTTON_EVENT_CLICK) {
|
||||
c = checkDisplayOn(KEY_NEXT);
|
||||
} else if (ev == BUTTON_EVENT_LONG_PRESS) {
|
||||
c = handleLongPress(KEY_ENTER);
|
||||
} else if (ev == BUTTON_EVENT_DOUBLE_CLICK) {
|
||||
c = handleDoubleClick(KEY_PREV);
|
||||
} else if (ev == BUTTON_EVENT_TRIPLE_CLICK) {
|
||||
c = handleTripleClick(KEY_SELECT);
|
||||
}
|
||||
_analogue_pin_read_millis = millis();
|
||||
}
|
||||
#endif
|
||||
#if defined(BACKLIGHT_BTN)
|
||||
if (millis() > next_backlight_btn_check) {
|
||||
bool touch_state = digitalRead(PIN_BUTTON2);
|
||||
#if defined(DISP_BACKLIGHT)
|
||||
digitalWrite(DISP_BACKLIGHT, !touch_state);
|
||||
#elif defined(EXP_PIN_BACKLIGHT)
|
||||
expander.digitalWrite(EXP_PIN_BACKLIGHT, !touch_state);
|
||||
#endif
|
||||
next_backlight_btn_check = millis() + 300;
|
||||
}
|
||||
#endif
|
||||
#if defined(HAS_TORCH)
|
||||
ev = back_btn.check();
|
||||
if (ev == BUTTON_EVENT_CLICK && c == 0) {
|
||||
c = checkDisplayOn(KEY_PREV);
|
||||
} else if (ev == BUTTON_EVENT_DOUBLE_CLICK) {
|
||||
board.toggleTorch();
|
||||
c = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (c != 0 && curr) {
|
||||
curr->handleInput(c);
|
||||
_auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer
|
||||
_next_refresh = 100; // trigger refresh
|
||||
}
|
||||
|
||||
userLedHandler();
|
||||
|
||||
#ifdef PIN_BUZZER
|
||||
if (buzzer.isPlaying()) buzzer.loop();
|
||||
#endif
|
||||
|
||||
if (curr) curr->poll();
|
||||
|
||||
if (_display != NULL && _display->isOn()) {
|
||||
_statusBar.update(*_display,
|
||||
_node_prefs->node_name,
|
||||
_cached_batt_mv,
|
||||
isBuzzerQuiet(),
|
||||
getGPSState(),
|
||||
isSerialEnabled());
|
||||
|
||||
bool status_dirty = _statusBar.needsRedraw();
|
||||
bool content_dirty = (millis() >= _next_refresh && curr);
|
||||
|
||||
if (status_dirty || content_dirty) {
|
||||
_display->startFrame();
|
||||
_statusBar.render(*_display);
|
||||
|
||||
if (curr) {
|
||||
int delay_millis = curr->render(*_display);
|
||||
if (content_dirty) {
|
||||
_next_refresh = millis() + delay_millis;
|
||||
}
|
||||
}
|
||||
|
||||
if (millis() < _alert_expiry) { // render alert popup
|
||||
_display->setTextSize(1);
|
||||
int y = _display->height() / 3;
|
||||
int p = _display->height() / 32;
|
||||
_display->setColor(DisplayDriver::DARK);
|
||||
_display->fillRect(p, y, _display->width() - p*2, y);
|
||||
_display->setColor(DisplayDriver::LIGHT); // draw box border
|
||||
_display->drawRect(p, y, _display->width() - p*2, y);
|
||||
_display->drawTextCentered(_display->width() / 2, y + p*3, _alert);
|
||||
_next_refresh = _alert_expiry; // will need refresh when alert is dismissed
|
||||
}
|
||||
|
||||
_display->endFrame();
|
||||
}
|
||||
#if AUTO_OFF_MILLIS > 0
|
||||
if (millis() > _auto_off) {
|
||||
_display->turnOff();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef PIN_VIBRATION
|
||||
vibration.loop();
|
||||
#endif
|
||||
|
||||
#ifdef AUTO_SHUTDOWN_MILLIVOLTS
|
||||
if (millis() > next_batt_chck) {
|
||||
_cached_batt_mv = getBattMilliVolts();
|
||||
if (_cached_batt_mv > 0 && _cached_batt_mv < AUTO_SHUTDOWN_MILLIVOLTS) {
|
||||
|
||||
shutdown();
|
||||
|
||||
}
|
||||
next_batt_chck = millis() + 8000;
|
||||
}
|
||||
#else
|
||||
if (_display != NULL && _display->isOn() && millis >= next_batt_chck) {
|
||||
_cached_batt_mv = getBattMilliVolts();
|
||||
next_batt_chck = millis() + 8000;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
char UITask::checkDisplayOn(char c) {
|
||||
if (_display != NULL) {
|
||||
if (!_display->isOn()) {
|
||||
_display->turnOn(); // turn display on and consume event
|
||||
c = 0;
|
||||
}
|
||||
_auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer
|
||||
_next_refresh = 0; // trigger refresh
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
char UITask::handleLongPress(char c) {
|
||||
if (millis() - ui_started_at < 8000) { // long press in first 8 seconds since startup -> CLI/rescue
|
||||
the_mesh.enterCLIRescue();
|
||||
c = 0; // consume event
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
char UITask::handleDoubleClick(char c) {
|
||||
MESH_DEBUG_PRINTLN("UITask: double click triggered");
|
||||
checkDisplayOn(c);
|
||||
return c;
|
||||
}
|
||||
|
||||
char UITask::handleTripleClick(char c) {
|
||||
MESH_DEBUG_PRINTLN("UITask: triple click triggered");
|
||||
checkDisplayOn(c);
|
||||
toggleBuzzer();
|
||||
c = 0;
|
||||
return c;
|
||||
}
|
||||
|
||||
bool UITask::getGPSState() {
|
||||
if (_sensors != NULL) {
|
||||
int num = _sensors->getNumSettings();
|
||||
for (int i = 0; i < num; i++) {
|
||||
if (strcmp(_sensors->getSettingName(i), "gps") == 0) {
|
||||
return !strcmp(_sensors->getSettingValue(i), "1");
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void UITask::toggleGPS() {
|
||||
if (_sensors != NULL) {
|
||||
// toggle GPS on/off
|
||||
int num = _sensors->getNumSettings();
|
||||
for (int i = 0; i < num; i++) {
|
||||
if (strcmp(_sensors->getSettingName(i), "gps") == 0) {
|
||||
if (strcmp(_sensors->getSettingValue(i), "1") == 0) {
|
||||
_sensors->setSettingValue("gps", "0");
|
||||
_node_prefs->gps_enabled = 0;
|
||||
notify(UIEventType::ack);
|
||||
} else {
|
||||
_sensors->setSettingValue("gps", "1");
|
||||
_node_prefs->gps_enabled = 1;
|
||||
notify(UIEventType::ack);
|
||||
}
|
||||
the_mesh.savePrefs();
|
||||
showAlert(_node_prefs->gps_enabled ? "GPS: Enabled" : "GPS: Disabled", 800);
|
||||
_next_refresh = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UITask::toggleBuzzer() {
|
||||
// Toggle buzzer quiet mode
|
||||
#ifdef PIN_BUZZER
|
||||
if (buzzer.isQuiet()) {
|
||||
buzzer.quiet(false);
|
||||
notify(UIEventType::ack);
|
||||
} else {
|
||||
buzzer.quiet(true);
|
||||
}
|
||||
_node_prefs->buzzer_quiet = buzzer.isQuiet();
|
||||
the_mesh.savePrefs();
|
||||
showAlert(buzzer.isQuiet() ? "Buzzer: OFF" : "Buzzer: ON", 800);
|
||||
_next_refresh = 0; // trigger refresh
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
#pragma once
|
||||
|
||||
#include <MeshCore.h>
|
||||
#include <helpers/ui/DisplayDriver.h>
|
||||
#include <helpers/ui/UIScreen.h>
|
||||
#include <helpers/SensorManager.h>
|
||||
#include <helpers/BaseSerialInterface.h>
|
||||
#include <Arduino.h>
|
||||
#include <helpers/sensors/LPPDataHelpers.h>
|
||||
|
||||
#include "ScrollingStatusBar.h"
|
||||
|
||||
|
||||
#ifndef LED_STATE_ON
|
||||
#define LED_STATE_ON 1
|
||||
#endif
|
||||
|
||||
#ifdef PIN_BUZZER
|
||||
#include <helpers/ui/buzzer.h>
|
||||
#endif
|
||||
#ifdef PIN_VIBRATION
|
||||
#include <helpers/ui/GenericVibration.h>
|
||||
#endif
|
||||
|
||||
#include "../AbstractUITask.h"
|
||||
#include "../NodePrefs.h"
|
||||
|
||||
class UITask : public AbstractUITask {
|
||||
DisplayDriver* _display;
|
||||
SensorManager* _sensors;
|
||||
ScrollingStatusBar _statusBar;
|
||||
#ifdef PIN_BUZZER
|
||||
genericBuzzer buzzer;
|
||||
#endif
|
||||
#ifdef PIN_VIBRATION
|
||||
GenericVibration vibration;
|
||||
#endif
|
||||
unsigned long _next_refresh, _auto_off;
|
||||
NodePrefs* _node_prefs;
|
||||
char _alert[80];
|
||||
unsigned long _alert_expiry;
|
||||
int _msgcount;
|
||||
unsigned long ui_started_at, next_batt_chck;
|
||||
int next_backlight_btn_check = 0;
|
||||
uint16_t _cached_batt_mv;
|
||||
#ifdef PIN_STATUS_LED
|
||||
int led_state = 0;
|
||||
int next_led_change = 0;
|
||||
int last_led_increment = 0;
|
||||
#endif
|
||||
|
||||
#ifdef PIN_USER_BTN_ANA
|
||||
unsigned long _analogue_pin_read_millis = millis();
|
||||
#endif
|
||||
|
||||
UIScreen* splash;
|
||||
UIScreen* home;
|
||||
// UIScreen* msg_preview;
|
||||
UIScreen* curr;
|
||||
|
||||
|
||||
void userLedHandler();
|
||||
|
||||
// Button action handlers
|
||||
char checkDisplayOn(char c);
|
||||
char handleLongPress(char c);
|
||||
char handleDoubleClick(char c);
|
||||
char handleTripleClick(char c);
|
||||
|
||||
void setCurrScreen(UIScreen* c);
|
||||
|
||||
public:
|
||||
|
||||
UITask(mesh::MainBoard* board, BaseSerialInterface* serial) : AbstractUITask(board, serial), _display(NULL), _sensors(NULL) {
|
||||
next_batt_chck = _next_refresh = 0;
|
||||
_cached_batt_mv = 0;
|
||||
ui_started_at = 0;
|
||||
curr = NULL;
|
||||
}
|
||||
void begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* node_prefs);
|
||||
|
||||
void gotoHomeScreen() { setCurrScreen(home); }
|
||||
void showAlert(const char* text, int duration_millis);
|
||||
int getMsgCount() const { return _msgcount; }
|
||||
uint16_t getCachedBattMV() const { return _cached_batt_mv; }
|
||||
bool hasDisplay() const { return _display != NULL; }
|
||||
bool isButtonPressed() const;
|
||||
|
||||
bool isBuzzerQuiet() {
|
||||
#ifdef PIN_BUZZER
|
||||
return buzzer.isQuiet();
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void toggleBuzzer();
|
||||
bool getGPSState();
|
||||
void toggleGPS();
|
||||
|
||||
|
||||
// from AbstractUITask
|
||||
void msgRead(int msgcount) override;
|
||||
void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) override;
|
||||
void notify(UIEventType t = UIEventType::none) override;
|
||||
void loop() override;
|
||||
|
||||
void shutdown(bool restart = false);
|
||||
};
|
||||
@@ -0,0 +1,104 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
// icons converted for use with U8g2 which needs a different format of xbm data.
|
||||
|
||||
// 'meshcore', 72x36px
|
||||
static const uint8_t meshcore_logo [] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0xf0, 0x00, 0x3e, 0xfe, 0x3f, 0xfe, 0x3f, 0x1e, 0x78,
|
||||
0xf8, 0x00, 0x1f, 0xff, 0x3f, 0xff, 0x3f, 0x1e, 0x78, 0xf8, 0x01, 0x1f,
|
||||
0xff, 0x9f, 0xff, 0x1f, 0x0e, 0x78, 0xf8, 0x81, 0x1f, 0x0f, 0x80, 0x07,
|
||||
0x00, 0x0f, 0x38, 0xf8, 0xc1, 0x1f, 0x0f, 0x80, 0x07, 0x00, 0x0f, 0x3c,
|
||||
0xf8, 0xc3, 0x1f, 0xff, 0x87, 0xff, 0x07, 0xff, 0x3f, 0xf8, 0xe3, 0x1f,
|
||||
0xff, 0x87, 0xff, 0x0f, 0xff, 0x3f, 0xfc, 0xf3, 0x8f, 0xff, 0x07, 0xff,
|
||||
0x1f, 0xff, 0x3f, 0xfc, 0xf3, 0x8f, 0x07, 0x00, 0x00, 0x9e, 0x0f, 0x1e,
|
||||
0xbc, 0x7f, 0x8f, 0x07, 0x00, 0x00, 0x9e, 0x07, 0x1e, 0x9c, 0x3f, 0x8f,
|
||||
0x07, 0x00, 0x00, 0x9f, 0x07, 0x1e, 0x9c, 0x3f, 0x8f, 0xff, 0xcf, 0xff,
|
||||
0x8f, 0x07, 0x1e, 0x1e, 0x1f, 0xc7, 0xff, 0xcf, 0xff, 0x87, 0x07, 0x1e,
|
||||
0x1e, 0x0f, 0xc7, 0xff, 0xc7, 0xff, 0x83, 0x03, 0x0e, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xc0, 0xff, 0xf0, 0xff, 0xe0, 0xff, 0xc1, 0xff, 0x07, 0xf0, 0xff, 0xf8,
|
||||
0xff, 0xf1, 0xff, 0xc3, 0xff, 0x07, 0xf0, 0xff, 0xfc, 0xff, 0xf1, 0xff,
|
||||
0xc7, 0xff, 0x07, 0x78, 0x00, 0x3c, 0xe0, 0xf1, 0xc0, 0xe7, 0x01, 0x00,
|
||||
0x78, 0x00, 0x1e, 0xe0, 0xf1, 0x80, 0xe7, 0x01, 0x00, 0x78, 0x00, 0x1e,
|
||||
0xe0, 0xf1, 0xc0, 0xe3, 0x01, 0x00, 0x78, 0x00, 0x1e, 0xe0, 0x71, 0xc0,
|
||||
0xe3, 0xff, 0x00, 0x3c, 0x00, 0x1e, 0xe0, 0xf9, 0xff, 0xe3, 0xff, 0x00,
|
||||
0x3c, 0x00, 0x1e, 0xe0, 0xf8, 0xff, 0xf1, 0xff, 0x00, 0x3c, 0x00, 0x0e,
|
||||
0xf0, 0xf8, 0xff, 0xf0, 0x00, 0x00, 0x3c, 0x00, 0x1f, 0xf0, 0x78, 0x7c,
|
||||
0xf0, 0x00, 0x00, 0xfc, 0x3f, 0xff, 0xff, 0x38, 0xf8, 0xf0, 0xff, 0x01,
|
||||
0xfc, 0x3f, 0xfe, 0x7f, 0x3c, 0xf0, 0xf0, 0xff, 0x01, 0xf8, 0x3f, 0xfe,
|
||||
0x3f, 0x3c, 0xf0, 0xf1, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
|
||||
};
|
||||
|
||||
|
||||
// bluetooth on icon, 32x32px, horizontal
|
||||
static const uint8_t bluetooth_on[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x0c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00,
|
||||
0x00, 0xfc, 0x01, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xdc, 0x07, 0x00,
|
||||
0x0c, 0x1c, 0x1f, 0x00, 0x3c, 0x1c, 0x3e, 0x00, 0x7c, 0x1c, 0x3e, 0x00,
|
||||
0xf8, 0x1d, 0x1f, 0x0e, 0xe0, 0x9f, 0x0f, 0x1e, 0xc0, 0xff, 0x03, 0x1e,
|
||||
0x00, 0xff, 0x01, 0x3c, 0x00, 0xfe, 0xe0, 0x38, 0x00, 0x7e, 0xe0, 0x38,
|
||||
0xc0, 0xff, 0x41, 0x38, 0xc0, 0xff, 0x03, 0x1e, 0xe0, 0xdf, 0x07, 0x1e,
|
||||
0xf0, 0x1d, 0x1f, 0x0e, 0x7c, 0x1c, 0x3e, 0x00, 0x3c, 0x1c, 0x3e, 0x00,
|
||||
0x1c, 0x1c, 0x1f, 0x00, 0x00, 0x9c, 0x0f, 0x00, 0x00, 0xfc, 0x03, 0x00,
|
||||
0x00, 0xfc, 0x01, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00,
|
||||
0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
|
||||
|
||||
// bluetooth off icon, 32x32px, horizontal
|
||||
static const uint8_t bluetooth_off[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x00,
|
||||
0x00, 0xc0, 0x03, 0x00, 0x00, 0xc0, 0x07, 0x00, 0x1c, 0xc0, 0x1f, 0x00,
|
||||
0x3c, 0xc0, 0x3f, 0x00, 0x7c, 0xc0, 0xfd, 0x00, 0xf0, 0xc1, 0xf1, 0x01,
|
||||
0xe0, 0xc3, 0xe1, 0x03, 0xc0, 0x0f, 0xc0, 0x03, 0x00, 0x1f, 0xf0, 0x01,
|
||||
0x00, 0x3e, 0xf0, 0x00, 0x00, 0xf8, 0x70, 0x00, 0x00, 0xf0, 0x01, 0x00,
|
||||
0x00, 0xe0, 0x07, 0x00, 0x00, 0xe0, 0x0f, 0x00, 0x00, 0xf0, 0x1f, 0x00,
|
||||
0x00, 0xfc, 0x7d, 0x00, 0x00, 0xfe, 0xf9, 0x00, 0x00, 0xdf, 0xf1, 0x03,
|
||||
0xc0, 0xc7, 0xc1, 0x07, 0xc0, 0xc3, 0xe1, 0x0f, 0xc0, 0xc1, 0xf1, 0x3f,
|
||||
0x00, 0xc0, 0xfd, 0x3c, 0x00, 0xc0, 0x3f, 0x38, 0x00, 0xc0, 0x1f, 0x00,
|
||||
0x00, 0xc0, 0x07, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, 0xc0, 0x01, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
|
||||
|
||||
// power icon, 32x32px, horizontal
|
||||
static const uint8_t power_icon[] = {
|
||||
0x00, 0x80, 0x01, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, 0xc0, 0x03, 0x00,
|
||||
0x00, 0xcc, 0x33, 0x00, 0x00, 0xcf, 0xf3, 0x00, 0x80, 0xcf, 0xf3, 0x01,
|
||||
0xc0, 0xcf, 0xf3, 0x03, 0xe0, 0xcf, 0xf3, 0x07, 0xf0, 0xc7, 0xe3, 0x0f,
|
||||
0xf8, 0xc3, 0xc3, 0x1f, 0xf8, 0xc1, 0x83, 0x1f, 0xfc, 0xc0, 0x03, 0x3f,
|
||||
0x7c, 0xc0, 0x03, 0x3e, 0x7c, 0xc0, 0x03, 0x3e, 0x7e, 0x80, 0x01, 0x7e,
|
||||
0x3e, 0x00, 0x00, 0x7c, 0x3e, 0x00, 0x00, 0x7c, 0x3e, 0x00, 0x00, 0x7c,
|
||||
0x3e, 0x00, 0x00, 0x7c, 0x3e, 0x00, 0x00, 0x7c, 0x7c, 0x00, 0x00, 0x3e,
|
||||
0x7c, 0x00, 0x00, 0x3e, 0xfc, 0x00, 0x00, 0x3f, 0xf8, 0x01, 0x80, 0x1f,
|
||||
0xf8, 0x03, 0xc0, 0x1f, 0xf0, 0x07, 0xe0, 0x0f, 0xf0, 0x1f, 0xf8, 0x0f,
|
||||
0xe0, 0xff, 0xff, 0x07, 0xc0, 0xff, 0xff, 0x03, 0x00, 0xff, 0xff, 0x00,
|
||||
0x00, 0xfc, 0x3f, 0x00, 0x00, 0xf0, 0x0f, 0x00 };
|
||||
|
||||
|
||||
|
||||
|
||||
// 'advert', 32x32px, horizontal
|
||||
static const uint8_t advert_icon[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x0c,
|
||||
0x38, 0x00, 0x00, 0x1c, 0x18, 0x00, 0x00, 0x18, 0x0c, 0x00, 0x00, 0x30,
|
||||
0x0c, 0x06, 0x60, 0x30, 0x06, 0x07, 0xe0, 0x60, 0x86, 0x03, 0xc0, 0x61,
|
||||
0x87, 0x81, 0x81, 0xe1, 0xc3, 0xe0, 0x07, 0xc3, 0xc3, 0xf0, 0x0f, 0xc3,
|
||||
0xc3, 0xf0, 0x0f, 0xc3, 0xc3, 0xf0, 0x0f, 0xc3, 0xc3, 0xf0, 0x0f, 0xc3,
|
||||
0xc3, 0xe0, 0x07, 0xc3, 0x83, 0xc1, 0x83, 0xc1, 0x86, 0x01, 0x80, 0x61,
|
||||
0x06, 0x03, 0xc0, 0x60, 0x0e, 0x07, 0xe0, 0x70, 0x0c, 0x02, 0x40, 0x30,
|
||||
0x1c, 0x00, 0x00, 0x38, 0x18, 0x00, 0x00, 0x18, 0x30, 0x00, 0x00, 0x0c,
|
||||
0x20, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
|
||||
|
||||
|
||||
// 'muted, 8x8px, horizontal
|
||||
static const uint8_t muted_icon[] = {
|
||||
0x20, 0x6a, 0xea, 0xe4, 0xe4, 0xea, 0x6a, 0x20 };
|
||||
@@ -119,6 +119,8 @@ void setup() {
|
||||
modem->setGetCurrentRssiCallback(onGetCurrentRssi);
|
||||
modem->setGetStatsCallback(onGetStats);
|
||||
modem->begin();
|
||||
|
||||
board.onBootComplete();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
|
||||
@@ -884,7 +884,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
_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 = 12; // 12 hours
|
||||
_prefs.flood_advert_interval = 47; // 47 hours
|
||||
_prefs.flood_max = 64;
|
||||
_prefs.interference_threshold = 0; // disabled
|
||||
|
||||
|
||||
@@ -103,6 +103,8 @@ void setup() {
|
||||
#if ENABLE_ADVERT_ON_BOOT == 1
|
||||
the_mesh.sendSelfAdvertisement(16000, false);
|
||||
#endif
|
||||
|
||||
board.onBootComplete();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
|
||||
@@ -641,7 +641,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
_prefs.tx_power_dbm = LORA_TX_POWER;
|
||||
_prefs.disable_fwd = 1;
|
||||
_prefs.advert_interval = 1; // default to 2 minutes for NEW installs
|
||||
_prefs.flood_advert_interval = 12; // 12 hours
|
||||
_prefs.flood_advert_interval = 47; // 47 hours
|
||||
_prefs.flood_max = 64;
|
||||
_prefs.interference_threshold = 0; // disabled
|
||||
#ifdef ROOM_PASSWORD
|
||||
|
||||
@@ -80,6 +80,8 @@ void setup() {
|
||||
#if ENABLE_ADVERT_ON_BOOT == 1
|
||||
the_mesh.sendSelfAdvertisement(16000, false);
|
||||
#endif
|
||||
|
||||
board.onBootComplete();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
|
||||
+2
-1
@@ -193,8 +193,9 @@ public:
|
||||
bool millisHasNowPassed(unsigned long timestamp) const;
|
||||
unsigned long futureMillis(int millis_from_now) const;
|
||||
|
||||
private:
|
||||
bool tryParsePacket(Packet* pkt, const uint8_t* raw, int len);
|
||||
|
||||
private:
|
||||
void checkRecv();
|
||||
void checkSend();
|
||||
};
|
||||
|
||||
+8
-11
@@ -361,13 +361,10 @@ DispatcherAction Mesh::forwardMultipartDirect(Packet* pkt) {
|
||||
|
||||
void Mesh::routeDirectRecvAcks(Packet* packet, uint32_t delay_millis) {
|
||||
if (!packet->isMarkedDoNotRetransmit()) {
|
||||
uint32_t crc;
|
||||
memcpy(&crc, packet->payload, 4);
|
||||
|
||||
uint8_t extra = getExtraAckTransmitCount();
|
||||
while (extra > 0) {
|
||||
delay_millis += getDirectRetransmitDelay(packet) + 300;
|
||||
auto a1 = createMultiAck(crc, extra);
|
||||
auto a1 = createMultiAck(packet->payload, packet->payload_len, extra);
|
||||
if (a1) {
|
||||
a1->path_len = Packet::copyPath(a1->path, packet->path, packet->path_len);
|
||||
a1->header &= ~PH_ROUTE_MASK;
|
||||
@@ -377,7 +374,7 @@ void Mesh::routeDirectRecvAcks(Packet* packet, uint32_t delay_millis) {
|
||||
extra--;
|
||||
}
|
||||
|
||||
auto a2 = createAck(crc);
|
||||
auto a2 = createAck(packet->payload, packet->payload_len);
|
||||
if (a2) {
|
||||
a2->path_len = Packet::copyPath(a2->path, packet->path, packet->path_len);
|
||||
a2->header &= ~PH_ROUTE_MASK;
|
||||
@@ -543,7 +540,7 @@ Packet* Mesh::createGroupDatagram(uint8_t type, const GroupChannel& channel, con
|
||||
return packet;
|
||||
}
|
||||
|
||||
Packet* Mesh::createAck(uint32_t ack_crc) {
|
||||
Packet* Mesh::createAck(const uint8_t* ack, uint8_t len) {
|
||||
Packet* packet = obtainNewPacket();
|
||||
if (packet == NULL) {
|
||||
MESH_DEBUG_PRINTLN("%s Mesh::createAck(): error, packet pool empty", getLogDateTime());
|
||||
@@ -551,13 +548,13 @@ Packet* Mesh::createAck(uint32_t ack_crc) {
|
||||
}
|
||||
packet->header = (PAYLOAD_TYPE_ACK << PH_TYPE_SHIFT); // ROUTE_TYPE_* set later
|
||||
|
||||
memcpy(packet->payload, &ack_crc, 4);
|
||||
packet->payload_len = 4;
|
||||
memcpy(packet->payload, ack, len);
|
||||
packet->payload_len = len;
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
Packet* Mesh::createMultiAck(uint32_t ack_crc, uint8_t remaining) {
|
||||
Packet* Mesh::createMultiAck(const uint8_t* ack, uint8_t len, uint8_t remaining) {
|
||||
Packet* packet = obtainNewPacket();
|
||||
if (packet == NULL) {
|
||||
MESH_DEBUG_PRINTLN("%s Mesh::createMultiAck(): error, packet pool empty", getLogDateTime());
|
||||
@@ -566,8 +563,8 @@ Packet* Mesh::createMultiAck(uint32_t ack_crc, uint8_t remaining) {
|
||||
packet->header = (PAYLOAD_TYPE_MULTIPART << PH_TYPE_SHIFT); // ROUTE_TYPE_* set later
|
||||
|
||||
packet->payload[0] = (remaining << 4) | PAYLOAD_TYPE_ACK;
|
||||
memcpy(&packet->payload[1], &ack_crc, 4);
|
||||
packet->payload_len = 5;
|
||||
memcpy(&packet->payload[1], ack, len);
|
||||
packet->payload_len = 1 + len;
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
+4
-2
@@ -185,8 +185,10 @@ public:
|
||||
Packet* createDatagram(uint8_t type, const Identity& dest, const uint8_t* secret, const uint8_t* data, size_t len);
|
||||
Packet* createAnonDatagram(uint8_t type, const LocalIdentity& sender, const Identity& dest, const uint8_t* secret, const uint8_t* data, size_t data_len);
|
||||
Packet* createGroupDatagram(uint8_t type, const GroupChannel& channel, const uint8_t* data, size_t data_len);
|
||||
Packet* createAck(uint32_t ack_crc);
|
||||
Packet* createMultiAck(uint32_t ack_crc, uint8_t remaining);
|
||||
Packet* createAck(const uint8_t* ack, uint8_t len);
|
||||
Packet* createAck(uint32_t ack_crc) { return createAck((uint8_t *) &ack_crc, 4); }
|
||||
Packet* createMultiAck(const uint8_t* ack, uint8_t len, uint8_t remaining);
|
||||
Packet* createMultiAck(uint32_t ack_crc, uint8_t remaining) { return createMultiAck((uint8_t *)&ack_crc, 4, remaining); }
|
||||
Packet* createPathReturn(const uint8_t* dest_hash, const uint8_t* secret, const uint8_t* path, uint8_t path_len, uint8_t extra_type, const uint8_t*extra, size_t extra_len);
|
||||
Packet* createPathReturn(const Identity& dest, const uint8_t* secret, const uint8_t* path, uint8_t path_len, uint8_t extra_type, const uint8_t*extra, size_t extra_len);
|
||||
Packet* createRawData(const uint8_t* data, size_t len);
|
||||
|
||||
@@ -52,6 +52,10 @@ public:
|
||||
virtual void onAfterTransmit() { }
|
||||
virtual void reboot() = 0;
|
||||
virtual void powerOff() { /* no op */ }
|
||||
// Called by example setup() functions to signal that boot is complete.
|
||||
// Boards may override to stop a boot-indicator LED sequence or similar.
|
||||
// Default no-op: boards that don't care need not implement anything.
|
||||
virtual void onBootComplete() { /* no op */ }
|
||||
virtual void sleep(uint32_t secs) { /* no op */ }
|
||||
virtual uint32_t getGpio() { return 0; }
|
||||
virtual void setGpio(uint32_t values) {}
|
||||
|
||||
@@ -38,19 +38,19 @@ mesh::Packet* BaseChatMesh::createSelfAdvert(const char* name, double lat, doubl
|
||||
return createAdvert(self_id, app_data, app_data_len);
|
||||
}
|
||||
|
||||
void BaseChatMesh::sendAckTo(const ContactInfo& dest, uint32_t ack_hash) {
|
||||
void BaseChatMesh::sendAckTo(const ContactInfo& dest, const uint8_t* ack_hash, uint8_t ack_len) {
|
||||
if (dest.out_path_len == OUT_PATH_UNKNOWN) {
|
||||
mesh::Packet* ack = createAck(ack_hash);
|
||||
mesh::Packet* ack = createAck(ack_hash, ack_len);
|
||||
if (ack) sendFloodScoped(dest, ack, TXT_ACK_DELAY);
|
||||
} else {
|
||||
uint32_t d = TXT_ACK_DELAY;
|
||||
if (getExtraAckTransmitCount() > 0) {
|
||||
mesh::Packet* a1 = createMultiAck(ack_hash, 1);
|
||||
mesh::Packet* a1 = createMultiAck(ack_hash, ack_len, 1);
|
||||
if (a1) sendDirect(a1, dest.out_path, dest.out_path_len, d);
|
||||
d += 300;
|
||||
}
|
||||
|
||||
mesh::Packet* a2 = createAck(ack_hash);
|
||||
mesh::Packet* a2 = createAck(ack_hash, ack_len);
|
||||
if (a2) sendDirect(a2, dest.out_path, dest.out_path_len, d);
|
||||
}
|
||||
}
|
||||
@@ -218,16 +218,20 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender
|
||||
from.lastmod = getRTCClock()->getCurrentTime(); // update last heard time
|
||||
onMessageRecv(from, packet, timestamp, (const char *) &data[5]); // let UI know
|
||||
|
||||
uint32_t ack_hash; // calc truncated hash of the message timestamp + text + sender pub_key, to prove to sender that we got it
|
||||
mesh::Utils::sha256((uint8_t *) &ack_hash, 4, data, 5 + strlen((char *)&data[5]), from.id.pub_key, PUB_KEY_SIZE);
|
||||
int text_len = strlen((char *)&data[5]);
|
||||
uint8_t ack_hash[6]; // calc truncated hash of the message timestamp + text + sender pub_key, to prove to sender that we got it
|
||||
mesh::Utils::sha256(ack_hash, 4, data, 5 + text_len, from.id.pub_key, PUB_KEY_SIZE);
|
||||
// NEW: append (potential) extended attempt byte (to make packethash unique)
|
||||
ack_hash[4] = data[5 + text_len + 1];
|
||||
getRNG()->random(&ack_hash[5], 1); // make 6th byte random
|
||||
|
||||
if (packet->isRouteFlood()) {
|
||||
// let this sender know path TO here, so they can use sendDirect(), and ALSO encode the ACK
|
||||
mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len,
|
||||
PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4);
|
||||
PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 6);
|
||||
if (path) sendFloodScoped(from, path, TXT_ACK_DELAY);
|
||||
} else {
|
||||
sendAckTo(from, ack_hash);
|
||||
sendAckTo(from, ack_hash, 6);
|
||||
}
|
||||
} else if (flags == TXT_TYPE_CLI_DATA) {
|
||||
onCommandDataRecv(from, packet, timestamp, (const char *) &data[5]); // let UI know
|
||||
@@ -254,7 +258,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender
|
||||
PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4);
|
||||
if (path) sendFloodScoped(from, path, TXT_ACK_DELAY);
|
||||
} else {
|
||||
sendAckTo(from, ack_hash);
|
||||
sendAckTo(from, (uint8_t *) &ack_hash);
|
||||
}
|
||||
} else {
|
||||
MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported message type: %u", (uint32_t) flags);
|
||||
|
||||
@@ -72,7 +72,7 @@ class BaseChatMesh : public mesh::Mesh {
|
||||
ConnectionInfo connections[MAX_CONNECTIONS];
|
||||
|
||||
mesh::Packet* composeMsgPacket(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char *text, uint32_t& expected_ack);
|
||||
void sendAckTo(const ContactInfo& dest, uint32_t ack_hash);
|
||||
void sendAckTo(const ContactInfo& dest, const uint8_t* ack_hash, uint8_t ack_len=4);
|
||||
|
||||
protected:
|
||||
BaseChatMesh(mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::PacketManager& mgr, mesh::MeshTables& tables)
|
||||
|
||||
@@ -547,7 +547,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
_prefs->disable_fwd = memcmp(&config[7], "off", 3) == 0;
|
||||
savePrefs();
|
||||
strcpy(reply, _prefs->disable_fwd ? "OK - repeat is now OFF" : "OK - repeat is now ON");
|
||||
#if defined(USE_SX1262) || defined(USE_SX1268)
|
||||
#if defined(USE_SX1262) || defined(USE_SX1268) || defined(USE_LR1110)
|
||||
} else if (memcmp(config, "radio.rxgain ", 13) == 0) {
|
||||
_prefs->rx_boosted_gain = memcmp(&config[13], "on", 2) == 0;
|
||||
strcpy(reply, "OK");
|
||||
@@ -582,21 +582,21 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
strcpy(reply, "OK");
|
||||
} else if (memcmp(config, "rxdelay ", 8) == 0) {
|
||||
float db = atof(&config[8]);
|
||||
if (db >= 0) {
|
||||
if (db >= 0 && db <= 20.0f) {
|
||||
_prefs->rx_delay_base = db;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Error, cannot be negative");
|
||||
strcpy(reply, "Error, must be 0-20");
|
||||
}
|
||||
} else if (memcmp(config, "txdelay ", 8) == 0) {
|
||||
float f = atof(&config[8]);
|
||||
if (f >= 0) {
|
||||
if (f >= 0 && f <= 2.0f) {
|
||||
_prefs->tx_delay_factor = f;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Error, cannot be negative");
|
||||
strcpy(reply, "Error, must be 0-2");
|
||||
}
|
||||
} else if (memcmp(config, "flood.max ", 10) == 0) {
|
||||
uint8_t m = atoi(&config[10]);
|
||||
@@ -609,12 +609,12 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
}
|
||||
} else if (memcmp(config, "direct.txdelay ", 15) == 0) {
|
||||
float f = atof(&config[15]);
|
||||
if (f >= 0) {
|
||||
if (f >= 0 && f <= 2.0f) {
|
||||
_prefs->direct_tx_delay_factor = f;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Error, cannot be negative");
|
||||
strcpy(reply, "Error, must be 0-2");
|
||||
}
|
||||
} else if (memcmp(config, "owner.info ", 11) == 0) {
|
||||
config += 11;
|
||||
@@ -769,7 +769,7 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->node_lat));
|
||||
} else if (memcmp(config, "lon", 3) == 0) {
|
||||
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->node_lon));
|
||||
#if defined(USE_SX1262) || defined(USE_SX1268)
|
||||
#if defined(USE_SX1262) || defined(USE_SX1268) || defined(USE_LR1110)
|
||||
} else if (memcmp(config, "radio.rxgain", 12) == 0) {
|
||||
sprintf(reply, "> %s", _prefs->rx_boosted_gain ? "on" : "off");
|
||||
#endif
|
||||
@@ -895,9 +895,76 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
}
|
||||
}
|
||||
|
||||
static char* skipSpaces(char* s) {
|
||||
while (*s == ' ') s++;
|
||||
return s;
|
||||
}
|
||||
|
||||
static void rtrimSpaces(char* s) {
|
||||
char* e = s + strlen(s);
|
||||
while (e > s && e[-1] == ' ') *--e = '\0';
|
||||
}
|
||||
|
||||
static char* takeToken(char** cursor) {
|
||||
char* p = skipSpaces(*cursor);
|
||||
if (*p == '\0') { *cursor = p; return nullptr; }
|
||||
char* tok = p;
|
||||
while (*p && *p != ' ') p++;
|
||||
if (*p) *p++ = '\0';
|
||||
*cursor = p;
|
||||
return tok;
|
||||
}
|
||||
|
||||
static char* splitNameJump(char* tok) {
|
||||
for (char* q = tok; *q; q++) {
|
||||
if (*q == '|' || *q == ',') {
|
||||
*q = '\0';
|
||||
char* jump = skipSpaces(q + 1);
|
||||
rtrimSpaces(jump);
|
||||
return jump;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static bool processRegionDefSegment(RegionMap* map, char* tok, RegionEntry** cursor, char* reply) {
|
||||
char* jump = splitNameJump(tok);
|
||||
char* name = skipSpaces(tok);
|
||||
if (*name == '\0') { snprintf(reply, 160, "Err - empty name"); return false; }
|
||||
if (jump && *jump == '\0') { snprintf(reply, 160, "Err - empty jump"); return false; }
|
||||
|
||||
RegionEntry* r = map->putRegion(name, (*cursor)->id);
|
||||
if (r == NULL) { snprintf(reply, 160, "Err - put failed: %s", name); return false; }
|
||||
r->flags = 0;
|
||||
|
||||
if (jump) {
|
||||
RegionEntry* j = map->findByNamePrefix(jump);
|
||||
if (j == NULL) { snprintf(reply, 160, "Err - unknown jump: %s", jump); return false; }
|
||||
*cursor = j;
|
||||
} else {
|
||||
*cursor = r;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CommonCLI::handleRegionCmd(char* command, char* reply) {
|
||||
reply[0] = 0;
|
||||
|
||||
// `region def`: must run before parseTextParts mutates the buffer
|
||||
char* cmd = skipSpaces(command);
|
||||
if (strncmp(cmd, "region def", 10) == 0 && (cmd[10] == ' ' || cmd[10] == '\0')) {
|
||||
char* payload = skipSpaces(cmd + 10);
|
||||
rtrimSpaces(payload);
|
||||
if (*payload == '\0') { snprintf(reply, 160, "Err - empty def"); return; }
|
||||
|
||||
RegionEntry* cursor = &_region_map->getWildcard();
|
||||
for (char* tok; (tok = takeToken(&payload)) != nullptr; ) {
|
||||
if (!processRegionDefSegment(_region_map, tok, &cursor, reply)) return;
|
||||
}
|
||||
_region_map->exportTo(reply, 160);
|
||||
return;
|
||||
}
|
||||
|
||||
const char* parts[4];
|
||||
int n = mesh::Utils::parseTextParts(command, parts, 4, ' ');
|
||||
if (n == 1) {
|
||||
|
||||
@@ -67,7 +67,7 @@ public:
|
||||
/*
|
||||
* The NRF52 has an internal DC/DC regulator that allows increased efficiency
|
||||
* compared to the LDO regulator. For being able to use it, the module/board
|
||||
* needs to have the required inductors and and capacitors populated. If the
|
||||
* needs to have the required inductors and capacitors populated. If the
|
||||
* hardware requirements are met, this subclass can be used to enable the DC/DC
|
||||
* regulator.
|
||||
*/
|
||||
|
||||
@@ -6,22 +6,17 @@
|
||||
#include <FS.h>
|
||||
#endif
|
||||
|
||||
#define MAX_PACKET_HASHES 128
|
||||
#define MAX_PACKET_ACKS 64
|
||||
#define MAX_PACKET_HASHES (128+32)
|
||||
|
||||
class SimpleMeshTables : public mesh::MeshTables {
|
||||
uint8_t _hashes[MAX_PACKET_HASHES*MAX_HASH_SIZE];
|
||||
int _next_idx;
|
||||
uint32_t _acks[MAX_PACKET_ACKS];
|
||||
int _next_ack_idx;
|
||||
uint32_t _direct_dups, _flood_dups;
|
||||
|
||||
public:
|
||||
SimpleMeshTables() {
|
||||
memset(_hashes, 0, sizeof(_hashes));
|
||||
_next_idx = 0;
|
||||
memset(_acks, 0, sizeof(_acks));
|
||||
_next_ack_idx = 0;
|
||||
_direct_dups = _flood_dups = 0;
|
||||
}
|
||||
|
||||
@@ -29,37 +24,14 @@ public:
|
||||
void restoreFrom(File f) {
|
||||
f.read(_hashes, sizeof(_hashes));
|
||||
f.read((uint8_t *) &_next_idx, sizeof(_next_idx));
|
||||
f.read((uint8_t *) &_acks[0], sizeof(_acks));
|
||||
f.read((uint8_t *) &_next_ack_idx, sizeof(_next_ack_idx));
|
||||
}
|
||||
void saveTo(File f) {
|
||||
f.write(_hashes, sizeof(_hashes));
|
||||
f.write((const uint8_t *) &_next_idx, sizeof(_next_idx));
|
||||
f.write((const uint8_t *) &_acks[0], sizeof(_acks));
|
||||
f.write((const uint8_t *) &_next_ack_idx, sizeof(_next_ack_idx));
|
||||
}
|
||||
#endif
|
||||
|
||||
bool hasSeen(const mesh::Packet* packet) override {
|
||||
if (packet->getPayloadType() == PAYLOAD_TYPE_ACK) {
|
||||
uint32_t ack;
|
||||
memcpy(&ack, packet->payload, 4);
|
||||
for (int i = 0; i < MAX_PACKET_ACKS; i++) {
|
||||
if (ack == _acks[i]) {
|
||||
if (packet->isRouteDirect()) {
|
||||
_direct_dups++; // keep some stats
|
||||
} else {
|
||||
_flood_dups++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
_acks[_next_ack_idx] = ack;
|
||||
_next_ack_idx = (_next_ack_idx + 1) % MAX_PACKET_ACKS; // cyclic table
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t hash[MAX_HASH_SIZE];
|
||||
packet->calculatePacketHash(hash);
|
||||
|
||||
@@ -81,25 +53,14 @@ public:
|
||||
}
|
||||
|
||||
void clear(const mesh::Packet* packet) override {
|
||||
if (packet->getPayloadType() == PAYLOAD_TYPE_ACK) {
|
||||
uint32_t ack;
|
||||
memcpy(&ack, packet->payload, 4);
|
||||
for (int i = 0; i < MAX_PACKET_ACKS; i++) {
|
||||
if (ack == _acks[i]) {
|
||||
_acks[i] = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
uint8_t hash[MAX_HASH_SIZE];
|
||||
packet->calculatePacketHash(hash);
|
||||
uint8_t hash[MAX_HASH_SIZE];
|
||||
packet->calculatePacketHash(hash);
|
||||
|
||||
uint8_t* sp = _hashes;
|
||||
for (int i = 0; i < MAX_PACKET_HASHES; i++, sp += MAX_HASH_SIZE) {
|
||||
if (memcmp(hash, sp, MAX_HASH_SIZE) == 0) {
|
||||
memset(sp, 0, MAX_HASH_SIZE);
|
||||
break;
|
||||
}
|
||||
uint8_t* sp = _hashes;
|
||||
for (int i = 0; i < MAX_PACKET_HASHES; i++, sp += MAX_HASH_SIZE) {
|
||||
if (memcmp(hash, sp, MAX_HASH_SIZE) == 0) {
|
||||
memset(sp, 0, MAX_HASH_SIZE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,9 @@ static Adafruit_BMP085 BMP085;
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_AHTX0
|
||||
#ifndef TELEM_AHTX_ADDRESS
|
||||
#define TELEM_AHTX_ADDRESS 0x38 // AHT10, AHT20 temperature and humidity sensor I2C address
|
||||
#endif
|
||||
#include <Adafruit_AHTX0.h>
|
||||
static Adafruit_AHTX0 AHTX0;
|
||||
#endif
|
||||
@@ -57,7 +59,9 @@ static Adafruit_SHTC3 SHTC3;
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_SHT4X
|
||||
#ifndef TELEM_SHT4X_ADDRESS
|
||||
#define TELEM_SHT4X_ADDRESS 0x44
|
||||
#endif
|
||||
#include <SensirionI2cSht4x.h>
|
||||
static SensirionI2cSht4x SHT4X;
|
||||
#endif
|
||||
@@ -82,19 +86,25 @@ static Adafruit_INA3221 INA3221;
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_INA219
|
||||
#ifndef TELEM_INA219_ADDRESS
|
||||
#define TELEM_INA219_ADDRESS 0x40 // INA219 single channel current sensor I2C address
|
||||
#endif
|
||||
#include <Adafruit_INA219.h>
|
||||
static Adafruit_INA219 INA219(TELEM_INA219_ADDRESS);
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_INA260
|
||||
#ifndef TELEM_INA260_ADDRESS
|
||||
#define TELEM_INA260_ADDRESS 0x41 // INA260 single channel current sensor I2C address
|
||||
#endif
|
||||
#include <Adafruit_INA260.h>
|
||||
static Adafruit_INA260 INA260;
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_INA226
|
||||
#ifndef TELEM_INA226_ADDRESS
|
||||
#define TELEM_INA226_ADDRESS 0x44
|
||||
#endif
|
||||
#define TELEM_INA226_SHUNT_VALUE 0.100
|
||||
#define TELEM_INA226_MAX_AMP 0.8
|
||||
#include <INA226.h>
|
||||
@@ -102,19 +112,25 @@ static INA226 INA226(TELEM_INA226_ADDRESS, TELEM_WIRE);
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_MLX90614
|
||||
#ifndef TELEM_MLX90614_ADDRESS
|
||||
#define TELEM_MLX90614_ADDRESS 0x5A // MLX90614 IR temperature sensor I2C address
|
||||
#endif
|
||||
#include <Adafruit_MLX90614.h>
|
||||
static Adafruit_MLX90614 MLX90614;
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_VL53L0X
|
||||
#ifndef TELEM_VL53L0X_ADDRESS
|
||||
#define TELEM_VL53L0X_ADDRESS 0x29 // VL53L0X time-of-flight distance sensor I2C address
|
||||
#endif
|
||||
#include <Adafruit_VL53L0X.h>
|
||||
static Adafruit_VL53L0X VL53L0X;
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_RAK12035
|
||||
#ifndef TELEM_RAK12035_ADDRESS
|
||||
#define TELEM_RAK12035_ADDRESS 0x20 // RAK12035 Soil Moisture sensor I2C address
|
||||
#endif
|
||||
#include "RAK12035_SoilMoisture.h"
|
||||
static RAK12035_SoilMoisture RAK12035;
|
||||
#endif
|
||||
@@ -127,7 +143,9 @@ static RAK12035_SoilMoisture RAK12035;
|
||||
static uint32_t gpsResetPin = 0;
|
||||
static bool i2cGPSFlag = false;
|
||||
static bool serialGPSFlag = false;
|
||||
#ifndef TELEM_RAK12500_ADDRESS
|
||||
#define TELEM_RAK12500_ADDRESS 0x42 //RAK12500 Ublox GPS via i2c
|
||||
#endif
|
||||
#include <SparkFun_u-blox_GNSS_Arduino_Library.h>
|
||||
static SFE_UBLOX_GNSS ublox_GNSS;
|
||||
|
||||
|
||||
@@ -67,10 +67,10 @@ void RAK12035_SoilMoisture::setup(TwoWire &i2c)
|
||||
// setup() and that the bus has been initialized externally (Wire.begin()).
|
||||
// It uses the passed in I2C Address (default 0x20)
|
||||
//
|
||||
// *** This code does not supprt three sensors ***
|
||||
// *** This code does not support three sensors ***
|
||||
// The RAK12023 has three connectors, but each of the sensors attached must
|
||||
// all have a different I2C addresses.
|
||||
// This code has a function to set the I2C adress of a sensor
|
||||
// This code has a function to set the I2C address of a sensor
|
||||
// and currently only supports one address 0x20 (the default).
|
||||
// To support three sensors, EnvironmentSensorManager would need to be modified
|
||||
// to support multiple instances of the RAK12035_SoilMoisture class,
|
||||
@@ -78,7 +78,7 @@ void RAK12035_SoilMoisture::setup(TwoWire &i2c)
|
||||
// The begin() function would need to be modified to loop through the three addresses
|
||||
//
|
||||
// DEBUG STATEMENTS: Can be enabled by uncommenting or adding:
|
||||
// File: varients/rak4631 platformio.ini
|
||||
// File: variants/rak4631 platformio.ini
|
||||
// Section example: [env:RAK_4631_companion_radio_ble]
|
||||
// Enable Debug statements: -D MESH_DEBUG=1
|
||||
//
|
||||
@@ -107,7 +107,7 @@ bool RAK12035_SoilMoisture::begin(uint8_t addr)
|
||||
* Change the value to 1 in the RAK12035_SoilMoisture.h file
|
||||
*
|
||||
* Calibration Procedure:
|
||||
* 1) Flash the the Calibration version of the firmware.
|
||||
* 1) Flash the Calibration version of the firmware.
|
||||
* 2) Leave the sensor dry, power up the device.
|
||||
* 3) After detecting the RAK12035 this firmware will display calibration data on Channel 3
|
||||
*
|
||||
|
||||
@@ -846,7 +846,7 @@ void OLEDDisplay::drawLogBuffer(uint16_t xMove, uint16_t yMove) {
|
||||
if (this->logBuffer[i] == 10) {
|
||||
length++;
|
||||
// Draw string on line `line` from lastPos to length
|
||||
// Passing 0 as the lenght because we are in TEXT_ALIGN_LEFT
|
||||
// Passing 0 as the length because we are in TEXT_ALIGN_LEFT
|
||||
drawStringInternal(xMove, yMove + (line++) * lineHeight, &this->logBuffer[lastPos], length, 0, false);
|
||||
// Remember last pos
|
||||
lastPos = i;
|
||||
|
||||
@@ -60,7 +60,7 @@ private:
|
||||
};
|
||||
|
||||
#else
|
||||
#error "Unkown operating system"
|
||||
#error "Unknown operating system"
|
||||
#endif
|
||||
|
||||
#include "OLEDDisplayFonts.h"
|
||||
@@ -160,7 +160,7 @@ class OLEDDisplay : public Print {
|
||||
#elif __MBED__
|
||||
class OLEDDisplay : public Stream {
|
||||
#else
|
||||
#error "Unkown operating system"
|
||||
#error "Unknown operating system"
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
#pragma once
|
||||
|
||||
#include "DisplayDriver.h"
|
||||
#include <U8g2lib.h>
|
||||
#include <Wire.h>
|
||||
|
||||
#ifndef DISPLAY_ADDRESS
|
||||
#define DISPLAY_ADDRESS 0x3C
|
||||
#endif
|
||||
|
||||
#ifndef OLED_WIDTH
|
||||
#define OLED_WIDTH 72
|
||||
#endif
|
||||
|
||||
#ifndef OLED_HEIGHT
|
||||
#define OLED_HEIGHT 40
|
||||
#endif
|
||||
|
||||
class U8g2Display : public DisplayDriver {
|
||||
// U8g2 constructor for SSD1306/SSD1315 72×40 panel — handles all
|
||||
// GDDRAM column/page offsets, SETMULTIPLEX, SETDISPLAYOFFSET internally
|
||||
U8G2_SSD1306_72X40_ER_F_HW_I2C _u8g2;
|
||||
bool _isOn;
|
||||
uint8_t _drawColor;
|
||||
|
||||
// Font metrics for current font (cached on setTextSize)
|
||||
uint8_t _fontAscent;
|
||||
uint8_t _fontHeight;
|
||||
|
||||
void applyFont(int sz) {
|
||||
if (sz >= 2) {
|
||||
_u8g2.setFont(u8g2_font_6x10_mr); // slightly larger font for better readability. TODO: more font sizes?
|
||||
} else {
|
||||
_u8g2.setFont(u8g2_font_5x7_mr);
|
||||
}
|
||||
_fontAscent = _u8g2.getAscent();
|
||||
_fontHeight = _u8g2.getAscent() - _u8g2.getDescent();
|
||||
}
|
||||
|
||||
public:
|
||||
U8g2Display() : DisplayDriver(OLED_WIDTH, OLED_HEIGHT),
|
||||
_u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE),
|
||||
_isOn(false), _drawColor(1), _fontAscent(5), _fontHeight(6) {}
|
||||
|
||||
bool begin() {
|
||||
// Wire must already be initialised by board.begin() before this is called
|
||||
bool ok = _u8g2.begin();
|
||||
if (ok) {
|
||||
_u8g2.setI2CAddress(DISPLAY_ADDRESS * 2); // U8g2 uses 8-bit address
|
||||
_u8g2.setFontPosTop(); // y coordinate = top of text, not baseline
|
||||
_u8g2.setFontMode(1); // transparent background
|
||||
applyFont(1); // default to compact font
|
||||
_isOn = true;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool isOn() override { return _isOn; }
|
||||
|
||||
void turnOn() override {
|
||||
_u8g2.setPowerSave(0);
|
||||
_isOn = true;
|
||||
}
|
||||
|
||||
void turnOff() override {
|
||||
_u8g2.setPowerSave(1);
|
||||
_isOn = false;
|
||||
}
|
||||
|
||||
void clear() override {
|
||||
_u8g2.clearBuffer();
|
||||
_u8g2.sendBuffer();
|
||||
}
|
||||
|
||||
void startFrame(Color bkg = DARK) override {
|
||||
_u8g2.clearBuffer();
|
||||
_drawColor = 1;
|
||||
_u8g2.setDrawColor(1);
|
||||
applyFont(1);
|
||||
}
|
||||
|
||||
void setTextSize(int sz) override {
|
||||
applyFont(sz);
|
||||
}
|
||||
|
||||
void setColor(Color c) override {
|
||||
_drawColor = (c != DARK) ? 1 : 0;
|
||||
_u8g2.setDrawColor(_drawColor);
|
||||
}
|
||||
|
||||
void setCursor(int x, int y) override {
|
||||
_cursorX = x;
|
||||
_cursorY = y;
|
||||
}
|
||||
|
||||
void print(const char* str) override {
|
||||
_u8g2.setDrawColor(_drawColor);
|
||||
_u8g2.drawStr(_cursorX, _cursorY, str);
|
||||
}
|
||||
|
||||
void fillRect(int x, int y, int w, int h) override {
|
||||
_u8g2.setDrawColor(_drawColor);
|
||||
_u8g2.drawBox(x, y, w, h);
|
||||
}
|
||||
|
||||
void drawRect(int x, int y, int w, int h) override {
|
||||
_u8g2.setDrawColor(_drawColor);
|
||||
_u8g2.drawFrame(x, y, w, h);
|
||||
}
|
||||
|
||||
void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override {
|
||||
_u8g2.setDrawColor(1);
|
||||
_u8g2.drawXBM(x, y, w, h, bits);
|
||||
}
|
||||
|
||||
uint16_t getTextWidth(const char* str) override {
|
||||
return _u8g2.getStrWidth(str);
|
||||
}
|
||||
|
||||
void endFrame() override {
|
||||
_u8g2.sendBuffer();
|
||||
}
|
||||
|
||||
private:
|
||||
int _cursorX = 0;
|
||||
int _cursorY = 0;
|
||||
};
|
||||
@@ -66,7 +66,6 @@ build_flags =
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=E290Display
|
||||
-D AUTO_OFF_MILLIS=0
|
||||
-D BLE_PIN_CODE=123456 ; dynamic, random PIN
|
||||
-D BLE_DEBUG_LOGGING=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
build_src_filter = ${Heltec_E290_base.build_src_filter}
|
||||
|
||||
@@ -103,6 +103,23 @@ build_flags =
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
|
||||
[env:Heltec_t096_sensor]
|
||||
extends = Heltec_t096
|
||||
build_flags =
|
||||
${Heltec_t096.build_flags}
|
||||
-D ADVERT_NAME='"Heltec_t096 Sensor"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D ENV_PIN_SDA=PIN_WIRE1_SDA
|
||||
-D ENV_PIN_SCL=PIN_WIRE1_SCL
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_t096.build_src_filter}
|
||||
+<../examples/simple_sensor>
|
||||
lib_deps =
|
||||
${Heltec_t096.lib_deps}
|
||||
|
||||
[env:Heltec_t096_companion_radio_ble]
|
||||
extends = Heltec_t096
|
||||
board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld
|
||||
|
||||
@@ -159,7 +159,7 @@ build_flags =
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D BLE_PIN_CODE=123456
|
||||
-D BLE_DEBUG_LOGGING=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
-D OFFLINE_QUEUE_SIZE=128
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_lora32_v2.build_src_filter}
|
||||
@@ -178,12 +178,12 @@ build_flags =
|
||||
${Heltec_lora32_v2.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D MAX_CONTACTS=100
|
||||
-D MAX_CONTACTS=160
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D WIFI_DEBUG_LOGGING=1
|
||||
-D WIFI_SSID='"myssid"'
|
||||
-D WIFI_PWD='"mypwd"'
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
-D OFFLINE_QUEUE_SIZE=128
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_lora32_v2.build_src_filter}
|
||||
|
||||
@@ -21,11 +21,11 @@ public:
|
||||
#endif
|
||||
|
||||
uint16_t getBattMilliVolts() override {
|
||||
// Please read befor going further ;)
|
||||
// Please read before going further ;)
|
||||
// https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging
|
||||
|
||||
// We can't drive VBAT_ENABLE to HIGH as long
|
||||
// as we don't know wether we are charging or not ...
|
||||
// as we don't know whether we are charging or not ...
|
||||
// this is a 3mA loss (4/1500)
|
||||
digitalWrite(VBAT_ENABLE, LOW);
|
||||
int adcvalue = 0;
|
||||
|
||||
@@ -29,11 +29,11 @@ public:
|
||||
#endif
|
||||
|
||||
uint16_t getBattMilliVolts() override {
|
||||
// Please read befor going further ;)
|
||||
// Please read before going further ;)
|
||||
// https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging
|
||||
|
||||
// We can't drive VBAT_ENABLE to HIGH as long
|
||||
// as we don't know wether we are charging or not ...
|
||||
// as we don't know whether we are charging or not ...
|
||||
// this is a 3mA loss (4/1500)
|
||||
digitalWrite(VBAT_ENABLE, LOW);
|
||||
int adcvalue = 0;
|
||||
|
||||
@@ -29,11 +29,11 @@ public:
|
||||
#endif
|
||||
|
||||
uint16_t getBattMilliVolts() override {
|
||||
// Please read befor going further ;)
|
||||
// Please read before going further ;)
|
||||
// https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging
|
||||
|
||||
// We can't drive VBAT_ENABLE to HIGH as long
|
||||
// as we don't know wether we are charging or not ...
|
||||
// as we don't know whether we are charging or not ...
|
||||
// this is a 3mA loss (4/1500)
|
||||
digitalWrite(VBAT_ENABLE, LOW);
|
||||
int adcvalue = 0;
|
||||
|
||||
@@ -42,7 +42,7 @@ build_flags =
|
||||
-D MAX_CONTACTS=160
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D BLE_PIN_CODE=123456
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
-D OFFLINE_QUEUE_SIZE=128
|
||||
; -D BLE_DEBUG_LOGGING=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
|
||||
@@ -41,7 +41,7 @@ build_flags =
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D BLE_PIN_CODE=123456
|
||||
; -D BLE_DEBUG_LOGGING=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
-D OFFLINE_QUEUE_SIZE=128
|
||||
; -D RADIOLIB_DEBUG_BASIC=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
|
||||
|
||||
#include "TechoCardBoard.h"
|
||||
|
||||
#ifdef LILYGO_TECHO_CARD
|
||||
|
||||
Adafruit_NeoPixel Led_A(1, WS2812_DATA_2, NEO_GRB + NEO_KHZ800);
|
||||
Adafruit_NeoPixel Led_B(1, WS2812_DATA_3, NEO_GRB + NEO_KHZ800);
|
||||
Adafruit_NeoPixel Led_C(1, WS2812_DATA_1, NEO_GRB + NEO_KHZ800);
|
||||
|
||||
Adafruit_NeoPixel *Led[] =
|
||||
{
|
||||
&Led_A,
|
||||
&Led_B,
|
||||
&Led_C,
|
||||
};
|
||||
|
||||
|
||||
void TechoCardBoard::begin() {
|
||||
NRF52BoardDCDC::begin();
|
||||
Wire.begin();
|
||||
|
||||
for (uint8_t i = 0; i < sizeof(Led) / sizeof(Led[0]); i++)
|
||||
{
|
||||
Led[i]->begin();
|
||||
delay(3); // allow the LEDs to initialise, otherwise they can get stuck
|
||||
Led[i]->setPixelColor(0, Led[i]->Color(0, 0, 0));
|
||||
Led[i]->show();
|
||||
}
|
||||
|
||||
// put IMU20948 to sleep
|
||||
// see https://product.tdk.com/system/files/dam/doc/product/sensor/mortion-inertial/imu/data_sheet/ds-000189-icm-20948-v1.5.pdf
|
||||
Wire.beginTransmission(0x68);
|
||||
Wire.write(0x06); // PWR_MGMT_1 register
|
||||
Wire.write(0x40); // set SLEEP bit
|
||||
Wire.endTransmission();
|
||||
|
||||
}
|
||||
|
||||
uint16_t TechoCardBoard::getBattMilliVolts() {
|
||||
int adcvalue = 0;
|
||||
|
||||
analogReference(AR_INTERNAL_3_0);
|
||||
analogReadResolution(12);
|
||||
|
||||
digitalWrite(PIN_BAT_CTL, HIGH); // enable vbat vdiv
|
||||
delay(10);
|
||||
|
||||
// ADC range is 0..3000mV and resolution is 12-bit (0..4095)
|
||||
adcvalue = analogRead(PIN_VBAT_READ);
|
||||
digitalWrite(PIN_BAT_CTL, LOW);
|
||||
|
||||
// Convert the raw value to compensated mv, taking the resistor-
|
||||
// divider into account (providing the actual LIPO voltage)
|
||||
return (uint16_t)((float)adcvalue * REAL_VBAT_MV_PER_LSB);
|
||||
}
|
||||
|
||||
void TechoCardBoard::onBeforeTransmit() {
|
||||
Led_A.setPixelColor(0, 20, 20, 20); // turn TX LED on
|
||||
Led_A.show();
|
||||
}
|
||||
|
||||
void TechoCardBoard::onAfterTransmit() {
|
||||
Led_A.setPixelColor(0, 0, 0, 0); // turn TX LED off
|
||||
Led_A.show();
|
||||
}
|
||||
|
||||
void TechoCardBoard::toggleTorch() {
|
||||
if (!_torchStatus) {
|
||||
Led_C.setPixelColor(0, 255, 255, 255);
|
||||
Led_C.show();
|
||||
_torchStatus = true;
|
||||
} else {
|
||||
Led_C.setPixelColor(0, 0, 0, 0);
|
||||
Led_C.show();
|
||||
_torchStatus = false;
|
||||
}
|
||||
}
|
||||
|
||||
void TechoCardBoard::turnOffLeds() {
|
||||
for (uint8_t i = 0; i < sizeof(Led) / sizeof(*Led); i++)
|
||||
{
|
||||
Led[i]->setPixelColor(0, 0, 0, 0);
|
||||
Led[i]->show();
|
||||
}
|
||||
}
|
||||
|
||||
void TechoCardBoard::powerOff() {
|
||||
nrf_gpio_cfg_sense_input(BUTTON_PIN, NRF_GPIO_PIN_PULLUP, NRF_GPIO_PIN_SENSE_LOW);
|
||||
turnOffLeds();
|
||||
digitalWrite(PIN_PWR_EN, LOW);
|
||||
sd_power_system_off();
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <MeshCore.h>
|
||||
#include <Arduino.h>
|
||||
#include <helpers/NRF52Board.h>
|
||||
#include <Adafruit_Neopixel.h>
|
||||
|
||||
// built-ins
|
||||
#define VBAT_MV_PER_LSB (0.73242188F) // 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096
|
||||
|
||||
#define VBAT_DIVIDER (0.5F) // Even voltage divider on VBAT
|
||||
#define VBAT_DIVIDER_COMP (2.0F) // Compensation factor for the VBAT divider
|
||||
|
||||
#define PIN_VBAT_READ (2)
|
||||
#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB)
|
||||
|
||||
class TechoCardBoard : public NRF52BoardDCDC {
|
||||
bool _torchStatus = false;
|
||||
public:
|
||||
TechoCardBoard() : NRF52Board("TECHO_OTA") {}
|
||||
void begin();
|
||||
uint16_t getBattMilliVolts() override;
|
||||
void onBeforeTransmit(void) override;
|
||||
void onAfterTransmit(void) override;
|
||||
|
||||
|
||||
const char* getManufacturerName() const override {
|
||||
return "LilyGo T-Echo Card";
|
||||
}
|
||||
|
||||
void powerOff() override;
|
||||
|
||||
void toggleTorch();
|
||||
void turnOffLeds();
|
||||
|
||||
};
|
||||
@@ -0,0 +1,119 @@
|
||||
[LilyGo_T-Echo_Card]
|
||||
extends = nrf52_base
|
||||
board = t-echo
|
||||
board_build.ldscript = boards/nrf52840_s140_v6.ld
|
||||
build_flags = ${nrf52_base.build_flags}
|
||||
-I variants/lilygo_techo_card
|
||||
-I src/helpers/nrf52
|
||||
-I lib/nrf52/s140_nrf52_6.1.1_API/include
|
||||
-I lib/nrf52/s140_nrf52_6.1.1_API/include/nrf52
|
||||
-D LILYGO_TECHO_CARD
|
||||
-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 HAS_NEOPIXEL=1
|
||||
-D HAS_TORCH=1
|
||||
-D DISABLE_DIAGNOSTIC_OUTPUT
|
||||
-D ENV_INCLUDE_GPS=1
|
||||
-D DISPLAY_CLASS=U8g2Display
|
||||
-D PIN_OLED_RESET=-1
|
||||
build_src_filter = ${nrf52_base.build_src_filter}
|
||||
+<helpers/*.cpp>
|
||||
+<TechoCardBoard.cpp>
|
||||
+<helpers/sensors/EnvironmentSensorManager.cpp>
|
||||
+<helpers/ui/U8g2Display.h>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<../variants/lilygo_techo_card>
|
||||
lib_deps =
|
||||
${nrf52_base.lib_deps}
|
||||
stevemarple/MicroNMEA @ ^2.0.6
|
||||
olikraus/U8g2 @ ^2.35.19
|
||||
adafruit/Adafruit NeoPixel@^1.10.0
|
||||
bakercp/CRC32 @ ^2.0.0
|
||||
debug_tool = jlink
|
||||
upload_protocol = nrfutil
|
||||
|
||||
[env:LilyGo_T-Echo_Card_repeater]
|
||||
extends = LilyGo_T-Echo_Card
|
||||
build_src_filter = ${LilyGo_T-Echo_Card.build_src_filter}
|
||||
+<../examples/simple_repeater>
|
||||
build_flags =
|
||||
${LilyGo_T-Echo_Card.build_flags}
|
||||
-D ADVERT_NAME='"T-Echo Card Repeater"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
|
||||
[env:LilyGo_T-Echo_Card_room_server]
|
||||
extends = LilyGo_T-Echo_Card
|
||||
build_src_filter = ${LilyGo_T-Echo_Card.build_src_filter}
|
||||
+<../examples/simple_room_server>
|
||||
build_flags =
|
||||
${LilyGo_T-Echo_Card.build_flags}
|
||||
-D ADVERT_NAME='"T-Echo Card Room"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
|
||||
[env:LilyGo_T-Echo_Card_companion_radio_ble]
|
||||
extends = LilyGo_T-Echo_Card
|
||||
board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld
|
||||
board_upload.maximum_size = 712704
|
||||
build_flags =
|
||||
${LilyGo_T-Echo_Card.build_flags}
|
||||
-I src/helpers/ui
|
||||
-I examples/companion_radio/ui-tiny
|
||||
-D PIN_BUZZER=38
|
||||
-D QSPIFLASH=1
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D BLE_PIN_CODE=123456
|
||||
; -D BLE_DEBUG_LOGGING=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
-D UI_RECENT_LIST_SIZE=3
|
||||
-D UI_GPS_PAGE=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
-D AUTO_SHUTDOWN_MILLIVOLTS=3300
|
||||
build_src_filter = ${LilyGo_T-Echo_Card.build_src_filter}
|
||||
+<helpers/nrf52/SerialBLEInterface.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-tiny/*.cpp>
|
||||
+<helpers/ui/buzzer.cpp>
|
||||
lib_deps =
|
||||
${LilyGo_T-Echo_Card.lib_deps}
|
||||
end2endzone/NonBlockingRTTTL@^1.3.0
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:LilyGo_T-Echo_Card_companion_radio_usb]
|
||||
extends = LilyGo_T-Echo_Card
|
||||
board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld
|
||||
board_upload.maximum_size = 712704
|
||||
build_flags =
|
||||
${LilyGo_T-Echo_Card.build_flags}
|
||||
-I src/helpers/ui
|
||||
-I examples/companion_radio/ui-tiny
|
||||
-D PIN_BUZZER=38
|
||||
-D QSPIFLASH=1
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
-D UI_RECENT_LIST_SIZE=3
|
||||
-D UI_GPS_PAGE=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
-D AUTO_SHUTDOWN_MILLIVOLTS=3300
|
||||
build_src_filter = ${LilyGo_T-Echo_Card.build_src_filter}
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-tiny/*.cpp>
|
||||
lib_deps =
|
||||
${LilyGo_T-Echo_Card.lib_deps}
|
||||
end2endzone/NonBlockingRTTTL@^1.3.0
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
@@ -0,0 +1,38 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
#include <helpers/sensors/MicroNMEALocationProvider.h>
|
||||
|
||||
TechoCardBoard board;
|
||||
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
VolatileRTCClock fallback_clock;
|
||||
AutoDiscoverRTCClock rtc_clock(fallback_clock);
|
||||
|
||||
#ifdef ENV_INCLUDE_GPS
|
||||
MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock);
|
||||
EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea);
|
||||
#else
|
||||
EnvironmentSensorManager sensors = EnvironmentSensorManager();
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
DISPLAY_CLASS display;
|
||||
MomentaryButton user_btn(PIN_USER_BTN, 1000, true);
|
||||
MomentaryButton back_btn(PIN_BUTTON2, 1000, true);
|
||||
#endif
|
||||
|
||||
bool radio_init() {
|
||||
rtc_clock.begin(Wire);
|
||||
|
||||
return radio.std_init(&SPI);
|
||||
}
|
||||
|
||||
mesh::LocalIdentity radio_new_identity() {
|
||||
RadioNoiseListener rng(radio);
|
||||
return mesh::LocalIdentity(&rng); // create new random identity
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#define RADIOLIB_STATIC_ONLY 1
|
||||
#include <RadioLib.h>
|
||||
#include <helpers/radiolib/RadioLibWrappers.h>
|
||||
#include <TechoCardBoard.h>
|
||||
#include <helpers/radiolib/CustomSX1262Wrapper.h>
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
#include <helpers/SensorManager.h>
|
||||
#include <helpers/sensors/EnvironmentSensorManager.h>
|
||||
#include <helpers/sensors/LocationProvider.h>
|
||||
#ifdef DISPLAY_CLASS
|
||||
#include <helpers/ui/U8g2Display.h>
|
||||
#include <helpers/ui/MomentaryButton.h>
|
||||
#endif
|
||||
|
||||
extern TechoCardBoard board;
|
||||
extern WRAPPER_CLASS radio_driver;
|
||||
extern AutoDiscoverRTCClock rtc_clock;
|
||||
extern EnvironmentSensorManager sensors;
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
extern DISPLAY_CLASS display;
|
||||
extern MomentaryButton user_btn;
|
||||
extern MomentaryButton back_btn;
|
||||
#endif
|
||||
|
||||
bool radio_init();
|
||||
mesh::LocalIdentity radio_new_identity();
|
||||
@@ -0,0 +1,45 @@
|
||||
#include "variant.h"
|
||||
#include "wiring_constants.h"
|
||||
#include "wiring_digital.h"
|
||||
#include "Adafruit_NeoPixel.h"
|
||||
|
||||
const int MISO = PIN_SPI_MISO;
|
||||
const int MOSI = PIN_SPI_MOSI;
|
||||
const int SCK = PIN_SPI_SCK;
|
||||
|
||||
|
||||
|
||||
const uint32_t g_ADigitalPinMap[] = {
|
||||
0xff, 0xff, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
|
||||
14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
|
||||
27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
|
||||
40, 41, 42, 43, 44, 45, 46, 47
|
||||
};
|
||||
|
||||
void initVariant() {
|
||||
// turn on 3v3 rail
|
||||
pinMode(PIN_PWR_EN, OUTPUT);
|
||||
digitalWrite(PIN_PWR_EN, HIGH);
|
||||
|
||||
// VDIV enable
|
||||
pinMode(PIN_BAT_CTL, OUTPUT);
|
||||
|
||||
// buttons
|
||||
pinMode(PIN_BUTTON1, INPUT_PULLUP);
|
||||
pinMode(PIN_BUTTON2, INPUT_PULLUP);
|
||||
|
||||
// speaker
|
||||
pinMode(SPEAKER_EN, OUTPUT);
|
||||
digitalWrite(SPEAKER_EN, LOW);
|
||||
pinMode(SPEAKER_EN_2, OUTPUT);
|
||||
digitalWrite(SPEAKER_EN_2, LOW);
|
||||
|
||||
// gps
|
||||
pinMode(PIN_GPS_STANDBY, OUTPUT);
|
||||
digitalWrite(PIN_GPS_STANDBY, HIGH);
|
||||
pinMode(PIN_GPS_EN, OUTPUT);
|
||||
digitalWrite(PIN_GPS_EN, HIGH);
|
||||
pinMode(PIN_GPS_RESET, OUTPUT);
|
||||
digitalWrite(PIN_GPS_RESET, HIGH);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* variant.h
|
||||
*
|
||||
* MIT License
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "WVariant.h"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Low frequency clock source
|
||||
|
||||
#define USE_LFXO // 32.768 kHz crystal oscillator
|
||||
#define VARIANT_MCK (64000000ul)
|
||||
|
||||
#define WIRE_INTERFACES_COUNT (1)
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Power
|
||||
|
||||
#define PIN_PWR_EN (30) // RT9080 LDO enable pin for 3v3 rail
|
||||
|
||||
#define PIN_BAT_CTL (31) // vdiv enable
|
||||
#define PIN_VBAT_READ (2)
|
||||
#define ADC_MULTIPLIER (4.90F)
|
||||
|
||||
#define ADC_RESOLUTION (14)
|
||||
#define BATTERY_SENSE_RES (12)
|
||||
#define AREF_VOLTAGE (3.0)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Number of pins
|
||||
|
||||
#define PINS_COUNT (48)
|
||||
#define NUM_DIGITAL_PINS (48)
|
||||
#define NUM_ANALOG_INPUTS (1)
|
||||
#define NUM_ANALOG_OUTPUTS (0)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// UART pin definition
|
||||
|
||||
#define PIN_SERIAL1_RX PIN_GPS_TX
|
||||
#define PIN_SERIAL1_TX PIN_GPS_RX
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// I2C pin definition
|
||||
|
||||
#define PIN_WIRE_SDA (36) // P1.04
|
||||
#define PIN_WIRE_SCL (34) // P1.02
|
||||
#define I2C_NO_RESCAN
|
||||
#define DISABLE_DS3231_PROBE // DS3231 lives at 0x68 but this board has ICM20948 at that address, causing broken clock.
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// SPI pin definition
|
||||
|
||||
#define SPI_INTERFACES_COUNT (1)
|
||||
|
||||
#define PIN_SPI_MISO (17)
|
||||
#define PIN_SPI_MOSI (15)
|
||||
#define PIN_SPI_SCK (13)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// QSPI FLASH
|
||||
|
||||
#define PIN_QSPI_SCK (4)
|
||||
#define PIN_QSPI_CS (12)
|
||||
#define PIN_QSPI_IO0 (6)
|
||||
#define PIN_QSPI_IO1 (8)
|
||||
#define PIN_QSPI_IO2 (41) // P1.09
|
||||
#define PIN_QSPI_IO3 (26)
|
||||
|
||||
#define EXTERNAL_FLASH_DEVICES ZD25WQ32CEIGR
|
||||
#define EXTERNAL_FLASH_USE_QSPI
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Builtin LEDs (only WS2812, no traditional LEDs available)
|
||||
|
||||
// WS1812 data lines
|
||||
#define WS2812_DATA_1 (39) // P1.07
|
||||
#define WS2812_DATA_2 (44) // P1.12
|
||||
#define WS2812_DATA_3 (28) // P0.28
|
||||
|
||||
#define LED_BLUE (-1)
|
||||
#define LED_BUILTIN (-1)
|
||||
#define LED_PIN LED_BUILTIN
|
||||
#define LED_STATE_ON LOW
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Builtin buttons
|
||||
|
||||
#define PIN_BUTTON1 (42) // P1.10
|
||||
#define BUTTON_PIN PIN_BUTTON1 // BUTTON A
|
||||
#define PIN_USER_BTN BUTTON_PIN
|
||||
|
||||
#define PIN_BUTTON2 (24)
|
||||
#define BUTTON_PIN2 PIN_BUTTON2 // BUTTON C
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Lora (Acsip S62F)
|
||||
|
||||
#define USE_SX1262
|
||||
#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 (40) // P1.08
|
||||
#define P_LORA_RESET (7) // P0.07
|
||||
#define P_LORA_BUSY (14) // P0.14
|
||||
#define P_LORA_NSS (11) // P0.11
|
||||
#define SX126X_RXEN (33) // P1.01
|
||||
#define SX126X_TXEN (27) // P0.27
|
||||
#define SX126X_DIO3_TCXO_VOLTAGE (1.8f)
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// GPS
|
||||
|
||||
// NOTE: these pins are defined differently to how lilygo does them but they
|
||||
// seem to work properly for how EnvironmentSensorManager operates.
|
||||
// TODO: MAYBE? migrate to board based sensor manager / add GPS_WAKE_UP to ESM
|
||||
|
||||
#define PIN_GPS_RX (21) // GPS_UART_RX in lilygo pin defs
|
||||
#define PIN_GPS_TX (19) // GPS_UART_TX in lilygo pin defs
|
||||
#define PIN_GPS_EN (25) // GPS_WAKE_UP in lilygo pin defs
|
||||
#define PIN_GPS_RESET (47) // GPS_EN in lilygo pin defs
|
||||
#define PIN_GPS_STANDBY (29) // GPS_RF_EN in lilygo pin defs
|
||||
#define PIN_GPS_PPS (23) // GPS_1PPS in lilygo pin defs
|
||||
|
||||
// buzzer - enabled in platformio.ini so it can be easily turned off if not wanted.
|
||||
// #define PIN_BUZZER (38) // P1.06
|
||||
|
||||
// microphone
|
||||
#define MICROPHONE_SCLK (35) // P1.03
|
||||
#define MICROPHONE_DATA (37) // P1.05
|
||||
|
||||
// speaker
|
||||
#define SPEAKER_EN (43) // P1.11
|
||||
#define SPEAKER_EN_2 (3) // P0.03
|
||||
#define SPEAKER_BCLK (16) // P0.16
|
||||
#define SPEAKER_DATA (20) // P0.20
|
||||
#define SPEAKER_WS_LRCK (22) // P0.22
|
||||
|
||||
// ICM20948 9dof motion sensor (accelerometer and magnetometer)
|
||||
#define ICM20948_SDA PIN_WIRE_SDA // P1.4
|
||||
#define ICM20948_SCL PIN_WIRE_SCL // P1.2
|
||||
#define ICM20948_ADDRESS 0x68
|
||||
@@ -95,7 +95,7 @@ build_flags =
|
||||
-D MAX_CONTACTS=160
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D BLE_PIN_CODE=123456
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
-D OFFLINE_QUEUE_SIZE=128
|
||||
; -D BLE_DEBUG_LOGGING=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
@@ -131,12 +131,12 @@ extends = LilyGo_TLora_V2_1_1_6
|
||||
build_flags =
|
||||
${LilyGo_TLora_V2_1_1_6.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=100
|
||||
-D MAX_CONTACTS=160
|
||||
-D MAX_GROUP_CHANNELS=8
|
||||
-D WIFI_SSID='"ssid"'
|
||||
-D WIFI_PWD='"password"'
|
||||
-D WIFI_DEBUG_LOGGING=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
-D OFFLINE_QUEUE_SIZE=128
|
||||
build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
|
||||
@@ -183,8 +183,9 @@ build_flags =
|
||||
-D RADIO_CLASS=CustomSX1262
|
||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||
-D LORA_TX_POWER=22
|
||||
-D MAX_CONTACTS=100
|
||||
-D MAX_CONTACTS=160
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D OFFLINE_QUEUE_SIZE=128
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
lib_deps =
|
||||
@@ -203,11 +204,11 @@ build_flags =
|
||||
-D RADIO_CLASS=CustomSX1262
|
||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||
-D LORA_TX_POWER=22
|
||||
-D MAX_CONTACTS=100
|
||||
-D MAX_CONTACTS=160
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D BLE_PIN_CODE=123456
|
||||
-D BLE_DEBUG_LOGGING=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
-D OFFLINE_QUEUE_SIZE=128
|
||||
-D MESH_PACKET_LOGGING=1
|
||||
-D MESH_DEBUG=1
|
||||
lib_deps =
|
||||
@@ -262,7 +263,8 @@ build_flags =
|
||||
-D RADIO_CLASS=CustomSX1268
|
||||
-D WRAPPER_CLASS=CustomSX1268Wrapper
|
||||
-D LORA_TX_POWER=22
|
||||
-D MAX_CONTACTS=100
|
||||
-D MAX_CONTACTS=160
|
||||
-D OFFLINE_QUEUE_SIZE=128
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
@@ -282,11 +284,11 @@ build_flags =
|
||||
-D RADIO_CLASS=CustomSX1268
|
||||
-D WRAPPER_CLASS=CustomSX1268Wrapper
|
||||
-D LORA_TX_POWER=22
|
||||
-D MAX_CONTACTS=100
|
||||
-D MAX_CONTACTS=160
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D BLE_PIN_CODE=123456
|
||||
-D BLE_DEBUG_LOGGING=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
-D OFFLINE_QUEUE_SIZE=128
|
||||
-D MESH_PACKET_LOGGING=1
|
||||
-D MESH_DEBUG=1
|
||||
lib_deps =
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <helpers/ESP32Board.h>
|
||||
#include <driver/rtc_io.h>
|
||||
|
||||
#ifndef P_PRIMARY_LNA_EN_ACTIVE
|
||||
#define P_PRIMARY_LNA_EN_ACTIVE LOW
|
||||
#endif
|
||||
|
||||
#ifndef P_PA1_EN_ACTIVE
|
||||
#define P_PA1_EN_ACTIVE HIGH
|
||||
#endif
|
||||
|
||||
class StationG3Board : public ESP32Board {
|
||||
void setPAModeHigh(bool enabled) {
|
||||
#ifdef P_PA1_EN
|
||||
// Station G3 PA PL1 mode: LOW/open is PA low, HIGH/short is PA high.
|
||||
digitalWrite(P_PA1_EN, enabled ? P_PA1_EN_ACTIVE : !P_PA1_EN_ACTIVE);
|
||||
#endif
|
||||
}
|
||||
|
||||
void setPrimaryLNAControl(bool enabled) {
|
||||
#ifdef P_PRIMARY_LNA_EN
|
||||
// Station G3 primary LNA mode is active-low: LOW/open is LNA on, HIGH/short is LNA off.
|
||||
digitalWrite(P_PRIMARY_LNA_EN, enabled ? P_PRIMARY_LNA_EN_ACTIVE : !P_PRIMARY_LNA_EN_ACTIVE);
|
||||
#endif
|
||||
}
|
||||
|
||||
public:
|
||||
void begin() {
|
||||
ESP32Board::begin();
|
||||
|
||||
#ifdef P_PA1_EN
|
||||
rtc_gpio_hold_dis((gpio_num_t)P_PA1_EN);
|
||||
pinMode(P_PA1_EN, OUTPUT);
|
||||
setPAModeHigh(false);
|
||||
#endif
|
||||
|
||||
#ifdef P_PRIMARY_LNA_EN
|
||||
rtc_gpio_hold_dis((gpio_num_t)P_PRIMARY_LNA_EN);
|
||||
pinMode(P_PRIMARY_LNA_EN, OUTPUT);
|
||||
setPrimaryLNAControl(true);
|
||||
#endif
|
||||
|
||||
esp_reset_reason_t reason = esp_reset_reason();
|
||||
if (reason == ESP_RST_DEEPSLEEP) {
|
||||
uint64_t wakeup_source = esp_sleep_get_ext1_wakeup_status();
|
||||
if (wakeup_source & (1ULL << P_LORA_DIO_1)) {
|
||||
startup_reason = BD_STARTUP_RX_PACKET;
|
||||
}
|
||||
|
||||
rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS);
|
||||
rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1);
|
||||
}
|
||||
}
|
||||
|
||||
void setPrimaryLNAEnable(bool enabled) {
|
||||
setPrimaryLNAControl(enabled);
|
||||
}
|
||||
|
||||
void setPrimaryPAHighPower(bool enabled) {
|
||||
setPAModeHigh(enabled);
|
||||
}
|
||||
|
||||
void onBeforeTransmit() override {
|
||||
ESP32Board::onBeforeTransmit();
|
||||
setPrimaryLNAControl(false);
|
||||
}
|
||||
|
||||
void onAfterTransmit() override {
|
||||
ESP32Board::onAfterTransmit();
|
||||
setPrimaryLNAControl(true);
|
||||
}
|
||||
|
||||
void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) {
|
||||
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
|
||||
|
||||
rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY);
|
||||
rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1);
|
||||
|
||||
rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS);
|
||||
|
||||
#ifdef P_PA1_EN
|
||||
setPAModeHigh(false);
|
||||
rtc_gpio_hold_en((gpio_num_t)P_PA1_EN);
|
||||
#endif
|
||||
|
||||
#ifdef P_PRIMARY_LNA_EN
|
||||
setPrimaryLNAControl(true);
|
||||
rtc_gpio_hold_en((gpio_num_t)P_PRIMARY_LNA_EN);
|
||||
#endif
|
||||
|
||||
if (pin_wake_btn < 0) {
|
||||
esp_sleep_enable_ext1_wakeup((1ULL << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH);
|
||||
} else {
|
||||
esp_sleep_enable_ext1_wakeup((1ULL << P_LORA_DIO_1) | (1ULL << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH);
|
||||
}
|
||||
|
||||
if (secs > 0) {
|
||||
esp_sleep_enable_timer_wakeup(secs * 1000000);
|
||||
}
|
||||
|
||||
esp_deep_sleep_start();
|
||||
}
|
||||
|
||||
uint16_t getBattMilliVolts() override {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char* getManufacturerName() const override {
|
||||
return "Station G3 ESP32";
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,160 @@
|
||||
[Station_G3_ESP32]
|
||||
extends = esp32_base
|
||||
board = station-g3-esp32
|
||||
build_flags =
|
||||
${esp32_base.build_flags}
|
||||
${sensor_base.build_flags}
|
||||
-I variants/station_g3_esp32
|
||||
-I src/helpers/ui
|
||||
-D STATION_G3_ESP32
|
||||
-D USE_SX1262
|
||||
-D RADIO_CLASS=CustomSX1262
|
||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||
-D P_LORA_DIO_1=48
|
||||
-D P_LORA_NSS=11
|
||||
-D P_LORA_RESET=21
|
||||
-D P_LORA_BUSY=47
|
||||
-D P_LORA_SCLK=12
|
||||
-D P_LORA_MISO=14
|
||||
-D P_LORA_MOSI=13
|
||||
-D P_PA1_EN=9 ; PA PL1 Mode: LOW/open is PA low, HIGH/short is PA high.
|
||||
-D P_PA1_EN_ACTIVE=HIGH
|
||||
-D P_PRIMARY_LNA_EN=10 ; Primary Slot LNA Mode: LOW/open is LNA on, HIGH/short is LNA off.
|
||||
-D P_PRIMARY_LNA_EN_ACTIVE=LOW
|
||||
-D LORA_TX_POWER=7 ; configured as 7dbm, because the final output will be ~27dbm (~0.5w) if the PA is enabled.
|
||||
-D MAX_LORA_TX_POWER=22
|
||||
; -D P_LORA_TX_LED=35
|
||||
-D PIN_BOARD_SDA=5
|
||||
-D PIN_BOARD_SCL=6
|
||||
-D PIN_USER_BTN=38
|
||||
-D PIN_GPS_RX=15
|
||||
-D PIN_GPS_TX=7
|
||||
-D SX126X_DIO2_AS_RF_SWITCH=true
|
||||
-D SX126X_DIO3_TCXO_VOLTAGE=1.8
|
||||
-D SX126X_CURRENT_LIMIT=140
|
||||
-D SX126X_RX_BOOSTED_GAIN=0
|
||||
-D DISPLAY_CLASS=SH1106Display
|
||||
build_src_filter = ${esp32_base.build_src_filter}
|
||||
+<../variants/station_g3_esp32>
|
||||
+<helpers/sensors>
|
||||
+<helpers/ui/SH1106Display.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
lib_deps =
|
||||
${esp32_base.lib_deps}
|
||||
${sensor_base.lib_deps}
|
||||
adafruit/Adafruit SH110X @ ~2.1.13
|
||||
adafruit/Adafruit GFX Library @ ^1.12.1
|
||||
|
||||
[env:Station_G3_ESP32_repeater]
|
||||
extends = Station_G3_ESP32
|
||||
build_flags =
|
||||
${Station_G3_ESP32.build_flags}
|
||||
-D ADVERT_NAME='"Station G3 ESP32 Repeater"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Station_G3_ESP32.build_src_filter}
|
||||
+<../examples/simple_repeater>
|
||||
lib_deps =
|
||||
${Station_G3_ESP32.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
[env:Station_G3_ESP32_logging_repeater]
|
||||
extends = Station_G3_ESP32
|
||||
build_flags =
|
||||
${Station_G3_ESP32.build_flags}
|
||||
-D ADVERT_NAME='"Station G3 ESP32 Logging Repeater"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
-D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Station_G3_ESP32.build_src_filter}
|
||||
+<../examples/simple_repeater>
|
||||
lib_deps =
|
||||
${Station_G3_ESP32.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
[env:Station_G3_ESP32_room_server]
|
||||
extends = Station_G3_ESP32
|
||||
build_src_filter = ${Station_G3_ESP32.build_src_filter}
|
||||
+<../examples/simple_room_server>
|
||||
build_flags =
|
||||
${Station_G3_ESP32.build_flags}
|
||||
-D ADVERT_NAME='"Station G3 ESP32 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
|
||||
lib_deps =
|
||||
${Station_G3_ESP32.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
[env:Station_G3_ESP32_companion_radio_usb]
|
||||
extends = Station_G3_ESP32
|
||||
build_flags =
|
||||
${Station_G3_ESP32.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1
|
||||
; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1
|
||||
build_src_filter = ${Station_G3_ESP32.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${Station_G3_ESP32.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Station_G3_ESP32_companion_radio_ble]
|
||||
extends = Station_G3_ESP32
|
||||
build_flags =
|
||||
${Station_G3_ESP32.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D BLE_PIN_CODE=123456
|
||||
-D BLE_DEBUG_LOGGING=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Station_G3_ESP32.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${Station_G3_ESP32.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Station_G3_ESP32_companion_radio_wifi]
|
||||
extends = Station_G3_ESP32
|
||||
build_flags =
|
||||
${Station_G3_ESP32.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D WIFI_DEBUG_LOGGING=1
|
||||
-D WIFI_SSID='"myssid"'
|
||||
-D WIFI_PWD='"mypwd"'
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Station_G3_ESP32.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${Station_G3_ESP32.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Station_G3_ESP32_kiss_modem]
|
||||
extends = Station_G3_ESP32
|
||||
build_src_filter = ${Station_G3_ESP32.build_src_filter}
|
||||
+<../examples/kiss_modem/>
|
||||
@@ -0,0 +1,46 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
|
||||
StationG3Board board;
|
||||
|
||||
#if defined(P_LORA_SCLK)
|
||||
static SPIClass spi;
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi);
|
||||
#else
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
#endif
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
ESP32RTCClock fallback_clock;
|
||||
AutoDiscoverRTCClock rtc_clock(fallback_clock);
|
||||
|
||||
#if ENV_INCLUDE_GPS
|
||||
#include <helpers/sensors/MicroNMEALocationProvider.h>
|
||||
MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock);
|
||||
EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea);
|
||||
#else
|
||||
EnvironmentSensorManager sensors;
|
||||
#endif
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
DISPLAY_CLASS display;
|
||||
MomentaryButton user_btn(PIN_USER_BTN, 1000, true);
|
||||
#endif
|
||||
|
||||
bool radio_init() {
|
||||
fallback_clock.begin();
|
||||
rtc_clock.begin(Wire);
|
||||
|
||||
#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
|
||||
}
|
||||
|
||||
mesh::LocalIdentity radio_new_identity() {
|
||||
RadioNoiseListener rng(radio);
|
||||
return mesh::LocalIdentity(&rng);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#define RADIOLIB_STATIC_ONLY 1
|
||||
#include <RadioLib.h>
|
||||
#include <helpers/radiolib/RadioLibWrappers.h>
|
||||
#include <StationG3Board.h>
|
||||
#include <helpers/radiolib/CustomSX1262Wrapper.h>
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
#include <helpers/sensors/EnvironmentSensorManager.h>
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
#include <helpers/ui/SH1106Display.h>
|
||||
#include <helpers/ui/MomentaryButton.h>
|
||||
#endif
|
||||
|
||||
extern StationG3Board board;
|
||||
extern WRAPPER_CLASS radio_driver;
|
||||
extern AutoDiscoverRTCClock rtc_clock;
|
||||
extern EnvironmentSensorManager sensors;
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
extern DISPLAY_CLASS display;
|
||||
extern MomentaryButton user_btn;
|
||||
#endif
|
||||
|
||||
bool radio_init();
|
||||
mesh::LocalIdentity radio_new_identity();
|
||||
@@ -13,6 +13,7 @@ build_flags = ${nrf52_base.build_flags}
|
||||
-D USER_BTN_PRESSED=HIGH
|
||||
-D PIN_STATUS_LED=24
|
||||
-D RADIO_CLASS=CustomLR1110
|
||||
-D USE_LR1110
|
||||
-D WRAPPER_CLASS=CustomLR1110Wrapper
|
||||
-D LORA_TX_POWER=22
|
||||
-D RF_SWITCH_TABLE
|
||||
|
||||
@@ -151,4 +151,9 @@ build_flags =
|
||||
; -D MESH_DEBUG=1
|
||||
lib_deps =
|
||||
${Xiao_S3.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
[env:Xiao_S3_kiss_modem]
|
||||
extends = Xiao_S3
|
||||
build_src_filter = ${Xiao_S3.build_src_filter}
|
||||
+<../examples/kiss_modem/>
|
||||
|
||||
Reference in New Issue
Block a user