Merge branch 'master' into tft-gui-work

This commit is contained in:
Manuel 2024-12-22 23:18:09 +01:00 committed by GitHub
commit 6a0f973a03
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
118 changed files with 11416 additions and 728 deletions

72
.github/workflows/build_docker.yml vendored Normal file
View File

@ -0,0 +1,72 @@
name: Build Docker
on: workflow_call
permissions:
contents: write
packages: write
jobs:
build-native:
runs-on: ubuntu-latest
steps:
- name: Install libs needed for native build
shell: bash
run: |
sudo apt-get update --fix-missing
sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev libusb-1.0-0-dev
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- name: Upgrade python tools
shell: bash
run: |
python -m pip install --upgrade pip
pip install -U platformio adafruit-nrfutil
pip install -U meshtastic --pre
- name: Upgrade platformio
shell: bash
run: |
pio upgrade
- name: Build Native
run: bin/build-native.sh
- name: Get release version string
run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- name: Docker login
if: ${{ github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }}
uses: docker/login-action@v3
with:
username: meshtastic
password: ${{ secrets.DOCKER_FIRMWARE_TOKEN }}
- name: Docker setup
if: ${{ github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }}
uses: docker/setup-buildx-action@v3
- name: Docker build and push tagged versions
if: ${{ github.event_name == 'workflow_dispatch' }}
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
push: true
tags: meshtastic/meshtasticd:${{ steps.version.outputs.version }}
- name: Docker build and push
if: ${{ github.ref == 'refs/heads/master' && github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }}
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
push: true
tags: meshtastic/meshtasticd:latest

View File

@ -50,36 +50,3 @@ jobs:
path: |
release/meshtasticd_linux_x86_64
bin/config-dist.yaml
- name: Docker login
if: ${{ github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }}
uses: docker/login-action@v3
continue-on-error: true # FIXME: Failing docker login auth
with:
username: meshtastic
password: ${{ secrets.DOCKER_FIRMWARE_TOKEN }}
- name: Docker setup
if: ${{ github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }}
continue-on-error: true # FIXME: Failing docker login auth
uses: docker/setup-buildx-action@v3
- name: Docker build and push tagged versions
if: ${{ github.event_name == 'workflow_dispatch' }}
continue-on-error: true # FIXME: Failing docker login auth
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
push: true
tags: meshtastic/device-simulator:${{ steps.version.outputs.version }}
- name: Docker build and push
if: ${{ github.ref == 'refs/heads/master' && github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }}
continue-on-error: true # FIXME: Failing docker login auth
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
push: true
tags: meshtastic/device-simulator:latest

View File

@ -37,7 +37,7 @@ jobs:
else
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} quick)
fi
echo "Name: ${{ github.ref_name }} Base: ${{ github.base_ref }} Head: ${{ github.head_ref }} Ref: ${{ github.ref }} Targets: $TARGETS"
echo "Name: ${{ github.ref_name }} Base: ${{ github.base_ref }} } Ref: ${{ github.ref }} Targets: $TARGETS"
echo "${{matrix.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT
outputs:
esp32: ${{ steps.jsonStep.outputs.esp32 }}
@ -137,6 +137,11 @@ jobs:
package-native:
uses: ./.github/workflows/package_amd64.yml
build-docker:
if: ${{ github.event_name == 'workflow_dispatch' }}
uses: ./.github/workflows/build_docker.yml
secrets: inherit
after-checks:
runs-on: ubuntu-latest
if: ${{ github.event_name != 'workflow_dispatch' }}

View File

@ -17,9 +17,9 @@ jobs:
- name: Download nanopb
run: |
wget https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.9-linux-x86.tar.gz
tar xvzf nanopb-0.4.9-linux-x86.tar.gz
mv nanopb-0.4.9-linux-x86 nanopb-0.4.9
wget https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.9.1-linux-x86.tar.gz
tar xvzf nanopb-0.4.9.1-linux-x86.tar.gz
mv nanopb-0.4.9.1-linux-x86 nanopb-0.4.9
- name: Re-generate protocol buffers
run: |

View File

@ -4,31 +4,32 @@ cli:
plugins:
sources:
- id: trunk
ref: v1.6.4
ref: v1.6.6
uri: https://github.com/trunk-io/plugins
lint:
enabled:
- trufflehog@3.83.6
- prettier@3.4.2
- trufflehog@3.86.1
- yamllint@1.35.1
- bandit@1.7.10
- checkov@3.2.287
- bandit@1.8.0
- checkov@3.2.334
- terrascan@1.19.9
- trivy@0.56.2
- trivy@0.58.0
#- trufflehog@3.63.2-rc0
- taplo@0.9.3
- ruff@0.7.3
- ruff@0.8.3
- isort@5.13.2
- markdownlint@0.42.0
- oxipng@9.1.2
- markdownlint@0.43.0
- oxipng@9.1.3
- svgo@3.3.2
- actionlint@1.7.4
- flake8@7.1.1
- hadolint@2.12.0
- hadolint@2.12.1-beta
- shfmt@3.6.0
- shellcheck@0.10.0
- black@24.10.0
- git-diff-check
- gitleaks@8.21.1
- gitleaks@8.21.2
- clang-format@16.0.3
#- prettier@3.3.3
ignore:
@ -39,11 +40,11 @@ runtimes:
enabled:
- python@3.10.8
- go@1.21.0
- node@18.12.1
- node@18.20.5
actions:
disabled:
- trunk-announce
enabled:
- trunk-fmt-pre-commit
- trunk-check-pre-push
- trunk-upgrade-available
- trunk-upgrade-available

View File

