mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-30 06:31:01 +00:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e30795c6b | ||
|
|
0228d4e2df | ||
|
|
11963ea3d2 | ||
|
|
aeb1e7e71a | ||
|
|
aecdc93c98 | ||
|
|
fa28412b82 | ||
|
|
cf5ecf30d7 | ||
|
|
df3934de85 | ||
|
|
c911a8e3f1 | ||
|
|
f392487b46 | ||
|
|
a8c2fb3945 | ||
|
|
c0d108adff | ||
|
|
f7ccab95b2 | ||
|
|
dc08311b56 | ||
|
|
ce389e44cd | ||
|
|
a19d88b022 | ||
|
|
8757584f93 | ||
|
|
83a49536c0 | ||
|
|
49c47c24e3 | ||
|
|
d847061d02 | ||
|
|
451695a210 | ||
|
|
20cd4a196c | ||
|
|
8575e13da8 | ||
|
|
91588f5136 | ||
|
|
82325a60ba |
24
.github/workflows/main_matrix.yml
vendored
24
.github/workflows/main_matrix.yml
vendored
@@ -240,7 +240,6 @@ jobs:
|
||||
needs: [build]
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
if: github.event_name == 'pull_request_target'
|
||||
with:
|
||||
filter: blob:none # means we download all the git history but none of the commit (except ones with checkout like the head)
|
||||
fetch-depth: 0
|
||||
@@ -254,26 +253,19 @@ jobs:
|
||||
uses: actions/upload-artifact@v6
|
||||
id: upload-manifest
|
||||
with:
|
||||
name: manifests-${{ github.sha }}
|
||||
name: manifests-all
|
||||
overwrite: true
|
||||
path: manifests-new/*.mt.json
|
||||
path: |
|
||||
manifests-new/*.mt.json
|
||||
- name: Find the merge base
|
||||
if: github.event_name == 'pull_request_target'
|
||||
run: echo "MERGE_BASE=$(git merge-base "origin/$base" "$head")" >> $GITHUB_ENV
|
||||
env:
|
||||
base: ${{ github.base_ref }}
|
||||
head: ${{ github.sha }}
|
||||
# Currently broken (for-loop through EVERY artifact -- rate limiting)
|
||||
# - name: Download the old manifests
|
||||
# if: github.event_name == 'pull_request_target'
|
||||
# run: gh run download -R "$repo" --name "manifests-$merge_base" --dir manifest-old/
|
||||
# env:
|
||||
# GH_TOKEN: ${{ github.token }}
|
||||
# merge_base: ${{ env.MERGE_BASE }}
|
||||
# repo: ${{ github.repository }}
|
||||
# - name: Do scan and post comment
|
||||
# if: github.event_name == 'pull_request_target'
|
||||
# run: python3 bin/shame.py ${{ github.event.pull_request.number }} manifests-old/ manifests-new/
|
||||
head: ${{ github.head_ref }}
|
||||
- name: Download the old manifests
|
||||
run: gh run download -R ${{ github.repository }} --commit ${{ env.MERGE_BASE }} --name manifests-all --dir manifest-old/
|
||||
- name: Do scan and post comment
|
||||
run: python3 bin/shame.py ${{ github.event.pull_request.number }} manifests-old/ manifests-new/
|
||||
|
||||
release-artifacts:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -41,12 +41,3 @@ src/mesh/raspihttp/private_key.pem
|
||||
|
||||
# Ignore logo (set at build time with platformio-custom.py)
|
||||
data/boot/logo.*
|
||||
|
||||
# pioarduino platform
|
||||
managed_components/*
|
||||
arduino-lib-builder*
|
||||
dependencies.lock
|
||||
idf_component.yml
|
||||
CMakeLists.txt
|
||||
sdkconfig.*
|
||||
.dummy/*
|
||||
|
||||
@@ -9,9 +9,9 @@ plugins:
|
||||
lint:
|
||||
enabled:
|
||||
- checkov@3.2.495
|
||||
- renovate@42.66.8
|
||||
- renovate@42.64.1
|
||||
- prettier@3.7.4
|
||||
- trufflehog@3.92.4
|
||||
- trufflehog@3.92.3
|
||||
- yamllint@1.37.1
|
||||
- bandit@1.9.2
|
||||
- trivy@0.68.2
|
||||
|
||||
@@ -21,14 +21,13 @@ rm -f $BUILDDIR/firmware*
|
||||
export APP_VERSION=$VERSION
|
||||
|
||||
basename=firmware-$1-$VERSION
|
||||
ota_basename=${basename}-ota
|
||||
|
||||
pio run --environment $1 -t mtjson # -v
|
||||
|
||||
cp $BUILDDIR/$basename.elf $OUTDIR/$basename.elf
|
||||
|
||||
echo "Copying NRF52 dfu (OTA) file"
|
||||
cp $BUILDDIR/$basename.zip $OUTDIR/$ota_basename.zip
|
||||
cp $BUILDDIR/$basename.zip $OUTDIR/$basename.zip
|
||||
|
||||
echo "Copying NRF52 UF2 file"
|
||||
cp $BUILDDIR/$basename.uf2 $OUTDIR/$basename.uf2
|
||||
|
||||
@@ -87,6 +87,9 @@
|
||||
</screenshots>
|
||||
|
||||
<releases>
|
||||
<release version="2.7.18" date="2025-12-20">
|
||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.18</url>
|
||||
</release>
|
||||
<release version="2.7.17" date="2025-11-28">
|
||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.17</url>
|
||||
</release>
|
||||
|
||||
@@ -17,8 +17,6 @@ lfsbin = f"{progname.replace('firmware-', 'littlefs-')}.bin"
|
||||
|
||||
def manifest_gather(source, target, env):
|
||||
out = []
|
||||
board_platform = env.BoardConfig().get("platform")
|
||||
needs_ota_suffix = board_platform == "nordicnrf52"
|
||||
check_paths = [
|
||||
progname,
|
||||
f"{progname}.elf",
|
||||
@@ -34,11 +32,8 @@ def manifest_gather(source, target, env):
|
||||
for p in check_paths:
|
||||
f = env.File(env.subst(f"$BUILD_DIR/{p}"))
|
||||
if f.exists():
|
||||
manifest_name = p
|
||||
if needs_ota_suffix and p == f"{progname}.zip":
|
||||
manifest_name = f"{progname}-ota.zip"
|
||||
d = {
|
||||
"name": manifest_name,
|
||||
"name": p,
|
||||
"md5": f.get_content_hash(), # Returns MD5 hash
|
||||
"bytes": f.get_size() # Returns file size in bytes
|
||||
}
|
||||
|
||||
6
debian/changelog
vendored
6
debian/changelog
vendored
@@ -1,3 +1,9 @@
|
||||
meshtasticd (2.7.18.0) unstable; urgency=medium
|
||||
|
||||
* Version 2.7.18
|
||||
|
||||
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Sat, 20 Dec 2025 15:47:25 +0000
|
||||
|
||||
meshtasticd (2.7.17.0) unstable; urgency=medium
|
||||
|
||||
* Version 2.7.17
|
||||
|
||||
@@ -64,7 +64,7 @@ monitor_speed = 115200
|
||||
monitor_filters = direct
|
||||
lib_deps =
|
||||
# renovate: datasource=git-refs depName=meshtastic-esp8266-oled-ssd1306 packageName=https://github.com/meshtastic/esp8266-oled-ssd1306 gitBranch=master
|
||||
https://github.com/meshtastic/esp8266-oled-ssd1306/archive/b34c6817c25d6faabb3a8a162b5d14fb75395433.zip
|
||||
https://github.com/meshtastic/esp8266-oled-ssd1306/archive/2887bf4a19f64d92c984dcc8fd5ca7429e425e4a.zip
|
||||
# renovate: datasource=git-refs depName=meshtastic-OneButton packageName=https://github.com/meshtastic/OneButton gitBranch=master
|
||||
https://github.com/meshtastic/OneButton/archive/fa352d668c53f290cfa480a5f79ad422cd828c70.zip
|
||||
# renovate: datasource=git-refs depName=meshtastic-arduino-fsm packageName=https://github.com/meshtastic/arduino-fsm gitBranch=master
|
||||
|
||||
Submodule protobufs updated: f78b3f0dcc...c474fd3f49
@@ -470,49 +470,18 @@ bool isAllowedPunctuation(char c)
|
||||
return allowed.find(c) != std::string::npos;
|
||||
}
|
||||
|
||||
static void replaceAll(std::string &s, const std::string &from, const std::string &to)
|
||||
{
|
||||
if (from.empty())
|
||||
return;
|
||||
size_t pos = 0;
|
||||
while ((pos = s.find(from, pos)) != std::string::npos) {
|
||||
s.replace(pos, from.size(), to);
|
||||
pos += to.size();
|
||||
}
|
||||
}
|
||||
|
||||
std::string sanitizeString(const std::string &input)
|
||||
{
|
||||
std::string output;
|
||||
bool inReplacement = false;
|
||||
|
||||
// Make a mutable copy so we can normalize UTF-8 “smart punctuation” into ASCII first.
|
||||
std::string s = input;
|
||||
|
||||
// Curly single quotes: ‘ ’
|
||||
replaceAll(s, "\xE2\x80\x98", "'"); // U+2018
|
||||
replaceAll(s, "\xE2\x80\x99", "'"); // U+2019
|
||||
|
||||
// Curly double quotes: “ ”
|
||||
replaceAll(s, "\xE2\x80\x9C", "\""); // U+201C
|
||||
replaceAll(s, "\xE2\x80\x9D", "\""); // U+201D
|
||||
|
||||
// En dash / Em dash: – —
|
||||
replaceAll(s, "\xE2\x80\x93", "-"); // U+2013
|
||||
replaceAll(s, "\xE2\x80\x94", "-"); // U+2014
|
||||
|
||||
// Non-breaking space
|
||||
replaceAll(s, "\xC2\xA0", " "); // U+00A0
|
||||
|
||||
// Now do your original sanitize pass over the normalized string.
|
||||
for (unsigned char uc : s) {
|
||||
char c = static_cast<char>(uc);
|
||||
if (std::isalnum(uc) || isAllowedPunctuation(c)) {
|
||||
for (char c : input) {
|
||||
if (std::isalnum(static_cast<unsigned char>(c)) || isAllowedPunctuation(c)) {
|
||||
output += c;
|
||||
inReplacement = false;
|
||||
} else {
|
||||
if (!inReplacement) {
|
||||
output += static_cast<char>(0xBF); // ISO-8859-1 for inverted question mark
|
||||
output += 0xbf; // ISO-8859-1 for inverted question mark
|
||||
inReplacement = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1092,23 +1092,11 @@ void menuHandler::favoriteBaseMenu()
|
||||
|
||||
void menuHandler::positionBaseMenu()
|
||||
{
|
||||
enum optionsNumbers {
|
||||
Back,
|
||||
GPSToggle,
|
||||
GPSFormat,
|
||||
CompassMenu,
|
||||
CompassCalibrate,
|
||||
GPSSmartPosition,
|
||||
GPSUpdateInterval,
|
||||
GPSPositionBroadcast,
|
||||
enumEnd
|
||||
};
|
||||
enum optionsNumbers { Back, GPSToggle, GPSFormat, CompassMenu, CompassCalibrate, enumEnd };
|
||||
|
||||
static const char *optionsArray[enumEnd] = {
|
||||
"Back", "On/Off Toggle", "Format", "Smart Position", "Update Interval", "Broadcast Interval", "Compass"};
|
||||
static int optionsEnumArray[enumEnd] = {
|
||||
Back, GPSToggle, GPSFormat, GPSSmartPosition, GPSUpdateInterval, GPSPositionBroadcast, CompassMenu};
|
||||
int options = 7;
|
||||
static const char *optionsArray[enumEnd] = {"Back", "GPS Toggle", "GPS Format", "Compass"};
|
||||
static int optionsEnumArray[enumEnd] = {Back, GPSToggle, GPSFormat, CompassMenu};
|
||||
int options = 4;
|
||||
|
||||
if (accelerometerThread) {
|
||||
optionsArray[options] = "Compass Calibrate";
|
||||
@@ -1116,7 +1104,7 @@ void menuHandler::positionBaseMenu()
|
||||
}
|
||||
|
||||
BannerOverlayOptions bannerOptions;
|
||||
bannerOptions.message = "GPS Action";
|
||||
bannerOptions.message = "Position Action";
|
||||
bannerOptions.optionsArrayPtr = optionsArray;
|
||||
bannerOptions.optionsEnumPtr = optionsEnumArray;
|
||||
bannerOptions.optionsCount = options;
|
||||
@@ -1132,15 +1120,6 @@ void menuHandler::positionBaseMenu()
|
||||
screen->runNow();
|
||||
} else if (selected == CompassCalibrate) {
|
||||
accelerometerThread->calibrate(30);
|
||||
} else if (selected == GPSSmartPosition) {
|
||||
menuQueue = gps_smart_position_menu;
|
||||
screen->runNow();
|
||||
} else if (selected == GPSUpdateInterval) {
|
||||
menuQueue = gps_update_interval_menu;
|
||||
screen->runNow();
|
||||
} else if (selected == GPSPositionBroadcast) {
|
||||
menuQueue = gps_position_broadcast_menu;
|
||||
screen->runNow();
|
||||
}
|
||||
};
|
||||
screen->showOverlayBanner(bannerOptions);
|
||||
@@ -1367,217 +1346,6 @@ void menuHandler::GPSFormatMenu()
|
||||
bannerOptions.InitialSelected = uiconfig.gps_format + 1;
|
||||
screen->showOverlayBanner(bannerOptions);
|
||||
}
|
||||
|
||||
void menuHandler::GPSSmartPositionMenu()
|
||||
{
|
||||
static const char *optionsArray[] = {"Back", "Enabled", "Disabled"};
|
||||
BannerOverlayOptions bannerOptions;
|
||||
bannerOptions.message = "Toggle Smart Position";
|
||||
if (currentResolution == ScreenResolution::UltraLow) {
|
||||
bannerOptions.message = "Smrt Postn";
|
||||
}
|
||||
bannerOptions.optionsArrayPtr = optionsArray;
|
||||
bannerOptions.optionsCount = 3;
|
||||
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||
if (selected == 0) {
|
||||
menuQueue = position_base_menu;
|
||||
screen->runNow();
|
||||
} else if (selected == 1) {
|
||||
config.position.position_broadcast_smart_enabled = true;
|
||||
saveUIConfig();
|
||||
service->reloadConfig(SEGMENT_CONFIG);
|
||||
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
|
||||
} else if (selected == 2) {
|
||||
config.position.position_broadcast_smart_enabled = false;
|
||||
saveUIConfig();
|
||||
service->reloadConfig(SEGMENT_CONFIG);
|
||||
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
|
||||
}
|
||||
};
|
||||
bannerOptions.InitialSelected = config.position.position_broadcast_smart_enabled ? 1 : 2;
|
||||
screen->showOverlayBanner(bannerOptions);
|
||||
}
|
||||
|
||||
void menuHandler::GPSUpdateIntervalMenu()
|
||||
{
|
||||
static const char *optionsArray[] = {"Back", "8 seconds", "20 seconds", "40 seconds", "1 minute", "80 seconds",
|
||||
"2 minutes", "5 minutes", "10 minutes", "15 minutes", "30 minutes", "1 hour",
|
||||
"6 hours", "12 hours", "24 hours", "At Boot Only"};
|
||||
BannerOverlayOptions bannerOptions;
|
||||
bannerOptions.message = "Update Interval";
|
||||
bannerOptions.optionsArrayPtr = optionsArray;
|
||||
bannerOptions.optionsCount = 16;
|
||||
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||
if (selected == 0) {
|
||||
menuQueue = position_base_menu;
|
||||
screen->runNow();
|
||||
} else if (selected == 1) {
|
||||
config.position.gps_update_interval = 8;
|
||||
} else if (selected == 2) {
|
||||
config.position.gps_update_interval = 20;
|
||||
} else if (selected == 3) {
|
||||
config.position.gps_update_interval = 40;
|
||||
} else if (selected == 4) {
|
||||
config.position.gps_update_interval = 60;
|
||||
} else if (selected == 5) {
|
||||
config.position.gps_update_interval = 80;
|
||||
} else if (selected == 6) {
|
||||
config.position.gps_update_interval = 120;
|
||||
} else if (selected == 7) {
|
||||
config.position.gps_update_interval = 300;
|
||||
} else if (selected == 8) {
|
||||
config.position.gps_update_interval = 600;
|
||||
} else if (selected == 9) {
|
||||
config.position.gps_update_interval = 900;
|
||||
} else if (selected == 10) {
|
||||
config.position.gps_update_interval = 1800;
|
||||
} else if (selected == 11) {
|
||||
config.position.gps_update_interval = 3600;
|
||||
} else if (selected == 12) {
|
||||
config.position.gps_update_interval = 21600;
|
||||
} else if (selected == 13) {
|
||||
config.position.gps_update_interval = 43200;
|
||||
} else if (selected == 14) {
|
||||
config.position.gps_update_interval = 86400;
|
||||
} else if (selected == 15) {
|
||||
config.position.gps_update_interval = 2147483647; // At Boot Only
|
||||
}
|
||||
|
||||
if (selected != 0) {
|
||||
saveUIConfig();
|
||||
service->reloadConfig(SEGMENT_CONFIG);
|
||||
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
|
||||
}
|
||||
};
|
||||
|
||||
if (config.position.gps_update_interval == 8) {
|
||||
bannerOptions.InitialSelected = 1;
|
||||
} else if (config.position.gps_update_interval == 20) {
|
||||
bannerOptions.InitialSelected = 2;
|
||||
} else if (config.position.gps_update_interval == 40) {
|
||||
bannerOptions.InitialSelected = 3;
|
||||
} else if (config.position.gps_update_interval == 60) {
|
||||
bannerOptions.InitialSelected = 4;
|
||||
} else if (config.position.gps_update_interval == 80) {
|
||||
bannerOptions.InitialSelected = 5;
|
||||
} else if (config.position.gps_update_interval == 120) {
|
||||
bannerOptions.InitialSelected = 6;
|
||||
} else if (config.position.gps_update_interval == 300) {
|
||||
bannerOptions.InitialSelected = 7;
|
||||
} else if (config.position.gps_update_interval == 600) {
|
||||
bannerOptions.InitialSelected = 8;
|
||||
} else if (config.position.gps_update_interval == 900) {
|
||||
bannerOptions.InitialSelected = 9;
|
||||
} else if (config.position.gps_update_interval == 1800) {
|
||||
bannerOptions.InitialSelected = 10;
|
||||
} else if (config.position.gps_update_interval == 3600) {
|
||||
bannerOptions.InitialSelected = 11;
|
||||
} else if (config.position.gps_update_interval == 21600) {
|
||||
bannerOptions.InitialSelected = 12;
|
||||
} else if (config.position.gps_update_interval == 43200) {
|
||||
bannerOptions.InitialSelected = 13;
|
||||
} else if (config.position.gps_update_interval == 86400) {
|
||||
bannerOptions.InitialSelected = 14;
|
||||
} else if (config.position.gps_update_interval == 2147483647) { // At Boot Only
|
||||
bannerOptions.InitialSelected = 15;
|
||||
} else {
|
||||
bannerOptions.InitialSelected = 0;
|
||||
}
|
||||
screen->showOverlayBanner(bannerOptions);
|
||||
}
|
||||
|
||||
void menuHandler::GPSPositionBroadcastMenu()
|
||||
{
|
||||
static const char *optionsArray[] = {"Back", "1 minute", "90 seconds", "5 minutes", "15 minutes", "1 hour",
|
||||
"2 hours", "3 hours", "4 hours", "5 hours", "6 hours", "12 hours",
|
||||
"18 hours", "24 hours", "36 hours", "48 hours", "72 hours"};
|
||||
BannerOverlayOptions bannerOptions;
|
||||
bannerOptions.message = "Broadcast Interval";
|
||||
bannerOptions.optionsArrayPtr = optionsArray;
|
||||
bannerOptions.optionsCount = 17;
|
||||
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||
if (selected == 0) {
|
||||
menuQueue = position_base_menu;
|
||||
screen->runNow();
|
||||
} else if (selected == 1) {
|
||||
config.position.position_broadcast_secs = 60;
|
||||
} else if (selected == 2) {
|
||||
config.position.position_broadcast_secs = 90;
|
||||
} else if (selected == 3) {
|
||||
config.position.position_broadcast_secs = 300;
|
||||
} else if (selected == 4) {
|
||||
config.position.position_broadcast_secs = 900;
|
||||
} else if (selected == 5) {
|
||||
config.position.position_broadcast_secs = 3600;
|
||||
} else if (selected == 6) {
|
||||
config.position.position_broadcast_secs = 7200;
|
||||
} else if (selected == 7) {
|
||||
config.position.position_broadcast_secs = 10800;
|
||||
} else if (selected == 8) {
|
||||
config.position.position_broadcast_secs = 14400;
|
||||
} else if (selected == 9) {
|
||||
config.position.position_broadcast_secs = 18000;
|
||||
} else if (selected == 10) {
|
||||
config.position.position_broadcast_secs = 21600;
|
||||
} else if (selected == 11) {
|
||||
config.position.position_broadcast_secs = 43200;
|
||||
} else if (selected == 12) {
|
||||
config.position.position_broadcast_secs = 64800;
|
||||
} else if (selected == 13) {
|
||||
config.position.position_broadcast_secs = 86400;
|
||||
} else if (selected == 14) {
|
||||
config.position.position_broadcast_secs = 129600;
|
||||
} else if (selected == 15) {
|
||||
config.position.position_broadcast_secs = 172800;
|
||||
} else if (selected == 16) {
|
||||
config.position.position_broadcast_secs = 259200;
|
||||
}
|
||||
|
||||
if (selected != 0) {
|
||||
saveUIConfig();
|
||||
service->reloadConfig(SEGMENT_CONFIG);
|
||||
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
|
||||
}
|
||||
};
|
||||
|
||||
if (config.position.position_broadcast_secs == 60) {
|
||||
bannerOptions.InitialSelected = 1;
|
||||
} else if (config.position.position_broadcast_secs == 90) {
|
||||
bannerOptions.InitialSelected = 2;
|
||||
} else if (config.position.position_broadcast_secs == 300) {
|
||||
bannerOptions.InitialSelected = 3;
|
||||
} else if (config.position.position_broadcast_secs == 900) {
|
||||
bannerOptions.InitialSelected = 4;
|
||||
} else if (config.position.position_broadcast_secs == 3600) {
|
||||
bannerOptions.InitialSelected = 5;
|
||||
} else if (config.position.position_broadcast_secs == 7200) {
|
||||
bannerOptions.InitialSelected = 6;
|
||||
} else if (config.position.position_broadcast_secs == 10800) {
|
||||
bannerOptions.InitialSelected = 7;
|
||||
} else if (config.position.position_broadcast_secs == 14400) {
|
||||
bannerOptions.InitialSelected = 8;
|
||||
} else if (config.position.position_broadcast_secs == 18000) {
|
||||
bannerOptions.InitialSelected = 9;
|
||||
} else if (config.position.position_broadcast_secs == 21600) {
|
||||
bannerOptions.InitialSelected = 10;
|
||||
} else if (config.position.position_broadcast_secs == 43200) {
|
||||
bannerOptions.InitialSelected = 11;
|
||||
} else if (config.position.position_broadcast_secs == 64800) {
|
||||
bannerOptions.InitialSelected = 12;
|
||||
} else if (config.position.position_broadcast_secs == 86400) {
|
||||
bannerOptions.InitialSelected = 13;
|
||||
} else if (config.position.position_broadcast_secs == 129600) {
|
||||
bannerOptions.InitialSelected = 14;
|
||||
} else if (config.position.position_broadcast_secs == 172800) {
|
||||
bannerOptions.InitialSelected = 15;
|
||||
} else if (config.position.position_broadcast_secs == 259200) {
|
||||
bannerOptions.InitialSelected = 16;
|
||||
} else {
|
||||
bannerOptions.InitialSelected = 0;
|
||||
}
|
||||
screen->showOverlayBanner(bannerOptions);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void menuHandler::BluetoothToggleMenu()
|
||||
@@ -2358,15 +2126,6 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
|
||||
case gps_format_menu:
|
||||
GPSFormatMenu();
|
||||
break;
|
||||
case gps_smart_position_menu:
|
||||
GPSSmartPositionMenu();
|
||||
break;
|
||||
case gps_update_interval_menu:
|
||||
GPSUpdateIntervalMenu();
|
||||
break;
|
||||
case gps_position_broadcast_menu:
|
||||
GPSPositionBroadcastMenu();
|
||||
break;
|
||||
#endif
|
||||
case compass_point_north_menu:
|
||||
compassNorthMenu();
|
||||
|
||||
@@ -22,9 +22,6 @@ class menuHandler
|
||||
node_base_menu,
|
||||
gps_toggle_menu,
|
||||
gps_format_menu,
|
||||
gps_smart_position_menu,
|
||||
gps_update_interval_menu,
|
||||
gps_position_broadcast_menu,
|
||||
compass_point_north_menu,
|
||||
reset_node_db_menu,
|
||||
buzzermodemenupicker,
|
||||
@@ -80,9 +77,6 @@ class menuHandler
|
||||
static void compassNorthMenu();
|
||||
static void GPSToggleMenu();
|
||||
static void GPSFormatMenu();
|
||||
static void GPSSmartPositionMenu();
|
||||
static void GPSUpdateIntervalMenu();
|
||||
static void GPSPositionBroadcastMenu();
|
||||
static void BuzzerModeMenu();
|
||||
static void switchToMUIMenu();
|
||||
static void TFTColorPickerMenu(OLEDDisplay *display);
|
||||
|
||||
@@ -27,9 +27,7 @@ bool RotaryEncoderInterruptImpl1::init()
|
||||
RotaryEncoderInterruptImpl1::handleIntA, RotaryEncoderInterruptImpl1::handleIntB,
|
||||
RotaryEncoderInterruptImpl1::handleIntPressed);
|
||||
inputBroker->registerSource(this);
|
||||
#ifndef HAS_PHYSICAL_KEYBOARD
|
||||
osk_found = true;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -45,9 +45,7 @@ void TrackballInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLef
|
||||
|
||||
LOG_DEBUG("Trackball GPIO initialized - UP:%d DOWN:%d LEFT:%d RIGHT:%d PRESS:%d", this->_pinUp, this->_pinDown,
|
||||
this->_pinLeft, this->_pinRight, pinPress);
|
||||
#ifndef HAS_PHYSICAL_KEYBOARD
|
||||
osk_found = true;
|
||||
#endif
|
||||
this->setInterval(100);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,9 +29,7 @@ bool UpDownInterruptImpl1::init()
|
||||
eventDownLong, UpDownInterruptImpl1::handleIntDown, UpDownInterruptImpl1::handleIntUp,
|
||||
UpDownInterruptImpl1::handleIntPressed);
|
||||
inputBroker->registerSource(this);
|
||||
#ifndef HAS_PHYSICAL_KEYBOARD
|
||||
osk_found = true;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1186,6 +1186,11 @@ void setup()
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef PIN_PWR_DELAY_MS
|
||||
// This may be required to give the peripherals time to power up.
|
||||
delay(PIN_PWR_DELAY_MS);
|
||||
#endif
|
||||
|
||||
#ifdef ARCH_PORTDUINO
|
||||
// as one can't use a function pointer to the class constructor:
|
||||
auto loraModuleInterface = [](LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
|
||||
@@ -1460,10 +1465,8 @@ void setup()
|
||||
#endif
|
||||
|
||||
#if defined(HAS_TRACKBALL) || (defined(INPUTDRIVER_ENCODER_TYPE) && INPUTDRIVER_ENCODER_TYPE == 2)
|
||||
#ifndef HAS_PHYSICAL_KEYBOARD
|
||||
osk_found = true;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WEBSERVER
|
||||
// Start web server thread.
|
||||
|
||||
@@ -195,13 +195,15 @@ void MeshService::handleToRadio(meshtastic_MeshPacket &p)
|
||||
|
||||
p.rx_time = getValidTime(RTCQualityFromNet); // Record the time the packet arrived from the phone
|
||||
|
||||
IF_SCREEN(if (p.decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP && p.decoded.payload.size > 0 &&
|
||||
p.to != NODENUM_BROADCAST && p.to != 0) // DM only
|
||||
{
|
||||
perhapsDecode(&p);
|
||||
const StoredMessage &sm = messageStore.addFromPacket(p);
|
||||
graphics::MessageRenderer::handleNewMessage(nullptr, sm, p); // notify UI
|
||||
})
|
||||
#if HAS_SCREEN
|
||||
if (screen && p.decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP && p.decoded.payload.size > 0 &&
|
||||
p.to != NODENUM_BROADCAST && p.to != 0) // DM only
|
||||
{
|
||||
perhapsDecode(&p);
|
||||
const StoredMessage &sm = messageStore.addFromPacket(p);
|
||||
graphics::MessageRenderer::handleNewMessage(nullptr, sm, p); // notify UI
|
||||
}
|
||||
#endif
|
||||
// Send the packet into the mesh
|
||||
DEBUG_HEAP_BEFORE;
|
||||
auto a = packetPool.allocCopy(p);
|
||||
|
||||
@@ -805,8 +805,7 @@ void NodeDB::installDefaultModuleConfig()
|
||||
moduleConfig.external_notification.output_ms = 500;
|
||||
moduleConfig.external_notification.nag_timeout = 2;
|
||||
#endif
|
||||
#if defined(RAK4630) || defined(RAK11310) || defined(RAK3312) || defined(MUZI_BASE) || defined(ELECROW_ThinkNode_M3) || \
|
||||
defined(ELECROW_ThinkNode_M6)
|
||||
#if defined(RAK4630) || defined(RAK11310) || defined(RAK3312) || defined(MUZI_BASE) || defined(ELECROW_ThinkNode_M3)
|
||||
// Default to PIN_LED2 for external notification output (LED color depends on device variant)
|
||||
moduleConfig.external_notification.enabled = true;
|
||||
moduleConfig.external_notification.output = PIN_LED2;
|
||||
|
||||
@@ -730,8 +730,7 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
|
||||
meshtastic_PortNum_POSITION_APP, meshtastic_PortNum_NODEINFO_APP, meshtastic_PortNum_ROUTING_APP,
|
||||
meshtastic_PortNum_TELEMETRY_APP, meshtastic_PortNum_ADMIN_APP, meshtastic_PortNum_ALERT_APP,
|
||||
meshtastic_PortNum_KEY_VERIFICATION_APP, meshtastic_PortNum_WAYPOINT_APP,
|
||||
meshtastic_PortNum_STORE_FORWARD_APP, meshtastic_PortNum_TRACEROUTE_APP,
|
||||
meshtastic_PortNum_STORE_FORWARD_PLUSPLUS_APP)) {
|
||||
meshtastic_PortNum_STORE_FORWARD_APP, meshtastic_PortNum_TRACEROUTE_APP)) {
|
||||
LOG_DEBUG("Ignore packet on non-standard portnum for CORE_PORTNUMS_ONLY");
|
||||
cancelSending(p->from, p->id);
|
||||
skipHandle = true;
|
||||
|
||||
@@ -33,5 +33,3 @@ PB_BIND(meshtastic_KeyVerificationAdmin, meshtastic_KeyVerificationAdmin, AUTO)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -16,16 +16,6 @@
|
||||
#endif
|
||||
|
||||
/* Enum definitions */
|
||||
/* Firmware update mode for OTA updates */
|
||||
typedef enum _meshtastic_OTAMode {
|
||||
/* Do not reboot into OTA mode */
|
||||
meshtastic_OTAMode_NO_REBOOT_OTA = 0,
|
||||
/* Reboot into OTA mode for BLE firmware update */
|
||||
meshtastic_OTAMode_OTA_BLE = 1,
|
||||
/* Reboot into OTA mode for WiFi firmware update */
|
||||
meshtastic_OTAMode_OTA_WIFI = 2
|
||||
} meshtastic_OTAMode;
|
||||
|
||||
/* TODO: REPLACE */
|
||||
typedef enum _meshtastic_AdminMessage_ConfigType {
|
||||
/* TODO: REPLACE */
|
||||
@@ -268,13 +258,10 @@ typedef struct _meshtastic_AdminMessage {
|
||||
meshtastic_SharedContact add_contact;
|
||||
/* Initiate or respond to a key verification request */
|
||||
meshtastic_KeyVerificationAdmin key_verification;
|
||||
/* Tell the node to reboot into OTA mode for firmware update via BLE or WiFi (ESP32 only for now) */
|
||||
meshtastic_OTAMode reboot_ota_mode;
|
||||
/* Tell the node to factory reset config everything; all device state and configuration will be returned to factory defaults and BLE bonds will be cleared. */
|
||||
int32_t factory_reset_device;
|
||||
/* Tell the node to reboot into the OTA Firmware in this many seconds (or <0 to cancel reboot)
|
||||
Only Implemented for ESP32 Devices. This needs to be issued to send a new main firmware via bluetooth.
|
||||
Deprecated in favor of reboot_ota_mode in 2.7.17 */
|
||||
Only Implemented for ESP32 Devices. This needs to be issued to send a new main firmware via bluetooth. */
|
||||
int32_t reboot_ota_seconds;
|
||||
/* This message is only supported for the simulator Portduino build.
|
||||
If received the simulator will exit successfully. */
|
||||
@@ -301,10 +288,6 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
/* Helper constants for enums */
|
||||
#define _meshtastic_OTAMode_MIN meshtastic_OTAMode_NO_REBOOT_OTA
|
||||
#define _meshtastic_OTAMode_MAX meshtastic_OTAMode_OTA_WIFI
|
||||
#define _meshtastic_OTAMode_ARRAYSIZE ((meshtastic_OTAMode)(meshtastic_OTAMode_OTA_WIFI+1))
|
||||
|
||||
#define _meshtastic_AdminMessage_ConfigType_MIN meshtastic_AdminMessage_ConfigType_DEVICE_CONFIG
|
||||
#define _meshtastic_AdminMessage_ConfigType_MAX meshtastic_AdminMessage_ConfigType_DEVICEUI_CONFIG
|
||||
#define _meshtastic_AdminMessage_ConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ConfigType)(meshtastic_AdminMessage_ConfigType_DEVICEUI_CONFIG+1))
|
||||
@@ -326,7 +309,6 @@ extern "C" {
|
||||
#define meshtastic_AdminMessage_payload_variant_backup_preferences_ENUMTYPE meshtastic_AdminMessage_BackupLocation
|
||||
#define meshtastic_AdminMessage_payload_variant_restore_preferences_ENUMTYPE meshtastic_AdminMessage_BackupLocation
|
||||
#define meshtastic_AdminMessage_payload_variant_remove_backup_preferences_ENUMTYPE meshtastic_AdminMessage_BackupLocation
|
||||
#define meshtastic_AdminMessage_payload_variant_reboot_ota_mode_ENUMTYPE meshtastic_OTAMode
|
||||
|
||||
|
||||
|
||||
@@ -414,7 +396,6 @@ extern "C" {
|
||||
#define meshtastic_AdminMessage_commit_edit_settings_tag 65
|
||||
#define meshtastic_AdminMessage_add_contact_tag 66
|
||||
#define meshtastic_AdminMessage_key_verification_tag 67
|
||||
#define meshtastic_AdminMessage_reboot_ota_mode_tag 68
|
||||
#define meshtastic_AdminMessage_factory_reset_device_tag 94
|
||||
#define meshtastic_AdminMessage_reboot_ota_seconds_tag 95
|
||||
#define meshtastic_AdminMessage_exit_simulator_tag 96
|
||||
@@ -473,7 +454,6 @@ X(a, STATIC, ONEOF, BOOL, (payload_variant,begin_edit_settings,begin_ed
|
||||
X(a, STATIC, ONEOF, BOOL, (payload_variant,commit_edit_settings,commit_edit_settings), 65) \
|
||||
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,add_contact,add_contact), 66) \
|
||||
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,key_verification,key_verification), 67) \
|
||||
X(a, STATIC, ONEOF, UENUM, (payload_variant,reboot_ota_mode,reboot_ota_mode), 68) \
|
||||
X(a, STATIC, ONEOF, INT32, (payload_variant,factory_reset_device,factory_reset_device), 94) \
|
||||
X(a, STATIC, ONEOF, INT32, (payload_variant,reboot_ota_seconds,reboot_ota_seconds), 95) \
|
||||
X(a, STATIC, ONEOF, BOOL, (payload_variant,exit_simulator,exit_simulator), 96) \
|
||||
|
||||
@@ -245,9 +245,7 @@ void setupModules()
|
||||
#if ARCH_PORTDUINO
|
||||
new HostMetricsModule();
|
||||
#if SFPP_ENABLED
|
||||
if (portduino_config.sfpp_enabled) {
|
||||
new StoreForwardPlusPlusModule();
|
||||
}
|
||||
new StoreForwardPlusPlusModule();
|
||||
#endif
|
||||
#endif
|
||||
#if HAS_TELEMETRY
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
// Eventual TODO: non-stratum0 nodes need to be pointed at their upstream source? Maybe
|
||||
// I've done a lot of this in SQLite for now, but honestly it needs to happen in memory, and get saved to sqlite during downtime
|
||||
|
||||
// TODO: custom hops. 1 maybe 0. Configurable?
|
||||
|
||||
// TODO: non-stratum0 nodes need to be pointed at their upstream source? Maybe
|
||||
|
||||
// TODO: evict messages from scratch after a timeout
|
||||
|
||||
// things may get weird if there are multiple stratum-0 nodes on a single mesh. Come up with mitigations
|
||||
#if __has_include("sqlite3.h")
|
||||
|
||||
#include "StoreForwardPlusPlus.h"
|
||||
@@ -9,17 +17,16 @@
|
||||
#include "modules/RoutingModule.h"
|
||||
|
||||
StoreForwardPlusPlusModule::StoreForwardPlusPlusModule()
|
||||
: ProtobufModule("StoreForwardpp",
|
||||
portduino_config.sfpp_steal_port ? meshtastic_PortNum_TEXT_MESSAGE_COMPRESSED_APP
|
||||
: meshtastic_PortNum_STORE_FORWARD_PLUSPLUS_APP,
|
||||
&meshtastic_StoreForwardPlusPlus_msg),
|
||||
: ProtobufModule("StoreForwardpp", meshtastic_PortNum_TEXT_MESSAGE_COMPRESSED_APP, &meshtastic_StoreForwardPlusPlus_msg),
|
||||
concurrency::OSThread("StoreForwardpp")
|
||||
{
|
||||
LOG_WARN("StoreForwardPlusPlusModule init");
|
||||
|
||||
std::string db_path = portduino_config.sfpp_db_path + "storeforwardpp.db";
|
||||
LOG_INFO("Opening StoreForwardpp DB at %s", db_path.c_str());
|
||||
if (portduino_config.sfpp_stratum0)
|
||||
LOG_INFO("SF++ running as stratum0");
|
||||
LOG_WARN("SF++ stratum0");
|
||||
std::string db_path = portduino_config.sfpp_db_path + "storeforwardpp.db";
|
||||
LOG_WARN("Opening SF++ DB at %s", db_path.c_str());
|
||||
|
||||
int res = sqlite3_open(db_path.c_str(), &ppDb);
|
||||
if (res != SQLITE_OK) {
|
||||
LOG_ERROR("Cannot open database: %s", sqlite3_errmsg(ppDb));
|
||||
@@ -33,11 +40,12 @@ StoreForwardPlusPlusModule::StoreForwardPlusPlusModule()
|
||||
ppDb = nullptr;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
LOG_WARN("Result1 %u", res);
|
||||
|
||||
char *err = nullptr;
|
||||
|
||||
res = sqlite3_exec(ppDb, " \
|
||||
CREATE TABLE IF NOT EXISTS \
|
||||
channel_messages( \
|
||||
CREATE TABLE channel_messages( \
|
||||
destination INT NOT NULL, \
|
||||
sender INT NOT NULL, \
|
||||
packet_id INT NOT NULL, \
|
||||
@@ -51,43 +59,37 @@ StoreForwardPlusPlusModule::StoreForwardPlusPlusModule()
|
||||
PRIMARY KEY (message_hash) \
|
||||
);",
|
||||
NULL, NULL, &err);
|
||||
if (res != SQLITE_OK) {
|
||||
LOG_ERROR("Failed to create table: %s", sqlite3_errmsg(ppDb));
|
||||
}
|
||||
LOG_WARN("Result2 %u", res);
|
||||
if (err != nullptr)
|
||||
LOG_ERROR("%s", err);
|
||||
sqlite3_free(err);
|
||||
|
||||
res = sqlite3_exec(ppDb, " \
|
||||
CREATE TABLE IF NOT EXISTS \
|
||||
local_messages( \
|
||||
CREATE TABLE local_messages( \
|
||||
destination INT NOT NULL, \
|
||||
sender INT NOT NULL, \
|
||||
packet_id INT NOT NULL, \
|
||||
rx_time INT NOT NULL, \
|
||||
root_hash BLOB NOT NULL, \
|
||||
root_hash BLOB NOT NULL, \
|
||||
encrypted_bytes BLOB NOT NULL, \
|
||||
message_hash BLOB NOT NULL, \
|
||||
payload TEXT, \
|
||||
PRIMARY KEY (message_hash) \
|
||||
);",
|
||||
NULL, NULL, &err);
|
||||
if (res != SQLITE_OK) {
|
||||
LOG_ERROR("Failed to create table: %s", sqlite3_errmsg(ppDb));
|
||||
}
|
||||
LOG_WARN("Result2 %u", res);
|
||||
if (err != nullptr)
|
||||
LOG_ERROR("%s", err);
|
||||
sqlite3_free(err);
|
||||
|
||||
// create table DMs
|
||||
res = sqlite3_exec(ppDb, " \
|
||||
CREATE TABLE IF NOT EXISTS \
|
||||
direct_messages( \
|
||||
CREATE TABLE direct_messages( \
|
||||
destination INT NOT NULL, \
|
||||
sender INT NOT NULL, \
|
||||
packet_id INT NOT NULL, \
|
||||
rx_time INT NOT NULL, \
|
||||
root_hash BLOB NOT NULL, \
|
||||
root_hash BLOB NOT NULL, \
|
||||
commit_hash BLOB NOT NULL, \
|
||||
encrypted_bytes BLOB NOT NULL, \
|
||||
message_hash BLOB NOT NULL, \
|
||||
@@ -95,29 +97,22 @@ StoreForwardPlusPlusModule::StoreForwardPlusPlusModule()
|
||||
PRIMARY KEY (message_hash) \
|
||||
);",
|
||||
NULL, NULL, &err);
|
||||
|
||||
if (res != SQLITE_OK) {
|
||||
LOG_ERROR("Failed to create table: %s", sqlite3_errmsg(ppDb));
|
||||
}
|
||||
LOG_WARN("Result2 %u", res);
|
||||
if (err != nullptr)
|
||||
LOG_ERROR("%s", err);
|
||||
sqlite3_free(err);
|
||||
|
||||
// mappings table -- connects the root hashes to channel hashes and DM identifiers
|
||||
res = sqlite3_exec(ppDb, " \
|
||||
CREATE TABLE IF NOT EXISTS \
|
||||
mappings( \
|
||||
chain_type INT NOT NULL, \
|
||||
identifier INT NOT NULL, \
|
||||
root_hash BLOB NOT NULL, \
|
||||
count INT DEFAULT 0, \
|
||||
PRIMARY KEY (identifier) \
|
||||
CREATE TABLE mappings( \
|
||||
chain_type INT NOT NULL, \
|
||||
identifier INT NOT NULL, \
|
||||
root_hash BLOB NOT NULL, \
|
||||
count INT DEFAULT 0, \
|
||||
PRIMARY KEY (identifier) \
|
||||
);",
|
||||
NULL, NULL, &err);
|
||||
|
||||
if (res != SQLITE_OK) {
|
||||
LOG_ERROR("Failed to create table: %s", sqlite3_errmsg(ppDb));
|
||||
}
|
||||
LOG_WARN("Result2 %u", res);
|
||||
if (err != nullptr)
|
||||
LOG_ERROR("%s", err);
|
||||
sqlite3_free(err);
|
||||
@@ -176,15 +171,7 @@ StoreForwardPlusPlusModule::StoreForwardPlusPlusModule()
|
||||
|
||||
sqlite3_prepare_v2(ppDb, "UPDATE mappings SET count=? WHERE substr(root_hash,1,?)=?;", -1, &setChainCountStmt, NULL);
|
||||
|
||||
sqlite3_prepare_v2(ppDb, "SELECT count(*) FROM channel_messages WHERE substr(root_hash,1,?)=?;", -1, &getChainCountStmt,
|
||||
NULL);
|
||||
|
||||
sqlite3_prepare_v2(ppDb, "DELETE FROM local_messages WHERE rx_time < ?;", -1, &pruneScratchQueueStmt, NULL);
|
||||
|
||||
sqlite3_prepare_v2(ppDb,
|
||||
"DELETE FROM channel_messages WHERE commit_hash in ( select commit_hash from channel_messages where "
|
||||
"substr(root_hash,1,?)=? ORDER BY rowid ASC LIMIT 1);",
|
||||
-1, &trimOldestLinkStmt, NULL);
|
||||
sqlite3_prepare_v2(ppDb, "SELECT count FROM mappings WHERE substr(root_hash,1,?)=?;", -1, &getChainCountStmt, NULL);
|
||||
|
||||
encryptedOk = true;
|
||||
|
||||
@@ -193,6 +180,9 @@ StoreForwardPlusPlusModule::StoreForwardPlusPlusModule()
|
||||
|
||||
int32_t StoreForwardPlusPlusModule::runOnce()
|
||||
{
|
||||
// get number of links on chain
|
||||
// if more than max_chain, evict oldest
|
||||
LOG_WARN("StoreForward++ runONce");
|
||||
pendingRun = false;
|
||||
if (getRTCQuality() < RTCQualityNTP) {
|
||||
LOG_WARN("StoreForward++ deferred due to time quality %u", getRTCQuality());
|
||||
@@ -202,36 +192,26 @@ int32_t StoreForwardPlusPlusModule::runOnce()
|
||||
ChannelHash hash = channels.getHash(0);
|
||||
getOrAddRootFromChannelHash(hash, root_hash_bytes);
|
||||
uint32_t chain_count = getChainCount(root_hash_bytes, SFPP_HASH_SIZE);
|
||||
LOG_DEBUG("Chain count is %u", chain_count);
|
||||
while (chain_count > portduino_config.sfpp_max_chain) {
|
||||
LOG_DEBUG("Chain length %u exceeds max %u, evicting oldest", chain_count, portduino_config.sfpp_max_chain);
|
||||
trimOldestLink(root_hash_bytes, SFPP_HASH_SIZE);
|
||||
chain_count--;
|
||||
LOG_WARN("Chain count is %u", chain_count);
|
||||
if (chain_count > portduino_config.sfpp_max_chain) {
|
||||
LOG_WARN("Chain length %u exceeds max %u, evicting oldest", chain_count, portduino_config.sfpp_max_chain);
|
||||
}
|
||||
// evict old messages from scratch
|
||||
pruneScratchQueue();
|
||||
|
||||
if (memfll(root_hash_bytes, '\0', SFPP_HASH_SIZE)) {
|
||||
LOG_WARN("No root hash found, not sending");
|
||||
return portduino_config.sfpp_announce_interval * 60 * 1000;
|
||||
}
|
||||
|
||||
if (doing_split_send) {
|
||||
LOG_DEBUG("Sending split second half");
|
||||
broadcastLink(split_link_out, true, true);
|
||||
split_link_out = link_object();
|
||||
split_link_out.validObject = false;
|
||||
return portduino_config.sfpp_announce_interval * 60 * 1000;
|
||||
}
|
||||
|
||||
// get tip of chain for this channel
|
||||
link_object chain_end = getLinkFromCount(0, root_hash_bytes, SFPP_HASH_SIZE);
|
||||
LOG_WARN("latest payload %s", chain_end.payload.c_str());
|
||||
|
||||
if (chain_end.rx_time == 0) {
|
||||
LOG_WARN("Store and Forward++ database lookup returned null");
|
||||
if (portduino_config.sfpp_stratum0) {
|
||||
LOG_DEBUG("Stratum0 with no messages on chain, sending empty announce");
|
||||
LOG_WARN("Stratum0 with no messages on chain, sending empty announce");
|
||||
} else {
|
||||
LOG_DEBUG("Non-stratum0 with no chain, not sending");
|
||||
LOG_WARN("Non-stratum0 with no chain, not sending");
|
||||
return portduino_config.sfpp_announce_interval * 60 * 1000;
|
||||
}
|
||||
|
||||
@@ -250,7 +230,7 @@ int32_t StoreForwardPlusPlusModule::runOnce()
|
||||
p->channel = 0;
|
||||
p->hop_limit = portduino_config.sfpp_hops;
|
||||
p->hop_start = portduino_config.sfpp_hops;
|
||||
LOG_INFO("Send packet to mesh payload size %u", p->decoded.payload.size);
|
||||
LOG_INFO("Send packet to mesh");
|
||||
service->sendToMesh(p, RX_SRC_LOCAL, true);
|
||||
|
||||
return portduino_config.sfpp_announce_interval * 60 * 1000;
|
||||
@@ -282,11 +262,12 @@ ProcessMessage StoreForwardPlusPlusModule::handleReceived(const meshtastic_MeshP
|
||||
if (mp.which_payload_variant != meshtastic_MeshPacket_decoded_tag) {
|
||||
return ProcessMessage::CONTINUE; // Let others look at this message also if they want
|
||||
}
|
||||
LOG_WARN("in handleReceived");
|
||||
if (mp.decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP && mp.to == NODENUM_BROADCAST) {
|
||||
link_object lo = ingestTextPacket(mp, router->p_encrypted);
|
||||
|
||||
if (isInDB(lo.message_hash, lo.message_hash_len)) {
|
||||
LOG_DEBUG("Found text message in chain DB");
|
||||
LOG_WARN("found message in db");
|
||||
// We may have this message already, but we may not have the payload
|
||||
// if we do, we can update the payload in the database
|
||||
if (lo.payload != "")
|
||||
@@ -297,11 +278,11 @@ ProcessMessage StoreForwardPlusPlusModule::handleReceived(const meshtastic_MeshP
|
||||
if (!portduino_config.sfpp_stratum0) {
|
||||
if (!isInDB(lo.message_hash, lo.message_hash_len)) {
|
||||
if (lo.root_hash_len == 0) {
|
||||
LOG_DEBUG("Received text message, but no chain. Possibly no Stratum0 on local mesh.");
|
||||
LOG_WARN("Received message, but no known chain");
|
||||
return ProcessMessage::CONTINUE;
|
||||
}
|
||||
addToScratch(lo);
|
||||
LOG_DEBUG("added message to scratch db");
|
||||
LOG_WARN("added message to scratch");
|
||||
// send link to upstream?
|
||||
}
|
||||
return ProcessMessage::CONTINUE;
|
||||
@@ -315,8 +296,8 @@ ProcessMessage StoreForwardPlusPlusModule::handleReceived(const meshtastic_MeshP
|
||||
// canonAnnounce(lo.message_hash, lo.commit_hash, lo.root_hash, lo.rx_time);
|
||||
return ProcessMessage::CONTINUE; // Let others look at this message also if they want
|
||||
// TODO: Block packets from self?
|
||||
} else if (mp.decoded.portnum == portduino_config.sfpp_steal_port ? meshtastic_PortNum_TEXT_MESSAGE_COMPRESSED_APP
|
||||
: meshtastic_PortNum_STORE_FORWARD_PLUSPLUS_APP) {
|
||||
} else if (mp.decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_COMPRESSED_APP) {
|
||||
LOG_WARN("Got a STORE_FORWARD++ packet");
|
||||
meshtastic_StoreForwardPlusPlus scratch;
|
||||
pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, meshtastic_StoreForwardPlusPlus_fields, &scratch);
|
||||
handleReceivedProtobuf(mp, &scratch);
|
||||
@@ -327,13 +308,11 @@ ProcessMessage StoreForwardPlusPlusModule::handleReceived(const meshtastic_MeshP
|
||||
|
||||
bool StoreForwardPlusPlusModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_StoreForwardPlusPlus *t)
|
||||
{
|
||||
LOG_DEBUG("StoreForwardpp node %u sent us sf++ packet", mp.from);
|
||||
LOG_WARN("in handleReceivedProtobuf");
|
||||
LOG_WARN("Sfp++ node %u sent us sf++ packet", mp.from);
|
||||
printBytes("commit_hash ", t->commit_hash.bytes, t->commit_hash.size);
|
||||
printBytes("root_hash ", t->root_hash.bytes, t->root_hash.size);
|
||||
|
||||
link_object incoming_link;
|
||||
incoming_link.validObject = false;
|
||||
|
||||
if (t->sfpp_message_type == meshtastic_StoreForwardPlusPlus_SFPP_message_type_CANON_ANNOUNCE) {
|
||||
|
||||
// TODO: Regardless of where we are in the chain, if we have a newer message, send it back.
|
||||
@@ -349,19 +328,20 @@ bool StoreForwardPlusPlusModule::handleReceivedProtobuf(const meshtastic_MeshPac
|
||||
} else {
|
||||
uint8_t tmp_root_hash_bytes[SFPP_HASH_SIZE] = {0};
|
||||
|
||||
LOG_DEBUG("Received a CANON_ANNOUNCE");
|
||||
LOG_WARN("Received a CANON_ANNOUNCE");
|
||||
if (getRootFromChannelHash(router->p_encrypted->channel, tmp_root_hash_bytes)) {
|
||||
// we found the hash, check if it's the right one
|
||||
if (memcmp(tmp_root_hash_bytes, t->root_hash.bytes, t->root_hash.size) != 0) {
|
||||
LOG_INFO("Root hash does not match. Possibly two stratum0 nodes on the mesh?");
|
||||
LOG_WARN("Found root hash, and it doesn't match!");
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// TODO: size check
|
||||
addRootToMappings(router->p_encrypted->channel, t->root_hash.bytes);
|
||||
LOG_DEBUG("Adding root hash to mappings");
|
||||
LOG_WARN("Adding root hash to mappings");
|
||||
}
|
||||
if (t->encapsulated_rxtime == 0) {
|
||||
LOG_DEBUG("No encapsulated time, conclude the chain is empty");
|
||||
LOG_WARN("No encapsulated time, conclude the chain is empty");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -370,11 +350,12 @@ bool StoreForwardPlusPlusModule::handleReceivedProtobuf(const meshtastic_MeshPac
|
||||
|
||||
// get chain tip
|
||||
if (chain_end.rx_time != 0) {
|
||||
// TODO: size check
|
||||
if (memcmp(chain_end.commit_hash, t->commit_hash.bytes, t->commit_hash.size) == 0) {
|
||||
LOG_DEBUG("End of chain matches!");
|
||||
LOG_WARN("End of chain matches!");
|
||||
sendFromScratch(chain_end.root_hash);
|
||||
} else {
|
||||
LOG_DEBUG("End of chain does not match!");
|
||||
LOG_INFO("End of chain does not match!");
|
||||
|
||||
// We just got an end of chain announce, checking if we have seen this message and have it in scratch.
|
||||
if (isInScratch(t->message_hash.bytes, t->message_hash.size)) {
|
||||
@@ -383,7 +364,7 @@ bool StoreForwardPlusPlusModule::handleReceivedProtobuf(const meshtastic_MeshPac
|
||||
// we know exactly what it is
|
||||
if (t->message_hash.size >= 8 &&
|
||||
checkCommitHash(scratch_object, t->commit_hash.bytes, t->message_hash.size)) {
|
||||
scratch_object.rx_time = t->encapsulated_rxtime;
|
||||
|
||||
addToChain(scratch_object);
|
||||
removeFromScratch(scratch_object.message_hash, scratch_object.message_hash_len);
|
||||
// short circuit and return
|
||||
@@ -396,8 +377,9 @@ bool StoreForwardPlusPlusModule::handleReceivedProtobuf(const meshtastic_MeshPac
|
||||
}
|
||||
}
|
||||
} else { // if chainEnd()
|
||||
LOG_WARN("No Messages on this chain, request!");
|
||||
// todo request using portduino config initial_sync
|
||||
if (airTime->isTxAllowedChannelUtil(true)) {
|
||||
LOG_DEBUG("New chain, requesting last %u messages", portduino_config.sfpp_initial_sync);
|
||||
requestMessageCount(t->root_hash.bytes, t->root_hash.size, portduino_config.sfpp_initial_sync);
|
||||
}
|
||||
}
|
||||
@@ -405,12 +387,12 @@ bool StoreForwardPlusPlusModule::handleReceivedProtobuf(const meshtastic_MeshPac
|
||||
} else if (t->sfpp_message_type == meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_REQUEST) {
|
||||
uint8_t next_commit_hash[SFPP_HASH_SIZE] = {0};
|
||||
|
||||
LOG_DEBUG("Received link request");
|
||||
LOG_WARN("Received link request");
|
||||
|
||||
// If chain_count is set, this is a request for x messages up the chain.
|
||||
if (t->chain_count != 0 && t->root_hash.size >= 8) {
|
||||
link_object link_from_count = getLinkFromCount(t->chain_count, t->root_hash.bytes, t->root_hash.size);
|
||||
LOG_DEBUG("Count requested %d", t->chain_count);
|
||||
LOG_WARN("Count requested %d", t->chain_count);
|
||||
if (link_from_count.validObject)
|
||||
broadcastLink(link_from_count, true);
|
||||
|
||||
@@ -423,58 +405,12 @@ bool StoreForwardPlusPlusModule::handleReceivedProtobuf(const meshtastic_MeshPac
|
||||
|
||||
// if root and chain hashes are the same, grab the first message on the chain
|
||||
// if different, get the message directly after.
|
||||
// check if the root
|
||||
|
||||
} else if (t->sfpp_message_type == meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_PROVIDE) {
|
||||
LOG_DEBUG("Link Provide received!");
|
||||
LOG_WARN("Link Provide received!");
|
||||
|
||||
link_object incoming_link = ingestLinkMessage(t);
|
||||
} else if (t->sfpp_message_type == meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_PROVIDE_FIRSTHALF) {
|
||||
LOG_DEBUG("Link Provide First Half received!");
|
||||
split_link_in = ingestLinkMessage(t, false);
|
||||
doing_split_receive = true;
|
||||
split_link_in.validObject = true;
|
||||
return true;
|
||||
|
||||
} else if (t->sfpp_message_type == meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_PROVIDE_SECONDHALF) {
|
||||
LOG_DEBUG("Link Provide Second Half received!");
|
||||
if (!doing_split_receive) {
|
||||
LOG_DEBUG("Received second half without first half, ignoring");
|
||||
return true;
|
||||
}
|
||||
if (!split_link_in.validObject) {
|
||||
LOG_WARN("No first half stored, cannot combine");
|
||||
doing_split_receive = false;
|
||||
return true;
|
||||
}
|
||||
link_object second_half = ingestLinkMessage(t, false);
|
||||
if (split_link_in.encrypted_len + second_half.encrypted_len > 256) {
|
||||
LOG_WARN("Combined link too large");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (split_link_in.from == second_half.from && split_link_in.to == second_half.to &&
|
||||
split_link_in.root_hash_len == second_half.root_hash_len &&
|
||||
memcmp(split_link_in.root_hash, second_half.root_hash, split_link_in.root_hash_len) == 0 &&
|
||||
split_link_in.message_hash_len == second_half.message_hash_len &&
|
||||
memcmp(split_link_in.message_hash, second_half.message_hash, split_link_in.message_hash_len) == 0) {
|
||||
incoming_link = split_link_in;
|
||||
memcpy(&incoming_link.encrypted_bytes[split_link_in.encrypted_len], second_half.encrypted_bytes,
|
||||
second_half.encrypted_len);
|
||||
incoming_link.encrypted_len = split_link_in.encrypted_len + second_half.encrypted_len;
|
||||
|
||||
// append the encrypted bytes
|
||||
|
||||
// clear first half
|
||||
split_link_in = link_object();
|
||||
split_link_in.validObject = false;
|
||||
doing_split_receive = false;
|
||||
} else {
|
||||
LOG_WARN("No first half stored, cannot combine");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (incoming_link.validObject) {
|
||||
if (incoming_link.root_hash_len == 0) {
|
||||
LOG_WARN("Hash bytes not found for incoming link");
|
||||
return true;
|
||||
@@ -487,7 +423,7 @@ bool StoreForwardPlusPlusModule::handleReceivedProtobuf(const meshtastic_MeshPac
|
||||
|
||||
if (portduino_config.sfpp_stratum0) {
|
||||
if (isInDB(incoming_link.message_hash, incoming_link.message_hash_len)) {
|
||||
LOG_INFO("Received link already in chain");
|
||||
LOG_WARN("Received link already in chain");
|
||||
// TODO: respond with next link?
|
||||
return true;
|
||||
}
|
||||
@@ -516,16 +452,17 @@ bool StoreForwardPlusPlusModule::handleReceivedProtobuf(const meshtastic_MeshPac
|
||||
} else {
|
||||
// if this packet is new to us, we rebroadcast it, but only up to an hour old
|
||||
if (incoming_link.rx_time > getValidTime(RTCQuality::RTCQualityNTP, true) - rebroadcastTimeout) {
|
||||
LOG_WARN("Attempting to Rebroadcast message");
|
||||
rebroadcastLinkObject(incoming_link);
|
||||
}
|
||||
}
|
||||
requestNextMessage(incoming_link.root_hash, incoming_link.root_hash_len, incoming_link.commit_hash,
|
||||
incoming_link.commit_hash_len);
|
||||
requestNextMessage(t->root_hash.bytes, t->root_hash.size, t->commit_hash.bytes, t->commit_hash.size);
|
||||
} else {
|
||||
if (!isInScratch(incoming_link.message_hash, incoming_link.message_hash_len)) {
|
||||
addToScratch(incoming_link);
|
||||
LOG_INFO("added incoming non-canon message to scratch");
|
||||
LOG_WARN("added incoming non-canon message to scratch");
|
||||
if (incoming_link.rx_time > getValidTime(RTCQuality::RTCQualityNTP, true) - rebroadcastTimeout) {
|
||||
LOG_WARN("Attempting to Rebroadcast message");
|
||||
rebroadcastLinkObject(incoming_link);
|
||||
}
|
||||
}
|
||||
@@ -543,6 +480,7 @@ bool StoreForwardPlusPlusModule::getRootFromChannelHash(ChannelHash _ch_hash, ui
|
||||
sqlite3_step(getRootFromChannelHashStmt);
|
||||
uint8_t *tmp_root_hash = (uint8_t *)sqlite3_column_blob(getRootFromChannelHashStmt, 0);
|
||||
if (tmp_root_hash) {
|
||||
LOG_WARN("Found root hash!");
|
||||
memcpy(_root_hash, tmp_root_hash, SFPP_HASH_SIZE);
|
||||
found = true;
|
||||
}
|
||||
@@ -563,11 +501,13 @@ ChannelHash StoreForwardPlusPlusModule::getChannelHashFromRoot(uint8_t *_root_ha
|
||||
// return code indicates bytes in root hash, or 0 if not found/added
|
||||
size_t StoreForwardPlusPlusModule::getOrAddRootFromChannelHash(ChannelHash _ch_hash, uint8_t *_root_hash)
|
||||
{
|
||||
LOG_WARN("getOrAddRootFromChannelHash()");
|
||||
bool wasFound = getRootFromChannelHash(_ch_hash, _root_hash);
|
||||
|
||||
if (!wasFound) {
|
||||
if (portduino_config.sfpp_stratum0) {
|
||||
LOG_INFO("Generating Root hash!");
|
||||
LOG_WARN("Generating Root hash!");
|
||||
// generate root hash
|
||||
SHA256 root_hash;
|
||||
root_hash.update(&_ch_hash, sizeof(_ch_hash));
|
||||
NodeNum ourNode = nodeDB->getNodeNum();
|
||||
@@ -587,7 +527,13 @@ size_t StoreForwardPlusPlusModule::getOrAddRootFromChannelHash(ChannelHash _ch_h
|
||||
|
||||
void StoreForwardPlusPlusModule::addRootToMappings(ChannelHash _ch_hash, uint8_t *_root_hash)
|
||||
{
|
||||
LOG_WARN("addRootToMappings()");
|
||||
printBytes("_root_hash", _root_hash, SFPP_HASH_SIZE);
|
||||
|
||||
// write to the table
|
||||
int type = chain_types::channel_chain;
|
||||
// note, must be an int variable
|
||||
|
||||
sqlite3_bind_int(addRootToMappingsStmt, 1, type);
|
||||
sqlite3_bind_int(addRootToMappingsStmt, 2, _ch_hash);
|
||||
sqlite3_bind_blob(addRootToMappingsStmt, 3, _root_hash, SFPP_HASH_SIZE, NULL);
|
||||
@@ -621,9 +567,11 @@ void StoreForwardPlusPlusModule::requestNextMessage(uint8_t *_root_hash, size_t
|
||||
p->channel = 0;
|
||||
p->hop_limit = portduino_config.sfpp_hops;
|
||||
p->hop_start = portduino_config.sfpp_hops;
|
||||
LOG_INFO("Send packet to mesh");
|
||||
service->sendToMesh(p, RX_SRC_LOCAL, true);
|
||||
}
|
||||
|
||||
// todo requestMessageCount
|
||||
void StoreForwardPlusPlusModule::requestMessageCount(uint8_t *_root_hash, size_t _root_hash_len, uint32_t count)
|
||||
{
|
||||
|
||||
@@ -645,12 +593,18 @@ void StoreForwardPlusPlusModule::requestMessageCount(uint8_t *_root_hash, size_t
|
||||
p->channel = 0;
|
||||
p->hop_limit = portduino_config.sfpp_hops;
|
||||
p->hop_start = portduino_config.sfpp_hops;
|
||||
LOG_INFO("Send packet to mesh");
|
||||
service->sendToMesh(p, RX_SRC_LOCAL, true);
|
||||
}
|
||||
|
||||
bool StoreForwardPlusPlusModule::getNextHash(uint8_t *_root_hash, size_t _root_hash_len, uint8_t *_commit_hash,
|
||||
size_t _commit_hash_len, uint8_t *next_commit_hash)
|
||||
{
|
||||
LOG_WARN("getNextHash");
|
||||
|
||||
// ChannelHash _channel_hash = getChannelHashFromRoot(_root_hash, _root_hash_len);
|
||||
// LOG_WARN("_channel_hash %u", _channel_hash);
|
||||
|
||||
int rc;
|
||||
sqlite3_bind_int(getNextHashStmt, 1, _root_hash_len);
|
||||
sqlite3_bind_blob(getNextHashStmt, 2, _root_hash, _root_hash_len, NULL);
|
||||
@@ -660,10 +614,11 @@ bool StoreForwardPlusPlusModule::getNextHash(uint8_t *_root_hash, size_t _root_h
|
||||
if (memcmp(_root_hash, _commit_hash, _commit_hash_len) == 0) {
|
||||
rc = sqlite3_step(getNextHashStmt);
|
||||
if (rc != SQLITE_OK) {
|
||||
LOG_WARN("Get Hash error %u, %s", rc, sqlite3_errmsg(ppDb));
|
||||
LOG_WARN("here2 %u, %s", rc, sqlite3_errmsg(ppDb));
|
||||
}
|
||||
uint8_t *tmp_commit_hash = (uint8_t *)sqlite3_column_blob(getNextHashStmt, 0);
|
||||
if (tmp_commit_hash == nullptr) {
|
||||
LOG_WARN("No next hash found");
|
||||
sqlite3_reset(getNextHashStmt);
|
||||
return false;
|
||||
}
|
||||
@@ -673,11 +628,13 @@ bool StoreForwardPlusPlusModule::getNextHash(uint8_t *_root_hash, size_t _root_h
|
||||
} else {
|
||||
bool found_hash = false;
|
||||
|
||||
LOG_WARN("Looking for next hashes");
|
||||
uint8_t *tmp_commit_hash;
|
||||
while (sqlite3_step(getNextHashStmt) != SQLITE_DONE) {
|
||||
tmp_commit_hash = (uint8_t *)sqlite3_column_blob(getNextHashStmt, 0);
|
||||
|
||||
if (found_hash) {
|
||||
LOG_WARN("Found hash");
|
||||
memcpy(next_commit_hash, tmp_commit_hash, SFPP_HASH_SIZE);
|
||||
next_hash = true;
|
||||
break;
|
||||
@@ -693,71 +650,56 @@ bool StoreForwardPlusPlusModule::getNextHash(uint8_t *_root_hash, size_t _root_h
|
||||
|
||||
void StoreForwardPlusPlusModule::broadcastLink(uint8_t *_commit_hash, size_t _commit_hash_len)
|
||||
{
|
||||
int rc;
|
||||
sqlite3_bind_int(getLinkStmt, 1, _commit_hash_len);
|
||||
sqlite3_bind_blob(getLinkStmt, 2, _commit_hash, _commit_hash_len, NULL);
|
||||
int res = sqlite3_step(getLinkStmt);
|
||||
link_object lo;
|
||||
LOG_WARN("%d", sqlite3_step(getLinkStmt));
|
||||
|
||||
lo.to = sqlite3_column_int(getLinkStmt, 0);
|
||||
lo.from = sqlite3_column_int(getLinkStmt, 1);
|
||||
lo.id = sqlite3_column_int(getLinkStmt, 2);
|
||||
meshtastic_StoreForwardPlusPlus storeforward = meshtastic_StoreForwardPlusPlus_init_zero;
|
||||
storeforward.sfpp_message_type = meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_PROVIDE;
|
||||
|
||||
storeforward.encapsulated_to = sqlite3_column_int(getLinkStmt, 0);
|
||||
storeforward.encapsulated_from = sqlite3_column_int(getLinkStmt, 1);
|
||||
storeforward.encapsulated_id = sqlite3_column_int(getLinkStmt, 2);
|
||||
|
||||
uint8_t *_payload = (uint8_t *)sqlite3_column_blob(getLinkStmt, 3);
|
||||
lo.encrypted_len = sqlite3_column_bytes(getLinkStmt, 3);
|
||||
memcpy(lo.encrypted_bytes, _payload, lo.encrypted_len);
|
||||
storeforward.message.size = sqlite3_column_bytes(getLinkStmt, 3);
|
||||
memcpy(storeforward.message.bytes, _payload, storeforward.message.size);
|
||||
|
||||
uint8_t *_message_hash = (uint8_t *)sqlite3_column_blob(getLinkStmt, 4);
|
||||
lo.message_hash_len = SFPP_HASH_SIZE;
|
||||
memcpy(lo.message_hash, _message_hash, lo.message_hash_len);
|
||||
// storeforward.message_hash.size = 8;
|
||||
// memcpy(storeforward.message_hash.bytes, _message_hash, storeforward.message_hash.size);
|
||||
|
||||
lo.rx_time = sqlite3_column_int(getLinkStmt, 5);
|
||||
storeforward.encapsulated_rxtime = sqlite3_column_int(getLinkStmt, 5);
|
||||
|
||||
uint8_t *_tmp_commit_hash = (uint8_t *)sqlite3_column_blob(getLinkStmt, 6);
|
||||
|
||||
lo.commit_hash_len = 8;
|
||||
memcpy(lo.commit_hash, _tmp_commit_hash, lo.commit_hash_len);
|
||||
storeforward.commit_hash.size = 8;
|
||||
memcpy(storeforward.commit_hash.bytes, _tmp_commit_hash, storeforward.commit_hash.size);
|
||||
|
||||
uint8_t *_root_hash = (uint8_t *)sqlite3_column_blob(getLinkStmt, 7);
|
||||
|
||||
lo.root_hash_len = 8;
|
||||
memcpy(lo.root_hash, _root_hash, lo.root_hash_len);
|
||||
storeforward.root_hash.size = 8;
|
||||
memcpy(storeforward.root_hash.bytes, _root_hash, storeforward.root_hash.size);
|
||||
|
||||
sqlite3_reset(getLinkStmt);
|
||||
|
||||
meshtastic_MeshPacket *p = allocDataProtobuf(storeforward);
|
||||
p->to = NODENUM_BROADCAST;
|
||||
p->decoded.want_response = false;
|
||||
p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
|
||||
p->channel = 0;
|
||||
p->hop_limit = portduino_config.sfpp_hops;
|
||||
p->hop_start = portduino_config.sfpp_hops;
|
||||
LOG_INFO("Send link to mesh");
|
||||
broadcastLink(lo, false);
|
||||
service->sendToMesh(p, RX_SRC_LOCAL, true);
|
||||
}
|
||||
|
||||
// TODO if stratum0, send the chain count
|
||||
void StoreForwardPlusPlusModule::broadcastLink(link_object &lo, bool full_commit_hash, bool is_split_second_half)
|
||||
void StoreForwardPlusPlusModule::broadcastLink(link_object &lo, bool full_commit_hash)
|
||||
{
|
||||
meshtastic_StoreForwardPlusPlus storeforward = meshtastic_StoreForwardPlusPlus_init_zero;
|
||||
storeforward.sfpp_message_type = meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_PROVIDE;
|
||||
if (lo.encrypted_len > 180) {
|
||||
LOG_WARN("Link too large to send (%u bytes)", lo.encrypted_len);
|
||||
doing_split_send = true;
|
||||
storeforward.message_hash.size = SFPP_SHORT_HASH_SIZE;
|
||||
memcpy(storeforward.message_hash.bytes, lo.message_hash, storeforward.message_hash.size);
|
||||
link_object full_link = lo;
|
||||
split_link_out = lo;
|
||||
size_t half_size = lo.encrypted_len / 2;
|
||||
storeforward.sfpp_message_type = meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_PROVIDE_FIRSTHALF;
|
||||
lo.encrypted_len = half_size;
|
||||
split_link_out.encrypted_len = full_link.encrypted_len - half_size;
|
||||
memcpy(split_link_out.encrypted_bytes, &full_link.encrypted_bytes[half_size], split_link_out.encrypted_len);
|
||||
setIntervalFromNow(30 * 1000); // send second half in 30 seconds
|
||||
|
||||
} else if (is_split_second_half) {
|
||||
storeforward.sfpp_message_type = meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_PROVIDE_SECONDHALF;
|
||||
storeforward.message_hash.size = SFPP_SHORT_HASH_SIZE;
|
||||
memcpy(storeforward.message_hash.bytes, lo.message_hash, storeforward.message_hash.size);
|
||||
doing_split_send = false;
|
||||
}
|
||||
|
||||
storeforward.encapsulated_to = lo.to;
|
||||
if (storeforward.encapsulated_to == NODENUM_BROADCAST) {
|
||||
storeforward.encapsulated_to = 0;
|
||||
}
|
||||
storeforward.encapsulated_from = lo.from;
|
||||
storeforward.encapsulated_id = lo.id;
|
||||
|
||||
@@ -772,11 +714,11 @@ void StoreForwardPlusPlusModule::broadcastLink(link_object &lo, bool full_commit
|
||||
if (full_commit_hash)
|
||||
storeforward.commit_hash.size = lo.commit_hash_len;
|
||||
else
|
||||
storeforward.commit_hash.size = SFPP_SHORT_HASH_SIZE;
|
||||
storeforward.commit_hash.size = 8;
|
||||
memcpy(storeforward.commit_hash.bytes, lo.commit_hash, storeforward.commit_hash.size);
|
||||
}
|
||||
|
||||
storeforward.root_hash.size = SFPP_SHORT_HASH_SIZE;
|
||||
storeforward.root_hash.size = 8;
|
||||
memcpy(storeforward.root_hash.bytes, lo.root_hash, storeforward.root_hash.size);
|
||||
|
||||
sqlite3_reset(getLinkStmt);
|
||||
@@ -796,9 +738,10 @@ void StoreForwardPlusPlusModule::broadcastLink(link_object &lo, bool full_commit
|
||||
StoreForwardPlusPlusModule::link_object StoreForwardPlusPlusModule::getLink(uint8_t *_commit_hash, size_t _commit_hash_len)
|
||||
{
|
||||
link_object lo;
|
||||
int rc;
|
||||
sqlite3_bind_int(getLinkStmt, 1, _commit_hash_len);
|
||||
sqlite3_bind_blob(getLinkStmt, 2, _commit_hash, _commit_hash_len, NULL);
|
||||
int res = sqlite3_step(getLinkStmt);
|
||||
LOG_WARN("%d", sqlite3_step(getLinkStmt));
|
||||
|
||||
lo.to = sqlite3_column_int(getLinkStmt, 0);
|
||||
lo.from = sqlite3_column_int(getLinkStmt, 1);
|
||||
@@ -835,38 +778,54 @@ StoreForwardPlusPlusModule::link_object StoreForwardPlusPlusModule::getLink(uint
|
||||
|
||||
bool StoreForwardPlusPlusModule::sendFromScratch(uint8_t *root_hash)
|
||||
{
|
||||
link_object lo;
|
||||
LOG_WARN("sendFromScratch");
|
||||
// "select destination, sender, packet_id, channel_hash, encrypted_bytes, message_hash, rx_time \
|
||||
// from local_messages order by rx_time desc LIMIT 1;"
|
||||
sqlite3_bind_blob(fromScratchStmt, 1, root_hash, SFPP_HASH_SIZE, NULL);
|
||||
if (sqlite3_step(fromScratchStmt) == SQLITE_DONE) {
|
||||
LOG_WARN("No messages in scratch to forward");
|
||||
return false;
|
||||
}
|
||||
lo.to = sqlite3_column_int(fromScratchStmt, 0);
|
||||
lo.from = sqlite3_column_int(fromScratchStmt, 1);
|
||||
lo.id = sqlite3_column_int(fromScratchStmt, 2);
|
||||
|
||||
meshtastic_StoreForwardPlusPlus storeforward = meshtastic_StoreForwardPlusPlus_init_zero;
|
||||
storeforward.sfpp_message_type = meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_PROVIDE;
|
||||
|
||||
storeforward.encapsulated_to = sqlite3_column_int(fromScratchStmt, 0);
|
||||
storeforward.encapsulated_from = sqlite3_column_int(fromScratchStmt, 1);
|
||||
storeforward.encapsulated_id = sqlite3_column_int(fromScratchStmt, 2);
|
||||
|
||||
uint8_t *_encrypted = (uint8_t *)sqlite3_column_blob(fromScratchStmt, 3);
|
||||
lo.encrypted_len = sqlite3_column_bytes(fromScratchStmt, 3);
|
||||
memcpy(lo.encrypted_bytes, _encrypted, lo.encrypted_len);
|
||||
storeforward.message.size = sqlite3_column_bytes(fromScratchStmt, 3);
|
||||
memcpy(storeforward.message.bytes, _encrypted, storeforward.message.size);
|
||||
|
||||
uint8_t *_message_hash = (uint8_t *)sqlite3_column_blob(fromScratchStmt, 4);
|
||||
// storeforward.message_hash.size = SFPP_HASH_SIZE;
|
||||
// memcpy(storeforward.message_hash.bytes, _message_hash, storeforward.message_hash.size);
|
||||
|
||||
lo.rx_time = sqlite3_column_int(fromScratchStmt, 5);
|
||||
storeforward.encapsulated_rxtime = sqlite3_column_int(fromScratchStmt, 5);
|
||||
|
||||
lo.root_hash_len = SFPP_SHORT_HASH_SIZE;
|
||||
memcpy(lo.root_hash, root_hash, lo.root_hash_len);
|
||||
storeforward.root_hash.size = SFPP_SHORT_HASH_SIZE;
|
||||
memcpy(storeforward.root_hash.bytes, root_hash, SFPP_SHORT_HASH_SIZE);
|
||||
|
||||
sqlite3_reset(fromScratchStmt);
|
||||
|
||||
meshtastic_MeshPacket *p = allocDataProtobuf(storeforward);
|
||||
p->to = NODENUM_BROADCAST;
|
||||
p->decoded.want_response = false;
|
||||
p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
|
||||
p->channel = 0;
|
||||
p->hop_limit = portduino_config.sfpp_hops;
|
||||
p->hop_start = portduino_config.sfpp_hops;
|
||||
printBytes("Send link to mesh ", _message_hash, 8);
|
||||
LOG_WARN("Size: %d", lo.encrypted_len);
|
||||
printBytes("encrypted ", lo.encrypted_bytes, lo.encrypted_len);
|
||||
broadcastLink(lo, false);
|
||||
|
||||
LOG_WARN("Size: %d", storeforward.message.size);
|
||||
printBytes("encrypted ", storeforward.message.bytes, storeforward.message.size);
|
||||
service->sendToMesh(p, RX_SRC_LOCAL, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StoreForwardPlusPlusModule::addToChain(link_object &lo)
|
||||
{
|
||||
LOG_WARN("Add to chain");
|
||||
link_object chain_end = getLinkFromCount(0, lo.root_hash, lo.root_hash_len);
|
||||
|
||||
// we may need to calculate the full commit hash at this point
|
||||
@@ -884,6 +843,7 @@ bool StoreForwardPlusPlusModule::addToChain(link_object &lo)
|
||||
}
|
||||
|
||||
commit_hash.update(lo.message_hash, SFPP_HASH_SIZE);
|
||||
// message_hash.update(&mp.rx_time, sizeof(mp.rx_time));
|
||||
commit_hash.finalize(lo.commit_hash, SFPP_HASH_SIZE);
|
||||
}
|
||||
lo.counter = chain_end.counter + 1;
|
||||
@@ -936,12 +896,9 @@ bool StoreForwardPlusPlusModule::addToScratch(link_object &lo)
|
||||
sqlite3_bind_int(scratch_insert_stmt, 7, lo.rx_time);
|
||||
// payload
|
||||
sqlite3_bind_text(scratch_insert_stmt, 8, lo.payload.c_str(), lo.payload.length(), NULL);
|
||||
const char *_error_mesg = sqlite3_errmsg(ppDb);
|
||||
|
||||
int res = sqlite3_step(scratch_insert_stmt);
|
||||
if (res != SQLITE_OK) {
|
||||
const char *_error_mesg = sqlite3_errmsg(ppDb);
|
||||
LOG_WARN("step %u, %s", res, _error_mesg);
|
||||
}
|
||||
LOG_WARN("step %u, %s", sqlite3_step(scratch_insert_stmt), _error_mesg);
|
||||
sqlite3_reset(scratch_insert_stmt);
|
||||
return true;
|
||||
}
|
||||
@@ -949,6 +906,7 @@ bool StoreForwardPlusPlusModule::addToScratch(link_object &lo)
|
||||
void StoreForwardPlusPlusModule::canonAnnounce(uint8_t *_message_hash, uint8_t *_commit_hash, uint8_t *_root_hash,
|
||||
uint32_t _rx_time)
|
||||
{
|
||||
LOG_WARN("canonAnnounce()");
|
||||
meshtastic_StoreForwardPlusPlus storeforward = meshtastic_StoreForwardPlusPlus_init_zero;
|
||||
storeforward.sfpp_message_type = meshtastic_StoreForwardPlusPlus_SFPP_message_type_CANON_ANNOUNCE;
|
||||
// set root hash
|
||||
@@ -975,7 +933,7 @@ void StoreForwardPlusPlusModule::canonAnnounce(uint8_t *_message_hash, uint8_t *
|
||||
p->channel = 0;
|
||||
p->hop_limit = portduino_config.sfpp_hops;
|
||||
p->hop_start = portduino_config.sfpp_hops;
|
||||
LOG_INFO("Send packet to mesh payload size %u", p->decoded.payload.size);
|
||||
LOG_INFO("Send packet to mesh");
|
||||
service->sendToMesh(p, RX_SRC_LOCAL, true);
|
||||
}
|
||||
|
||||
@@ -993,6 +951,7 @@ bool StoreForwardPlusPlusModule::isInDB(uint8_t *message_hash_bytes, size_t mess
|
||||
|
||||
bool StoreForwardPlusPlusModule::isInScratch(uint8_t *message_hash_bytes, size_t message_hash_len)
|
||||
{
|
||||
LOG_WARN("isInScratch");
|
||||
sqlite3_bind_int(checkScratch, 1, message_hash_len);
|
||||
sqlite3_bind_blob(checkScratch, 2, message_hash_bytes, message_hash_len, NULL);
|
||||
sqlite3_step(checkScratch);
|
||||
@@ -1005,10 +964,13 @@ bool StoreForwardPlusPlusModule::isInScratch(uint8_t *message_hash_bytes, size_t
|
||||
|
||||
void StoreForwardPlusPlusModule::removeFromScratch(uint8_t *message_hash_bytes, size_t message_hash_len)
|
||||
{
|
||||
LOG_WARN("removeFromScratch");
|
||||
printBytes("removing from scratch: ", message_hash_bytes, message_hash_len);
|
||||
sqlite3_bind_int(removeScratch, 1, message_hash_len);
|
||||
sqlite3_bind_blob(removeScratch, 2, message_hash_bytes, message_hash_len, NULL);
|
||||
sqlite3_step(removeScratch);
|
||||
int numberFound = sqlite3_column_int(removeScratch, 0);
|
||||
LOG_WARN("removed %d entries from scratch", numberFound);
|
||||
sqlite3_reset(removeScratch);
|
||||
}
|
||||
|
||||
@@ -1019,24 +981,23 @@ void StoreForwardPlusPlusModule::updatePayload(uint8_t *message_hash_bytes, size
|
||||
sqlite3_bind_int(updatePayloadStmt, 2, message_hash_len);
|
||||
sqlite3_bind_blob(updatePayloadStmt, 3, message_hash_bytes, message_hash_len, NULL);
|
||||
auto res = sqlite3_step(updatePayloadStmt);
|
||||
if (res != SQLITE_OK) {
|
||||
const char *_error_mesg = sqlite3_errmsg(ppDb);
|
||||
LOG_WARN("step error %u, %s", res, _error_mesg);
|
||||
}
|
||||
const char *_error_mesg = sqlite3_errmsg(ppDb);
|
||||
LOG_WARN("step %u, %s", res, _error_mesg);
|
||||
sqlite3_reset(updatePayloadStmt);
|
||||
}
|
||||
|
||||
StoreForwardPlusPlusModule::link_object StoreForwardPlusPlusModule::getFromScratch(uint8_t *message_hash_bytes, size_t hash_len)
|
||||
{
|
||||
|
||||
// vscode wrote this
|
||||
LOG_WARN("getFromScratch");
|
||||
link_object lo;
|
||||
|
||||
sqlite3_bind_int(fromScratchByHashStmt, 1, hash_len);
|
||||
sqlite3_bind_blob(fromScratchByHashStmt, 2, message_hash_bytes, hash_len, NULL);
|
||||
auto res = sqlite3_step(fromScratchByHashStmt);
|
||||
if (res != SQLITE_ROW && res != SQLITE_OK) {
|
||||
const char *_error_mesg = sqlite3_errmsg(ppDb);
|
||||
LOG_WARN("step error %u, %s", res, _error_mesg);
|
||||
}
|
||||
const char *_error_mesg = sqlite3_errmsg(ppDb);
|
||||
LOG_WARN("step %u, %s", res, _error_mesg);
|
||||
lo.to = sqlite3_column_int(fromScratchByHashStmt, 0);
|
||||
lo.from = sqlite3_column_int(fromScratchByHashStmt, 1);
|
||||
lo.id = sqlite3_column_int(fromScratchByHashStmt, 2);
|
||||
@@ -1061,6 +1022,7 @@ StoreForwardPlusPlusModule::link_object StoreForwardPlusPlusModule::getFromScrat
|
||||
StoreForwardPlusPlusModule::link_object
|
||||
StoreForwardPlusPlusModule::ingestTextPacket(const meshtastic_MeshPacket &mp, const meshtastic_MeshPacket *encrypted_meshpacket)
|
||||
{
|
||||
LOG_WARN("ingestTextPacket()");
|
||||
link_object lo;
|
||||
SHA256 message_hash;
|
||||
lo.to = mp.to;
|
||||
@@ -1084,16 +1046,12 @@ StoreForwardPlusPlusModule::ingestTextPacket(const meshtastic_MeshPacket &mp, co
|
||||
return lo;
|
||||
}
|
||||
|
||||
StoreForwardPlusPlusModule::link_object StoreForwardPlusPlusModule::ingestLinkMessage(meshtastic_StoreForwardPlusPlus *t,
|
||||
bool recalc)
|
||||
StoreForwardPlusPlusModule::link_object StoreForwardPlusPlusModule::ingestLinkMessage(meshtastic_StoreForwardPlusPlus *t)
|
||||
{
|
||||
// TODO: If not stratum0, injest the chain count
|
||||
link_object lo;
|
||||
|
||||
lo.to = t->encapsulated_to;
|
||||
if (lo.to == 0) {
|
||||
lo.to = NODENUM_BROADCAST;
|
||||
}
|
||||
lo.from = t->encapsulated_from;
|
||||
lo.id = t->encapsulated_id;
|
||||
lo.rx_time = t->encapsulated_rxtime;
|
||||
@@ -1104,60 +1062,52 @@ StoreForwardPlusPlusModule::link_object StoreForwardPlusPlusModule::ingestLinkMe
|
||||
|
||||
memcpy(lo.encrypted_bytes, t->message.bytes, t->message.size);
|
||||
lo.encrypted_len = t->message.size;
|
||||
if (recalc) {
|
||||
message_hash.reset();
|
||||
message_hash.update(lo.encrypted_bytes, lo.encrypted_len);
|
||||
message_hash.update(&lo.to, sizeof(lo.to));
|
||||
message_hash.update(&lo.from, sizeof(lo.from));
|
||||
message_hash.update(&lo.id, sizeof(lo.id));
|
||||
message_hash.finalize(lo.message_hash, SFPP_HASH_SIZE);
|
||||
lo.message_hash_len = SFPP_HASH_SIZE;
|
||||
|
||||
// look up full root hash and copy over the partial if it matches
|
||||
if (lookUpFullRootHash(t->root_hash.bytes, t->root_hash.size, lo.root_hash)) {
|
||||
printBytes("Found full root hash: 0x", lo.root_hash, SFPP_HASH_SIZE);
|
||||
lo.root_hash_len = SFPP_HASH_SIZE;
|
||||
} else {
|
||||
LOG_WARN("root hash does not match %d bytes", t->root_hash.size);
|
||||
lo.root_hash_len = 0;
|
||||
lo.validObject = false;
|
||||
return lo;
|
||||
}
|
||||
message_hash.reset();
|
||||
message_hash.update(lo.encrypted_bytes, lo.encrypted_len);
|
||||
message_hash.update(&lo.to, sizeof(lo.to));
|
||||
message_hash.update(&lo.from, sizeof(lo.from));
|
||||
message_hash.update(&lo.id, sizeof(lo.id));
|
||||
message_hash.finalize(lo.message_hash, SFPP_HASH_SIZE);
|
||||
lo.message_hash_len = SFPP_HASH_SIZE;
|
||||
|
||||
if (t->commit_hash.size == SFPP_HASH_SIZE && getChainCount(t->root_hash.bytes, t->root_hash.size) == 0 &&
|
||||
portduino_config.sfpp_initial_sync != 0 && !portduino_config.sfpp_stratum0) {
|
||||
lo.commit_hash_len = SFPP_HASH_SIZE;
|
||||
memcpy(lo.commit_hash, t->commit_hash.bytes, SFPP_HASH_SIZE);
|
||||
|
||||
} else if (t->commit_hash.size > 0) {
|
||||
// calculate the full commit hash and replace the partial if it matches
|
||||
if (checkCommitHash(lo, t->commit_hash.bytes, t->commit_hash.size)) {
|
||||
printBytes("commit hash matches: 0x", t->commit_hash.bytes, t->commit_hash.size);
|
||||
} else {
|
||||
LOG_WARN("commit hash does not match, rejecting link.");
|
||||
lo.commit_hash_len = 0;
|
||||
lo.validObject = false;
|
||||
}
|
||||
}
|
||||
// look up full root hash and copy over the partial if it matches
|
||||
if (lookUpFullRootHash(t->root_hash.bytes, t->root_hash.size, lo.root_hash)) {
|
||||
printBytes("Found full root hash: 0x", lo.root_hash, SFPP_HASH_SIZE);
|
||||
lo.root_hash_len = SFPP_HASH_SIZE;
|
||||
} else {
|
||||
memcpy(lo.message_hash, t->message_hash.bytes, t->message_hash.size);
|
||||
lo.message_hash_len = t->message_hash.size;
|
||||
memcpy(lo.root_hash, t->root_hash.bytes, t->root_hash.size);
|
||||
lo.root_hash_len = t->root_hash.size;
|
||||
memcpy(lo.commit_hash, t->commit_hash.bytes, t->commit_hash.size);
|
||||
lo.commit_hash_len = t->commit_hash.size;
|
||||
LOG_WARN("root hash does not match %d bytes", t->root_hash.size);
|
||||
lo.root_hash_len = 0;
|
||||
lo.validObject = false;
|
||||
return lo;
|
||||
}
|
||||
|
||||
if (t->commit_hash.size == SFPP_HASH_SIZE && getChainCount(t->root_hash.bytes, t->root_hash.size) == 0 &&
|
||||
portduino_config.sfpp_initial_sync != 0 && !portduino_config.sfpp_stratum0) {
|
||||
LOG_WARN("Accepting SF++ ch ");
|
||||
lo.commit_hash_len = SFPP_HASH_SIZE;
|
||||
memcpy(lo.commit_hash, t->commit_hash.bytes, SFPP_HASH_SIZE);
|
||||
|
||||
} else if (t->commit_hash.size > 0) {
|
||||
// calculate the full commit hash and replace the partial if it matches
|
||||
if (checkCommitHash(lo, t->commit_hash.bytes, t->commit_hash.size)) {
|
||||
printBytes("commit hash matches: 0x", t->commit_hash.bytes, t->commit_hash.size);
|
||||
} else {
|
||||
LOG_WARN("commit hash does not match");
|
||||
lo.commit_hash_len = 0;
|
||||
lo.validObject = false;
|
||||
}
|
||||
}
|
||||
|
||||
// we don't ever get the payload here, so it's always an empty string
|
||||
lo.payload = "";
|
||||
lo.validObject = true;
|
||||
|
||||
return lo;
|
||||
}
|
||||
|
||||
void StoreForwardPlusPlusModule::rebroadcastLinkObject(link_object &lo)
|
||||
{
|
||||
LOG_INFO("Attempting to Rebroadcast a message received over SF++");
|
||||
LOG_WARN("Attempting to Rebroadcast1");
|
||||
meshtastic_MeshPacket *p = router->allocForSending();
|
||||
p->to = lo.to;
|
||||
p->from = lo.from;
|
||||
@@ -1186,7 +1136,7 @@ bool StoreForwardPlusPlusModule::checkCommitHash(StoreForwardPlusPlusModule::lin
|
||||
commit_hash.update(chain_end.commit_hash, SFPP_HASH_SIZE);
|
||||
} else {
|
||||
if (lo.root_hash_len != SFPP_HASH_SIZE) {
|
||||
LOG_ERROR("Short root hash in link object, cannot create new chain");
|
||||
LOG_WARN("Short root hash in link object, cannot create new chain");
|
||||
return false;
|
||||
}
|
||||
printBytes("new chain root: 0x", lo.root_hash, SFPP_HASH_SIZE);
|
||||
@@ -1206,13 +1156,14 @@ bool StoreForwardPlusPlusModule::checkCommitHash(StoreForwardPlusPlusModule::lin
|
||||
bool StoreForwardPlusPlusModule::lookUpFullRootHash(uint8_t *partial_root_hash, size_t partial_root_hash_len,
|
||||
uint8_t *full_root_hash)
|
||||
{
|
||||
LOG_WARN("lookUpFullRootHash");
|
||||
printBytes("partial_root_hash", partial_root_hash, partial_root_hash_len);
|
||||
sqlite3_bind_int(getFullRootHashStmt, 1, partial_root_hash_len);
|
||||
sqlite3_bind_blob(getFullRootHashStmt, 2, partial_root_hash, partial_root_hash_len, NULL);
|
||||
sqlite3_step(getFullRootHashStmt);
|
||||
uint8_t *tmp_root_hash = (uint8_t *)sqlite3_column_blob(getFullRootHashStmt, 0);
|
||||
if (tmp_root_hash) {
|
||||
LOG_DEBUG("Found full root hash!");
|
||||
LOG_WARN("Found full root hash!");
|
||||
memcpy(full_root_hash, tmp_root_hash, SFPP_HASH_SIZE);
|
||||
sqlite3_reset(getFullRootHashStmt);
|
||||
return true;
|
||||
@@ -1223,22 +1174,16 @@ bool StoreForwardPlusPlusModule::lookUpFullRootHash(uint8_t *partial_root_hash,
|
||||
|
||||
void StoreForwardPlusPlusModule::setChainCount(uint8_t *root_hash, size_t root_hash_len, uint32_t count)
|
||||
{
|
||||
sqlite3_bind_int(setChainCountStmt, 1, root_hash_len);
|
||||
sqlite3_bind_blob(setChainCountStmt, 2, root_hash, root_hash_len, NULL);
|
||||
sqlite3_bind_int(setChainCountStmt, 3, count);
|
||||
sqlite3_bind_blob(setChainCountStmt, 1, root_hash, root_hash_len, NULL);
|
||||
sqlite3_bind_int(setChainCountStmt, 2, count);
|
||||
sqlite3_step(setChainCountStmt);
|
||||
sqlite3_reset(setChainCountStmt);
|
||||
}
|
||||
|
||||
uint32_t StoreForwardPlusPlusModule::getChainCount(uint8_t *root_hash, size_t root_hash_len)
|
||||
{
|
||||
sqlite3_bind_int(getChainCountStmt, 1, root_hash_len);
|
||||
sqlite3_bind_blob(getChainCountStmt, 2, root_hash, root_hash_len, NULL);
|
||||
|
||||
int res = sqlite3_step(getChainCountStmt);
|
||||
if (res != SQLITE_OK && res != SQLITE_DONE && res != SQLITE_ROW) {
|
||||
LOG_ERROR("getChainCount sqlite error %u, %s", res, sqlite3_errmsg(ppDb));
|
||||
}
|
||||
sqlite3_bind_blob(getChainCountStmt, 1, root_hash, root_hash_len, NULL);
|
||||
sqlite3_step(getChainCountStmt);
|
||||
uint32_t count = sqlite3_column_int(getChainCountStmt, 0);
|
||||
sqlite3_reset(getChainCountStmt);
|
||||
return count;
|
||||
@@ -1247,7 +1192,11 @@ uint32_t StoreForwardPlusPlusModule::getChainCount(uint8_t *root_hash, size_t ro
|
||||
StoreForwardPlusPlusModule::link_object StoreForwardPlusPlusModule::getLinkFromCount(uint32_t _count, uint8_t *_root_hash,
|
||||
size_t _root_hash_len)
|
||||
{
|
||||
// TODO return blank if there's no entries
|
||||
LOG_WARN("getLinkFromCount");
|
||||
link_object lo;
|
||||
|
||||
int rc;
|
||||
int step = 0;
|
||||
uint32_t _rx_time = 0;
|
||||
|
||||
@@ -1269,6 +1218,7 @@ StoreForwardPlusPlusModule::link_object StoreForwardPlusPlusModule::getLinkFromC
|
||||
|
||||
step++;
|
||||
}
|
||||
LOG_WARN("step %d", step);
|
||||
if (last_message_commit_hash != nullptr && _rx_time != 0) {
|
||||
lo = getLink(last_message_commit_hash, SFPP_HASH_SIZE);
|
||||
} else {
|
||||
@@ -1280,25 +1230,4 @@ StoreForwardPlusPlusModule::link_object StoreForwardPlusPlusModule::getLinkFromC
|
||||
return lo;
|
||||
}
|
||||
|
||||
void StoreForwardPlusPlusModule::pruneScratchQueue()
|
||||
{
|
||||
sqlite3_bind_int(pruneScratchQueueStmt, 1, time(nullptr) - 60 * 60 * 24);
|
||||
int res = sqlite3_step(pruneScratchQueueStmt);
|
||||
if (res != SQLITE_OK && res != SQLITE_DONE) {
|
||||
LOG_ERROR("Prune Scratch sqlite error %u, %s", res, sqlite3_errmsg(ppDb));
|
||||
}
|
||||
sqlite3_reset(pruneScratchQueueStmt);
|
||||
}
|
||||
|
||||
void StoreForwardPlusPlusModule::trimOldestLink(uint8_t *root_hash, size_t root_hash_len)
|
||||
{
|
||||
sqlite3_bind_int(trimOldestLinkStmt, 1, root_hash_len);
|
||||
sqlite3_bind_blob(trimOldestLinkStmt, 2, root_hash, root_hash_len, NULL);
|
||||
int res = sqlite3_step(trimOldestLinkStmt);
|
||||
if (res != SQLITE_OK && res != SQLITE_DONE) {
|
||||
LOG_ERROR("Trim Oldest Link sqlite error %u, %s", res, sqlite3_errmsg(ppDb));
|
||||
}
|
||||
sqlite3_reset(trimOldestLinkStmt);
|
||||
}
|
||||
|
||||
#endif // has include sqlite3
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "SinglePortModule.h"
|
||||
#include "sqlite3.h"
|
||||
|
||||
#define SFPP_HASH_SIZE 16
|
||||
#define SFPP_HASH_SIZE 32
|
||||
#define SFPP_SHORT_HASH_SIZE 8
|
||||
|
||||
/**
|
||||
@@ -78,11 +78,11 @@ class StoreForwardPlusPlusModule : public ProtobufModule<meshtastic_StoreForward
|
||||
*/
|
||||
virtual bool wantPacket(const meshtastic_MeshPacket *p) override
|
||||
{
|
||||
if (p->decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP ||
|
||||
p->decoded.portnum == (portduino_config.sfpp_steal_port ? meshtastic_PortNum_TEXT_MESSAGE_COMPRESSED_APP
|
||||
: meshtastic_PortNum_STORE_FORWARD_PLUSPLUS_APP)) {
|
||||
switch (p->decoded.portnum) {
|
||||
case meshtastic_PortNum_TEXT_MESSAGE_APP:
|
||||
case meshtastic_PortNum_TEXT_MESSAGE_COMPRESSED_APP:
|
||||
return true;
|
||||
} else {
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -117,8 +117,6 @@ class StoreForwardPlusPlusModule : public ProtobufModule<meshtastic_StoreForward
|
||||
sqlite3_stmt *getFullRootHashStmt;
|
||||
sqlite3_stmt *setChainCountStmt;
|
||||
sqlite3_stmt *getChainCountStmt;
|
||||
sqlite3_stmt *pruneScratchQueueStmt;
|
||||
sqlite3_stmt *trimOldestLinkStmt;
|
||||
|
||||
// For a given Meshtastic ChannelHash, fills the root_hash buffer with a 32-byte root hash
|
||||
// returns true if the root hash was found
|
||||
@@ -153,7 +151,7 @@ class StoreForwardPlusPlusModule : public ProtobufModule<meshtastic_StoreForward
|
||||
void broadcastLink(uint8_t *, size_t);
|
||||
|
||||
// sends a LINK_PROVIDE message broadcasting the given link object
|
||||
void broadcastLink(link_object &, bool, bool = false);
|
||||
void broadcastLink(link_object &, bool);
|
||||
|
||||
// sends a LINK_PROVIDE message broadcasting the given link object from scratch message store
|
||||
bool sendFromScratch(uint8_t *);
|
||||
@@ -189,7 +187,7 @@ class StoreForwardPlusPlusModule : public ProtobufModule<meshtastic_StoreForward
|
||||
|
||||
// ingests a LINK_PROVIDE message and builds a link_object
|
||||
// confirms the root hash and commit hash
|
||||
link_object ingestLinkMessage(meshtastic_StoreForwardPlusPlus *, bool = true);
|
||||
link_object ingestLinkMessage(meshtastic_StoreForwardPlusPlus *);
|
||||
|
||||
// retrieves a link object from the canonical chain database given a message hash
|
||||
link_object getLink(uint8_t *, size_t);
|
||||
@@ -212,10 +210,6 @@ class StoreForwardPlusPlusModule : public ProtobufModule<meshtastic_StoreForward
|
||||
|
||||
link_object getLinkFromCount(uint32_t, uint8_t *, size_t);
|
||||
|
||||
void pruneScratchQueue();
|
||||
|
||||
void trimOldestLink(uint8_t *, size_t);
|
||||
|
||||
// Track if we have a scheduled runOnce pending
|
||||
// useful to not accudentally delay a scheduled runOnce
|
||||
bool pendingRun = false;
|
||||
@@ -226,10 +220,5 @@ class StoreForwardPlusPlusModule : public ProtobufModule<meshtastic_StoreForward
|
||||
};
|
||||
|
||||
uint32_t rebroadcastTimeout = 3600; // Messages older than this (in seconds) will not be rebroadcast
|
||||
bool doing_split_send = false;
|
||||
link_object split_link_out;
|
||||
|
||||
bool doing_split_receive = false;
|
||||
link_object split_link_in;
|
||||
};
|
||||
#endif
|
||||
@@ -429,9 +429,7 @@ int32_t PositionModule::runOnce()
|
||||
|
||||
if (lastGpsSend == 0 || msSinceLastSend >= intervalMs) {
|
||||
if (waitingForFreshPosition) {
|
||||
#ifdef GPS_DEBUG
|
||||
LOG_DEBUG("Skip initial position send; no fresh position since boot");
|
||||
#endif
|
||||
} else if (nodeDB->hasValidPosition(node)) {
|
||||
lastGpsSend = now;
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ class PositionModule : public ProtobufModule<meshtastic_Position>, private concu
|
||||
// In event mode we want to prevent excessive position broadcasts
|
||||
// we set the minimum interval to 5m
|
||||
const uint32_t minimumTimeThreshold =
|
||||
max(uint32_t(300000), Default::getConfiguredOrDefaultMs(config.position.broadcast_smart_minimum_interval_secs, 30));
|
||||
max(300000, Default::getConfiguredOrDefaultMs(config.position.broadcast_smart_minimum_interval_secs, 30));
|
||||
#else
|
||||
const uint32_t minimumTimeThreshold =
|
||||
Default::getConfiguredOrDefaultMs(config.position.broadcast_smart_minimum_interval_secs, 30);
|
||||
|
||||
@@ -20,7 +20,7 @@ int StatusLEDModule::handleStatusUpdate(const meshtastic::Status *arg)
|
||||
switch (arg->getStatusType()) {
|
||||
case STATUS_TYPE_POWER: {
|
||||
meshtastic::PowerStatus *powerStatus = (meshtastic::PowerStatus *)arg;
|
||||
if (powerStatus->getHasUSB() || powerStatus->getIsCharging()) {
|
||||
if (powerStatus->getHasUSB()) {
|
||||
power_state = charging;
|
||||
if (powerStatus->getBatteryChargePercent() >= 100) {
|
||||
power_state = charged;
|
||||
|
||||
@@ -21,17 +21,18 @@ ProcessMessage TextMessageModule::handleReceived(const meshtastic_MeshPacket &mp
|
||||
// We only store/display messages destined for us.
|
||||
devicestate.rx_text_message = mp;
|
||||
devicestate.has_rx_text_message = true;
|
||||
IF_SCREEN(
|
||||
// Guard against running in MeshtasticUI or with no screen
|
||||
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
|
||||
// Store in the central message history
|
||||
const StoredMessage &sm = messageStore.addFromPacket(mp);
|
||||
#if HAS_SCREEN
|
||||
// Guard against running in MeshtasticUI
|
||||
if (screen && config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
|
||||
// Store in the central message history
|
||||
const StoredMessage &sm = messageStore.addFromPacket(mp);
|
||||
|
||||
// Pass message to renderer (banner + thread switching + scroll reset)
|
||||
// Use the global Screen singleton to retrieve the current OLED display
|
||||
auto *display = screen ? screen->getDisplayDevice() : nullptr;
|
||||
graphics::MessageRenderer::handleNewMessage(display, sm, mp);
|
||||
})
|
||||
// Pass message to renderer (banner + thread switching + scroll reset)
|
||||
// Use the global Screen singleton to retrieve the current OLED display
|
||||
auto *display = screen ? screen->getDisplayDevice() : nullptr;
|
||||
graphics::MessageRenderer::handleNewMessage(display, sm, mp);
|
||||
}
|
||||
#endif
|
||||
// Only trigger screen wake if configuration allows it
|
||||
if (shouldWakeOnReceivedMessage()) {
|
||||
powerFSM.trigger(EVENT_RECEIVED_MSG);
|
||||
|
||||
@@ -279,7 +279,7 @@ void portduinoSetup()
|
||||
// RAK6421-13300-S1:aabbcc123456:5ba85807d92138b7519cfb60460573af:3061e8d8
|
||||
// <model string>:mac address :<16 random unique bytes in hexidecimal> : crc32
|
||||
// crc32 is calculated on the eeprom string up to but not including the final colon
|
||||
if (strlen(autoconf_product) < 6 && portduino_config.i2cdev != "") {
|
||||
if (strlen(autoconf_product) < 6) {
|
||||
try {
|
||||
char *mac_start = nullptr;
|
||||
char *devID_start = nullptr;
|
||||
@@ -790,13 +790,8 @@ bool loadConfig(const char *configPath)
|
||||
|
||||
if (yamlConfig["StoreAndForward"]) {
|
||||
portduino_config.sfpp_stratum0 = (yamlConfig["StoreAndForward"]["Stratum0"]).as<bool>(false);
|
||||
portduino_config.sfpp_enabled = (yamlConfig["StoreAndForward"]["Enabled"]).as<bool>(true);
|
||||
portduino_config.sfpp_db_path = (yamlConfig["StoreAndForward"]["DBPath"]).as<std::string>("/var/lib/meshtasticd/");
|
||||
portduino_config.sfpp_initial_sync = (yamlConfig["StoreAndForward"]["InitialSync"]).as<int>(10);
|
||||
portduino_config.sfpp_hops = (yamlConfig["StoreAndForward"]["Hops"]).as<int>(3);
|
||||
portduino_config.sfpp_announce_interval = (yamlConfig["StoreAndForward"]["AnnounceInterval"]).as<int>(5);
|
||||
portduino_config.sfpp_max_chain = (yamlConfig["StoreAndForward"]["MaxChain"]).as<uint32_t>(1000);
|
||||
portduino_config.sfpp_steal_port = (yamlConfig["StoreAndForward"]["StealPort"]).as<bool>(false);
|
||||
}
|
||||
|
||||
if (yamlConfig["General"]) {
|
||||
@@ -880,4 +875,4 @@ void readGPIOFromYaml(YAML::Node sourceNode, pinMapping &destPin, int pinDefault
|
||||
destPin.line = destPin.pin;
|
||||
destPin.gpiochip = portduino_config.lora_default_gpiochip;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -170,10 +170,9 @@ extern struct portduino_config_struct {
|
||||
bool has_configDisplayMode = false;
|
||||
|
||||
// Store and Forward++
|
||||
// DB location /var/lib/meshtasticd/
|
||||
std::string sfpp_db_path = "/var/lib/meshtasticd/";
|
||||
bool sfpp_stratum0 = false;
|
||||
bool sfpp_enabled = true;
|
||||
bool sfpp_steal_port = false;
|
||||
int sfpp_initial_sync = 10;
|
||||
int sfpp_hops = 3;
|
||||
int sfpp_announce_interval = 5; // minutes
|
||||
@@ -503,16 +502,14 @@ extern struct portduino_config_struct {
|
||||
}
|
||||
|
||||
// StoreAndForward
|
||||
if (sfpp_enabled) {
|
||||
if (sfpp_stratum0 || sfpp_initial_sync != 10 || sfpp_hops != 3 || sfpp_announce_interval != 5 || sfpp_max_chain != 1000) {
|
||||
out << YAML::Key << "StoreAndForward" << YAML::Value << YAML::BeginMap;
|
||||
out << YAML::Key << "Enabled" << YAML::Value << sfpp_enabled;
|
||||
out << YAML::Key << "DBPath" << YAML::Value << sfpp_db_path;
|
||||
|
||||
out << YAML::Key << "Stratum0" << YAML::Value << sfpp_stratum0;
|
||||
out << YAML::Key << "InitialSync" << YAML::Value << sfpp_initial_sync;
|
||||
out << YAML::Key << "Hops" << YAML::Value << sfpp_hops;
|
||||
out << YAML::Key << "AnnounceInterval" << YAML::Value << sfpp_announce_interval;
|
||||
out << YAML::Key << "MaxChainLength" << YAML::Value << sfpp_max_chain;
|
||||
out << YAML::Key << "StealPort" << YAML::Value << sfpp_steal_port;
|
||||
out << YAML::EndMap; // StoreAndForward
|
||||
}
|
||||
|
||||
|
||||
@@ -100,5 +100,3 @@
|
||||
#define MODEM_DTR 8
|
||||
#define MODEM_RX 10
|
||||
#define MODEM_TX 11
|
||||
|
||||
#define HAS_PHYSICAL_KEYBOARD 1
|
||||
@@ -23,7 +23,6 @@
|
||||
#define SCREEN_TRANSITION_FRAMERATE 5
|
||||
#define BRIGHTNESS_DEFAULT 130 // Medium Low Brightness
|
||||
#define USE_TFTDISPLAY 1
|
||||
#define HAS_PHYSICAL_KEYBOARD 1
|
||||
|
||||
#define HAS_TOUCHSCREEN 1
|
||||
#define SCREEN_TOUCH_INT 16
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
#define SCREEN_TRANSITION_FRAMERATE 5
|
||||
#define BRIGHTNESS_DEFAULT 130 // Medium Low Brightness
|
||||
#define USE_TFTDISPLAY 1
|
||||
#define HAS_PHYSICAL_KEYBOARD 1
|
||||
|
||||
#define I2C_SDA SDA
|
||||
#define I2C_SCL SCL
|
||||
|
||||
@@ -15,5 +15,5 @@ lib_deps =
|
||||
${nrf52840_base.lib_deps}
|
||||
# renovate: datasource=custom.pio depName=nRF52_PWM packageName=khoih-prog/library/nRF52_PWM
|
||||
khoih-prog/nRF52_PWM@1.0.1
|
||||
; # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib
|
||||
; lewisxhe/SensorLib@0.3.3
|
||||
# renovate: datasource=custom.pio depName=PCF8563 packageName=lewisxhe/library/PCF8563_Library
|
||||
lewisxhe/PCF8563_Library@1.0.1
|
||||
|
||||
@@ -114,8 +114,7 @@ extern "C" {
|
||||
#define LR11X0_DIO_AS_RF_SWITCH
|
||||
|
||||
// PCF8563 RTC Module
|
||||
// REVISIT https://github.com/meshtastic/firmware/pull/9084
|
||||
// #define PCF8563_RTC 0x51
|
||||
#define PCF8563_RTC 0x51
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -12,5 +12,5 @@ build_flags = ${nrf52840_base.build_flags}
|
||||
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/ELECROW-ThinkNode-M6>
|
||||
lib_deps =
|
||||
${nrf52840_base.lib_deps}
|
||||
; # renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib
|
||||
; lewisxhe/SensorLib@0.3.3
|
||||
# renovate: datasource=custom.pio depName=PCF8563 packageName=lewisxhe/library/PCF8563_Library
|
||||
lewisxhe/PCF8563_Library@1.0.1
|
||||
|
||||
@@ -41,30 +41,3 @@ void initVariant()
|
||||
pinMode(VDD_FLASH_EN, OUTPUT);
|
||||
digitalWrite(VDD_FLASH_EN, HIGH);
|
||||
}
|
||||
|
||||
// called from main-nrf52.cpp during the cpuDeepSleep() function
|
||||
void variant_shutdown()
|
||||
{
|
||||
// This sets the pin to OUTPUT and LOW for the pins *not* in the if block.
|
||||
for (int pin = 0; pin < 48; pin++) {
|
||||
if (pin == PIN_GPS_EN || pin == ADC_CTRL || pin == PIN_BUTTON1 || pin == PIN_SPI_MISO || pin == PIN_SPI_MOSI ||
|
||||
pin == PIN_SPI_SCK) {
|
||||
continue;
|
||||
}
|
||||
pinMode(pin, OUTPUT);
|
||||
digitalWrite(pin, LOW);
|
||||
if (pin >= 32) {
|
||||
NRF_P1->DIRCLR = (1 << (pin - 32));
|
||||
} else {
|
||||
NRF_GPIO->DIRCLR = (1 << pin);
|
||||
}
|
||||
}
|
||||
|
||||
digitalWrite(PIN_GPS_EN, LOW);
|
||||
digitalWrite(ADC_CTRL, LOW);
|
||||
// digitalWrite(RTC_POWER, LOW);
|
||||
|
||||
nrf_gpio_cfg_input(PIN_BUTTON1, NRF_GPIO_PIN_PULLUP); // Configure the pin to be woken up as an input
|
||||
nrf_gpio_pin_sense_t sense1 = NRF_GPIO_PIN_SENSE_LOW;
|
||||
nrf_gpio_cfg_sense_set(PIN_BUTTON1, sense1);
|
||||
}
|
||||
|
||||
@@ -44,10 +44,8 @@ extern "C" {
|
||||
#define LED_BLUE -1
|
||||
#define LED_CHARGE (12)
|
||||
#define LED_PAIRING (7)
|
||||
#define PIN_LED2 LED_PAIRING
|
||||
|
||||
#define LED_STATE_ON HIGH
|
||||
#define LED_STATE_OFF LOW
|
||||
#define LED_STATE_ON 1
|
||||
|
||||
// USB power detection
|
||||
#define EXT_PWR_DETECT (13)
|
||||
@@ -121,8 +119,7 @@ static const uint8_t A0 = PIN_A0;
|
||||
#define PIN_SERIAL2_TX (24)
|
||||
|
||||
// PCF8563 RTC Module
|
||||
// REVISIT https://github.com/meshtastic/firmware/pull/9084
|
||||
// #define PCF8563_RTC 0x51
|
||||
#define PCF8563_RTC 0x51
|
||||
|
||||
// SPI
|
||||
#define SPI_INTERFACES_COUNT 1
|
||||
|
||||
@@ -103,7 +103,7 @@ static const uint8_t A0 = PIN_A0;
|
||||
#define EXTERNAL_FLASH_USE_QSPI
|
||||
|
||||
// Add a delay on startup to allow LoRa and GPS to power up
|
||||
#define PERIPHERAL_WARMUP_MS 100
|
||||
#define PIN_PWR_DELAY_MS 100
|
||||
|
||||
/*
|
||||
* Lora radio
|
||||
@@ -178,4 +178,4 @@ static const uint8_t A0 = PIN_A0;
|
||||
* Arduino objects - C++ only
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -1,4 +1,4 @@
|
||||
[VERSION]
|
||||
major = 2
|
||||
minor = 7
|
||||
build = 17
|
||||
build = 18
|
||||
|
||||
Reference in New Issue
Block a user