mirror of
https://github.com/meshtastic/firmware.git
synced 2025-08-24 22:10:09 +00:00
Merge branch 'master' into NextHopRouter
This commit is contained in:
commit
5154e29b07
66
.github/workflows/build_debian_src.yml
vendored
Normal file
66
.github/workflows/build_debian_src.yml
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
name: Build Debian Source Package
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
secrets:
|
||||
PPA_GPG_PRIVATE_KEY:
|
||||
required: true
|
||||
inputs:
|
||||
series:
|
||||
description: Ubuntu series to target
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
build-debian-src:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
path: meshtasticd
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- name: Install deps
|
||||
shell: bash
|
||||
working-directory: meshtasticd
|
||||
run: |
|
||||
sudo apt-get update -y --fix-missing
|
||||
sudo apt-get install -y software-properties-common build-essential devscripts equivs
|
||||
sudo add-apt-repository ppa:meshtastic/build-tools -y
|
||||
sudo apt-get update -y --fix-missing
|
||||
sudo mk-build-deps --install --remove --tool='apt-get -o Debug::pkgProblemResolver=yes --no-install-recommends --yes' debian/control
|
||||
|
||||
- name: Import GPG key
|
||||
uses: crazy-max/ghaction-import-gpg@v6
|
||||
with:
|
||||
gpg_private_key: ${{ secrets.PPA_GPG_PRIVATE_KEY }}
|
||||
id: gpg
|
||||
|
||||
- name: Get release version string
|
||||
working-directory: meshtasticd
|
||||
run: |
|
||||
echo "deb=$(./bin/buildinfo.py deb)" >> $GITHUB_OUTPUT
|
||||
id: version
|
||||
|
||||
- name: Fetch libdeps, package debian source
|
||||
working-directory: meshtasticd
|
||||
run: debian/ci_pack_sdeb.sh
|
||||
env:
|
||||
SERIES: ${{ inputs.series }}
|
||||
GPG_KEY_ID: ${{ steps.gpg.outputs.keyid }}
|
||||
PKG_VERSION: ${{ steps.version.outputs.deb }}
|
||||
|
||||
- name: Store binaries as an artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
|
||||
overwrite: true
|
||||
path: |
|
||||
meshtasticd_${{ steps.version.outputs.deb }}*
|
5
.github/workflows/main_matrix.yml
vendored
5
.github/workflows/main_matrix.yml
vendored
@ -332,12 +332,17 @@ jobs:
|
||||
run: >-
|
||||
bin/bump_version.py
|
||||
|
||||
- name: Update debian changelog
|
||||
run: >-
|
||||
debian/ci_changelog.sh
|
||||
|
||||
- name: Create version.properties pull request
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
title: Bump version.properties
|
||||
add-paths: |
|
||||
version.properties
|
||||
debian/changelog
|
||||
|
||||
release-firmware:
|
||||
strategy:
|
||||
|
10
.github/workflows/nightly.yml
vendored
10
.github/workflows/nightly.yml
vendored
@ -17,3 +17,13 @@ jobs:
|
||||
uses: trunk-io/trunk-action@782e83f803ca6e369f035d64c6ba2768174ba61b
|
||||
with:
|
||||
trunk-token: ${{ secrets.TRUNK_TOKEN }}
|
||||
package-ppa:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
series: [plucky, oracular, noble, jammy]
|
||||
uses: ./.github/workflows/package_ppa.yml
|
||||
with:
|
||||
ppa_repo: daily
|
||||
series: ${{ matrix.series }}
|
||||
secrets: inherit
|
||||
|
72
.github/workflows/package_ppa.yml
vendored
Normal file
72
.github/workflows/package_ppa.yml
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
name: Package Launchpad PPA
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
secrets:
|
||||
PPA_GPG_PRIVATE_KEY:
|
||||
required: true
|
||||
inputs:
|
||||
ppa_repo:
|
||||
description: Meshtastic PPA to target
|
||||
required: true
|
||||
type: string
|
||||
series:
|
||||
description: Ubuntu series to target
|
||||
required: true
|
||||
type: string
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
build-debian-src:
|
||||
uses: ./.github/workflows/build_debian_src.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
series: ${{ inputs.series }}
|
||||
|
||||
package-ppa:
|
||||
runs-on: ubuntu-24.04
|
||||
needs: build-debian-src
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
path: meshtasticd
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- name: Install deps
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get update -y --fix-missing
|
||||
sudo apt-get install -y dput
|
||||
|
||||
- name: Import GPG key
|
||||
uses: crazy-max/ghaction-import-gpg@v6
|
||||
with:
|
||||
gpg_private_key: ${{ secrets.PPA_GPG_PRIVATE_KEY }}
|
||||
id: gpg
|
||||
|
||||
- name: Get release version string
|
||||
working-directory: meshtasticd
|
||||
run: |
|
||||
echo "deb=$(./bin/buildinfo.py deb)" >> $GITHUB_OUTPUT
|
||||
id: version
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
|
||||
merge-multiple: true
|
||||
|
||||
- name: Display structure of downloaded files
|
||||
run: ls -lah
|
||||
|
||||
- name: Publish with dput
|
||||
if: ${{ github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }}
|
||||
run: |
|
||||
dput ppa:meshtastic/${{ inputs.ppa_repo }} meshtasticd_${{ steps.version.outputs.deb }}~${{ inputs.series }}_source.changes
|
20
.github/workflows/release_channels.yml
vendored
Normal file
20
.github/workflows/release_channels.yml
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
name: Trigger release workflows upon Publish
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
package-ppa:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
series: [plucky, oracular, noble, jammy]
|
||||
uses: ./.github/workflows/package_ppa.yml
|
||||
with:
|
||||
ppa_repo: |-
|
||||
${{ contains(github.event.release.name, 'Beta') && 'beta' || contains(github.event.release.name, 'Alpha') && 'alpha' }}
|
||||
series: ${{ matrix.series }}
|
||||
secrets: inherit
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,4 +1,8 @@
|
||||
.pio
|
||||
pio
|
||||
pio.tar
|
||||
web
|
||||
web.tar
|
||||
|
||||
# ignore vscode IDE settings files
|
||||
.vscode/*
|
||||
@ -30,4 +34,4 @@ release/
|
||||
.vscode/extensions.json
|
||||
/compile_commands.json
|
||||
src/mesh/raspihttp/certificate.pem
|
||||
src/mesh/raspihttp/private_key.pem
|
||||
src/mesh/raspihttp/private_key.pem
|
@ -7,12 +7,12 @@ platform_packages = framework-arduinopico@https://github.com/earlephilhower/ardu
|
||||
board_build.core = earlephilhower
|
||||
board_build.filesystem_size = 0.5m
|
||||
build_flags =
|
||||
${arduino_base.build_flags} -Wno-unused-variable
|
||||
${arduino_base.build_flags} -Wno-unused-variable -Wcast-align
|
||||
-Isrc/platform/rp2xx0
|
||||
-D__PLAT_RP2040__
|
||||
-D__PLAT_RP2350__
|
||||
# -D _POSIX_THREADS
|
||||
build_src_filter =
|
||||
${arduino_base.build_src_filter} -<platform/esp32/> -<nimble/> -<modules/esp32> -<platform/nrf52/> -<platform/stm32wl> -<mesh/eth/> -<mesh/wifi/> -<mesh/http/> -<mesh/raspihttp>
|
||||
${arduino_base.build_src_filter} -<platform/esp32/> -<nimble/> -<modules/esp32> -<platform/nrf52/> -<platform/stm32wl> -<mesh/eth/> -<mesh/wifi/> -<mesh/http/> -<mesh/raspihttp> -<platform/rp2xx0/pico_sleep> -<platform/rp2xx0/hardware_rosc>
|
||||
|
||||
lib_ignore =
|
||||
BluetoothOTA
|
||||
|
1
bin/.gitignore
vendored
Normal file
1
bin/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
config.yaml
|
@ -12,12 +12,6 @@ Lora:
|
||||
# IRQ: 17
|
||||
# Reset: 22
|
||||
|
||||
# Module: RF95 # Adafruit RFM9x
|
||||
# Reset: 25
|
||||
# CS: 7
|
||||
# IRQ: 22
|
||||
# Busy: 23
|
||||
|
||||
# Module: RF95 # Elecrow Lora RFM95 IOT https://www.elecrow.com/lora-rfm95-iot-board-for-rpi.html
|
||||
# Reset: 22
|
||||
# CS: 7
|
||||
|
5
bin/config.d/lora-Adafruit-RFM9x
Normal file
5
bin/config.d/lora-Adafruit-RFM9x
Normal file
@ -0,0 +1,5 @@
|
||||
# Module: RF95 # Adafruit RFM9x
|
||||
# Reset: 25
|
||||
# CS: 7
|
||||
# IRQ: 22
|
||||
# Busy: 23
|
@ -1,6 +1,7 @@
|
||||
import configparser
|
||||
import subprocess
|
||||
|
||||
import os
|
||||
run_number = os.getenv('GITHUB_RUN_NUMBER', '0')
|
||||
|
||||
def readProps(prefsLoc):
|
||||
"""Read the version of our project as a string"""
|
||||
@ -10,6 +11,7 @@ def readProps(prefsLoc):
|
||||
version = dict(config.items("VERSION"))
|
||||
verObj = dict(
|
||||
short="{}.{}.{}".format(version["major"], version["minor"], version["build"]),
|
||||
deb="unset",
|
||||
long="unset",
|
||||
)
|
||||
|
||||
@ -27,13 +29,13 @@ def readProps(prefsLoc):
|
||||
# if isDirty:
|
||||
# # short for 'dirty', we want to keep our verstrings source for protobuf reasons
|
||||
# suffix = sha + "-d"
|
||||
verObj["long"] = "{}.{}.{}.{}".format(
|
||||
version["major"], version["minor"], version["build"], suffix
|
||||
)
|
||||
verObj["long"] = "{}.{}".format(verObj["short"], suffix)
|
||||
verObj["deb"] = "{}-{}~ppa{}".format(verObj["short"], run_number, sha)
|
||||
except:
|
||||
# print("Unexpected error:", sys.exc_info()[0])
|
||||
# traceback.print_exc()
|
||||
verObj["long"] = verObj["short"]
|
||||
verObj["deb"] = "{}-{}~ppa".format(verObj["short"], run_number)
|
||||
|
||||
# print("firmware version " + verStr)
|
||||
return verObj
|
||||
|
6
debian/.gitignore
vendored
Normal file
6
debian/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
.debhelper
|
||||
debhelper-build-stamp
|
||||
meshtasticd
|
||||
files
|
||||
meshtasticd.substvars
|
||||
meshtasticd.postrm.debhelper
|
5
debian/changelog
vendored
Normal file
5
debian/changelog
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
meshtasticd (2.5.19) UNRELEASED; urgency=medium
|
||||
|
||||
* Initial packaging
|
||||
|
||||
-- Austin Lane <vidplace7@gmail.com> Thu, 02 Jan 2025 12:00:00 +0000
|
7
debian/ci_changelog.sh
vendored
Executable file
7
debian/ci_changelog.sh
vendored
Executable file
@ -0,0 +1,7 @@
|
||||
#!/usr/bin/bash
|
||||
export DEBEMAIL="github-actions[bot]@users.noreply.github.com"
|
||||
PKG_VERSION=$(python3 bin/buildinfo.py short)
|
||||
|
||||
dch --newversion "$PKG_VERSION-1" \
|
||||
--distribution UNRELEASED \
|
||||
"GitHub Actions Automatic version bump"
|
23
debian/ci_pack_sdeb.sh
vendored
Executable file
23
debian/ci_pack_sdeb.sh
vendored
Executable file
@ -0,0 +1,23 @@
|
||||
#!/usr/bin/bash
|
||||
export DEBEMAIL="jbennett@incomsystems.biz"
|
||||
export PLATFORMIO_LIBDEPS_DIR=pio/libdeps
|
||||
export PLATFORMIO_PACKAGES_DIR=pio/packages
|
||||
export PLATFORMIO_CORE_DIR=pio/core
|
||||
|
||||
# Download libraries to `pio`
|
||||
platformio pkg install -e native
|
||||
platformio pkg install -e native -t platformio/tool-scons@4.40502.0
|
||||
# Compress `pio` directory to prevent dh_clean from sanitizing it
|
||||
tar -cf pio.tar pio/
|
||||
rm -rf pio
|
||||
# Download the latest meshtastic/web release build.tar to `web.tar`
|
||||
curl -L https://github.com/meshtastic/web/releases/download/latest/build.tar -o web.tar
|
||||
|
||||
package=$(dpkg-parsechangelog --show-field Source)
|
||||
|
||||
rm -rf debian/changelog
|
||||
dch --create --distribution "$SERIES" --package "$package" --newversion "$PKG_VERSION~$SERIES" \
|
||||
"GitHub Actions Automatic packaging for $PKG_VERSION~$SERIES"
|
||||
|
||||
# Build the source deb
|
||||
debuild -S -nc -k"$GPG_KEY_ID"
|
32
debian/control
vendored
Normal file
32
debian/control
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
Source: meshtasticd
|
||||
Section: misc
|
||||
Priority: optional
|
||||
Maintainer: Austin Lane <vidplace7@gmail.com>
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
tar,
|
||||
gzip,
|
||||
platformio,
|
||||
python3-protobuf,
|
||||
python3-grpcio,
|
||||
git,
|
||||
g++,
|
||||
pkg-config,
|
||||
libyaml-cpp-dev,
|
||||
libgpiod-dev,
|
||||
libbluetooth-dev,
|
||||
libusb-1.0-0-dev,
|
||||
libi2c-dev,
|
||||
openssl,
|
||||
libssl-dev,
|
||||
libulfius-dev,
|
||||
liborcania-dev
|
||||
Standards-Version: 4.6.2
|
||||
Homepage: https://github.com/meshtastic/firmware
|
||||
Rules-Requires-Root: no
|
||||
|
||||
Package: meshtasticd
|
||||
Architecture: any
|
||||
Depends: ${misc:Depends}, ${shlibs:Depends}
|
||||
Description: Meshtastic daemon for communicating with Meshtastic devices
|
||||
Meshtastic is an off-grid text communication platform that uses inexpensive
|
||||
LoRa radios.
|
4
debian/meshtasticd.dirs
vendored
Normal file
4
debian/meshtasticd.dirs
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
etc/meshtasticd
|
||||
etc/meshtasticd/config.d
|
||||
etc/meshtasticd/available.d
|
||||
usr/share/meshtasticd/web
|
8
debian/meshtasticd.install
vendored
Normal file
8
debian/meshtasticd.install
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
.pio/build/native/meshtasticd usr/sbin
|
||||
|
||||
bin/config.yaml etc/meshtasticd
|
||||
bin/config.d/* etc/meshtasticd/available.d
|
||||
|
||||
bin/meshtasticd.service lib/systemd/system
|
||||
|
||||
web/* usr/share/meshtasticd/web
|
23
debian/rules
vendored
Executable file
23
debian/rules
vendored
Executable file
@ -0,0 +1,23 @@
|
||||
#!/usr/bin/make -f
|
||||
# export DH_VERBOSE = 1
|
||||
|
||||
# Use the "dh" sequencer
|
||||
%:
|
||||
dh $@
|
||||
|
||||
# https://docs.platformio.org/en/latest/envvars.html
|
||||
PIO_ENV:=\
|
||||
PLATFORMIO_CORE_DIR=pio/core \
|
||||
PLATFORMIO_LIBDEPS_DIR=pio/libdeps \
|
||||
PLATFORMIO_PACKAGES_DIR=pio/packages
|
||||
|
||||
override_dh_auto_build:
|
||||
# Extract tarballs within source deb
|
||||
tar -xf pio.tar
|
||||
mkdir -p web && tar -xf web.tar -C web
|
||||
gunzip web/ -r
|
||||
# Build with platformio
|
||||
$(PIO_ENV) platformio run -e native
|
||||
# Move the binary and default config to the correct name
|
||||
mv .pio/build/native/program .pio/build/native/meshtasticd
|
||||
cp bin/config-dist.yaml bin/config.yaml
|
1
debian/source/format
vendored
Normal file
1
debian/source/format
vendored
Normal file
@ -0,0 +1 @@
|
||||
3.0 (native)
|
2
debian/source/include-binaries
vendored
Normal file
2
debian/source/include-binaries
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
pio.tar
|
||||
web.tar
|
1
debian/source/options
vendored
Normal file
1
debian/source/options
vendored
Normal file
@ -0,0 +1 @@
|
||||
extend-diff-ignore = "\.pio"
|
@ -3,43 +3,7 @@
|
||||
|
||||
[platformio]
|
||||
default_envs = tbeam
|
||||
;default_envs = pico
|
||||
;default_envs = tbeam-s3-core
|
||||
;default_envs = tbeam0.7
|
||||
;default_envs = heltec-v1
|
||||
;default_envs = heltec-v2_0
|
||||
;default_envs = heltec-v2_1
|
||||
;default_envs = heltec-wireless-tracker
|
||||
;default_envs = chatter2
|
||||
;default_envs = tlora-v1
|
||||
;default_envs = tlora_v1_3
|
||||
;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
|
||||
;default_envs = native
|
||||
;default_envs = nano-g1
|
||||
;default_envs = pca10059_diy_eink
|
||||
;default_envs = meshtastic-diy-v1
|
||||
;default_envs = meshtastic-diy-v1_1
|
||||
;default_envs = meshtastic-dr-dev
|
||||
;default_envs = m5stack-coreink
|
||||
;default_envs = rak4631
|
||||
;default_envs = rak4631_eth_gw
|
||||
;default_envs = rak2560
|
||||
;default_envs = rak11310
|
||||
;default_envs = rak_wismeshtap
|
||||
;default_envs = wio-e5
|
||||
;default_envs = radiomaster_900_bandit_nano
|
||||
;default_envs = radiomaster_900_bandit_micro
|
||||
;default_envs = radiomaster_900_bandit
|
||||
;default_envs = heltec_vision_master_t190
|
||||
;default_envs = heltec_vision_master_e213
|
||||
;default_envs = heltec_vision_master_e290
|
||||
;default_envs = heltec_mesh_node_t114
|
||||
|
||||
extra_configs =
|
||||
arch/*/*.ini
|
||||
variants/*/platformio.ini
|
||||
@ -124,8 +88,7 @@ lib_deps =
|
||||
|
||||
[radiolib_base]
|
||||
lib_deps =
|
||||
; jgromes/RadioLib@7.1.0
|
||||
https://github.com/jgromes/RadioLib.git#92b687821ff4e6c358d866f84566f66672ab02b8
|
||||
jgromes/RadioLib@7.1.2
|
||||
|
||||
; Common libs for environmental measurements in telemetry module
|
||||
; (not included in native / portduino)
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit c55f120a9c1ce90c85e4826907a0b9bcb2d5f5a2
|
||||
Subproject commit 76f806e1bb1e2a7b157a14fadd095775f63db5e4
|
@ -142,7 +142,7 @@ class MeshService
|
||||
void sendToPhone(meshtastic_MeshPacket *p);
|
||||
|
||||
/// Send an MQTT message to the phone for client proxying
|
||||
void sendMqttMessageToClientProxy(meshtastic_MqttClientProxyMessage *m);
|
||||
virtual void sendMqttMessageToClientProxy(meshtastic_MqttClientProxyMessage *m);
|
||||
|
||||
/// Send a ClientNotification to the phone
|
||||
void sendClientNotification(meshtastic_ClientNotification *cn);
|
||||
|
@ -148,7 +148,7 @@ class NodeDB
|
||||
return &meshNodes->at(x);
|
||||
}
|
||||
|
||||
meshtastic_NodeInfoLite *getMeshNode(NodeNum n);
|
||||
virtual meshtastic_NodeInfoLite *getMeshNode(NodeNum n);
|
||||
size_t getNumMeshNodes() { return numMeshNodes; }
|
||||
|
||||
// returns true if the maximum number of nodes is reached or we are running low on memory
|
||||
|
@ -437,6 +437,7 @@ void RadioLibInterface::handleReceiveInterrupt()
|
||||
// nodes.
|
||||
meshtastic_MeshPacket *mp = packetPool.allocZeroed();
|
||||
|
||||
// Keep the assigned fields in sync with src/mqtt/MQTT.cpp:onReceiveProto
|
||||
mp->from = radioBuffer.header.from;
|
||||
mp->to = radioBuffer.header.to;
|
||||
mp->id = radioBuffer.header.id;
|
||||
|
@ -75,7 +75,7 @@ class Router : protected concurrency::OSThread, protected PacketHistory
|
||||
* RadioInterface calls this to queue up packets that have been received from the radio. The router is now responsible for
|
||||
* freeing the packet
|
||||
*/
|
||||
void enqueueReceivedMessage(meshtastic_MeshPacket *p);
|
||||
virtual void enqueueReceivedMessage(meshtastic_MeshPacket *p);
|
||||
|
||||
/**
|
||||
* Send a packet on a suitable interface. This routine will
|
||||
|
@ -63,6 +63,8 @@ PB_BIND(meshtastic_Config_SessionkeyConfig, meshtastic_Config_SessionkeyConfig,
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -139,6 +139,14 @@ typedef enum _meshtastic_Config_NetworkConfig_AddressMode {
|
||||
meshtastic_Config_NetworkConfig_AddressMode_STATIC = 1
|
||||
} meshtastic_Config_NetworkConfig_AddressMode;
|
||||
|
||||
/* Available flags auxiliary network protocols */
|
||||
typedef enum _meshtastic_Config_NetworkConfig_ProtocolFlags {
|
||||
/* Do not broadcast packets over any network protocol */
|
||||
meshtastic_Config_NetworkConfig_ProtocolFlags_NO_BROADCAST = 0,
|
||||
/* Enable broadcasting packets via UDP over the local network */
|
||||
meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST = 1
|
||||
} meshtastic_Config_NetworkConfig_ProtocolFlags;
|
||||
|
||||
/* How the GPS coordinates are displayed on the OLED screen. */
|
||||
typedef enum _meshtastic_Config_DisplayConfig_GpsCoordinateFormat {
|
||||
/* GPS coordinates are displayed in the normal decimal degrees format:
|
||||
@ -429,6 +437,8 @@ typedef struct _meshtastic_Config_NetworkConfig {
|
||||
meshtastic_Config_NetworkConfig_IpV4Config ipv4_config;
|
||||
/* rsyslog Server and Port */
|
||||
char rsyslog_server[33];
|
||||
/* Flags for enabling/disabling network protocols */
|
||||
uint32_t enabled_protocols;
|
||||
} meshtastic_Config_NetworkConfig;
|
||||
|
||||
/* Display Config */
|
||||
@ -613,6 +623,10 @@ extern "C" {
|
||||
#define _meshtastic_Config_NetworkConfig_AddressMode_MAX meshtastic_Config_NetworkConfig_AddressMode_STATIC
|
||||
#define _meshtastic_Config_NetworkConfig_AddressMode_ARRAYSIZE ((meshtastic_Config_NetworkConfig_AddressMode)(meshtastic_Config_NetworkConfig_AddressMode_STATIC+1))
|
||||
|
||||
#define _meshtastic_Config_NetworkConfig_ProtocolFlags_MIN meshtastic_Config_NetworkConfig_ProtocolFlags_NO_BROADCAST
|
||||
#define _meshtastic_Config_NetworkConfig_ProtocolFlags_MAX meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST
|
||||
#define _meshtastic_Config_NetworkConfig_ProtocolFlags_ARRAYSIZE ((meshtastic_Config_NetworkConfig_ProtocolFlags)(meshtastic_Config_NetworkConfig_ProtocolFlags_UDP_BROADCAST+1))
|
||||
|
||||
#define _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DEC
|
||||
#define _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MAX meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR
|
||||
#define _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_ARRAYSIZE ((meshtastic_Config_DisplayConfig_GpsCoordinateFormat)(meshtastic_Config_DisplayConfig_GpsCoordinateFormat_OSGR+1))
|
||||
@ -674,7 +688,7 @@ extern "C" {
|
||||
#define meshtastic_Config_DeviceConfig_init_default {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0}
|
||||
#define meshtastic_Config_PositionConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _meshtastic_Config_PositionConfig_GpsMode_MIN}
|
||||
#define meshtastic_Config_PowerConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
#define meshtastic_Config_NetworkConfig_init_default {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_default, ""}
|
||||
#define meshtastic_Config_NetworkConfig_init_default {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_default, "", 0}
|
||||
#define meshtastic_Config_NetworkConfig_IpV4Config_init_default {0, 0, 0, 0}
|
||||
#define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN}
|
||||
#define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0}
|
||||
@ -685,7 +699,7 @@ extern "C" {
|
||||
#define meshtastic_Config_DeviceConfig_init_zero {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0}
|
||||
#define meshtastic_Config_PositionConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _meshtastic_Config_PositionConfig_GpsMode_MIN}
|
||||
#define meshtastic_Config_PowerConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
#define meshtastic_Config_NetworkConfig_init_zero {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_zero, ""}
|
||||
#define meshtastic_Config_NetworkConfig_init_zero {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_zero, "", 0}
|
||||
#define meshtastic_Config_NetworkConfig_IpV4Config_init_zero {0, 0, 0, 0}
|
||||
#define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN}
|
||||
#define meshtastic_Config_LoRaConfig_init_zero {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0}
|
||||
@ -739,6 +753,7 @@ extern "C" {
|
||||
#define meshtastic_Config_NetworkConfig_address_mode_tag 7
|
||||
#define meshtastic_Config_NetworkConfig_ipv4_config_tag 8
|
||||
#define meshtastic_Config_NetworkConfig_rsyslog_server_tag 9
|
||||
#define meshtastic_Config_NetworkConfig_enabled_protocols_tag 10
|
||||
#define meshtastic_Config_DisplayConfig_screen_on_secs_tag 1
|
||||
#define meshtastic_Config_DisplayConfig_gps_format_tag 2
|
||||
#define meshtastic_Config_DisplayConfig_auto_screen_carousel_secs_tag 3
|
||||
@ -867,7 +882,8 @@ X(a, STATIC, SINGULAR, STRING, ntp_server, 5) \
|
||||
X(a, STATIC, SINGULAR, BOOL, eth_enabled, 6) \
|
||||
X(a, STATIC, SINGULAR, UENUM, address_mode, 7) \
|
||||
X(a, STATIC, OPTIONAL, MESSAGE, ipv4_config, 8) \
|
||||
X(a, STATIC, SINGULAR, STRING, rsyslog_server, 9)
|
||||
X(a, STATIC, SINGULAR, STRING, rsyslog_server, 9) \
|
||||
X(a, STATIC, SINGULAR, UINT32, enabled_protocols, 10)
|
||||
#define meshtastic_Config_NetworkConfig_CALLBACK NULL
|
||||
#define meshtastic_Config_NetworkConfig_DEFAULT NULL
|
||||
#define meshtastic_Config_NetworkConfig_ipv4_config_MSGTYPE meshtastic_Config_NetworkConfig_IpV4Config
|
||||
@ -972,12 +988,12 @@ extern const pb_msgdesc_t meshtastic_Config_SessionkeyConfig_msg;
|
||||
#define meshtastic_Config_DisplayConfig_size 30
|
||||
#define meshtastic_Config_LoRaConfig_size 85
|
||||
#define meshtastic_Config_NetworkConfig_IpV4Config_size 20
|
||||
#define meshtastic_Config_NetworkConfig_size 196
|
||||
#define meshtastic_Config_NetworkConfig_size 202
|
||||
#define meshtastic_Config_PositionConfig_size 62
|
||||
#define meshtastic_Config_PowerConfig_size 52
|
||||
#define meshtastic_Config_SecurityConfig_size 178
|
||||
#define meshtastic_Config_SessionkeyConfig_size 0
|
||||
#define meshtastic_Config_size 199
|
||||
#define meshtastic_Config_size 205
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
|
@ -51,6 +51,8 @@ typedef enum _meshtastic_Language {
|
||||
meshtastic_Language_GREEK = 13,
|
||||
/* Norwegian */
|
||||
meshtastic_Language_NORWEGIAN = 14,
|
||||
/* Slovenian */
|
||||
meshtastic_Language_SLOVENIAN = 15,
|
||||
/* Simplified Chinese (experimental) */
|
||||
meshtastic_Language_SIMPLIFIED_CHINESE = 30,
|
||||
/* Traditional Chinese (experimental) */
|
||||
@ -71,6 +73,8 @@ typedef struct _meshtastic_NodeFilter {
|
||||
bool position_switch;
|
||||
/* Filter nodes by matching name string */
|
||||
char node_name[16];
|
||||
/* Filter based on channel */
|
||||
int8_t channel;
|
||||
} meshtastic_NodeFilter;
|
||||
|
||||
typedef struct _meshtastic_NodeHighlight {
|
||||
@ -138,10 +142,10 @@ extern "C" {
|
||||
|
||||
/* Initializer values for message structs */
|
||||
#define meshtastic_DeviceUIConfig_init_default {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_default, false, meshtastic_NodeHighlight_init_default, {0, {0}}}
|
||||
#define meshtastic_NodeFilter_init_default {0, 0, 0, 0, 0, ""}
|
||||
#define meshtastic_NodeFilter_init_default {0, 0, 0, 0, 0, "", 0}
|
||||
#define meshtastic_NodeHighlight_init_default {0, 0, 0, 0, ""}
|
||||
#define meshtastic_DeviceUIConfig_init_zero {0, 0, 0, 0, 0, 0, _meshtastic_Theme_MIN, 0, 0, 0, _meshtastic_Language_MIN, false, meshtastic_NodeFilter_init_zero, false, meshtastic_NodeHighlight_init_zero, {0, {0}}}
|
||||
#define meshtastic_NodeFilter_init_zero {0, 0, 0, 0, 0, ""}
|
||||
#define meshtastic_NodeFilter_init_zero {0, 0, 0, 0, 0, "", 0}
|
||||
#define meshtastic_NodeHighlight_init_zero {0, 0, 0, 0, ""}
|
||||
|
||||
/* Field tags (for use in manual encoding/decoding) */
|
||||
@ -151,6 +155,7 @@ extern "C" {
|
||||
#define meshtastic_NodeFilter_hops_away_tag 4
|
||||
#define meshtastic_NodeFilter_position_switch_tag 5
|
||||
#define meshtastic_NodeFilter_node_name_tag 6
|
||||
#define meshtastic_NodeFilter_channel_tag 7
|
||||
#define meshtastic_NodeHighlight_chat_switch_tag 1
|
||||
#define meshtastic_NodeHighlight_position_switch_tag 2
|
||||
#define meshtastic_NodeHighlight_telemetry_switch_tag 3
|
||||
@ -198,7 +203,8 @@ X(a, STATIC, SINGULAR, BOOL, offline_switch, 2) \
|
||||
X(a, STATIC, SINGULAR, BOOL, public_key_switch, 3) \
|
||||
X(a, STATIC, SINGULAR, INT32, hops_away, 4) \
|
||||
X(a, STATIC, SINGULAR, BOOL, position_switch, 5) \
|
||||
X(a, STATIC, SINGULAR, STRING, node_name, 6)
|
||||
X(a, STATIC, SINGULAR, STRING, node_name, 6) \
|
||||
X(a, STATIC, SINGULAR, INT32, channel, 7)
|
||||
#define meshtastic_NodeFilter_CALLBACK NULL
|
||||
#define meshtastic_NodeFilter_DEFAULT NULL
|
||||
|
||||
@ -222,8 +228,8 @@ extern const pb_msgdesc_t meshtastic_NodeHighlight_msg;
|
||||
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
#define MESHTASTIC_MESHTASTIC_DEVICE_UI_PB_H_MAX_SIZE meshtastic_DeviceUIConfig_size
|
||||
#define meshtastic_DeviceUIConfig_size 117
|
||||
#define meshtastic_NodeFilter_size 36
|
||||
#define meshtastic_DeviceUIConfig_size 128
|
||||
#define meshtastic_NodeFilter_size 47
|
||||
#define meshtastic_NodeHighlight_size 25
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
@ -187,7 +187,7 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg;
|
||||
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
#define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalConfig_size
|
||||
#define meshtastic_LocalConfig_size 735
|
||||
#define meshtastic_LocalConfig_size 741
|
||||
#define meshtastic_LocalModuleConfig_size 699
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
@ -143,6 +143,11 @@ static int32_t reconnectWiFi()
|
||||
delay(5000);
|
||||
|
||||
if (!WiFi.isConnected()) {
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32C3
|
||||
WiFi.mode(WIFI_MODE_NULL);
|
||||
WiFi.useStaticBuffers(true);
|
||||
WiFi.mode(WIFI_STA);
|
||||
#endif
|
||||
WiFi.begin(wifiName, wifiPsw);
|
||||
}
|
||||
isReconnecting = false;
|
||||
|
@ -13,7 +13,8 @@ class RoutingModule : public ProtobufModule<meshtastic_Routing>
|
||||
*/
|
||||
RoutingModule();
|
||||
|
||||
void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex, uint8_t hopLimit = 0);
|
||||
virtual void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex,
|
||||
uint8_t hopLimit = 0);
|
||||
|
||||
// Given the hopStart and hopLimit upon reception of a request, return the hop limit to use for the response
|
||||
uint8_t getHopLimitForResponse(uint8_t hopStart, uint8_t hopLimit);
|
||||
|
@ -99,44 +99,45 @@ bool PowerTelemetryModule::wantUIFrame()
|
||||
void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display->setFont(FONT_MEDIUM);
|
||||
display->drawString(x, y, "Power Telemetry");
|
||||
display->setFont(FONT_SMALL);
|
||||
|
||||
if (lastMeasurementPacket == nullptr) {
|
||||
display->setFont(FONT_SMALL);
|
||||
display->drawString(x, y += _fontHeight(FONT_MEDIUM), "No measurement");
|
||||
// In case of no valid packet, display "Power Telemetry", "No measurement"
|
||||
display->drawString(x, y, "Power Telemetry");
|
||||
display->drawString(x, y += _fontHeight(FONT_SMALL), "No measurement");
|
||||
return;
|
||||
}
|
||||
|
||||
// Decode the last power packet
|
||||
meshtastic_Telemetry lastMeasurement;
|
||||
|
||||
uint32_t agoSecs = service->GetTimeSinceMeshPacket(lastMeasurementPacket);
|
||||
const char *lastSender = getSenderShortName(*lastMeasurementPacket);
|
||||
|
||||
const meshtastic_Data &p = lastMeasurementPacket->decoded;
|
||||
if (!pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_Telemetry_msg, &lastMeasurement)) {
|
||||
display->setFont(FONT_SMALL);
|
||||
display->drawString(x, y += _fontHeight(FONT_MEDIUM), "Measurement Error");
|
||||
display->drawString(x, y, "Measurement Error");
|
||||
LOG_ERROR("Unable to decode last packet");
|
||||
return;
|
||||
}
|
||||
|
||||
// Display "Pow. From: ..."
|
||||
display->drawString(x, y, "Pow. From: " + String(lastSender) + "(" + String(agoSecs) + "s)");
|
||||
|
||||
// Display current and voltage based on ...power_metrics.has_[channel/voltage/current]... flags
|
||||
display->setFont(FONT_SMALL);
|
||||
display->drawString(x, y += _fontHeight(FONT_MEDIUM) - 2, "From: " + String(lastSender) + "(" + String(agoSecs) + "s)");
|
||||
if (lastMeasurement.variant.power_metrics.has_ch1_voltage || lastMeasurement.variant.power_metrics.has_ch1_current) {
|
||||
display->drawString(x, y += _fontHeight(FONT_SMALL),
|
||||
"Ch1 Volt: " + String(lastMeasurement.variant.power_metrics.ch1_voltage, 2) +
|
||||
"V / Curr: " + String(lastMeasurement.variant.power_metrics.ch1_current, 0) + "mA");
|
||||
"Ch1: " + String(lastMeasurement.variant.power_metrics.ch1_voltage, 2) +
|
||||
"V " + String(lastMeasurement.variant.power_metrics.ch1_current, 0) + "mA");
|
||||
}
|
||||
if (lastMeasurement.variant.power_metrics.has_ch2_voltage || lastMeasurement.variant.power_metrics.has_ch2_current) {
|
||||
display->drawString(x, y += _fontHeight(FONT_SMALL),
|
||||
"Ch2 Volt: " + String(lastMeasurement.variant.power_metrics.ch2_voltage, 2) +
|
||||
"V / Curr: " + String(lastMeasurement.variant.power_metrics.ch2_current, 0) + "mA");
|
||||
"Ch2: " + String(lastMeasurement.variant.power_metrics.ch2_voltage, 2) +
|
||||
"V " + String(lastMeasurement.variant.power_metrics.ch2_current, 0) + "mA");
|
||||
}
|
||||
if (lastMeasurement.variant.power_metrics.has_ch3_voltage || lastMeasurement.variant.power_metrics.has_ch3_current) {
|
||||
display->drawString(x, y += _fontHeight(FONT_SMALL),
|
||||
"Ch3 Volt: " + String(lastMeasurement.variant.power_metrics.ch3_voltage, 2) +
|
||||
"V / Curr: " + String(lastMeasurement.variant.power_metrics.ch3_current, 0) + "mA");
|
||||
"Ch3: " + String(lastMeasurement.variant.power_metrics.ch3_voltage, 2) +
|
||||
"V " + String(lastMeasurement.variant.power_metrics.ch3_current, 0) + "mA");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -76,12 +76,22 @@ inline void onReceiveProto(char *topic, byte *payload, size_t length)
|
||||
return;
|
||||
}
|
||||
LOG_INFO("Received MQTT topic %s, len=%u", topic, length);
|
||||
if (e.packet->hop_limit > HOP_MAX || e.packet->hop_start > HOP_MAX) {
|
||||
LOG_INFO("Invalid hop_limit(%u) or hop_start(%u)", e.packet->hop_limit, e.packet->hop_start);
|
||||
return;
|
||||
}
|
||||
|
||||
UniquePacketPoolPacket p = packetPool.allocUniqueCopy(*e.packet);
|
||||
UniquePacketPoolPacket p = packetPool.allocUniqueZeroed();
|
||||
p->from = e.packet->from;
|
||||
p->to = e.packet->to;
|
||||
p->id = e.packet->id;
|
||||
p->channel = e.packet->channel;
|
||||
p->hop_limit = e.packet->hop_limit;
|
||||
p->hop_start = e.packet->hop_start;
|
||||
p->want_ack = e.packet->want_ack;
|
||||
p->via_mqtt = true; // Mark that the packet was received via MQTT
|
||||
// Unset received SNR/RSSI which might have been added by the MQTT gateway
|
||||
p->rx_snr = 0;
|
||||
p->rx_rssi = 0;
|
||||
p->which_payload_variant = e.packet->which_payload_variant;
|
||||
memcpy(&p->decoded, &e.packet->decoded, std::max(sizeof(p->decoded), sizeof(p->encrypted)));
|
||||
|
||||
if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
|
||||
if (moduleConfig.mqtt.encryption_enabled) {
|
||||
|
@ -2,14 +2,11 @@
|
||||
#include "hardware/xosc.h"
|
||||
#include <hardware/clocks.h>
|
||||
#include <hardware/pll.h>
|
||||
#include <pico/sleep.h>
|
||||
#include <pico/stdlib.h>
|
||||
#include <pico/unique_id.h>
|
||||
|
||||
void setBluetoothEnable(bool enable)
|
||||
{
|
||||
// not needed
|
||||
}
|
||||
#ifdef __PLAT_RP2040__
|
||||
#include <pico/sleep.h>
|
||||
|
||||
static bool awake;
|
||||
|
||||
@ -66,7 +63,20 @@ void cpuDeepSleep(uint32_t msecs)
|
||||
rp2040.reboot();
|
||||
|
||||
/* Set RP2040 in dormant mode. Will not wake up. */
|
||||
// xosc_dormant();
|
||||
// xosc_dormant();
|
||||
}
|
||||
|
||||
#else
|
||||
void cpuDeepSleep(uint32_t msecs)
|
||||
{
|
||||
/* Set RP2040 in dormant mode. Will not wake up. */
|
||||
xosc_dormant();
|
||||
}
|
||||
#endif
|
||||
|
||||
void setBluetoothEnable(bool enable)
|
||||
{
|
||||
// not needed
|
||||
}
|
||||
|
||||
void updateBatteryLevel(uint8_t level)
|
||||
|
856
test/test_mqtt/MQTT.cpp
Normal file
856
test/test_mqtt/MQTT.cpp
Normal file
@ -0,0 +1,856 @@
|
||||
#include "DebugConfiguration.h"
|
||||
#include "TestUtil.h"
|
||||
#include <unity.h>
|
||||
|
||||
#ifdef ARCH_PORTDUINO
|
||||
#include "mesh/CryptoEngine.h"
|
||||
#include "mesh/Default.h"
|
||||
#include "mesh/MeshService.h"
|
||||
#include "mesh/NodeDB.h"
|
||||
#include "mesh/Router.h"
|
||||
#include "modules/RoutingModule.h"
|
||||
#include "mqtt/MQTT.h"
|
||||
#include "mqtt/ServiceEnvelope.h"
|
||||
|
||||
#include <PubSubClient.h>
|
||||
#include <WiFiClient.h>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <list>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
namespace
|
||||
{
|
||||
// Minimal router needed to receive messages from MQTT.
|
||||
class MockRouter : public Router
|
||||
{
|
||||
public:
|
||||
~MockRouter()
|
||||
{
|
||||
// cryptLock is created in the constructor for Router.
|
||||
delete cryptLock;
|
||||
cryptLock = NULL;
|
||||
}
|
||||
void enqueueReceivedMessage(meshtastic_MeshPacket *p) override
|
||||
{
|
||||
packets_.emplace_back(*p);
|
||||
packetPool.release(p);
|
||||
}
|
||||
std::list<meshtastic_MeshPacket> packets_; // Packets received by the Router.
|
||||
};
|
||||
|
||||
// Minimal MeshService needed to receive messages from MQTT for testing PKI channel.
|
||||
class MockMeshService : public MeshService
|
||||
{
|
||||
public:
|
||||
void sendMqttMessageToClientProxy(meshtastic_MqttClientProxyMessage *m) override
|
||||
{
|
||||
messages_.emplace_back(*m);
|
||||
releaseMqttClientProxyMessageToPool(m);
|
||||
}
|
||||
std::list<meshtastic_MqttClientProxyMessage> messages_; // Messages received from the MeshService.
|
||||
};
|
||||
|
||||
// Minimal NodeDB needed to return values from getMeshNode.
|
||||
class MockNodeDB : public NodeDB
|
||||
{
|
||||
public:
|
||||
meshtastic_NodeInfoLite *getMeshNode(NodeNum n) override { return &emptyNode; }
|
||||
meshtastic_NodeInfoLite emptyNode = {};
|
||||
};
|
||||
|
||||
// Minimal RoutingModule needed to return values from sendAckNak.
|
||||
class MockRoutingModule : public RoutingModule
|
||||
{
|
||||
public:
|
||||
void sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex,
|
||||
uint8_t hopLimit = 0) override
|
||||
{
|
||||
ackNacks_.emplace_back(err, to, idFrom, chIndex, hopLimit);
|
||||
}
|
||||
std::list<std::tuple<meshtastic_Routing_Error, NodeNum, PacketId, ChannelIndex, uint8_t>>
|
||||
ackNacks_; // ackNacks received by the RoutingModule.
|
||||
};
|
||||
|
||||
// A WiFi client used by the MQTT::PubSubClient. Implements a minimal pub/sub server.
|
||||
// There isn't an easy way to mock PubSubClient due to it not having virtual methods, so we mock using
|
||||
// the WiFiClinet that PubSubClient uses.
|
||||
class MockPubSubServer : public WiFiClient
|
||||
{
|
||||
public:
|
||||
static constexpr char kTextTopic[] = "TextTopic";
|
||||
uint8_t connected() override { return connected_; }
|
||||
void flush() override {}
|
||||
IPAddress remoteIP() const override { return IPAddress(htonl(ipAddress_)); }
|
||||
void stop() override { connected_ = false; }
|
||||
|
||||
int connect(IPAddress ip, uint16_t port) override
|
||||
{
|
||||
if (refuseConnection_)
|
||||
return 0;
|
||||
connected_ = true;
|
||||
return 1;
|
||||
}
|
||||
int connect(const char *host, uint16_t port) override
|
||||
{
|
||||
if (refuseConnection_)
|
||||
return 0;
|
||||
connected_ = true;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int available() override
|
||||
{
|
||||
if (buffer_.empty())
|
||||
return 0;
|
||||
return buffer_.front().size();
|
||||
}
|
||||
|
||||
int read() override
|
||||
{
|
||||
assert(available());
|
||||
std::string &front = buffer_.front();
|
||||
char ch = front[0];
|
||||
front = front.substr(1, front.size());
|
||||
if (front.empty())
|
||||
buffer_.pop_front();
|
||||
return ch;
|
||||
}
|
||||
|
||||
size_t write(uint8_t data) override { return write(&data, 1); }
|
||||
size_t write(const uint8_t *buf, size_t size) override
|
||||
{
|
||||
command_ += std::string(reinterpret_cast<const char *>(buf), size);
|
||||
if (command_.size() < 2)
|
||||
return size;
|
||||
const int len = (uint8_t)command_[1] + 2;
|
||||
if (command_.size() < len)
|
||||
return size;
|
||||
handleCommand(command_[0], command_.substr(2, len));
|
||||
command_ = command_.substr(len, command_.size());
|
||||
return size;
|
||||
}
|
||||
|
||||
// The pub/sub "server".
|
||||
// https://public.dhe.ibm.com/software/dw/webservices/ws-mqtt/MQTT_V3.1_Protocol_Specific.pdf
|
||||
void handleCommand(uint8_t header, std::string_view message)
|
||||
{
|
||||
switch (header & 0xf0) {
|
||||
case MQTTCONNECT:
|
||||
LOG_DEBUG("MQTTCONNECT");
|
||||
buffer_.push_back(std::string("\x20\x02\x00\x00", 4));
|
||||
break;
|
||||
|
||||
case MQTTSUBSCRIBE: {
|
||||
LOG_DEBUG("MQTTSUBSCRIBE");
|
||||
assert(message.size() >= 5);
|
||||
message.remove_prefix(2); // skip messageId
|
||||
|
||||
while (message.size() >= 3) {
|
||||
const uint16_t topicSize = ((uint8_t)message[0]) << 8 | (uint8_t)message[1];
|
||||
message.remove_prefix(2);
|
||||
|
||||
assert(message.size() >= topicSize + 1);
|
||||
std::string topic(message.data(), topicSize);
|
||||
message.remove_prefix(topicSize + 1);
|
||||
|
||||
LOG_DEBUG("Subscribed to topic: %s", topic.c_str());
|
||||
subscriptions_.insert(std::move(topic));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MQTTPINGREQ:
|
||||
LOG_DEBUG("MQTTPINGREQ");
|
||||
buffer_.push_back(std::string("\xd0\x00", 2));
|
||||
break;
|
||||
|
||||
case MQTTPUBLISH: {
|
||||
LOG_DEBUG("MQTTPUBLISH");
|
||||
assert(message.size() >= 3);
|
||||
const uint16_t topicSize = ((uint8_t)message[0]) << 8 | (uint8_t)message[1];
|
||||
message.remove_prefix(2);
|
||||
|
||||
assert(message.size() >= topicSize);
|
||||
std::string topic(message.data(), topicSize);
|
||||
message.remove_prefix(topicSize);
|
||||
|
||||
if (topic == kTextTopic) {
|
||||
published_.emplace_back(std::move(topic), std::string(message.data(), message.size()));
|
||||
} else {
|
||||
published_.emplace_back(
|
||||
std::move(topic), DecodedServiceEnvelope(reinterpret_cast<const uint8_t *>(message.data()), message.size()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool connected_ = false;
|
||||
bool refuseConnection_ = false; // Simulate a failed connection.
|
||||
uint32_t ipAddress_ = 0x01010101; // IP address of the MQTT server.
|
||||
std::list<std::string> buffer_; // Buffer of messages for the pubSub client to receive.
|
||||
std::string command_; // Current command received from the pubSub client.
|
||||
std::set<std::string> subscriptions_; // Topics that the pubSub client has subscribed to.
|
||||
std::list<std::pair<std::string, std::variant<std::string,
|
||||
DecodedServiceEnvelope>>>
|
||||
published_; // Messages published from the pubSub client. Each list element is a pair containing the topic name and either
|
||||
// a text message (if from the kTextTopic topic) or a DecodedServiceEnvelope.
|
||||
};
|
||||
|
||||
// Instances of our mocks.
|
||||
class MQTTUnitTest;
|
||||
MQTTUnitTest *unitTest;
|
||||
MockPubSubServer *pubsub;
|
||||
MockRoutingModule *mockRoutingModule;
|
||||
MockMeshService *mockMeshService;
|
||||
MockRouter *mockRouter;
|
||||
|
||||
// Keep running the loop until either conditionMet returns true or 4 seconds elapse.
|
||||
// Returns true if conditionMet returns true, returns false on timeout.
|
||||
bool loopUntil(std::function<bool()> conditionMet)
|
||||
{
|
||||
long start = millis();
|
||||
while (start + 4000 > millis()) {
|
||||
long delayMsec = concurrency::mainController.runOrDelay();
|
||||
if (conditionMet())
|
||||
return true;
|
||||
concurrency::mainDelay.delay(std::min(delayMsec, 5L));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Used to access protected/private members of MQTT for unit testing.
|
||||
class MQTTUnitTest : public MQTT
|
||||
{
|
||||
public:
|
||||
MQTTUnitTest() : MQTT(std::make_unique<MockPubSubServer>())
|
||||
{
|
||||
pubsub = reinterpret_cast<MockPubSubServer *>(mqttClient.get());
|
||||
}
|
||||
~MQTTUnitTest()
|
||||
{
|
||||
// Needed because WiFiClient does not have a virtual destructor.
|
||||
mqttClient.release();
|
||||
delete pubsub;
|
||||
}
|
||||
int queueSize() { return mqttQueue.numUsed(); }
|
||||
void reportToMap(std::optional<uint32_t> precision = std::nullopt)
|
||||
{
|
||||
if (precision.has_value())
|
||||
map_position_precision = precision.value();
|
||||
map_publish_interval_msecs = 0;
|
||||
perhapsReportToMap();
|
||||
}
|
||||
void publish(const meshtastic_MeshPacket *p, std::string gateway = "!87654321", std::string channel = "test")
|
||||
{
|
||||
std::stringstream topic;
|
||||
topic << "msh/2/e/" << channel << "/!" << gateway;
|
||||
const meshtastic_ServiceEnvelope env = {.packet = const_cast<meshtastic_MeshPacket *>(p),
|
||||
.channel_id = const_cast<char *>(channel.c_str()),
|
||||
.gateway_id = const_cast<char *>(gateway.c_str())};
|
||||
uint8_t bytes[256];
|
||||
size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_ServiceEnvelope_msg, &env);
|
||||
mqttCallback(const_cast<char *>(topic.str().c_str()), bytes, numBytes);
|
||||
}
|
||||
static void restart()
|
||||
{
|
||||
if (mqtt != NULL) {
|
||||
delete mqtt;
|
||||
mqtt = unitTest = NULL;
|
||||
}
|
||||
mqtt = unitTest = new MQTTUnitTest();
|
||||
mqtt->start();
|
||||
|
||||
if (!moduleConfig.mqtt.enabled || moduleConfig.mqtt.proxy_to_client_enabled || *moduleConfig.mqtt.root) {
|
||||
loopUntil([] { return true; }); // Loop once
|
||||
return;
|
||||
}
|
||||
// Wait for MQTT to subscribe to all topics.
|
||||
TEST_ASSERT_TRUE(loopUntil(
|
||||
[] { return pubsub->subscriptions_.count("msh/2/e/test/+") && pubsub->subscriptions_.count("msh/2/e/PKI/+"); }));
|
||||
}
|
||||
PubSubClient &getPubSub() { return pubSub; }
|
||||
};
|
||||
|
||||
// Packets used in unit tests.
|
||||
const meshtastic_MeshPacket decoded = {
|
||||
.from = 1,
|
||||
.to = 2,
|
||||
.which_payload_variant = meshtastic_MeshPacket_decoded_tag,
|
||||
.decoded = {.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP, .has_bitfield = true, .bitfield = BITFIELD_OK_TO_MQTT_MASK},
|
||||
.id = 4,
|
||||
};
|
||||
const meshtastic_MeshPacket encrypted = {
|
||||
.from = 1,
|
||||
.to = 2,
|
||||
.which_payload_variant = meshtastic_MeshPacket_encrypted_tag,
|
||||
.encrypted = {.size = 0},
|
||||
.id = 3,
|
||||
};
|
||||
} // namespace
|
||||
|
||||
// Initialize mocks and configuration before running each test.
|
||||
void setUp(void)
|
||||
{
|
||||
moduleConfig.mqtt =
|
||||
meshtastic_ModuleConfig_MQTTConfig{.enabled = true, .map_reporting_enabled = true, .has_map_report_settings = true};
|
||||
channelFile.channels[0] = meshtastic_Channel{
|
||||
.index = 0,
|
||||
.has_settings = true,
|
||||
.settings = {.name = "test", .uplink_enabled = true, .downlink_enabled = true},
|
||||
.role = meshtastic_Channel_Role_PRIMARY,
|
||||
};
|
||||
channelFile.channels_count = 1;
|
||||
owner = meshtastic_User{.id = "!12345678"};
|
||||
myNodeInfo = meshtastic_MyNodeInfo{.my_node_num = 10};
|
||||
localPosition =
|
||||
meshtastic_Position{.has_latitude_i = true, .latitude_i = 7 * 1e7, .has_longitude_i = true, .longitude_i = 3 * 1e7};
|
||||
|
||||
router = mockRouter = new MockRouter();
|
||||
service = mockMeshService = new MockMeshService();
|
||||
routingModule = mockRoutingModule = new MockRoutingModule();
|
||||
MQTTUnitTest::restart();
|
||||
}
|
||||
|
||||
// Deinitialize all objects created in setUp.
|
||||
void tearDown(void)
|
||||
{
|
||||
delete unitTest;
|
||||
mqtt = unitTest = NULL;
|
||||
delete mockRoutingModule;
|
||||
routingModule = mockRoutingModule = NULL;
|
||||
delete mockMeshService;
|
||||
service = mockMeshService = NULL;
|
||||
delete mockRouter;
|
||||
router = mockRouter = NULL;
|
||||
}
|
||||
|
||||
// Test that the decoded MeshPacket is published when encryption_enabled = false.
|
||||
void test_sendDirectlyConnectedDecoded(void)
|
||||
{
|
||||
mqtt->onSend(encrypted, decoded, 0);
|
||||
|
||||
TEST_ASSERT_EQUAL(1, pubsub->published_.size());
|
||||
const auto &[topic, payload] = pubsub->published_.front();
|
||||
const DecodedServiceEnvelope &env = std::get<DecodedServiceEnvelope>(payload);
|
||||
TEST_ASSERT_EQUAL_STRING("msh/2/e/test/!12345678", topic.c_str());
|
||||
TEST_ASSERT_TRUE(env.validDecode);
|
||||
TEST_ASSERT_EQUAL(decoded.id, env.packet->id);
|
||||
}
|
||||
|
||||
// Test that the encrypted MeshPacket is published when encryption_enabled = true.
|
||||
void test_sendDirectlyConnectedEncrypted(void)
|
||||
{
|
||||
moduleConfig.mqtt.encryption_enabled = true;
|
||||
|
||||
mqtt->onSend(encrypted, decoded, 0);
|
||||
|
||||
TEST_ASSERT_EQUAL(1, pubsub->published_.size());
|
||||
const auto &[topic, payload] = pubsub->published_.front();
|
||||
const DecodedServiceEnvelope &env = std::get<DecodedServiceEnvelope>(payload);
|
||||
TEST_ASSERT_EQUAL_STRING("msh/2/e/test/!12345678", topic.c_str());
|
||||
TEST_ASSERT_TRUE(env.validDecode);
|
||||
TEST_ASSERT_EQUAL(encrypted.id, env.packet->id);
|
||||
}
|
||||
|
||||
// Verify that the decoded MeshPacket is proxied through the MeshService when encryption_enabled = false.
|
||||
void test_proxyToMeshServiceDecoded(void)
|
||||
{
|
||||
moduleConfig.mqtt.proxy_to_client_enabled = true;
|
||||
MQTTUnitTest::restart();
|
||||
|
||||
mqtt->onSend(encrypted, decoded, 0);
|
||||
|
||||
TEST_ASSERT_EQUAL(1, mockMeshService->messages_.size());
|
||||
const meshtastic_MqttClientProxyMessage &message = mockMeshService->messages_.front();
|
||||
TEST_ASSERT_EQUAL_STRING("msh/2/e/test/!12345678", message.topic);
|
||||
TEST_ASSERT_EQUAL(meshtastic_MqttClientProxyMessage_data_tag, message.which_payload_variant);
|
||||
const DecodedServiceEnvelope env(message.payload_variant.data.bytes, message.payload_variant.data.size);
|
||||
TEST_ASSERT_TRUE(env.validDecode);
|
||||
TEST_ASSERT_EQUAL(decoded.id, env.packet->id);
|
||||
}
|
||||
|
||||
// Verify that the encrypted MeshPacket is proxied through the MeshService when encryption_enabled = true.
|
||||
void test_proxyToMeshServiceEncrypted(void)
|
||||
{
|
||||
moduleConfig.mqtt.proxy_to_client_enabled = true;
|
||||
moduleConfig.mqtt.encryption_enabled = true;
|
||||
MQTTUnitTest::restart();
|
||||
|
||||
mqtt->onSend(encrypted, decoded, 0);
|
||||
|
||||
TEST_ASSERT_EQUAL(1, mockMeshService->messages_.size());
|
||||
const meshtastic_MqttClientProxyMessage &message = mockMeshService->messages_.front();
|
||||
TEST_ASSERT_EQUAL_STRING("msh/2/e/test/!12345678", message.topic);
|
||||
TEST_ASSERT_EQUAL(meshtastic_MqttClientProxyMessage_data_tag, message.which_payload_variant);
|
||||
const DecodedServiceEnvelope env(message.payload_variant.data.bytes, message.payload_variant.data.size);
|
||||
TEST_ASSERT_TRUE(env.validDecode);
|
||||
TEST_ASSERT_EQUAL(encrypted.id, env.packet->id);
|
||||
}
|
||||
|
||||
// A packet without the OK to MQTT bit set should not be published to a public server.
|
||||
void test_dontMqttMeOnPublicServer(void)
|
||||
{
|
||||
meshtastic_MeshPacket p = decoded;
|
||||
p.decoded.bitfield = 0;
|
||||
p.decoded.has_bitfield = 0;
|
||||
|
||||
mqtt->onSend(encrypted, p, 0);
|
||||
|
||||
TEST_ASSERT_TRUE(pubsub->published_.empty());
|
||||
}
|
||||
|
||||
// A packet without the OK to MQTT bit set should be published to a private server.
|
||||
void test_okToMqttOnPrivateServer(void)
|
||||
{
|
||||
// Cause a disconnect.
|
||||
pubsub->connected_ = false;
|
||||
pubsub->refuseConnection_ = true;
|
||||
TEST_ASSERT_TRUE(loopUntil([] { return !unitTest->getPubSub().connected(); }));
|
||||
|
||||
// Use 127.0.0.1 for the server's IP.
|
||||
pubsub->ipAddress_ = 0x7f000001;
|
||||
|
||||
// Reconnect.
|
||||
pubsub->refuseConnection_ = false;
|
||||
TEST_ASSERT_TRUE(loopUntil([] { return unitTest->getPubSub().connected(); }));
|
||||
|
||||
// Send the same packet as test_dontMqttMeOnPublicServer.
|
||||
meshtastic_MeshPacket p = decoded;
|
||||
p.decoded.bitfield = 0;
|
||||
p.decoded.has_bitfield = 0;
|
||||
|
||||
mqtt->onSend(encrypted, p, 0);
|
||||
|
||||
TEST_ASSERT_EQUAL(1, pubsub->published_.size());
|
||||
}
|
||||
|
||||
// Range tests messages are not uplinked to the default server.
|
||||
void test_noRangeTestAppOnDefaultServer(void)
|
||||
{
|
||||
meshtastic_MeshPacket p = decoded;
|
||||
p.decoded.portnum = meshtastic_PortNum_RANGE_TEST_APP;
|
||||
|
||||
mqtt->onSend(encrypted, p, 0);
|
||||
|
||||
TEST_ASSERT_TRUE(pubsub->published_.empty());
|
||||
}
|
||||
|
||||
// Detection sensor messages are not uplinked to the default server.
|
||||
void test_noDetectionSensorAppOnDefaultServer(void)
|
||||
{
|
||||
meshtastic_MeshPacket p = decoded;
|
||||
p.decoded.portnum = meshtastic_PortNum_DETECTION_SENSOR_APP;
|
||||
|
||||
mqtt->onSend(encrypted, p, 0);
|
||||
|
||||
TEST_ASSERT_TRUE(pubsub->published_.empty());
|
||||
}
|
||||
|
||||
// Test that a MeshPacket is queued while the MQTT server is disconnected.
|
||||
void test_sendQueued(void)
|
||||
{
|
||||
// Cause a disconnect.
|
||||
pubsub->connected_ = false;
|
||||
pubsub->refuseConnection_ = true;
|
||||
TEST_ASSERT_TRUE(loopUntil([] { return !unitTest->getPubSub().connected(); }));
|
||||
|
||||
// Send while disconnected.
|
||||
mqtt->onSend(encrypted, decoded, 0);
|
||||
TEST_ASSERT_EQUAL(1, unitTest->queueSize());
|
||||
TEST_ASSERT_TRUE(pubsub->published_.empty());
|
||||
TEST_ASSERT_FALSE(unitTest->getPubSub().connected());
|
||||
|
||||
// Allow reconnect to happen. Expect to see the packet published now.
|
||||
pubsub->refuseConnection_ = false;
|
||||
TEST_ASSERT_TRUE(loopUntil([] { return !pubsub->published_.empty(); }));
|
||||
|
||||
TEST_ASSERT_EQUAL(0, unitTest->queueSize());
|
||||
const auto &[topic, payload] = pubsub->published_.front();
|
||||
const DecodedServiceEnvelope &env = std::get<DecodedServiceEnvelope>(payload);
|
||||
TEST_ASSERT_EQUAL_STRING("msh/2/e/test/!12345678", topic.c_str());
|
||||
TEST_ASSERT_TRUE(env.validDecode);
|
||||
TEST_ASSERT_EQUAL(decoded.id, env.packet->id);
|
||||
}
|
||||
|
||||
// Verify reconnecting with the proxy enabled does not reconnect to a MQTT server.
|
||||
void test_reconnectProxyDoesNotReconnectMqtt(void)
|
||||
{
|
||||
moduleConfig.mqtt.proxy_to_client_enabled = true;
|
||||
MQTTUnitTest::restart();
|
||||
|
||||
mqtt->reconnect();
|
||||
|
||||
TEST_ASSERT_FALSE(pubsub->connected_);
|
||||
}
|
||||
|
||||
// Test receiving an empty MeshPacket on a subscribed topic.
|
||||
void test_receiveEmptyMeshPacket(void)
|
||||
{
|
||||
unitTest->publish(NULL);
|
||||
|
||||
TEST_ASSERT_TRUE(mockRouter->packets_.empty());
|
||||
TEST_ASSERT_TRUE(mockRoutingModule->ackNacks_.empty());
|
||||
}
|
||||
|
||||
// Test receiving a decoded MeshPacket on a subscribed topic.
|
||||
void test_receiveDecodedProto(void)
|
||||
{
|
||||
unitTest->publish(&decoded);
|
||||
|
||||
TEST_ASSERT_EQUAL(1, mockRouter->packets_.size());
|
||||
const meshtastic_MeshPacket &p = mockRouter->packets_.front();
|
||||
TEST_ASSERT_EQUAL(decoded.id, p.id);
|
||||
TEST_ASSERT_TRUE(p.via_mqtt);
|
||||
}
|
||||
|
||||
// Test receiving a decoded MeshPacket from the phone proxy.
|
||||
void test_receiveDecodedProtoFromProxy(void)
|
||||
{
|
||||
const meshtastic_ServiceEnvelope env = {
|
||||
.packet = const_cast<meshtastic_MeshPacket *>(&decoded), .channel_id = "test", .gateway_id = "!87654321"};
|
||||
meshtastic_MqttClientProxyMessage message = meshtastic_MqttClientProxyMessage_init_default;
|
||||
strcat(message.topic, "msh/2/e/test/!87654321");
|
||||
message.which_payload_variant = meshtastic_MqttClientProxyMessage_data_tag;
|
||||
message.payload_variant.data.size = pb_encode_to_bytes(
|
||||
message.payload_variant.data.bytes, sizeof(message.payload_variant.data.bytes), &meshtastic_ServiceEnvelope_msg, &env);
|
||||
|
||||
mqtt->onClientProxyReceive(message);
|
||||
|
||||
TEST_ASSERT_EQUAL(1, mockRouter->packets_.size());
|
||||
const meshtastic_MeshPacket &p = mockRouter->packets_.front();
|
||||
TEST_ASSERT_EQUAL(decoded.id, p.id);
|
||||
TEST_ASSERT_TRUE(p.via_mqtt);
|
||||
}
|
||||
|
||||
// Properly handles the case where the received message is empty.
|
||||
void test_receiveEmptyDataFromProxy(void)
|
||||
{
|
||||
meshtastic_MqttClientProxyMessage message = meshtastic_MqttClientProxyMessage_init_default;
|
||||
message.which_payload_variant = meshtastic_MqttClientProxyMessage_data_tag;
|
||||
|
||||
mqtt->onClientProxyReceive(message);
|
||||
|
||||
TEST_ASSERT_TRUE(mockRouter->packets_.empty());
|
||||
}
|
||||
|
||||
// Packets should be ignored if downlink is not enabled.
|
||||
void test_receiveWithoutChannelDownlink(void)
|
||||
{
|
||||
channelFile.channels[0].settings.downlink_enabled = false;
|
||||
|
||||
unitTest->publish(&decoded);
|
||||
|
||||
TEST_ASSERT_TRUE(mockRouter->packets_.empty());
|
||||
}
|
||||
|
||||
// Test receiving an encrypted MeshPacket on the PKI topic.
|
||||
void test_receiveEncryptedPKITopicToUs(void)
|
||||
{
|
||||
meshtastic_MeshPacket e = encrypted;
|
||||
e.to = myNodeInfo.my_node_num;
|
||||
|
||||
unitTest->publish(&e, "!87654321", "PKI");
|
||||
|
||||
TEST_ASSERT_EQUAL(1, mockRouter->packets_.size());
|
||||
const meshtastic_MeshPacket &p = mockRouter->packets_.front();
|
||||
TEST_ASSERT_EQUAL(encrypted.id, p.id);
|
||||
TEST_ASSERT_TRUE(p.via_mqtt);
|
||||
}
|
||||
|
||||
// Should ignore messages published to MQTT by this gateway.
|
||||
void test_receiveIgnoresOwnPublishedMessages(void)
|
||||
{
|
||||
unitTest->publish(&decoded, owner.id);
|
||||
|
||||
TEST_ASSERT_TRUE(mockRouter->packets_.empty());
|
||||
TEST_ASSERT_TRUE(mockRoutingModule->ackNacks_.empty());
|
||||
}
|
||||
|
||||
// Considers receiving one of our packets an acknowledgement of it being sent.
|
||||
void test_receiveAcksOwnSentMessages(void)
|
||||
{
|
||||
meshtastic_MeshPacket p = decoded;
|
||||
p.from = myNodeInfo.my_node_num;
|
||||
|
||||
unitTest->publish(&p, owner.id);
|
||||
|
||||
TEST_ASSERT_TRUE(mockRouter->packets_.empty());
|
||||
TEST_ASSERT_EQUAL(1, mockRoutingModule->ackNacks_.size());
|
||||
const auto &[err, to, idFrom, chIndex, hopLimit] = mockRoutingModule->ackNacks_.front();
|
||||
TEST_ASSERT_EQUAL(meshtastic_Routing_Error_NONE, err);
|
||||
TEST_ASSERT_EQUAL(myNodeInfo.my_node_num, to);
|
||||
TEST_ASSERT_EQUAL(p.id, idFrom);
|
||||
}
|
||||
|
||||
// Should ignore our own messages from MQTT that were heard by other nodes.
|
||||
void test_receiveIgnoresSentMessagesFromOthers(void)
|
||||
{
|
||||
meshtastic_MeshPacket p = decoded;
|
||||
p.from = myNodeInfo.my_node_num;
|
||||
|
||||
unitTest->publish(&p);
|
||||
|
||||
TEST_ASSERT_TRUE(mockRouter->packets_.empty());
|
||||
TEST_ASSERT_TRUE(mockRoutingModule->ackNacks_.empty());
|
||||
}
|
||||
|
||||
// Decoded MQTT messages should be ignored when encryption is enabled.
|
||||
void test_receiveIgnoresDecodedWhenEncryptionEnabled(void)
|
||||
{
|
||||
moduleConfig.mqtt.encryption_enabled = true;
|
||||
|
||||
unitTest->publish(&decoded);
|
||||
|
||||
TEST_ASSERT_TRUE(mockRouter->packets_.empty());
|
||||
}
|
||||
|
||||
// Non-encrypted messages for the Admin App should be ignored.
|
||||
void test_receiveIgnoresDecodedAdminApp(void)
|
||||
{
|
||||
meshtastic_MeshPacket p = decoded;
|
||||
p.decoded.portnum = meshtastic_PortNum_ADMIN_APP;
|
||||
|
||||
unitTest->publish(&p);
|
||||
|
||||
TEST_ASSERT_TRUE(mockRouter->packets_.empty());
|
||||
}
|
||||
|
||||
// Only the same fields that are transmitted over LoRa should be set in MQTT messages.
|
||||
void test_receiveIgnoresUnexpectedFields(void)
|
||||
{
|
||||
meshtastic_MeshPacket input = decoded;
|
||||
input.rx_snr = 10;
|
||||
input.rx_rssi = 20;
|
||||
|
||||
unitTest->publish(&input);
|
||||
|
||||
TEST_ASSERT_EQUAL(1, mockRouter->packets_.size());
|
||||
const meshtastic_MeshPacket &p = mockRouter->packets_.front();
|
||||
TEST_ASSERT_EQUAL(0, p.rx_snr);
|
||||
TEST_ASSERT_EQUAL(0, p.rx_rssi);
|
||||
}
|
||||
|
||||
// Messages with an invalid hop_limit are ignored.
|
||||
void test_receiveIgnoresInvalidHopLimit(void)
|
||||
{
|
||||
meshtastic_MeshPacket p = decoded;
|
||||
p.hop_limit = 10;
|
||||
|
||||
unitTest->publish(&p);
|
||||
|
||||
TEST_ASSERT_TRUE(mockRouter->packets_.empty());
|
||||
}
|
||||
|
||||
// Publishing to a text channel.
|
||||
void test_publishTextMessageDirect(void)
|
||||
{
|
||||
TEST_ASSERT_TRUE(mqtt->publish(MockPubSubServer::kTextTopic, "payload", 0));
|
||||
|
||||
TEST_ASSERT_EQUAL(1, pubsub->published_.size());
|
||||
const auto &[topic, payload] = pubsub->published_.front();
|
||||
TEST_ASSERT_EQUAL_STRING("payload", std::get<std::string>(payload).c_str());
|
||||
}
|
||||
|
||||
// Publishing to a text channel via the MQTT client proxy.
|
||||
void test_publishTextMessageWithProxy(void)
|
||||
{
|
||||
moduleConfig.mqtt.proxy_to_client_enabled = true;
|
||||
|
||||
TEST_ASSERT_TRUE(mqtt->publish(MockPubSubServer::kTextTopic, "payload", 0));
|
||||
|
||||
TEST_ASSERT_EQUAL(1, mockMeshService->messages_.size());
|
||||
const meshtastic_MqttClientProxyMessage &message = mockMeshService->messages_.front();
|
||||
TEST_ASSERT_EQUAL_STRING(MockPubSubServer::kTextTopic, message.topic);
|
||||
TEST_ASSERT_EQUAL(meshtastic_MqttClientProxyMessage_text_tag, message.which_payload_variant);
|
||||
TEST_ASSERT_EQUAL_STRING("payload", message.payload_variant.text);
|
||||
}
|
||||
|
||||
// Helper method to verify the expected latitude/longitude was received.
|
||||
void verifyLatLong(const DecodedServiceEnvelope &env, uint32_t latitude, uint32_t longitude)
|
||||
{
|
||||
TEST_ASSERT_TRUE(env.validDecode);
|
||||
const meshtastic_MeshPacket &p = *env.packet;
|
||||
TEST_ASSERT_EQUAL(NODENUM_BROADCAST, p.to);
|
||||
TEST_ASSERT_EQUAL(meshtastic_MeshPacket_decoded_tag, p.which_payload_variant);
|
||||
TEST_ASSERT_EQUAL(meshtastic_PortNum_MAP_REPORT_APP, p.decoded.portnum);
|
||||
|
||||
meshtastic_MapReport mapReport;
|
||||
TEST_ASSERT_TRUE(
|
||||
pb_decode_from_bytes(p.decoded.payload.bytes, p.decoded.payload.size, &meshtastic_MapReport_msg, &mapReport));
|
||||
TEST_ASSERT_EQUAL(latitude, mapReport.latitude_i);
|
||||
TEST_ASSERT_EQUAL(longitude, mapReport.longitude_i);
|
||||
}
|
||||
|
||||
// Map reporting defaults to an imprecise location.
|
||||
void test_reportToMapDefaultImprecise(void)
|
||||
{
|
||||
unitTest->reportToMap();
|
||||
|
||||
TEST_ASSERT_EQUAL(1, pubsub->published_.size());
|
||||
const auto &[topic, payload] = pubsub->published_.front();
|
||||
TEST_ASSERT_EQUAL_STRING("msh/2/map/", topic.c_str());
|
||||
verifyLatLong(std::get<DecodedServiceEnvelope>(payload), 70123520, 30015488);
|
||||
}
|
||||
|
||||
// Precise location is reported when configured.
|
||||
void test_reportToMapPrecise(void)
|
||||
{
|
||||
unitTest->reportToMap(/*precision=*/32);
|
||||
|
||||
TEST_ASSERT_EQUAL(1, pubsub->published_.size());
|
||||
const auto &[topic, payload] = pubsub->published_.front();
|
||||
TEST_ASSERT_EQUAL_STRING("msh/2/map/", topic.c_str());
|
||||
verifyLatLong(std::get<DecodedServiceEnvelope>(payload), localPosition.latitude_i, localPosition.longitude_i);
|
||||
}
|
||||
|
||||
// Location is sent over the phone proxy.
|
||||
void test_reportToMapPreciseProxied(void)
|
||||
{
|
||||
moduleConfig.mqtt.proxy_to_client_enabled = true;
|
||||
MQTTUnitTest::restart();
|
||||
|
||||
unitTest->reportToMap(/*precision=*/32);
|
||||
|
||||
TEST_ASSERT_EQUAL(1, mockMeshService->messages_.size());
|
||||
const meshtastic_MqttClientProxyMessage &message = mockMeshService->messages_.front();
|
||||
TEST_ASSERT_EQUAL_STRING("msh/2/map/", message.topic);
|
||||
TEST_ASSERT_EQUAL(meshtastic_MqttClientProxyMessage_data_tag, message.which_payload_variant);
|
||||
const DecodedServiceEnvelope env(message.payload_variant.data.bytes, message.payload_variant.data.size);
|
||||
verifyLatLong(env, localPosition.latitude_i, localPosition.longitude_i);
|
||||
}
|
||||
|
||||
// No location is reported when the precision is invalid.
|
||||
void test_reportToMapInvalidPrecision(void)
|
||||
{
|
||||
unitTest->reportToMap(/*precision=*/0);
|
||||
|
||||
TEST_ASSERT_TRUE(pubsub->published_.empty());
|
||||
}
|
||||
|
||||
// isUsingDefaultServer returns true when using the default server.
|
||||
void test_usingDefaultServer(void)
|
||||
{
|
||||
TEST_ASSERT_TRUE(mqtt->isUsingDefaultServer());
|
||||
}
|
||||
|
||||
// isUsingDefaultServer returns true when using the default server and a port.
|
||||
void test_usingDefaultServerWithPort(void)
|
||||
{
|
||||
std::string server = default_mqtt_address;
|
||||
server += ":1883";
|
||||
strcpy(moduleConfig.mqtt.address, server.c_str());
|
||||
MQTTUnitTest::restart();
|
||||
|
||||
TEST_ASSERT_TRUE(mqtt->isUsingDefaultServer());
|
||||
}
|
||||
|
||||
// isUsingDefaultServer returns true when using the default server and invalid port.
|
||||
void test_usingDefaultServerWithInvalidPort(void)
|
||||
{
|
||||
std::string server = default_mqtt_address;
|
||||
server += ":invalid";
|
||||
strcpy(moduleConfig.mqtt.address, server.c_str());
|
||||
MQTTUnitTest::restart();
|
||||
|
||||
TEST_ASSERT_TRUE(mqtt->isUsingDefaultServer());
|
||||
}
|
||||
|
||||
// isUsingDefaultServer returns false when not using the default server.
|
||||
void test_usingCustomServer(void)
|
||||
{
|
||||
strcpy(moduleConfig.mqtt.address, "custom");
|
||||
MQTTUnitTest::restart();
|
||||
|
||||
TEST_ASSERT_FALSE(mqtt->isUsingDefaultServer());
|
||||
}
|
||||
|
||||
// Test that isEnabled returns true the MQTT module is enabled.
|
||||
void test_enabled(void)
|
||||
{
|
||||
TEST_ASSERT_TRUE(mqtt->isEnabled());
|
||||
}
|
||||
|
||||
// Test that isEnabled returns false the MQTT module not enabled.
|
||||
void test_disabled(void)
|
||||
{
|
||||
moduleConfig.mqtt.enabled = false;
|
||||
MQTTUnitTest::restart();
|
||||
|
||||
TEST_ASSERT_FALSE(mqtt->isEnabled());
|
||||
}
|
||||
|
||||
// Subscriptions contain the moduleConfig.mqtt.root prefix.
|
||||
void test_customMqttRoot(void)
|
||||
{
|
||||
strcpy(moduleConfig.mqtt.root, "custom");
|
||||
MQTTUnitTest::restart();
|
||||
|
||||
TEST_ASSERT_TRUE(loopUntil(
|
||||
[] { return pubsub->subscriptions_.count("custom/2/e/test/+") && pubsub->subscriptions_.count("custom/2/e/PKI/+"); }));
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
initializeTestEnvironment();
|
||||
const std::unique_ptr<MockNodeDB> mockNodeDB(new MockNodeDB());
|
||||
nodeDB = mockNodeDB.get();
|
||||
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_sendDirectlyConnectedDecoded);
|
||||
RUN_TEST(test_sendDirectlyConnectedEncrypted);
|
||||
RUN_TEST(test_proxyToMeshServiceDecoded);
|
||||
RUN_TEST(test_proxyToMeshServiceEncrypted);
|
||||
RUN_TEST(test_dontMqttMeOnPublicServer);
|
||||
RUN_TEST(test_okToMqttOnPrivateServer);
|
||||
RUN_TEST(test_noRangeTestAppOnDefaultServer);
|
||||
RUN_TEST(test_noDetectionSensorAppOnDefaultServer);
|
||||
RUN_TEST(test_sendQueued);
|
||||
RUN_TEST(test_reconnectProxyDoesNotReconnectMqtt);
|
||||
RUN_TEST(test_receiveEmptyMeshPacket);
|
||||
RUN_TEST(test_receiveDecodedProto);
|
||||
RUN_TEST(test_receiveDecodedProtoFromProxy);
|
||||
RUN_TEST(test_receiveEmptyDataFromProxy);
|
||||
RUN_TEST(test_receiveWithoutChannelDownlink);
|
||||
RUN_TEST(test_receiveEncryptedPKITopicToUs);
|
||||
RUN_TEST(test_receiveIgnoresOwnPublishedMessages);
|
||||
RUN_TEST(test_receiveAcksOwnSentMessages);
|
||||
RUN_TEST(test_receiveIgnoresSentMessagesFromOthers);
|
||||
RUN_TEST(test_receiveIgnoresDecodedWhenEncryptionEnabled);
|
||||
RUN_TEST(test_receiveIgnoresDecodedAdminApp);
|
||||
RUN_TEST(test_receiveIgnoresUnexpectedFields);
|
||||
RUN_TEST(test_receiveIgnoresInvalidHopLimit);
|
||||
RUN_TEST(test_publishTextMessageDirect);
|
||||
RUN_TEST(test_publishTextMessageWithProxy);
|
||||
RUN_TEST(test_reportToMapDefaultImprecise);
|
||||
RUN_TEST(test_reportToMapPrecise);
|
||||
RUN_TEST(test_reportToMapPreciseProxied);
|
||||
RUN_TEST(test_reportToMapInvalidPrecision);
|
||||
RUN_TEST(test_usingDefaultServer);
|
||||
RUN_TEST(test_usingDefaultServerWithPort);
|
||||
RUN_TEST(test_usingDefaultServerWithInvalidPort);
|
||||
RUN_TEST(test_usingCustomServer);
|
||||
RUN_TEST(test_enabled);
|
||||
RUN_TEST(test_disabled);
|
||||
RUN_TEST(test_customMqttRoot);
|
||||
exit(UNITY_END());
|
||||
}
|
||||
#else
|
||||
void setup()
|
||||
{
|
||||
initializeTestEnvironment();
|
||||
LOG_WARN("This test requires the ARCH_PORTDUINO variant of WiFiClient");
|
||||
UNITY_BEGIN();
|
||||
UNITY_END();
|
||||
}
|
||||
#endif
|
||||
void loop() {}
|
Loading…
Reference in New Issue
Block a user