@ -14,7 +14,7 @@ USER root
# trunk-ignore(hadolint/DL3008): Use latest version of packages for buildchain
RUN apt-get update && apt-get install --no-install-recommends -y wget python3 python3-pip python3-wheel python3-venv g++ zip git \
ca-certificates libgpiod-dev libyaml-cpp-dev libbluetooth-dev \
libulfius-dev liborcania-dev libssl-dev pkg-config && \
libusb-1.0-0-dev libulfius-dev liborcania-dev libssl-dev pkg-config && \
apt-get clean && rm -rf /var/lib/apt/lists/* && mkdir /tmp/firmware
RUN groupadd -g 1000 mesh && useradd -ml -u 1000 -g 1000 mesh && chown mesh:mesh /tmp/firmware
@ -37,7 +37,7 @@ ENV TZ=Etc/UTC
# trunk-ignore(terrascan/AC_DOCKER_0002): Known terrascan issue
# trunk-ignore(hadolint/DL3008): Use latest version of packages for buildchain
RUN apt-get update && apt-get --no-install-recommends -y install libc-bin libc6 libgpiod2 libyaml-cpp0.7 libulfius2.7 liborcania2.3 libssl3 && \
RUN apt-get update && apt-get --no-install-recommends -y install libc-bin libc6 libgpiod2 libyaml-cpp0.7 libulfius2.7 libusb-1.0-0-dev liborcania2.3 libssl3 && \
apt-get clean && rm -rf /var/lib/apt/lists/*
RUN groupadd -g 1000 mesh && useradd -ml -u 1000 -g 1000 mesh
@ -51,4 +51,4 @@ VOLUME /home/mesh/data
CMD [ "sh", "-cx", "./meshtasticd -d /home/mesh/data --hwid=${HWID:-$RANDOM}" ]
HEALTHCHECK NONE
HEALTHCHECK NONE

View File

@ -1,6 +1,6 @@
[nrf52_base]
; Instead of the standard nordicnrf52 platform, we use our fork which has our added variant files
platform = platformio/nordicnrf52@^10.6.0
platform = platformio/nordicnrf52@^10.7.0
extends = arduino_base
platform_packages =
; our custom Git version until they merge our PR

View File

@ -26,6 +26,7 @@ lib_deps =
${radiolib_base.lib_deps}
rweather/Crypto@^0.4.0
https://github.com/lovyan03/LovyanGFX.git#1401c28a47646fe00538d487adcb2eb3c72de805
https://github.com/pine64/libch341-spi-userspace#8695637adeabf5abf5601d8e82cb0ba19ce9ec46
build_flags =
${arduino_base.build_flags}
@ -33,7 +34,9 @@ build_flags =
-Isrc/platform/portduino
-DRADIOLIB_EEPROM_UNSUPPORTED
-DPORTDUINO_LINUX_HARDWARE
-lpthread
-lstdc++fs
-lbluetooth
-lgpiod
-lyaml-cpp
-std=c++17

View File

@ -12,13 +12,6 @@ Lora:
# IRQ: 17
# Reset: 22
# Module: sx1262 # pinedio
# CS: 0
# IRQ: 10
# Busy: 11
# DIO2_AS_RF_SWITCH: true
# spidev: spidev0.1
# Module: RF95 # Adafruit RFM9x
# Reset: 25
# CS: 7
@ -50,8 +43,6 @@ Lora:
# TXen: x # TX and RX enable pins
# RXen: x
# ch341_quirk: true # Uncomment this to use the chunked SPI transfer that seems to fix the ch341
# spiSpeed: 2000000
### Set gpio chip to use in /dev/. Defaults to 0.

View File

@ -0,0 +1,9 @@
## https://www.mikroe.com/lr-iot-click
Lora:
Module: lr1110 # OpenWRT ONE mikroBUS with LR-IOT-CLICK
# CS: 25
IRQ: 10
Busy: 12
# Reset: 2
spidev: spidev2.0
DIO3_TCXO_VOLTAGE: 1.6

View File

@ -1,16 +0,0 @@
---
Lora:
## Ebyte E22-900M30S, E22-900M22S with no external RF switching setup
## Will work with any module without RF switching, and with TCXO
Module: sx1262
gpiochip: 1 # subtract 32 from the gpio numbers
DIO2_AS_RF_SWITCH: true
DIO3_TCXO_VOLTAGE: true
CS: 16 #pin6 / GPIO48 1C0
IRQ: 23 #pin17 / GPIO55 1C7
Busy: 22 #pin16 / GPIO54 1C6
Reset: 25 #pin13 / GPIO57 1D1
RXen: 24 #pin12 / GPIO56 1D0
#TXen: bridge to DIO2 on E22 module
spidev: spidev0.0
spiSpeed: 2000000

View File

@ -1,16 +0,0 @@
---
Lora:
## Ebyte E22-900MM22S with no external RF switching setup
## Will work with any module without RF switching and no TCXO
Module: sx1262
gpiochip: 1 # subtract 32 from the gpio numbers
DIO2_AS_RF_SWITCH: true
DIO3_TCXO_VOLTAGE: true
CS: 16 #pin6 / GPIO48 1C0
IRQ: 23 #pin17 / GPIO55 1C7
Busy: 22 #pin16 / GPIO54 1C6
Reset: 25 #pin13 / GPIO57 1D1
RXen: 24 #pin12 / GPIO56 1D0
#TXen: bridge to DIO2 on E22 module
spidev: spidev0.0
spiSpeed: 2000000

View File

@ -1,14 +0,0 @@
---
Lora:
## Heltec HT-RA62, Seeed WIO SX1262
## Will work with any module with automatic RF switching, and with TCXO
Module: sx1262
gpiochip: 1 # subtract 32 from the gpio numbers
DIO2_AS_RF_SWITCH: true
DIO3_TCXO_VOLTAGE: true
CS: 16 #pin6 (GPIO pin 48 1C0)
IRQ: 23 #pin17 (GPIO pin 55 1C7)
Reset: 25 #pin13 (GPIO pin 57 1D1)
Busy: 22 #pin16 (GPIO pin 54 1C6)
spidev: spidev0.0 #pins are (CS=6, CLK=7, MOSI=8, MISO=9)
spiSpeed: 2000000

View File

@ -0,0 +1,20 @@
---
Lora:
## Ebyte E80-900M22S
## This is a bit experimental
##
##
Module: lr1121
gpiochip: 1 # subtract 32 from the gpio numbers
DIO3_TCXO_VOLTAGE: 1.8
CS: 16 #pin6 / GPIO48 1C0
IRQ: 23 #pin17 / GPIO55 1C7
Busy: 22 #pin16 / GPIO54 1C6
Reset: 25 #pin13 / GPIO57 1D1
spidev: spidev0.0 #pins are (CS=16, CLK=17, MOSI=18, MISO=19)
spiSpeed: 2000000
General:
MACAddressSource: eth0

View File

@ -0,0 +1,21 @@
---
Lora:
## Ebyte E22-900M30S, E22-900M22S with or without external RF switching setup
## HT-RA62 (Has internal switching, but whatever)
## Seeed WIO SX1262 (already has TXEN-DIO2 link, but needs RXEN)
## Will work with any module with or without RF switching, and with TCXO
Module: sx1262
gpiochip: 1 # subtract 32 from the gpio numbers
DIO2_AS_RF_SWITCH: true
DIO3_TCXO_VOLTAGE: true
CS: 16 #pin6 / GPIO48 1C0
IRQ: 23 #pin17 / GPIO55 1C7
Busy: 22 #pin16 / GPIO54 1C6
Reset: 25 #pin13 / GPIO57 1D1
RXen: 24 #pin12 / GPIO56 1D0 # Not strictly needed for auto-switching, but why complicate things?
# TXen: bridge to DIO2 on E22 module
spidev: spidev0.0 #pins are (CS=16, CLK=17, MOSI=18, MISO=19)
spiSpeed: 2000000
General:
MACAddressSource: eth0

View File

@ -0,0 +1,21 @@
---
Lora:
## Ebyte E22-900MM22S with no external RF switching setup
## Waveshare SX126X XXXM, AI Thinker RA-01SH
## Will work with any module with or without RF switching and no TCXO
Module: sx1262
gpiochip: 1 # subtract 32 from the gpio numbers
DIO2_AS_RF_SWITCH: true
DIO3_TCXO_VOLTAGE: false
CS: 16 #pin6 / GPIO48 1C0
IRQ: 23 #pin17 / GPIO55 1C7
Busy: 22 #pin16 / GPIO54 1C6
Reset: 25 #pin13 / GPIO57 1D1
RXen: 24 #pin12 / GPIO56 1D0 # Not strictly needed for auto-switching, but why complicate things?
# TXen: bridge to DIO2 on E22 module
spidev: spidev0.0 #pins are (CS=16, CLK=17, MOSI=18, MISO=19)
spiSpeed: 2000000
General:
MACAddressSource: eth0

View File

@ -1,13 +0,0 @@
---
Lora:
## Waveshare SX126X XXXM, AI Thinker RA-01SH
## Will work with any module with automatic RF switching, and with no TCXO
Module: sx1262
gpiochip: 1 # subtract 32 from the gpio numbers
DIO2_AS_RF_SWITCH: true
CS: 16 #pin6 (GPIO pin 48 1C0)
IRQ: 23 #pin17 (GPIO pin 55 1C7)
Reset: 25 #pin13 (GPIO pin 57 1D1)
Busy: 22 #pin16 (GPIO pin 54 1C6)
spidev: spidev0.0 #pins are (CS=6, CLK=7, MOSI=8, MISO=9)
spiSpeed: 2000000

View File

@ -0,0 +1,11 @@
Lora:
Module: sx1262
CS: 0
IRQ: 6
Reset: 2
Busy: 4
spidev: ch341
DIO3_TCXO_VOLTAGE: true
# USB_Serialnum: 12345678
USB_PID: 0x5512
USB_VID: 0x1A86

View File

@ -0,0 +1,5 @@
Lora:
Module: sx1262
CS: 0
IRQ: 10
spidev: ch341

41
boards/esp32-s3-zero.json Normal file
View File

@ -0,0 +1,41 @@
{
"build": {
"arduino": {
"partitions": "default.csv",
"memory_type": "qio_qspi"
},
"core": "esp32",
"extra_flags": [
"-DARDUINO_ESP32S3_DEV",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DBOARD_HAS_PSRAM"
],
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
"psram_type": "qio",
"hwids": [["0x303A", "0x1001"]],
"mcu": "esp32s3",
"variant": "esp32s3"
},
"connectivity": ["wifi", "bluetooth"],
"debug": {
"default_tool": "esp-builtin",
"onboard_tools": ["esp-builtin"],
"openocd_target": "esp32s3.cfg"
},
"frameworks": ["arduino", "espidf"],
"platforms": ["espressif32"],
"name": "Espressif ESP32-S3-FH4R2 (4 MB QD, 2MB PSRAM)",
"upload": {
"flash_size": "4MB",
"maximum_ram_size": 327680,
"maximum_size": 4194304,
"require_upload_port": true,
"speed": 921600
},
"url": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/hw-reference/esp32s3/user-guide-devkitc-1.html",
"vendor": "Espressif"
}

View File

@ -48,6 +48,6 @@
"require_upload_port": true,
"wait_for_upload_port": true
},
"url": "FIXME",
"vendor": "TTGO"
"url": "https://lilygo.cc/products/t-echo-lilygo",
"vendor": "LILYGO"
}

View File

@ -16,6 +16,7 @@ default_envs = tbeam
;default_envs = tlora-v2
;default_envs = tlora-v2-1-1_6
;default_envs = tlora-v2-1-1_6-tcxo
;default_envs = tlora-v3-3-0-tcxo
;default_envs = tlora-t3s3-v1
;default_envs = t-echo
;default_envs = canaryone
@ -159,4 +160,5 @@ lib_deps =
https://github.com/KodinLanewave/INA3221@1.0.1
mprograms/QMC5883LCompass@1.2.3
dfrobot/DFRobot_RTU@1.0.3
https://github.com/meshtastic/DFRobot_LarkWeatherStation#4de3a9cadef0f6a5220a8a906cf9775b02b0040d
https://github.com/meshtastic/DFRobot_LarkWeatherStation#4de3a9cadef0f6a5220a8a906cf9775b02b0040d
robtillaart/INA226@0.6.0

@ -1 +1 @@
Subproject commit 00c9c9932ea50c14cdc44d497d2672a0031641ce
Subproject commit 2cffaf53e3faf1b6e41a8b8f05312f2f893be413

View File

@ -72,8 +72,9 @@ static const uint8_t ext_chrg_detect_value = EXT_CHRG_DETECT_VALUE;
#endif
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_PORTDUINO)
INA260Sensor ina260Sensor;
INA219Sensor ina219Sensor;
INA226Sensor ina226Sensor;
INA260Sensor ina260Sensor;
INA3221Sensor ina3221Sensor;
#endif
@ -413,7 +414,20 @@ class AnalogBatteryLevel : public HasBatteryLevel
#ifdef EXT_CHRG_DETECT
return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value;
#else
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && \
!defined(DISABLE_INA_CHARGING_DETECTION)
if (hasINA()) {
// get current flow from INA sensor - negative value means power flowing into the battery
// default assuming BATTERY+ <--> INA_VIN+ <--> SHUNT RESISTOR <--> INA_VIN- <--> LOAD
LOG_DEBUG("Using INA on I2C addr 0x%x for charging detection", config.power.device_battery_ina_address);
#if defined(INA_CHARGING_DETECTION_INVERT)
return getINACurrent() > 0;
#else
return getINACurrent() < 0;
#endif
}
return isBatteryConnect() && isVbusIn();
#endif
#endif
}
@ -450,6 +464,9 @@ class AnalogBatteryLevel : public HasBatteryLevel
{
if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA219].first == config.power.device_battery_ina_address) {
return ina219Sensor.getBusVoltageMv();
} else if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA226].first ==
config.power.device_battery_ina_address) {
return ina226Sensor.getBusVoltageMv();
} else if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA260].first ==
config.power.device_battery_ina_address) {
return ina260Sensor.getBusVoltageMv();
@ -460,6 +477,20 @@ class AnalogBatteryLevel : public HasBatteryLevel
return 0;
}
int16_t getINACurrent()
{
if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA219].first == config.power.device_battery_ina_address) {
return ina219Sensor.getCurrentMa();
} else if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA226].first ==
config.power.device_battery_ina_address) {
return ina226Sensor.getCurrentMa();
} else if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA3221].first ==
config.power.device_battery_ina_address) {
return ina3221Sensor.getCurrentMa();
}
return 0;
}
bool hasINA()
{
if (!config.power.device_battery_ina_address) {
@ -469,6 +500,10 @@ class AnalogBatteryLevel : public HasBatteryLevel
if (!ina219Sensor.isInitialized())
return ina219Sensor.runOnce() > 0;
return ina219Sensor.isRunning();
} else if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA226].first ==
config.power.device_battery_ina_address) {
if (!ina226Sensor.isInitialized())
return ina226Sensor.runOnce() > 0;
} else if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA260].first ==
config.power.device_battery_ina_address) {
if (!ina260Sensor.isInitialized())
@ -1154,4 +1189,4 @@ bool Power::lipoInit()
{
return false;
}
#endif
#endif

View File

@ -63,7 +63,8 @@ class ScanI2C
MAX30102,
TPS65233,
MPR121KB,
CGRADSENS
CGRADSENS,
INA226
} DeviceType;
// typedef uint8_t DeviceAddress;
@ -127,4 +128,4 @@ class ScanI2C
private:
bool shouldSuppressScreen = false;
};
};

View File

@ -250,8 +250,16 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xFE), 2);
LOG_DEBUG("Register MFG_UID: 0x%x", registerValue);
if (registerValue == 0x5449) {
logFoundDevice("INA260", (uint8_t)addr.address);
type = INA260;
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xFF), 2);
LOG_DEBUG("Register DIE_UID: 0x%x", registerValue);
if (registerValue == 0x2260) {
logFoundDevice("INA226", (uint8_t)addr.address);
type = INA226;
} else {
logFoundDevice("INA260", (uint8_t)addr.address);
type = INA260;
}
} else { // Assume INA219 if INA260 ID is not found
logFoundDevice("INA219", (uint8_t)addr.address);
type = INA219;

View File

@ -91,6 +91,7 @@ NRF52Bluetooth *nrf52Bluetooth = nullptr;
#include "linux/LinuxHardwareI2C.h"
#include "mesh/raspihttp/PiWebServer.h"
#include "platform/portduino/PortduinoGlue.h"
#include "platform/portduino/USBHal.h"
#include <fstream>
#include <iostream>
#include <string>
@ -228,6 +229,9 @@ static OSThread *powerFSMthread;
static OSThread *ambientLightingThread;
RadioInterface *rIf = NULL;
#ifdef ARCH_PORTDUINO
RadioLibHal *RadioLibHAL = NULL;
#endif
/**
* Some platforms (nrf52) might provide an alterate version that suppresses calling delay from sleep.
@ -252,6 +256,17 @@ void printInfo()
#ifndef PIO_UNIT_TESTING
void setup()
{
#if defined(T_DECK)
// GPIO10 manages all peripheral power supplies
// Turn on peripheral power immediately after MUC starts.
// If some boards are turned on late, ESP32 will reset due to low voltage.
// ESP32-C3(Keyboard) , MAX98357A(Audio Power Amplifier) ,
// TF Card , Display backlight(AW9364DNR) , AN48841B(Trackball) , ES7210(Decoder)
pinMode(KB_POWERON, OUTPUT);
digitalWrite(KB_POWERON, HIGH);
delay(100);
#endif
concurrency::hasBeenSetup = true;
#if ARCH_PORTDUINO
SPISettings spiSettings(settingsMap[spiSpeed], MSBFIRST, SPI_MODE0);
@ -424,15 +439,6 @@ void setup()
digitalWrite(AQ_SET_PIN, HIGH);
#endif
#if defined(T_DECK)
// enable keyboard
pinMode(KB_POWERON, OUTPUT);
digitalWrite(KB_POWERON, HIGH);
// There needs to be a delay after power on, give LILYGO-KEYBOARD some startup time
// otherwise keyboard and touch screen will not work
delay(200);
#endif
// Currently only the tbeam has a PMU
// PMU initialization needs to be placed before i2c scanning
power = new Power();
@ -588,6 +594,7 @@ void setup()
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::BMP_3XX, meshtastic_TelemetrySensorType_BMP3XX);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::BMP_085, meshtastic_TelemetrySensorType_BMP085);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA260, meshtastic_TelemetrySensorType_INA260);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA226, meshtastic_TelemetrySensorType_INA226);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA219, meshtastic_TelemetrySensorType_INA219);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA3221, meshtastic_TelemetrySensorType_INA3221);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MAX17048, meshtastic_TelemetrySensorType_MAX17048);
@ -718,12 +725,16 @@ void setup()
pinMode(LORA_CS, OUTPUT);
digitalWrite(LORA_CS, HIGH);
SPI1.begin(false);
#else // HW_SPI1_DEVICE
#else // HW_SPI1_DEVICE
SPI.setSCK(LORA_SCK);
SPI.setTX(LORA_MOSI);
SPI.setRX(LORA_MISO);
SPI.begin(false);
#endif // HW_SPI1_DEVICE
#endif // HW_SPI1_DEVICE
#elif ARCH_PORTDUINO
if (settingsStrings[spidev] != "ch341") {
SPI.begin();
}
#elif !defined(ARCH_ESP32) // ARCH_RP2040
SPI.begin();
#else
@ -910,8 +921,11 @@ void setup()
if (settingsMap[use_sx1262]) {
if (!rIf) {
LOG_DEBUG("Activate sx1262 radio on SPI port %s", settingsStrings[spidev].c_str());
LockingArduinoHal *RadioLibHAL =
new LockingArduinoHal(SPI, spiSettings, (settingsMap[ch341Quirk] ? settingsMap[busy] : RADIOLIB_NC));
if (settingsStrings[spidev] == "ch341") {
RadioLibHAL = ch341Hal;
} else {
RadioLibHAL = new LockingArduinoHal(SPI, spiSettings);
}
rIf = new SX1262Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset],
settingsMap[busy]);
if (!rIf->init()) {
@ -925,8 +939,7 @@ void setup()
} else if (settingsMap[use_rf95]) {
if (!rIf) {
LOG_DEBUG("Activate rf95 radio on SPI port %s", settingsStrings[spidev].c_str());
LockingArduinoHal *RadioLibHAL =
new LockingArduinoHal(SPI, spiSettings, (settingsMap[ch341Quirk] ? settingsMap[busy] : RADIOLIB_NC));
RadioLibHAL = new LockingArduinoHal(SPI, spiSettings);
rIf = new RF95Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset],
settingsMap[busy]);
if (!rIf->init()) {
@ -941,7 +954,7 @@ void setup()
} else if (settingsMap[use_sx1280]) {
if (!rIf) {
LOG_DEBUG("Activate sx1280 radio on SPI port %s", settingsStrings[spidev].c_str());
LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings);
RadioLibHAL = new LockingArduinoHal(SPI, spiSettings);
rIf = new SX1280Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset],
settingsMap[busy]);
if (!rIf->init()) {
@ -1001,7 +1014,7 @@ void setup()
} else if (settingsMap[use_sx1268]) {
if (!rIf) {
LOG_DEBUG("Activate sx1268 radio on SPI port %s", settingsStrings[spidev].c_str());
LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings);
RadioLibHAL = new LockingArduinoHal(SPI, spiSettings);
rIf = new SX1268Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset],
settingsMap[busy]);
if (!rIf->init()) {
@ -1370,7 +1383,6 @@ void loop()
mainDelay.delay(delayMsec);
}
}
#endif
#if HAS_TFT
void tft_task_handler(void *param = nullptr)
@ -1388,5 +1400,6 @@ void tft_task_handler(void *param = nullptr)
#endif
}
}
#endif
#endif

View File

@ -58,10 +58,16 @@ void CryptoEngine::clearKeys()
* Encrypt a packet's payload using a key generated with Curve25519 and SHA256
* for a specific node.
*
* @param bytes is updated in place
* @param toNode The MeshPacket `to` field.
* @param fromNode The MeshPacket `from` field.
* @param remotePublic The remote node's Curve25519 public key.
* @param packetId The MeshPacket `id` field.
* @param numBytes Number of bytes of plaintext in the bytes buffer.
* @param bytes Buffer containing plaintext input.
* @param bytesOut Output buffer to be populated with encrypted ciphertext.
*/
bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, meshtastic_UserLite_public_key_t remotePublic,
uint64_t packetNum, size_t numBytes, uint8_t *bytes, uint8_t *bytesOut)
uint64_t packetNum, size_t numBytes, const uint8_t *bytes, uint8_t *bytesOut)
{
uint8_t *auth;
long extraNonceTmp = random();
@ -93,14 +99,18 @@ bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, meshtas
* Decrypt a packet's payload using a key generated with Curve25519 and SHA256
* for a specific node.
*
* @param bytes is updated in place
* @param fromNode The MeshPacket `from` field.
* @param remotePublic The remote node's Curve25519 public key.
* @param packetId The MeshPacket `id` field.
* @param numBytes Number of bytes of ciphertext in the bytes buffer.
* @param bytes Buffer containing ciphertext input.
* @param bytesOut Output buffer to be populated with decrypted plaintext.
*/
bool CryptoEngine::decryptCurve25519(uint32_t fromNode, meshtastic_UserLite_public_key_t remotePublic, uint64_t packetNum,
size_t numBytes, uint8_t *bytes, uint8_t *bytesOut)
size_t numBytes, const uint8_t *bytes, uint8_t *bytesOut)
{
uint8_t *auth; // set to last 8 bytes of text?
uint32_t extraNonce; // pointer was not really used
auth = bytes + numBytes - 12;
const uint8_t *auth = bytes + numBytes - 12; // set to last 8 bytes of text?
uint32_t extraNonce; // pointer was not really used
memcpy(&extraNonce, auth + 8,
sizeof(uint32_t)); // do not use dereference on potential non aligned pointers : (uint32_t *)(auth + 8);
LOG_INFO("Random nonce value: %d", extraNonce);

View File

@ -40,9 +40,9 @@ class CryptoEngine
void clearKeys();
void setDHPrivateKey(uint8_t *_private_key);
virtual bool encryptCurve25519(uint32_t toNode, uint32_t fromNode, meshtastic_UserLite_public_key_t remotePublic,
uint64_t packetNum, size_t numBytes, uint8_t *bytes, uint8_t *bytesOut);
uint64_t packetNum, size_t numBytes, const uint8_t *bytes, uint8_t *bytesOut);
virtual bool decryptCurve25519(uint32_t fromNode, meshtastic_UserLite_public_key_t remotePublic, uint64_t packetNum,
size_t numBytes, uint8_t *bytes, uint8_t *bytesOut);
size_t numBytes, const uint8_t *bytes, uint8_t *bytesOut);
virtual bool setDHPublicKey(uint8_t *publicKey);
virtual void hash(uint8_t *bytes, size_t numBytes);

View File

@ -48,8 +48,10 @@ template <typename T> bool LR11x0Interface<T>::init()
digitalWrite(LR11X0_POWER_EN, HIGH);
#endif
#if ARCH_PORTDUINO
float tcxoVoltage = (float)settingsMap[dio3_tcxo_voltage] / 1000;
// FIXME: correct logic to default to not using TCXO if no voltage is specified for LR11x0_DIO3_TCXO_VOLTAGE
#if !defined(LR11X0_DIO3_TCXO_VOLTAGE)
#elif !defined(LR11X0_DIO3_TCXO_VOLTAGE)
float tcxoVoltage =
0; // "TCXO reference voltage to be set on DIO3. Defaults to 1.6 V, set to 0 to skip." per
// https://github.com/jgromes/RadioLib/blob/690a050ebb46e6097c5d00c371e961c1caa3b52e/src/modules/LR11x0/LR11x0.h#L471C26-L471C104

View File

@ -2,6 +2,8 @@
#include <Arduino.h>
#include <assert.h>
#include <functional>
#include <memory>
#include "PointerQueue.h"
@ -9,6 +11,7 @@ template <class T> class Allocator
{
public:
Allocator() : deleter([this](T *p) { this->release(p); }) {}
virtual ~Allocator() {}
/// Return a queable object which has been prefilled with zeros. Panic if no buffer is available
@ -43,12 +46,32 @@ template <class T> class Allocator
return p;
}
/// Variations of the above methods that return std::unique_ptr instead of raw pointers.
using UniqueAllocation = std::unique_ptr<T, const std::function<void(T *)> &>;
/// Return a queable object which has been prefilled with zeros.
/// std::unique_ptr wrapped variant of allocZeroed().
UniqueAllocation allocUniqueZeroed() { return UniqueAllocation(allocZeroed(), deleter); }
/// Return a queable object which has been prefilled with zeros - allow timeout to wait for available buffers (you probably
/// don't want this version).
/// std::unique_ptr wrapped variant of allocZeroed(TickType_t maxWait).
UniqueAllocation allocUniqueZeroed(TickType_t maxWait) { return UniqueAllocation(allocZeroed(maxWait), deleter); }
/// Return a queable object which is a copy of some other object
/// std::unique_ptr wrapped variant of allocCopy(const T &src, TickType_t maxWait).
UniqueAllocation allocUniqueCopy(const T &src, TickType_t maxWait = portMAX_DELAY)
{
return UniqueAllocation(allocCopy(src, maxWait), deleter);
}
/// Return a buffer for use by others
virtual void release(T *p) = 0;
protected:
// Alloc some storage
virtual T *alloc(TickType_t maxWait) = 0;
private:
// std::unique_ptr Deleter function; calls release().
const std::function<void(T *)> deleter;
};
/**

View File

@ -44,6 +44,7 @@ typedef int ErrorCode;
/// Alloc and free packets to our global, ISR safe pool
extern Allocator<meshtastic_MeshPacket> &packetPool;
using UniquePacketPoolPacket = Allocator<meshtastic_MeshPacket>::UniqueAllocation;
/**
* Most (but not always) of the time we want to treat packets 'from' the local phone (where from == 0), as if they originated on

View File

@ -71,6 +71,78 @@ static unsigned char userprefs_admin_key_1[] = USERPREFS_USE_ADMIN_KEY_1;
static unsigned char userprefs_admin_key_2[] = USERPREFS_USE_ADMIN_KEY_2;
#endif
#ifdef HELTEC_MESH_NODE_T114
uint32_t read8(uint8_t bits, uint8_t dummy, uint8_t cs, uint8_t sck, uint8_t mosi, uint8_t dc, uint8_t rst)
{
uint32_t ret = 0;
uint8_t SDAPIN = mosi;
pinMode(SDAPIN, INPUT_PULLUP);
digitalWrite(dc, HIGH);
for (int i = 0; i < dummy; i++) { // any dummy clocks
digitalWrite(sck, HIGH);
delay(1);
digitalWrite(sck, LOW);
delay(1);
}
for (int i = 0; i < bits; i++) { // read results
ret <<= 1;
delay(1);
if (digitalRead(SDAPIN))
ret |= 1;
;
digitalWrite(sck, HIGH);
delay(1);
digitalWrite(sck, LOW);
}
return ret;
}
void write9(uint8_t val, uint8_t dc_val, uint8_t cs, uint8_t sck, uint8_t mosi, uint8_t dc, uint8_t rst)
{
pinMode(mosi, OUTPUT);
digitalWrite(dc, dc_val);
for (int i = 0; i < 8; i++) { // send command
digitalWrite(mosi, (val & 0x80) != 0);
delay(1);
digitalWrite(sck, HIGH);
delay(1);
digitalWrite(sck, LOW);
val <<= 1;
}
}
uint32_t readwrite8(uint8_t cmd, uint8_t bits, uint8_t dummy, uint8_t cs, uint8_t sck, uint8_t mosi, uint8_t dc, uint8_t rst)
{
digitalWrite(cs, LOW);
write9(cmd, 0, cs, sck, mosi, dc, rst);
uint32_t ret = read8(bits, dummy, cs, sck, mosi, dc, rst);
digitalWrite(cs, HIGH);
return ret;
}
uint32_t get_st7789_id(uint8_t cs, uint8_t sck, uint8_t mosi, uint8_t dc, uint8_t rst)
{
pinMode(cs, OUTPUT);
digitalWrite(cs, HIGH);
pinMode(cs, OUTPUT);
pinMode(sck, OUTPUT);
pinMode(mosi, OUTPUT);
pinMode(dc, OUTPUT);
pinMode(rst, OUTPUT);
digitalWrite(rst, LOW); // Hardware Reset
delay(10);
digitalWrite(rst, HIGH);
delay(10);
uint32_t ID = 0;
ID = readwrite8(0x04, 24, 1, cs, sck, mosi, dc, rst);
ID = readwrite8(0x04, 24, 1, cs, sck, mosi, dc, rst); // ST7789 needs twice
return ID;
}
#endif
bool meshtastic_DeviceState_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_iter_t *field)
{
if (ostream) {
@ -497,6 +569,12 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) || \
defined(HX8357_CS) || defined(USE_ST7789)
bool hasScreen = true;
#ifdef HELTEC_MESH_NODE_T114
uint32_t st7789_id = get_st7789_id(ST7789_NSS, ST7789_SCK, ST7789_SDA, ST7789_RS, ST7789_RESET);
if (st7789_id == 0xFFFFFF) {
hasScreen = false;
}
#endif
#elif ARCH_PORTDUINO
bool hasScreen = false;
if (settingsMap[displayPanel])
@ -782,12 +860,12 @@ void NodeDB::installDefaultDeviceState()
#ifdef USERPREFS_CONFIG_OWNER_LONG_NAME
snprintf(owner.long_name, sizeof(owner.long_name), USERPREFS_CONFIG_OWNER_LONG_NAME);
#else
snprintf(owner.long_name, sizeof(owner.long_name), "Meshtastic %02x%02x", ourMacAddr[4], ourMacAddr[5]);
snprintf(owner.long_name, sizeof(owner.long_name), "Meshtastic %04x", getNodeNum() & 0x0ffff);
#endif
#ifdef USERPREFS_CONFIG_OWNER_SHORT_NAME
snprintf(owner.short_name, sizeof(owner.short_name), USERPREFS_CONFIG_OWNER_SHORT_NAME);
#else
snprintf(owner.short_name, sizeof(owner.short_name), "%02x%02x", ourMacAddr[4], ourMacAddr[5]);
snprintf(owner.short_name, sizeof(owner.short_name), "%04x", getNodeNum() & 0x0ffff);
#endif
snprintf(owner.id, sizeof(owner.id), "!%08x", getNodeNum()); // Default node ID now based on nodenum
memcpy(owner.macaddr, ourMacAddr, sizeof(owner.macaddr));

View File

@ -31,31 +31,7 @@ void LockingArduinoHal::spiEndTransaction()
#if ARCH_PORTDUINO
void LockingArduinoHal::spiTransfer(uint8_t *out, size_t len, uint8_t *in)
{
if (busy == RADIOLIB_NC) {
spi->transfer(out, in, len);
} else {
uint16_t offset = 0;
while (len) {
uint8_t block_size = (len < 20 ? len : 20);
spi->transfer((out != NULL ? out + offset : NULL), (in != NULL ? in + offset : NULL), block_size);
if (block_size == len)
return;
// ensure GPIO is low
uint32_t start = millis();
while (digitalRead(busy)) {
if (!Throttle::isWithinTimespanMs(start, 2000)) {
LOG_ERROR("GPIO mid-transfer timeout, is it connected?");
return;
}
}
offset += block_size;
len -= block_size;
}
}
spi->transfer(out, in, len);
}
#endif

View File

@ -22,18 +22,11 @@
class LockingArduinoHal : public ArduinoHal
{
public:
LockingArduinoHal(SPIClass &spi, SPISettings spiSettings, RADIOLIB_PIN_TYPE _busy = RADIOLIB_NC)
: ArduinoHal(spi, spiSettings)
{
#if ARCH_PORTDUINO
busy = _busy;
#endif
};
LockingArduinoHal(SPIClass &spi, SPISettings spiSettings) : ArduinoHal(spi, spiSettings){};
void spiBeginTransaction() override;
void spiEndTransaction() override;
#if ARCH_PORTDUINO
RADIOLIB_PIN_TYPE busy;
void spiTransfer(uint8_t *out, size_t len, uint8_t *in) override;
#endif

View File

@ -37,7 +37,6 @@ static MemoryDynamic<meshtastic_MeshPacket> staticPool;
Allocator<meshtastic_MeshPacket> &packetPool = staticPool;
static uint8_t bytes[MAX_LORA_PAYLOAD_LEN + 1] __attribute__((__aligned__));
static uint8_t ScratchEncrypted[MAX_LORA_PAYLOAD_LEN + 1] __attribute__((__aligned__));
/**
* Constructor
@ -327,9 +326,6 @@ bool perhapsDecode(meshtastic_MeshPacket *p)
}
bool decrypted = false;
ChannelIndex chIndex = 0;
memcpy(bytes, p->encrypted.bytes,
rawSize); // we have to copy into a scratch buffer, because these bytes are a union with the decoded protobuf
memcpy(ScratchEncrypted, p->encrypted.bytes, rawSize);
#if !(MESHTASTIC_EXCLUDE_PKI)
// Attempt PKI decryption first
if (p->channel == 0 && isToUs(p) && p->to > 0 && !isBroadcast(p->to) && nodeDB->getMeshNode(p->from) != nullptr &&
@ -337,7 +333,7 @@ bool perhapsDecode(meshtastic_MeshPacket *p)
rawSize > MESHTASTIC_PKC_OVERHEAD) {
LOG_DEBUG("Attempt PKI decryption");
if (crypto->decryptCurve25519(p->from, nodeDB->getMeshNode(p->from)->user.public_key, p->id, rawSize, ScratchEncrypted,
if (crypto->decryptCurve25519(p->from, nodeDB->getMeshNode(p->from)->user.public_key, p->id, rawSize, p->encrypted.bytes,
bytes)) {
LOG_INFO("PKI Decryption worked!");
memset(&p->decoded, 0, sizeof(p->decoded));
@ -349,8 +345,6 @@ bool perhapsDecode(meshtastic_MeshPacket *p)
p->pki_encrypted = true;
memcpy(&p->public_key.bytes, nodeDB->getMeshNode(p->from)->user.public_key.bytes, 32);
p->public_key.size = 32;
// memcpy(bytes, ScratchEncrypted, rawSize); // TODO: Rename the bytes buffers
// chIndex = 8;
} else {
LOG_ERROR("PKC Decrypted, but pb_decode failed!");
return false;
@ -367,6 +361,9 @@ bool perhapsDecode(meshtastic_MeshPacket *p)
for (chIndex = 0; chIndex < channels.getNumChannels(); chIndex++) {
// Try to use this hash/channel pair
if (channels.decryptForHash(chIndex, p->channel)) {
// we have to copy into a scratch buffer, because these bytes are a union with the decoded protobuf. Create a
// fresh copy for each decrypt attempt.
memcpy(bytes, p->encrypted.bytes, rawSize);
// Try to decrypt the packet if we can
crypto->decrypt(p->from, p->id, rawSize, bytes);
@ -515,9 +512,8 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
*node->user.public_key.bytes);
return meshtastic_Routing_Error_PKI_FAILED;
}
crypto->encryptCurve25519(p->to, getFrom(p), node->user.public_key, p->id, numbytes, bytes, ScratchEncrypted);
crypto->encryptCurve25519(p->to, getFrom(p), node->user.public_key, p->id, numbytes, bytes, p->encrypted.bytes);
numbytes += MESHTASTIC_PKC_OVERHEAD;
memcpy(p->encrypted.bytes, ScratchEncrypted, numbytes);
p->channel = 0;
p->pki_encrypted = true;
} else {

View File

@ -50,9 +50,7 @@ template <typename T> bool SX126xInterface<T>::init()
#endif
#if ARCH_PORTDUINO
float tcxoVoltage = 0;
if (settingsMap[dio3_tcxo_voltage])
tcxoVoltage = 1.8;
float tcxoVoltage = (float)settingsMap[dio3_tcxo_voltage] / 1000;
if (settingsMap[sx126x_ant_sw] != RADIOLIB_NC) {
digitalWrite(settingsMap[sx126x_ant_sw], HIGH);
pinMode(settingsMap[sx126x_ant_sw], OUTPUT);

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#include "meshtastic/admin.pb.h"
#if PB_PROTO_HEADER_VERSION != 40

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#ifndef PB_MESHTASTIC_MESHTASTIC_ADMIN_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_ADMIN_PB_H_INCLUDED

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#include "meshtastic/apponly.pb.h"
#if PB_PROTO_HEADER_VERSION != 40

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#ifndef PB_MESHTASTIC_MESHTASTIC_APPONLY_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_APPONLY_PB_H_INCLUDED

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#include "meshtastic/atak.pb.h"
#if PB_PROTO_HEADER_VERSION != 40

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#ifndef PB_MESHTASTIC_MESHTASTIC_ATAK_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_ATAK_PB_H_INCLUDED

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#include "meshtastic/cannedmessages.pb.h"
#if PB_PROTO_HEADER_VERSION != 40

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#ifndef PB_MESHTASTIC_MESHTASTIC_CANNEDMESSAGES_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_CANNEDMESSAGES_PB_H_INCLUDED

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#include "meshtastic/channel.pb.h"
#if PB_PROTO_HEADER_VERSION != 40

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#ifndef PB_MESHTASTIC_MESHTASTIC_CHANNEL_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_CHANNEL_PB_H_INCLUDED

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#include "meshtastic/clientonly.pb.h"
#if PB_PROTO_HEADER_VERSION != 40

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#ifndef PB_MESHTASTIC_MESHTASTIC_CLIENTONLY_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_CLIENTONLY_PB_H_INCLUDED

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#include "meshtastic/config.pb.h"
#if PB_PROTO_HEADER_VERSION != 40

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#ifndef PB_MESHTASTIC_MESHTASTIC_CONFIG_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_CONFIG_PB_H_INCLUDED

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#include "meshtastic/connection_status.pb.h"
#if PB_PROTO_HEADER_VERSION != 40

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#ifndef PB_MESHTASTIC_MESHTASTIC_CONNECTION_STATUS_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_CONNECTION_STATUS_PB_H_INCLUDED

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#include "meshtastic/device_ui.pb.h"
#if PB_PROTO_HEADER_VERSION != 40

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#ifndef PB_MESHTASTIC_MESHTASTIC_DEVICE_UI_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_DEVICE_UI_PB_H_INCLUDED

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#include "meshtastic/deviceonly.pb.h"
#if PB_PROTO_HEADER_VERSION != 40

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#ifndef PB_MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_INCLUDED

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#include "meshtastic/localonly.pb.h"
#if PB_PROTO_HEADER_VERSION != 40

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#ifndef PB_MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_INCLUDED

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#include "meshtastic/mesh.pb.h"
#if PB_PROTO_HEADER_VERSION != 40

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#ifndef PB_MESHTASTIC_MESHTASTIC_MESH_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_MESH_PB_H_INCLUDED
@ -220,6 +220,9 @@ typedef enum _meshtastic_HardwareModel {
the same frame format.
Runs on linux, see https://github.com/Jorropo/routastic */
meshtastic_HardwareModel_ROUTASTIC = 85,
/* Mesh-Tab, esp32 based
https://github.com/valzzu/Mesh-Tab */
meshtastic_HardwareModel_MESH_TAB = 86,
/* ------------------------------------------------------------------------------------------------------------------------------------------
Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
------------------------------------------------------------------------------------------------------------------------------------------ */
@ -414,6 +417,8 @@ typedef enum _meshtastic_MeshPacket_Priority {
meshtastic_MeshPacket_Priority_RESPONSE = 80,
/* Higher priority for specific message types (portnums) to distinguish between other reliable packets. */
meshtastic_MeshPacket_Priority_HIGH = 100,
/* Higher priority alert message used for critical alerts which take priority over other reliable packets. */
meshtastic_MeshPacket_Priority_ALERT = 110,
/* Ack/naks are sent with very high priority to ensure that retransmission
stops as soon as possible */
meshtastic_MeshPacket_Priority_ACK = 120,

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#include "meshtastic/module_config.pb.h"
#if PB_PROTO_HEADER_VERSION != 40

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#ifndef PB_MESHTASTIC_MESHTASTIC_MODULE_CONFIG_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_MODULE_CONFIG_PB_H_INCLUDED

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#include "meshtastic/mqtt.pb.h"
#if PB_PROTO_HEADER_VERSION != 40

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#ifndef PB_MESHTASTIC_MESHTASTIC_MQTT_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_MQTT_PB_H_INCLUDED

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#include "meshtastic/paxcount.pb.h"
#if PB_PROTO_HEADER_VERSION != 40

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#ifndef PB_MESHTASTIC_MESHTASTIC_PAXCOUNT_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_PAXCOUNT_PB_H_INCLUDED

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#include "meshtastic/portnums.pb.h"
#if PB_PROTO_HEADER_VERSION != 40

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#ifndef PB_MESHTASTIC_MESHTASTIC_PORTNUMS_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_PORTNUMS_PB_H_INCLUDED
@ -72,6 +72,8 @@ typedef enum _meshtastic_PortNum {
/* Same as Text Message but originating from Detection Sensor Module.
NOTE: This portnum traffic is not sent to the public MQTT starting at firmware version 2.2.9 */
meshtastic_PortNum_DETECTION_SENSOR_APP = 10,
/* Same as Text Message but used for critical alerts. */
meshtastic_PortNum_ALERT_APP = 11,
/* Provides a 'ping' service that replies to any packet it receives.
Also serves as a small example module.
ENCODING: ASCII Plaintext */

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#include "meshtastic/powermon.pb.h"
#if PB_PROTO_HEADER_VERSION != 40

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#ifndef PB_MESHTASTIC_MESHTASTIC_POWERMON_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_POWERMON_PB_H_INCLUDED

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#include "meshtastic/remote_hardware.pb.h"
#if PB_PROTO_HEADER_VERSION != 40

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#ifndef PB_MESHTASTIC_MESHTASTIC_REMOTE_HARDWARE_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_REMOTE_HARDWARE_PB_H_INCLUDED

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#include "meshtastic/rtttl.pb.h"
#if PB_PROTO_HEADER_VERSION != 40

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#ifndef PB_MESHTASTIC_MESHTASTIC_RTTTL_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_RTTTL_PB_H_INCLUDED

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#include "meshtastic/storeforward.pb.h"
#if PB_PROTO_HEADER_VERSION != 40

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#ifndef PB_MESHTASTIC_MESHTASTIC_STOREFORWARD_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_STOREFORWARD_PB_H_INCLUDED

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#include "meshtastic/telemetry.pb.h"
#if PB_PROTO_HEADER_VERSION != 40

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#ifndef PB_MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_INCLUDED
@ -79,7 +79,9 @@ typedef enum _meshtastic_TelemetrySensorType {
/* SCD40/SCD41 CO2, humidity, temperature sensor */
meshtastic_TelemetrySensorType_SCD4X = 32,
/* ClimateGuard RadSens, radiation, Geiger-Muller Tube */
meshtastic_TelemetrySensorType_RADSENS = 33
meshtastic_TelemetrySensorType_RADSENS = 33,
/* High accuracy current and voltage */
meshtastic_TelemetrySensorType_INA226 = 34
} meshtastic_TelemetrySensorType;
/* Struct definitions */
@ -304,8 +306,8 @@ extern "C" {
/* Helper constants for enums */
#define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET
#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_RADSENS
#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_RADSENS+1))
#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_INA226
#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_INA226+1))

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#include "meshtastic/xmodem.pb.h"
#if PB_PROTO_HEADER_VERSION != 40

View File

@ -1,5 +1,5 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-0.4.9 */
/* Generated by nanopb-0.4.9.1 */
#ifndef PB_MESHTASTIC_MESHTASTIC_XMODEM_PB_H_INCLUDED
#define PB_MESHTASTIC_MESHTASTIC_XMODEM_PB_H_INCLUDED

View File

@ -93,6 +93,7 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer)
ResourceNode *nodeJsonScanNetworks = new ResourceNode("/json/scanNetworks", "GET", &handleScanNetworks);
ResourceNode *nodeJsonBlinkLED = new ResourceNode("/json/blink", "POST", &handleBlinkLED);
ResourceNode *nodeJsonReport = new ResourceNode("/json/report", "GET", &handleReport);
ResourceNode *nodeJsonNodes = new ResourceNode("/json/nodes", "GET", &handleNodes);
ResourceNode *nodeJsonFsBrowseStatic = new ResourceNode("/json/fs/browse/static", "GET", &handleFsBrowseStatic);
ResourceNode *nodeJsonDelete = new ResourceNode("/json/fs/delete/static", "DELETE", &handleFsDeleteStatic);
@ -112,6 +113,7 @@ void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer)
secureServer->registerNode(nodeJsonFsBrowseStatic);
secureServer->registerNode(nodeJsonDelete);
secureServer->registerNode(nodeJsonReport);
secureServer->registerNode(nodeJsonNodes);
// secureServer->registerNode(nodeUpdateFs);
// secureServer->registerNode(nodeDeleteFs);
secureServer->registerNode(nodeAdmin);
@ -680,6 +682,78 @@ void handleReport(HTTPRequest *req, HTTPResponse *res)
delete value;
}
void handleNodes(HTTPRequest *req, HTTPResponse *res)
{
ResourceParameters *params = req->getParams();
std::string content;
if (!params->getQueryParameter("content", content)) {
content = "json";
}
if (content == "json") {
res->setHeader("Content-Type", "application/json");
res->setHeader("Access-Control-Allow-Origin", "*");
res->setHeader("Access-Control-Allow-Methods", "GET");
} else {
res->setHeader("Content-Type", "text/html");
res->println("<pre>");
}
JSONArray nodesArray;
uint32_t readIndex = 0;
const meshtastic_NodeInfoLite *tempNodeInfo = nodeDB->readNextMeshNode(readIndex);
while (tempNodeInfo != NULL) {
if (tempNodeInfo->has_user) {
JSONObject node;
char id[16];
snprintf(id, sizeof(id), "!%08x", tempNodeInfo->num);
node["id"] = new JSONValue(id);
node["snr"] = new JSONValue(tempNodeInfo->snr);
node["via_mqtt"] = new JSONValue(BoolToString(tempNodeInfo->via_mqtt));
node["last_heard"] = new JSONValue((int)tempNodeInfo->last_heard);
node["position"] = new JSONValue();
if (nodeDB->hasValidPosition(tempNodeInfo)) {
JSONObject position;
position["latitude"] = new JSONValue((float)tempNodeInfo->position.latitude_i * 1e-7);
position["longitude"] = new JSONValue((float)tempNodeInfo->position.longitude_i * 1e-7);
position["altitude"] = new JSONValue((int)tempNodeInfo->position.altitude);
node["position"] = new JSONValue(position);
}
JSONObject user;
node["long_name"] = new JSONValue(tempNodeInfo->user.long_name);
node["short_name"] = new JSONValue(tempNodeInfo->user.short_name);
char macStr[18];
snprintf(macStr, sizeof(macStr), "%02X:%02X:%02X:%02X:%02X:%02X", tempNodeInfo->user.macaddr[0],
tempNodeInfo->user.macaddr[1], tempNodeInfo->user.macaddr[2], tempNodeInfo->user.macaddr[3],
tempNodeInfo->user.macaddr[4], tempNodeInfo->user.macaddr[5]);
node["mac_address"] = new JSONValue(macStr);
node["hw_model"] = new JSONValue(tempNodeInfo->user.hw_model);
nodesArray.push_back(new JSONValue(node));
}
tempNodeInfo = nodeDB->readNextMeshNode(readIndex);
}
// collect data to inner data object
JSONObject jsonObjInner;
jsonObjInner["nodes"] = new JSONValue(nodesArray);
// create json output structure
JSONObject jsonObjOuter;
jsonObjOuter["data"] = new JSONValue(jsonObjInner);
jsonObjOuter["status"] = new JSONValue("ok");
// serialize and write it to the stream
JSONValue *value = new JSONValue(jsonObjOuter);
res->print(value->Stringify().c_str());
delete value;
}
/*
This supports the Apple Captive Network Assistant (CNA) Portal
*/

View File

@ -13,6 +13,7 @@ void handleFsBrowseStatic(HTTPRequest *req, HTTPResponse *res);
void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res);
void handleBlinkLED(HTTPRequest *req, HTTPResponse *res);
void handleReport(HTTPRequest *req, HTTPResponse *res);
void handleNodes(HTTPRequest *req, HTTPResponse *res);
void handleUpdateFs(HTTPRequest *req, HTTPResponse *res);
void handleDeleteFsContent(HTTPRequest *req, HTTPResponse *res);
void handleFs(HTTPRequest *req, HTTPResponse *res);

View File

@ -155,8 +155,6 @@ ProcessMessage RangeTestModuleRadio::handleReceived(const meshtastic_MeshPacket
LOG_DEBUG("mp.from %d", mp.from);
LOG_DEBUG("mp.rx_snr %f", mp.rx_snr);
LOG_DEBUG("mp.hop_limit %d", mp.hop_limit);
// LOG_DEBUG("mp.decoded.position.latitude_i %d", mp.decoded.position.latitude_i); // Deprecated
// LOG_DEBUG("mp.decoded.position.longitude_i %d", mp.decoded.position.longitude_i); // Deprecated
LOG_DEBUG("---- Node Information of Received Packet (mp.from):");
LOG_DEBUG("n->user.long_name %s", n->user.long_name);
LOG_DEBUG("n->user.short_name %s", n->user.short_name);
@ -194,8 +192,6 @@ bool RangeTestModuleRadio::appendFile(const meshtastic_MeshPacket &mp)
LOG_DEBUG("mp.from %d", mp.from);
LOG_DEBUG("mp.rx_snr %f", mp.rx_snr);
LOG_DEBUG("mp.hop_limit %d", mp.hop_limit);
// LOG_DEBUG("mp.decoded.position.latitude_i %d", mp.decoded.position.latitude_i); // Deprecated
// LOG_DEBUG("mp.decoded.position.longitude_i %d", mp.decoded.position.longitude_i); // Deprecated
LOG_DEBUG("---- Node Information of Received Packet (mp.from):");
LOG_DEBUG("n->user.long_name %s", n->user.long_name);
LOG_DEBUG("n->user.short_name %s", n->user.short_name);
@ -265,13 +261,21 @@ bool RangeTestModuleRadio::appendFile(const meshtastic_MeshPacket &mp)
fileToAppend.printf("??:??:??,"); // Time
}
fileToAppend.printf("%d,", getFrom(&mp)); // From
fileToAppend.printf("%s,", n->user.long_name); // Long Name
fileToAppend.printf("%f,", n->position.latitude_i * 1e-7); // Sender Lat
fileToAppend.printf("%f,", n->position.longitude_i * 1e-7); // Sender Long
fileToAppend.printf("%f,", gpsStatus->getLatitude() * 1e-7); // RX Lat
fileToAppend.printf("%f,", gpsStatus->getLongitude() * 1e-7); // RX Long
fileToAppend.printf("%d,", gpsStatus->getAltitude()); // RX Altitude
fileToAppend.printf("%d,", getFrom(&mp)); // From
fileToAppend.printf("%s,", n->user.long_name); // Long Name
fileToAppend.printf("%f,", n->position.latitude_i * 1e-7); // Sender Lat
fileToAppend.printf("%f,", n->position.longitude_i * 1e-7); // Sender Long
if (gpsStatus->getIsConnected() || config.position.fixed_position) {
fileToAppend.printf("%f,", gpsStatus->getLatitude() * 1e-7); // RX Lat
fileToAppend.printf("%f,", gpsStatus->getLongitude() * 1e-7); // RX Long
fileToAppend.printf("%d,", gpsStatus->getAltitude()); // RX Altitude
} else {
// When the phone API is in use, the node info will be updated with position
meshtastic_NodeInfoLite *us = nodeDB->getMeshNode(nodeDB->getNodeNum());
fileToAppend.printf("%f,", us->position.latitude_i * 1e-7); // RX Lat
fileToAppend.printf("%f,", us->position.longitude_i * 1e-7); // RX Long
fileToAppend.printf("%d,", us->position.altitude); // RX Altitude
}
fileToAppend.printf("%f,", mp.rx_snr); // RX SNR
@ -292,4 +296,4 @@ bool RangeTestModuleRadio::appendFile(const meshtastic_MeshPacket &mp)
#endif
return 1;
}
}

View File

@ -73,11 +73,11 @@ void StoreForwardModule::populatePSRAM()
LOG_DEBUG("Before PSRAM init: heap %d/%d PSRAM %d/%d", memGet.getFreeHeap(), memGet.getHeapSize(), memGet.getFreePsram(),
memGet.getPsramSize());
/* Use a maximum of 2/3 the available PSRAM unless otherwise specified.
/* Use a maximum of 3/4 the available PSRAM unless otherwise specified.
Note: This needs to be done after every thing that would use PSRAM
*/
uint32_t numberOfPackets =
(this->records ? this->records : (((memGet.getFreePsram() / 3) * 2) / sizeof(PacketHistoryStruct)));
(this->records ? this->records : (((memGet.getFreePsram() / 4) * 3) / sizeof(PacketHistoryStruct)));
this->records = numberOfPackets;
#if defined(ARCH_ESP32)
this->packetHistory = static_cast<PacketHistoryStruct *>(ps_calloc(numberOfPackets, sizeof(PacketHistoryStruct)));
@ -198,6 +198,9 @@ void StoreForwardModule::historyAdd(const meshtastic_MeshPacket &mp)
this->packetHistory[this->packetHistoryTotalCount].to = mp.to;
this->packetHistory[this->packetHistoryTotalCount].channel = mp.channel;
this->packetHistory[this->packetHistoryTotalCount].from = getFrom(&mp);
this->packetHistory[this->packetHistoryTotalCount].id = mp.id;
this->packetHistory[this->packetHistoryTotalCount].reply_id = p.reply_id;
this->packetHistory[this->packetHistoryTotalCount].emoji = (bool)p.emoji;
this->packetHistory[this->packetHistoryTotalCount].payload_size = p.payload.size;
memcpy(this->packetHistory[this->packetHistoryTotalCount].payload, p.payload.bytes, meshtastic_Constants_DATA_PAYLOAD_LEN);
@ -244,8 +247,11 @@ meshtastic_MeshPacket *StoreForwardModule::preparePayload(NodeNum dest, uint32_t
p->to = local ? this->packetHistory[i].to : dest; // PhoneAPI can handle original `to`
p->from = this->packetHistory[i].from;
p->id = this->packetHistory[i].id;
p->channel = this->packetHistory[i].channel;
p->decoded.reply_id = this->packetHistory[i].reply_id;
p->rx_time = this->packetHistory[i].time;
p->decoded.emoji = (uint32_t)this->packetHistory[i].emoji;
// Let's assume that if the server received the S&F request that the client is in range.
// TODO: Make this configurable.
@ -617,4 +623,4 @@ StoreForwardModule::StoreForwardModule()
disable();
}
#endif
}
}

View File

@ -13,7 +13,10 @@ struct PacketHistoryStruct {
uint32_t time;
uint32_t to;
uint32_t from;
uint32_t id;
uint8_t channel;
uint32_t reply_id;
bool emoji;
uint8_t payload[meshtastic_Constants_DATA_PAYLOAD_LEN];
pb_size_t payload_size;
};

View File

@ -113,12 +113,18 @@ bool AirQualityTelemetryModule::getAirQualityTelemetry(meshtastic_Telemetry *m)
m->time = getTime();
m->which_variant = meshtastic_Telemetry_air_quality_metrics_tag;
m->variant.air_quality_metrics.has_pm10_standard = true;
m->variant.air_quality_metrics.pm10_standard = data.pm10_standard;
m->variant.air_quality_metrics.has_pm25_standard = true;
m->variant.air_quality_metrics.pm25_standard = data.pm25_standard;
m->variant.air_quality_metrics.has_pm100_standard = true;
m->variant.air_quality_metrics.pm100_standard = data.pm100_standard;
m->variant.air_quality_metrics.has_pm10_environmental = true;
m->variant.air_quality_metrics.pm10_environmental = data.pm10_env;
m->variant.air_quality_metrics.has_pm25_environmental = true;
m->variant.air_quality_metrics.pm25_environmental = data.pm25_env;
m->variant.air_quality_metrics.has_pm100_environmental = true;
m->variant.air_quality_metrics.pm100_environmental = data.pm100_env;
LOG_INFO("Send: PM1.0(Standard)=%i, PM2.5(Standard)=%i, PM10.0(Standard)=%i", m->variant.air_quality_metrics.pm10_standard,

View File

@ -56,6 +56,8 @@ int32_t PowerTelemetryModule::runOnce()
// therefore, we should only enable the sensor loop if measurement is also enabled
if (ina219Sensor.hasSensor() && !ina219Sensor.isInitialized())
result = ina219Sensor.runOnce();
if (ina226Sensor.hasSensor() && !ina226Sensor.isInitialized())
result = ina226Sensor.runOnce();
if (ina260Sensor.hasSensor() && !ina260Sensor.isInitialized())
result = ina260Sensor.runOnce();
if (ina3221Sensor.hasSensor() && !ina3221Sensor.isInitialized())
@ -170,6 +172,8 @@ bool PowerTelemetryModule::getPowerTelemetry(meshtastic_Telemetry *m)
#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO)
if (ina219Sensor.hasSensor())
valid = ina219Sensor.getMetrics(m);
if (ina226Sensor.hasSensor())
valid = ina226Sensor.getMetrics(m);
if (ina260Sensor.hasSensor())
valid = ina260Sensor.getMetrics(m);
if (ina3221Sensor.hasSensor())
@ -253,4 +257,4 @@ bool PowerTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
return false;
}
#endif
#endif

View File

@ -0,0 +1,13 @@
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
#pragma once
class CurrentSensor
{
public:
virtual int16_t getCurrentMa() = 0;
};
#endif

View File

@ -45,4 +45,9 @@ uint16_t INA219Sensor::getBusVoltageMv()
return lround(ina219.getBusVoltage_V() * 1000);
}
int16_t INA219Sensor::getCurrentMa()
{
return lround(ina219.getCurrent_mA());
}
#endif

View File

@ -3,11 +3,12 @@
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "CurrentSensor.h"
#include "TelemetrySensor.h"
#include "VoltageSensor.h"
#include <Adafruit_INA219.h>
class INA219Sensor : public TelemetrySensor, VoltageSensor
class INA219Sensor : public TelemetrySensor, VoltageSensor, CurrentSensor
{
private:
Adafruit_INA219 ina219;
@ -20,6 +21,7 @@ class INA219Sensor : public TelemetrySensor, VoltageSensor
virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
virtual uint16_t getBusVoltageMv() override;
virtual int16_t getCurrentMa() override;
};
#endif

View File

@ -0,0 +1,58 @@
#include "configuration.h"
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "INA226.h"
#include "INA226Sensor.h"
#include "TelemetrySensor.h"
INA226Sensor::INA226Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_INA226, "INA226") {}
int32_t INA226Sensor::runOnce()
{
LOG_INFO("Init sensor: %s", sensorName);
if (!hasSensor()) {
return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
}
begin(nodeTelemetrySensorsMap[sensorType].second, nodeTelemetrySensorsMap[sensorType].first);
if (!status) {
status = ina226.begin();
}
return initI2CSensor();
}
void INA226Sensor::setup() {}
void INA226Sensor::begin(TwoWire *wire, uint8_t addr)
{
_wire = wire;
_addr = addr;
ina226 = INA226(_addr, _wire);
_wire->begin();
}
bool INA226Sensor::getMetrics(meshtastic_Telemetry *measurement)
{
measurement->variant.environment_metrics.has_voltage = true;
measurement->variant.environment_metrics.has_current = true;
// mV conversion to V
measurement->variant.environment_metrics.voltage = ina226.getBusVoltage() / 1000;
measurement->variant.environment_metrics.current = ina226.getCurrent_mA();
return true;
}
uint16_t INA226Sensor::getBusVoltageMv()
{
return lround(ina226.getBusVoltage());
}
int16_t INA226Sensor::getCurrentMa()
{
return lround(ina226.getCurrent_mA());
}
#endif

View File

@ -0,0 +1,30 @@
#include "configuration.h"
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "CurrentSensor.h"
#include "TelemetrySensor.h"
#include "VoltageSensor.h"
#include <INA226.h>
class INA226Sensor : public TelemetrySensor, VoltageSensor, CurrentSensor
{
private:
uint8_t _addr = INA_ADDR;
TwoWire *_wire = &Wire;
INA226 ina226 = INA226(_addr, _wire);
protected:
virtual void setup() override;
void begin(TwoWire *wire = &Wire, uint8_t addr = INA_ADDR);
public:
INA226Sensor();
virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
virtual uint16_t getBusVoltageMv() override;
virtual int16_t getCurrentMa() override;
};
#endif

View File

@ -102,4 +102,9 @@ uint16_t INA3221Sensor::getBusVoltageMv()
return lround(ina3221.getVoltage(BAT_CH) * 1000);
}
int16_t INA3221Sensor::getCurrentMa()
{
return lround(ina3221.getCurrent(BAT_CH));
}
#endif

View File

@ -3,11 +3,12 @@
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "CurrentSensor.h"
#include "TelemetrySensor.h"
#include "VoltageSensor.h"
#include <INA3221.h>
class INA3221Sensor : public TelemetrySensor, VoltageSensor
class INA3221Sensor : public TelemetrySensor, VoltageSensor, CurrentSensor
{
private:
INA3221 ina3221 = INA3221(INA3221_ADDR42_SDA);
@ -35,6 +36,7 @@ class INA3221Sensor : public TelemetrySensor, VoltageSensor
int32_t runOnce() override;
bool getMetrics(meshtastic_Telemetry *measurement) override;
virtual uint16_t getBusVoltageMv() override;
virtual int16_t getCurrentMa() override;
};
struct _INA3221Measurement {

View File

@ -19,24 +19,237 @@
#include <WiFi.h>
#endif
#include "Default.h"
#if !defined(ARCH_NRF52) || NRF52_USE_JSON
#include "serialization/JSON.h"
#include "serialization/MeshPacketSerializer.h"
#endif
#include <Throttle.h>
#include <assert.h>
#include <pb_decode.h>
#include <utility>
const int reconnectMax = 5;
#include <IPAddress.h>
#if defined(ARCH_PORTDUINO)
#include <netinet/in.h>
#elif !defined(ntohl)
#include <machine/endian.h>
#define ntohl __ntohl
#endif
MQTT *mqtt;
static MemoryDynamic<meshtastic_ServiceEnvelope> staticMqttPool;
Allocator<meshtastic_ServiceEnvelope> &mqttPool = staticMqttPool;
namespace
{
constexpr int reconnectMax = 5;
// FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets
static uint8_t bytes[meshtastic_MqttClientProxyMessage_size + 30]; // 12 for channel name and 16 for nodeid
static bool isMqttServerAddressPrivate = false;
// meshtastic_ServiceEnvelope that automatically releases dynamically allocated memory when it goes out of scope.
struct DecodedServiceEnvelope : public meshtastic_ServiceEnvelope {
DecodedServiceEnvelope() = delete;
DecodedServiceEnvelope(const uint8_t *payload, size_t length)
: meshtastic_ServiceEnvelope(meshtastic_ServiceEnvelope_init_default),
validDecode(pb_decode_from_bytes(payload, length, &meshtastic_ServiceEnvelope_msg, this))
{
}
~DecodedServiceEnvelope()
{
if (validDecode)
pb_release(&meshtastic_ServiceEnvelope_msg, this);
}
// Clients must check that this is true before using.
const bool validDecode;
};
inline void onReceiveProto(char *topic, byte *payload, size_t length)
{
const DecodedServiceEnvelope e(payload, length);
if (!e.validDecode || e.channel_id == NULL || e.gateway_id == NULL || e.packet == NULL) {
LOG_ERROR("Invalid MQTT service envelope, topic %s, len %u!", topic, length);
return;
}
const meshtastic_Channel &ch = channels.getByName(e.channel_id);
if (strcmp(e.gateway_id, owner.id) == 0) {
// Generate an implicit ACK towards ourselves (handled and processed only locally!) for this message.
// We do this because packets are not rebroadcasted back into MQTT anymore and we assume that at least one node
// receives it when we get our own packet back. Then we'll stop our retransmissions.
if (isFromUs(e.packet))
routingModule->sendAckNak(meshtastic_Routing_Error_NONE, getFrom(e.packet), e.packet->id, ch.index);
else
LOG_INFO("Ignore downlink message we originally sent");
return;
}
if (isFromUs(e.packet)) {
LOG_INFO("Ignore downlink message we originally sent");
return;
}
// Find channel by channel_id and check downlink_enabled
if (!(strcmp(e.channel_id, "PKI") == 0 ||
(strcmp(e.channel_id, channels.getGlobalId(ch.index)) == 0 && ch.settings.downlink_enabled))) {
return;
}
LOG_INFO("Received MQTT topic %s, len=%u", topic, length);
UniquePacketPoolPacket p = packetPool.allocUniqueCopy(*e.packet);
p->via_mqtt = true; // Mark that the packet was received via MQTT
if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
if (moduleConfig.mqtt.encryption_enabled) {
LOG_INFO("Ignore decoded message on MQTT, encryption is enabled");
return;
}
if (p->decoded.portnum == meshtastic_PortNum_ADMIN_APP) {
LOG_INFO("Ignore decoded admin packet");
return;
}
p->channel = ch.index;
}
// PKI messages get accepted even if we can't decrypt
if (router && p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag && strcmp(e.channel_id, "PKI") == 0) {
const meshtastic_NodeInfoLite *tx = nodeDB->getMeshNode(getFrom(p.get()));
const meshtastic_NodeInfoLite *rx = nodeDB->getMeshNode(p->to);
// Only accept PKI messages to us, or if we have both the sender and receiver in our nodeDB, as then it's
// likely they discovered each other via a channel we have downlink enabled for
if (isToUs(p.get()) || (tx && tx->has_user && rx && rx->has_user))
router->enqueueReceivedMessage(p.release());
} else if (router && perhapsDecode(p.get())) // ignore messages if we don't have the channel key
router->enqueueReceivedMessage(p.release());
}
#if !defined(ARCH_NRF52) || NRF52_USE_JSON
// returns true if this is a valid JSON envelope which we accept on downlink
inline bool isValidJsonEnvelope(JSONObject &json)
{
// if "sender" is provided, avoid processing packets we uplinked
return (json.find("sender") != json.end() ? (json["sender"]->AsString().compare(owner.id) != 0) : true) &&
(json.find("hopLimit") != json.end() ? json["hopLimit"]->IsNumber() : true) && // hop limit should be a number
(json.find("from") != json.end()) && json["from"]->IsNumber() &&
(json["from"]->AsNumber() == nodeDB->getNodeNum()) && // only accept message if the "from" is us
(json.find("type") != json.end()) && json["type"]->IsString() && // should specify a type
(json.find("payload") != json.end()); // should have a payload
}
inline void onReceiveJson(byte *payload, size_t length)
{
char payloadStr[length + 1];
memcpy(payloadStr, payload, length);
payloadStr[length] = 0; // null terminated string
std::unique_ptr<JSONValue> json_value(JSON::Parse(payloadStr));
if (json_value == nullptr) {
LOG_ERROR("JSON received payload on MQTT but not a valid JSON");
return;
}
JSONObject json;
json = json_value->AsObject();
if (!isValidJsonEnvelope(json)) {
LOG_ERROR("JSON received payload on MQTT but not a valid envelope");
return;
}
// this is a valid envelope
if (json["type"]->AsString().compare("sendtext") == 0 && json["payload"]->IsString()) {
std::string jsonPayloadStr = json["payload"]->AsString();
LOG_INFO("JSON payload %s, length %u", jsonPayloadStr.c_str(), jsonPayloadStr.length());
// construct protobuf data packet using TEXT_MESSAGE, send it to the mesh
meshtastic_MeshPacket *p = router->allocForSending();
p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP;
if (json.find("channel") != json.end() && json["channel"]->IsNumber() &&
(json["channel"]->AsNumber() < channels.getNumChannels()))
p->channel = json["channel"]->AsNumber();
if (json.find("to") != json.end() && json["to"]->IsNumber())
p->to = json["to"]->AsNumber();
if (json.find("hopLimit") != json.end() && json["hopLimit"]->IsNumber())
p->hop_limit = json["hopLimit"]->AsNumber();
if (jsonPayloadStr.length() <= sizeof(p->decoded.payload.bytes)) {
memcpy(p->decoded.payload.bytes, jsonPayloadStr.c_str(), jsonPayloadStr.length());
p->decoded.payload.size = jsonPayloadStr.length();
service->sendToMesh(p, RX_SRC_LOCAL);
} else {
LOG_WARN("Received MQTT json payload too long, drop");
}
} else if (json["type"]->AsString().compare("sendposition") == 0 && json["payload"]->IsObject()) {
// invent the "sendposition" type for a valid envelope
JSONObject posit;
posit = json["payload"]->AsObject(); // get nested JSON Position
meshtastic_Position pos = meshtastic_Position_init_default;
if (posit.find("latitude_i") != posit.end() && posit["latitude_i"]->IsNumber())
pos.latitude_i = posit["latitude_i"]->AsNumber();
if (posit.find("longitude_i") != posit.end() && posit["longitude_i"]->IsNumber())
pos.longitude_i = posit["longitude_i"]->AsNumber();
if (posit.find("altitude") != posit.end() && posit["altitude"]->IsNumber())
pos.altitude = posit["altitude"]->AsNumber();
if (posit.find("time") != posit.end() && posit["time"]->IsNumber())
pos.time = posit["time"]->AsNumber();
// construct protobuf data packet using POSITION, send it to the mesh
meshtastic_MeshPacket *p = router->allocForSending();
p->decoded.portnum = meshtastic_PortNum_POSITION_APP;
if (json.find("channel") != json.end() && json["channel"]->IsNumber() &&
(json["channel"]->AsNumber() < channels.getNumChannels()))
p->channel = json["channel"]->AsNumber();
if (json.find("to") != json.end() && json["to"]->IsNumber())
p->to = json["to"]->AsNumber();
if (json.find("hopLimit") != json.end() && json["hopLimit"]->IsNumber())
p->hop_limit = json["hopLimit"]->AsNumber();
p->decoded.payload.size =
pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), &meshtastic_Position_msg,
&pos); // make the Data protobuf from position
service->sendToMesh(p, RX_SRC_LOCAL);
} else {
LOG_DEBUG("JSON ignore downlink message with unsupported type");
}
}
#endif
/// Determines if the given IPAddress is a private IPv4 address, i.e. not routable on the public internet.
bool isPrivateIpAddress(const IPAddress &ip)
{
constexpr struct {
uint32_t network;
uint32_t mask;
} privateCidrRanges[] = {
{.network = 192u << 24 | 168 << 16, .mask = 0xffff0000}, // 192.168.0.0/16
{.network = 172u << 24 | 16 << 16, .mask = 0xfff00000}, // 172.16.0.0/12
{.network = 169u << 24 | 254 << 16, .mask = 0xffff0000}, // 169.254.0.0/16
{.network = 10u << 24, .mask = 0xff000000}, // 10.0.0.0/8
{.network = 127u << 24 | 1, .mask = 0xffffffff}, // 127.0.0.1/32
};
const uint32_t addr = ntohl(ip);
for (const auto &cidrRange : privateCidrRanges) {
if (cidrRange.network == (addr & cidrRange.mask)) {
LOG_INFO("MQTT server on a private IP");
return true;
}
}
return false;
}
// Separate a <host>[:<port>] string. Returns a pair containing the parsed host and port. If the port is
// not present in the input string, or is invalid, the value of the `port` argument will be returned.
std::pair<String, uint16_t> parseHostAndPort(String server, uint16_t port = 0)
{
const int delimIndex = server.indexOf(':');
if (delimIndex > 0) {
const long parsedPort = server.substring(delimIndex + 1, server.length()).toInt();
if (parsedPort < 1 || parsedPort > UINT16_MAX) {
LOG_WARN("Invalid MQTT port %d: %s", parsedPort, server.c_str());
} else {
port = parsedPort;
}
server[delimIndex] = 0;
}
return std::make_pair(std::move(server), port);
}
} // namespace
void MQTT::mqttCallback(char *topic, byte *payload, unsigned int length)
{
mqtt->onReceive(topic, payload, length);
@ -49,170 +262,32 @@ void MQTT::onClientProxyReceive(meshtastic_MqttClientProxyMessage msg)
void MQTT::onReceive(char *topic, byte *payload, size_t length)
{
meshtastic_ServiceEnvelope e = meshtastic_ServiceEnvelope_init_default;
if (moduleConfig.mqtt.json_enabled && (strncmp(topic, jsonTopic.c_str(), jsonTopic.length()) == 0)) {
// check if this is a json payload message by comparing the topic start
char payloadStr[length + 1];
memcpy(payloadStr, payload, length);
payloadStr[length] = 0; // null terminated string
JSONValue *json_value = JSON::Parse(payloadStr);
if (json_value != NULL) {
// check if it is a valid envelope
JSONObject json;
json = json_value->AsObject();
// parse the channel name from the topic string
// the topic has been checked above for having jsonTopic prefix, so just move past it
char *ptr = topic + jsonTopic.length();
ptr = strtok(ptr, "/") ? strtok(ptr, "/") : ptr; // if another "/" was added, parse string up to that character
meshtastic_Channel sendChannel = channels.getByName(ptr);
// We allow downlink JSON packets only on a channel named "mqtt"
if (strncasecmp(channels.getGlobalId(sendChannel.index), Channels::mqttChannel, strlen(Channels::mqttChannel)) == 0 &&
sendChannel.settings.downlink_enabled) {
if (isValidJsonEnvelope(json)) {
// this is a valid envelope
if (json["type"]->AsString().compare("sendtext") == 0 && json["payload"]->IsString()) {
std::string jsonPayloadStr = json["payload"]->AsString();
LOG_INFO("JSON payload %s, length %u", jsonPayloadStr.c_str(), jsonPayloadStr.length());
// construct protobuf data packet using TEXT_MESSAGE, send it to the mesh
meshtastic_MeshPacket *p = router->allocForSending();
p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP;
if (json.find("channel") != json.end() && json["channel"]->IsNumber() &&
(json["channel"]->AsNumber() < channels.getNumChannels()))
p->channel = json["channel"]->AsNumber();
if (json.find("to") != json.end() && json["to"]->IsNumber())
p->to = json["to"]->AsNumber();
if (json.find("hopLimit") != json.end() && json["hopLimit"]->IsNumber())
p->hop_limit = json["hopLimit"]->AsNumber();
if (jsonPayloadStr.length() <= sizeof(p->decoded.payload.bytes)) {
memcpy(p->decoded.payload.bytes, jsonPayloadStr.c_str(), jsonPayloadStr.length());
p->decoded.payload.size = jsonPayloadStr.length();
service->sendToMesh(p, RX_SRC_LOCAL);
} else {
LOG_WARN("Received MQTT json payload too long, drop");
}
} else if (json["type"]->AsString().compare("sendposition") == 0 && json["payload"]->IsObject()) {
// invent the "sendposition" type for a valid envelope
JSONObject posit;
posit = json["payload"]->AsObject(); // get nested JSON Position
meshtastic_Position pos = meshtastic_Position_init_default;
if (posit.find("latitude_i") != posit.end() && posit["latitude_i"]->IsNumber())
pos.latitude_i = posit["latitude_i"]->AsNumber();
if (posit.find("longitude_i") != posit.end() && posit["longitude_i"]->IsNumber())
pos.longitude_i = posit["longitude_i"]->AsNumber();
if (posit.find("altitude") != posit.end() && posit["altitude"]->IsNumber())
pos.altitude = posit["altitude"]->AsNumber();
if (posit.find("time") != posit.end() && posit["time"]->IsNumber())
pos.time = posit["time"]->AsNumber();
// construct protobuf data packet using POSITION, send it to the mesh
meshtastic_MeshPacket *p = router->allocForSending();
p->decoded.portnum = meshtastic_PortNum_POSITION_APP;
if (json.find("channel") != json.end() && json["channel"]->IsNumber() &&
(json["channel"]->AsNumber() < channels.getNumChannels()))
p->channel = json["channel"]->AsNumber();
if (json.find("to") != json.end() && json["to"]->IsNumber())
p->to = json["to"]->AsNumber();
if (json.find("hopLimit") != json.end() && json["hopLimit"]->IsNumber())
p->hop_limit = json["hopLimit"]->AsNumber();
p->decoded.payload.size =
pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes),
&meshtastic_Position_msg, &pos); // make the Data protobuf from position
service->sendToMesh(p, RX_SRC_LOCAL);
} else {
LOG_DEBUG("JSON ignore downlink message with unsupported type");
}
} else {
LOG_ERROR("JSON received payload on MQTT but not a valid envelope");
}
} else {
LOG_WARN("JSON downlink received on channel not called 'mqtt' or without downlink enabled");
}
} else {
// no json, this is an invalid payload
LOG_ERROR("JSON received payload on MQTT but not a valid JSON");
}
delete json_value;
} else {
if (length == 0) {
LOG_WARN("Empty MQTT payload received, topic %s!", topic);
return;
} else if (!pb_decode_from_bytes(payload, length, &meshtastic_ServiceEnvelope_msg, &e)) {
LOG_ERROR("Invalid MQTT service envelope, topic %s, len %u!", topic, length);
return;
} else {
if (e.channel_id == NULL || e.gateway_id == NULL) {
LOG_ERROR("Invalid MQTT service envelope, topic %s, len %u!", topic, length);
return;
}
meshtastic_Channel ch = channels.getByName(e.channel_id);
if (strcmp(e.gateway_id, owner.id) == 0) {
// Generate an implicit ACK towards ourselves (handled and processed only locally!) for this message.
// We do this because packets are not rebroadcasted back into MQTT anymore and we assume that at least one node
// receives it when we get our own packet back. Then we'll stop our retransmissions.
if (e.packet && isFromUs(e.packet))
routingModule->sendAckNak(meshtastic_Routing_Error_NONE, getFrom(e.packet), e.packet->id, ch.index);
else
LOG_INFO("Ignore downlink message we originally sent");
} else {
// Find channel by channel_id and check downlink_enabled
if ((strcmp(e.channel_id, "PKI") == 0 && e.packet) ||
(strcmp(e.channel_id, channels.getGlobalId(ch.index)) == 0 && e.packet && ch.settings.downlink_enabled)) {
LOG_INFO("Received MQTT topic %s, len=%u", topic, length);
meshtastic_MeshPacket *p = packetPool.allocCopy(*e.packet);
p->via_mqtt = true; // Mark that the packet was received via MQTT
if (isFromUs(p)) {
LOG_INFO("Ignore downlink message we originally sent");
packetPool.release(p);
free(e.channel_id);
free(e.gateway_id);
free(e.packet);
return;
}
if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
if (moduleConfig.mqtt.encryption_enabled) {
LOG_INFO("Ignore decoded message on MQTT, encryption is enabled");
packetPool.release(p);
free(e.channel_id);
free(e.gateway_id);
free(e.packet);
return;
}
if (p->decoded.portnum == meshtastic_PortNum_ADMIN_APP) {
LOG_INFO("Ignore decoded admin packet");
packetPool.release(p);
free(e.channel_id);
free(e.gateway_id);
free(e.packet);
return;
}
p->channel = ch.index;
}
// PKI messages get accepted even if we can't decrypt
if (router && p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag &&
strcmp(e.channel_id, "PKI") == 0) {
const meshtastic_NodeInfoLite *tx = nodeDB->getMeshNode(getFrom(p));
const meshtastic_NodeInfoLite *rx = nodeDB->getMeshNode(p->to);
// Only accept PKI messages to us, or if we have both the sender and receiver in our nodeDB, as then it's
// likely they discovered each other via a channel we have downlink enabled for
if (isToUs(p) || (tx && tx->has_user && rx && rx->has_user))
router->enqueueReceivedMessage(p);
} else if (router && perhapsDecode(p)) // ignore messages if we don't have the channel key
router->enqueueReceivedMessage(p);
else
packetPool.release(p);
}
}
}
// make sure to free both strings and the MeshPacket (passing in NULL is acceptable)
free(e.channel_id);
free(e.gateway_id);
free(e.packet);
if (length == 0) {
LOG_WARN("Empty MQTT payload received, topic %s!", topic);
return;
}
// check if this is a json payload message by comparing the topic start
if (moduleConfig.mqtt.json_enabled && (strncmp(topic, jsonTopic.c_str(), jsonTopic.length()) == 0)) {
#if !defined(ARCH_NRF52) || NRF52_USE_JSON
// parse the channel name from the topic string
// the topic has been checked above for having jsonTopic prefix, so just move past it
char *channelName = topic + jsonTopic.length();
// if another "/" was added, parse string up to that character
channelName = strtok(channelName, "/") ? strtok(channelName, "/") : channelName;
// We allow downlink JSON packets only on a channel named "mqtt"
meshtastic_Channel &sendChannel = channels.getByName(channelName);
if (!(strncasecmp(channels.getGlobalId(sendChannel.index), Channels::mqttChannel, strlen(Channels::mqttChannel)) == 0 &&
sendChannel.settings.downlink_enabled)) {
LOG_WARN("JSON downlink received on channel not called 'mqtt' or without downlink enabled");
return;
}
onReceiveJson(payload, length);
#endif
return;
}
onReceiveProto(topic, payload, length);
}
void mqttInit()
@ -249,10 +324,9 @@ MQTT::MQTT() : concurrency::OSThread("mqtt"), mqttQueue(MAX_MQTT_QUEUE)
moduleConfig.mqtt.map_report_settings.publish_interval_secs, default_map_publish_interval_secs);
}
isMqttServerAddressPrivate = isPrivateIpAddress(moduleConfig.mqtt.address);
if (isMqttServerAddressPrivate) {
LOG_INFO("MQTT server on a private IP");
}
IPAddress ip;
isMqttServerAddressPrivate =
ip.fromString(parseHostAndPort(moduleConfig.mqtt.address).first.c_str()) && isPrivateIpAddress(ip);
#if HAS_NETWORKING
if (!moduleConfig.mqtt.proxy_to_client_enabled)
@ -368,14 +442,9 @@ void MQTT::reconnect()
pubSub.setClient(mqttClient);
#endif
String server = String(serverAddr);
int delimIndex = server.indexOf(':');
if (delimIndex > 0) {
String port = server.substring(delimIndex + 1, server.length());
server[delimIndex] = 0;
serverPort = port.toInt();
serverAddr = server.c_str();
}
std::pair<String, uint16_t> hostAndPort = parseHostAndPort(serverAddr, serverPort);
serverAddr = hostAndPort.first.c_str();
serverPort = hostAndPort.second;
pubSub.setServer(serverAddr, serverPort);
pubSub.setBufferSize(512);
@ -504,39 +573,37 @@ void MQTT::publishNodeInfo()
}
void MQTT::publishQueuedMessages()
{
if (!mqttQueue.isEmpty()) {
LOG_DEBUG("Publish enqueued MQTT message");
meshtastic_ServiceEnvelope *env = mqttQueue.dequeuePtr(0);
size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, env);
std::string topic;
if (env->packet->pki_encrypted) {
topic = cryptTopic + "PKI/" + owner.id;
} else {
topic = cryptTopic + env->channel_id + "/" + owner.id;
}
LOG_INFO("publish %s, %u bytes from queue", topic.c_str(), numBytes);
if (mqttQueue.isEmpty())
return;
publish(topic.c_str(), bytes, numBytes, false);
LOG_DEBUG("Publish enqueued MQTT message");
const std::unique_ptr<QueueEntry> entry(mqttQueue.dequeuePtr(0));
LOG_INFO("publish %s, %u bytes from queue", entry->topic.c_str(), entry->envBytes.size());
publish(entry->topic.c_str(), entry->envBytes.data(), entry->envBytes.size(), false);
#if !defined(ARCH_NRF52) || \
defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ###
if (moduleConfig.mqtt.json_enabled) {
// handle json topic
auto jsonString = MeshPacketSerializer::JsonSerialize(env->packet);
if (jsonString.length() != 0) {
std::string topicJson;
if (env->packet->pki_encrypted) {
topicJson = jsonTopic + "PKI/" + owner.id;
} else {
topicJson = jsonTopic + env->channel_id + "/" + owner.id;
}
LOG_INFO("JSON publish message to %s, %u bytes: %s", topicJson.c_str(), jsonString.length(), jsonString.c_str());
publish(topicJson.c_str(), jsonString.c_str(), false);
}
}
#endif // ARCH_NRF52 NRF52_USE_JSON
mqttPool.release(env);
if (!moduleConfig.mqtt.json_enabled)
return;
// handle json topic
const DecodedServiceEnvelope env(entry->envBytes.data(), entry->envBytes.size());
if (!env.validDecode || env.packet == NULL || env.channel_id == NULL)
return;
auto jsonString = MeshPacketSerializer::JsonSerialize(env.packet);
if (jsonString.length() == 0)
return;
std::string topicJson;
if (env.packet->pki_encrypted) {
topicJson = jsonTopic + "PKI/" + owner.id;
} else {
topicJson = jsonTopic + env.channel_id + "/" + owner.id;
}
LOG_INFO("JSON publish message to %s, %u bytes: %s", topicJson.c_str(), jsonString.length(), jsonString.c_str());
publish(topicJson.c_str(), jsonString.c_str(), false);
#endif // ARCH_NRF52 NRF52_USE_JSON
}
void MQTT::onSend(const meshtastic_MeshPacket &mp_encrypted, const meshtastic_MeshPacket &mp_decoded, ChannelIndex chIndex)
@ -575,59 +642,56 @@ void MQTT::onSend(const meshtastic_MeshPacket &mp_encrypted, const meshtastic_Me
// Either encrypted packet (we couldn't decrypt) is marked as pki_encrypted, or we could decode the PKI encrypted packet
bool isPKIEncrypted = mp_encrypted.pki_encrypted || mp_decoded.pki_encrypted;
// If it was to a channel, check uplink enabled, else must be pki_encrypted
if ((ch.settings.uplink_enabled && !isPKIEncrypted) || isPKIEncrypted) {
const char *channelId = isPKIEncrypted ? "PKI" : channels.getGlobalId(chIndex);
if (!(ch.settings.uplink_enabled || isPKIEncrypted))
return;
const char *channelId = isPKIEncrypted ? "PKI" : channels.getGlobalId(chIndex);
meshtastic_ServiceEnvelope *env = mqttPool.allocZeroed();
env->channel_id = (char *)channelId;
env->gateway_id = owner.id;
LOG_DEBUG("MQTT onSend - Publish ");
const meshtastic_MeshPacket *p;
if (moduleConfig.mqtt.encryption_enabled) {
p = &mp_encrypted;
LOG_DEBUG("encrypted message");
} else if (mp_decoded.which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
p = &mp_decoded;
LOG_DEBUG("portnum %i message", mp_decoded.decoded.portnum);
} else {
LOG_DEBUG("nothing, pkt not decrypted");
return; // Don't upload a still-encrypted PKI packet if not encryption_enabled
}
LOG_DEBUG("MQTT onSend - Publish ");
if (moduleConfig.mqtt.encryption_enabled) {
env->packet = (meshtastic_MeshPacket *)&mp_encrypted;
LOG_DEBUG("encrypted message");
} else if (mp_decoded.which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
env->packet = (meshtastic_MeshPacket *)&mp_decoded;
LOG_DEBUG("portnum %i message", env->packet->decoded.portnum);
} else {
LOG_DEBUG("nothing, pkt not decrypted");
mqttPool.release(env);
return; // Don't upload a still-encrypted PKI packet if not encryption_enabled
}
const meshtastic_ServiceEnvelope env = {
.packet = const_cast<meshtastic_MeshPacket *>(p), .channel_id = const_cast<char *>(channelId), .gateway_id = owner.id};
size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, &env);
std::string topic = cryptTopic + channelId + "/" + owner.id;
if (moduleConfig.mqtt.proxy_to_client_enabled || this->isConnectedDirectly()) {
size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, env);
std::string topic = cryptTopic + channelId + "/" + owner.id;
LOG_DEBUG("MQTT Publish %s, %u bytes", topic.c_str(), numBytes);
publish(topic.c_str(), bytes, numBytes, false);
if (moduleConfig.mqtt.proxy_to_client_enabled || this->isConnectedDirectly()) {
LOG_DEBUG("MQTT Publish %s, %u bytes", topic.c_str(), numBytes);
publish(topic.c_str(), bytes, numBytes, false);
#if !defined(ARCH_NRF52) || \
defined(NRF52_USE_JSON) // JSON is not supported on nRF52, see issue #2804 ### Fixed by using ArduinoJson ###
if (moduleConfig.mqtt.json_enabled) {
// handle json topic
auto jsonString = MeshPacketSerializer::JsonSerialize((meshtastic_MeshPacket *)&mp_decoded);
if (jsonString.length() != 0) {
std::string topicJson = jsonTopic + channelId + "/" + owner.id;
LOG_INFO("JSON publish message to %s, %u bytes: %s", topicJson.c_str(), jsonString.length(),
jsonString.c_str());
publish(topicJson.c_str(), jsonString.c_str(), false);
}
}
if (!moduleConfig.mqtt.json_enabled)
return;
// handle json topic
auto jsonString = MeshPacketSerializer::JsonSerialize(&mp_decoded);
if (jsonString.length() == 0)
return;
std::string topicJson = jsonTopic + channelId + "/" + owner.id;
LOG_INFO("JSON publish message to %s, %u bytes: %s", topicJson.c_str(), jsonString.length(), jsonString.c_str());
publish(topicJson.c_str(), jsonString.c_str(), false);
#endif // ARCH_NRF52 NRF52_USE_JSON
} else {
LOG_INFO("MQTT not connected, queue packet");
QueueEntry *entry;
if (mqttQueue.numFree() == 0) {
LOG_WARN("MQTT queue is full, discard oldest");
entry = mqttQueue.dequeuePtr(0);
} else {
LOG_INFO("MQTT not connected, queue packet");
if (mqttQueue.numFree() == 0) {
LOG_WARN("MQTT queue is full, discard oldest");
meshtastic_ServiceEnvelope *d = mqttQueue.dequeuePtr(0);
if (d)
mqttPool.release(d);
}
// make a copy of serviceEnvelope and queue it
meshtastic_ServiceEnvelope *copied = mqttPool.allocCopy(*env);
assert(mqttQueue.enqueue(copied, 0));
entry = new QueueEntry;
}
mqttPool.release(env);
entry->topic = std::move(topic);
entry->envBytes.assign(bytes, numBytes);
assert(mqttQueue.enqueue(entry, 0));
}
}
@ -636,148 +700,68 @@ void MQTT::perhapsReportToMap()
if (!moduleConfig.mqtt.map_reporting_enabled || !(moduleConfig.mqtt.proxy_to_client_enabled || isConnectedDirectly()))
return;
if (Throttle::isWithinTimespanMs(last_report_to_map, map_publish_interval_msecs)) {
if (Throttle::isWithinTimespanMs(last_report_to_map, map_publish_interval_msecs))
return;
} else {
if (map_position_precision == 0 || (localPosition.latitude_i == 0 && localPosition.longitude_i == 0)) {
last_report_to_map = millis();
if (map_position_precision == 0)
LOG_WARN("MQTT Map report enabled, but precision is 0");
if (localPosition.latitude_i == 0 && localPosition.longitude_i == 0)
LOG_WARN("MQTT Map report enabled, but no position available");
return;
}
// Allocate ServiceEnvelope and fill it
meshtastic_ServiceEnvelope *se = mqttPool.allocZeroed();
se->channel_id = (char *)channels.getGlobalId(channels.getPrimaryIndex()); // Use primary channel as the channel_id
se->gateway_id = owner.id;
// Allocate MeshPacket and fill it
meshtastic_MeshPacket *mp = packetPool.allocZeroed();
mp->which_payload_variant = meshtastic_MeshPacket_decoded_tag;
mp->from = nodeDB->getNodeNum();
mp->to = NODENUM_BROADCAST;
mp->decoded.portnum = meshtastic_PortNum_MAP_REPORT_APP;
// Fill MapReport message
meshtastic_MapReport mapReport = meshtastic_MapReport_init_default;
memcpy(mapReport.long_name, owner.long_name, sizeof(owner.long_name));
memcpy(mapReport.short_name, owner.short_name, sizeof(owner.short_name));
mapReport.role = config.device.role;
mapReport.hw_model = owner.hw_model;
strncpy(mapReport.firmware_version, optstr(APP_VERSION), sizeof(mapReport.firmware_version));
mapReport.region = config.lora.region;
mapReport.modem_preset = config.lora.modem_preset;
mapReport.has_default_channel = channels.hasDefaultChannel();
// Set position with precision (same as in PositionModule)
if (map_position_precision < 32 && map_position_precision > 0) {
mapReport.latitude_i = localPosition.latitude_i & (UINT32_MAX << (32 - map_position_precision));
mapReport.longitude_i = localPosition.longitude_i & (UINT32_MAX << (32 - map_position_precision));
mapReport.latitude_i += (1 << (31 - map_position_precision));
mapReport.longitude_i += (1 << (31 - map_position_precision));
} else {
mapReport.latitude_i = localPosition.latitude_i;
mapReport.longitude_i = localPosition.longitude_i;
}
mapReport.altitude = localPosition.altitude;
mapReport.position_precision = map_position_precision;
mapReport.num_online_local_nodes = nodeDB->getNumOnlineMeshNodes(true);
// Encode MapReport message and set it to MeshPacket in ServiceEnvelope
mp->decoded.payload.size = pb_encode_to_bytes(mp->decoded.payload.bytes, sizeof(mp->decoded.payload.bytes),
&meshtastic_MapReport_msg, &mapReport);
se->packet = mp;
size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, se);
LOG_INFO("MQTT Publish map report to %s", mapTopic.c_str());
publish(mapTopic.c_str(), bytes, numBytes, false);
// Release the allocated memory for ServiceEnvelope and MeshPacket
mqttPool.release(se);
packetPool.release(mp);
// Update the last report time
if (map_position_precision == 0 || (localPosition.latitude_i == 0 && localPosition.longitude_i == 0)) {
last_report_to_map = millis();
}
}
bool MQTT::isValidJsonEnvelope(JSONObject &json)
{
// if "sender" is provided, avoid processing packets we uplinked
return (json.find("sender") != json.end() ? (json["sender"]->AsString().compare(owner.id) != 0) : true) &&
(json.find("hopLimit") != json.end() ? json["hopLimit"]->IsNumber() : true) && // hop limit should be a number
(json.find("from") != json.end()) && json["from"]->IsNumber() &&
(json["from"]->AsNumber() == nodeDB->getNodeNum()) && // only accept message if the "from" is us
(json.find("type") != json.end()) && json["type"]->IsString() && // should specify a type
(json.find("payload") != json.end()); // should have a payload
}
bool MQTT::isPrivateIpAddress(const char address[])
{
// Min. length like 10.0.0.0 (8), max like 192.168.255.255:65535 (21)
size_t length = strlen(address);
if (length < 8 || length > 21) {
return false;
if (map_position_precision == 0)
LOG_WARN("MQTT Map report enabled, but precision is 0");
if (localPosition.latitude_i == 0 && localPosition.longitude_i == 0)
LOG_WARN("MQTT Map report enabled, but no position available");
return;
}
// Ensure the address contains only digits and dots and maybe a colon.
// Some limited validation is done.
// Even if it's not a valid IP address, we will know it's not a domain.
bool hasColon = false;
int numDots = 0;
for (size_t i = 0; i < length; i++) {
if (!isdigit(address[i]) && address[i] != '.' && address[i] != ':') {
return false;
}
// Allocate MeshPacket and fill it
meshtastic_MeshPacket *mp = packetPool.allocZeroed();
mp->which_payload_variant = meshtastic_MeshPacket_decoded_tag;
mp->from = nodeDB->getNodeNum();
mp->to = NODENUM_BROADCAST;
mp->decoded.portnum = meshtastic_PortNum_MAP_REPORT_APP;
// Dots can't be the first character, immediately follow another dot,
// occur more than 3 times, or occur after a colon.
if (address[i] == '.') {
if (++numDots > 3 || i == 0 || address[i - 1] == '.' || hasColon) {
return false;
}
}
// There can only be a single colon, and it can only occur after 3 dots
else if (address[i] == ':') {
if (hasColon || numDots < 3) {
return false;
}
// Fill MapReport message
meshtastic_MapReport mapReport = meshtastic_MapReport_init_default;
memcpy(mapReport.long_name, owner.long_name, sizeof(owner.long_name));
memcpy(mapReport.short_name, owner.short_name, sizeof(owner.short_name));
mapReport.role = config.device.role;
mapReport.hw_model = owner.hw_model;
strncpy(mapReport.firmware_version, optstr(APP_VERSION), sizeof(mapReport.firmware_version));
mapReport.region = config.lora.region;
mapReport.modem_preset = config.lora.modem_preset;
mapReport.has_default_channel = channels.hasDefaultChannel();
hasColon = true;
}
// Set position with precision (same as in PositionModule)
if (map_position_precision < 32 && map_position_precision > 0) {
mapReport.latitude_i = localPosition.latitude_i & (UINT32_MAX << (32 - map_position_precision));
mapReport.longitude_i = localPosition.longitude_i & (UINT32_MAX << (32 - map_position_precision));
mapReport.latitude_i += (1 << (31 - map_position_precision));
mapReport.longitude_i += (1 << (31 - map_position_precision));
} else {
mapReport.latitude_i = localPosition.latitude_i;
mapReport.longitude_i = localPosition.longitude_i;
}
mapReport.altitude = localPosition.altitude;
mapReport.position_precision = map_position_precision;
// Final validation for IPv4 address and port format.
// Note that the values of octets haven't been tested, only the address format.
if (numDots != 3) {
return false;
}
mapReport.num_online_local_nodes = nodeDB->getNumOnlineMeshNodes(true);
// Check the easy ones first.
if (strcmp(address, "127.0.0.1") == 0 || strncmp(address, "10.", 3) == 0 || strncmp(address, "192.168", 7) == 0 ||
strncmp(address, "169.254", 7) == 0) {
return true;
}
// Encode MapReport message into the MeshPacket
mp->decoded.payload.size =
pb_encode_to_bytes(mp->decoded.payload.bytes, sizeof(mp->decoded.payload.bytes), &meshtastic_MapReport_msg, &mapReport);
// See if it's definitely not a 172 address.
if (strncmp(address, "172", 3) != 0) {
return false;
}
// Encode the MeshPacket into a binary ServiceEnvelope and publish
const meshtastic_ServiceEnvelope se = {
.packet = mp,
.channel_id = (char *)channels.getGlobalId(channels.getPrimaryIndex()), // Use primary channel as the channel_id
.gateway_id = owner.id};
size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, &se);
// We know it's a 172 address, now see if the second octet is 2 digits.
if (address[6] != '.') {
return false;
}
LOG_INFO("MQTT Publish map report to %s", mapTopic.c_str());
publish(mapTopic.c_str(), bytes, numBytes, false);
// Copy the second octet into a secondary buffer we can null-terminate and parse.
char octet2[3];
strncpy(octet2, address + 4, 2);
octet2[2] = 0;
// Release the allocated memory for MeshPacket
packetPool.release(mp);
int octet2Num = atoi(octet2);
return octet2Num >= 16 && octet2Num <= 31;
}
// Update the last report time
last_report_to_map = millis();
}

View File

@ -5,7 +5,9 @@
#include "concurrency/OSThread.h"
#include "mesh/Channels.h"
#include "mesh/generated/meshtastic/mqtt.pb.h"
#if !defined(ARCH_NRF52) || NRF52_USE_JSON
#include "serialization/JSON.h"
#endif
#if HAS_WIFI
#include <WiFiClient.h>
#if !defined(ARCH_PORTDUINO)
@ -78,7 +80,11 @@ class MQTT : private concurrency::OSThread
void start() { setIntervalFromNow(0); };
protected:
PointerQueue<meshtastic_ServiceEnvelope> mqttQueue;
struct QueueEntry {
std::string topic;
std::basic_string<uint8_t> envBytes; // binary/pb_encode_to_bytes ServiceEnvelope
};
PointerQueue<QueueEntry> mqttQueue;
int reconnectCount = 0;
@ -117,17 +123,10 @@ class MQTT : private concurrency::OSThread
// Check if we should report unencrypted information about our node for consumption by a map
void perhapsReportToMap();
// returns true if this is a valid JSON envelope which we accept on downlink
bool isValidJsonEnvelope(JSONObject &json);
/// Determines if the given address is a private IPv4 address, i.e. not routable on the public internet.
/// These are the ranges: 127.0.0.1, 10.0.0.0-10.255.255.255, 172.16.0.0-172.31.255.255, 192.168.0.0-192.168.255.255.
bool isPrivateIpAddress(const char address[]);
/// Return 0 if sleep is okay, veto sleep if we are connected to pubsub server
// int preflightSleepCb(void *unused = NULL) { return pubSub.connected() ? 1 : 0; }
};
void mqttInit();
extern MQTT *mqtt;
extern MQTT *mqtt;

View File

@ -21,9 +21,12 @@
#include <sys/ioctl.h>
#include <unistd.h>
#include "platform/portduino/USBHal.h"
std::map<configNames, int> settingsMap;
std::map<configNames, std::string> settingsStrings;
std::ofstream traceFile;
Ch341Hal *ch341Hal = nullptr;
char *configPath = nullptr;
char *optionMac = nullptr;
@ -87,7 +90,8 @@ void getMacAddr(uint8_t *dmac)
if (strlen(optionMac) >= 12) {
MAC_from_string(optionMac, dmac);
} else {
uint32_t hwId = sscanf(optionMac, "%u", &hwId);
uint32_t hwId;
sscanf(optionMac, "%u", &hwId);
dmac[0] = 0x80;
dmac[1] = 0;
dmac[2] = hwId >> 24;
@ -103,7 +107,6 @@ void getMacAddr(uint8_t *dmac)
struct hci_dev_info di;
di.dev_id = 0;
bdaddr_t bdaddr;
char addr[18];
int btsock;
btsock = socket(AF_BLUETOOTH, SOCK_RAW, 1);
if (btsock < 0) { // If anything fails, just return with the default value
@ -201,8 +204,36 @@ void portduinoSetup()
}
}
}
// if we're using a usermode driver, we need to initialize it here, to get a serial number back for mac address
uint8_t dmac[6] = {0};
if (settingsStrings[spidev] == "ch341") {
ch341Hal = new Ch341Hal(0);
if (settingsStrings[lora_usb_serial_num] != "") {
ch341Hal->serial = settingsStrings[lora_usb_serial_num];
}
ch341Hal->vid = settingsMap[lora_usb_vid];
ch341Hal->pid = settingsMap[lora_usb_pid];
ch341Hal->init();
if (!ch341Hal->isInit()) {
std::cout << "Could not initialize CH341 device!" << std::endl;
exit(EXIT_FAILURE);
}
char serial[9] = {0};
ch341Hal->getSerialString(serial, 8);
std::cout << "Serial " << serial << std::endl;
if (strlen(serial) == 8 && settingsStrings[mac_address].length() < 12) {
uint8_t hash[32] = {0};
memcpy(hash, serial, 8);
crypto->hash(hash, 8);
dmac[0] = (hash[0] << 4) | 2;
dmac[1] = hash[1];
dmac[2] = hash[2];
dmac[3] = hash[3];
dmac[4] = hash[4];
dmac[5] = hash[5];
}
}
getMacAddr(dmac);
if (dmac[0] == 0 && dmac[1] == 0 && dmac[2] == 0 && dmac[3] == 0 && dmac[4] == 0 && dmac[5] == 0) {
std::cout << "*** Blank MAC Address not allowed!" << std::endl;
@ -225,47 +256,11 @@ void portduinoSetup()
// Need to bind all the configured GPIO pins so they're not simulated
// TODO: Can we do this in the for loop above?
// TODO: If one of these fails, we should log and terminate
if (settingsMap.count(cs) > 0 && settingsMap[cs] != RADIOLIB_NC) {
if (initGPIOPin(settingsMap[cs], gpioChipName) != ERRNO_OK) {
settingsMap[cs] = RADIOLIB_NC;
}
}
if (settingsMap.count(irq) > 0 && settingsMap[irq] != RADIOLIB_NC) {
if (initGPIOPin(settingsMap[irq], gpioChipName) != ERRNO_OK) {
settingsMap[irq] = RADIOLIB_NC;
}
}
if (settingsMap.count(busy) > 0 && settingsMap[busy] != RADIOLIB_NC) {
if (initGPIOPin(settingsMap[busy], gpioChipName) != ERRNO_OK) {
settingsMap[busy] = RADIOLIB_NC;
}
}
if (settingsMap.count(reset) > 0 && settingsMap[reset] != RADIOLIB_NC) {
if (initGPIOPin(settingsMap[reset], gpioChipName) != ERRNO_OK) {
settingsMap[reset] = RADIOLIB_NC;
}
}
if (settingsMap.count(sx126x_ant_sw) > 0 && settingsMap[sx126x_ant_sw] != RADIOLIB_NC) {
if (initGPIOPin(settingsMap[sx126x_ant_sw], gpioChipName) != ERRNO_OK) {
settingsMap[sx126x_ant_sw] = RADIOLIB_NC;
}
}
if (settingsMap.count(user) > 0 && settingsMap[user] != RADIOLIB_NC) {
if (initGPIOPin(settingsMap[user], gpioChipName) != ERRNO_OK) {
settingsMap[user] = RADIOLIB_NC;
}
}
if (settingsMap.count(rxen) > 0 && settingsMap[rxen] != RADIOLIB_NC) {
if (initGPIOPin(settingsMap[rxen], gpioChipName) != ERRNO_OK) {
settingsMap[rxen] = RADIOLIB_NC;
}
}
if (settingsMap.count(txen) > 0 && settingsMap[txen] != RADIOLIB_NC) {
if (initGPIOPin(settingsMap[txen], gpioChipName) != ERRNO_OK) {
settingsMap[txen] = RADIOLIB_NC;
}
}
if (settingsMap[displayPanel] != no_screen) {
if (settingsMap[displayCS] > 0)
initGPIOPin(settingsMap[displayCS], gpioChipName);
@ -283,7 +278,43 @@ void portduinoSetup()
initGPIOPin(settingsMap[touchscreenIRQ], gpioChipName);
}
if (settingsStrings[spidev] != "") {
// Only initialize the radio pins when dealing with real, kernel controlled SPI hardware
if (settingsStrings[spidev] != "" && settingsStrings[spidev] != "ch341") {
if (settingsMap.count(cs) > 0 && settingsMap[cs] != RADIOLIB_NC) {
if (initGPIOPin(settingsMap[cs], gpioChipName) != ERRNO_OK) {
settingsMap[cs] = RADIOLIB_NC;
}
}
if (settingsMap.count(irq) > 0 && settingsMap[irq] != RADIOLIB_NC) {
if (initGPIOPin(settingsMap[irq], gpioChipName) != ERRNO_OK) {
settingsMap[irq] = RADIOLIB_NC;
}
}
if (settingsMap.count(busy) > 0 && settingsMap[busy] != RADIOLIB_NC) {
if (initGPIOPin(settingsMap[busy], gpioChipName) != ERRNO_OK) {
settingsMap[busy] = RADIOLIB_NC;
}
}
if (settingsMap.count(reset) > 0 && settingsMap[reset] != RADIOLIB_NC) {
if (initGPIOPin(settingsMap[reset], gpioChipName) != ERRNO_OK) {
settingsMap[reset] = RADIOLIB_NC;
}
}
if (settingsMap.count(sx126x_ant_sw) > 0 && settingsMap[sx126x_ant_sw] != RADIOLIB_NC) {
if (initGPIOPin(settingsMap[sx126x_ant_sw], gpioChipName) != ERRNO_OK) {
settingsMap[sx126x_ant_sw] = RADIOLIB_NC;
}
}
if (settingsMap.count(rxen) > 0 && settingsMap[rxen] != RADIOLIB_NC) {
if (initGPIOPin(settingsMap[rxen], gpioChipName) != ERRNO_OK) {
settingsMap[rxen] = RADIOLIB_NC;
}
}
if (settingsMap.count(txen) > 0 && settingsMap[txen] != RADIOLIB_NC) {
if (initGPIOPin(settingsMap[txen], gpioChipName) != ERRNO_OK) {
settingsMap[txen] = RADIOLIB_NC;
}
}
SPI.begin(settingsStrings[spidev].c_str());
}
if (settingsStrings[traceFilename] != "") {
@ -366,7 +397,10 @@ bool loadConfig(const char *configPath)
settingsMap[use_sx1268] = true;
}
settingsMap[dio2_as_rf_switch] = yamlConfig["Lora"]["DIO2_AS_RF_SWITCH"].as<bool>(false);
settingsMap[dio3_tcxo_voltage] = yamlConfig["Lora"]["DIO3_TCXO_VOLTAGE"].as<bool>(false);
settingsMap[dio3_tcxo_voltage] = yamlConfig["Lora"]["DIO3_TCXO_VOLTAGE"].as<float>(0) * 1000;
if (settingsMap[dio3_tcxo_voltage] == 0 && yamlConfig["Lora"]["DIO3_TCXO_VOLTAGE"].as<bool>(false)) {
settingsMap[dio3_tcxo_voltage] = 1800; // default millivolts for "true"
}
settingsMap[cs] = yamlConfig["Lora"]["CS"].as<int>(RADIOLIB_NC);
settingsMap[irq] = yamlConfig["Lora"]["IRQ"].as<int>(RADIOLIB_NC);
settingsMap[busy] = yamlConfig["Lora"]["Busy"].as<int>(RADIOLIB_NC);
@ -375,17 +409,24 @@ bool loadConfig(const char *configPath)
settingsMap[rxen] = yamlConfig["Lora"]["RXen"].as<int>(RADIOLIB_NC);
settingsMap[sx126x_ant_sw] = yamlConfig["Lora"]["SX126X_ANT_SW"].as<int>(RADIOLIB_NC);
settingsMap[gpiochip] = yamlConfig["Lora"]["gpiochip"].as<int>(0);
settingsMap[ch341Quirk] = yamlConfig["Lora"]["ch341_quirk"].as<bool>(false);
settingsMap[spiSpeed] = yamlConfig["Lora"]["spiSpeed"].as<int>(2000000);
settingsStrings[lora_usb_serial_num] = yamlConfig["Lora"]["USB_Serialnum"].as<std::string>("");
settingsMap[lora_usb_pid] = yamlConfig["Lora"]["USB_PID"].as<int>(0x5512);
settingsMap[lora_usb_vid] = yamlConfig["Lora"]["USB_VID"].as<int>(0x1A86);
settingsStrings[spidev] = "/dev/" + yamlConfig["Lora"]["spidev"].as<std::string>("spidev0.0");
if (settingsStrings[spidev].length() == 14) {
int x = settingsStrings[spidev].at(11) - '0';
int y = settingsStrings[spidev].at(13) - '0';
if (x >= 0 && x < 10 && y >= 0 && y < 10) {
settingsMap[spidev] = x + y << 4;
settingsMap[displayspidev] = settingsMap[spidev];
settingsMap[touchscreenspidev] = settingsMap[spidev];
settingsStrings[spidev] = yamlConfig["Lora"]["spidev"].as<std::string>("spidev0.0");
if (settingsStrings[spidev] != "ch341") {
settingsStrings[spidev] = "/dev/" + settingsStrings[spidev];
if (settingsStrings[spidev].length() == 14) {
int x = settingsStrings[spidev].at(11) - '0';
int y = settingsStrings[spidev].at(13) - '0';
// Pretty sure this is always true
if (x >= 0 && x < 10 && y >= 0 && y < 10) {
// I believe this bit of weirdness is specifically for the new GUI
settingsMap[spidev] = x + y << 4;
settingsMap[displayspidev] = settingsMap[spidev];
settingsMap[touchscreenspidev] = settingsMap[spidev];
}
}
}
}
@ -533,4 +574,4 @@ bool MAC_from_string(std::string mac_str, uint8_t *dmac)
} else {
return false;
}
}
}

View File

@ -2,6 +2,8 @@
#include <fstream>
#include <map>
#include "platform/portduino/USBHal.h"
enum configNames {
use_sx1262,
cs,
@ -13,13 +15,15 @@ enum configNames {
rxen,
dio2_as_rf_switch,
dio3_tcxo_voltage,
ch341Quirk,
use_rf95,
use_sx1280,
use_lr1110,
use_lr1120,
use_lr1121,
use_sx1268,
lora_usb_serial_num,
lora_usb_pid,
lora_usb_vid,
user,
gpiochip,
spidev,
@ -70,8 +74,9 @@ enum { level_error, level_warn, level_info, level_debug, level_trace };
extern std::map<configNames, int> settingsMap;
extern std::map<configNames, std::string> settingsStrings;
extern std::ofstream traceFile;
extern Ch341Hal *ch341Hal;
int initGPIOPin(int pinNum, std::string gpioChipname);
bool loadConfig(const char *configPath);
static bool ends_with(std::string_view str, std::string_view suffix);
void getMacAddr(uint8_t *dmac);
bool MAC_from_string(std::string mac_str, uint8_t *dmac);
bool MAC_from_string(std::string mac_str, uint8_t *dmac);

View File

@ -0,0 +1,194 @@
#ifndef PI_HAL_LGPIO_H
#define PI_HAL_LGPIO_H
// include RadioLib
#include "platform/portduino/PortduinoGlue.h"
#include <RadioLib.h>
#include <csignal>
#include <libpinedio-usb.h>
#include <unistd.h>
// include the library for Raspberry GPIO pins
#define PI_RISING (PINEDIO_INT_MODE_RISING)
#define PI_FALLING (PINEDIO_INT_MODE_FALLING)
#define PI_INPUT (0)
#define PI_OUTPUT (1)
#define PI_LOW (0)
#define PI_HIGH (1)
#define CH341_PIN_CS (101)
#define CH341_PIN_IRQ (0)
// the HAL must inherit from the base RadioLibHal class
// and implement all of its virtual methods
class Ch341Hal : public RadioLibHal
{
public:
// default constructor - initializes the base HAL and any needed private members
Ch341Hal(uint8_t spiChannel, uint32_t spiSpeed = 2000000, uint8_t spiDevice = 0, uint8_t gpioDevice = 0)
: RadioLibHal(PI_INPUT, PI_OUTPUT, PI_LOW, PI_HIGH, PI_RISING, PI_FALLING)
{
}
void getSerialString(char *_serial, size_t len)
{
if (!pinedio_is_init) {
return;
}
strncpy(_serial, pinedio.serial_number, len);
}
void init() override
{
// now the SPI
spiBegin();
}
void term() override
{
// stop the SPI
spiEnd();
}
// GPIO-related methods (pinMode, digitalWrite etc.) should check
// RADIOLIB_NC as an alias for non-connected pins
void pinMode(uint32_t pin, uint32_t mode) override
{
if (pin == RADIOLIB_NC) {
return;
}
pinedio_set_pin_mode(&pinedio, pin, mode);
}
void digitalWrite(uint32_t pin, uint32_t value) override
{
if (pin == RADIOLIB_NC) {
return;
}
pinedio_digital_write(&pinedio, pin, value);
}
uint32_t digitalRead(uint32_t pin) override
{
if (pin == RADIOLIB_NC) {
return 0;
}
return pinedio_digital_read(&pinedio, pin);
}
void attachInterrupt(uint32_t interruptNum, void (*interruptCb)(void), uint32_t mode) override
{
if ((interruptNum == RADIOLIB_NC)) {
return;
}
// LOG_DEBUG("Attach interrupt to pin %d", interruptNum);
pinedio_attach_interrupt(&this->pinedio, (pinedio_int_pin)interruptNum, (pinedio_int_mode)mode, interruptCb);
}
void detachInterrupt(uint32_t interruptNum) override
{
if ((interruptNum == RADIOLIB_NC)) {
return;
}
// LOG_DEBUG("Detach interrupt from pin %d", interruptNum);
pinedio_deattach_interrupt(&this->pinedio, (pinedio_int_pin)interruptNum);
}
void delay(unsigned long ms) override
{
if (ms == 0) {
sched_yield();
return;
}
usleep(ms * 1000);
}
void delayMicroseconds(unsigned long us) override
{
if (us == 0) {
sched_yield();
return;
}
usleep(us);
}
void yield() override { sched_yield(); }
unsigned long millis() override
{
struct timeval tv;
gettimeofday(&tv, NULL);
return (tv.tv_sec * 1000ULL) + (tv.tv_usec / 1000ULL);
}
unsigned long micros() override
{
struct timeval tv;
gettimeofday(&tv, NULL);
return (tv.tv_sec * 1000000ULL) + tv.tv_usec;
}
long pulseIn(uint32_t pin, uint32_t state, unsigned long timeout) override
{
fprintf(stderr, "pulseIn for pin %u is not supported!\n", pin);
return 0;
}
void spiBegin()
{
if (!pinedio_is_init) {
if (serial != "") {
strncpy(pinedio.serial_number, serial.c_str(), 8);
pinedio_set_option(&pinedio, PINEDIO_OPTION_SEARCH_SERIAL, 1);
}
pinedio_set_option(&pinedio, PINEDIO_OPTION_PID, pid);
pinedio_set_option(&pinedio, PINEDIO_OPTION_VID, vid);
int32_t ret = pinedio_init(&pinedio, NULL);
if (ret != 0) {
fprintf(stderr, "Could not open SPI: %d\n", ret);
} else {
pinedio_is_init = true;
// LOG_INFO("USB Serial: %s", pinedio.serial_number);
pinedio_set_option(&pinedio, PINEDIO_OPTION_AUTO_CS, 0);
pinedio_set_pin_mode(&pinedio, 3, true);
pinedio_set_pin_mode(&pinedio, 5, true);
}
}
}
void spiBeginTransaction() {}
void spiTransfer(uint8_t *out, size_t len, uint8_t *in)
{
int32_t result = pinedio_transceive(&this->pinedio, out, in, len);
if (result < 0) {
fprintf(stderr, "Could not perform SPI transfer: %d\n", result);
}
}
void spiEndTransaction() {}
void spiEnd()
{
if (pinedio_is_init) {
pinedio_deinit(&pinedio);
pinedio_is_init = false;
}
}
bool isInit() { return pinedio_is_init; }
std::string serial = "";
uint32_t pid = 0x5512;
uint32_t vid = 0x1A86;
private:
// the HAL can contain any additional private members
pinedio_inst pinedio = {0};
bool pinedio_is_init = false;
};
#endif

Some files were not shown because too many files have changed in this diff Show More