mirror of
https://github.com/meshtastic/web.git
synced 2025-12-24 08:08:20 -05:00
Compare commits
143 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0108494bd8 | ||
|
|
3ecea59da8 | ||
|
|
0b2fdb6439 | ||
|
|
94220e729b | ||
|
|
020e9d6b63 | ||
|
|
6a7be99a6a | ||
|
|
5debdb4689 | ||
|
|
057043864f | ||
|
|
8918603f96 | ||
|
|
68cea2e858 | ||
|
|
06cc266acd | ||
|
|
295755ec4c | ||
|
|
b99057df69 | ||
|
|
390b46f026 | ||
|
|
94d51912e6 | ||
|
|
ac28fbc53d | ||
|
|
648a9c3640 | ||
|
|
7f21b3b531 | ||
|
|
d1597ce00f | ||
|
|
0c8fcec23d | ||
|
|
2e03b4a413 | ||
|
|
a00cb2098b | ||
|
|
6aeaed988e | ||
|
|
7c9013a217 | ||
|
|
ab0308701c | ||
|
|
e13d543e73 | ||
|
|
d8ad0efcdf | ||
|
|
2e60af1e29 | ||
|
|
1af1295b8f | ||
|
|
a1a646983e | ||
|
|
4386854e9d | ||
|
|
e821a65dd4 | ||
|
|
3392c9db08 | ||
|
|
e53edfb00f | ||
|
|
1df63bb84b | ||
|
|
679f7986cc | ||
|
|
f375911c4a | ||
|
|
68ad535259 | ||
|
|
0cf677c8d5 | ||
|
|
ff02b1455d | ||
|
|
cdad811295 | ||
|
|
fc1e327b74 | ||
|
|
af2fac1465 | ||
|
|
15daa0270c | ||
|
|
80c9306db3 | ||
|
|
c3f073a380 | ||
|
|
fe35376450 | ||
|
|
64d1f0f7aa | ||
|
|
a61bb2dfb6 | ||
|
|
acfa7d2269 | ||
|
|
8f62fb7877 | ||
|
|
94409dd8e1 | ||
|
|
ac25326d24 | ||
|
|
34bdbe4709 | ||
|
|
8d98aca496 | ||
|
|
ec9ad1d309 | ||
|
|
a97195c57e | ||
|
|
22a1d92191 | ||
|
|
35596249b4 | ||
|
|
566c588377 | ||
|
|
9ab49d1431 | ||
|
|
f5a7132421 | ||
|
|
c66b315623 | ||
|
|
76ba4e917e | ||
|
|
d0da79bf56 | ||
|
|
66bf3f1578 | ||
|
|
6a358883f9 | ||
|
|
86622f83d0 | ||
|
|
66c3945bc4 | ||
|
|
38cb002ce8 | ||
|
|
36443fd838 | ||
|
|
8cc451546d | ||
|
|
1bdd923365 | ||
|
|
d5610de826 | ||
|
|
aaf85943a8 | ||
|
|
37ad0ac9eb | ||
|
|
eb2791e206 | ||
|
|
119cde65c7 | ||
|
|
cb66c22974 | ||
|
|
96c5d0d86f | ||
|
|
2d98f57b65 | ||
|
|
3e2fe721d3 | ||
|
|
52fd2f712c | ||
|
|
ad6b9f470f | ||
|
|
765f672bf5 | ||
|
|
67a5d75283 | ||
|
|
4ff010e14b | ||
|
|
1214b6ecf6 | ||
|
|
aababb8075 | ||
|
|
3b0406c5af | ||
|
|
6772f066ee | ||
|
|
d8df40816f | ||
|
|
f9bd61af49 | ||
|
|
bcdda8b751 | ||
|
|
dcb44d27fe | ||
|
|
799283fd46 | ||
|
|
d0f4939a4d | ||
|
|
d66f10e715 | ||
|
|
e421eb4244 | ||
|
|
ce16bf5aa4 | ||
|
|
3582499a3c | ||
|
|
1946000d14 | ||
|
|
01fa030ef9 | ||
|
|
90cf136b8c | ||
|
|
ee131c3681 | ||
|
|
85accf7c25 | ||
|
|
91426a89e5 | ||
|
|
d7e492cdc9 | ||
|
|
c5cad1dca6 | ||
|
|
85aa7a37b9 | ||
|
|
b87fad23d8 | ||
|
|
68ec7ee5d8 | ||
|
|
e33463b371 | ||
|
|
d4b80cf90b | ||
|
|
d7e32e9b03 | ||
|
|
449fb3ac36 | ||
|
|
1de92cd2e9 | ||
|
|
fdd3e7f971 | ||
|
|
6eece7172a | ||
|
|
5b2c25d8ee | ||
|
|
40c2bfa535 | ||
|
|
a10023fad6 | ||
|
|
0a1afa988e | ||
|
|
97b884f119 | ||
|
|
eb9b7159d4 | ||
|
|
65a53388bb | ||
|
|
2ca3eb5685 | ||
|
|
a4c817cd30 | ||
|
|
d5b03c09db | ||
|
|
0b9ebade38 | ||
|
|
ee1758a548 | ||
|
|
a2a45ac898 | ||
|
|
59d172765d | ||
|
|
d453ff809a | ||
|
|
ed0a99dbd9 | ||
|
|
32f31cb502 | ||
|
|
176d554ef9 | ||
|
|
2735c37fad | ||
|
|
f04ec36faf | ||
|
|
28f0ca4337 | ||
|
|
1dbf0b07b6 | ||
|
|
27ed4e58bd | ||
|
|
a7f56c0bd5 |
13
.devcontainer/devcontainer.json
Normal file
13
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "meshtastic-web",
|
||||
"image": "mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm",
|
||||
"features": {
|
||||
"ghcr.io/r3dpoint/devcontainer-features/tailwindcss-standalone-cli:1": {
|
||||
"version": "latest"
|
||||
},
|
||||
"ghcr.io/devcontainers-extra/features/pnpm:2": {
|
||||
"version": "latest"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
81
.github/workflows/ci.yml
vendored
81
.github/workflows/ci.yml
vendored
@@ -16,51 +16,64 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: latest
|
||||
|
||||
- name: Setup Deno
|
||||
uses: denoland/setup-deno@v2
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
deno-version: v2.x
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||
|
||||
- name: Cache Deno dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/deno
|
||||
key: ${{ runner.os }}-deno-${{ hashFiles('**/deno.lock', '**/package.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-deno-
|
||||
- name: Prewarm & Install (workspace)
|
||||
run: |
|
||||
set -euo pipefail
|
||||
pnpm fetch
|
||||
pnpm install --frozen-lockfile --offline
|
||||
|
||||
- name: Cache pnpm dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.pnpm-store
|
||||
packages/web/node_modules
|
||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-
|
||||
|
||||
- name: Build All Packages
|
||||
- name: Build All Packages (with exclusions)
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
for pkg_dir in packages/*/; do
|
||||
pkg_dir=${pkg_dir%/} # Remove trailing slash
|
||||
echo "🔍 Inspecting $pkg_dir..."
|
||||
# List packages to exclude (full paths under repo root)
|
||||
EXCLUDED_DIRS=("packages/protobufs" "packages/transport-deno" "packages/ui" "pacakges/web")
|
||||
|
||||
if [[ -f "$pkg_dir/package.json" ]] && [[ "$pkg_dir" != "packages/web" ]]; then
|
||||
echo "🔧 Building with pnpm: $pkg_dir"
|
||||
(cd "$pkg_dir" && pnpm install && pnpm run build:npm)
|
||||
else
|
||||
echo "⚠️ Skipping $pkg_dir (web package or no package.json)"
|
||||
is_excluded() {
|
||||
local dir="$1"
|
||||
for ex in "${EXCLUDED_DIRS[@]}"; do
|
||||
if [[ "$dir" == "$ex" ]]; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
for pkg_dir in packages/*/; do
|
||||
pkg_dir="${pkg_dir%/}" # trim trailing slash
|
||||
|
||||
# Must be a directory with a package.json
|
||||
if [[ ! -f "$pkg_dir/package.json" ]]; then
|
||||
echo "⚠️ Skipping $pkg_dir (no package.json)"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Allow for exclusions
|
||||
if is_excluded "$pkg_dir"; then
|
||||
echo "🚫 Skipping $pkg_dir (excluded)"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Optionally skip Deno-first packages automatically
|
||||
if [[ -f "$pkg_dir/deno.json" || -f "$pkg_dir/deno.jsonc" ]]; then
|
||||
echo "🦕 Skipping $pkg_dir (deno project)"
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "🔧 Building: $pkg_dir"
|
||||
# No per-package install needed; workspace install already done
|
||||
pnpm -C "$pkg_dir" run build:npm
|
||||
done
|
||||
|
||||
24
.github/workflows/inactive-issue.yml
vendored
Normal file
24
.github/workflows/inactive-issue.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
name: Close inactive issues
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 */4 * * *" # every 4 hours
|
||||
workflow_dispatch: # allow manual runs
|
||||
jobs:
|
||||
close-issues:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@v10
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
days-before-issue-stale: 60
|
||||
days-before-issue-close: 14
|
||||
stale-issue-label: "stale"
|
||||
stale-issue-message: "This issue is stale because it has been open for 60 days with no activity."
|
||||
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
|
||||
days-before-pr-stale: -1
|
||||
days-before-pr-close: -1
|
||||
only-issue-labels: "Bug"
|
||||
remove-stale-when-updated: true
|
||||
117
.github/workflows/nightly.yml
vendored
117
.github/workflows/nightly.yml
vendored
@@ -1,101 +1,120 @@
|
||||
name: "Nightly Release"
|
||||
name: Nightly Release
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 5 * * *" # Run every day at 5am UTC
|
||||
- cron: "0 5 * * *" # 05:00 UTC daily
|
||||
workflow_dispatch: {} # allow manual runs too
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
env:
|
||||
REGISTRY_IMAGE: ghcr.io/${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
nightly-build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: latest
|
||||
|
||||
- name: Cache pnpm dependencies
|
||||
uses: actions/cache@v4
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
path: |
|
||||
~/.pnpm-store
|
||||
packages/web/node_modules
|
||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('packages/web/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||
|
||||
# - name: Run tests
|
||||
# working-directory: packages/web
|
||||
# run: deno task test
|
||||
- name: Install dependencies (root)
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Install Dependencies
|
||||
working-directory: packages/web
|
||||
run: pnpm install
|
||||
- name: Run tests
|
||||
run: pnpm run test
|
||||
|
||||
- name: Build Package
|
||||
- name: Build web package
|
||||
working-directory: packages/web
|
||||
run: pnpm run build
|
||||
|
||||
- name: Package Output
|
||||
- name: Package output
|
||||
working-directory: packages/web
|
||||
run: pnpm run package
|
||||
|
||||
- name: Archive compressed build
|
||||
- name: Upload compressed build (artifact)
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build
|
||||
name: web-build-nightly
|
||||
path: packages/web/dist/build.tar
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Get latest release version
|
||||
id: get_release
|
||||
- name: Compute tags and labels
|
||||
id: meta
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
LATEST_TAG=$(curl -sL \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \
|
||||
https://api.github.com/repos/${{ github.repository }}/releases/latest | jq -r ".tag_name")
|
||||
# Fallback to a default if no release is found
|
||||
if [ -z "$LATEST_TAG" ]; then
|
||||
LATEST_TAG="2.6.0"
|
||||
set -euo pipefail
|
||||
DATE="$(date -u +%Y%m%d)"
|
||||
ISO_CREATED="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
SHORTSHA="$(git rev-parse --short=12 HEAD)"
|
||||
|
||||
# Try to use latest release tag if it exists; fallback to package version; else date
|
||||
LATEST_TAG="$(gh release view --json tagName --jq .tagName 2>/dev/null || true)"
|
||||
if [ -z "$LATEST_TAG" ] && [ -f packages/web/package.json ]; then
|
||||
LATEST_TAG="v$(jq -r .version packages/web/package.json)"
|
||||
fi
|
||||
echo "tag=${LATEST_TAG}" >> $GITHUB_OUTPUT
|
||||
|
||||
if [ -n "${LATEST_TAG:-}" ] && [ "$LATEST_TAG" != "vnull" ]; then
|
||||
IMMUTABLE="nightly-${LATEST_TAG}-${SHORTSHA}"
|
||||
else
|
||||
IMMUTABLE="nightly-${DATE}-${SHORTSHA}"
|
||||
fi
|
||||
|
||||
# Outputs
|
||||
echo "moving_tag=nightly" >> "$GITHUB_OUTPUT"
|
||||
echo "immutable_tag=${IMMUTABLE}" >> "$GITHUB_OUTPUT"
|
||||
echo "all_tags=nightly ${IMMUTABLE}" >> "$GITHUB_OUTPUT"
|
||||
echo "created=${ISO_CREATED}" >> "$GITHUB_OUTPUT"
|
||||
echo "Resolved tags: nightly and ${IMMUTABLE}"
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Buildah Build
|
||||
- name: Build Container Image (multi-arch)
|
||||
id: build-container
|
||||
uses: redhat-actions/buildah-build@v2
|
||||
with:
|
||||
containerfiles: |
|
||||
./packages/web/infra/Containerfile
|
||||
image: ${{ github.event.repository.full_name }}
|
||||
tags: nightly-${{ steps.get_release.outputs.tag }}-${{ github.sha }}
|
||||
|
||||
|
||||
image: ${{ env.REGISTRY_IMAGE }}
|
||||
tags: |
|
||||
${{ steps.meta.outputs.moving_tag }}
|
||||
${{ steps.meta.outputs.immutable_tag }}
|
||||
oci: true
|
||||
platforms: linux/amd64, linux/arm64
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
labels: |
|
||||
org.opencontainers.image.source=${{ github.repository }}
|
||||
org.opencontainers.image.revision=${{ github.sha }}
|
||||
org.opencontainers.image.created=${{ steps.meta.outputs.created }}
|
||||
|
||||
- name: Push To Registry
|
||||
- name: Push To GHCR
|
||||
id: push-to-registry
|
||||
uses: redhat-actions/push-to-registry@v2
|
||||
with:
|
||||
image: ${{ steps.build-container.outputs.image }}
|
||||
tags: ${{ steps.build-container.outputs.tags }}
|
||||
# Push the same tags used at build time:
|
||||
tags: ${{ steps.meta.outputs.all_tags }}
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Print image url
|
||||
run: echo "Image pushed to ${{ steps.push-to-registry.outputs.registry-paths }}"
|
||||
|
||||
- name: Print image URLs
|
||||
run: |
|
||||
echo "Moving tag: ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.moving_tag }}"
|
||||
echo "Immutable tag: ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.immutable_tag }}"
|
||||
|
||||
55
.github/workflows/pr.yml
vendored
55
.github/workflows/pr.yml
vendored
@@ -1,49 +1,52 @@
|
||||
name: Pull Request CI
|
||||
|
||||
on: pull_request
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: pr-${{ github.event.pull_request.number }}-ci
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CI: true
|
||||
|
||||
jobs:
|
||||
build-and-package:
|
||||
runs-on: ubuntu-latest
|
||||
# Skip for draft PRs; remove this line if you want to run on drafts too
|
||||
if: ${{ github.event.pull_request.draft == false }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: latest
|
||||
|
||||
- name: Install Dependencies
|
||||
# Commands will run from 'packages/web'
|
||||
working-directory: packages/web
|
||||
run: pnpm install
|
||||
|
||||
- name: Cache pnpm dependencies
|
||||
uses: actions/cache@v4
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
path: |
|
||||
~/.pnpm-store
|
||||
packages/web/node_modules
|
||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('packages/web/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Run linter
|
||||
run: pnpm run lint
|
||||
|
||||
- name: Check formatter
|
||||
- name: Check formatter
|
||||
run: pnpm run check
|
||||
|
||||
- name: Build Package
|
||||
working-directory: packages/web
|
||||
run: pnpm run build
|
||||
- name: Run tests
|
||||
run: pnpm run test
|
||||
|
||||
- name: Build web package
|
||||
run: pnpm --filter "./packages/web" run build
|
||||
|
||||
176
.github/workflows/release-packages.yml
vendored
176
.github/workflows/release-packages.yml
vendored
@@ -4,83 +4,145 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
packages:
|
||||
description: 'Packages to release (comma-separated, or "all" for all packages)'
|
||||
description: 'Packages to release (comma-separated, or "all")'
|
||||
required: false
|
||||
default: 'all'
|
||||
bump:
|
||||
description: 'Semver bump (patch | minor | major)'
|
||||
required: false
|
||||
default: 'patch'
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write # we commit the bumped versions back
|
||||
id-token: write # required for JSR OIDC
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# --- Setup Node.js and pnpm ---
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: latest
|
||||
|
||||
# --- Setup Deno ---
|
||||
- name: Setup Deno
|
||||
uses: denoland/setup-deno@v2
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
deno-version: v2.x
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
# --- Cache pnpm Dependencies ---
|
||||
- name: Cache pnpm Dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.pnpm-store
|
||||
packages/web/node_modules
|
||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
# --- Cache Deno Dependencies ---
|
||||
- name: Cache Deno Dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/deno
|
||||
key: ${{ runner.os }}-deno-${{ hashFiles('**/deno.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-deno-
|
||||
|
||||
- name: Configure pnpm registry
|
||||
run: pnpm config set registry https://registry.npmjs.org/
|
||||
|
||||
- name: Configure pnpm auth
|
||||
run: pnpm config set //registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}
|
||||
|
||||
- name: Publish packages to npm and JSR
|
||||
- name: Resolve package list
|
||||
id: pkgs
|
||||
shell: bash
|
||||
run: |
|
||||
for dir in packages/*; do
|
||||
echo "Processing $dir"
|
||||
set -euo pipefail
|
||||
if [ "${{ github.event.inputs.packages }}" = "all" ] || [ -z "${{ github.event.inputs.packages }}" ]; then
|
||||
mapfile -t TARGETS < <(ls -d packages/* | grep -v 'packages/web')
|
||||
else
|
||||
IFS=',' read -ra TARGETS <<< "${{ github.event.inputs.packages }}"
|
||||
TARGETS=("${TARGETS[@]/#/packages/}")
|
||||
fi
|
||||
printf '%s\n' "${TARGETS[@]}" | paste -sd, - > targets.txt
|
||||
echo "list=$(cat targets.txt)" >> "$GITHUB_OUTPUT"
|
||||
echo "Targets: $(cat targets.txt)"
|
||||
|
||||
cd $dir
|
||||
|
||||
# Build and publish to npm if package.json exists
|
||||
if [ -f "package.json" ]; then
|
||||
echo "Building and publishing $dir to npm..."
|
||||
pnpm run build:npm
|
||||
pnpm run publish:npm || echo "npm publish failed for $dir"
|
||||
- name: Bump package.json versions (no git tag)
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
BUMP="${{ github.event.inputs.bump }}"
|
||||
IFS=',' read -ra TARGETS <<< "${{ steps.pkgs.outputs.list }}"
|
||||
for dir in "${TARGETS[@]}"; do
|
||||
if [ -f "$dir/package.json" ]; then
|
||||
echo "Bumping $dir -> $BUMP"
|
||||
(cd "$dir" && npm version "$BUMP" --no-git-tag-version --allow-same-version)
|
||||
fi
|
||||
|
||||
pnpm run prepare:jsr
|
||||
|
||||
# Publish to JSR if jsr.json exists
|
||||
if [ -f "jsr.json" ]; then
|
||||
echo "Publishing $dir to jsr..."
|
||||
deno publish || echo "JSR publish failed for $dir"
|
||||
fi
|
||||
|
||||
cd - > /dev/null
|
||||
done
|
||||
|
||||
|
||||
- name: Generate jsr.json from package.json (pkg-to-jsr)
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
IFS=',' read -ra TARGETS <<< "${{ steps.pkgs.outputs.list }}"
|
||||
for dir in "${TARGETS[@]}"; do
|
||||
if [ -f "$dir/package.json" ]; then
|
||||
echo "Generating jsr.json for $dir"
|
||||
pnpm dlx pkg-to-jsr --root "$dir"
|
||||
# # Optional: show result
|
||||
# jq -C . "$dir/jsr.json" || cat "$dir/jsr.json"
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
- name: Commit version bumps
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git add packages/*/package.json packages/*/jsr.json 2>/dev/null || true
|
||||
if ! git diff --cached --quiet; then
|
||||
git checkout -b version_bump
|
||||
git commit -m "chore(release): bump package versions (${{ github.event.inputs.bump }})"
|
||||
git push -u origin version_bump
|
||||
gh pr create --title "Bump package versions" --body "Bump versions of NPM and JSR packages" --base main --head version_bump
|
||||
echo "Pushed to branch version_bump"
|
||||
else
|
||||
echo "No changes to commit."
|
||||
fi
|
||||
|
||||
- name: Build selected packages
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
IFS=',' read -ra TARGETS <<< "${{ steps.pkgs.outputs.list }}"
|
||||
for dir in "${TARGETS[@]}"; do
|
||||
echo "Building $dir"
|
||||
pnpm --filter "./$dir" run build
|
||||
done
|
||||
|
||||
- name: Publish to JSR (OIDC)
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
IFS=',' read -ra TARGETS <<< "${{ steps.pkgs.outputs.list }}"
|
||||
for dir in "${TARGETS[@]}"; do
|
||||
if [ -f "$dir/jsr.json" ]; then
|
||||
echo "Publishing $dir to JSR via OIDC…"
|
||||
( cd "$dir"
|
||||
[ -d dist ] || pnpm run build
|
||||
npx --yes jsr publish
|
||||
)
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Configure npm auth
|
||||
run: |
|
||||
pnpm config set //registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}
|
||||
pnpm config set registry https://registry.npmjs.org/
|
||||
|
||||
- name: Publish to npm
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
IFS=',' read -ra TARGETS <<< "${{ steps.pkgs.outputs.list }}"
|
||||
for dir in "${TARGETS[@]}"; do
|
||||
if [ -f "$dir/package.json" ]; then
|
||||
echo "Publishing $dir to npm…"
|
||||
( cd "$dir"
|
||||
[ -d dist ] || pnpm run build
|
||||
npm publish --access public
|
||||
)
|
||||
fi
|
||||
done
|
||||
|
||||
143
.github/workflows/release-protobufs.yml
vendored
Normal file
143
.github/workflows/release-protobufs.yml
vendored
Normal file
@@ -0,0 +1,143 @@
|
||||
name: Create Protobuf Release for JSR
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Version to publish (e.g. v1.2.3). Used when manually dispatching."
|
||||
required: true
|
||||
type: string
|
||||
dry_run:
|
||||
description: "Don't actually publish to JSR (passes --dry-run)."
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
permissions: write-all
|
||||
|
||||
env:
|
||||
PROTOBUF_DIR: ./packages/protobufs
|
||||
|
||||
jobs:
|
||||
codegen:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Show files exist
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
ls -la $PROTOBUF_DIR/packages/ts || true
|
||||
cat $PROTOBUF_DIR/packages/ts/deno.json
|
||||
|
||||
- name: Determine VERSION
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ -n "${{ inputs.version }}" ]; then
|
||||
VERSION="${{ inputs.version }}"
|
||||
else
|
||||
echo "No 'version' input. Provide inputs.version." >&2
|
||||
exit 1
|
||||
fi
|
||||
STRIPPED="${VERSION#v}"
|
||||
echo "VERSION=$STRIPPED" >> "$GITHUB_ENV"
|
||||
echo "Resolved VERSION=$STRIPPED"
|
||||
|
||||
- name: Set Package Versions to current tag
|
||||
working-directory: ${{ env.PROTOBUF_DIR }}/packages/ts
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
for f in deno.json; do
|
||||
test -f "$f"
|
||||
jq --arg version "${VERSION//\//-}" '
|
||||
walk(
|
||||
if type == "string" and test("__PACKAGE_VERSION__")
|
||||
then sub("__PACKAGE_VERSION__"; $version)
|
||||
else .
|
||||
end
|
||||
)
|
||||
' "$f" > "$f.tmp"
|
||||
mv "$f.tmp" "$f"
|
||||
done
|
||||
|
||||
- name: Setup Buf
|
||||
uses: bufbuild/buf-setup-action@main
|
||||
with:
|
||||
github_token: ${{ github.token }}
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: latest
|
||||
|
||||
- name: Install workspace dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Generate code
|
||||
run: pnpm --filter @meshtastic/protobufs-ws build
|
||||
|
||||
- name: Move generated .ts files and clean up
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
SRC_DIR="$PROTOBUF_DIR/packages/ts/dist/meshtastic"
|
||||
DEST_DIR="$PROTOBUF_DIR/packages/ts/dist"
|
||||
|
||||
if [ -d "$SRC_DIR" ]; then
|
||||
shopt -s nullglob
|
||||
ts_files=("$SRC_DIR"/*.ts)
|
||||
if [ ${#ts_files[@]} -gt 0 ]; then
|
||||
mv "$SRC_DIR"/*.ts "$DEST_DIR"/
|
||||
fi
|
||||
rm -rf "$SRC_DIR"
|
||||
else
|
||||
echo "Source directory not found: $SRC_DIR" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Remove nanopb_pb.ts if present
|
||||
if [ -f "$DEST_DIR/nanopb_pb.ts" ]; then
|
||||
rm "$DEST_DIR/nanopb_pb.ts"
|
||||
fi
|
||||
|
||||
- name: Copy license & README
|
||||
run: |
|
||||
cp LICENSE $PROTOBUF_DIR/packages/ts
|
||||
cp README.md $PROTOBUF_DIR/packages/ts
|
||||
|
||||
- name: Upload TypeScript code
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ts_code
|
||||
path: ${{ env.PROTOBUF_DIR }}/packages/ts
|
||||
|
||||
publish-jsr:
|
||||
runs-on: ubuntu-24.04
|
||||
needs: codegen
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
env:
|
||||
DRY_RUN: ${{ inputs.dry_run }}
|
||||
steps:
|
||||
- name: Download TypeScript code
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: ts_code
|
||||
|
||||
- name: Set up Deno
|
||||
uses: denoland/setup-deno@main
|
||||
with:
|
||||
deno-version: rc
|
||||
|
||||
- name: Publish to JSR
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
FLAGS="--unstable-sloppy-imports"
|
||||
if [ "${DRY_RUN}" = "true" ]; then
|
||||
FLAGS="$FLAGS --dry-run"
|
||||
echo "Running publish in dry-run mode..."
|
||||
fi
|
||||
deno publish $FLAGS
|
||||
157
.github/workflows/release-web.yml
vendored
157
.github/workflows/release-web.yml
vendored
@@ -3,77 +3,158 @@ name: Release Web
|
||||
on:
|
||||
release:
|
||||
types: [released, prereleased]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
ref:
|
||||
description: "Git ref (branch, tag, or SHA) to build"
|
||||
required: false
|
||||
default: ""
|
||||
tag_name:
|
||||
description: "Tag to use for artifacts/images (defaults to <sha>)"
|
||||
required: false
|
||||
default: ""
|
||||
attach_to_release:
|
||||
description: "Upload build.tar to an existing GitHub Release (requires tag_name)"
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
env:
|
||||
REGISTRY_IMAGE: ghcr.io/${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
release-web:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ inputs.ref != '' && inputs.ref || github.ref }}
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
bun-version: latest
|
||||
version: latest
|
||||
|
||||
- name: Cache Bun Dependencies
|
||||
uses: actions/cache@v4
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
path: |
|
||||
~/.bun/install/cache
|
||||
packages/web/node_modules
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-
|
||||
node-version: 22
|
||||
cache: pnpm
|
||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||
|
||||
- name: Run Web App Tests
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build web package
|
||||
working-directory: packages/web
|
||||
run: bun run test
|
||||
run: pnpm run build
|
||||
|
||||
- name: Create Web App Release Archive
|
||||
- name: Create release archive
|
||||
working-directory: packages/web
|
||||
run: bun run package
|
||||
run: pnpm run package
|
||||
|
||||
- name: Upload Web App Archive
|
||||
- name: Upload archive (artifact)
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: web-build
|
||||
if-no-files-found: error
|
||||
path: packages/web/dist/build.tar
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Attach Web Archive to GitHub Release
|
||||
run: gh release upload ${{ github.event.release.tag_name }} packages/web/dist/build.tar
|
||||
- name: Attach archive to GitHub Release
|
||||
if: ${{ github.event_name == 'release' || inputs.attach_to_release == true }}
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: packages/web/dist/build.tar
|
||||
tag_name: ${{ github.event_name == 'release' && github.event.release.tag_name || inputs.tag_name }}
|
||||
fail_on_unmatched_files: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Compute created timestamp & short SHA for labels/tags
|
||||
- name: Compute meta
|
||||
id: gen
|
||||
shell: bash
|
||||
run: |
|
||||
echo "created=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT"
|
||||
echo "shortsha=$(git rev-parse --short=12 HEAD)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Build/tag matrix:
|
||||
# - For release events: vX.Y.Z, semver helpers, and latest (only if NOT prerelease)
|
||||
# - For prerelease events: vX.Y.Z and semver helpers (NO latest)
|
||||
# - For manual runs:
|
||||
# * if tag_name set: that raw tag
|
||||
# * else: adhoc-<shortsha>
|
||||
- name: Docker metadata (tags & labels)
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY_IMAGE }}
|
||||
labels: |
|
||||
org.opencontainers.image.source=${{ github.repository }}
|
||||
org.opencontainers.image.revision=${{ github.sha }}
|
||||
org.opencontainers.image.created=${{ steps.gen.outputs.created }}
|
||||
tags: |
|
||||
# Full release (semantic tag)
|
||||
type=raw,value=${{ github.event.release.tag_name }},enable=${{ github.event_name == 'release' }}
|
||||
# Semver helpers (major, major.minor) for releases/prereleases
|
||||
type=semver,pattern={{version}},value=${{ github.event.release.tag_name }},enable=${{ github.event_name == 'release' }}
|
||||
type=semver,pattern={{major}}.{{minor}},value=${{ github.event.release.tag_name }},enable=${{ github.event_name == 'release' && github.event.release.prerelease == false }}
|
||||
type=semver,pattern={{major}},value=${{ github.event.release.tag_name }},enable=${{ github.event_name == 'release' && github.event.release.prerelease == false }}
|
||||
# Latest only for full releases (not prereleases)
|
||||
type=raw,value=latest,enable=${{ github.event_name == 'release' && github.event.release.prerelease == false }}
|
||||
# Manual: provided tag_name
|
||||
type=raw,value=${{ inputs.tag_name }},enable=${{ github.event_name == 'workflow_dispatch' && inputs.tag_name != '' }}
|
||||
# Manual: fallback to adhoc-<shortsha>
|
||||
type=raw,value=adhoc-${{ steps.gen.outputs.shortsha }},enable=${{ github.event_name == 'workflow_dispatch' && inputs.tag_name == '' }}
|
||||
# Always add immutable sha tag for traceability
|
||||
type=raw,value=sha-${{ steps.gen.outputs.shortsha }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Build Container Image
|
||||
id: build-container
|
||||
uses: redhat-actions/buildah-build@v2
|
||||
with:
|
||||
containerfiles: |
|
||||
./infra/Containerfile
|
||||
image: ghcr.io/${{ github.repository }}
|
||||
tags: latest, ${{ github.event.release.tag_name }}
|
||||
oci: true
|
||||
platforms: linux/amd64, linux/arm64
|
||||
- name: Set up Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Push Container to GHCR
|
||||
id: push-to-registry
|
||||
uses: redhat-actions/push-to-registry@v2
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
image: ${{ steps.build-container.outputs.image }}
|
||||
tags: ${{ steps.build-container.outputs.tags }}
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Output Image URL
|
||||
run: echo "🖼️ Image pushed to ${{ steps.push-to-registry.outputs.registry-paths }}"
|
||||
# Push images for:
|
||||
# - release (released or prereleased)
|
||||
# - manual with a tag_name (adhoc builds can still be pushed by name)
|
||||
- name: Build and push image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./packages/web/infra/Containerfile
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
push: ${{ github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && inputs.tag_name != '') }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
provenance: false
|
||||
|
||||
- name: Output image refs
|
||||
if: ${{ github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && inputs.tag_name != '') }}
|
||||
run: |
|
||||
echo "🖼️ Pushed tags:"
|
||||
echo "${{ steps.meta.outputs.tags }}"
|
||||
echo
|
||||
echo "Pull examples:"
|
||||
if [[ "${{ github.event_name }}" == "release" && "${{ github.event.release.prerelease }}" == "false" ]]; then
|
||||
echo " docker pull ${{ env.REGISTRY_IMAGE }}:latest"
|
||||
fi
|
||||
# Always available:
|
||||
echo " docker pull ${{ env.REGISTRY_IMAGE }}:${{ github.event.release.tag_name || inputs.tag_name || format('adhoc-{0}', steps.gen.outputs.shortsha) }}"
|
||||
echo " docker pull ${{ env.REGISTRY_IMAGE }}:sha-${{ steps.gen.outputs.shortsha }}"
|
||||
|
||||
- name: Explain no image push
|
||||
if: ${{ !(github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && inputs.tag_name != '')) }}
|
||||
run: echo "ℹ️ No image pushed (manual run without tag_name)."
|
||||
|
||||
91
.github/workflows/update-stable-from-master.yml
vendored
91
.github/workflows/update-stable-from-master.yml
vendored
@@ -7,44 +7,89 @@ on:
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: update-stable-${{ github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
update-stable-branch:
|
||||
name: Update Stable Branch from Main
|
||||
name: Update stable from latest release source
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
fetch-tags: true # IMPORTANT: we need tags to resolve the release commit
|
||||
|
||||
- name: Configure Git
|
||||
- name: Configure Git author
|
||||
run: |
|
||||
git config user.name "GitHub Actions Bot"
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
- name: Fetch latest main and stable branches
|
||||
- name: Determine source SHA (prefer tag)
|
||||
id: meta
|
||||
shell: bash
|
||||
run: |
|
||||
git fetch origin main:main
|
||||
git fetch origin stable:stable || echo "Stable branch not found remotely, will create."
|
||||
set -euo pipefail
|
||||
|
||||
- name: Get latest main commit SHA
|
||||
id: get_main_sha
|
||||
run: echo "MAIN_SHA=$(git rev-parse main)" >> $GITHUB_ENV
|
||||
TAG="${{ github.event.release.tag_name }}"
|
||||
TARGET="${{ github.event.release.target_commitish || '' }}"
|
||||
|
||||
- name: Check out stable branch
|
||||
run: |
|
||||
if git show-ref --verify --quiet refs/heads/stable; then
|
||||
git checkout stable
|
||||
git pull origin stable # Sync with remote stable if it exists
|
||||
else
|
||||
echo "Creating local stable branch based on main HEAD."
|
||||
git checkout -b stable ${{ env.MAIN_SHA }}
|
||||
# Always ensure we have latest remote heads/tags
|
||||
git fetch --tags --prune origin
|
||||
|
||||
SHA=""
|
||||
SRC_DESC=""
|
||||
|
||||
# 1) Prefer the release tag commit
|
||||
if [ -n "${TAG}" ] && git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then
|
||||
SHA="$(git rev-list -n1 "${TAG}")"
|
||||
SRC_DESC="tag:${TAG}"
|
||||
fi
|
||||
|
||||
- name: Reset stable branch to latest main
|
||||
run: git reset --hard ${{ env.MAIN_SHA }}
|
||||
# 2) Fall back to target_commitish if it points to a branch on origin
|
||||
if [ -z "${SHA}" ] && [ -n "${TARGET}" ] && git ls-remote --exit-code --heads origin "${TARGET}" >/dev/null 2>&1; then
|
||||
git fetch origin "${TARGET}:${TARGET}" --prune
|
||||
SHA="$(git rev-parse "${TARGET}^{commit}")"
|
||||
SRC_DESC="branch:${TARGET}"
|
||||
fi
|
||||
|
||||
- name: Force push stable branch
|
||||
run: git push origin stable --force
|
||||
# 3) Fall back to main if present
|
||||
if [ -z "${SHA}" ] && git ls-remote --exit-code --heads origin "main" >/dev/null 2>&1; then
|
||||
git fetch origin "main:main" --prune
|
||||
SHA="$(git rev-parse "main^{commit}")"
|
||||
SRC_DESC="branch:main"
|
||||
fi
|
||||
|
||||
if [ -z "${SHA}" ]; then
|
||||
echo "::error::Unable to resolve source commit from tag (${TAG}), target_commitish (${TARGET}), or main."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Using source: ${SRC_DESC}"
|
||||
echo "sha=${SHA}" >> "$GITHUB_OUTPUT"
|
||||
echo "source=${SRC_DESC}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Create/reset local stable to SHA
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# Make sure we know if remote stable exists (non-fatal if not)
|
||||
git fetch origin stable:refs/remotes/origin/stable || true
|
||||
|
||||
# Create or reset local stable in one command
|
||||
git checkout -B stable "${{ steps.meta.outputs.sha }}"
|
||||
git status --short --branch
|
||||
|
||||
- name: Push stable (force-with-lease)
|
||||
run: |
|
||||
# Safer than --force; refuses if remote moved unexpectedly (protects against races)
|
||||
REMOTE_STABLE_SHA="$(git rev-parse refs/remotes/origin/stable || echo '')"
|
||||
if [ -z "$REMOTE_STABLE_SHA" ]; then
|
||||
# If remote stable doesn't exist, just use --force-with-lease=stable (no SHA)
|
||||
git push origin stable:stable --force-with-lease=stable
|
||||
else
|
||||
# Use the specific SHA for maximum safety
|
||||
git push origin stable:stable --force-with-lease=stable:$REMOTE_STABLE_SHA
|
||||
fi
|
||||
|
||||
10
.gitignore
vendored
10
.gitignore
vendored
@@ -8,3 +8,13 @@ __screenshots__*
|
||||
*.diff
|
||||
npm/
|
||||
.idea
|
||||
**/LICENSE
|
||||
.DS_Store
|
||||
|
||||
packages/protobufs/packages/ts/dist
|
||||
.pnpm-store/
|
||||
|
||||
# Local dev certs
|
||||
*.pem
|
||||
*.crt
|
||||
*.key
|
||||
|
||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "packages/packages/protobufs"]
|
||||
path = packages/packages/protobufs
|
||||
url = https://github.com/meshtastic/protobufs
|
||||
2
.vscode/extensions.json
vendored
2
.vscode/extensions.json
vendored
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"recommendations": ["bradlc.vscode-tailwindcss", "denoland.vscode-deno"]
|
||||
"recommendations": ["bradlc.vscode-tailwindcss", "biomejs.biome"]
|
||||
}
|
||||
|
||||
8
.vscode/settings.json
vendored
8
.vscode/settings.json
vendored
@@ -1,6 +1,10 @@
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.biome": "explicit",
|
||||
}
|
||||
"source.fixAll.biome": "explicit"
|
||||
},
|
||||
"search.exclude": {
|
||||
"**/i18n/locales/*-*/**": true
|
||||
},
|
||||
"vitest.workspaceConfig": "vitest.config.ts"
|
||||
}
|
||||
|
||||
44
README.md
44
README.md
@@ -12,6 +12,9 @@ This monorepo consolidates the official [Meshtastic](https://meshtastic.org) web
|
||||
interface and its supporting JavaScript libraries. It aims to provide a unified
|
||||
development experience for interacting with Meshtastic devices.
|
||||
|
||||
> [!NOTE]
|
||||
> You can find the main Meshtastic documentation at https://meshtastic.org/docs/introduction/.
|
||||
|
||||
### Projects within this Monorepo (`packages/`)
|
||||
|
||||
All projects are located within the `packages/` directory:
|
||||
@@ -21,17 +24,19 @@ All projects are located within the `packages/` directory:
|
||||
- **[Hosted version](https://client.meshtastic.org)**
|
||||
- **`packages/core`:** Core functionality for Meshtastic JS.
|
||||
- **`packages/transport-node`:** TCP Transport for the NodeJS runtime.
|
||||
- **`packages/transport-node-serial`:** NodeJS Serial Transport for the NodeJS runtime.
|
||||
- **`packages/transport-deno`:** TCP Transport for the Deno runtime.
|
||||
- **`packages/transport-http`:** HTTP Transport.
|
||||
- **`packages/transport-web-bluetooth`:** Web Bluetooth Transport.
|
||||
- **`packages/transport-web-serial`:** Web Serial Transport.
|
||||
- **`packages/protobufs`:** Git submodule containing Meshtastic’s shared protobuf definitions, used to generate and publish the JSR protobuf package.
|
||||
|
||||
All `Meshtastic JS` packages (core and transports) are published both to
|
||||
[JSR](https://jsr.io/@meshtastic). [NPM](https://www.npmjs.com/org/meshtastic)
|
||||
|
||||
---
|
||||
|
||||
## Stats
|
||||
## Repository activity
|
||||
|
||||
| Project | Repobeats |
|
||||
| :------------- | :-------------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -72,6 +77,9 @@ Follow the installation instructions on their home page.
|
||||
```
|
||||
This command installs all necessary dependencies for all packages within the
|
||||
monorepo.
|
||||
3. **Install the Buf CLI**
|
||||
Required for building `packages/protobufs`
|
||||
https://buf.build/docs/cli/installation/
|
||||
|
||||
### Running Projects
|
||||
|
||||
@@ -85,28 +93,18 @@ If you encounter any issues, please report them in our
|
||||
[issues tracker](https://github.com/meshtastic/web/issues). Your feedback helps
|
||||
improve the stability of future releases
|
||||
|
||||
### Contributing
|
||||
## Star history
|
||||
|
||||
We welcome contributions! Here’s how the deployment flow works for pull
|
||||
requests:
|
||||
<a href="https://star-history.com/#meshtastic/web&Date">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=meshtastic/web&type=Date&theme=dark" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=meshtastic/web&type=Date" />
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=meshtastic/web&type=Date" width="100%" />
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
- **Preview Deployments:**\
|
||||
Every pull request automatically generates a preview deployment on Vercel.
|
||||
This allows you and reviewers to easily preview changes before merging.
|
||||
## Contributors
|
||||
|
||||
- **Staging Environment (`client-test`):**\
|
||||
Once your PR is merged, your changes will be available on our staging site:
|
||||
[client-test.meshtastic.org](https://client-test.meshtastic.org/).\
|
||||
This environment supports rapid feature iteration and testing without
|
||||
impacting the production site.
|
||||
|
||||
- **Production Releases:**\
|
||||
At regular intervals, stable and fully tested releases are promoted to our
|
||||
production site: [client.meshtastic.org](https://client.meshtastic.org/).\
|
||||
This is the primary interface used by the public to connect with their
|
||||
Meshtastic nodes.
|
||||
|
||||
Please review our
|
||||
[Contribution Guidelines](https://github.com/meshtastic/web/blob/main/CONTRIBUTING.md)
|
||||
before submitting a pull request. We appreciate your help in making the project
|
||||
better!
|
||||
<a href="https://github.com/meshtastic/web/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=meshtastic/web" width="100%"/>
|
||||
</a>
|
||||
|
||||
113
biome.json
113
biome.json
@@ -1,44 +1,75 @@
|
||||
{
|
||||
"files": {
|
||||
"includes": ["**/*.ts", "**/*.tsx", "!**/*.test.ts", "!**/*.test.tsx", "!npm_modules/**", "!dist/**", "!npm/**"],
|
||||
"ignoreUnknown": false
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"formatWithErrors": false,
|
||||
"indentStyle": "space",
|
||||
"indentWidth": 2,
|
||||
"lineWidth": 80,
|
||||
"attributePosition": "auto"
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"includes": ["**", "!test/**"],
|
||||
"rules": {
|
||||
"recommended": true,
|
||||
"suspicious": {
|
||||
"noExplicitAny": "error",
|
||||
"noDebugger": "error"
|
||||
},
|
||||
"style": {
|
||||
"useBlockStatements": "error",
|
||||
"useSingleVarDeclarator": "off"
|
||||
},
|
||||
"correctness": {
|
||||
"noUnusedVariables": "error",
|
||||
"noUnusedImports": "error"
|
||||
}
|
||||
}
|
||||
},
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"quoteStyle": "double",
|
||||
"semicolons": "always"
|
||||
}
|
||||
},
|
||||
"json": {
|
||||
"formatter": {
|
||||
"enabled": false
|
||||
"files": {
|
||||
"includes": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
"!npm_modules",
|
||||
"!**/dist",
|
||||
"!**/protobufs",
|
||||
"!**/.*",
|
||||
"!npm",
|
||||
"**/*.json",
|
||||
"!**/locales/*-*/*.json",
|
||||
"!**/packages/ui/"
|
||||
],
|
||||
"ignoreUnknown": false
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"formatWithErrors": false,
|
||||
"indentStyle": "space",
|
||||
"indentWidth": 2,
|
||||
"lineWidth": 80,
|
||||
"attributePosition": "auto"
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"includes": ["**", "!test/**", "!**/dist/**"],
|
||||
"rules": {
|
||||
"recommended": true,
|
||||
"suspicious": {
|
||||
"noExplicitAny": "error",
|
||||
"noDebugger": "error"
|
||||
},
|
||||
"style": {
|
||||
"useBlockStatements": "error",
|
||||
"useSingleVarDeclarator": "off"
|
||||
},
|
||||
"correctness": {
|
||||
"noUnusedVariables": "error",
|
||||
"noUnusedImports": "error",
|
||||
"useImportExtensions": "error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"quoteStyle": "double",
|
||||
"semicolons": "always"
|
||||
}
|
||||
},
|
||||
"json": {
|
||||
"formatter": {
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"includes": [
|
||||
"**/*.test.ts",
|
||||
"**/*.test.tsx",
|
||||
"**/__tests__/**/*.{ts,tsx}"
|
||||
],
|
||||
"linter": {
|
||||
"rules": {
|
||||
"suspicious": {
|
||||
"noExplicitAny": "off"
|
||||
},
|
||||
"style": {
|
||||
"noNonNullAssertion": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
91
package.json
91
package.json
@@ -1,42 +1,53 @@
|
||||
{
|
||||
"name": "meshtastic-web",
|
||||
"version": "2.7.0-0",
|
||||
"type": "module",
|
||||
"description": "Meshtastic web client monorepo",
|
||||
"license": "GPL-3.0-only",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/meshtastic/web.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/meshtastic/web/issues"
|
||||
},
|
||||
"homepage": "https://meshtastic.org",
|
||||
"simple-git-hooks": {
|
||||
"pre-commit": "pnpm run check:fix"
|
||||
},
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"lint": "biome lint",
|
||||
"lint:fix": "biome lint --write",
|
||||
"format": "biome format",
|
||||
"format:fix": "biome format . --write",
|
||||
"check": "biome check",
|
||||
"check:fix": "biome check --write",
|
||||
"build:all": "pnpm run --filter '*' build",
|
||||
"clean:all": "pnpm run --filter '*' clean",
|
||||
"publish:packages": "pnpm run --filter 'packages/transport-* packages/core' build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@bufbuild/protobuf": "^2.6.1",
|
||||
"@meshtastic/protobufs": "npm:@jsr/meshtastic__protobufs@^2.7.0",
|
||||
"ste-simple-events": "^3.0.11",
|
||||
"tslog": "^4.9.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.16.4",
|
||||
"biome": "^0.3.3",
|
||||
"tsdown": "^0.13.4",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
"name": "@meshtastic/web",
|
||||
"version": "2.7.0-0",
|
||||
"type": "module",
|
||||
"description": "Meshtastic web client monorepo",
|
||||
"license": "GPL-3.0-only",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/meshtastic/web.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/meshtastic/web/issues"
|
||||
},
|
||||
"homepage": "https://meshtastic.org",
|
||||
"simple-git-hooks": {
|
||||
"pre-commit": "pnpm run check:fix"
|
||||
},
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"lint": "biome lint",
|
||||
"lint:fix": "biome lint --write",
|
||||
"format": "biome format",
|
||||
"format:fix": "biome format . --write",
|
||||
"check": "biome check",
|
||||
"check:fix": "biome check --write",
|
||||
"build:all": "pnpm run --filter '*' build",
|
||||
"clean:all": "pnpm run --filter '*' clean",
|
||||
"publish:packages": "pnpm run build --filter 'packages/transport-*'",
|
||||
"test": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@bufbuild/protobuf": "^2.9.0",
|
||||
"@meshtastic/protobufs": "npm:@jsr/meshtastic__protobufs@^2.7.12-1",
|
||||
"ste-simple-events": "^3.0.11",
|
||||
"tslog": "^4.9.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.2.4",
|
||||
"@types/node": "^24.3.1",
|
||||
"tsdown": "^0.15.0",
|
||||
"typescript": "^5.9.2",
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"@serialport/bindings-cpp",
|
||||
"@tailwindcss/oxide",
|
||||
"core-js",
|
||||
"esbuild",
|
||||
"simple-git-hooks"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"name": "@meshtastic/core",
|
||||
"version": "2.6.6",
|
||||
"exports": {
|
||||
".": "./mod.ts"
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,50 @@
|
||||
{
|
||||
"name": "@meshtastic/core",
|
||||
"version": "2.6.6",
|
||||
"description": "Core functionalities for Meshtastic web applications.",
|
||||
"version": "2.6.7",
|
||||
"description": "Core functionalities for Meshtastic web applications.",
|
||||
"exports": {
|
||||
".": "./mod.ts"
|
||||
},
|
||||
"main": "./dist/mod.mjs",
|
||||
"module": "./dist/mod.mjs",
|
||||
"types": "./dist/mod.d.mts",
|
||||
".": "./mod.ts"
|
||||
},
|
||||
"type": "module",
|
||||
"main": "./dist/mod.js",
|
||||
"module": "./dist/mod.js",
|
||||
"types": "./dist/mod.d.ts",
|
||||
"license": "GPL-3.0-only",
|
||||
"tsdown": {
|
||||
"entry": ["mod.ts"],
|
||||
"entry": "mod.ts",
|
||||
"platform": "browser",
|
||||
"dts": true,
|
||||
"format": ["esm"],
|
||||
"format": [
|
||||
"esm"
|
||||
],
|
||||
"splitting": false,
|
||||
"clean": true
|
||||
},
|
||||
"jsrInclude": [
|
||||
"mod.ts",
|
||||
"src",
|
||||
"README.md",
|
||||
"LICENSE"
|
||||
],
|
||||
"jsrExclude": [
|
||||
"src/**/*.test.ts"
|
||||
],
|
||||
"files": [
|
||||
"package.json",
|
||||
"README.md",
|
||||
"LICENSE",
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"prepack": "cp ../../LICENSE ./LICENSE",
|
||||
"clean": "rm -rf dist LICENSE",
|
||||
"build:npm": "tsdown",
|
||||
"publish:npm": "pnpm clean && pnpm build:npm && pnpm publish --access public",
|
||||
"publish:npm": "pnpm clean && pnpm build:npm && pnpm publish --access public --no-git-checks",
|
||||
"prepare:jsr": "rm -rf dist && pnpm dlx pkg-to-jsr",
|
||||
"publish:jsr": "pnpm run prepack && pnpm prepare:jsr && deno publish --allow-dirty --no-check"
|
||||
},
|
||||
"dependencies": {
|
||||
"crc": "npm:crc@^4.3.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +64,11 @@ export class MeshDevice {
|
||||
this.isConfigured = true;
|
||||
} else if (status === DeviceStatusEnum.DeviceConfiguring) {
|
||||
this.isConfigured = false;
|
||||
} else if (status === DeviceStatusEnum.DeviceDisconnected) {
|
||||
if (this._heartbeatIntervalId !== undefined) {
|
||||
clearInterval(this._heartbeatIntervalId);
|
||||
}
|
||||
this.complete();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -378,6 +383,53 @@ export class MeshDevice {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the fixed position of a device. Can be used to
|
||||
* position GPS-less devices.
|
||||
*/
|
||||
public async setFixedPosition(
|
||||
latitude: number,
|
||||
longitude: number,
|
||||
): Promise<number> {
|
||||
const setPositionMessage = create(Protobuf.Admin.AdminMessageSchema, {
|
||||
payloadVariant: {
|
||||
case: "setFixedPosition",
|
||||
value: create(Protobuf.Mesh.PositionSchema, {
|
||||
latitudeI: Math.floor(latitude / 1e-7),
|
||||
longitudeI: Math.floor(longitude / 1e-7),
|
||||
}),
|
||||
},
|
||||
});
|
||||
return await this.sendPacket(
|
||||
toBinary(Protobuf.Admin.AdminMessageSchema, setPositionMessage),
|
||||
Protobuf.Portnums.PortNum.ADMIN_APP,
|
||||
"self",
|
||||
0,
|
||||
true,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the fixed position of a device
|
||||
*/
|
||||
public async removeFixedPosition(): Promise<number> {
|
||||
const removePositionMessage = create(Protobuf.Admin.AdminMessageSchema, {
|
||||
payloadVariant: {
|
||||
case: "removeFixedPosition",
|
||||
value: true,
|
||||
},
|
||||
});
|
||||
return await this.sendPacket(
|
||||
toBinary(Protobuf.Admin.AdminMessageSchema, removePositionMessage),
|
||||
Protobuf.Portnums.PortNum.ADMIN_APP,
|
||||
"self",
|
||||
0,
|
||||
true,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets specified channel information from the radio
|
||||
*/
|
||||
@@ -626,7 +678,7 @@ export class MeshDevice {
|
||||
public async reboot(time: number): Promise<number> {
|
||||
this.log.debug(
|
||||
Emitter[Emitter.Reboot],
|
||||
`🔌 Rebooting node ${time > 0 ? "now" : `in ${time} seconds`}`,
|
||||
`🔌 Rebooting node ${time === 0 ? "now" : `in ${time} seconds`}`,
|
||||
);
|
||||
|
||||
const reboot = create(Protobuf.Admin.AdminMessageSchema, {
|
||||
@@ -649,7 +701,7 @@ export class MeshDevice {
|
||||
public async rebootOta(time: number): Promise<number> {
|
||||
this.log.debug(
|
||||
Emitter[Emitter.RebootOta],
|
||||
`🔌 Rebooting into OTA mode ${time > 0 ? "now" : `in ${time} seconds`}`,
|
||||
`🔌 Rebooting into OTA mode ${time === 0 ? "now" : `in ${time} seconds`}`,
|
||||
);
|
||||
|
||||
const rebootOta = create(Protobuf.Admin.AdminMessageSchema, {
|
||||
@@ -723,7 +775,14 @@ export class MeshDevice {
|
||||
},
|
||||
});
|
||||
|
||||
return this.sendRaw(toBinary(Protobuf.Mesh.ToRadioSchema, toRadio));
|
||||
return this.sendRaw(toBinary(Protobuf.Mesh.ToRadioSchema, toRadio)).catch(
|
||||
(e) => {
|
||||
if (this.deviceStatus === DeviceStatusEnum.DeviceDisconnected) {
|
||||
throw new Error("Device connection lost");
|
||||
}
|
||||
throw e;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -750,7 +809,12 @@ export class MeshDevice {
|
||||
clearInterval(this._heartbeatIntervalId);
|
||||
}
|
||||
this._heartbeatIntervalId = setInterval(() => {
|
||||
this.heartbeat();
|
||||
this.heartbeat().catch((err) => {
|
||||
this.log.error(
|
||||
Emitter[Emitter.Ping],
|
||||
`⚠️ Unable to send heartbeat: ${err.message}`,
|
||||
);
|
||||
});
|
||||
}, interval);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,12 @@ interface DebugLog {
|
||||
data: string;
|
||||
}
|
||||
|
||||
export type DeviceOutput = Packet | DebugLog;
|
||||
interface StatusEvent {
|
||||
type: "status";
|
||||
data: { status: DeviceStatusEnum; reason?: string };
|
||||
}
|
||||
|
||||
export type DeviceOutput = Packet | DebugLog | StatusEvent;
|
||||
|
||||
export interface Transport {
|
||||
toDevice: WritableStream<Uint8Array>;
|
||||
@@ -41,6 +46,7 @@ export enum DeviceStatusEnum {
|
||||
DeviceConnected = 5,
|
||||
DeviceConfiguring = 6,
|
||||
DeviceConfigured = 7,
|
||||
DeviceError = 8,
|
||||
}
|
||||
|
||||
export type LogEventPacket = LogEvent & { date: Date };
|
||||
@@ -101,6 +107,7 @@ export enum Emitter {
|
||||
RemoveNodeByNum = 32,
|
||||
SetCannedMessages = 33,
|
||||
Disconnect = 34,
|
||||
ConnectionStatus = 35,
|
||||
}
|
||||
|
||||
export interface LogEvent {
|
||||
|
||||
@@ -330,6 +330,15 @@ export class EventSystem {
|
||||
PacketMetadata<Uint8Array>
|
||||
> = new SimpleEventDispatcher<PacketMetadata<Uint8Array>>();
|
||||
|
||||
/**
|
||||
* Fires when a new MeshPacket message containing a ClientNotification packet has been
|
||||
* received from device
|
||||
*
|
||||
* @event onClientNotificationPacket
|
||||
*/
|
||||
public readonly onClientNotificationPacket: SimpleEventDispatcher<Protobuf.Mesh.ClientNotification> =
|
||||
new SimpleEventDispatcher<Protobuf.Mesh.ClientNotification>();
|
||||
|
||||
/**
|
||||
* Fires when the devices connection or configuration status changes
|
||||
*
|
||||
@@ -378,4 +387,12 @@ export class EventSystem {
|
||||
*/
|
||||
public readonly onQueueStatus: SimpleEventDispatcher<Protobuf.Mesh.QueueStatus> =
|
||||
new SimpleEventDispatcher<Protobuf.Mesh.QueueStatus>();
|
||||
|
||||
/**
|
||||
* Fires when a configCompleteId message is received from the device
|
||||
*
|
||||
* @event onConfigComplete
|
||||
*/
|
||||
public readonly onConfigComplete: SimpleEventDispatcher<number> =
|
||||
new SimpleEventDispatcher<number>();
|
||||
}
|
||||
|
||||
@@ -117,6 +117,14 @@ export class Queue {
|
||||
await writer.write(item.data);
|
||||
item.sent = true;
|
||||
} catch (error) {
|
||||
if (
|
||||
error?.code === "ECONNRESET" ||
|
||||
error?.code === "ERR_INVALID_STATE"
|
||||
) {
|
||||
writer.releaseLock();
|
||||
this.lock = false;
|
||||
throw error;
|
||||
}
|
||||
console.error(`Error sending packet ${item.id}`, error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,20 +7,51 @@ export const decodePacket = (device: MeshDevice) =>
|
||||
new WritableStream<DeviceOutput>({
|
||||
write(chunk) {
|
||||
switch (chunk.type) {
|
||||
case "status": {
|
||||
const { status, reason } = chunk.data as {
|
||||
status: Types.DeviceStatusEnum;
|
||||
reason?: string;
|
||||
};
|
||||
|
||||
device.updateDeviceStatus(status);
|
||||
device.log.info(
|
||||
Types.Emitter[Types.Emitter.ConnectionStatus],
|
||||
`🔗 ${Types.DeviceStatusEnum[status]} ${reason ? `(${reason})` : ""}`,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "debug": {
|
||||
break;
|
||||
}
|
||||
case "packet": {
|
||||
const decodedMessage = fromBinary(
|
||||
Protobuf.Mesh.FromRadioSchema,
|
||||
chunk.data,
|
||||
);
|
||||
let decodedMessage: Protobuf.Mesh.FromRadio;
|
||||
try {
|
||||
decodedMessage = fromBinary(
|
||||
Protobuf.Mesh.FromRadioSchema,
|
||||
chunk.data,
|
||||
);
|
||||
} catch (e) {
|
||||
device.log.error(
|
||||
Types.Emitter[Types.Emitter.HandleFromRadio],
|
||||
"⚠️ Received undecodable packet",
|
||||
e,
|
||||
);
|
||||
break;
|
||||
}
|
||||
device.events.onFromRadio.dispatch(decodedMessage);
|
||||
|
||||
/** @todo Add map here when `all=true` gets fixed. */
|
||||
switch (decodedMessage.payloadVariant.case) {
|
||||
case "packet": {
|
||||
device.handleMeshPacket(decodedMessage.payloadVariant.value);
|
||||
try {
|
||||
device.handleMeshPacket(decodedMessage.payloadVariant.value);
|
||||
} catch (e) {
|
||||
device.log.error(
|
||||
Types.Emitter[Types.Emitter.HandleFromRadio],
|
||||
"⚠️ Unable to handle mesh packet",
|
||||
e,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -104,21 +135,27 @@ export const decodePacket = (device: MeshDevice) =>
|
||||
}
|
||||
|
||||
case "configCompleteId": {
|
||||
if (decodedMessage.payloadVariant.value !== device.configId) {
|
||||
device.log.error(
|
||||
Types.Emitter[Types.Emitter.HandleFromRadio],
|
||||
`❌ Invalid config id received from device, expected ${device.configId} but received ${decodedMessage.payloadVariant.value}`,
|
||||
);
|
||||
}
|
||||
|
||||
device.log.info(
|
||||
Types.Emitter[Types.Emitter.HandleFromRadio],
|
||||
`⚙️ Valid config id received from device: ${device.configId}`,
|
||||
`⚙️ Received config complete id: ${decodedMessage.payloadVariant.value}`,
|
||||
);
|
||||
|
||||
device.updateDeviceStatus(
|
||||
Types.DeviceStatusEnum.DeviceConfigured,
|
||||
// Emit the configCompleteId event for MeshService to handle two-stage flow
|
||||
device.events.onConfigComplete.dispatch(
|
||||
decodedMessage.payloadVariant.value,
|
||||
);
|
||||
|
||||
// For backward compatibility: if configId matches, update device status
|
||||
// MeshService will override this behavior for two-stage flow
|
||||
if (decodedMessage.payloadVariant.value === device.configId) {
|
||||
device.log.info(
|
||||
Types.Emitter[Types.Emitter.HandleFromRadio],
|
||||
`⚙️ Config id matches device.configId: ${device.configId}`,
|
||||
);
|
||||
device.updateDeviceStatus(
|
||||
Types.DeviceStatusEnum.DeviceConfigured,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -209,6 +246,18 @@ export const decodePacket = (device: MeshDevice) =>
|
||||
break;
|
||||
}
|
||||
|
||||
case "clientNotification": {
|
||||
device.log.trace(
|
||||
Types.Emitter[Types.Emitter.HandleFromRadio],
|
||||
`📣 Received ClientNotification: ${decodedMessage.payloadVariant.value.message}`,
|
||||
);
|
||||
|
||||
device.events.onClientNotificationPacket.dispatch(
|
||||
decodedMessage.payloadVariant.value,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
device.log.warn(
|
||||
Types.Emitter[Types.Emitter.HandleFromRadio],
|
||||
|
||||
@@ -14,7 +14,7 @@ export const fromDeviceStream: () => TransformStream<Uint8Array, DeviceOutput> =
|
||||
byteBuffer = new Uint8Array([...byteBuffer, ...chunk]);
|
||||
let processingExhausted = false;
|
||||
while (byteBuffer.length !== 0 && !processingExhausted) {
|
||||
const framingIndex = byteBuffer.findIndex((byte) => byte === 0x94);
|
||||
const framingIndex = byteBuffer.indexOf(0x94);
|
||||
const framingByte2 = byteBuffer[framingIndex + 1];
|
||||
if (framingByte2 === 0xc3) {
|
||||
if (byteBuffer.subarray(0, framingIndex).length) {
|
||||
@@ -35,9 +35,7 @@ export const fromDeviceStream: () => TransformStream<Uint8Array, DeviceOutput> =
|
||||
) {
|
||||
const packet = byteBuffer.subarray(4, 4 + (msb << 8) + lsb);
|
||||
|
||||
const malformedDetectorIndex = packet.findIndex(
|
||||
(byte) => byte === 0x94,
|
||||
);
|
||||
const malformedDetectorIndex = packet.indexOf(0x94);
|
||||
if (
|
||||
malformedDetectorIndex !== -1 &&
|
||||
packet[malformedDetectorIndex + 1] === 0xc3
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
/**
|
||||
* Pads packets with appropriate framing information before writing to the output stream.
|
||||
*/
|
||||
export const toDeviceStream: TransformStream<Uint8Array, Uint8Array> =
|
||||
new TransformStream<Uint8Array, Uint8Array>({
|
||||
transform(chunk: Uint8Array, controller): void {
|
||||
const bufLen = chunk.length;
|
||||
const header = new Uint8Array([
|
||||
0x94,
|
||||
0xc3,
|
||||
(bufLen >> 8) & 0xff,
|
||||
bufLen & 0xff,
|
||||
]);
|
||||
controller.enqueue(new Uint8Array([...header, ...chunk]));
|
||||
},
|
||||
});
|
||||
export const toDeviceStream: () => TransformStream<Uint8Array, Uint8Array> =
|
||||
() => {
|
||||
return new TransformStream<Uint8Array, Uint8Array>({
|
||||
transform(chunk: Uint8Array, controller): void {
|
||||
const bufLen = chunk.length;
|
||||
const header = new Uint8Array([
|
||||
0x94,
|
||||
0xc3,
|
||||
(bufLen >> 8) & 0xff,
|
||||
bufLen & 0xff,
|
||||
]);
|
||||
controller.enqueue(new Uint8Array([...header, ...chunk]));
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "ESNext",
|
||||
@@ -7,12 +7,7 @@
|
||||
"outDir": "./dist",
|
||||
"moduleResolution": "bundler",
|
||||
"emitDeclarationOnly": false,
|
||||
"esModuleInterop": true,
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
1
packages/packages/protobufs
Submodule
1
packages/packages/protobufs
Submodule
Submodule packages/packages/protobufs added at a1b8c3d171
3
packages/protobufs/.gitattributes
vendored
Normal file
3
packages/protobufs/.gitattributes
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
* text=auto eol=lf
|
||||
*.{cmd,[cC][mM][dD]} text eol=crlf
|
||||
*.{bat,[bB][aA][tT]} text eol=crlf
|
||||
30
packages/protobufs/.github/pull_request_template.md
vendored
Normal file
30
packages/protobufs/.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
<!-- Describe what you are intending to change -->
|
||||
|
||||
# What does this PR do?
|
||||
|
||||
<!-- Please remove or replace the issue url -->
|
||||
|
||||
> [Related Issue](https://github.com/meshtastic/protobufs/issues/0)
|
||||
|
||||
## Checklist before merging
|
||||
|
||||
- [ ] All top level messages commented
|
||||
- [ ] All enum members have unique descriptions
|
||||
|
||||
|
||||
### New Hardware Model Acceptance Policy
|
||||
|
||||
Due to limited availability and ongoing support, new Hardware Models will only be accepted from [Meshtastic Backers and Partners](https://meshtastic.com/). The Meshtastic team reserves the right to make exceptions to this policy.
|
||||
|
||||
#### Alternative for Community Contributors
|
||||
|
||||
You are welcome to use one of the existing DIY hardware models in your PlatformIO environment and create a pull request in the firmware project. Please note the following conditions:
|
||||
|
||||
- The device will **not** be officially supported by the core Meshtastic team.
|
||||
- The device will **not** appear in the [Web Flasher](https://flasher.meshtastic.org/) or Github release assets.
|
||||
- You will be responsible for ongoing maintenance and support.
|
||||
- Community-contributed / DIY hardware models are considered experimental and will likely have limited or no testing.
|
||||
|
||||
#### Getting Official Support
|
||||
|
||||
To have your hardware model officially supported and included in the Meshtastic ecosystem, consider becoming a Meshtastic Backer or Partner. Visit [meshtastic.com](https://meshtastic.com/) for more information about partnership opportunities.
|
||||
24
packages/protobufs/.github/workflows/ci.yml
vendored
Normal file
24
packages/protobufs/.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
name: Push commit to schema registry
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
push_to_registry:
|
||||
name: Push to schema registry
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Push to schema registry
|
||||
uses: bufbuild/buf-action@v1.2.0
|
||||
with:
|
||||
github_token: ${{ github.token }}
|
||||
token: ${{ secrets.BUF_TOKEN }}
|
||||
push: true
|
||||
71
packages/protobufs/.github/workflows/create_tag.yml
vendored
Normal file
71
packages/protobufs/.github/workflows/create_tag.yml
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
name: Create tag
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
increment_type:
|
||||
type: choice
|
||||
description: Select the type of version increment
|
||||
required: true
|
||||
options:
|
||||
- patch
|
||||
- minor
|
||||
- major
|
||||
|
||||
jobs:
|
||||
increment_version:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- id: version
|
||||
name: Get current version
|
||||
run: |
|
||||
VERSION=$(git describe --abbrev=0 --tags)
|
||||
|
||||
# Split version into major, minor, and patch
|
||||
MAJOR=$(echo $VERSION | awk -F '.' '{print $1}' | cut -c 2-)
|
||||
MINOR=$(echo $VERSION | awk -F '.' '{print $2}')
|
||||
PATCH=$(echo $VERSION | awk -F '.' '{print $3}')
|
||||
|
||||
# Increment the appropriate part of the version
|
||||
if [[ ${{ inputs.increment_type }} == "patch" ]]; then
|
||||
PATCH=$((PATCH + 1))
|
||||
elif [[ ${{ inputs.increment_type }} == "minor" ]]; then
|
||||
MINOR=$((MINOR + 1))
|
||||
PATCH=0
|
||||
elif [[ ${{ inputs.increment_type }} == "major" ]]; then
|
||||
MAJOR=$((MAJOR + 1))
|
||||
MINOR=0
|
||||
PATCH=0
|
||||
fi
|
||||
|
||||
# Update the version
|
||||
echo "NEW_VERSION=v$MAJOR.$MINOR.$PATCH" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
name: Meshtastic Protobufs ${{ steps.version.outputs.NEW_VERSION }}
|
||||
tag: ${{ steps.version.outputs.NEW_VERSION }}
|
||||
generateReleaseNotes: true
|
||||
token: ${{ github.token }}
|
||||
|
||||
- name: Setup Buf
|
||||
uses: bufbuild/buf-action@v1.2.0
|
||||
with:
|
||||
github_token: ${{ github.token }}
|
||||
token: ${{ secrets.BUF_TOKEN }}
|
||||
setup_only: true
|
||||
|
||||
- name: Push to schema registry
|
||||
env:
|
||||
BUF_TOKEN: ${{ secrets.BUF_TOKEN }}
|
||||
run: |
|
||||
buf push --tag ${{ steps.version.outputs.NEW_VERSION }}
|
||||
30
packages/protobufs/.github/workflows/publish.yml
vendored
Normal file
30
packages/protobufs/.github/workflows/publish.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
name: Push new version to schema registry
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "**"
|
||||
|
||||
jobs:
|
||||
push_to_registry:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Buf
|
||||
uses: bufbuild/buf-action@v1.2.0
|
||||
with:
|
||||
github_token: ${{ github.token }}
|
||||
token: ${{ secrets.BUF_TOKEN }}
|
||||
setup_only: true
|
||||
|
||||
- name: Push to schema registry
|
||||
env:
|
||||
BUF_TOKEN: ${{ secrets.BUF_TOKEN }}
|
||||
run: |
|
||||
buf push --tag ${{ github.ref_name }}
|
||||
23
packages/protobufs/.github/workflows/pull_request.yml
vendored
Normal file
23
packages/protobufs/.github/workflows/pull_request.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
name: pull-request
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
on: pull_request
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Buf PR Checks
|
||||
uses: bufbuild/buf-action@v1.2.0
|
||||
with:
|
||||
github_token: ${{ github.token }}
|
||||
token: ${{ secrets.BUF_TOKEN }}
|
||||
format: true
|
||||
lint: true
|
||||
breaking: true
|
||||
1
packages/protobufs/.gitignore
vendored
Normal file
1
packages/protobufs/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.DS_Store
|
||||
2
packages/protobufs/.gitmodules
vendored
Normal file
2
packages/protobufs/.gitmodules
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
[submodule "packages/protobufs"]
|
||||
branch = master
|
||||
3
packages/protobufs/.vscode/extensions.json
vendored
Normal file
3
packages/protobufs/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["pbkit.vscode-pbkit", "bufbuild.vscode-buf"]
|
||||
}
|
||||
4
packages/protobufs/.vscode/settings.json
vendored
Normal file
4
packages/protobufs/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "pbkit.vscode-pbkit"
|
||||
}
|
||||
16
packages/protobufs/README.md
Normal file
16
packages/protobufs/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# Meshtastic Protobuf Definitions
|
||||
|
||||
[](https://github.com/meshtastic/protobufs/actions/workflows/ci.yml)
|
||||
[](https://cla-assistant.io/meshtastic/protobufs)
|
||||
[](https://opencollective.com/meshtastic/)
|
||||
[](https://vercel.com?utm_source=meshtastic&utm_campaign=oss)
|
||||
|
||||
## Overview
|
||||
|
||||
The [Protobuf](https://developers.google.com/protocol-buffers) message definitions for the Meshtastic project (used by apps and the device firmware).
|
||||
|
||||
**[Documentation/API Reference](https://buf.build/meshtastic/protobufs)**
|
||||
|
||||
## Stats
|
||||
|
||||

|
||||
5
packages/protobufs/buf.gen.yaml
Normal file
5
packages/protobufs/buf.gen.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
version: v2
|
||||
plugins:
|
||||
- remote: buf.build/bufbuild/es
|
||||
out: packages/ts/dist
|
||||
opt: target=ts
|
||||
15
packages/protobufs/buf.yaml
Normal file
15
packages/protobufs/buf.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
version: v1
|
||||
name: buf.build/meshtastic/protobufs
|
||||
deps: []
|
||||
build:
|
||||
excludes:
|
||||
- node_modules
|
||||
breaking:
|
||||
use:
|
||||
- FILE
|
||||
lint:
|
||||
ignore_only:
|
||||
PACKAGE_DEFINED:
|
||||
- nanopb.proto
|
||||
use:
|
||||
- MINIMAL
|
||||
13
packages/protobufs/deno.json
Normal file
13
packages/protobufs/deno.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "@meshtastic/protobufs",
|
||||
"version": "__PACKAGE_VERSION__",
|
||||
"exports": {
|
||||
".": "./mod.ts"
|
||||
},
|
||||
"imports": {
|
||||
"@bufbuild/protobuf": "npm:@bufbuild/protobuf@^2.9.0"
|
||||
},
|
||||
"publish": {
|
||||
"exclude": ["!dist"]
|
||||
}
|
||||
}
|
||||
19
packages/protobufs/meshtastic/admin.options
Normal file
19
packages/protobufs/meshtastic/admin.options
Normal file
@@ -0,0 +1,19 @@
|
||||
*AdminMessage.payload_variant anonymous_oneof:true
|
||||
|
||||
*AdminMessage.session_passkey max_size:8
|
||||
|
||||
*AdminMessage.InputEvent.event_code int_size:8
|
||||
*AdminMessage.InputEvent.kb_char int_size:8
|
||||
*AdminMessage.InputEvent.touch_x int_size:16
|
||||
*AdminMessage.InputEvent.touch_y int_size:16
|
||||
|
||||
*AdminMessage.set_canned_message_module_messages max_size:201
|
||||
*AdminMessage.get_canned_message_module_messages_response max_size:201
|
||||
*AdminMessage.delete_file_request max_size:201
|
||||
|
||||
*AdminMessage.set_ringtone_message max_size:231
|
||||
*AdminMessage.get_ringtone_response max_size:231
|
||||
|
||||
*HamParameters.call_sign max_size:8
|
||||
*HamParameters.short_name max_size:5
|
||||
*NodeRemoteHardwarePinsResponse.node_remote_hardware_pins max_count:16
|
||||
582
packages/protobufs/meshtastic/admin.proto
Normal file
582
packages/protobufs/meshtastic/admin.proto
Normal file
@@ -0,0 +1,582 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package meshtastic;
|
||||
|
||||
import "meshtastic/channel.proto";
|
||||
import "meshtastic/config.proto";
|
||||
import "meshtastic/connection_status.proto";
|
||||
import "meshtastic/device_ui.proto";
|
||||
import "meshtastic/mesh.proto";
|
||||
import "meshtastic/module_config.proto";
|
||||
|
||||
option csharp_namespace = "Meshtastic.Protobufs";
|
||||
option go_package = "github.com/meshtastic/go/generated";
|
||||
option java_outer_classname = "AdminProtos";
|
||||
option java_package = "com.geeksville.mesh";
|
||||
option swift_prefix = "";
|
||||
|
||||
/*
|
||||
* This message is handled by the Admin module and is responsible for all settings/channel read/write operations.
|
||||
* This message is used to do settings operations to both remote AND local nodes.
|
||||
* (Prior to 1.2 these operations were done via special ToRadio operations)
|
||||
*/
|
||||
message AdminMessage {
|
||||
/*
|
||||
* The node generates this key and sends it with any get_x_response packets.
|
||||
* The client MUST include the same key with any set_x commands. Key expires after 300 seconds.
|
||||
* Prevents replay attacks for admin messages.
|
||||
*/
|
||||
bytes session_passkey = 101;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
enum ConfigType {
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
DEVICE_CONFIG = 0;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
POSITION_CONFIG = 1;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
POWER_CONFIG = 2;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
NETWORK_CONFIG = 3;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
DISPLAY_CONFIG = 4;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
LORA_CONFIG = 5;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
BLUETOOTH_CONFIG = 6;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
SECURITY_CONFIG = 7;
|
||||
|
||||
/*
|
||||
* Session key config
|
||||
*/
|
||||
SESSIONKEY_CONFIG = 8;
|
||||
|
||||
/*
|
||||
* device-ui config
|
||||
*/
|
||||
DEVICEUI_CONFIG = 9;
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
enum ModuleConfigType {
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
MQTT_CONFIG = 0;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
SERIAL_CONFIG = 1;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
EXTNOTIF_CONFIG = 2;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
STOREFORWARD_CONFIG = 3;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
RANGETEST_CONFIG = 4;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
TELEMETRY_CONFIG = 5;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
CANNEDMSG_CONFIG = 6;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
AUDIO_CONFIG = 7;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
REMOTEHARDWARE_CONFIG = 8;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
NEIGHBORINFO_CONFIG = 9;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
AMBIENTLIGHTING_CONFIG = 10;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
DETECTIONSENSOR_CONFIG = 11;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
PAXCOUNTER_CONFIG = 12;
|
||||
}
|
||||
|
||||
enum BackupLocation {
|
||||
/*
|
||||
* Backup to the internal flash
|
||||
*/
|
||||
FLASH = 0;
|
||||
|
||||
/*
|
||||
* Backup to the SD card
|
||||
*/
|
||||
SD = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Input event message to be sent to the node.
|
||||
*/
|
||||
message InputEvent {
|
||||
/*
|
||||
* The input event code
|
||||
*/
|
||||
uint32 event_code = 1;
|
||||
/*
|
||||
* Keyboard character code
|
||||
*/
|
||||
uint32 kb_char = 2;
|
||||
/*
|
||||
* The touch X coordinate
|
||||
*/
|
||||
uint32 touch_x = 3;
|
||||
/*
|
||||
* The touch Y coordinate
|
||||
*/
|
||||
uint32 touch_y = 4;
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
oneof payload_variant {
|
||||
/*
|
||||
* Send the specified channel in the response to this message
|
||||
* NOTE: This field is sent with the channel index + 1 (to ensure we never try to send 'zero' - which protobufs treats as not present)
|
||||
*/
|
||||
uint32 get_channel_request = 1;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
Channel get_channel_response = 2;
|
||||
|
||||
/*
|
||||
* Send the current owner data in the response to this message.
|
||||
*/
|
||||
bool get_owner_request = 3;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
User get_owner_response = 4;
|
||||
|
||||
/*
|
||||
* Ask for the following config data to be sent
|
||||
*/
|
||||
ConfigType get_config_request = 5;
|
||||
|
||||
/*
|
||||
* Send the current Config in the response to this message.
|
||||
*/
|
||||
Config get_config_response = 6;
|
||||
|
||||
/*
|
||||
* Ask for the following config data to be sent
|
||||
*/
|
||||
ModuleConfigType get_module_config_request = 7;
|
||||
|
||||
/*
|
||||
* Send the current Config in the response to this message.
|
||||
*/
|
||||
ModuleConfig get_module_config_response = 8;
|
||||
|
||||
/*
|
||||
* Get the Canned Message Module messages in the response to this message.
|
||||
*/
|
||||
bool get_canned_message_module_messages_request = 10;
|
||||
|
||||
/*
|
||||
* Get the Canned Message Module messages in the response to this message.
|
||||
*/
|
||||
string get_canned_message_module_messages_response = 11;
|
||||
|
||||
/*
|
||||
* Request the node to send device metadata (firmware, protobuf version, etc)
|
||||
*/
|
||||
bool get_device_metadata_request = 12;
|
||||
|
||||
/*
|
||||
* Device metadata response
|
||||
*/
|
||||
DeviceMetadata get_device_metadata_response = 13;
|
||||
|
||||
/*
|
||||
* Get the Ringtone in the response to this message.
|
||||
*/
|
||||
bool get_ringtone_request = 14;
|
||||
|
||||
/*
|
||||
* Get the Ringtone in the response to this message.
|
||||
*/
|
||||
string get_ringtone_response = 15;
|
||||
|
||||
/*
|
||||
* Request the node to send it's connection status
|
||||
*/
|
||||
bool get_device_connection_status_request = 16;
|
||||
|
||||
/*
|
||||
* Device connection status response
|
||||
*/
|
||||
DeviceConnectionStatus get_device_connection_status_response = 17;
|
||||
|
||||
/*
|
||||
* Setup a node for licensed amateur (ham) radio operation
|
||||
*/
|
||||
HamParameters set_ham_mode = 18;
|
||||
|
||||
/*
|
||||
* Get the mesh's nodes with their available gpio pins for RemoteHardware module use
|
||||
*/
|
||||
bool get_node_remote_hardware_pins_request = 19;
|
||||
|
||||
/*
|
||||
* Respond with the mesh's nodes with their available gpio pins for RemoteHardware module use
|
||||
*/
|
||||
NodeRemoteHardwarePinsResponse get_node_remote_hardware_pins_response = 20;
|
||||
|
||||
/*
|
||||
* Enter (UF2) DFU mode
|
||||
* Only implemented on NRF52 currently
|
||||
*/
|
||||
bool enter_dfu_mode_request = 21;
|
||||
|
||||
/*
|
||||
* Delete the file by the specified path from the device
|
||||
*/
|
||||
string delete_file_request = 22;
|
||||
|
||||
/*
|
||||
* Set zero and offset for scale chips
|
||||
*/
|
||||
uint32 set_scale = 23;
|
||||
|
||||
/*
|
||||
* Backup the node's preferences
|
||||
*/
|
||||
BackupLocation backup_preferences = 24;
|
||||
|
||||
/*
|
||||
* Restore the node's preferences
|
||||
*/
|
||||
BackupLocation restore_preferences = 25;
|
||||
|
||||
/*
|
||||
* Remove backups of the node's preferences
|
||||
*/
|
||||
BackupLocation remove_backup_preferences = 26;
|
||||
|
||||
/*
|
||||
* Send an input event to the node.
|
||||
* This is used to trigger physical input events like button presses, touch events, etc.
|
||||
*/
|
||||
InputEvent send_input_event = 27;
|
||||
|
||||
/*
|
||||
* Set the owner for this node
|
||||
*/
|
||||
User set_owner = 32;
|
||||
|
||||
/*
|
||||
* Set channels (using the new API).
|
||||
* A special channel is the "primary channel".
|
||||
* The other records are secondary channels.
|
||||
* Note: only one channel can be marked as primary.
|
||||
* If the client sets a particular channel to be primary, the previous channel will be set to SECONDARY automatically.
|
||||
*/
|
||||
Channel set_channel = 33;
|
||||
|
||||
/*
|
||||
* Set the current Config
|
||||
*/
|
||||
Config set_config = 34;
|
||||
|
||||
/*
|
||||
* Set the current Config
|
||||
*/
|
||||
ModuleConfig set_module_config = 35;
|
||||
|
||||
/*
|
||||
* Set the Canned Message Module messages text.
|
||||
*/
|
||||
string set_canned_message_module_messages = 36;
|
||||
|
||||
/*
|
||||
* Set the ringtone for ExternalNotification.
|
||||
*/
|
||||
string set_ringtone_message = 37;
|
||||
|
||||
/*
|
||||
* Remove the node by the specified node-num from the NodeDB on the device
|
||||
*/
|
||||
uint32 remove_by_nodenum = 38;
|
||||
|
||||
/*
|
||||
* Set specified node-num to be favorited on the NodeDB on the device
|
||||
*/
|
||||
uint32 set_favorite_node = 39;
|
||||
|
||||
/*
|
||||
* Set specified node-num to be un-favorited on the NodeDB on the device
|
||||
*/
|
||||
uint32 remove_favorite_node = 40;
|
||||
|
||||
/*
|
||||
* Set fixed position data on the node and then set the position.fixed_position = true
|
||||
*/
|
||||
Position set_fixed_position = 41;
|
||||
|
||||
/*
|
||||
* Clear fixed position coordinates and then set position.fixed_position = false
|
||||
*/
|
||||
bool remove_fixed_position = 42;
|
||||
|
||||
/*
|
||||
* Set time only on the node
|
||||
* Convenience method to set the time on the node (as Net quality) without any other position data
|
||||
*/
|
||||
fixed32 set_time_only = 43;
|
||||
|
||||
/*
|
||||
* Tell the node to send the stored ui data.
|
||||
*/
|
||||
bool get_ui_config_request = 44;
|
||||
|
||||
/*
|
||||
* Reply stored device ui data.
|
||||
*/
|
||||
DeviceUIConfig get_ui_config_response = 45;
|
||||
|
||||
/*
|
||||
* Tell the node to store UI data persistently.
|
||||
*/
|
||||
DeviceUIConfig store_ui_config = 46;
|
||||
|
||||
/*
|
||||
* Set specified node-num to be ignored on the NodeDB on the device
|
||||
*/
|
||||
uint32 set_ignored_node = 47;
|
||||
|
||||
/*
|
||||
* Set specified node-num to be un-ignored on the NodeDB on the device
|
||||
*/
|
||||
uint32 remove_ignored_node = 48;
|
||||
|
||||
/*
|
||||
* Begins an edit transaction for config, module config, owner, and channel settings changes
|
||||
* This will delay the standard *implicit* save to the file system and subsequent reboot behavior until committed (commit_edit_settings)
|
||||
*/
|
||||
bool begin_edit_settings = 64;
|
||||
|
||||
/*
|
||||
* Commits an open transaction for any edits made to config, module config, owner, and channel settings
|
||||
*/
|
||||
bool commit_edit_settings = 65;
|
||||
|
||||
/*
|
||||
* Add a contact (User) to the nodedb
|
||||
*/
|
||||
SharedContact add_contact = 66;
|
||||
|
||||
/*
|
||||
* Initiate or respond to a key verification request
|
||||
*/
|
||||
KeyVerificationAdmin key_verification = 67;
|
||||
|
||||
/*
|
||||
* Tell the node to factory reset config everything; all device state and configuration will be returned to factory defaults and BLE bonds will be cleared.
|
||||
*/
|
||||
int32 factory_reset_device = 94;
|
||||
|
||||
/*
|
||||
* Tell the node to reboot into the OTA Firmware in this many seconds (or <0 to cancel reboot)
|
||||
* Only Implemented for ESP32 Devices. This needs to be issued to send a new main firmware via bluetooth.
|
||||
*/
|
||||
int32 reboot_ota_seconds = 95;
|
||||
|
||||
/*
|
||||
* This message is only supported for the simulator Portduino build.
|
||||
* If received the simulator will exit successfully.
|
||||
*/
|
||||
bool exit_simulator = 96;
|
||||
|
||||
/*
|
||||
* Tell the node to reboot in this many seconds (or <0 to cancel reboot)
|
||||
*/
|
||||
int32 reboot_seconds = 97;
|
||||
|
||||
/*
|
||||
* Tell the node to shutdown in this many seconds (or <0 to cancel shutdown)
|
||||
*/
|
||||
int32 shutdown_seconds = 98;
|
||||
|
||||
/*
|
||||
* Tell the node to factory reset config; all device state and configuration will be returned to factory defaults; BLE bonds will be preserved.
|
||||
*/
|
||||
int32 factory_reset_config = 99;
|
||||
|
||||
/*
|
||||
* Tell the node to reset the nodedb.
|
||||
*/
|
||||
int32 nodedb_reset = 100;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Parameters for setting up Meshtastic for ameteur radio usage
|
||||
*/
|
||||
message HamParameters {
|
||||
/*
|
||||
* Amateur radio call sign, eg. KD2ABC
|
||||
*/
|
||||
string call_sign = 1;
|
||||
|
||||
/*
|
||||
* Transmit power in dBm at the LoRA transceiver, not including any amplification
|
||||
*/
|
||||
int32 tx_power = 2;
|
||||
|
||||
/*
|
||||
* The selected frequency of LoRA operation
|
||||
* Please respect your local laws, regulations, and band plans.
|
||||
* Ensure your radio is capable of operating of the selected frequency before setting this.
|
||||
*/
|
||||
float frequency = 3;
|
||||
|
||||
/*
|
||||
* Optional short name of user
|
||||
*/
|
||||
string short_name = 4;
|
||||
}
|
||||
|
||||
/*
|
||||
* Response envelope for node_remote_hardware_pins
|
||||
*/
|
||||
message NodeRemoteHardwarePinsResponse {
|
||||
/*
|
||||
* Nodes and their respective remote hardware GPIO pins
|
||||
*/
|
||||
repeated NodeRemoteHardwarePin node_remote_hardware_pins = 1;
|
||||
}
|
||||
|
||||
message SharedContact {
|
||||
/*
|
||||
* The node number of the contact
|
||||
*/
|
||||
uint32 node_num = 1;
|
||||
|
||||
/*
|
||||
* The User of the contact
|
||||
*/
|
||||
User user = 2;
|
||||
|
||||
/*
|
||||
* Add this contact to the blocked / ignored list
|
||||
*/
|
||||
bool should_ignore = 3;
|
||||
|
||||
/*
|
||||
* Set the IS_KEY_MANUALLY_VERIFIED bit
|
||||
*/
|
||||
bool manually_verified = 4;
|
||||
}
|
||||
|
||||
/*
|
||||
* This message is used by a client to initiate or complete a key verification
|
||||
*/
|
||||
message KeyVerificationAdmin {
|
||||
/*
|
||||
* Three stages of this request.
|
||||
*/
|
||||
enum MessageType {
|
||||
/*
|
||||
* This is the first stage, where a client initiates
|
||||
*/
|
||||
INITIATE_VERIFICATION = 0;
|
||||
|
||||
/*
|
||||
* After the nonce has been returned over the mesh, the client prompts for the security number
|
||||
* And uses this message to provide it to the node.
|
||||
*/
|
||||
PROVIDE_SECURITY_NUMBER = 1;
|
||||
|
||||
/*
|
||||
* Once the user has compared the verification message, this message notifies the node.
|
||||
*/
|
||||
DO_VERIFY = 2;
|
||||
|
||||
/*
|
||||
* This is the cancel path, can be taken at any point
|
||||
*/
|
||||
DO_NOT_VERIFY = 3;
|
||||
}
|
||||
|
||||
MessageType message_type = 1;
|
||||
|
||||
/*
|
||||
* The nodenum we're requesting
|
||||
*/
|
||||
uint32 remote_nodenum = 2;
|
||||
|
||||
/*
|
||||
* The nonce is used to track the connection
|
||||
*/
|
||||
uint64 nonce = 3;
|
||||
|
||||
/*
|
||||
* The 4 digit code generated by the remote node, and communicated outside the mesh
|
||||
*/
|
||||
optional uint32 security_number = 4;
|
||||
}
|
||||
1
packages/protobufs/meshtastic/apponly.options
Normal file
1
packages/protobufs/meshtastic/apponly.options
Normal file
@@ -0,0 +1 @@
|
||||
*ChannelSet.settings max_count:8
|
||||
31
packages/protobufs/meshtastic/apponly.proto
Normal file
31
packages/protobufs/meshtastic/apponly.proto
Normal file
@@ -0,0 +1,31 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package meshtastic;
|
||||
|
||||
import "meshtastic/channel.proto";
|
||||
import "meshtastic/config.proto";
|
||||
|
||||
option csharp_namespace = "Meshtastic.Protobufs";
|
||||
option go_package = "github.com/meshtastic/go/generated";
|
||||
option java_outer_classname = "AppOnlyProtos";
|
||||
option java_package = "com.geeksville.mesh";
|
||||
option swift_prefix = "";
|
||||
|
||||
/*
|
||||
* This is the most compact possible representation for a set of channels.
|
||||
* It includes only one PRIMARY channel (which must be first) and
|
||||
* any SECONDARY channels.
|
||||
* No DISABLED channels are included.
|
||||
* This abstraction is used only on the the 'app side' of the world (ie python, javascript and android etc) to show a group of Channels as a (long) URL
|
||||
*/
|
||||
message ChannelSet {
|
||||
/*
|
||||
* Channel list with settings
|
||||
*/
|
||||
repeated ChannelSettings settings = 1;
|
||||
|
||||
/*
|
||||
* LoRa config
|
||||
*/
|
||||
Config.LoRaConfig lora_config = 2;
|
||||
}
|
||||
8
packages/protobufs/meshtastic/atak.options
Normal file
8
packages/protobufs/meshtastic/atak.options
Normal file
@@ -0,0 +1,8 @@
|
||||
*Contact.callsign max_size:120
|
||||
*Contact.device_callsign max_size:120
|
||||
*Status.battery int_size:8
|
||||
*PLI.course int_size:16
|
||||
*GeoChat.message max_size:200
|
||||
*GeoChat.to max_size:120
|
||||
*GeoChat.to_callsign max_size:120
|
||||
*TAKPacket.detail max_size:220
|
||||
263
packages/protobufs/meshtastic/atak.proto
Normal file
263
packages/protobufs/meshtastic/atak.proto
Normal file
@@ -0,0 +1,263 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package meshtastic;
|
||||
|
||||
option csharp_namespace = "Meshtastic.Protobufs";
|
||||
option go_package = "github.com/meshtastic/go/generated";
|
||||
option java_outer_classname = "ATAKProtos";
|
||||
option java_package = "com.geeksville.mesh";
|
||||
option swift_prefix = "";
|
||||
|
||||
/*
|
||||
* Packets for the official ATAK Plugin
|
||||
*/
|
||||
message TAKPacket {
|
||||
/*
|
||||
* Are the payloads strings compressed for LoRA transport?
|
||||
*/
|
||||
bool is_compressed = 1;
|
||||
/*
|
||||
* The contact / callsign for ATAK user
|
||||
*/
|
||||
Contact contact = 2;
|
||||
/*
|
||||
* The group for ATAK user
|
||||
*/
|
||||
Group group = 3;
|
||||
/*
|
||||
* The status of the ATAK EUD
|
||||
*/
|
||||
Status status = 4;
|
||||
/*
|
||||
* The payload of the packet
|
||||
*/
|
||||
oneof payload_variant {
|
||||
/*
|
||||
* TAK position report
|
||||
*/
|
||||
PLI pli = 5;
|
||||
/*
|
||||
* ATAK GeoChat message
|
||||
*/
|
||||
GeoChat chat = 6;
|
||||
|
||||
/*
|
||||
* Generic CoT detail XML
|
||||
* May be compressed / truncated by the sender (EUD)
|
||||
*/
|
||||
bytes detail = 7;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* ATAK GeoChat message
|
||||
*/
|
||||
message GeoChat {
|
||||
/*
|
||||
* The text message
|
||||
*/
|
||||
string message = 1;
|
||||
|
||||
/*
|
||||
* Uid recipient of the message
|
||||
*/
|
||||
optional string to = 2;
|
||||
|
||||
/*
|
||||
* Callsign of the recipient for the message
|
||||
*/
|
||||
optional string to_callsign = 3;
|
||||
}
|
||||
|
||||
/*
|
||||
* ATAK Group
|
||||
* <__group role='Team Member' name='Cyan'/>
|
||||
*/
|
||||
message Group {
|
||||
/*
|
||||
* Role of the group member
|
||||
*/
|
||||
MemberRole role = 1;
|
||||
/*
|
||||
* Team (color)
|
||||
* Default Cyan
|
||||
*/
|
||||
Team team = 2;
|
||||
}
|
||||
|
||||
enum Team {
|
||||
/*
|
||||
* Unspecifed
|
||||
*/
|
||||
Unspecifed_Color = 0;
|
||||
/*
|
||||
* White
|
||||
*/
|
||||
White = 1;
|
||||
/*
|
||||
* Yellow
|
||||
*/
|
||||
Yellow = 2;
|
||||
/*
|
||||
* Orange
|
||||
*/
|
||||
Orange = 3;
|
||||
/*
|
||||
* Magenta
|
||||
*/
|
||||
Magenta = 4;
|
||||
/*
|
||||
* Red
|
||||
*/
|
||||
Red = 5;
|
||||
/*
|
||||
* Maroon
|
||||
*/
|
||||
Maroon = 6;
|
||||
/*
|
||||
* Purple
|
||||
*/
|
||||
Purple = 7;
|
||||
/*
|
||||
* Dark Blue
|
||||
*/
|
||||
Dark_Blue = 8;
|
||||
/*
|
||||
* Blue
|
||||
*/
|
||||
Blue = 9;
|
||||
/*
|
||||
* Cyan
|
||||
*/
|
||||
Cyan = 10;
|
||||
/*
|
||||
* Teal
|
||||
*/
|
||||
Teal = 11;
|
||||
/*
|
||||
* Green
|
||||
*/
|
||||
Green = 12;
|
||||
/*
|
||||
* Dark Green
|
||||
*/
|
||||
Dark_Green = 13;
|
||||
/*
|
||||
* Brown
|
||||
*/
|
||||
Brown = 14;
|
||||
}
|
||||
|
||||
/*
|
||||
* Role of the group member
|
||||
*/
|
||||
enum MemberRole {
|
||||
/*
|
||||
* Unspecifed
|
||||
*/
|
||||
Unspecifed = 0;
|
||||
/*
|
||||
* Team Member
|
||||
*/
|
||||
TeamMember = 1;
|
||||
/*
|
||||
* Team Lead
|
||||
*/
|
||||
TeamLead = 2;
|
||||
/*
|
||||
* Headquarters
|
||||
*/
|
||||
HQ = 3;
|
||||
/*
|
||||
* Airsoft enthusiast
|
||||
*/
|
||||
Sniper = 4;
|
||||
/*
|
||||
* Medic
|
||||
*/
|
||||
Medic = 5;
|
||||
/*
|
||||
* ForwardObserver
|
||||
*/
|
||||
ForwardObserver = 6;
|
||||
/*
|
||||
* Radio Telephone Operator
|
||||
*/
|
||||
RTO = 7;
|
||||
/*
|
||||
* Doggo
|
||||
*/
|
||||
K9 = 8;
|
||||
}
|
||||
|
||||
/*
|
||||
* ATAK EUD Status
|
||||
* <status battery='100' />
|
||||
*/
|
||||
message Status {
|
||||
/*
|
||||
* Battery level
|
||||
*/
|
||||
uint32 battery = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* ATAK Contact
|
||||
* <contact endpoint='0.0.0.0:4242:tcp' phone='+12345678' callsign='FALKE'/>
|
||||
*/
|
||||
message Contact {
|
||||
/*
|
||||
* Callsign
|
||||
*/
|
||||
string callsign = 1;
|
||||
|
||||
/*
|
||||
* Device callsign
|
||||
*/
|
||||
string device_callsign = 2;
|
||||
/*
|
||||
* IP address of endpoint in integer form (0.0.0.0 default)
|
||||
*/
|
||||
// fixed32 enpoint_address = 3;
|
||||
/*
|
||||
* Port of endpoint (4242 default)
|
||||
*/
|
||||
// uint32 endpoint_port = 4;
|
||||
/*
|
||||
* Phone represented as integer
|
||||
* Terrible practice, but we really need the wire savings
|
||||
*/
|
||||
// uint32 phone = 4;
|
||||
}
|
||||
|
||||
/*
|
||||
* Position Location Information from ATAK
|
||||
*/
|
||||
message PLI {
|
||||
/*
|
||||
* The new preferred location encoding, multiply by 1e-7 to get degrees
|
||||
* in floating point
|
||||
*/
|
||||
sfixed32 latitude_i = 1;
|
||||
|
||||
/*
|
||||
* The new preferred location encoding, multiply by 1e-7 to get degrees
|
||||
* in floating point
|
||||
*/
|
||||
sfixed32 longitude_i = 2;
|
||||
|
||||
/*
|
||||
* Altitude (ATAK prefers HAE)
|
||||
*/
|
||||
int32 altitude = 3;
|
||||
|
||||
/*
|
||||
* Speed
|
||||
*/
|
||||
uint32 speed = 4;
|
||||
|
||||
/*
|
||||
* Course in degrees
|
||||
*/
|
||||
uint32 course = 5;
|
||||
}
|
||||
1
packages/protobufs/meshtastic/cannedmessages.options
Normal file
1
packages/protobufs/meshtastic/cannedmessages.options
Normal file
@@ -0,0 +1 @@
|
||||
*CannedMessageModuleConfig.messages max_size:201
|
||||
19
packages/protobufs/meshtastic/cannedmessages.proto
Normal file
19
packages/protobufs/meshtastic/cannedmessages.proto
Normal file
@@ -0,0 +1,19 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package meshtastic;
|
||||
|
||||
option csharp_namespace = "Meshtastic.Protobufs";
|
||||
option go_package = "github.com/meshtastic/go/generated";
|
||||
option java_outer_classname = "CannedMessageConfigProtos";
|
||||
option java_package = "com.geeksville.mesh";
|
||||
option swift_prefix = "";
|
||||
|
||||
/*
|
||||
* Canned message module configuration.
|
||||
*/
|
||||
message CannedMessageModuleConfig {
|
||||
/*
|
||||
* Predefined messages for canned message module separated by '|' characters.
|
||||
*/
|
||||
string messages = 1;
|
||||
}
|
||||
5
packages/protobufs/meshtastic/channel.options
Normal file
5
packages/protobufs/meshtastic/channel.options
Normal file
@@ -0,0 +1,5 @@
|
||||
*Channel.index int_size:8
|
||||
|
||||
# 256 bit or 128 bit psk key
|
||||
*ChannelSettings.psk max_size:32
|
||||
*ChannelSettings.name max_size:12
|
||||
161
packages/protobufs/meshtastic/channel.proto
Normal file
161
packages/protobufs/meshtastic/channel.proto
Normal file
@@ -0,0 +1,161 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package meshtastic;
|
||||
|
||||
option csharp_namespace = "Meshtastic.Protobufs";
|
||||
option go_package = "github.com/meshtastic/go/generated";
|
||||
option java_outer_classname = "ChannelProtos";
|
||||
option java_package = "com.geeksville.mesh";
|
||||
option swift_prefix = "";
|
||||
|
||||
/*
|
||||
* This information can be encoded as a QRcode/url so that other users can configure
|
||||
* their radio to join the same channel.
|
||||
* A note about how channel names are shown to users: channelname-X
|
||||
* poundsymbol is a prefix used to indicate this is a channel name (idea from @professr).
|
||||
* Where X is a letter from A-Z (base 26) representing a hash of the PSK for this
|
||||
* channel - so that if the user changes anything about the channel (which does
|
||||
* force a new PSK) this letter will also change. Thus preventing user confusion if
|
||||
* two friends try to type in a channel name of "BobsChan" and then can't talk
|
||||
* because their PSKs will be different.
|
||||
* The PSK is hashed into this letter by "0x41 + [xor all bytes of the psk ] modulo 26"
|
||||
* This also allows the option of someday if people have the PSK off (zero), the
|
||||
* users COULD type in a channel name and be able to talk.
|
||||
* FIXME: Add description of multi-channel support and how primary vs secondary channels are used.
|
||||
* FIXME: explain how apps use channels for security.
|
||||
* explain how remote settings and remote gpio are managed as an example
|
||||
*/
|
||||
message ChannelSettings {
|
||||
/*
|
||||
* Deprecated in favor of LoraConfig.channel_num
|
||||
*/
|
||||
uint32 channel_num = 1 [deprecated = true];
|
||||
|
||||
/*
|
||||
* A simple pre-shared key for now for crypto.
|
||||
* Must be either 0 bytes (no crypto), 16 bytes (AES128), or 32 bytes (AES256).
|
||||
* A special shorthand is used for 1 byte long psks.
|
||||
* These psks should be treated as only minimally secure,
|
||||
* because they are listed in this source code.
|
||||
* Those bytes are mapped using the following scheme:
|
||||
* `0` = No crypto
|
||||
* `1` = The special "default" channel key: {0xd4, 0xf1, 0xbb, 0x3a, 0x20, 0x29, 0x07, 0x59, 0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0x01}
|
||||
* `2` through 10 = The default channel key, except with 1 through 9 added to the last byte.
|
||||
* Shown to user as simple1 through 10
|
||||
*/
|
||||
bytes psk = 2;
|
||||
|
||||
/*
|
||||
* A SHORT name that will be packed into the URL.
|
||||
* Less than 12 bytes.
|
||||
* Something for end users to call the channel
|
||||
* If this is the empty string it is assumed that this channel
|
||||
* is the special (minimally secure) "Default"channel.
|
||||
* In user interfaces it should be rendered as a local language translation of "X".
|
||||
* For channel_num hashing empty string will be treated as "X".
|
||||
* Where "X" is selected based on the English words listed above for ModemPreset
|
||||
*/
|
||||
string name = 3;
|
||||
|
||||
/*
|
||||
* Used to construct a globally unique channel ID.
|
||||
* The full globally unique ID will be: "name.id" where ID is shown as base36.
|
||||
* Assuming that the number of meshtastic users is below 20K (true for a long time)
|
||||
* the chance of this 64 bit random number colliding with anyone else is super low.
|
||||
* And the penalty for collision is low as well, it just means that anyone trying to decrypt channel messages might need to
|
||||
* try multiple candidate channels.
|
||||
* Any time a non wire compatible change is made to a channel, this field should be regenerated.
|
||||
* There are a small number of 'special' globally known (and fairly) insecure standard channels.
|
||||
* Those channels do not have a numeric id included in the settings, but instead it is pulled from
|
||||
* a table of well known IDs.
|
||||
* (see Well Known Channels FIXME)
|
||||
*/
|
||||
fixed32 id = 4;
|
||||
|
||||
/*
|
||||
* If true, messages on the mesh will be sent to the *public* internet by any gateway ndoe
|
||||
*/
|
||||
bool uplink_enabled = 5;
|
||||
|
||||
/*
|
||||
* If true, messages seen on the internet will be forwarded to the local mesh.
|
||||
*/
|
||||
bool downlink_enabled = 6;
|
||||
|
||||
/*
|
||||
* Per-channel module settings.
|
||||
*/
|
||||
ModuleSettings module_settings = 7;
|
||||
|
||||
/*
|
||||
* Whether or not we should receive notifactions / alerts through this channel
|
||||
*/
|
||||
bool mute = 8;
|
||||
}
|
||||
|
||||
/*
|
||||
* This message is specifically for modules to store per-channel configuration data.
|
||||
*/
|
||||
message ModuleSettings {
|
||||
/*
|
||||
* Bits of precision for the location sent in position packets.
|
||||
*/
|
||||
uint32 position_precision = 1;
|
||||
|
||||
/*
|
||||
* Controls whether or not the phone / clients should mute the current channel
|
||||
* Useful for noisy public channels you don't necessarily want to disable
|
||||
*/
|
||||
bool is_client_muted = 2;
|
||||
}
|
||||
|
||||
/*
|
||||
* A pair of a channel number, mode and the (sharable) settings for that channel
|
||||
*/
|
||||
message Channel {
|
||||
/*
|
||||
* How this channel is being used (or not).
|
||||
* Note: this field is an enum to give us options for the future.
|
||||
* In particular, someday we might make a 'SCANNING' option.
|
||||
* SCANNING channels could have different frequencies and the radio would
|
||||
* occasionally check that freq to see if anything is being transmitted.
|
||||
* For devices that have multiple physical radios attached, we could keep multiple PRIMARY/SCANNING channels active at once to allow
|
||||
* cross band routing as needed.
|
||||
* If a device has only a single radio (the common case) only one channel can be PRIMARY at a time
|
||||
* (but any number of SECONDARY channels can't be sent received on that common frequency)
|
||||
*/
|
||||
enum Role {
|
||||
/*
|
||||
* This channel is not in use right now
|
||||
*/
|
||||
DISABLED = 0;
|
||||
|
||||
/*
|
||||
* This channel is used to set the frequency for the radio - all other enabled channels must be SECONDARY
|
||||
*/
|
||||
PRIMARY = 1;
|
||||
|
||||
/*
|
||||
* Secondary channels are only used for encryption/decryption/authentication purposes.
|
||||
* Their radio settings (freq etc) are ignored, only psk is used.
|
||||
*/
|
||||
SECONDARY = 2;
|
||||
}
|
||||
|
||||
/*
|
||||
* The index of this channel in the channel table (from 0 to MAX_NUM_CHANNELS-1)
|
||||
* (Someday - not currently implemented) An index of -1 could be used to mean "set by name",
|
||||
* in which case the target node will find and set the channel by settings.name.
|
||||
*/
|
||||
int32 index = 1;
|
||||
|
||||
/*
|
||||
* The new settings, or NULL to disable that channel
|
||||
*/
|
||||
ChannelSettings settings = 2;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
Role role = 3;
|
||||
}
|
||||
4
packages/protobufs/meshtastic/clientonly.options
Normal file
4
packages/protobufs/meshtastic/clientonly.options
Normal file
@@ -0,0 +1,4 @@
|
||||
*DeviceProfile.long_name max_size:40
|
||||
*DeviceProfile.short_name max_size:5
|
||||
*DeviceProfile.ringtone max_size:231
|
||||
*DeviceProfile.canned_messages max_size:201
|
||||
58
packages/protobufs/meshtastic/clientonly.proto
Normal file
58
packages/protobufs/meshtastic/clientonly.proto
Normal file
@@ -0,0 +1,58 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package meshtastic;
|
||||
|
||||
import "meshtastic/localonly.proto";
|
||||
import "meshtastic/mesh.proto";
|
||||
|
||||
option csharp_namespace = "Meshtastic.Protobufs";
|
||||
option go_package = "github.com/meshtastic/go/generated";
|
||||
option java_outer_classname = "ClientOnlyProtos";
|
||||
option java_package = "com.geeksville.mesh";
|
||||
option swift_prefix = "";
|
||||
|
||||
/*
|
||||
* This abstraction is used to contain any configuration for provisioning a node on any client.
|
||||
* It is useful for importing and exporting configurations.
|
||||
*/
|
||||
message DeviceProfile {
|
||||
/*
|
||||
* Long name for the node
|
||||
*/
|
||||
optional string long_name = 1;
|
||||
|
||||
/*
|
||||
* Short name of the node
|
||||
*/
|
||||
optional string short_name = 2;
|
||||
|
||||
/*
|
||||
* The url of the channels from our node
|
||||
*/
|
||||
optional string channel_url = 3;
|
||||
|
||||
/*
|
||||
* The Config of the node
|
||||
*/
|
||||
optional LocalConfig config = 4;
|
||||
|
||||
/*
|
||||
* The ModuleConfig of the node
|
||||
*/
|
||||
optional LocalModuleConfig module_config = 5;
|
||||
|
||||
/*
|
||||
* Fixed position data
|
||||
*/
|
||||
optional Position fixed_position = 6;
|
||||
|
||||
/*
|
||||
* Ringtone for ExternalNotification
|
||||
*/
|
||||
optional string ringtone = 7;
|
||||
|
||||
/*
|
||||
* Predefined messages for CannedMessage
|
||||
*/
|
||||
optional string canned_messages = 8;
|
||||
}
|
||||
24
packages/protobufs/meshtastic/config.options
Normal file
24
packages/protobufs/meshtastic/config.options
Normal file
@@ -0,0 +1,24 @@
|
||||
# longest current is 45 chars, plan with a bit of buffer
|
||||
*DeviceConfig.tzdef max_size:65
|
||||
*DeviceConfig.buzzer_mode int_size:8
|
||||
|
||||
|
||||
*NetworkConfig.wifi_ssid max_size:33
|
||||
*NetworkConfig.wifi_psk max_size:65
|
||||
*NetworkConfig.ntp_server max_size:33
|
||||
*NetworkConfig.rsyslog_server max_size:33
|
||||
|
||||
# Max of three ignored nodes for our testing
|
||||
*LoRaConfig.ignore_incoming max_count:3
|
||||
|
||||
*LoRaConfig.tx_power int_size:8
|
||||
*LoRaConfig.bandwidth int_size:16
|
||||
*LoRaConfig.coding_rate int_size:8
|
||||
*LoRaConfig.channel_num int_size:16
|
||||
|
||||
*PowerConfig.device_battery_ina_address int_size:8
|
||||
|
||||
*SecurityConfig.public_key max_size:32
|
||||
*SecurityConfig.private_key max_size:32
|
||||
*SecurityConfig.admin_key max_size:32
|
||||
*SecurityConfig.admin_key max_count:3
|
||||
1200
packages/protobufs/meshtastic/config.proto
Normal file
1200
packages/protobufs/meshtastic/config.proto
Normal file
File diff suppressed because it is too large
Load Diff
1
packages/protobufs/meshtastic/connection_status.options
Normal file
1
packages/protobufs/meshtastic/connection_status.options
Normal file
@@ -0,0 +1 @@
|
||||
*WifiConnectionStatus.ssid max_size:33
|
||||
120
packages/protobufs/meshtastic/connection_status.proto
Normal file
120
packages/protobufs/meshtastic/connection_status.proto
Normal file
@@ -0,0 +1,120 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package meshtastic;
|
||||
|
||||
option csharp_namespace = "Meshtastic.Protobufs";
|
||||
option go_package = "github.com/meshtastic/go/generated";
|
||||
option java_outer_classname = "ConnStatusProtos";
|
||||
option java_package = "com.geeksville.mesh";
|
||||
option swift_prefix = "";
|
||||
|
||||
message DeviceConnectionStatus {
|
||||
/*
|
||||
* WiFi Status
|
||||
*/
|
||||
optional WifiConnectionStatus wifi = 1;
|
||||
/*
|
||||
* WiFi Status
|
||||
*/
|
||||
optional EthernetConnectionStatus ethernet = 2;
|
||||
|
||||
/*
|
||||
* Bluetooth Status
|
||||
*/
|
||||
optional BluetoothConnectionStatus bluetooth = 3;
|
||||
|
||||
/*
|
||||
* Serial Status
|
||||
*/
|
||||
optional SerialConnectionStatus serial = 4;
|
||||
}
|
||||
|
||||
/*
|
||||
* WiFi connection status
|
||||
*/
|
||||
message WifiConnectionStatus {
|
||||
/*
|
||||
* Connection status
|
||||
*/
|
||||
NetworkConnectionStatus status = 1;
|
||||
|
||||
/*
|
||||
* WiFi access point SSID
|
||||
*/
|
||||
string ssid = 2;
|
||||
|
||||
/*
|
||||
* RSSI of wireless connection
|
||||
*/
|
||||
int32 rssi = 3;
|
||||
}
|
||||
|
||||
/*
|
||||
* Ethernet connection status
|
||||
*/
|
||||
message EthernetConnectionStatus {
|
||||
/*
|
||||
* Connection status
|
||||
*/
|
||||
NetworkConnectionStatus status = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Ethernet or WiFi connection status
|
||||
*/
|
||||
message NetworkConnectionStatus {
|
||||
/*
|
||||
* IP address of device
|
||||
*/
|
||||
fixed32 ip_address = 1;
|
||||
|
||||
/*
|
||||
* Whether the device has an active connection or not
|
||||
*/
|
||||
bool is_connected = 2;
|
||||
|
||||
/*
|
||||
* Whether the device has an active connection to an MQTT broker or not
|
||||
*/
|
||||
bool is_mqtt_connected = 3;
|
||||
|
||||
/*
|
||||
* Whether the device is actively remote syslogging or not
|
||||
*/
|
||||
bool is_syslog_connected = 4;
|
||||
}
|
||||
|
||||
/*
|
||||
* Bluetooth connection status
|
||||
*/
|
||||
message BluetoothConnectionStatus {
|
||||
/*
|
||||
* The pairing PIN for bluetooth
|
||||
*/
|
||||
uint32 pin = 1;
|
||||
|
||||
/*
|
||||
* RSSI of bluetooth connection
|
||||
*/
|
||||
int32 rssi = 2;
|
||||
|
||||
/*
|
||||
* Whether the device has an active connection or not
|
||||
*/
|
||||
bool is_connected = 3;
|
||||
}
|
||||
|
||||
/*
|
||||
* Serial connection status
|
||||
*/
|
||||
message SerialConnectionStatus {
|
||||
/*
|
||||
* Serial baud rate
|
||||
*/
|
||||
uint32 baud = 1;
|
||||
|
||||
/*
|
||||
* Whether the device has an active connection or not
|
||||
*/
|
||||
bool is_connected = 2;
|
||||
}
|
||||
12
packages/protobufs/meshtastic/device_ui.options
Normal file
12
packages/protobufs/meshtastic/device_ui.options
Normal file
@@ -0,0 +1,12 @@
|
||||
*DeviceUIConfig.screen_brightness int_size:8
|
||||
*DeviceUIConfig.screen_timeout int_size:16
|
||||
*DeviceUIConfig.ring_tone_id int_size:8
|
||||
*DeviceUIConfig.calibration_data max_size:16
|
||||
*DeviceUIConfig.compass_mode int_size:8
|
||||
*DeviceUIConfig.gps_format int_size:8
|
||||
*NodeFilter.node_name max_size:16
|
||||
*NodeFilter.hops_away int_size:8
|
||||
*NodeFilter.channel int_size:8
|
||||
*NodeHighlight.node_name max_size:16
|
||||
*GeoPoint.zoom int_size:8
|
||||
*Map.style max_size:20
|
||||
389
packages/protobufs/meshtastic/device_ui.proto
Normal file
389
packages/protobufs/meshtastic/device_ui.proto
Normal file
@@ -0,0 +1,389 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package meshtastic;
|
||||
|
||||
option csharp_namespace = "Meshtastic.Protobufs";
|
||||
option go_package = "github.com/meshtastic/go/generated";
|
||||
option java_outer_classname = "DeviceUIProtos";
|
||||
option java_package = "com.geeksville.mesh";
|
||||
option swift_prefix = "";
|
||||
|
||||
/*
|
||||
* Protobuf structures for device-ui persistency
|
||||
*/
|
||||
|
||||
message DeviceUIConfig {
|
||||
/*
|
||||
* A version integer used to invalidate saved files when we make incompatible changes.
|
||||
*/
|
||||
uint32 version = 1;
|
||||
|
||||
/*
|
||||
* TFT display brightness 1..255
|
||||
*/
|
||||
uint32 screen_brightness = 2;
|
||||
|
||||
/*
|
||||
* Screen timeout 0..900
|
||||
*/
|
||||
uint32 screen_timeout = 3;
|
||||
|
||||
/*
|
||||
* Screen/Settings lock enabled
|
||||
*/
|
||||
bool screen_lock = 4;
|
||||
bool settings_lock = 5;
|
||||
uint32 pin_code = 6;
|
||||
|
||||
/*
|
||||
* Color theme
|
||||
*/
|
||||
Theme theme = 7;
|
||||
|
||||
/*
|
||||
* Audible message, banner and ring tone
|
||||
*/
|
||||
bool alert_enabled = 8;
|
||||
bool banner_enabled = 9;
|
||||
uint32 ring_tone_id = 10;
|
||||
|
||||
/*
|
||||
* Localization
|
||||
*/
|
||||
Language language = 11;
|
||||
|
||||
/*
|
||||
* Node list filter
|
||||
*/
|
||||
NodeFilter node_filter = 12;
|
||||
|
||||
/*
|
||||
* Node list highlightening
|
||||
*/
|
||||
NodeHighlight node_highlight = 13;
|
||||
|
||||
/*
|
||||
* 8 integers for screen calibration data
|
||||
*/
|
||||
bytes calibration_data = 14;
|
||||
|
||||
/*
|
||||
* Map related data
|
||||
*/
|
||||
Map map_data = 15;
|
||||
|
||||
/*
|
||||
* Compass mode
|
||||
*/
|
||||
CompassMode compass_mode = 16;
|
||||
|
||||
/*
|
||||
* RGB color for BaseUI
|
||||
* 0xRRGGBB format, e.g. 0xFF0000 for red
|
||||
*/
|
||||
uint32 screen_rgb_color = 17;
|
||||
|
||||
/*
|
||||
* Clockface analog style
|
||||
* true for analog clockface, false for digital clockface
|
||||
*/
|
||||
bool is_clockface_analog = 18;
|
||||
|
||||
/*
|
||||
* How the GPS coordinates are formatted on the OLED screen.
|
||||
*/
|
||||
GpsCoordinateFormat gps_format = 19;
|
||||
|
||||
/*
|
||||
* How the GPS coordinates are displayed on the OLED screen.
|
||||
*/
|
||||
enum GpsCoordinateFormat {
|
||||
/*
|
||||
* GPS coordinates are displayed in the normal decimal degrees format:
|
||||
* DD.DDDDDD DDD.DDDDDD
|
||||
*/
|
||||
DEC = 0;
|
||||
|
||||
/*
|
||||
* GPS coordinates are displayed in the degrees minutes seconds format:
|
||||
* DD°MM'SS"C DDD°MM'SS"C, where C is the compass point representing the locations quadrant
|
||||
*/
|
||||
DMS = 1;
|
||||
|
||||
/*
|
||||
* Universal Transverse Mercator format:
|
||||
* ZZB EEEEEE NNNNNNN, where Z is zone, B is band, E is easting, N is northing
|
||||
*/
|
||||
UTM = 2;
|
||||
|
||||
/*
|
||||
* Military Grid Reference System format:
|
||||
* ZZB CD EEEEE NNNNN, where Z is zone, B is band, C is the east 100k square, D is the north 100k square,
|
||||
* E is easting, N is northing
|
||||
*/
|
||||
MGRS = 3;
|
||||
|
||||
/*
|
||||
* Open Location Code (aka Plus Codes).
|
||||
*/
|
||||
OLC = 4;
|
||||
|
||||
/*
|
||||
* Ordnance Survey Grid Reference (the National Grid System of the UK).
|
||||
* Format: AB EEEEE NNNNN, where A is the east 100k square, B is the north 100k square,
|
||||
* E is the easting, N is the northing
|
||||
*/
|
||||
OSGR = 5;
|
||||
|
||||
/*
|
||||
* Maidenhead Locator System
|
||||
* Described here: https://en.wikipedia.org/wiki/Maidenhead_Locator_System
|
||||
*/
|
||||
MLS = 6;
|
||||
}
|
||||
}
|
||||
|
||||
message NodeFilter {
|
||||
/*
|
||||
* Filter unknown nodes
|
||||
*/
|
||||
bool unknown_switch = 1;
|
||||
|
||||
/*
|
||||
* Filter offline nodes
|
||||
*/
|
||||
bool offline_switch = 2;
|
||||
|
||||
/*
|
||||
* Filter nodes w/o public key
|
||||
*/
|
||||
bool public_key_switch = 3;
|
||||
|
||||
/*
|
||||
* Filter based on hops away
|
||||
*/
|
||||
int32 hops_away = 4;
|
||||
|
||||
/*
|
||||
* Filter nodes w/o position
|
||||
*/
|
||||
bool position_switch = 5;
|
||||
|
||||
/*
|
||||
* Filter nodes by matching name string
|
||||
*/
|
||||
string node_name = 6;
|
||||
|
||||
/*
|
||||
* Filter based on channel
|
||||
*/
|
||||
int32 channel = 7;
|
||||
}
|
||||
|
||||
message NodeHighlight {
|
||||
/*
|
||||
* Hightlight nodes w/ active chat
|
||||
*/
|
||||
bool chat_switch = 1;
|
||||
|
||||
/*
|
||||
* Highlight nodes w/ position
|
||||
*/
|
||||
bool position_switch = 2;
|
||||
|
||||
/*
|
||||
* Highlight nodes w/ telemetry data
|
||||
*/
|
||||
bool telemetry_switch = 3;
|
||||
|
||||
/*
|
||||
* Highlight nodes w/ iaq data
|
||||
*/
|
||||
bool iaq_switch = 4;
|
||||
|
||||
/*
|
||||
* Highlight nodes by matching name string
|
||||
*/
|
||||
string node_name = 5;
|
||||
}
|
||||
|
||||
message GeoPoint {
|
||||
/*
|
||||
* Zoom level
|
||||
*/
|
||||
int32 zoom = 1;
|
||||
|
||||
/*
|
||||
* Coordinate: latitude
|
||||
*/
|
||||
int32 latitude = 2;
|
||||
|
||||
/*
|
||||
* Coordinate: longitude
|
||||
*/
|
||||
int32 longitude = 3;
|
||||
}
|
||||
|
||||
message Map {
|
||||
/*
|
||||
* Home coordinates
|
||||
*/
|
||||
GeoPoint home = 1;
|
||||
|
||||
/*
|
||||
* Map tile style
|
||||
*/
|
||||
string style = 2;
|
||||
|
||||
/*
|
||||
* Map scroll follows GPS
|
||||
*/
|
||||
bool follow_gps = 3;
|
||||
}
|
||||
|
||||
enum CompassMode {
|
||||
/*
|
||||
* Compass with dynamic ring and heading
|
||||
*/
|
||||
DYNAMIC = 0;
|
||||
|
||||
/*
|
||||
* Compass with fixed ring and heading
|
||||
*/
|
||||
FIXED_RING = 1;
|
||||
|
||||
/*
|
||||
* Compass with heading and freeze option
|
||||
*/
|
||||
FREEZE_HEADING = 2;
|
||||
}
|
||||
|
||||
enum Theme {
|
||||
/*
|
||||
* Dark
|
||||
*/
|
||||
DARK = 0;
|
||||
/*
|
||||
* Light
|
||||
*/
|
||||
LIGHT = 1;
|
||||
/*
|
||||
* Red
|
||||
*/
|
||||
RED = 2;
|
||||
}
|
||||
|
||||
/*
|
||||
* Localization
|
||||
*/
|
||||
enum Language {
|
||||
/*
|
||||
* English
|
||||
*/
|
||||
ENGLISH = 0;
|
||||
|
||||
/*
|
||||
* French
|
||||
*/
|
||||
FRENCH = 1;
|
||||
|
||||
/*
|
||||
* German
|
||||
*/
|
||||
GERMAN = 2;
|
||||
|
||||
/*
|
||||
* Italian
|
||||
*/
|
||||
ITALIAN = 3;
|
||||
|
||||
/*
|
||||
* Portuguese
|
||||
*/
|
||||
PORTUGUESE = 4;
|
||||
|
||||
/*
|
||||
* Spanish
|
||||
*/
|
||||
SPANISH = 5;
|
||||
|
||||
/*
|
||||
* Swedish
|
||||
*/
|
||||
SWEDISH = 6;
|
||||
|
||||
/*
|
||||
* Finnish
|
||||
*/
|
||||
FINNISH = 7;
|
||||
|
||||
/*
|
||||
* Polish
|
||||
*/
|
||||
POLISH = 8;
|
||||
|
||||
/*
|
||||
* Turkish
|
||||
*/
|
||||
TURKISH = 9;
|
||||
|
||||
/*
|
||||
* Serbian
|
||||
*/
|
||||
SERBIAN = 10;
|
||||
|
||||
/*
|
||||
* Russian
|
||||
*/
|
||||
RUSSIAN = 11;
|
||||
|
||||
/*
|
||||
* Dutch
|
||||
*/
|
||||
DUTCH = 12;
|
||||
|
||||
/*
|
||||
* Greek
|
||||
*/
|
||||
GREEK = 13;
|
||||
|
||||
/*
|
||||
* Norwegian
|
||||
*/
|
||||
NORWEGIAN = 14;
|
||||
|
||||
/*
|
||||
* Slovenian
|
||||
*/
|
||||
SLOVENIAN = 15;
|
||||
|
||||
/*
|
||||
* Ukrainian
|
||||
*/
|
||||
UKRAINIAN = 16;
|
||||
|
||||
/*
|
||||
* Bulgarian
|
||||
*/
|
||||
BULGARIAN = 17;
|
||||
|
||||
/*
|
||||
* Czech
|
||||
*/
|
||||
CZECH = 18;
|
||||
|
||||
/*
|
||||
* Danish
|
||||
*/
|
||||
DANISH = 19;
|
||||
|
||||
/*
|
||||
* Simplified Chinese (experimental)
|
||||
*/
|
||||
SIMPLIFIED_CHINESE = 30;
|
||||
|
||||
/*
|
||||
* Traditional Chinese (experimental)
|
||||
*/
|
||||
TRADITIONAL_CHINESE = 31;
|
||||
}
|
||||
18
packages/protobufs/meshtastic/deviceonly.options
Normal file
18
packages/protobufs/meshtastic/deviceonly.options
Normal file
@@ -0,0 +1,18 @@
|
||||
# options for nanopb
|
||||
# https://jpa.kapsi.fi/nanopb/docs/reference.html#proto-file-options
|
||||
|
||||
# FIXME - max_count is actually 32 but we save/load this as one long string of preencoded MeshPacket bytes - not a big array in RAM
|
||||
*DeviceState.receive_queue max_count:1
|
||||
|
||||
*ChannelFile.channels max_count:8
|
||||
|
||||
*DeviceState.node_remote_hardware_pins max_count:12
|
||||
|
||||
*NodeInfoLite.channel int_size:8
|
||||
*NodeInfoLite.hops_away int_size:8
|
||||
*NodeInfoLite.next_hop int_size:8
|
||||
|
||||
*UserLite.long_name max_size:40
|
||||
*UserLite.short_name max_size:5
|
||||
*UserLite.public_key max_size:32 # public key
|
||||
*UserLite.macaddr max_size:6 fixed_length:true
|
||||
301
packages/protobufs/meshtastic/deviceonly.proto
Normal file
301
packages/protobufs/meshtastic/deviceonly.proto
Normal file
@@ -0,0 +1,301 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package meshtastic;
|
||||
|
||||
import "meshtastic/channel.proto";
|
||||
import "meshtastic/config.proto";
|
||||
import "meshtastic/localonly.proto";
|
||||
import "meshtastic/mesh.proto";
|
||||
import "meshtastic/telemetry.proto";
|
||||
import "nanopb.proto";
|
||||
|
||||
option csharp_namespace = "Meshtastic.Protobufs";
|
||||
option go_package = "github.com/meshtastic/go/generated";
|
||||
option java_outer_classname = "DeviceOnly";
|
||||
option java_package = "com.geeksville.mesh";
|
||||
option swift_prefix = "";
|
||||
option (nanopb_fileopt).include = "<vector>";
|
||||
|
||||
/*
|
||||
* Position with static location information only for NodeDBLite
|
||||
*/
|
||||
message PositionLite {
|
||||
/*
|
||||
* The new preferred location encoding, multiply by 1e-7 to get degrees
|
||||
* in floating point
|
||||
*/
|
||||
sfixed32 latitude_i = 1;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
sfixed32 longitude_i = 2;
|
||||
|
||||
/*
|
||||
* In meters above MSL (but see issue #359)
|
||||
*/
|
||||
int32 altitude = 3;
|
||||
|
||||
/*
|
||||
* This is usually not sent over the mesh (to save space), but it is sent
|
||||
* from the phone so that the local device can set its RTC If it is sent over
|
||||
* the mesh (because there are devices on the mesh without GPS), it will only
|
||||
* be sent by devices which has a hardware GPS clock.
|
||||
* seconds since 1970
|
||||
*/
|
||||
fixed32 time = 4;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
Position.LocSource location_source = 5;
|
||||
}
|
||||
|
||||
message UserLite {
|
||||
/*
|
||||
* This is the addr of the radio.
|
||||
*/
|
||||
bytes macaddr = 1 [deprecated = true];
|
||||
|
||||
/*
|
||||
* A full name for this user, i.e. "Kevin Hester"
|
||||
*/
|
||||
string long_name = 2;
|
||||
|
||||
/*
|
||||
* A VERY short name, ideally two characters.
|
||||
* Suitable for a tiny OLED screen
|
||||
*/
|
||||
string short_name = 3;
|
||||
|
||||
/*
|
||||
* TBEAM, HELTEC, etc...
|
||||
* Starting in 1.2.11 moved to hw_model enum in the NodeInfo object.
|
||||
* Apps will still need the string here for older builds
|
||||
* (so OTA update can find the right image), but if the enum is available it will be used instead.
|
||||
*/
|
||||
HardwareModel hw_model = 4;
|
||||
|
||||
/*
|
||||
* In some regions Ham radio operators have different bandwidth limitations than others.
|
||||
* If this user is a licensed operator, set this flag.
|
||||
* Also, "long_name" should be their licence number.
|
||||
*/
|
||||
bool is_licensed = 5;
|
||||
|
||||
/*
|
||||
* Indicates that the user's role in the mesh
|
||||
*/
|
||||
Config.DeviceConfig.Role role = 6;
|
||||
|
||||
/*
|
||||
* The public key of the user's device.
|
||||
* This is sent out to other nodes on the mesh to allow them to compute a shared secret key.
|
||||
*/
|
||||
bytes public_key = 7;
|
||||
|
||||
/*
|
||||
* Whether or not the node can be messaged
|
||||
*/
|
||||
optional bool is_unmessagable = 9;
|
||||
}
|
||||
|
||||
message NodeInfoLite {
|
||||
/*
|
||||
* The node number
|
||||
*/
|
||||
uint32 num = 1;
|
||||
|
||||
/*
|
||||
* The user info for this node
|
||||
*/
|
||||
UserLite user = 2;
|
||||
|
||||
/*
|
||||
* This position data. Note: before 1.2.14 we would also store the last time we've heard from this node in position.time, that is no longer true.
|
||||
* Position.time now indicates the last time we received a POSITION from that node.
|
||||
*/
|
||||
PositionLite position = 3;
|
||||
|
||||
/*
|
||||
* Returns the Signal-to-noise ratio (SNR) of the last received message,
|
||||
* as measured by the receiver. Return SNR of the last received message in dB
|
||||
*/
|
||||
float snr = 4;
|
||||
|
||||
/*
|
||||
* Set to indicate the last time we received a packet from this node
|
||||
*/
|
||||
fixed32 last_heard = 5;
|
||||
/*
|
||||
* The latest device metrics for the node.
|
||||
*/
|
||||
DeviceMetrics device_metrics = 6;
|
||||
|
||||
/*
|
||||
* local channel index we heard that node on. Only populated if its not the default channel.
|
||||
*/
|
||||
uint32 channel = 7;
|
||||
|
||||
/*
|
||||
* True if we witnessed the node over MQTT instead of LoRA transport
|
||||
*/
|
||||
bool via_mqtt = 8;
|
||||
|
||||
/*
|
||||
* Number of hops away from us this node is (0 if direct neighbor)
|
||||
*/
|
||||
optional uint32 hops_away = 9;
|
||||
|
||||
/*
|
||||
* True if node is in our favorites list
|
||||
* Persists between NodeDB internal clean ups
|
||||
*/
|
||||
bool is_favorite = 10;
|
||||
|
||||
/*
|
||||
* True if node is in our ignored list
|
||||
* Persists between NodeDB internal clean ups
|
||||
*/
|
||||
bool is_ignored = 11;
|
||||
|
||||
/*
|
||||
* Last byte of the node number of the node that should be used as the next hop to reach this node.
|
||||
*/
|
||||
uint32 next_hop = 12;
|
||||
|
||||
/*
|
||||
* Bitfield for storing booleans.
|
||||
* LSB 0 is_key_manually_verified
|
||||
*/
|
||||
uint32 bitfield = 13;
|
||||
}
|
||||
|
||||
/*
|
||||
* This message is never sent over the wire, but it is used for serializing DB
|
||||
* state to flash in the device code
|
||||
* FIXME, since we write this each time we enter deep sleep (and have infinite
|
||||
* flash) it would be better to use some sort of append only data structure for
|
||||
* the receive queue and use the preferences store for the other stuff
|
||||
*/
|
||||
message DeviceState {
|
||||
/*
|
||||
* Read only settings/info about this node
|
||||
*/
|
||||
MyNodeInfo my_node = 2;
|
||||
|
||||
/*
|
||||
* My owner info
|
||||
*/
|
||||
User owner = 3;
|
||||
|
||||
/*
|
||||
* Received packets saved for delivery to the phone
|
||||
*/
|
||||
repeated MeshPacket receive_queue = 5;
|
||||
|
||||
/*
|
||||
* A version integer used to invalidate old save files when we make
|
||||
* incompatible changes This integer is set at build time and is private to
|
||||
* NodeDB.cpp in the device code.
|
||||
*/
|
||||
uint32 version = 8;
|
||||
|
||||
/*
|
||||
* We keep the last received text message (only) stored in the device flash,
|
||||
* so we can show it on the screen.
|
||||
* Might be null
|
||||
*/
|
||||
MeshPacket rx_text_message = 7;
|
||||
|
||||
/*
|
||||
* Used only during development.
|
||||
* Indicates developer is testing and changes should never be saved to flash.
|
||||
* Deprecated in 2.3.1
|
||||
*/
|
||||
bool no_save = 9 [deprecated = true];
|
||||
|
||||
/*
|
||||
* Previously used to manage GPS factory resets.
|
||||
* Deprecated in 2.5.23
|
||||
*/
|
||||
bool did_gps_reset = 11 [deprecated = true];
|
||||
|
||||
/*
|
||||
* We keep the last received waypoint stored in the device flash,
|
||||
* so we can show it on the screen.
|
||||
* Might be null
|
||||
*/
|
||||
MeshPacket rx_waypoint = 12;
|
||||
|
||||
/*
|
||||
* The mesh's nodes with their available gpio pins for RemoteHardware module
|
||||
*/
|
||||
repeated NodeRemoteHardwarePin node_remote_hardware_pins = 13;
|
||||
}
|
||||
|
||||
message NodeDatabase {
|
||||
/*
|
||||
* A version integer used to invalidate old save files when we make
|
||||
* incompatible changes This integer is set at build time and is private to
|
||||
* NodeDB.cpp in the device code.
|
||||
*/
|
||||
uint32 version = 1;
|
||||
|
||||
/*
|
||||
* New lite version of NodeDB to decrease memory footprint
|
||||
*/
|
||||
repeated NodeInfoLite nodes = 2 [(nanopb).callback_datatype = "std::vector<meshtastic_NodeInfoLite>"];
|
||||
}
|
||||
|
||||
/*
|
||||
* The on-disk saved channels
|
||||
*/
|
||||
message ChannelFile {
|
||||
/*
|
||||
* The channels our node knows about
|
||||
*/
|
||||
repeated Channel channels = 1;
|
||||
|
||||
/*
|
||||
* A version integer used to invalidate old save files when we make
|
||||
* incompatible changes This integer is set at build time and is private to
|
||||
* NodeDB.cpp in the device code.
|
||||
*/
|
||||
uint32 version = 2;
|
||||
}
|
||||
|
||||
/*
|
||||
* The on-disk backup of the node's preferences
|
||||
*/
|
||||
message BackupPreferences {
|
||||
/*
|
||||
* The version of the backup
|
||||
*/
|
||||
uint32 version = 1;
|
||||
|
||||
/*
|
||||
* The timestamp of the backup (if node has time)
|
||||
*/
|
||||
fixed32 timestamp = 2;
|
||||
|
||||
/*
|
||||
* The node's configuration
|
||||
*/
|
||||
LocalConfig config = 3;
|
||||
|
||||
/*
|
||||
* The node's module configuration
|
||||
*/
|
||||
LocalModuleConfig module_config = 4;
|
||||
|
||||
/*
|
||||
* The node's channels
|
||||
*/
|
||||
ChannelFile channels = 5;
|
||||
|
||||
/*
|
||||
* The node's user (owner) information
|
||||
*/
|
||||
User owner = 6;
|
||||
}
|
||||
1
packages/protobufs/meshtastic/interdevice.options
Normal file
1
packages/protobufs/meshtastic/interdevice.options
Normal file
@@ -0,0 +1 @@
|
||||
*InterdeviceMessage.nmea max_size:1024
|
||||
44
packages/protobufs/meshtastic/interdevice.proto
Normal file
44
packages/protobufs/meshtastic/interdevice.proto
Normal file
@@ -0,0 +1,44 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package meshtastic;
|
||||
|
||||
option csharp_namespace = "Meshtastic.Protobufs";
|
||||
option go_package = "github.com/meshtastic/go/generated";
|
||||
option java_outer_classname = "InterdeviceProtos";
|
||||
option java_package = "com.geeksville.mesh";
|
||||
option swift_prefix = "";
|
||||
|
||||
// encapsulate up to 1k of NMEA string data
|
||||
|
||||
enum MessageType {
|
||||
ACK = 0;
|
||||
COLLECT_INTERVAL = 160; // in ms
|
||||
BEEP_ON = 161; // duration ms
|
||||
BEEP_OFF = 162; // cancel prematurely
|
||||
SHUTDOWN = 163;
|
||||
POWER_ON = 164;
|
||||
SCD41_TEMP = 176;
|
||||
SCD41_HUMIDITY = 177;
|
||||
SCD41_CO2 = 178;
|
||||
AHT20_TEMP = 179;
|
||||
AHT20_HUMIDITY = 180;
|
||||
TVOC_INDEX = 181;
|
||||
}
|
||||
|
||||
message SensorData {
|
||||
// The message type
|
||||
MessageType type = 1;
|
||||
// The sensor data, either as a float or an uint32
|
||||
oneof data {
|
||||
float float_value = 2;
|
||||
uint32 uint32_value = 3;
|
||||
}
|
||||
}
|
||||
|
||||
message InterdeviceMessage {
|
||||
// The message data
|
||||
oneof data {
|
||||
string nmea = 1;
|
||||
SensorData sensor = 2;
|
||||
}
|
||||
}
|
||||
140
packages/protobufs/meshtastic/localonly.proto
Normal file
140
packages/protobufs/meshtastic/localonly.proto
Normal file
@@ -0,0 +1,140 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package meshtastic;
|
||||
|
||||
import "meshtastic/config.proto";
|
||||
import "meshtastic/module_config.proto";
|
||||
|
||||
option csharp_namespace = "Meshtastic.Protobufs";
|
||||
option go_package = "github.com/meshtastic/go/generated";
|
||||
option java_outer_classname = "LocalOnlyProtos";
|
||||
option java_package = "com.geeksville.mesh";
|
||||
option swift_prefix = "";
|
||||
|
||||
/*
|
||||
* Protobuf structures common to apponly.proto and deviceonly.proto
|
||||
* This is never sent over the wire, only for local use
|
||||
*/
|
||||
|
||||
message LocalConfig {
|
||||
/*
|
||||
* The part of the config that is specific to the Device
|
||||
*/
|
||||
Config.DeviceConfig device = 1;
|
||||
|
||||
/*
|
||||
* The part of the config that is specific to the GPS Position
|
||||
*/
|
||||
Config.PositionConfig position = 2;
|
||||
|
||||
/*
|
||||
* The part of the config that is specific to the Power settings
|
||||
*/
|
||||
Config.PowerConfig power = 3;
|
||||
|
||||
/*
|
||||
* The part of the config that is specific to the Wifi Settings
|
||||
*/
|
||||
Config.NetworkConfig network = 4;
|
||||
|
||||
/*
|
||||
* The part of the config that is specific to the Display
|
||||
*/
|
||||
Config.DisplayConfig display = 5;
|
||||
|
||||
/*
|
||||
* The part of the config that is specific to the Lora Radio
|
||||
*/
|
||||
Config.LoRaConfig lora = 6;
|
||||
|
||||
/*
|
||||
* The part of the config that is specific to the Bluetooth settings
|
||||
*/
|
||||
Config.BluetoothConfig bluetooth = 7;
|
||||
|
||||
/*
|
||||
* A version integer used to invalidate old save files when we make
|
||||
* incompatible changes This integer is set at build time and is private to
|
||||
* NodeDB.cpp in the device code.
|
||||
*/
|
||||
uint32 version = 8;
|
||||
|
||||
/*
|
||||
* The part of the config that is specific to Security settings
|
||||
*/
|
||||
Config.SecurityConfig security = 9;
|
||||
}
|
||||
|
||||
message LocalModuleConfig {
|
||||
/*
|
||||
* The part of the config that is specific to the MQTT module
|
||||
*/
|
||||
ModuleConfig.MQTTConfig mqtt = 1;
|
||||
|
||||
/*
|
||||
* The part of the config that is specific to the Serial module
|
||||
*/
|
||||
ModuleConfig.SerialConfig serial = 2;
|
||||
|
||||
/*
|
||||
* The part of the config that is specific to the ExternalNotification module
|
||||
*/
|
||||
ModuleConfig.ExternalNotificationConfig external_notification = 3;
|
||||
|
||||
/*
|
||||
* The part of the config that is specific to the Store & Forward module
|
||||
*/
|
||||
ModuleConfig.StoreForwardConfig store_forward = 4;
|
||||
|
||||
/*
|
||||
* The part of the config that is specific to the RangeTest module
|
||||
*/
|
||||
ModuleConfig.RangeTestConfig range_test = 5;
|
||||
|
||||
/*
|
||||
* The part of the config that is specific to the Telemetry module
|
||||
*/
|
||||
ModuleConfig.TelemetryConfig telemetry = 6;
|
||||
|
||||
/*
|
||||
* The part of the config that is specific to the Canned Message module
|
||||
*/
|
||||
ModuleConfig.CannedMessageConfig canned_message = 7;
|
||||
|
||||
/*
|
||||
* The part of the config that is specific to the Audio module
|
||||
*/
|
||||
ModuleConfig.AudioConfig audio = 9;
|
||||
|
||||
/*
|
||||
* The part of the config that is specific to the Remote Hardware module
|
||||
*/
|
||||
ModuleConfig.RemoteHardwareConfig remote_hardware = 10;
|
||||
|
||||
/*
|
||||
* The part of the config that is specific to the Neighbor Info module
|
||||
*/
|
||||
ModuleConfig.NeighborInfoConfig neighbor_info = 11;
|
||||
|
||||
/*
|
||||
* The part of the config that is specific to the Ambient Lighting module
|
||||
*/
|
||||
ModuleConfig.AmbientLightingConfig ambient_lighting = 12;
|
||||
|
||||
/*
|
||||
* The part of the config that is specific to the Detection Sensor module
|
||||
*/
|
||||
ModuleConfig.DetectionSensorConfig detection_sensor = 13;
|
||||
|
||||
/*
|
||||
* Paxcounter Config
|
||||
*/
|
||||
ModuleConfig.PaxcounterConfig paxcounter = 14;
|
||||
|
||||
/*
|
||||
* A version integer used to invalidate old save files when we make
|
||||
* incompatible changes This integer is set at build time and is private to
|
||||
* NodeDB.cpp in the device code.
|
||||
*/
|
||||
uint32 version = 8;
|
||||
}
|
||||
92
packages/protobufs/meshtastic/mesh.options
Normal file
92
packages/protobufs/meshtastic/mesh.options
Normal file
@@ -0,0 +1,92 @@
|
||||
# options for nanopb
|
||||
# https://jpa.kapsi.fi/nanopb/docs/reference.html#proto-file-options
|
||||
|
||||
*macaddr max_size:6 fixed_length:true # macaddrs
|
||||
*id max_size:16 # node id strings
|
||||
*public_key max_size:32 # public key
|
||||
|
||||
*User.long_name max_size:40
|
||||
*User.short_name max_size:5
|
||||
|
||||
*RouteDiscovery.route max_count:8
|
||||
*RouteDiscovery.snr_towards max_count:8
|
||||
*RouteDiscovery.snr_towards int_size:8
|
||||
*RouteDiscovery.route_back max_count:8
|
||||
*RouteDiscovery.snr_back max_count:8
|
||||
*RouteDiscovery.snr_back int_size:8
|
||||
|
||||
# note: this payload length is ONLY the bytes that are sent inside of the Data protobuf (excluding protobuf overhead). The 16 byte header is
|
||||
# outside of this envelope
|
||||
*Data.payload max_size:233
|
||||
*Data.bitfield int_size:8
|
||||
|
||||
*NodeInfo.channel int_size:8
|
||||
*NodeInfo.hops_away int_size:8
|
||||
|
||||
# Big enough for 1.2.28.568032c-d
|
||||
*MyNodeInfo.firmware_version max_size:18
|
||||
*MyNodeInfo.device_id max_size:16
|
||||
*MyNodeInfo.pio_env max_size:40
|
||||
|
||||
*MyNodeInfo.air_period_tx max_count:8
|
||||
*MyNodeInfo.air_period_rx max_count:8
|
||||
|
||||
*MyNodeInfo.firmware_edition int_size:8
|
||||
*MyNodeInfo.nodedb_count int_size:16
|
||||
|
||||
# Note: the actual limit (because of header bytes) on the size of encrypted payloads is 251 bytes, but I use 256
|
||||
# here because we might need to fill with zeros for padding to encryption block size (16 bytes per block)
|
||||
*MeshPacket.encrypted max_size:256
|
||||
*MeshPacket.payload_variant anonymous_oneof:true
|
||||
*MeshPacket.hop_limit int_size:8
|
||||
*MeshPacket.hop_start int_size:8
|
||||
*MeshPacket.channel int_size:8
|
||||
*MeshPacket.next_hop int_size:8
|
||||
*MeshPacket.relay_node int_size:8
|
||||
|
||||
*QueueStatus.res int_size:8
|
||||
*QueueStatus.free int_size:8
|
||||
*QueueStatus.maxlen int_size:8
|
||||
|
||||
*ToRadio.payload_variant anonymous_oneof:true
|
||||
|
||||
*FromRadio.payload_variant anonymous_oneof:true
|
||||
|
||||
*Routing.variant anonymous_oneof:true
|
||||
|
||||
*LogRecord.message max_size:384
|
||||
*LogRecord.source max_size:32
|
||||
|
||||
*FileInfo.file_name max_size:228
|
||||
|
||||
*ClientNotification.message max_size:400
|
||||
|
||||
*KeyVerificationNumberInform.remote_longname max_size:40
|
||||
*KeyVerificationNumberRequest.remote_longname max_size:40
|
||||
*KeyVerificationFinal.remote_longname max_size:40
|
||||
*KeyVerificationFinal.verification_characters max_size:10
|
||||
|
||||
*KeyVerification.hash1 max_size:32
|
||||
*KeyVerification.hash2 max_size:32
|
||||
|
||||
|
||||
# MyMessage.name max_size:40
|
||||
# or fixed_length or fixed_count, or max_count
|
||||
|
||||
#This value may want to be a few bytes smaller to compensate for the parent fields.
|
||||
*Compressed.data max_size:233
|
||||
|
||||
*Waypoint.name max_size:30
|
||||
*Waypoint.description max_size:100
|
||||
|
||||
*NeighborInfo.neighbors max_count:10
|
||||
|
||||
*DeviceMetadata.firmware_version max_size:18
|
||||
|
||||
*MqttClientProxyMessage.topic max_size:60
|
||||
*MqttClientProxyMessage.data max_size:435
|
||||
*MqttClientProxyMessage.text max_size:435
|
||||
|
||||
*ChunkedPayload.chunk_count int_size:16
|
||||
*ChunkedPayload.chunk_index int_size:16
|
||||
*ChunkedPayload.payload_chunk max_size:228
|
||||
2409
packages/protobufs/meshtastic/mesh.proto
Normal file
2409
packages/protobufs/meshtastic/mesh.proto
Normal file
File diff suppressed because it is too large
Load Diff
29
packages/protobufs/meshtastic/module_config.options
Normal file
29
packages/protobufs/meshtastic/module_config.options
Normal file
@@ -0,0 +1,29 @@
|
||||
*CannedMessageConfig.allow_input_source max_size:16
|
||||
|
||||
*MQTTConfig.address max_size:64
|
||||
*MQTTConfig.username max_size:64
|
||||
*MQTTConfig.password max_size:32
|
||||
*MQTTConfig.root max_size:32
|
||||
|
||||
*AudioConfig.ptt_pin int_size:8
|
||||
*AudioConfig.i2s_ws int_size:8
|
||||
*AudioConfig.i2s_sd int_size:8
|
||||
*AudioConfig.i2s_din int_size:8
|
||||
*AudioConfig.i2s_sck int_size:8
|
||||
|
||||
*ExternalNotificationConfig.output_vibra int_size:8
|
||||
*ExternalNotificationConfig.output_buzzer int_size:8
|
||||
*ExternalNotificationConfig.nag_timeout int_size:16
|
||||
|
||||
*RemoteHardwareConfig.available_pins max_count:4
|
||||
*RemoteHardwarePin.name max_size:15
|
||||
*RemoteHardwarePin.gpio_pin int_size:8
|
||||
|
||||
*AmbientLightingConfig.current int_size:8
|
||||
*AmbientLightingConfig.red int_size:8
|
||||
*AmbientLightingConfig.green int_size:8
|
||||
*AmbientLightingConfig.blue int_size:8
|
||||
|
||||
*DetectionSensorConfig.monitor_pin int_size:8
|
||||
*DetectionSensorConfig.name max_size:20
|
||||
*DetectionSensorConfig.detection_trigger_type max_size:8
|
||||
870
packages/protobufs/meshtastic/module_config.proto
Normal file
870
packages/protobufs/meshtastic/module_config.proto
Normal file
@@ -0,0 +1,870 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package meshtastic;
|
||||
|
||||
option csharp_namespace = "Meshtastic.Protobufs";
|
||||
option go_package = "github.com/meshtastic/go/generated";
|
||||
option java_outer_classname = "ModuleConfigProtos";
|
||||
option java_package = "com.geeksville.mesh";
|
||||
option swift_prefix = "";
|
||||
|
||||
/*
|
||||
* Module Config
|
||||
*/
|
||||
message ModuleConfig {
|
||||
/*
|
||||
* MQTT Client Config
|
||||
*/
|
||||
message MQTTConfig {
|
||||
/*
|
||||
* If a meshtastic node is able to reach the internet it will normally attempt to gateway any channels that are marked as
|
||||
* is_uplink_enabled or is_downlink_enabled.
|
||||
*/
|
||||
bool enabled = 1;
|
||||
|
||||
/*
|
||||
* The server to use for our MQTT global message gateway feature.
|
||||
* If not set, the default server will be used
|
||||
*/
|
||||
string address = 2;
|
||||
|
||||
/*
|
||||
* MQTT username to use (most useful for a custom MQTT server).
|
||||
* If using a custom server, this will be honoured even if empty.
|
||||
* If using the default server, this will only be honoured if set, otherwise the device will use the default username
|
||||
*/
|
||||
string username = 3;
|
||||
|
||||
/*
|
||||
* MQTT password to use (most useful for a custom MQTT server).
|
||||
* If using a custom server, this will be honoured even if empty.
|
||||
* If using the default server, this will only be honoured if set, otherwise the device will use the default password
|
||||
*/
|
||||
string password = 4;
|
||||
|
||||
/*
|
||||
* Whether to send encrypted or decrypted packets to MQTT.
|
||||
* This parameter is only honoured if you also set server
|
||||
* (the default official mqtt.meshtastic.org server can handle encrypted packets)
|
||||
* Decrypted packets may be useful for external systems that want to consume meshtastic packets
|
||||
*/
|
||||
bool encryption_enabled = 5;
|
||||
|
||||
/*
|
||||
* Whether to send / consume json packets on MQTT
|
||||
*/
|
||||
bool json_enabled = 6;
|
||||
|
||||
/*
|
||||
* If true, we attempt to establish a secure connection using TLS
|
||||
*/
|
||||
bool tls_enabled = 7;
|
||||
|
||||
/*
|
||||
* The root topic to use for MQTT messages. Default is "msh".
|
||||
* This is useful if you want to use a single MQTT server for multiple meshtastic networks and separate them via ACLs
|
||||
*/
|
||||
string root = 8;
|
||||
|
||||
/*
|
||||
* If true, we can use the connected phone / client to proxy messages to MQTT instead of a direct connection
|
||||
*/
|
||||
bool proxy_to_client_enabled = 9;
|
||||
|
||||
/*
|
||||
* If true, we will periodically report unencrypted information about our node to a map via MQTT
|
||||
*/
|
||||
bool map_reporting_enabled = 10;
|
||||
|
||||
/*
|
||||
* Settings for reporting information about our node to a map via MQTT
|
||||
*/
|
||||
MapReportSettings map_report_settings = 11;
|
||||
}
|
||||
|
||||
/*
|
||||
* Settings for reporting unencrypted information about our node to a map via MQTT
|
||||
*/
|
||||
message MapReportSettings {
|
||||
/*
|
||||
* How often we should report our info to the map (in seconds)
|
||||
*/
|
||||
uint32 publish_interval_secs = 1;
|
||||
|
||||
/*
|
||||
* Bits of precision for the location sent (default of 32 is full precision).
|
||||
*/
|
||||
uint32 position_precision = 2;
|
||||
|
||||
/*
|
||||
* Whether we have opted-in to report our location to the map
|
||||
*/
|
||||
bool should_report_location = 3;
|
||||
}
|
||||
|
||||
/*
|
||||
* RemoteHardwareModule Config
|
||||
*/
|
||||
message RemoteHardwareConfig {
|
||||
/*
|
||||
* Whether the Module is enabled
|
||||
*/
|
||||
bool enabled = 1;
|
||||
|
||||
/*
|
||||
* Whether the Module allows consumers to read / write to pins not defined in available_pins
|
||||
*/
|
||||
bool allow_undefined_pin_access = 2;
|
||||
|
||||
/*
|
||||
* Exposes the available pins to the mesh for reading and writing
|
||||
*/
|
||||
repeated RemoteHardwarePin available_pins = 3;
|
||||
}
|
||||
|
||||
/*
|
||||
* NeighborInfoModule Config
|
||||
*/
|
||||
message NeighborInfoConfig {
|
||||
/*
|
||||
* Whether the Module is enabled
|
||||
*/
|
||||
bool enabled = 1;
|
||||
|
||||
/*
|
||||
* Interval in seconds of how often we should try to send our
|
||||
* Neighbor Info (minimum is 14400, i.e., 4 hours)
|
||||
*/
|
||||
uint32 update_interval = 2;
|
||||
|
||||
/*
|
||||
* Whether in addition to sending it to MQTT and the PhoneAPI, our NeighborInfo should be transmitted over LoRa.
|
||||
* Note that this is not available on a channel with default key and name.
|
||||
*/
|
||||
bool transmit_over_lora = 3;
|
||||
}
|
||||
|
||||
/*
|
||||
* Detection Sensor Module Config
|
||||
*/
|
||||
message DetectionSensorConfig {
|
||||
enum TriggerType {
|
||||
// Event is triggered if pin is low
|
||||
LOGIC_LOW = 0;
|
||||
// Event is triggered if pin is high
|
||||
LOGIC_HIGH = 1;
|
||||
// Event is triggered when pin goes high to low
|
||||
FALLING_EDGE = 2;
|
||||
// Event is triggered when pin goes low to high
|
||||
RISING_EDGE = 3;
|
||||
// Event is triggered on every pin state change, low is considered to be
|
||||
// "active"
|
||||
EITHER_EDGE_ACTIVE_LOW = 4;
|
||||
// Event is triggered on every pin state change, high is considered to be
|
||||
// "active"
|
||||
EITHER_EDGE_ACTIVE_HIGH = 5;
|
||||
}
|
||||
/*
|
||||
* Whether the Module is enabled
|
||||
*/
|
||||
bool enabled = 1;
|
||||
|
||||
/*
|
||||
* Interval in seconds of how often we can send a message to the mesh when a
|
||||
* trigger event is detected
|
||||
*/
|
||||
uint32 minimum_broadcast_secs = 2;
|
||||
|
||||
/*
|
||||
* Interval in seconds of how often we should send a message to the mesh
|
||||
* with the current state regardless of trigger events When set to 0, only
|
||||
* trigger events will be broadcasted Works as a sort of status heartbeat
|
||||
* for peace of mind
|
||||
*/
|
||||
uint32 state_broadcast_secs = 3;
|
||||
|
||||
/*
|
||||
* Send ASCII bell with alert message
|
||||
* Useful for triggering ext. notification on bell
|
||||
*/
|
||||
bool send_bell = 4;
|
||||
|
||||
/*
|
||||
* Friendly name used to format message sent to mesh
|
||||
* Example: A name "Motion" would result in a message "Motion detected"
|
||||
* Maximum length of 20 characters
|
||||
*/
|
||||
string name = 5;
|
||||
|
||||
/*
|
||||
* GPIO pin to monitor for state changes
|
||||
*/
|
||||
uint32 monitor_pin = 6;
|
||||
|
||||
/*
|
||||
* The type of trigger event to be used
|
||||
*/
|
||||
TriggerType detection_trigger_type = 7;
|
||||
|
||||
/*
|
||||
* Whether or not use INPUT_PULLUP mode for GPIO pin
|
||||
* Only applicable if the board uses pull-up resistors on the pin
|
||||
*/
|
||||
bool use_pullup = 8;
|
||||
}
|
||||
|
||||
/*
|
||||
* Audio Config for codec2 voice
|
||||
*/
|
||||
message AudioConfig {
|
||||
/*
|
||||
* Baudrate for codec2 voice
|
||||
*/
|
||||
enum Audio_Baud {
|
||||
CODEC2_DEFAULT = 0;
|
||||
CODEC2_3200 = 1;
|
||||
CODEC2_2400 = 2;
|
||||
CODEC2_1600 = 3;
|
||||
CODEC2_1400 = 4;
|
||||
CODEC2_1300 = 5;
|
||||
CODEC2_1200 = 6;
|
||||
CODEC2_700 = 7;
|
||||
CODEC2_700B = 8;
|
||||
}
|
||||
|
||||
/*
|
||||
* Whether Audio is enabled
|
||||
*/
|
||||
bool codec2_enabled = 1;
|
||||
|
||||
/*
|
||||
* PTT Pin
|
||||
*/
|
||||
uint32 ptt_pin = 2;
|
||||
|
||||
/*
|
||||
* The audio sample rate to use for codec2
|
||||
*/
|
||||
Audio_Baud bitrate = 3;
|
||||
|
||||
/*
|
||||
* I2S Word Select
|
||||
*/
|
||||
uint32 i2s_ws = 4;
|
||||
|
||||
/*
|
||||
* I2S Data IN
|
||||
*/
|
||||
uint32 i2s_sd = 5;
|
||||
|
||||
/*
|
||||
* I2S Data OUT
|
||||
*/
|
||||
uint32 i2s_din = 6;
|
||||
|
||||
/*
|
||||
* I2S Clock
|
||||
*/
|
||||
uint32 i2s_sck = 7;
|
||||
}
|
||||
|
||||
/*
|
||||
* Config for the Paxcounter Module
|
||||
*/
|
||||
message PaxcounterConfig {
|
||||
/*
|
||||
* Enable the Paxcounter Module
|
||||
*/
|
||||
bool enabled = 1;
|
||||
|
||||
/*
|
||||
* Interval in seconds of how often we should try to send our
|
||||
* metrics to the mesh
|
||||
*/
|
||||
|
||||
uint32 paxcounter_update_interval = 2;
|
||||
|
||||
/*
|
||||
* WiFi RSSI threshold. Defaults to -80
|
||||
*/
|
||||
int32 wifi_threshold = 3;
|
||||
|
||||
/*
|
||||
* BLE RSSI threshold. Defaults to -80
|
||||
*/
|
||||
int32 ble_threshold = 4;
|
||||
}
|
||||
|
||||
/*
|
||||
* Serial Config
|
||||
*/
|
||||
message SerialConfig {
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
enum Serial_Baud {
|
||||
BAUD_DEFAULT = 0;
|
||||
BAUD_110 = 1;
|
||||
BAUD_300 = 2;
|
||||
BAUD_600 = 3;
|
||||
BAUD_1200 = 4;
|
||||
BAUD_2400 = 5;
|
||||
BAUD_4800 = 6;
|
||||
BAUD_9600 = 7;
|
||||
BAUD_19200 = 8;
|
||||
BAUD_38400 = 9;
|
||||
BAUD_57600 = 10;
|
||||
BAUD_115200 = 11;
|
||||
BAUD_230400 = 12;
|
||||
BAUD_460800 = 13;
|
||||
BAUD_576000 = 14;
|
||||
BAUD_921600 = 15;
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
enum Serial_Mode {
|
||||
DEFAULT = 0;
|
||||
SIMPLE = 1;
|
||||
PROTO = 2;
|
||||
TEXTMSG = 3;
|
||||
NMEA = 4;
|
||||
// NMEA messages specifically tailored for CalTopo
|
||||
CALTOPO = 5;
|
||||
// Ecowitt WS85 weather station
|
||||
WS85 = 6;
|
||||
// VE.Direct is a serial protocol used by Victron Energy products
|
||||
// https://beta.ivc.no/wiki/index.php/Victron_VE_Direct_DIY_Cable
|
||||
VE_DIRECT = 7;
|
||||
//Used to configure and view some parameters of MeshSolar.
|
||||
//https://heltec.org/project/meshsolar/
|
||||
MS_CONFIG = 8;
|
||||
}
|
||||
|
||||
/*
|
||||
* Preferences for the SerialModule
|
||||
*/
|
||||
bool enabled = 1;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
bool echo = 2;
|
||||
|
||||
/*
|
||||
* RX pin (should match Arduino gpio pin number)
|
||||
*/
|
||||
uint32 rxd = 3;
|
||||
|
||||
/*
|
||||
* TX pin (should match Arduino gpio pin number)
|
||||
*/
|
||||
uint32 txd = 4;
|
||||
|
||||
/*
|
||||
* Serial baud rate
|
||||
*/
|
||||
Serial_Baud baud = 5;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
uint32 timeout = 6;
|
||||
|
||||
/*
|
||||
* Mode for serial module operation
|
||||
*/
|
||||
Serial_Mode mode = 7;
|
||||
|
||||
/*
|
||||
* Overrides the platform's defacto Serial port instance to use with Serial module config settings
|
||||
* This is currently only usable in output modes like NMEA / CalTopo and may behave strangely or not work at all in other modes
|
||||
* Existing logging over the Serial Console will still be present
|
||||
*/
|
||||
bool override_console_serial_port = 8;
|
||||
}
|
||||
|
||||
/*
|
||||
* External Notifications Config
|
||||
*/
|
||||
message ExternalNotificationConfig {
|
||||
/*
|
||||
* Enable the ExternalNotificationModule
|
||||
*/
|
||||
bool enabled = 1;
|
||||
|
||||
/*
|
||||
* When using in On/Off mode, keep the output on for this many
|
||||
* milliseconds. Default 1000ms (1 second).
|
||||
*/
|
||||
uint32 output_ms = 2;
|
||||
|
||||
/*
|
||||
* Define the output pin GPIO setting Defaults to
|
||||
* EXT_NOTIFY_OUT if set for the board.
|
||||
* In standalone devices this pin should drive the LED to match the UI.
|
||||
*/
|
||||
uint32 output = 3;
|
||||
|
||||
/*
|
||||
* Optional: Define a secondary output pin for a vibra motor
|
||||
* This is used in standalone devices to match the UI.
|
||||
*/
|
||||
uint32 output_vibra = 8;
|
||||
|
||||
/*
|
||||
* Optional: Define a tertiary output pin for an active buzzer
|
||||
* This is used in standalone devices to to match the UI.
|
||||
*/
|
||||
uint32 output_buzzer = 9;
|
||||
|
||||
/*
|
||||
* IF this is true, the 'output' Pin will be pulled active high, false
|
||||
* means active low.
|
||||
*/
|
||||
bool active = 4;
|
||||
|
||||
/*
|
||||
* True: Alert when a text message arrives (output)
|
||||
*/
|
||||
bool alert_message = 5;
|
||||
|
||||
/*
|
||||
* True: Alert when a text message arrives (output_vibra)
|
||||
*/
|
||||
bool alert_message_vibra = 10;
|
||||
|
||||
/*
|
||||
* True: Alert when a text message arrives (output_buzzer)
|
||||
*/
|
||||
bool alert_message_buzzer = 11;
|
||||
|
||||
/*
|
||||
* True: Alert when the bell character is received (output)
|
||||
*/
|
||||
bool alert_bell = 6;
|
||||
|
||||
/*
|
||||
* True: Alert when the bell character is received (output_vibra)
|
||||
*/
|
||||
bool alert_bell_vibra = 12;
|
||||
|
||||
/*
|
||||
* True: Alert when the bell character is received (output_buzzer)
|
||||
*/
|
||||
bool alert_bell_buzzer = 13;
|
||||
|
||||
/*
|
||||
* use a PWM output instead of a simple on/off output. This will ignore
|
||||
* the 'output', 'output_ms' and 'active' settings and use the
|
||||
* device.buzzer_gpio instead.
|
||||
*/
|
||||
bool use_pwm = 7;
|
||||
|
||||
/*
|
||||
* The notification will toggle with 'output_ms' for this time of seconds.
|
||||
* Default is 0 which means don't repeat at all. 60 would mean blink
|
||||
* and/or beep for 60 seconds
|
||||
*/
|
||||
uint32 nag_timeout = 14;
|
||||
|
||||
/*
|
||||
* When true, enables devices with native I2S audio output to use the RTTTL over speaker like a buzzer
|
||||
* T-Watch S3 and T-Deck for example have this capability
|
||||
*/
|
||||
bool use_i2s_as_buzzer = 15;
|
||||
}
|
||||
|
||||
/*
|
||||
* Store and Forward Module Config
|
||||
*/
|
||||
message StoreForwardConfig {
|
||||
/*
|
||||
* Enable the Store and Forward Module
|
||||
*/
|
||||
bool enabled = 1;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
bool heartbeat = 2;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
uint32 records = 3;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
uint32 history_return_max = 4;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
uint32 history_return_window = 5;
|
||||
|
||||
/*
|
||||
* Set to true to let this node act as a server that stores received messages and resends them upon request.
|
||||
*/
|
||||
bool is_server = 6;
|
||||
}
|
||||
|
||||
/*
|
||||
* Preferences for the RangeTestModule
|
||||
*/
|
||||
message RangeTestConfig {
|
||||
/*
|
||||
* Enable the Range Test Module
|
||||
*/
|
||||
bool enabled = 1;
|
||||
|
||||
/*
|
||||
* Send out range test messages from this node
|
||||
*/
|
||||
uint32 sender = 2;
|
||||
|
||||
/*
|
||||
* Bool value indicating that this node should save a RangeTest.csv file.
|
||||
* ESP32 Only
|
||||
*/
|
||||
bool save = 3;
|
||||
|
||||
/*
|
||||
* Bool indicating that the node should cleanup / destroy it's RangeTest.csv file.
|
||||
* ESP32 Only
|
||||
*/
|
||||
bool clear_on_reboot = 4;
|
||||
}
|
||||
|
||||
/*
|
||||
* Configuration for both device and environment metrics
|
||||
*/
|
||||
message TelemetryConfig {
|
||||
/*
|
||||
* Interval in seconds of how often we should try to send our
|
||||
* device metrics to the mesh
|
||||
*/
|
||||
uint32 device_update_interval = 1;
|
||||
|
||||
/*
|
||||
* Interval in seconds of how often we should try to send our
|
||||
* environment measurements to the mesh
|
||||
*/
|
||||
|
||||
uint32 environment_update_interval = 2;
|
||||
|
||||
/*
|
||||
* Preferences for the Telemetry Module (Environment)
|
||||
* Enable/Disable the telemetry measurement module measurement collection
|
||||
*/
|
||||
bool environment_measurement_enabled = 3;
|
||||
|
||||
/*
|
||||
* Enable/Disable the telemetry measurement module on-device display
|
||||
*/
|
||||
bool environment_screen_enabled = 4;
|
||||
|
||||
/*
|
||||
* We'll always read the sensor in Celsius, but sometimes we might want to
|
||||
* display the results in Fahrenheit as a "user preference".
|
||||
*/
|
||||
bool environment_display_fahrenheit = 5;
|
||||
|
||||
/*
|
||||
* Enable/Disable the air quality metrics
|
||||
*/
|
||||
bool air_quality_enabled = 6;
|
||||
|
||||
/*
|
||||
* Interval in seconds of how often we should try to send our
|
||||
* air quality metrics to the mesh
|
||||
*/
|
||||
uint32 air_quality_interval = 7;
|
||||
|
||||
/*
|
||||
* Enable/disable Power metrics
|
||||
*/
|
||||
bool power_measurement_enabled = 8;
|
||||
|
||||
/*
|
||||
* Interval in seconds of how often we should try to send our
|
||||
* power metrics to the mesh
|
||||
*/
|
||||
uint32 power_update_interval = 9;
|
||||
|
||||
/*
|
||||
* Enable/Disable the power measurement module on-device display
|
||||
*/
|
||||
bool power_screen_enabled = 10;
|
||||
|
||||
/*
|
||||
* Preferences for the (Health) Telemetry Module
|
||||
* Enable/Disable the telemetry measurement module measurement collection
|
||||
*/
|
||||
bool health_measurement_enabled = 11;
|
||||
|
||||
/*
|
||||
* Interval in seconds of how often we should try to send our
|
||||
* health metrics to the mesh
|
||||
*/
|
||||
uint32 health_update_interval = 12;
|
||||
|
||||
/*
|
||||
* Enable/Disable the health telemetry module on-device display
|
||||
*/
|
||||
bool health_screen_enabled = 13;
|
||||
|
||||
/*
|
||||
* Enable/Disable the device telemetry module to send metrics to the mesh
|
||||
* Note: We will still send telemtry to the connected phone / client every minute over the API
|
||||
*/
|
||||
bool device_telemetry_enabled = 14;
|
||||
}
|
||||
|
||||
/*
|
||||
* Canned Messages Module Config
|
||||
*/
|
||||
message CannedMessageConfig {
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
enum InputEventChar {
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
NONE = 0;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
UP = 17;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
DOWN = 18;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
LEFT = 19;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
RIGHT = 20;
|
||||
|
||||
/*
|
||||
* '\n'
|
||||
*/
|
||||
SELECT = 10;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
BACK = 27;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
CANCEL = 24;
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable the rotary encoder #1. This is a 'dumb' encoder sending pulses on both A and B pins while rotating.
|
||||
*/
|
||||
bool rotary1_enabled = 1;
|
||||
|
||||
/*
|
||||
* GPIO pin for rotary encoder A port.
|
||||
*/
|
||||
uint32 inputbroker_pin_a = 2;
|
||||
|
||||
/*
|
||||
* GPIO pin for rotary encoder B port.
|
||||
*/
|
||||
uint32 inputbroker_pin_b = 3;
|
||||
|
||||
/*
|
||||
* GPIO pin for rotary encoder Press port.
|
||||
*/
|
||||
uint32 inputbroker_pin_press = 4;
|
||||
|
||||
/*
|
||||
* Generate input event on CW of this kind.
|
||||
*/
|
||||
InputEventChar inputbroker_event_cw = 5;
|
||||
|
||||
/*
|
||||
* Generate input event on CCW of this kind.
|
||||
*/
|
||||
InputEventChar inputbroker_event_ccw = 6;
|
||||
|
||||
/*
|
||||
* Generate input event on Press of this kind.
|
||||
*/
|
||||
InputEventChar inputbroker_event_press = 7;
|
||||
|
||||
/*
|
||||
* Enable the Up/Down/Select input device. Can be RAK rotary encoder or 3 buttons. Uses the a/b/press definitions from inputbroker.
|
||||
*/
|
||||
bool updown1_enabled = 8;
|
||||
|
||||
/*
|
||||
* Enable/disable CannedMessageModule.
|
||||
*/
|
||||
bool enabled = 9 [deprecated = true];
|
||||
|
||||
/*
|
||||
* Input event origin accepted by the canned message module.
|
||||
* Can be e.g. "rotEnc1", "upDownEnc1", "scanAndSelect", "cardkb", "serialkb", or keyword "_any"
|
||||
*/
|
||||
string allow_input_source = 10 [deprecated = true];
|
||||
|
||||
/*
|
||||
* CannedMessageModule also sends a bell character with the messages.
|
||||
* ExternalNotificationModule can benefit from this feature.
|
||||
*/
|
||||
bool send_bell = 11;
|
||||
}
|
||||
|
||||
/*
|
||||
Ambient Lighting Module - Settings for control of onboard LEDs to allow users to adjust the brightness levels and respective color levels.
|
||||
Initially created for the RAK14001 RGB LED module.
|
||||
*/
|
||||
message AmbientLightingConfig {
|
||||
/*
|
||||
* Sets LED to on or off.
|
||||
*/
|
||||
bool led_state = 1;
|
||||
|
||||
/*
|
||||
* Sets the current for the LED output. Default is 10.
|
||||
*/
|
||||
uint32 current = 2;
|
||||
|
||||
/*
|
||||
* Sets the red LED level. Values are 0-255.
|
||||
*/
|
||||
uint32 red = 3;
|
||||
|
||||
/*
|
||||
* Sets the green LED level. Values are 0-255.
|
||||
*/
|
||||
uint32 green = 4;
|
||||
|
||||
/*
|
||||
* Sets the blue LED level. Values are 0-255.
|
||||
*/
|
||||
uint32 blue = 5;
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
oneof payload_variant {
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
MQTTConfig mqtt = 1;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
SerialConfig serial = 2;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
ExternalNotificationConfig external_notification = 3;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
StoreForwardConfig store_forward = 4;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
RangeTestConfig range_test = 5;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
TelemetryConfig telemetry = 6;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
CannedMessageConfig canned_message = 7;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
AudioConfig audio = 8;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
RemoteHardwareConfig remote_hardware = 9;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
NeighborInfoConfig neighbor_info = 10;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
AmbientLightingConfig ambient_lighting = 11;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
DetectionSensorConfig detection_sensor = 12;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
PaxcounterConfig paxcounter = 13;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* A GPIO pin definition for remote hardware module
|
||||
*/
|
||||
message RemoteHardwarePin {
|
||||
/*
|
||||
* GPIO Pin number (must match Arduino)
|
||||
*/
|
||||
uint32 gpio_pin = 1;
|
||||
|
||||
/*
|
||||
* Name for the GPIO pin (i.e. Front gate, mailbox, etc)
|
||||
*/
|
||||
string name = 2;
|
||||
|
||||
/*
|
||||
* Type of GPIO access available to consumers on the mesh
|
||||
*/
|
||||
RemoteHardwarePinType type = 3;
|
||||
}
|
||||
|
||||
enum RemoteHardwarePinType {
|
||||
/*
|
||||
* Unset/unused
|
||||
*/
|
||||
UNKNOWN = 0;
|
||||
|
||||
/*
|
||||
* GPIO pin can be read (if it is high / low)
|
||||
*/
|
||||
DIGITAL_READ = 1;
|
||||
|
||||
/*
|
||||
* GPIO pin can be written to (high / low)
|
||||
*/
|
||||
DIGITAL_WRITE = 2;
|
||||
}
|
||||
8
packages/protobufs/meshtastic/mqtt.options
Normal file
8
packages/protobufs/meshtastic/mqtt.options
Normal file
@@ -0,0 +1,8 @@
|
||||
*ServiceEnvelope.packet type:FT_POINTER
|
||||
*ServiceEnvelope.channel_id type:FT_POINTER
|
||||
*ServiceEnvelope.gateway_id type:FT_POINTER
|
||||
|
||||
*MapReport.long_name max_size:40
|
||||
*MapReport.short_name max_size:5
|
||||
*MapReport.firmware_version max_size:18
|
||||
*MapReport.num_online_local_nodes int_size:16
|
||||
112
packages/protobufs/meshtastic/mqtt.proto
Normal file
112
packages/protobufs/meshtastic/mqtt.proto
Normal file
@@ -0,0 +1,112 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package meshtastic;
|
||||
|
||||
import "meshtastic/config.proto";
|
||||
import "meshtastic/mesh.proto";
|
||||
|
||||
option csharp_namespace = "Meshtastic.Protobufs";
|
||||
option go_package = "github.com/meshtastic/go/generated";
|
||||
option java_outer_classname = "MQTTProtos";
|
||||
option java_package = "com.geeksville.mesh";
|
||||
option swift_prefix = "";
|
||||
|
||||
/*
|
||||
* This message wraps a MeshPacket with extra metadata about the sender and how it arrived.
|
||||
*/
|
||||
message ServiceEnvelope {
|
||||
/*
|
||||
* The (probably encrypted) packet
|
||||
*/
|
||||
MeshPacket packet = 1;
|
||||
|
||||
/*
|
||||
* The global channel ID it was sent on
|
||||
*/
|
||||
string channel_id = 2;
|
||||
|
||||
/*
|
||||
* The sending gateway node ID. Can we use this to authenticate/prevent fake
|
||||
* nodeid impersonation for senders? - i.e. use gateway/mesh id (which is authenticated) + local node id as
|
||||
* the globally trusted nodenum
|
||||
*/
|
||||
string gateway_id = 3;
|
||||
}
|
||||
|
||||
/*
|
||||
* Information about a node intended to be reported unencrypted to a map using MQTT.
|
||||
*/
|
||||
message MapReport {
|
||||
/*
|
||||
* A full name for this user, i.e. "Kevin Hester"
|
||||
*/
|
||||
string long_name = 1;
|
||||
|
||||
/*
|
||||
* A VERY short name, ideally two characters.
|
||||
* Suitable for a tiny OLED screen
|
||||
*/
|
||||
string short_name = 2;
|
||||
|
||||
/*
|
||||
* Role of the node that applies specific settings for a particular use-case
|
||||
*/
|
||||
Config.DeviceConfig.Role role = 3;
|
||||
|
||||
/*
|
||||
* Hardware model of the node, i.e. T-Beam, Heltec V3, etc...
|
||||
*/
|
||||
HardwareModel hw_model = 4;
|
||||
|
||||
/*
|
||||
* Device firmware version string
|
||||
*/
|
||||
string firmware_version = 5;
|
||||
|
||||
/*
|
||||
* The region code for the radio (US, CN, EU433, etc...)
|
||||
*/
|
||||
Config.LoRaConfig.RegionCode region = 6;
|
||||
|
||||
/*
|
||||
* Modem preset used by the radio (LongFast, MediumSlow, etc...)
|
||||
*/
|
||||
Config.LoRaConfig.ModemPreset modem_preset = 7;
|
||||
|
||||
/*
|
||||
* Whether the node has a channel with default PSK and name (LongFast, MediumSlow, etc...)
|
||||
* and it uses the default frequency slot given the region and modem preset.
|
||||
*/
|
||||
bool has_default_channel = 8;
|
||||
|
||||
/*
|
||||
* Latitude: multiply by 1e-7 to get degrees in floating point
|
||||
*/
|
||||
sfixed32 latitude_i = 9;
|
||||
|
||||
/*
|
||||
* Longitude: multiply by 1e-7 to get degrees in floating point
|
||||
*/
|
||||
sfixed32 longitude_i = 10;
|
||||
|
||||
/*
|
||||
* Altitude in meters above MSL
|
||||
*/
|
||||
int32 altitude = 11;
|
||||
|
||||
/*
|
||||
* Indicates the bits of precision for latitude and longitude set by the sending node
|
||||
*/
|
||||
uint32 position_precision = 12;
|
||||
|
||||
/*
|
||||
* Number of online nodes (heard in the last 2 hours) this node has in its list that were received locally (not via MQTT)
|
||||
*/
|
||||
uint32 num_online_local_nodes = 13;
|
||||
|
||||
/*
|
||||
* User has opted in to share their location (map report) with the mqtt server
|
||||
* Controlled by map_report.should_report_location
|
||||
*/
|
||||
bool has_opted_report_location = 14;
|
||||
}
|
||||
29
packages/protobufs/meshtastic/paxcount.proto
Normal file
29
packages/protobufs/meshtastic/paxcount.proto
Normal file
@@ -0,0 +1,29 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package meshtastic;
|
||||
|
||||
option csharp_namespace = "Meshtastic.Protobufs";
|
||||
option go_package = "github.com/meshtastic/go/generated";
|
||||
option java_outer_classname = "PaxcountProtos";
|
||||
option java_package = "com.geeksville.mesh";
|
||||
option swift_prefix = "";
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
message Paxcount {
|
||||
/*
|
||||
* seen Wifi devices
|
||||
*/
|
||||
uint32 wifi = 1;
|
||||
|
||||
/*
|
||||
* Seen BLE devices
|
||||
*/
|
||||
uint32 ble = 2;
|
||||
|
||||
/*
|
||||
* Uptime in seconds
|
||||
*/
|
||||
uint32 uptime = 3;
|
||||
}
|
||||
244
packages/protobufs/meshtastic/portnums.proto
Normal file
244
packages/protobufs/meshtastic/portnums.proto
Normal file
@@ -0,0 +1,244 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package meshtastic;
|
||||
|
||||
option csharp_namespace = "Meshtastic.Protobufs";
|
||||
option go_package = "github.com/meshtastic/go/generated";
|
||||
option java_outer_classname = "Portnums";
|
||||
option java_package = "com.geeksville.mesh";
|
||||
option swift_prefix = "";
|
||||
|
||||
/*
|
||||
* For any new 'apps' that run on the device or via sister apps on phones/PCs they should pick and use a
|
||||
* unique 'portnum' for their application.
|
||||
* If you are making a new app using meshtastic, please send in a pull request to add your 'portnum' to this
|
||||
* master table.
|
||||
* PortNums should be assigned in the following range:
|
||||
* 0-63 Core Meshtastic use, do not use for third party apps
|
||||
* 64-127 Registered 3rd party apps, send in a pull request that adds a new entry to portnums.proto to register your application
|
||||
* 256-511 Use one of these portnums for your private applications that you don't want to register publically
|
||||
* All other values are reserved.
|
||||
* Note: This was formerly a Type enum named 'typ' with the same id #
|
||||
* We have change to this 'portnum' based scheme for specifying app handlers for particular payloads.
|
||||
* This change is backwards compatible by treating the legacy OPAQUE/CLEAR_TEXT values identically.
|
||||
*/
|
||||
enum PortNum {
|
||||
/*
|
||||
* Deprecated: do not use in new code (formerly called OPAQUE)
|
||||
* A message sent from a device outside of the mesh, in a form the mesh does not understand
|
||||
* NOTE: This must be 0, because it is documented in IMeshService.aidl to be so
|
||||
* ENCODING: binary undefined
|
||||
*/
|
||||
UNKNOWN_APP = 0;
|
||||
|
||||
/*
|
||||
* A simple UTF-8 text message, which even the little micros in the mesh
|
||||
* can understand and show on their screen eventually in some circumstances
|
||||
* even signal might send messages in this form (see below)
|
||||
* ENCODING: UTF-8 Plaintext (?)
|
||||
*/
|
||||
TEXT_MESSAGE_APP = 1;
|
||||
|
||||
/*
|
||||
* Reserved for built-in GPIO/example app.
|
||||
* See remote_hardware.proto/HardwareMessage for details on the message sent/received to this port number
|
||||
* ENCODING: Protobuf
|
||||
*/
|
||||
REMOTE_HARDWARE_APP = 2;
|
||||
|
||||
/*
|
||||
* The built-in position messaging app.
|
||||
* Payload is a Position message.
|
||||
* ENCODING: Protobuf
|
||||
*/
|
||||
POSITION_APP = 3;
|
||||
|
||||
/*
|
||||
* The built-in user info app.
|
||||
* Payload is a User message.
|
||||
* ENCODING: Protobuf
|
||||
*/
|
||||
NODEINFO_APP = 4;
|
||||
|
||||
/*
|
||||
* Protocol control packets for mesh protocol use.
|
||||
* Payload is a Routing message.
|
||||
* ENCODING: Protobuf
|
||||
*/
|
||||
ROUTING_APP = 5;
|
||||
|
||||
/*
|
||||
* Admin control packets.
|
||||
* Payload is a AdminMessage message.
|
||||
* ENCODING: Protobuf
|
||||
*/
|
||||
ADMIN_APP = 6;
|
||||
|
||||
/*
|
||||
* Compressed TEXT_MESSAGE payloads.
|
||||
* ENCODING: UTF-8 Plaintext (?) with Unishox2 Compression
|
||||
* NOTE: The Device Firmware converts a TEXT_MESSAGE_APP to TEXT_MESSAGE_COMPRESSED_APP if the compressed
|
||||
* payload is shorter. There's no need for app developers to do this themselves. Also the firmware will decompress
|
||||
* any incoming TEXT_MESSAGE_COMPRESSED_APP payload and convert to TEXT_MESSAGE_APP.
|
||||
*/
|
||||
TEXT_MESSAGE_COMPRESSED_APP = 7;
|
||||
|
||||
/*
|
||||
* Waypoint payloads.
|
||||
* Payload is a Waypoint message.
|
||||
* ENCODING: Protobuf
|
||||
*/
|
||||
WAYPOINT_APP = 8;
|
||||
|
||||
/*
|
||||
* Audio Payloads.
|
||||
* Encapsulated codec2 packets. On 2.4 GHZ Bandwidths only for now
|
||||
* ENCODING: codec2 audio frames
|
||||
* NOTE: audio frames contain a 3 byte header (0xc0 0xde 0xc2) and a one byte marker for the decompressed bitrate.
|
||||
* This marker comes from the 'moduleConfig.audio.bitrate' enum minus one.
|
||||
*/
|
||||
AUDIO_APP = 9;
|
||||
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
DETECTION_SENSOR_APP = 10;
|
||||
|
||||
/*
|
||||
* Same as Text Message but used for critical alerts.
|
||||
*/
|
||||
ALERT_APP = 11;
|
||||
|
||||
/*
|
||||
* Module/port for handling key verification requests.
|
||||
*/
|
||||
KEY_VERIFICATION_APP = 12;
|
||||
|
||||
/*
|
||||
* Provides a 'ping' service that replies to any packet it receives.
|
||||
* Also serves as a small example module.
|
||||
* ENCODING: ASCII Plaintext
|
||||
*/
|
||||
REPLY_APP = 32;
|
||||
|
||||
/*
|
||||
* Used for the python IP tunnel feature
|
||||
* ENCODING: IP Packet. Handled by the python API, firmware ignores this one and pases on.
|
||||
*/
|
||||
IP_TUNNEL_APP = 33;
|
||||
|
||||
/*
|
||||
* Paxcounter lib included in the firmware
|
||||
* ENCODING: protobuf
|
||||
*/
|
||||
PAXCOUNTER_APP = 34;
|
||||
|
||||
/*
|
||||
* Provides a hardware serial interface to send and receive from the Meshtastic network.
|
||||
* Connect to the RX/TX pins of a device with 38400 8N1. Packets received from the Meshtastic
|
||||
* network is forwarded to the RX pin while sending a packet to TX will go out to the Mesh network.
|
||||
* Maximum packet size of 240 bytes.
|
||||
* Module is disabled by default can be turned on by setting SERIAL_MODULE_ENABLED = 1 in SerialPlugh.cpp.
|
||||
* ENCODING: binary undefined
|
||||
*/
|
||||
SERIAL_APP = 64;
|
||||
|
||||
/*
|
||||
* STORE_FORWARD_APP (Work in Progress)
|
||||
* Maintained by Jm Casler (MC Hamster) : jm@casler.org
|
||||
* ENCODING: Protobuf
|
||||
*/
|
||||
STORE_FORWARD_APP = 65;
|
||||
|
||||
/*
|
||||
* Optional port for messages for the range test module.
|
||||
* ENCODING: ASCII Plaintext
|
||||
* NOTE: This portnum traffic is not sent to the public MQTT starting at firmware version 2.2.9
|
||||
*/
|
||||
RANGE_TEST_APP = 66;
|
||||
|
||||
/*
|
||||
* Provides a format to send and receive telemetry data from the Meshtastic network.
|
||||
* Maintained by Charles Crossan (crossan007) : crossan007@gmail.com
|
||||
* ENCODING: Protobuf
|
||||
*/
|
||||
TELEMETRY_APP = 67;
|
||||
|
||||
/*
|
||||
* Experimental tools for estimating node position without a GPS
|
||||
* Maintained by Github user a-f-G-U-C (a Meshtastic contributor)
|
||||
* Project files at https://github.com/a-f-G-U-C/Meshtastic-ZPS
|
||||
* ENCODING: arrays of int64 fields
|
||||
*/
|
||||
ZPS_APP = 68;
|
||||
|
||||
/*
|
||||
* Used to let multiple instances of Linux native applications communicate
|
||||
* as if they did using their LoRa chip.
|
||||
* Maintained by GitHub user GUVWAF.
|
||||
* Project files at https://github.com/GUVWAF/Meshtasticator
|
||||
* ENCODING: Protobuf (?)
|
||||
*/
|
||||
SIMULATOR_APP = 69;
|
||||
|
||||
/*
|
||||
* Provides a traceroute functionality to show the route a packet towards
|
||||
* a certain destination would take on the mesh. Contains a RouteDiscovery message as payload.
|
||||
* ENCODING: Protobuf
|
||||
*/
|
||||
TRACEROUTE_APP = 70;
|
||||
|
||||
/*
|
||||
* Aggregates edge info for the network by sending out a list of each node's neighbors
|
||||
* ENCODING: Protobuf
|
||||
*/
|
||||
NEIGHBORINFO_APP = 71;
|
||||
|
||||
/*
|
||||
* ATAK Plugin
|
||||
* Portnum for payloads from the official Meshtastic ATAK plugin
|
||||
*/
|
||||
ATAK_PLUGIN = 72;
|
||||
|
||||
/*
|
||||
* Provides unencrypted information about a node for consumption by a map via MQTT
|
||||
*/
|
||||
MAP_REPORT_APP = 73;
|
||||
|
||||
/*
|
||||
* PowerStress based monitoring support (for automated power consumption testing)
|
||||
*/
|
||||
POWERSTRESS_APP = 74;
|
||||
|
||||
/*
|
||||
* Reticulum Network Stack Tunnel App
|
||||
* ENCODING: Fragmented RNS Packet. Handled by Meshtastic RNS interface
|
||||
*/
|
||||
RETICULUM_TUNNEL_APP = 76;
|
||||
|
||||
/*
|
||||
* App for transporting Cayenne Low Power Payload, popular for LoRaWAN sensor nodes. Offers ability to send
|
||||
* arbitrary telemetry over meshtastic that is not covered by telemetry.proto
|
||||
* ENCODING: CayenneLLP
|
||||
*/
|
||||
CAYENNE_APP = 77;
|
||||
|
||||
/*
|
||||
* Private applications should use portnums >= 256.
|
||||
* To simplify initial development and testing you can use "PRIVATE_APP"
|
||||
* in your code without needing to rebuild protobuf files (via [regen-protos.sh](https://github.com/meshtastic/firmware/blob/master/bin/regen-protos.sh))
|
||||
*/
|
||||
PRIVATE_APP = 256;
|
||||
|
||||
/*
|
||||
* ATAK Forwarder Module https://github.com/paulmandal/atak-forwarder
|
||||
* ENCODING: libcotshrink
|
||||
*/
|
||||
ATAK_FORWARDER = 257;
|
||||
|
||||
/*
|
||||
* Currently we limit port nums to no higher than this value
|
||||
*/
|
||||
MAX = 511;
|
||||
}
|
||||
103
packages/protobufs/meshtastic/powermon.proto
Normal file
103
packages/protobufs/meshtastic/powermon.proto
Normal file
@@ -0,0 +1,103 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package meshtastic;
|
||||
|
||||
option csharp_namespace = "Meshtastic.Protobufs";
|
||||
option go_package = "github.com/meshtastic/go/generated";
|
||||
option java_outer_classname = "PowerMonProtos";
|
||||
option java_package = "com.geeksville.mesh";
|
||||
option swift_prefix = "";
|
||||
|
||||
/* Note: There are no 'PowerMon' messages normally in use (PowerMons are sent only as structured logs - slogs).
|
||||
* But we wrap our State enum in this message to effectively nest a namespace (without our linter yelling at us)
|
||||
*/
|
||||
message PowerMon {
|
||||
/* Any significant power changing event in meshtastic should be tagged with a powermon state transition.
|
||||
* If you are making new meshtastic features feel free to add new entries at the end of this definition.
|
||||
*/
|
||||
enum State {
|
||||
None = 0;
|
||||
|
||||
CPU_DeepSleep = 0x01;
|
||||
CPU_LightSleep = 0x02;
|
||||
|
||||
/*
|
||||
The external Vext1 power is on. Many boards have auxillary power rails that the CPU turns on only
|
||||
occasionally. In cases where that rail has multiple devices on it we usually want to have logging on
|
||||
the state of that rail as an independent record.
|
||||
For instance on the Heltec Tracker 1.1 board, this rail is the power source for the GPS and screen.
|
||||
|
||||
The log messages will be short and complete (see PowerMon.Event in the protobufs for details).
|
||||
something like "S:PM:C,0x00001234,REASON" where the hex number is the bitmask of all current states.
|
||||
(We use a bitmask for states so that if a log message gets lost it won't be fatal)
|
||||
*/
|
||||
Vext1_On = 0x04;
|
||||
|
||||
Lora_RXOn = 0x08;
|
||||
Lora_TXOn = 0x10;
|
||||
Lora_RXActive = 0x20;
|
||||
BT_On = 0x40;
|
||||
LED_On = 0x80;
|
||||
|
||||
Screen_On = 0x100;
|
||||
Screen_Drawing = 0x200;
|
||||
Wifi_On = 0x400;
|
||||
|
||||
/*
|
||||
* GPS is actively trying to find our location
|
||||
* See GPSPowerState for more details
|
||||
*/
|
||||
GPS_Active = 0x800;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* PowerStress testing support via the C++ PowerStress module
|
||||
*/
|
||||
message PowerStressMessage {
|
||||
/*
|
||||
* What operation would we like the UUT to perform.
|
||||
* note: senders should probably set want_response in their request packets, so that they can know when the state
|
||||
* machine has started processing their request
|
||||
*/
|
||||
enum Opcode {
|
||||
/*
|
||||
* Unset/unused
|
||||
*/
|
||||
UNSET = 0;
|
||||
|
||||
PRINT_INFO = 1; // Print board version slog and send an ack that we are alive and ready to process commands
|
||||
FORCE_QUIET = 2; // Try to turn off all automatic processing of packets, screen, sleeping, etc (to make it easier to measure in isolation)
|
||||
END_QUIET = 3; // Stop powerstress processing - probably by just rebooting the board
|
||||
|
||||
SCREEN_ON = 16; // Turn the screen on
|
||||
SCREEN_OFF = 17; // Turn the screen off
|
||||
|
||||
CPU_IDLE = 32; // Let the CPU run but we assume mostly idling for num_seconds
|
||||
CPU_DEEPSLEEP = 33; // Force deep sleep for FIXME seconds
|
||||
CPU_FULLON = 34; // Spin the CPU as fast as possible for num_seconds
|
||||
|
||||
LED_ON = 48; // Turn the LED on for num_seconds (and leave it on - for baseline power measurement purposes)
|
||||
LED_OFF = 49; // Force the LED off for num_seconds
|
||||
|
||||
LORA_OFF = 64; // Completely turn off the LORA radio for num_seconds
|
||||
LORA_TX = 65; // Send Lora packets for num_seconds
|
||||
LORA_RX = 66; // Receive Lora packets for num_seconds (node will be mostly just listening, unless an external agent is helping stress this by sending packets on the current channel)
|
||||
|
||||
BT_OFF = 80; // Turn off the BT radio for num_seconds
|
||||
BT_ON = 81; // Turn on the BT radio for num_seconds
|
||||
|
||||
WIFI_OFF = 96; // Turn off the WIFI radio for num_seconds
|
||||
WIFI_ON = 97; // Turn on the WIFI radio for num_seconds
|
||||
|
||||
GPS_OFF = 112; // Turn off the GPS radio for num_seconds
|
||||
GPS_ON = 113; // Turn on the GPS radio for num_seconds
|
||||
}
|
||||
|
||||
/*
|
||||
* What type of HardwareMessage is this?
|
||||
*/
|
||||
Opcode cmd = 1;
|
||||
|
||||
float num_seconds = 2;
|
||||
}
|
||||
75
packages/protobufs/meshtastic/remote_hardware.proto
Normal file
75
packages/protobufs/meshtastic/remote_hardware.proto
Normal file
@@ -0,0 +1,75 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package meshtastic;
|
||||
|
||||
option csharp_namespace = "Meshtastic.Protobufs";
|
||||
option go_package = "github.com/meshtastic/go/generated";
|
||||
option java_outer_classname = "RemoteHardware";
|
||||
option java_package = "com.geeksville.mesh";
|
||||
option swift_prefix = "";
|
||||
|
||||
/*
|
||||
* An example app to show off the module system. This message is used for
|
||||
* REMOTE_HARDWARE_APP PortNums.
|
||||
* Also provides easy remote access to any GPIO.
|
||||
* In the future other remote hardware operations can be added based on user interest
|
||||
* (i.e. serial output, spi/i2c input/output).
|
||||
* FIXME - currently this feature is turned on by default which is dangerous
|
||||
* because no security yet (beyond the channel mechanism).
|
||||
* It should be off by default and then protected based on some TBD mechanism
|
||||
* (a special channel once multichannel support is included?)
|
||||
*/
|
||||
message HardwareMessage {
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
enum Type {
|
||||
/*
|
||||
* Unset/unused
|
||||
*/
|
||||
UNSET = 0;
|
||||
|
||||
/*
|
||||
* Set gpio gpios based on gpio_mask/gpio_value
|
||||
*/
|
||||
WRITE_GPIOS = 1;
|
||||
|
||||
/*
|
||||
* We are now interested in watching the gpio_mask gpios.
|
||||
* If the selected gpios change, please broadcast GPIOS_CHANGED.
|
||||
* Will implicitly change the gpios requested to be INPUT gpios.
|
||||
*/
|
||||
WATCH_GPIOS = 2;
|
||||
|
||||
/*
|
||||
* The gpios listed in gpio_mask have changed, the new values are listed in gpio_value
|
||||
*/
|
||||
GPIOS_CHANGED = 3;
|
||||
|
||||
/*
|
||||
* Read the gpios specified in gpio_mask, send back a READ_GPIOS_REPLY reply with gpio_value populated
|
||||
*/
|
||||
READ_GPIOS = 4;
|
||||
|
||||
/*
|
||||
* A reply to READ_GPIOS. gpio_mask and gpio_value will be populated
|
||||
*/
|
||||
READ_GPIOS_REPLY = 5;
|
||||
}
|
||||
|
||||
/*
|
||||
* What type of HardwareMessage is this?
|
||||
*/
|
||||
Type type = 1;
|
||||
|
||||
/*
|
||||
* What gpios are we changing. Not used for all MessageTypes, see MessageType for details
|
||||
*/
|
||||
uint64 gpio_mask = 2;
|
||||
|
||||
/*
|
||||
* For gpios that were listed in gpio_mask as valid, what are the signal levels for those gpios.
|
||||
* Not used for all MessageTypes, see MessageType for details
|
||||
*/
|
||||
uint64 gpio_value = 3;
|
||||
}
|
||||
1
packages/protobufs/meshtastic/rtttl.options
Normal file
1
packages/protobufs/meshtastic/rtttl.options
Normal file
@@ -0,0 +1 @@
|
||||
*RTTTLConfig.ringtone max_size:231
|
||||
19
packages/protobufs/meshtastic/rtttl.proto
Normal file
19
packages/protobufs/meshtastic/rtttl.proto
Normal file
@@ -0,0 +1,19 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package meshtastic;
|
||||
|
||||
option csharp_namespace = "Meshtastic.Protobufs";
|
||||
option go_package = "github.com/meshtastic/go/generated";
|
||||
option java_outer_classname = "RTTTLConfigProtos";
|
||||
option java_package = "com.geeksville.mesh";
|
||||
option swift_prefix = "";
|
||||
|
||||
/*
|
||||
* Canned message module configuration.
|
||||
*/
|
||||
message RTTTLConfig {
|
||||
/*
|
||||
* Ringtone for PWM Buzzer in RTTTL Format.
|
||||
*/
|
||||
string ringtone = 1;
|
||||
}
|
||||
1
packages/protobufs/meshtastic/storeforward.options
Normal file
1
packages/protobufs/meshtastic/storeforward.options
Normal file
@@ -0,0 +1 @@
|
||||
*StoreAndForward.text max_size:233
|
||||
218
packages/protobufs/meshtastic/storeforward.proto
Normal file
218
packages/protobufs/meshtastic/storeforward.proto
Normal file
@@ -0,0 +1,218 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package meshtastic;
|
||||
|
||||
option csharp_namespace = "Meshtastic.Protobufs";
|
||||
option go_package = "github.com/meshtastic/go/generated";
|
||||
option java_outer_classname = "StoreAndForwardProtos";
|
||||
option java_package = "com.geeksville.mesh";
|
||||
option swift_prefix = "";
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
message StoreAndForward {
|
||||
/*
|
||||
* 001 - 063 = From Router
|
||||
* 064 - 127 = From Client
|
||||
*/
|
||||
enum RequestResponse {
|
||||
/*
|
||||
* Unset/unused
|
||||
*/
|
||||
UNSET = 0;
|
||||
|
||||
/*
|
||||
* Router is an in error state.
|
||||
*/
|
||||
ROUTER_ERROR = 1;
|
||||
|
||||
/*
|
||||
* Router heartbeat
|
||||
*/
|
||||
ROUTER_HEARTBEAT = 2;
|
||||
|
||||
/*
|
||||
* Router has requested the client respond. This can work as a
|
||||
* "are you there" message.
|
||||
*/
|
||||
ROUTER_PING = 3;
|
||||
|
||||
/*
|
||||
* The response to a "Ping"
|
||||
*/
|
||||
ROUTER_PONG = 4;
|
||||
|
||||
/*
|
||||
* Router is currently busy. Please try again later.
|
||||
*/
|
||||
ROUTER_BUSY = 5;
|
||||
|
||||
/*
|
||||
* Router is responding to a request for history.
|
||||
*/
|
||||
ROUTER_HISTORY = 6;
|
||||
|
||||
/*
|
||||
* Router is responding to a request for stats.
|
||||
*/
|
||||
ROUTER_STATS = 7;
|
||||
|
||||
/*
|
||||
* Router sends a text message from its history that was a direct message.
|
||||
*/
|
||||
ROUTER_TEXT_DIRECT = 8;
|
||||
|
||||
/*
|
||||
* Router sends a text message from its history that was a broadcast.
|
||||
*/
|
||||
ROUTER_TEXT_BROADCAST = 9;
|
||||
|
||||
/*
|
||||
* Client is an in error state.
|
||||
*/
|
||||
CLIENT_ERROR = 64;
|
||||
|
||||
/*
|
||||
* Client has requested a replay from the router.
|
||||
*/
|
||||
CLIENT_HISTORY = 65;
|
||||
|
||||
/*
|
||||
* Client has requested stats from the router.
|
||||
*/
|
||||
CLIENT_STATS = 66;
|
||||
|
||||
/*
|
||||
* Client has requested the router respond. This can work as a
|
||||
* "are you there" message.
|
||||
*/
|
||||
CLIENT_PING = 67;
|
||||
|
||||
/*
|
||||
* The response to a "Ping"
|
||||
*/
|
||||
CLIENT_PONG = 68;
|
||||
|
||||
/*
|
||||
* Client has requested that the router abort processing the client's request
|
||||
*/
|
||||
CLIENT_ABORT = 106;
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
message Statistics {
|
||||
/*
|
||||
* Number of messages we have ever seen
|
||||
*/
|
||||
uint32 messages_total = 1;
|
||||
|
||||
/*
|
||||
* Number of messages we have currently saved our history.
|
||||
*/
|
||||
uint32 messages_saved = 2;
|
||||
|
||||
/*
|
||||
* Maximum number of messages we will save
|
||||
*/
|
||||
uint32 messages_max = 3;
|
||||
|
||||
/*
|
||||
* Router uptime in seconds
|
||||
*/
|
||||
uint32 up_time = 4;
|
||||
|
||||
/*
|
||||
* Number of times any client sent a request to the S&F.
|
||||
*/
|
||||
uint32 requests = 5;
|
||||
|
||||
/*
|
||||
* Number of times the history was requested.
|
||||
*/
|
||||
uint32 requests_history = 6;
|
||||
|
||||
/*
|
||||
* Is the heartbeat enabled on the server?
|
||||
*/
|
||||
bool heartbeat = 7;
|
||||
|
||||
/*
|
||||
* Maximum number of messages the server will return.
|
||||
*/
|
||||
uint32 return_max = 8;
|
||||
|
||||
/*
|
||||
* Maximum history window in minutes the server will return messages from.
|
||||
*/
|
||||
uint32 return_window = 9;
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
message History {
|
||||
/*
|
||||
* Number of that will be sent to the client
|
||||
*/
|
||||
uint32 history_messages = 1;
|
||||
|
||||
/*
|
||||
* The window of messages that was used to filter the history client requested
|
||||
*/
|
||||
uint32 window = 2;
|
||||
|
||||
/*
|
||||
* Index in the packet history of the last message sent in a previous request to the server.
|
||||
* Will be sent to the client before sending the history and can be set in a subsequent request to avoid getting packets the server already sent to the client.
|
||||
*/
|
||||
uint32 last_request = 3;
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
message Heartbeat {
|
||||
/*
|
||||
* Period in seconds that the heartbeat is sent out that will be sent to the client
|
||||
*/
|
||||
uint32 period = 1;
|
||||
|
||||
/*
|
||||
* If set, this is not the primary Store & Forward router on the mesh
|
||||
*/
|
||||
uint32 secondary = 2;
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
RequestResponse rr = 1;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
oneof variant {
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
Statistics stats = 2;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
History history = 3;
|
||||
|
||||
/*
|
||||
* TODO: REPLACE
|
||||
*/
|
||||
Heartbeat heartbeat = 4;
|
||||
|
||||
/*
|
||||
* Text from history message.
|
||||
*/
|
||||
bytes text = 5;
|
||||
}
|
||||
}
|
||||
18
packages/protobufs/meshtastic/telemetry.options
Normal file
18
packages/protobufs/meshtastic/telemetry.options
Normal file
@@ -0,0 +1,18 @@
|
||||
# options for nanopb
|
||||
# https://jpa.kapsi.fi/nanopb/docs/reference.html#proto-file-options
|
||||
|
||||
*EnvironmentMetrics.iaq int_size:16
|
||||
*EnvironmentMetrics.wind_direction int_size:16
|
||||
*EnvironmentMetrics.soil_moisture int_size:8
|
||||
|
||||
*LocalStats.num_online_nodes int_size:16
|
||||
*LocalStats.num_total_nodes int_size:16
|
||||
*LocalStats.num_tx_dropped int_size:16
|
||||
|
||||
*HealthMetrics.heart_bpm int_size:8
|
||||
*HealthMetrics.spO2 int_size:8
|
||||
|
||||
*HostMetrics.load1 int_size:16
|
||||
*HostMetrics.load5 int_size:16
|
||||
*HostMetrics.load15 int_size:16
|
||||
*HostMetrics.user_string max_size:200
|
||||
808
packages/protobufs/meshtastic/telemetry.proto
Normal file
808
packages/protobufs/meshtastic/telemetry.proto
Normal file
@@ -0,0 +1,808 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package meshtastic;
|
||||
|
||||
option csharp_namespace = "Meshtastic.Protobufs";
|
||||
option go_package = "github.com/meshtastic/go/generated";
|
||||
option java_outer_classname = "TelemetryProtos";
|
||||
option java_package = "com.geeksville.mesh";
|
||||
option swift_prefix = "";
|
||||
|
||||
/*
|
||||
* Key native device metrics such as battery level
|
||||
*/
|
||||
message DeviceMetrics {
|
||||
/*
|
||||
* 0-100 (>100 means powered)
|
||||
*/
|
||||
optional uint32 battery_level = 1;
|
||||
|
||||
/*
|
||||
* Voltage measured
|
||||
*/
|
||||
optional float voltage = 2;
|
||||
|
||||
/*
|
||||
* Utilization for the current channel, including well formed TX, RX and malformed RX (aka noise).
|
||||
*/
|
||||
optional float channel_utilization = 3;
|
||||
|
||||
/*
|
||||
* Percent of airtime for transmission used within the last hour.
|
||||
*/
|
||||
optional float air_util_tx = 4;
|
||||
|
||||
/*
|
||||
* How long the device has been running since the last reboot (in seconds)
|
||||
*/
|
||||
optional uint32 uptime_seconds = 5;
|
||||
}
|
||||
|
||||
/*
|
||||
* Weather station or other environmental metrics
|
||||
*/
|
||||
message EnvironmentMetrics {
|
||||
/*
|
||||
* Temperature measured
|
||||
*/
|
||||
optional float temperature = 1;
|
||||
|
||||
/*
|
||||
* Relative humidity percent measured
|
||||
*/
|
||||
optional float relative_humidity = 2;
|
||||
|
||||
/*
|
||||
* Barometric pressure in hPA measured
|
||||
*/
|
||||
optional float barometric_pressure = 3;
|
||||
|
||||
/*
|
||||
* Gas resistance in MOhm measured
|
||||
*/
|
||||
optional float gas_resistance = 4;
|
||||
|
||||
/*
|
||||
* Voltage measured (To be depreciated in favor of PowerMetrics in Meshtastic 3.x)
|
||||
*/
|
||||
optional float voltage = 5;
|
||||
|
||||
/*
|
||||
* Current measured (To be depreciated in favor of PowerMetrics in Meshtastic 3.x)
|
||||
*/
|
||||
optional float current = 6;
|
||||
|
||||
/*
|
||||
* relative scale IAQ value as measured by Bosch BME680 . value 0-500.
|
||||
* Belongs to Air Quality but is not particle but VOC measurement. Other VOC values can also be put in here.
|
||||
*/
|
||||
optional uint32 iaq = 7;
|
||||
|
||||
/*
|
||||
* RCWL9620 Doppler Radar Distance Sensor, used for water level detection. Float value in mm.
|
||||
*/
|
||||
optional float distance = 8;
|
||||
|
||||
/*
|
||||
* VEML7700 high accuracy ambient light(Lux) digital 16-bit resolution sensor.
|
||||
*/
|
||||
optional float lux = 9;
|
||||
|
||||
/*
|
||||
* VEML7700 high accuracy white light(irradiance) not calibrated digital 16-bit resolution sensor.
|
||||
*/
|
||||
optional float white_lux = 10;
|
||||
|
||||
/*
|
||||
* Infrared lux
|
||||
*/
|
||||
optional float ir_lux = 11;
|
||||
|
||||
/*
|
||||
* Ultraviolet lux
|
||||
*/
|
||||
optional float uv_lux = 12;
|
||||
|
||||
/*
|
||||
* Wind direction in degrees
|
||||
* 0 degrees = North, 90 = East, etc...
|
||||
*/
|
||||
optional uint32 wind_direction = 13;
|
||||
|
||||
/*
|
||||
* Wind speed in m/s
|
||||
*/
|
||||
optional float wind_speed = 14;
|
||||
|
||||
/*
|
||||
* Weight in KG
|
||||
*/
|
||||
optional float weight = 15;
|
||||
|
||||
/*
|
||||
* Wind gust in m/s
|
||||
*/
|
||||
optional float wind_gust = 16;
|
||||
|
||||
/*
|
||||
* Wind lull in m/s
|
||||
*/
|
||||
optional float wind_lull = 17;
|
||||
|
||||
/*
|
||||
* Radiation in µR/h
|
||||
*/
|
||||
optional float radiation = 18;
|
||||
|
||||
/*
|
||||
* Rainfall in the last hour in mm
|
||||
*/
|
||||
optional float rainfall_1h = 19;
|
||||
|
||||
/*
|
||||
* Rainfall in the last 24 hours in mm
|
||||
*/
|
||||
optional float rainfall_24h = 20;
|
||||
|
||||
/*
|
||||
* Soil moisture measured (% 1-100)
|
||||
*/
|
||||
optional uint32 soil_moisture = 21;
|
||||
|
||||
/*
|
||||
* Soil temperature measured (*C)
|
||||
*/
|
||||
optional float soil_temperature = 22;
|
||||
}
|
||||
|
||||
/*
|
||||
* Power Metrics (voltage / current / etc)
|
||||
*/
|
||||
message PowerMetrics {
|
||||
/*
|
||||
* Voltage (Ch1)
|
||||
*/
|
||||
optional float ch1_voltage = 1;
|
||||
|
||||
/*
|
||||
* Current (Ch1)
|
||||
*/
|
||||
optional float ch1_current = 2;
|
||||
|
||||
/*
|
||||
* Voltage (Ch2)
|
||||
*/
|
||||
optional float ch2_voltage = 3;
|
||||
|
||||
/*
|
||||
* Current (Ch2)
|
||||
*/
|
||||
optional float ch2_current = 4;
|
||||
|
||||
/*
|
||||
* Voltage (Ch3)
|
||||
*/
|
||||
optional float ch3_voltage = 5;
|
||||
|
||||
/*
|
||||
* Current (Ch3)
|
||||
*/
|
||||
optional float ch3_current = 6;
|
||||
|
||||
/*
|
||||
* Voltage (Ch4)
|
||||
*/
|
||||
optional float ch4_voltage = 7;
|
||||
|
||||
/*
|
||||
* Current (Ch4)
|
||||
*/
|
||||
optional float ch4_current = 8;
|
||||
|
||||
/*
|
||||
* Voltage (Ch5)
|
||||
*/
|
||||
optional float ch5_voltage = 9;
|
||||
|
||||
/*
|
||||
* Current (Ch5)
|
||||
*/
|
||||
optional float ch5_current = 10;
|
||||
|
||||
/*
|
||||
* Voltage (Ch6)
|
||||
*/
|
||||
optional float ch6_voltage = 11;
|
||||
|
||||
/*
|
||||
* Current (Ch6)
|
||||
*/
|
||||
optional float ch6_current = 12;
|
||||
|
||||
/*
|
||||
* Voltage (Ch7)
|
||||
*/
|
||||
optional float ch7_voltage = 13;
|
||||
|
||||
/*
|
||||
* Current (Ch7)
|
||||
*/
|
||||
optional float ch7_current = 14;
|
||||
|
||||
/*
|
||||
* Voltage (Ch8)
|
||||
*/
|
||||
optional float ch8_voltage = 15;
|
||||
|
||||
/*
|
||||
* Current (Ch8)
|
||||
*/
|
||||
optional float ch8_current = 16;
|
||||
}
|
||||
|
||||
/*
|
||||
* Air quality metrics
|
||||
*/
|
||||
message AirQualityMetrics {
|
||||
/*
|
||||
* Concentration Units Standard PM1.0 in ug/m3
|
||||
*/
|
||||
optional uint32 pm10_standard = 1;
|
||||
|
||||
/*
|
||||
* Concentration Units Standard PM2.5 in ug/m3
|
||||
*/
|
||||
optional uint32 pm25_standard = 2;
|
||||
|
||||
/*
|
||||
* Concentration Units Standard PM10.0 in ug/m3
|
||||
*/
|
||||
optional uint32 pm100_standard = 3;
|
||||
|
||||
/*
|
||||
* Concentration Units Environmental PM1.0 in ug/m3
|
||||
*/
|
||||
optional uint32 pm10_environmental = 4;
|
||||
|
||||
/*
|
||||
* Concentration Units Environmental PM2.5 in ug/m3
|
||||
*/
|
||||
optional uint32 pm25_environmental = 5;
|
||||
|
||||
/*
|
||||
* Concentration Units Environmental PM10.0 in ug/m3
|
||||
*/
|
||||
optional uint32 pm100_environmental = 6;
|
||||
|
||||
/*
|
||||
* 0.3um Particle Count in #/0.1l
|
||||
*/
|
||||
optional uint32 particles_03um = 7;
|
||||
|
||||
/*
|
||||
* 0.5um Particle Count in #/0.1l
|
||||
*/
|
||||
optional uint32 particles_05um = 8;
|
||||
|
||||
/*
|
||||
* 1.0um Particle Count in #/0.1l
|
||||
*/
|
||||
optional uint32 particles_10um = 9;
|
||||
|
||||
/*
|
||||
* 2.5um Particle Count in #/0.1l
|
||||
*/
|
||||
optional uint32 particles_25um = 10;
|
||||
|
||||
/*
|
||||
* 5.0um Particle Count in #/0.1l
|
||||
*/
|
||||
optional uint32 particles_50um = 11;
|
||||
|
||||
/*
|
||||
* 10.0um Particle Count in #/0.1l
|
||||
*/
|
||||
optional uint32 particles_100um = 12;
|
||||
|
||||
/*
|
||||
* CO2 concentration in ppm
|
||||
*/
|
||||
optional uint32 co2 = 13;
|
||||
|
||||
/*
|
||||
* CO2 sensor temperature in degC
|
||||
*/
|
||||
optional float co2_temperature = 14;
|
||||
|
||||
/*
|
||||
* CO2 sensor relative humidity in %
|
||||
*/
|
||||
optional float co2_humidity = 15;
|
||||
|
||||
/*
|
||||
* Formaldehyde sensor formaldehyde concentration in ppb
|
||||
*/
|
||||
optional float form_formaldehyde = 16;
|
||||
|
||||
/*
|
||||
* Formaldehyde sensor relative humidity in %RH
|
||||
*/
|
||||
optional float form_humidity = 17;
|
||||
|
||||
/*
|
||||
* Formaldehyde sensor temperature in degrees Celsius
|
||||
*/
|
||||
optional float form_temperature = 18;
|
||||
|
||||
/*
|
||||
* Concentration Units Standard PM4.0 in ug/m3
|
||||
*/
|
||||
optional uint32 pm40_standard = 19;
|
||||
|
||||
/*
|
||||
* 4.0um Particle Count in #/0.1l
|
||||
*/
|
||||
optional uint32 particles_40um = 20;
|
||||
|
||||
/*
|
||||
* PM Sensor Temperature
|
||||
*/
|
||||
optional float pm_temperature = 21;
|
||||
|
||||
/*
|
||||
* PM Sensor humidity
|
||||
*/
|
||||
optional float pm_humidity = 22;
|
||||
|
||||
/*
|
||||
* PM Sensor VOC Index
|
||||
*/
|
||||
optional float pm_voc_idx = 23;
|
||||
|
||||
/*
|
||||
* PM Sensor NOx Index
|
||||
*/
|
||||
optional float pm_nox_idx = 24;
|
||||
|
||||
/*
|
||||
* Typical Particle Size in um
|
||||
*/
|
||||
optional float particles_tps = 25;
|
||||
}
|
||||
|
||||
/*
|
||||
* Local device mesh statistics
|
||||
*/
|
||||
message LocalStats {
|
||||
/*
|
||||
* How long the device has been running since the last reboot (in seconds)
|
||||
*/
|
||||
uint32 uptime_seconds = 1;
|
||||
/*
|
||||
* Utilization for the current channel, including well formed TX, RX and malformed RX (aka noise).
|
||||
*/
|
||||
float channel_utilization = 2;
|
||||
/*
|
||||
* Percent of airtime for transmission used within the last hour.
|
||||
*/
|
||||
float air_util_tx = 3;
|
||||
|
||||
/*
|
||||
* Number of packets sent
|
||||
*/
|
||||
uint32 num_packets_tx = 4;
|
||||
|
||||
/*
|
||||
* Number of packets received (both good and bad)
|
||||
*/
|
||||
uint32 num_packets_rx = 5;
|
||||
|
||||
/*
|
||||
* Number of packets received that are malformed or violate the protocol
|
||||
*/
|
||||
uint32 num_packets_rx_bad = 6;
|
||||
|
||||
/*
|
||||
* Number of nodes online (in the past 2 hours)
|
||||
*/
|
||||
uint32 num_online_nodes = 7;
|
||||
|
||||
/*
|
||||
* Number of nodes total
|
||||
*/
|
||||
uint32 num_total_nodes = 8;
|
||||
|
||||
/*
|
||||
* Number of received packets that were duplicates (due to multiple nodes relaying).
|
||||
* If this number is high, there are nodes in the mesh relaying packets when it's unnecessary, for example due to the ROUTER/REPEATER role.
|
||||
*/
|
||||
uint32 num_rx_dupe = 9;
|
||||
|
||||
/*
|
||||
* Number of packets we transmitted that were a relay for others (not originating from ourselves).
|
||||
*/
|
||||
uint32 num_tx_relay = 10;
|
||||
|
||||
/*
|
||||
* Number of times we canceled a packet to be relayed, because someone else did it before us.
|
||||
* This will always be zero for ROUTERs/REPEATERs. If this number is high, some other node(s) is/are relaying faster than you.
|
||||
*/
|
||||
uint32 num_tx_relay_canceled = 11;
|
||||
|
||||
/*
|
||||
* Number of bytes used in the heap
|
||||
*/
|
||||
uint32 heap_total_bytes = 12;
|
||||
|
||||
/*
|
||||
* Number of bytes free in the heap
|
||||
*/
|
||||
uint32 heap_free_bytes = 13;
|
||||
|
||||
/*
|
||||
* Number of packets that were dropped because the transmit queue was full.
|
||||
*/
|
||||
uint32 num_tx_dropped = 14;
|
||||
}
|
||||
|
||||
/*
|
||||
* Health telemetry metrics
|
||||
*/
|
||||
message HealthMetrics {
|
||||
/*
|
||||
* Heart rate (beats per minute)
|
||||
*/
|
||||
optional uint32 heart_bpm = 1;
|
||||
|
||||
/*
|
||||
* SpO2 (blood oxygen saturation) level
|
||||
*/
|
||||
optional uint32 spO2 = 2;
|
||||
|
||||
/*
|
||||
* Body temperature in degrees Celsius
|
||||
*/
|
||||
optional float temperature = 3;
|
||||
}
|
||||
|
||||
/*
|
||||
* Linux host metrics
|
||||
*/
|
||||
message HostMetrics {
|
||||
/*
|
||||
* Host system uptime
|
||||
*/
|
||||
uint32 uptime_seconds = 1;
|
||||
|
||||
/*
|
||||
* Host system free memory
|
||||
*/
|
||||
uint64 freemem_bytes = 2;
|
||||
|
||||
/*
|
||||
* Host system disk space free for /
|
||||
*/
|
||||
uint64 diskfree1_bytes = 3;
|
||||
|
||||
/*
|
||||
* Secondary system disk space free
|
||||
*/
|
||||
optional uint64 diskfree2_bytes = 4;
|
||||
|
||||
/*
|
||||
* Tertiary disk space free
|
||||
*/
|
||||
optional uint64 diskfree3_bytes = 5;
|
||||
|
||||
/*
|
||||
* Host system one minute load in 1/100ths
|
||||
*/
|
||||
uint32 load1 = 6;
|
||||
|
||||
/*
|
||||
* Host system five minute load in 1/100ths
|
||||
*/
|
||||
uint32 load5 = 7;
|
||||
|
||||
/*
|
||||
* Host system fifteen minute load in 1/100ths
|
||||
*/
|
||||
uint32 load15 = 8;
|
||||
|
||||
/*
|
||||
* Optional User-provided string for arbitrary host system information
|
||||
* that doesn't make sense as a dedicated entry.
|
||||
*/
|
||||
optional string user_string = 9;
|
||||
}
|
||||
|
||||
/*
|
||||
* Types of Measurements the telemetry module is equipped to handle
|
||||
*/
|
||||
message Telemetry {
|
||||
/*
|
||||
* Seconds since 1970 - or 0 for unknown/unset
|
||||
*/
|
||||
fixed32 time = 1;
|
||||
|
||||
oneof variant {
|
||||
/*
|
||||
* Key native device metrics such as battery level
|
||||
*/
|
||||
DeviceMetrics device_metrics = 2;
|
||||
|
||||
/*
|
||||
* Weather station or other environmental metrics
|
||||
*/
|
||||
EnvironmentMetrics environment_metrics = 3;
|
||||
|
||||
/*
|
||||
* Air quality metrics
|
||||
*/
|
||||
AirQualityMetrics air_quality_metrics = 4;
|
||||
|
||||
/*
|
||||
* Power Metrics
|
||||
*/
|
||||
PowerMetrics power_metrics = 5;
|
||||
|
||||
/*
|
||||
* Local device mesh statistics
|
||||
*/
|
||||
LocalStats local_stats = 6;
|
||||
|
||||
/*
|
||||
* Health telemetry metrics
|
||||
*/
|
||||
HealthMetrics health_metrics = 7;
|
||||
|
||||
/*
|
||||
* Linux host metrics
|
||||
*/
|
||||
HostMetrics host_metrics = 8;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Supported I2C Sensors for telemetry in Meshtastic
|
||||
*/
|
||||
enum TelemetrySensorType {
|
||||
/*
|
||||
* No external telemetry sensor explicitly set
|
||||
*/
|
||||
SENSOR_UNSET = 0;
|
||||
|
||||
/*
|
||||
* High accuracy temperature, pressure, humidity
|
||||
*/
|
||||
BME280 = 1;
|
||||
|
||||
/*
|
||||
* High accuracy temperature, pressure, humidity, and air resistance
|
||||
*/
|
||||
BME680 = 2;
|
||||
|
||||
/*
|
||||
* Very high accuracy temperature
|
||||
*/
|
||||
MCP9808 = 3;
|
||||
|
||||
/*
|
||||
* Moderate accuracy current and voltage
|
||||
*/
|
||||
INA260 = 4;
|
||||
|
||||
/*
|
||||
* Moderate accuracy current and voltage
|
||||
*/
|
||||
INA219 = 5;
|
||||
|
||||
/*
|
||||
* High accuracy temperature and pressure
|
||||
*/
|
||||
BMP280 = 6;
|
||||
|
||||
/*
|
||||
* High accuracy temperature and humidity
|
||||
*/
|
||||
SHTC3 = 7;
|
||||
|
||||
/*
|
||||
* High accuracy pressure
|
||||
*/
|
||||
LPS22 = 8;
|
||||
|
||||
/*
|
||||
* 3-Axis magnetic sensor
|
||||
*/
|
||||
QMC6310 = 9;
|
||||
|
||||
/*
|
||||
* 6-Axis inertial measurement sensor
|
||||
*/
|
||||
QMI8658 = 10;
|
||||
|
||||
/*
|
||||
* 3-Axis magnetic sensor
|
||||
*/
|
||||
QMC5883L = 11;
|
||||
|
||||
/*
|
||||
* High accuracy temperature and humidity
|
||||
*/
|
||||
SHT31 = 12;
|
||||
|
||||
/*
|
||||
* PM2.5 air quality sensor
|
||||
*/
|
||||
PMSA003I = 13;
|
||||
|
||||
/*
|
||||
* INA3221 3 Channel Voltage / Current Sensor
|
||||
*/
|
||||
INA3221 = 14;
|
||||
|
||||
/*
|
||||
* BMP085/BMP180 High accuracy temperature and pressure (older Version of BMP280)
|
||||
*/
|
||||
BMP085 = 15;
|
||||
|
||||
/*
|
||||
* RCWL-9620 Doppler Radar Distance Sensor, used for water level detection
|
||||
*/
|
||||
RCWL9620 = 16;
|
||||
|
||||
/*
|
||||
* Sensirion High accuracy temperature and humidity
|
||||
*/
|
||||
SHT4X = 17;
|
||||
|
||||
/*
|
||||
* VEML7700 high accuracy ambient light(Lux) digital 16-bit resolution sensor.
|
||||
*/
|
||||
VEML7700 = 18;
|
||||
|
||||
/*
|
||||
* MLX90632 non-contact IR temperature sensor.
|
||||
*/
|
||||
MLX90632 = 19;
|
||||
|
||||
/*
|
||||
* TI OPT3001 Ambient Light Sensor
|
||||
*/
|
||||
OPT3001 = 20;
|
||||
|
||||
/*
|
||||
* Lite On LTR-390UV-01 UV Light Sensor
|
||||
*/
|
||||
LTR390UV = 21;
|
||||
|
||||
/*
|
||||
* AMS TSL25911FN RGB Light Sensor
|
||||
*/
|
||||
TSL25911FN = 22;
|
||||
|
||||
/*
|
||||
* AHT10 Integrated temperature and humidity sensor
|
||||
*/
|
||||
AHT10 = 23;
|
||||
|
||||
/*
|
||||
* DFRobot Lark Weather station (temperature, humidity, pressure, wind speed and direction)
|
||||
*/
|
||||
DFROBOT_LARK = 24;
|
||||
|
||||
/*
|
||||
* NAU7802 Scale Chip or compatible
|
||||
*/
|
||||
NAU7802 = 25;
|
||||
|
||||
/*
|
||||
* BMP3XX High accuracy temperature and pressure
|
||||
*/
|
||||
BMP3XX = 26;
|
||||
|
||||
/*
|
||||
* ICM-20948 9-Axis digital motion processor
|
||||
*/
|
||||
ICM20948 = 27;
|
||||
|
||||
/*
|
||||
* MAX17048 1S lipo battery sensor (voltage, state of charge, time to go)
|
||||
*/
|
||||
MAX17048 = 28;
|
||||
|
||||
/*
|
||||
* Custom I2C sensor implementation based on https://github.com/meshtastic/i2c-sensor
|
||||
*/
|
||||
CUSTOM_SENSOR = 29;
|
||||
|
||||
/*
|
||||
* MAX30102 Pulse Oximeter and Heart-Rate Sensor
|
||||
*/
|
||||
MAX30102 = 30;
|
||||
|
||||
/*
|
||||
* MLX90614 non-contact IR temperature sensor
|
||||
*/
|
||||
MLX90614 = 31;
|
||||
|
||||
/*
|
||||
* SCD40/SCD41 CO2, humidity, temperature sensor
|
||||
*/
|
||||
SCD4X = 32;
|
||||
|
||||
/*
|
||||
* ClimateGuard RadSens, radiation, Geiger-Muller Tube
|
||||
*/
|
||||
RADSENS = 33;
|
||||
|
||||
/*
|
||||
* High accuracy current and voltage
|
||||
*/
|
||||
INA226 = 34;
|
||||
|
||||
/*
|
||||
* DFRobot Gravity tipping bucket rain gauge
|
||||
*/
|
||||
DFROBOT_RAIN = 35;
|
||||
|
||||
/*
|
||||
* Infineon DPS310 High accuracy pressure and temperature
|
||||
*/
|
||||
DPS310 = 36;
|
||||
|
||||
/*
|
||||
* RAKWireless RAK12035 Soil Moisture Sensor Module
|
||||
*/
|
||||
RAK12035 = 37;
|
||||
|
||||
/*
|
||||
* MAX17261 lipo battery gauge
|
||||
*/
|
||||
MAX17261 = 38;
|
||||
|
||||
/*
|
||||
* PCT2075 Temperature Sensor
|
||||
*/
|
||||
PCT2075 = 39;
|
||||
|
||||
/*
|
||||
* ADS1X15 ADC
|
||||
*/
|
||||
ADS1X15 = 40;
|
||||
|
||||
/*
|
||||
* ADS1X15 ADC_ALT
|
||||
*/
|
||||
ADS1X15_ALT = 41;
|
||||
|
||||
/*
|
||||
* Sensirion SFA30 Formaldehyde sensor
|
||||
*/
|
||||
SFA30 = 42;
|
||||
|
||||
/*
|
||||
* SEN5X PM SENSORS
|
||||
*/
|
||||
SEN5X = 43;
|
||||
/*
|
||||
* TSL2561 light sensor
|
||||
*/
|
||||
TSL2561 = 44;
|
||||
}
|
||||
|
||||
/*
|
||||
* NAU7802 Telemetry configuration, for saving to flash
|
||||
*/
|
||||
message Nau7802Config {
|
||||
/*
|
||||
* The offset setting for the NAU7802
|
||||
*/
|
||||
int32 zeroOffset = 1;
|
||||
|
||||
/*
|
||||
* The calibration factor for the NAU7802
|
||||
*/
|
||||
float calibrationFactor = 2;
|
||||
}
|
||||
6
packages/protobufs/meshtastic/xmodem.options
Normal file
6
packages/protobufs/meshtastic/xmodem.options
Normal file
@@ -0,0 +1,6 @@
|
||||
# options for nanopb
|
||||
# https://jpa.kapsi.fi/nanopb/docs/reference.html#proto-file-options
|
||||
|
||||
*XModem.buffer max_size:128
|
||||
*XModem.seq int_size:16
|
||||
*XModem.crc16 int_size:16
|
||||
27
packages/protobufs/meshtastic/xmodem.proto
Normal file
27
packages/protobufs/meshtastic/xmodem.proto
Normal file
@@ -0,0 +1,27 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package meshtastic;
|
||||
|
||||
option csharp_namespace = "Meshtastic.Protobufs";
|
||||
option go_package = "github.com/meshtastic/go/generated";
|
||||
option java_outer_classname = "XmodemProtos";
|
||||
option java_package = "com.geeksville.mesh";
|
||||
option swift_prefix = "";
|
||||
|
||||
message XModem {
|
||||
enum Control {
|
||||
NUL = 0;
|
||||
SOH = 1;
|
||||
STX = 2;
|
||||
EOT = 4;
|
||||
ACK = 6;
|
||||
NAK = 21;
|
||||
CAN = 24;
|
||||
CTRLZ = 26;
|
||||
}
|
||||
|
||||
Control control = 1;
|
||||
uint32 seq = 2;
|
||||
uint32 crc16 = 3;
|
||||
bytes buffer = 4;
|
||||
}
|
||||
185
packages/protobufs/nanopb.proto
Normal file
185
packages/protobufs/nanopb.proto
Normal file
@@ -0,0 +1,185 @@
|
||||
// Custom options for defining:
|
||||
// - Maximum size of string/bytes
|
||||
// - Maximum number of elements in array
|
||||
//
|
||||
// These are used by nanopb to generate statically allocable structures
|
||||
// for memory-limited environments.
|
||||
|
||||
syntax = "proto2";
|
||||
|
||||
import "google/protobuf/descriptor.proto";
|
||||
|
||||
option go_package = "github.com/meshtastic/go/generated";
|
||||
option java_package = "fi.kapsi.koti.jpa.nanopb";
|
||||
|
||||
enum FieldType {
|
||||
FT_DEFAULT = 0; // Automatically decide field type, generate static field if possible.
|
||||
FT_CALLBACK = 1; // Always generate a callback field.
|
||||
FT_POINTER = 4; // Always generate a dynamically allocated field.
|
||||
FT_STATIC = 2; // Generate a static field or raise an exception if not possible.
|
||||
FT_IGNORE = 3; // Ignore the field completely.
|
||||
FT_INLINE = 5; // Legacy option, use the separate 'fixed_length' option instead
|
||||
}
|
||||
|
||||
enum IntSize {
|
||||
IS_DEFAULT = 0; // Default, 32/64bit based on type in .proto
|
||||
IS_8 = 8;
|
||||
IS_16 = 16;
|
||||
IS_32 = 32;
|
||||
IS_64 = 64;
|
||||
}
|
||||
|
||||
enum TypenameMangling {
|
||||
M_NONE = 0; // Default, no typename mangling
|
||||
M_STRIP_PACKAGE = 1; // Strip current package name
|
||||
M_FLATTEN = 2; // Only use last path component
|
||||
M_PACKAGE_INITIALS = 3; // Replace the package name by the initials
|
||||
}
|
||||
|
||||
enum DescriptorSize {
|
||||
DS_AUTO = 0; // Select minimal size based on field type
|
||||
DS_1 = 1; // 1 word; up to 15 byte fields, no arrays
|
||||
DS_2 = 2; // 2 words; up to 4095 byte fields, 4095 entry arrays
|
||||
DS_4 = 4; // 4 words; up to 2^32-1 byte fields, 2^16-1 entry arrays
|
||||
DS_8 = 8; // 8 words; up to 2^32-1 entry arrays
|
||||
}
|
||||
|
||||
// This is the inner options message, which basically defines options for
|
||||
// a field. When it is used in message or file scope, it applies to all
|
||||
// fields.
|
||||
message NanoPBOptions {
|
||||
// Allocated size for 'bytes' and 'string' fields.
|
||||
// For string fields, this should include the space for null terminator.
|
||||
optional int32 max_size = 1;
|
||||
|
||||
// Maximum length for 'string' fields. Setting this is equivalent
|
||||
// to setting max_size to a value of length+1.
|
||||
optional int32 max_length = 14;
|
||||
|
||||
// Allocated number of entries in arrays ('repeated' fields)
|
||||
optional int32 max_count = 2;
|
||||
|
||||
// Size of integer fields. Can save some memory if you don't need
|
||||
// full 32 bits for the value.
|
||||
optional IntSize int_size = 7 [default = IS_DEFAULT];
|
||||
|
||||
// Force type of field (callback or static allocation)
|
||||
optional FieldType type = 3 [default = FT_DEFAULT];
|
||||
|
||||
// Use long names for enums, i.e. EnumName_EnumValue.
|
||||
optional bool long_names = 4 [default = true];
|
||||
|
||||
// Add 'packed' attribute to generated structs.
|
||||
// Note: this cannot be used on CPUs that break on unaligned
|
||||
// accesses to variables.
|
||||
optional bool packed_struct = 5 [default = false];
|
||||
|
||||
// Add 'packed' attribute to generated enums.
|
||||
optional bool packed_enum = 10 [default = false];
|
||||
|
||||
// Skip this message
|
||||
optional bool skip_message = 6 [default = false];
|
||||
|
||||
// Generate oneof fields as normal optional fields instead of union.
|
||||
optional bool no_unions = 8 [default = false];
|
||||
|
||||
// integer type tag for a message
|
||||
optional uint32 msgid = 9;
|
||||
|
||||
// decode oneof as anonymous union
|
||||
optional bool anonymous_oneof = 11 [default = false];
|
||||
|
||||
// Proto3 singular field does not generate a "has_" flag
|
||||
optional bool proto3 = 12 [default = false];
|
||||
|
||||
// Force proto3 messages to have no "has_" flag.
|
||||
// This was default behavior until nanopb-0.4.0.
|
||||
optional bool proto3_singular_msgs = 21 [default = false];
|
||||
|
||||
// Generate an enum->string mapping function (can take up lots of space).
|
||||
optional bool enum_to_string = 13 [default = false];
|
||||
|
||||
// Generate bytes arrays with fixed length
|
||||
optional bool fixed_length = 15 [default = false];
|
||||
|
||||
// Generate repeated field with fixed count
|
||||
optional bool fixed_count = 16 [default = false];
|
||||
|
||||
// Generate message-level callback that is called before decoding submessages.
|
||||
// This can be used to set callback fields for submsgs inside oneofs.
|
||||
optional bool submsg_callback = 22 [default = false];
|
||||
|
||||
// Shorten or remove package names from type names.
|
||||
// This option applies only on the file level.
|
||||
optional TypenameMangling mangle_names = 17 [default = M_NONE];
|
||||
|
||||
// Data type for storage associated with callback fields.
|
||||
optional string callback_datatype = 18 [default = "pb_callback_t"];
|
||||
|
||||
// Callback function used for encoding and decoding.
|
||||
// Prior to nanopb-0.4.0, the callback was specified in per-field pb_callback_t
|
||||
// structure. This is still supported, but does not work inside e.g. oneof or pointer
|
||||
// fields. Instead, a new method allows specifying a per-message callback that
|
||||
// will be called for all callback fields in a message type.
|
||||
optional string callback_function = 19 [default = "pb_default_field_callback"];
|
||||
|
||||
// Select the size of field descriptors. This option has to be defined
|
||||
// for the whole message, not per-field. Usually automatic selection is
|
||||
// ok, but if it results in compilation errors you can increase the field
|
||||
// size here.
|
||||
optional DescriptorSize descriptorsize = 20 [default = DS_AUTO];
|
||||
|
||||
// Set default value for has_ fields.
|
||||
optional bool default_has = 23 [default = false];
|
||||
|
||||
// Extra files to include in generated `.pb.h`
|
||||
repeated string include = 24;
|
||||
|
||||
// Automatic includes to exclude from generated `.pb.h`
|
||||
// Same as nanopb_generator.py command line flag -x.
|
||||
repeated string exclude = 26;
|
||||
|
||||
// Package name that applies only for nanopb.
|
||||
optional string package = 25;
|
||||
|
||||
// Override type of the field in generated C code. Only to be used with related field types
|
||||
optional google.protobuf.FieldDescriptorProto.Type type_override = 27;
|
||||
|
||||
// Due to historical reasons, nanopb orders fields in structs by their tag number
|
||||
// instead of the order in .proto. Set this to false to keep the .proto order.
|
||||
// The default value will probably change to false in nanopb-0.5.0.
|
||||
optional bool sort_by_tag = 28 [default = true];
|
||||
|
||||
// Set the FT_DEFAULT field conversion strategy.
|
||||
// A field that can become a static member of a c struct (e.g. int, bool, etc)
|
||||
// will be a a static field.
|
||||
// Fields with dynamic length are converted to either a pointer or a callback.
|
||||
optional FieldType fallback_type = 29 [default = FT_CALLBACK];
|
||||
}
|
||||
|
||||
// Extensions to protoc 'Descriptor' type in order to define options
|
||||
// inside a .proto file.
|
||||
//
|
||||
// Protocol Buffers extension number registry
|
||||
// --------------------------------
|
||||
// Project: Nanopb
|
||||
// Contact: Petteri Aimonen <jpa@kapsi.fi>
|
||||
// Web site: http://kapsi.fi/~jpa/nanopb
|
||||
// Extensions: 1010 (all types)
|
||||
// --------------------------------
|
||||
|
||||
extend google.protobuf.FileOptions {
|
||||
optional NanoPBOptions nanopb_fileopt = 1010;
|
||||
}
|
||||
|
||||
extend google.protobuf.MessageOptions {
|
||||
optional NanoPBOptions nanopb_msgopt = 1010;
|
||||
}
|
||||
|
||||
extend google.protobuf.EnumOptions {
|
||||
optional NanoPBOptions nanopb_enumopt = 1010;
|
||||
}
|
||||
|
||||
extend google.protobuf.FieldOptions {
|
||||
optional NanoPBOptions nanopb = 1010;
|
||||
}
|
||||
30
packages/protobufs/package.json
Normal file
30
packages/protobufs/package.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "@meshtastic/protobufs-ws",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"description": "Workspace package for Meshtastic protobuf stubs (local dev only). This package is published to the JSR registry separately.",
|
||||
"license": "GPL-3.0-only",
|
||||
"files": [
|
||||
"./packages/ts/dist/"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./packages/ts/dist/mod.d.ts",
|
||||
"default": "./packages/ts/dist/mod.js"
|
||||
},
|
||||
"./*": "./packages/ts/dist/*"
|
||||
},
|
||||
"types": "./packages/ts/dist/mod.d.ts",
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"gen": "buf generate",
|
||||
"clean": "rimraf dist",
|
||||
"build": "pnpm run clean && pnpm run gen"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@bufbuild/protoc-gen-es": "^1.9.0",
|
||||
"rimraf": "^6.0.0"
|
||||
}
|
||||
}
|
||||
16
packages/protobufs/packages/ts/README.md
Normal file
16
packages/protobufs/packages/ts/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# Meshtastic Protobuf Definitions
|
||||
|
||||
[](https://github.com/meshtastic/protobufs/actions/workflows/ci.yml)
|
||||
[](https://cla-assistant.io/meshtastic/protobufs)
|
||||
[](https://opencollective.com/meshtastic/)
|
||||
[](https://vercel.com?utm_source=meshtastic&utm_campaign=oss)
|
||||
|
||||
## Overview
|
||||
|
||||
The [Protobuf](https://developers.google.com/protocol-buffers) message definitions for the Meshtastic project (used by apps and the device firmware).
|
||||
|
||||
**[Documentation/API Reference](https://buf.build/meshtastic/protobufs)**
|
||||
|
||||
## Stats
|
||||
|
||||

|
||||
13
packages/protobufs/packages/ts/deno.json
Normal file
13
packages/protobufs/packages/ts/deno.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "@meshtastic/protobufs",
|
||||
"version": "__PACKAGE_VERSION__",
|
||||
"exports": {
|
||||
".": "./mod.ts"
|
||||
},
|
||||
"imports": {
|
||||
"@bufbuild/protobuf": "npm:@bufbuild/protobuf@^2.9.0"
|
||||
},
|
||||
"publish": {
|
||||
"exclude": ["!dist"]
|
||||
}
|
||||
}
|
||||
384
packages/protobufs/packages/ts/deno.lock
generated
Normal file
384
packages/protobufs/packages/ts/deno.lock
generated
Normal file
@@ -0,0 +1,384 @@
|
||||
{
|
||||
"version": "5",
|
||||
"specifiers": {
|
||||
"npm:@bufbuild/protobuf@^2.9.0": "2.9.0",
|
||||
"npm:tsdown@~0.15.6": "0.15.6_typescript@5.9.3_rolldown@1.0.0-beta.42",
|
||||
"npm:typescript@^5.9.3": "5.9.3"
|
||||
},
|
||||
"npm": {
|
||||
"@babel/generator@7.28.3": {
|
||||
"integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==",
|
||||
"dependencies": [
|
||||
"@babel/parser",
|
||||
"@babel/types",
|
||||
"@jridgewell/gen-mapping",
|
||||
"@jridgewell/trace-mapping",
|
||||
"jsesc"
|
||||
]
|
||||
},
|
||||
"@babel/helper-string-parser@7.27.1": {
|
||||
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="
|
||||
},
|
||||
"@babel/helper-validator-identifier@7.27.1": {
|
||||
"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="
|
||||
},
|
||||
"@babel/parser@7.28.4": {
|
||||
"integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==",
|
||||
"dependencies": [
|
||||
"@babel/types"
|
||||
],
|
||||
"bin": true
|
||||
},
|
||||
"@babel/types@7.28.4": {
|
||||
"integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==",
|
||||
"dependencies": [
|
||||
"@babel/helper-string-parser",
|
||||
"@babel/helper-validator-identifier"
|
||||
]
|
||||
},
|
||||
"@bufbuild/protobuf@2.9.0": {
|
||||
"integrity": "sha512-rnJenoStJ8nvmt9Gzye8nkYd6V22xUAnu4086ER7h1zJ508vStko4pMvDeQ446ilDTFpV5wnoc5YS7XvMwwMqA=="
|
||||
},
|
||||
"@emnapi/core@1.5.0": {
|
||||
"integrity": "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==",
|
||||
"dependencies": [
|
||||
"@emnapi/wasi-threads",
|
||||
"tslib"
|
||||
]
|
||||
},
|
||||
"@emnapi/runtime@1.5.0": {
|
||||
"integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==",
|
||||
"dependencies": [
|
||||
"tslib"
|
||||
]
|
||||
},
|
||||
"@emnapi/wasi-threads@1.1.0": {
|
||||
"integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==",
|
||||
"dependencies": [
|
||||
"tslib"
|
||||
]
|
||||
},
|
||||
"@jridgewell/gen-mapping@0.3.13": {
|
||||
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
||||
"dependencies": [
|
||||
"@jridgewell/sourcemap-codec",
|
||||
"@jridgewell/trace-mapping"
|
||||
]
|
||||
},
|
||||
"@jridgewell/resolve-uri@3.1.2": {
|
||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="
|
||||
},
|
||||
"@jridgewell/sourcemap-codec@1.5.5": {
|
||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="
|
||||
},
|
||||
"@jridgewell/trace-mapping@0.3.31": {
|
||||
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
||||
"dependencies": [
|
||||
"@jridgewell/resolve-uri",
|
||||
"@jridgewell/sourcemap-codec"
|
||||
]
|
||||
},
|
||||
"@napi-rs/wasm-runtime@1.0.7": {
|
||||
"integrity": "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==",
|
||||
"dependencies": [
|
||||
"@emnapi/core",
|
||||
"@emnapi/runtime",
|
||||
"@tybys/wasm-util"
|
||||
]
|
||||
},
|
||||
"@oxc-project/types@0.94.0": {
|
||||
"integrity": "sha512-+UgQT/4o59cZfH6Cp7G0hwmqEQ0wE+AdIwhikdwnhWI9Dp8CgSY081+Q3O67/wq3VJu8mgUEB93J9EHHn70fOw=="
|
||||
},
|
||||
"@quansync/fs@0.1.5": {
|
||||
"integrity": "sha512-lNS9hL2aS2NZgNW7BBj+6EBl4rOf8l+tQ0eRY6JWCI8jI2kc53gSoqbjojU0OnAWhzoXiOjFyGsHcDGePB3lhA==",
|
||||
"dependencies": [
|
||||
"quansync"
|
||||
]
|
||||
},
|
||||
"@rolldown/binding-android-arm64@1.0.0-beta.42": {
|
||||
"integrity": "sha512-W5ZKF3TP3bOWuBfotAGp+UGjxOkGV7jRmIRbBA7NFjggx7Oi6vOmGDqpHEIX7kDCiry1cnIsWQaxNvWbMdkvzQ==",
|
||||
"os": ["android"],
|
||||
"cpu": ["arm64"]
|
||||
},
|
||||
"@rolldown/binding-darwin-arm64@1.0.0-beta.42": {
|
||||
"integrity": "sha512-abw/wtgJA8OCgaTlL+xJxnN/Z01BwV1rfzIp5Hh9x+IIO6xOBfPsQ0nzi0+rWx3TyZ9FZXyC7bbC+5NpQ9EaXQ==",
|
||||
"os": ["darwin"],
|
||||
"cpu": ["arm64"]
|
||||
},
|
||||
"@rolldown/binding-darwin-x64@1.0.0-beta.42": {
|
||||
"integrity": "sha512-Y/UrZIRVr8CvXVEB88t6PeC46r1K9/QdPEo2ASE/b/KBEyXIx+QbM6kv9QfQVWU2Atly2+SVsQzxQsIvuk3lZQ==",
|
||||
"os": ["darwin"],
|
||||
"cpu": ["x64"]
|
||||
},
|
||||
"@rolldown/binding-freebsd-x64@1.0.0-beta.42": {
|
||||
"integrity": "sha512-zRM0oOk7BZiy6DoWBvdV4hyEg+j6+WcBZIMHVirMEZRu8hd18kZdJkg+bjVMfCEhwpWeFUfBfZ1qcaZ5UdYzlQ==",
|
||||
"os": ["freebsd"],
|
||||
"cpu": ["x64"]
|
||||
},
|
||||
"@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.42": {
|
||||
"integrity": "sha512-6RjFaC52QNwo7ilU8C5H7swbGlgfTkG9pudXwzr3VYyT18s0C9gLg3mvc7OMPIGqNxnQ0M5lU8j6aQCk2DTRVg==",
|
||||
"os": ["linux"],
|
||||
"cpu": ["arm"]
|
||||
},
|
||||
"@rolldown/binding-linux-arm64-gnu@1.0.0-beta.42": {
|
||||
"integrity": "sha512-LMYHM5Sf6ROq+VUwHMDVX2IAuEsWTv4SnlFEedBnMGpvRuQ14lCmD4m5Q8sjyAQCgyha9oghdGoK8AEg1sXZKg==",
|
||||
"os": ["linux"],
|
||||
"cpu": ["arm64"]
|
||||
},
|
||||
"@rolldown/binding-linux-arm64-musl@1.0.0-beta.42": {
|
||||
"integrity": "sha512-/bNTYb9aKNhzdbPn3O4MK2aLv55AlrkUKPE4KNfBYjkoZUfDr4jWp7gsSlvTc5A/99V1RCm9axvt616ZzeXGyA==",
|
||||
"os": ["linux"],
|
||||
"cpu": ["arm64"]
|
||||
},
|
||||
"@rolldown/binding-linux-x64-gnu@1.0.0-beta.42": {
|
||||
"integrity": "sha512-n/SLa4h342oyeGykZdch7Y3GNCNliRPL4k5wkeZ/5eQZs+c6/ZG1SHCJQoy7bZcmxiMyaXs9HoFmv1PEKrZgWg==",
|
||||
"os": ["linux"],
|
||||
"cpu": ["x64"]
|
||||
},
|
||||
"@rolldown/binding-linux-x64-musl@1.0.0-beta.42": {
|
||||
"integrity": "sha512-4PSd46sFzqpLHSGdaSViAb1mk55sCUMpJg+X8ittXaVocQsV3QLG/uydSH8RyL0ngHX5fy3D70LcCzlB15AgHw==",
|
||||
"os": ["linux"],
|
||||
"cpu": ["x64"]
|
||||
},
|
||||
"@rolldown/binding-openharmony-arm64@1.0.0-beta.42": {
|
||||
"integrity": "sha512-BmWoeJJyeZXmZBcfoxG6J9+rl2G7eO47qdTkAzEegj4n3aC6CBIHOuDcbE8BvhZaEjQR0nh0nJrtEDlt65Q7Sw==",
|
||||
"os": ["openharmony"],
|
||||
"cpu": ["arm64"]
|
||||
},
|
||||
"@rolldown/binding-wasm32-wasi@1.0.0-beta.42": {
|
||||
"integrity": "sha512-2Ft32F7uiDTrGZUKws6CLNTlvTWHC33l4vpXrzUucf9rYtUThAdPCOt89Pmn13tNX6AulxjGEP2R0nZjTSW3eQ==",
|
||||
"dependencies": [
|
||||
"@napi-rs/wasm-runtime"
|
||||
],
|
||||
"cpu": ["wasm32"]
|
||||
},
|
||||
"@rolldown/binding-win32-arm64-msvc@1.0.0-beta.42": {
|
||||
"integrity": "sha512-hC1kShXW/z221eG+WzQMN06KepvPbMBknF0iGR3VMYJLOe9gwnSTfGxFT5hf8XrPv7CEZqTWRd0GQpkSHRbGsw==",
|
||||
"os": ["win32"],
|
||||
"cpu": ["arm64"]
|
||||
},
|
||||
"@rolldown/binding-win32-ia32-msvc@1.0.0-beta.42": {
|
||||
"integrity": "sha512-AICBYromawouGjj+GS33369E8Vwhy6UwhQEhQ5evfS8jPCsyVvoICJatbDGDGH01dwtVGLD5eDFzPicUOVpe4g==",
|
||||
"os": ["win32"],
|
||||
"cpu": ["ia32"]
|
||||
},
|
||||
"@rolldown/binding-win32-x64-msvc@1.0.0-beta.42": {
|
||||
"integrity": "sha512-XpZ0M+tjoEiSc9c+uZR7FCnOI0uxDRNs1elGOMjeB0pUP1QmvVbZGYNsyLbLoP4u7e3VQN8rie1OQ8/mB6rcJg==",
|
||||
"os": ["win32"],
|
||||
"cpu": ["x64"]
|
||||
},
|
||||
"@rolldown/pluginutils@1.0.0-beta.42": {
|
||||
"integrity": "sha512-N7pQzk9CyE7q0bBN/q0J8s6Db279r5kUZc6d7/wWRe9/zXqC52HQovVyu6iXPIDY4BEzzgbVLhVFXrOuGJ22ZQ=="
|
||||
},
|
||||
"@tybys/wasm-util@0.10.1": {
|
||||
"integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
|
||||
"dependencies": [
|
||||
"tslib"
|
||||
]
|
||||
},
|
||||
"ansis@4.2.0": {
|
||||
"integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig=="
|
||||
},
|
||||
"ast-kit@2.1.3": {
|
||||
"integrity": "sha512-TH+b3Lv6pUjy/Nu0m6A2JULtdzLpmqF9x1Dhj00ZoEiML8qvVA9j1flkzTKNYgdEhWrjDwtWNpyyCUbfQe514g==",
|
||||
"dependencies": [
|
||||
"@babel/parser",
|
||||
"pathe"
|
||||
]
|
||||
},
|
||||
"birpc@2.6.1": {
|
||||
"integrity": "sha512-LPnFhlDpdSH6FJhJyn4M0kFO7vtQ5iPw24FnG0y21q09xC7e8+1LeR31S1MAIrDAHp4m7aas4bEkTDTvMAtebQ=="
|
||||
},
|
||||
"cac@6.7.14": {
|
||||
"integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="
|
||||
},
|
||||
"chokidar@4.0.3": {
|
||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||
"dependencies": [
|
||||
"readdirp"
|
||||
]
|
||||
},
|
||||
"debug@4.4.3": {
|
||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||
"dependencies": [
|
||||
"ms"
|
||||
]
|
||||
},
|
||||
"defu@6.1.4": {
|
||||
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="
|
||||
},
|
||||
"diff@8.0.2": {
|
||||
"integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg=="
|
||||
},
|
||||
"dts-resolver@2.1.2": {
|
||||
"integrity": "sha512-xeXHBQkn2ISSXxbJWD828PFjtyg+/UrMDo7W4Ffcs7+YWCquxU8YjV1KoxuiL+eJ5pg3ll+bC6flVv61L3LKZg=="
|
||||
},
|
||||
"empathic@2.0.0": {
|
||||
"integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA=="
|
||||
},
|
||||
"fdir@6.5.0_picomatch@4.0.3": {
|
||||
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
||||
"dependencies": [
|
||||
"picomatch"
|
||||
],
|
||||
"optionalPeers": [
|
||||
"picomatch"
|
||||
]
|
||||
},
|
||||
"get-tsconfig@4.12.0": {
|
||||
"integrity": "sha512-LScr2aNr2FbjAjZh2C6X6BxRx1/x+aTDExct/xyq2XKbYOiG5c0aK7pMsSuyc0brz3ibr/lbQiHD9jzt4lccJw==",
|
||||
"dependencies": [
|
||||
"resolve-pkg-maps"
|
||||
]
|
||||
},
|
||||
"hookable@5.5.3": {
|
||||
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="
|
||||
},
|
||||
"jiti@2.6.1": {
|
||||
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
|
||||
"bin": true
|
||||
},
|
||||
"jsesc@3.1.0": {
|
||||
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
|
||||
"bin": true
|
||||
},
|
||||
"magic-string@0.30.19": {
|
||||
"integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==",
|
||||
"dependencies": [
|
||||
"@jridgewell/sourcemap-codec"
|
||||
]
|
||||
},
|
||||
"ms@2.1.3": {
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||
},
|
||||
"pathe@2.0.3": {
|
||||
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="
|
||||
},
|
||||
"picomatch@4.0.3": {
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="
|
||||
},
|
||||
"quansync@0.2.11": {
|
||||
"integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="
|
||||
},
|
||||
"readdirp@4.1.2": {
|
||||
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="
|
||||
},
|
||||
"resolve-pkg-maps@1.0.0": {
|
||||
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="
|
||||
},
|
||||
"rolldown-plugin-dts@0.16.11_rolldown@1.0.0-beta.42_typescript@5.9.3": {
|
||||
"integrity": "sha512-9IQDaPvPqTx3RjG2eQCK5GYZITo203BxKunGI80AGYicu1ySFTUyugicAaTZWRzFWh9DSnzkgNeMNbDWBbSs0w==",
|
||||
"dependencies": [
|
||||
"@babel/generator",
|
||||
"@babel/parser",
|
||||
"@babel/types",
|
||||
"ast-kit",
|
||||
"birpc",
|
||||
"debug",
|
||||
"dts-resolver",
|
||||
"get-tsconfig",
|
||||
"magic-string",
|
||||
"rolldown",
|
||||
"typescript"
|
||||
],
|
||||
"optionalPeers": [
|
||||
"typescript"
|
||||
]
|
||||
},
|
||||
"rolldown@1.0.0-beta.42": {
|
||||
"integrity": "sha512-xaPcckj+BbJhYLsv8gOqezc8EdMcKKe/gk8v47B0KPvgABDrQ0qmNPAiT/gh9n9Foe0bUkEv2qzj42uU5q1WRg==",
|
||||
"dependencies": [
|
||||
"@oxc-project/types",
|
||||
"@rolldown/pluginutils",
|
||||
"ansis"
|
||||
],
|
||||
"optionalDependencies": [
|
||||
"@rolldown/binding-android-arm64",
|
||||
"@rolldown/binding-darwin-arm64",
|
||||
"@rolldown/binding-darwin-x64",
|
||||
"@rolldown/binding-freebsd-x64",
|
||||
"@rolldown/binding-linux-arm-gnueabihf",
|
||||
"@rolldown/binding-linux-arm64-gnu",
|
||||
"@rolldown/binding-linux-arm64-musl",
|
||||
"@rolldown/binding-linux-x64-gnu",
|
||||
"@rolldown/binding-linux-x64-musl",
|
||||
"@rolldown/binding-openharmony-arm64",
|
||||
"@rolldown/binding-wasm32-wasi",
|
||||
"@rolldown/binding-win32-arm64-msvc",
|
||||
"@rolldown/binding-win32-ia32-msvc",
|
||||
"@rolldown/binding-win32-x64-msvc"
|
||||
],
|
||||
"bin": true
|
||||
},
|
||||
"semver@7.7.3": {
|
||||
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
|
||||
"bin": true
|
||||
},
|
||||
"tinyexec@1.0.1": {
|
||||
"integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw=="
|
||||
},
|
||||
"tinyglobby@0.2.15_picomatch@4.0.3": {
|
||||
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
|
||||
"dependencies": [
|
||||
"fdir",
|
||||
"picomatch"
|
||||
]
|
||||
},
|
||||
"tree-kill@1.2.2": {
|
||||
"integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
|
||||
"bin": true
|
||||
},
|
||||
"tsdown@0.15.6_typescript@5.9.3_rolldown@1.0.0-beta.42": {
|
||||
"integrity": "sha512-W6++O3JeV9gm3JY6P/vLiC7zzTcJbZhQxXb+p3AvRMpDOPBIg82yXULyZCcwjsihY/bFG+Qw37HkezZbP7fzUg==",
|
||||
"dependencies": [
|
||||
"ansis",
|
||||
"cac",
|
||||
"chokidar",
|
||||
"debug",
|
||||
"diff",
|
||||
"empathic",
|
||||
"hookable",
|
||||
"rolldown",
|
||||
"rolldown-plugin-dts",
|
||||
"semver",
|
||||
"tinyexec",
|
||||
"tinyglobby",
|
||||
"tree-kill",
|
||||
"typescript",
|
||||
"unconfig"
|
||||
],
|
||||
"optionalPeers": [
|
||||
"typescript"
|
||||
],
|
||||
"bin": true
|
||||
},
|
||||
"tslib@2.8.1": {
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
|
||||
},
|
||||
"typescript@5.9.3": {
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"bin": true
|
||||
},
|
||||
"unconfig@7.3.3": {
|
||||
"integrity": "sha512-QCkQoOnJF8L107gxfHL0uavn7WD9b3dpBcFX6HtfQYmjw2YzWxGuFQ0N0J6tE9oguCBJn9KOvfqYDCMPHIZrBA==",
|
||||
"dependencies": [
|
||||
"@quansync/fs",
|
||||
"defu",
|
||||
"jiti",
|
||||
"quansync"
|
||||
]
|
||||
}
|
||||
},
|
||||
"workspace": {
|
||||
"dependencies": [
|
||||
"npm:@bufbuild/protobuf@^2.9.0"
|
||||
],
|
||||
"packageJson": {
|
||||
"dependencies": [
|
||||
"npm:@bufbuild/protobuf@^2.9.0",
|
||||
"npm:tsdown@~0.15.6",
|
||||
"npm:typescript@^5.9.3"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
20
packages/protobufs/packages/ts/mod.ts
Normal file
20
packages/protobufs/packages/ts/mod.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
export * as Admin from "./dist/admin_pb.ts";
|
||||
export * as AppOnly from "./dist/apponly_pb.ts";
|
||||
export * as ATAK from "./dist/atak_pb.ts";
|
||||
export * as CannedMessages from "./dist/cannedmessages_pb.ts";
|
||||
export * as Channel from "./dist/channel_pb.ts";
|
||||
export * as ClientOnly from "./dist/clientonly_pb.ts";
|
||||
export * as Config from "./dist/config_pb.ts";
|
||||
export * as ConnectionStatus from "./dist/connection_status_pb.ts";
|
||||
export * as LocalOnly from "./dist/localonly_pb.ts";
|
||||
export * as Mesh from "./dist/mesh_pb.ts";
|
||||
export * as ModuleConfig from "./dist/module_config_pb.ts";
|
||||
export * as Mqtt from "./dist/mqtt_pb.ts";
|
||||
export * as PaxCount from "./dist/paxcount_pb.ts";
|
||||
export * as Portnums from "./dist/portnums_pb.ts";
|
||||
export * as PowerMon from "./dist/powermon_pb.ts";
|
||||
export * as RemoteHardware from "./dist/remote_hardware_pb.ts";
|
||||
export * as Rtttl from "./dist/rtttl_pb.ts";
|
||||
export * as StoreForward from "./dist/storeforward_pb.ts";
|
||||
export * as Telemetry from "./dist/telemetry_pb.ts";
|
||||
export * as Xmodem from "./dist/xmodem_pb.ts";
|
||||
@@ -1,15 +1,29 @@
|
||||
{
|
||||
"name": "@meshtastic/transport-deno",
|
||||
"version": "0.1.1",
|
||||
"description": "Deno-specific transport layer for Meshtastic web applications.",
|
||||
"exports": {
|
||||
".": "./mod.ts"
|
||||
},
|
||||
"main": "./dist/mod.mjs",
|
||||
"module": "./dist/mod.mjs",
|
||||
"types": "./dist/mod.d.mts",
|
||||
"files": ["dist/*", "mod.ts", "README.md", "../../LICENSE"],
|
||||
"name": "@meshtastic/transport-deno",
|
||||
"version": "0.1.1",
|
||||
"description": "Deno-specific transport layer for Meshtastic web applications.",
|
||||
"exports": {
|
||||
".": "./mod.ts"
|
||||
},
|
||||
"main": "./dist/mod.js",
|
||||
"module": "./dist/mod.js",
|
||||
"types": "./dist/mod.d.ts",
|
||||
"license": "GPL-3.0-only",
|
||||
"tsdown": {
|
||||
"entry": "mod.ts",
|
||||
"dts": true,
|
||||
"format": [
|
||||
"esm"
|
||||
],
|
||||
"splitting": false,
|
||||
"clean": true
|
||||
},
|
||||
"files": [
|
||||
"package.json",
|
||||
"README.md",
|
||||
"LICENSE",
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"prepack": "cp ../../LICENSE ./LICENSE",
|
||||
@@ -18,5 +32,8 @@
|
||||
"publish:npm": "pnpm clean && pnpm build:npm && pnpm publish --access public",
|
||||
"prepare:jsr": "rm -rf dist && pnpm dlx pkg-to-jsr",
|
||||
"publish:jsr": "pnpm run prepack && pnpm prepare:jsr && deno publish --allow-dirty --no-check"
|
||||
},
|
||||
"dependencies": {
|
||||
"@meshtastic/core": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,8 @@ await build({
|
||||
// package.json properties
|
||||
name: "@meshtastic/transport-deno",
|
||||
version: Deno.args[0],
|
||||
description: "A Deno transport layer for your project, enabling seamless integration with npm.",
|
||||
description:
|
||||
"A Deno transport layer for your project, enabling seamless integration with npm.",
|
||||
license: "GPL-3.0-only",
|
||||
repository: {
|
||||
type: "git",
|
||||
|
||||
@@ -16,9 +16,10 @@ export class TransportDeno implements Types.Transport {
|
||||
|
||||
constructor(connection: Deno.Conn) {
|
||||
this.connection = connection;
|
||||
Utils.toDeviceStream.readable.pipeTo(this.connection.writable);
|
||||
const toDeviceStream = Utils.toDeviceStream();
|
||||
toDeviceStream.readable.pipeTo(this.connection.writable);
|
||||
|
||||
this._toDevice = Utils.toDeviceStream.writable;
|
||||
this._toDevice = toDeviceStream.writable;
|
||||
this._fromDevice = this.connection.readable.pipeThrough(
|
||||
Utils.fromDeviceStream(),
|
||||
);
|
||||
|
||||
@@ -1,32 +1,50 @@
|
||||
{
|
||||
"name": "@meshtastic/transport-http",
|
||||
"version": "0.2.3",
|
||||
"description": "A transport layer for Meshtastic applications using HTTP.",
|
||||
"exports": {".": "./mod.ts"},
|
||||
"main": "./dist/mod.mjs",
|
||||
"module": "./dist/mod.mjs",
|
||||
"types": "./dist/mod.d.mts",
|
||||
"license": "GPL-3.0-only",
|
||||
"tsdown": {
|
||||
"entry": ["mod.ts"],
|
||||
"dts": true,
|
||||
"format": ["esm"],
|
||||
"splitting": false,
|
||||
"clean": true
|
||||
"name": "@meshtastic/transport-http",
|
||||
"version": "0.2.5",
|
||||
"description": "A transport layer for Meshtastic applications using HTTP.",
|
||||
"exports": {
|
||||
".": "./mod.ts"
|
||||
},
|
||||
"type": "module",
|
||||
"files": [
|
||||
"package.json",
|
||||
"README.md",
|
||||
"LICENSE",
|
||||
"dist"
|
||||
],
|
||||
"main": "./dist/mod.js",
|
||||
"module": "./dist/mod.js",
|
||||
"types": "./dist/mod.d.ts",
|
||||
"license": "GPL-3.0-only",
|
||||
"tsdown": {
|
||||
"entry": "mod.ts",
|
||||
"dts": true,
|
||||
"format": [
|
||||
"esm"
|
||||
],
|
||||
"splitting": false,
|
||||
"clean": true
|
||||
},
|
||||
"jsrInclude": [
|
||||
"mod.ts",
|
||||
"src",
|
||||
"README.md",
|
||||
"LICENSE"
|
||||
],
|
||||
"jsrExclude": [
|
||||
"src/**/*.test.ts"
|
||||
],
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"prepack": "cp ../../LICENSE ./LICENSE",
|
||||
"clean": "rm -rf dist LICENSE",
|
||||
"build:npm": "tsdown",
|
||||
"publish:npm": "pnpm clean && pnpm build:npm && pnpm publish --access public",
|
||||
|
||||
"publish:npm": "pnpm clean && pnpm build:npm && pnpm publish --access public --no-git-checks",
|
||||
"prepare:jsr": "rm -rf dist && pnpm dlx pkg-to-jsr",
|
||||
"publish:jsr": "pnpm run prepack && pnpm prepare:jsr && deno publish --allow-dirty --no-check"
|
||||
},
|
||||
"dependencies": {
|
||||
"@meshtastic/core": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
247
packages/transport-http/src/transport.test.ts
Normal file
247
packages/transport-http/src/transport.test.ts
Normal file
@@ -0,0 +1,247 @@
|
||||
import {
|
||||
afterEach,
|
||||
beforeEach,
|
||||
describe,
|
||||
expect,
|
||||
it,
|
||||
type MockInstance,
|
||||
vi,
|
||||
} from "vitest";
|
||||
import { runTransportContract } from "../../../tests/utils/transportContract.ts";
|
||||
import { TransportHTTP } from "./transport.ts";
|
||||
|
||||
let abortTimeoutSpy: MockInstance | undefined;
|
||||
beforeEach(() => {
|
||||
abortTimeoutSpy = vi
|
||||
.spyOn(
|
||||
globalThis.AbortSignal as unknown as { timeout(ms: number): AbortSignal },
|
||||
"timeout",
|
||||
)
|
||||
.mockImplementation((ms: number) => {
|
||||
const ctrl = new AbortController();
|
||||
const abort = () =>
|
||||
ctrl.abort(new DOMException("Timeout reached", "TimeoutError"));
|
||||
// Uses setTimeout so vi.useFakeTimers() can fast-forward it
|
||||
setTimeout(abort, ms);
|
||||
return ctrl.signal;
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
abortTimeoutSpy?.mockRestore();
|
||||
});
|
||||
|
||||
function stubFetch() {
|
||||
const inbox: Uint8Array[] = [];
|
||||
let lastWritten: ArrayBuffer | undefined;
|
||||
|
||||
let forceNextReadToHang = false;
|
||||
let forceNextReadToReturn500 = false;
|
||||
|
||||
function makeAbortAwareHang(signal?: AbortSignal): Promise<Response> {
|
||||
return new Promise((_, reject) => {
|
||||
const abort = () => reject(new DOMException("Aborted", "AbortError"));
|
||||
if (signal?.aborted) {
|
||||
abort();
|
||||
return;
|
||||
}
|
||||
if (signal) {
|
||||
signal.addEventListener("abort", abort, { once: true });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const mockFetch = vi.fn(async (url: string, init?: RequestInit) => {
|
||||
const method = (init?.method ?? "GET").toUpperCase();
|
||||
|
||||
if (url.includes("/api/v1/toradio") && method === "OPTIONS") {
|
||||
return { ok: true, status: 204 } as Response;
|
||||
}
|
||||
|
||||
if (url.includes("/api/v1/toradio") && method === "PUT") {
|
||||
lastWritten = init?.body as ArrayBuffer;
|
||||
return { ok: true, status: 200 } as Response;
|
||||
}
|
||||
|
||||
if (url.includes("/api/v1/fromradio") && method === "GET") {
|
||||
if (forceNextReadToHang) {
|
||||
forceNextReadToHang = false;
|
||||
return makeAbortAwareHang(init?.signal ?? undefined);
|
||||
}
|
||||
|
||||
if (forceNextReadToReturn500) {
|
||||
forceNextReadToReturn500 = false;
|
||||
return {
|
||||
ok: false,
|
||||
status: 500,
|
||||
arrayBuffer: async () => new ArrayBuffer(0),
|
||||
} as Response;
|
||||
}
|
||||
|
||||
const next = inbox.shift() ?? new Uint8Array();
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
arrayBuffer: async () => next.buffer,
|
||||
} as Response;
|
||||
}
|
||||
|
||||
return { ok: true, status: 200 } as Response;
|
||||
});
|
||||
|
||||
vi.stubGlobal("fetch", mockFetch);
|
||||
|
||||
return {
|
||||
pushIncoming: (u8: Uint8Array) => inbox.push(u8),
|
||||
assertLastWritten: (u8: Uint8Array) => {
|
||||
const got = new Uint8Array(lastWritten || new ArrayBuffer(0));
|
||||
expect(got).toEqual(u8);
|
||||
},
|
||||
forceReadErrorOnce: () => {
|
||||
forceNextReadToReturn500 = true;
|
||||
},
|
||||
forceReadTimeoutOnce: () => {
|
||||
forceNextReadToHang = true;
|
||||
},
|
||||
getMock: () => mockFetch,
|
||||
cleanup: () => vi.unstubAllGlobals(),
|
||||
};
|
||||
}
|
||||
|
||||
async function tickNextTimer() {
|
||||
try {
|
||||
await vi.advanceTimersToNextTimerAsync();
|
||||
} catch {
|
||||
await new Promise((r) => setTimeout(r, 5));
|
||||
}
|
||||
}
|
||||
|
||||
describe("TransportHTTP (contract)", () => {
|
||||
runTransportContract({
|
||||
name: "TransportHTTP",
|
||||
setup: () => {
|
||||
vi.useFakeTimers();
|
||||
},
|
||||
teardown: () => {
|
||||
vi.useRealTimers();
|
||||
vi.restoreAllMocks();
|
||||
vi.unstubAllGlobals();
|
||||
},
|
||||
create: async () => {
|
||||
(
|
||||
globalThis as unknown as { __http: ReturnType<typeof stubFetch> }
|
||||
).__http = stubFetch();
|
||||
const transport = await TransportHTTP.create("127.0.0.1:80", false);
|
||||
await tickNextTimer();
|
||||
return transport;
|
||||
},
|
||||
pushIncoming: async (bytes) => {
|
||||
(
|
||||
globalThis as unknown as { __http: ReturnType<typeof stubFetch> }
|
||||
).__http.pushIncoming(bytes);
|
||||
await tickNextTimer();
|
||||
},
|
||||
assertLastWritten: (bytes) => {
|
||||
(
|
||||
globalThis as unknown as { __http: ReturnType<typeof stubFetch> }
|
||||
).__http.assertLastWritten(bytes);
|
||||
},
|
||||
triggerDisconnect: async () => {
|
||||
(
|
||||
globalThis as unknown as { __http: ReturnType<typeof stubFetch> }
|
||||
).__http.forceReadErrorOnce();
|
||||
await tickNextTimer();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe("TransportHTTP (extras)", () => {
|
||||
let httpStub: ReturnType<typeof stubFetch> | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
vi.restoreAllMocks();
|
||||
httpStub?.cleanup();
|
||||
httpStub = undefined;
|
||||
});
|
||||
|
||||
async function createTransport(): Promise<TransportHTTP> {
|
||||
httpStub = stubFetch();
|
||||
const transport = await TransportHTTP.create("127.0.0.1:80", false);
|
||||
await tickNextTimer();
|
||||
return transport;
|
||||
}
|
||||
|
||||
async function advanceOnePoll() {
|
||||
await tickNextTimer();
|
||||
}
|
||||
|
||||
it("emits DeviceDisconnected with reason 'read-timeout' when GET /fromradio hangs", async () => {
|
||||
const transport = await createTransport();
|
||||
const reader = transport.fromDevice.getReader();
|
||||
|
||||
httpStub!.forceReadTimeoutOnce();
|
||||
|
||||
await tickNextTimer();
|
||||
await vi.advanceTimersByTimeAsync(8000);
|
||||
|
||||
let sawReadTimeout = false;
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const { value } = await reader.read();
|
||||
if (value?.type === "status" && value.data.reason === "read-timeout") {
|
||||
sawReadTimeout = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
expect(sawReadTimeout).toBe(true);
|
||||
|
||||
reader.releaseLock();
|
||||
await transport.disconnect();
|
||||
});
|
||||
|
||||
it("stops polling after disconnect()", async () => {
|
||||
const transport = await createTransport();
|
||||
|
||||
const fetchMock = httpStub!.getMock();
|
||||
const callsBeforeDisconnect = fetchMock.mock.calls.length;
|
||||
|
||||
await transport.disconnect();
|
||||
|
||||
await advanceOnePoll();
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
|
||||
const callsAfterDisconnect = fetchMock.mock.calls.length;
|
||||
expect(callsAfterDisconnect).toBe(callsBeforeDisconnect);
|
||||
});
|
||||
|
||||
it("emits DeviceDisconnected with reason 'read-timeout' when GET /fromradio hangs", async () => {
|
||||
const transport = await createTransport();
|
||||
const reader = transport.fromDevice.getReader();
|
||||
|
||||
httpStub!.forceReadTimeoutOnce();
|
||||
|
||||
await vi.advanceTimersToNextTimerAsync();
|
||||
|
||||
await vi.advanceTimersByTimeAsync(8000);
|
||||
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
|
||||
let sawReadTimeout = false;
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const { value } = await reader.read();
|
||||
if (value?.type === "status" && value.data.reason === "read-timeout") {
|
||||
sawReadTimeout = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
expect(sawReadTimeout).toBe(true);
|
||||
|
||||
reader.releaseLock();
|
||||
await transport.disconnect();
|
||||
});
|
||||
});
|
||||
@@ -1,14 +1,49 @@
|
||||
import type { Types } from "@meshtastic/core";
|
||||
import { Types } from "@meshtastic/core";
|
||||
|
||||
const FETCH_INTERVAL_MS = 3000;
|
||||
const READ_TIMEOUT_MS = 7000;
|
||||
const WRITE_TIMEOUT_MS = 4000;
|
||||
|
||||
function toArrayBuffer(uint8array: Uint8Array): ArrayBuffer {
|
||||
if (
|
||||
uint8array.buffer instanceof ArrayBuffer &&
|
||||
uint8array.byteOffset === 0 &&
|
||||
uint8array.byteLength === uint8array.buffer.byteLength
|
||||
) {
|
||||
return uint8array.buffer;
|
||||
}
|
||||
return uint8array.slice().buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides HTTP(S) transport for Meshtastic devices.
|
||||
*
|
||||
* Implements {@link Types.Transport} using the device's HTTP API.
|
||||
* Polls `/api/v1/fromradio` for incoming packets and writes to `/api/v1/toradio`.
|
||||
*/
|
||||
export class TransportHTTP implements Types.Transport {
|
||||
private _toDevice: WritableStream<Uint8Array>;
|
||||
private _fromDevice: ReadableStream<Types.DeviceOutput>;
|
||||
private fromDeviceController?: ReadableStreamDefaultController<Types.DeviceOutput>;
|
||||
|
||||
private url: string;
|
||||
private receiveBatchRequests: boolean;
|
||||
private fetchInterval: number;
|
||||
private fetching: boolean;
|
||||
private interval: ReturnType<typeof setInterval> | undefined;
|
||||
|
||||
private inflightReadController?: AbortController;
|
||||
|
||||
private lastStatus: Types.DeviceStatusEnum =
|
||||
Types.DeviceStatusEnum.DeviceDisconnected;
|
||||
private closingByUser = false;
|
||||
|
||||
/**
|
||||
* Probe the device and return a connected HTTP transport.
|
||||
*
|
||||
* @param address Hostname or IP address (with optional port).
|
||||
* @param tls Use HTTPS if true, HTTP otherwise.
|
||||
*/
|
||||
public static async create(
|
||||
address: string,
|
||||
tls?: boolean,
|
||||
@@ -17,97 +52,194 @@ export class TransportHTTP implements Types.Transport {
|
||||
await fetch(`${connectionUrl}/api/v1/toradio`, {
|
||||
method: "OPTIONS",
|
||||
});
|
||||
await Promise.resolve();
|
||||
return new TransportHTTP(connectionUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new HTTP transport for the given device URL.
|
||||
*
|
||||
* @param url Base URL of the device (`http://host:port` or `https://host:port`).
|
||||
*/
|
||||
constructor(url: string) {
|
||||
this.url = url;
|
||||
this.receiveBatchRequests = false;
|
||||
this.fetchInterval = 3000;
|
||||
this.fetchInterval = FETCH_INTERVAL_MS;
|
||||
this.fetching = false;
|
||||
|
||||
this._toDevice = new WritableStream<Uint8Array>({
|
||||
write: async (chunk) => {
|
||||
await this.writeToRadio(chunk);
|
||||
try {
|
||||
await this.writeToRadio(chunk);
|
||||
} catch (error) {
|
||||
if (!this.closingByUser) {
|
||||
this.emitStatus(
|
||||
Types.DeviceStatusEnum.DeviceDisconnected,
|
||||
this.isTimeoutOrAbort(error) ? "write-timeout" : "write-error",
|
||||
);
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
let controller: ReadableStreamDefaultController<Types.DeviceOutput>;
|
||||
|
||||
this._fromDevice = new ReadableStream<Types.DeviceOutput>({
|
||||
start: (ctrl) => {
|
||||
controller = ctrl;
|
||||
this.fromDeviceController = ctrl;
|
||||
this.emitStatus(Types.DeviceStatusEnum.DeviceConnecting);
|
||||
|
||||
// Start polling immediately
|
||||
void this.safePoll();
|
||||
this.interval = setInterval(
|
||||
() => void this.safePoll(),
|
||||
this.fetchInterval,
|
||||
);
|
||||
},
|
||||
cancel: () => {
|
||||
if (this.interval) {
|
||||
clearInterval(this.interval);
|
||||
}
|
||||
this.interval = undefined;
|
||||
},
|
||||
});
|
||||
|
||||
this.interval = setInterval(async () => {
|
||||
if (this.fetching) {
|
||||
// We still have the previous request open
|
||||
return;
|
||||
}
|
||||
this.fetching = true;
|
||||
try {
|
||||
await this.readFromRadio(controller);
|
||||
} catch {
|
||||
// TODO: Emit disconnection events for certain types of errors
|
||||
}
|
||||
this.fetching = false;
|
||||
}, this.fetchInterval);
|
||||
}
|
||||
|
||||
private async readFromRadio(
|
||||
controller: ReadableStreamDefaultController<Types.DeviceOutput>,
|
||||
): Promise<void> {
|
||||
/** Poll `/api/v1/fromradio` and enqueue incoming packets. */
|
||||
private async readFromRadio(): Promise<void> {
|
||||
let readBuffer = new ArrayBuffer(1);
|
||||
|
||||
while (readBuffer.byteLength > 0) {
|
||||
const response = await fetch(
|
||||
`${this.url}/api/v1/fromradio?all=${
|
||||
this.receiveBatchRequests ? "true" : "false"
|
||||
}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
Accept: "application/x-protobuf",
|
||||
const inflight = new AbortController();
|
||||
this.inflightReadController = inflight;
|
||||
|
||||
const signal = AbortSignal.any([
|
||||
inflight.signal,
|
||||
AbortSignal.timeout(READ_TIMEOUT_MS),
|
||||
]);
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${this.url}/api/v1/fromradio?all=${this.receiveBatchRequests ? "true" : "false"}`,
|
||||
{
|
||||
method: "GET",
|
||||
headers: { Accept: "application/x-protobuf" },
|
||||
signal,
|
||||
},
|
||||
},
|
||||
);
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`fromradio ${response.status} ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
|
||||
readBuffer = await response.arrayBuffer();
|
||||
this.emitStatus(Types.DeviceStatusEnum.DeviceConnected);
|
||||
|
||||
if (readBuffer.byteLength > 0) {
|
||||
controller.enqueue({
|
||||
type: "packet",
|
||||
data: new Uint8Array(readBuffer),
|
||||
});
|
||||
readBuffer = await response.arrayBuffer();
|
||||
|
||||
if (readBuffer.byteLength > 0) {
|
||||
this.fromDeviceController?.enqueue({
|
||||
type: "packet",
|
||||
data: new Uint8Array(readBuffer),
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
this.inflightReadController = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Write a protobuf-encoded request to `/api/v1/toradio`. */
|
||||
private async writeToRadio(data: Uint8Array): Promise<void> {
|
||||
await fetch(`${this.url}/api/v1/toradio`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/x-protobuf",
|
||||
},
|
||||
body: data,
|
||||
});
|
||||
try {
|
||||
const response = await fetch(`${this.url}/api/v1/toradio`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/x-protobuf" },
|
||||
body: toArrayBuffer(data),
|
||||
signal: AbortSignal.timeout(WRITE_TIMEOUT_MS),
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`toradio ${response.status} ${response.statusText}`);
|
||||
}
|
||||
} catch (error) {
|
||||
if (!this.closingByUser) {
|
||||
this.emitStatus(
|
||||
Types.DeviceStatusEnum.DeviceDisconnected,
|
||||
this.isTimeoutOrAbort(error) ? "write-timeout" : "write-error",
|
||||
);
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/** Writable stream of bytes to the device. */
|
||||
get toDevice(): WritableStream<Uint8Array> {
|
||||
return this._toDevice;
|
||||
}
|
||||
|
||||
/** Readable stream of {@link Types.DeviceOutput} from the device. */
|
||||
get fromDevice(): ReadableStream<Types.DeviceOutput> {
|
||||
return this._fromDevice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop polling and emit `DeviceDisconnected("user")`.
|
||||
*/
|
||||
disconnect(): Promise<void> {
|
||||
this.fetching = false;
|
||||
this.closingByUser = true;
|
||||
|
||||
if (this.interval) {
|
||||
clearInterval(this.interval);
|
||||
}
|
||||
this.interval = undefined;
|
||||
this.fetching = false;
|
||||
|
||||
try {
|
||||
this.inflightReadController?.abort();
|
||||
} catch {}
|
||||
this.inflightReadController = undefined;
|
||||
|
||||
this.emitStatus(Types.DeviceStatusEnum.DeviceDisconnected, "user");
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
private emitStatus(next: Types.DeviceStatusEnum, reason?: string): void {
|
||||
if (next === this.lastStatus) {
|
||||
return;
|
||||
}
|
||||
this.lastStatus = next;
|
||||
this.fromDeviceController?.enqueue({
|
||||
type: "status",
|
||||
data: { status: next, reason },
|
||||
});
|
||||
}
|
||||
|
||||
private isTimeoutOrAbort(err: unknown): boolean {
|
||||
return (
|
||||
(err instanceof DOMException &&
|
||||
(err.name === "AbortError" || err.name === "TimeoutError")) ||
|
||||
(err instanceof Error &&
|
||||
(err.name === "AbortError" || err.name === "TimeoutError"))
|
||||
);
|
||||
}
|
||||
|
||||
private async safePoll(): Promise<void> {
|
||||
if (this.fetching) {
|
||||
return;
|
||||
}
|
||||
this.fetching = true;
|
||||
try {
|
||||
await this.readFromRadio();
|
||||
} catch (error) {
|
||||
if (!this.closingByUser) {
|
||||
this.emitStatus(
|
||||
Types.DeviceStatusEnum.DeviceDisconnected,
|
||||
this.isTimeoutOrAbort(error) ? "read-timeout" : "read-error",
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
this.fetching = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"outDir": "./dist",
|
||||
"moduleResolution": "bundler",
|
||||
"emitDeclarationOnly": false,
|
||||
"esModuleInterop": true,
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
}
|
||||
|
||||
28
packages/transport-node-serial/README.md
Normal file
28
packages/transport-node-serial/README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# @meshtastic/transport-node-serial
|
||||
|
||||
[](https://jsr.io/@meshtastic/transport-node-serial)
|
||||
[](https://github.com/meshtastic/js/actions/workflows/ci.yml)
|
||||
[](https://cla-assistant.io/meshtastic/meshtastic.js)
|
||||
[](https://opencollective.com/meshtastic/)
|
||||
[](https://vercel.com?utm_source=meshtastic&utm_campaign=oss)
|
||||
|
||||
## Overview
|
||||
|
||||
`@meshtastic/transport-noden-node` Provides Serial transport (Node) for Meshtastic
|
||||
devices. Installation instructions are available at
|
||||
[JSR](https://jsr.io/@meshtastic/transport-node-serial)
|
||||
[NPM](https://www.npmjs.com/package/@meshtastic/transport-node-serial)
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { MeshDevice } from "@meshtastic/core";
|
||||
import { TransportNodeSerial } from "@meshtastic/transport-node-serial";
|
||||
|
||||
const transport = await TransportNodeSerial.create("/dev/cu.usbserial-0001");
|
||||
const device = new MeshDevice(transport);
|
||||
```
|
||||
|
||||
## Stats
|
||||
|
||||

|
||||
1
packages/transport-node-serial/mod.ts
Normal file
1
packages/transport-node-serial/mod.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { TransportNodeSerial } from "./src/transport.ts";
|
||||
50
packages/transport-node-serial/package.json
Normal file
50
packages/transport-node-serial/package.json
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"name": "@meshtastic/transport-node-serial",
|
||||
"version": "0.0.2",
|
||||
"description": "NodeJS-specific serial transport layer for Meshtastic web applications.",
|
||||
"exports": {
|
||||
".": "./mod.ts"
|
||||
},
|
||||
"type": "module",
|
||||
"main": "./dist/mod.js",
|
||||
"module": "./dist/mod.js",
|
||||
"types": "./dist/mod.d.ts",
|
||||
"files": [
|
||||
"package.json",
|
||||
"README.md",
|
||||
"LICENSE",
|
||||
"dist"
|
||||
],
|
||||
"license": "GPL-3.0-only",
|
||||
"tsdown": {
|
||||
"entry": "mod.ts",
|
||||
"dts": true,
|
||||
"format": [
|
||||
"esm"
|
||||
],
|
||||
"splitting": false,
|
||||
"clean": true
|
||||
},
|
||||
"jsrInclude": [
|
||||
"mod.ts",
|
||||
"src",
|
||||
"README.md",
|
||||
"LICENSE"
|
||||
],
|
||||
"jsrExclude": [
|
||||
"src/**/*.test.ts"
|
||||
],
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"prepack": "cp ../../LICENSE ./LICENSE",
|
||||
"clean": "rm -rf dist LICENSE",
|
||||
"build:npm": "tsdown",
|
||||
"publish:npm": "pnpm clean && pnpm build:npm && pnpm publish --access public --no-git-checks",
|
||||
"prepare:jsr": "rm -rf dist && pnpm dlx pkg-to-jsr",
|
||||
"publish:jsr": "pnpm run prepack && pnpm prepare:jsr && deno publish --allow-dirty --no-check"
|
||||
},
|
||||
"dependencies": {
|
||||
"@meshtastic/core": "workspace:*",
|
||||
"serialport": "^13.0.0"
|
||||
}
|
||||
}
|
||||
189
packages/transport-node-serial/src/transport.test.ts
Normal file
189
packages/transport-node-serial/src/transport.test.ts
Normal file
@@ -0,0 +1,189 @@
|
||||
import { Duplex } from "node:stream";
|
||||
import { Types, Utils } from "@meshtastic/core";
|
||||
import type { SerialPort } from "serialport";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { runTransportContract } from "../../../tests/utils/transportContract.ts";
|
||||
import { TransportNodeSerial } from "./transport.ts";
|
||||
|
||||
function isStatusEvent(
|
||||
output: Types.DeviceOutput | undefined,
|
||||
): output is Extract<Types.DeviceOutput, { type: "status" }> {
|
||||
return output !== undefined && output.type === "status";
|
||||
}
|
||||
|
||||
class FakeSerialPort extends Duplex {
|
||||
public lastWritten: Uint8Array | undefined;
|
||||
|
||||
constructor() {
|
||||
super({ objectMode: false });
|
||||
}
|
||||
|
||||
_read() {}
|
||||
|
||||
_write(
|
||||
chunk: Buffer,
|
||||
_encoding: BufferEncoding,
|
||||
callback: (error?: Error | null) => void,
|
||||
) {
|
||||
this.lastWritten = new Uint8Array(
|
||||
chunk.buffer,
|
||||
chunk.byteOffset,
|
||||
chunk.byteLength,
|
||||
);
|
||||
callback();
|
||||
}
|
||||
|
||||
pushIncoming(data: Uint8Array) {
|
||||
const buf = Buffer.from(data.buffer, data.byteOffset, data.byteLength);
|
||||
this.push(buf);
|
||||
}
|
||||
|
||||
emitErrorOnce(message = "simulated serial error") {
|
||||
this.emit("error", new Error(message));
|
||||
}
|
||||
|
||||
emitClose() {
|
||||
this.emit("close");
|
||||
}
|
||||
|
||||
close() {
|
||||
this.destroy();
|
||||
this.emit("close");
|
||||
}
|
||||
}
|
||||
|
||||
function stubCoreTransforms() {
|
||||
const toDevice = () =>
|
||||
new TransformStream<Uint8Array, Uint8Array>({
|
||||
transform(chunk, controller) {
|
||||
controller.enqueue(chunk);
|
||||
},
|
||||
});
|
||||
|
||||
const fromDeviceFactory = () =>
|
||||
new TransformStream<Uint8Array, Types.DeviceOutput>({
|
||||
transform(chunk, controller) {
|
||||
controller.enqueue({ type: "packet", data: chunk });
|
||||
},
|
||||
});
|
||||
|
||||
// Utils.toDeviceStream is a getter
|
||||
const transform = Utils.toDeviceStream;
|
||||
vi.spyOn(Utils, "toDeviceStream", "get").mockReturnValue(
|
||||
toDevice as unknown as typeof transform,
|
||||
);
|
||||
|
||||
vi.spyOn(Utils, "fromDeviceStream").mockImplementation(
|
||||
() =>
|
||||
fromDeviceFactory() as unknown as TransformStream<
|
||||
Uint8Array,
|
||||
Types.DeviceOutput
|
||||
>,
|
||||
);
|
||||
|
||||
return {
|
||||
restore: () => vi.restoreAllMocks(),
|
||||
};
|
||||
}
|
||||
|
||||
describe("TransportNodeSerial (contract)", () => {
|
||||
let transformsStub: { restore: () => void } | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
transformsStub = stubCoreTransforms();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
transformsStub?.restore();
|
||||
});
|
||||
|
||||
runTransportContract({
|
||||
name: "TransportNodeSerial",
|
||||
setup: () => {},
|
||||
teardown: () => {
|
||||
vi.restoreAllMocks();
|
||||
},
|
||||
create: async () => {
|
||||
const fakePort = new FakeSerialPort();
|
||||
const transport = new TransportNodeSerial(
|
||||
fakePort as unknown as SerialPort,
|
||||
);
|
||||
await Promise.resolve();
|
||||
(globalThis as unknown as { __fakePort: FakeSerialPort }).__fakePort =
|
||||
fakePort;
|
||||
return transport;
|
||||
},
|
||||
pushIncoming: async (bytes) => {
|
||||
(
|
||||
globalThis as unknown as { __fakePort: FakeSerialPort }
|
||||
).__fakePort.pushIncoming(bytes);
|
||||
await Promise.resolve();
|
||||
},
|
||||
assertLastWritten: (bytes) => {
|
||||
const port = (globalThis as unknown as { __fakePort: FakeSerialPort })
|
||||
.__fakePort;
|
||||
expect(port.lastWritten).toBeDefined();
|
||||
expect(port.lastWritten).toEqual(bytes);
|
||||
},
|
||||
triggerDisconnect: async () => {
|
||||
(
|
||||
globalThis as unknown as { __fakePort: FakeSerialPort }
|
||||
).__fakePort.emitErrorOnce("test-disconnect");
|
||||
await Promise.resolve();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe("TransportNodeSerial (extras)", () => {
|
||||
let transformsStub: { restore: () => void } | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
transformsStub = stubCoreTransforms();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
transformsStub?.restore();
|
||||
});
|
||||
|
||||
it("emits DeviceDisconnected with reason 'port-closed' on close event", async () => {
|
||||
const fakePort = new FakeSerialPort();
|
||||
const transport = new TransportNodeSerial(
|
||||
fakePort as unknown as SerialPort,
|
||||
);
|
||||
const reader = transport.fromDevice.getReader();
|
||||
|
||||
await Promise.resolve();
|
||||
|
||||
const first = await reader.read();
|
||||
expect(isStatusEvent(first.value)).toBe(true);
|
||||
if (isStatusEvent(first.value)) {
|
||||
expect(first.value.data.status).toBe(
|
||||
Types.DeviceStatusEnum.DeviceConnecting,
|
||||
);
|
||||
}
|
||||
|
||||
const second = await reader.read();
|
||||
expect(isStatusEvent(second.value)).toBe(true);
|
||||
if (isStatusEvent(second.value)) {
|
||||
expect(second.value.data.status).toBe(
|
||||
Types.DeviceStatusEnum.DeviceConnected,
|
||||
);
|
||||
}
|
||||
|
||||
fakePort.emitClose();
|
||||
await Promise.resolve();
|
||||
|
||||
let sawClosed = false;
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const { value } = await reader.read();
|
||||
if (isStatusEvent(value) && value.data.reason === "port-closed") {
|
||||
sawClosed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
expect(sawClosed).toBe(true);
|
||||
|
||||
reader.releaseLock();
|
||||
await transport.disconnect();
|
||||
});
|
||||
});
|
||||
182
packages/transport-node-serial/src/transport.ts
Normal file
182
packages/transport-node-serial/src/transport.ts
Normal file
@@ -0,0 +1,182 @@
|
||||
import { Readable, Writable } from "node:stream";
|
||||
import { Types, Utils } from "@meshtastic/core";
|
||||
import { SerialPort } from "serialport";
|
||||
|
||||
/**
|
||||
* Node.js Serial transport for Meshtastic.
|
||||
*
|
||||
* Implements {@link Types.Transport} on top of a Node `SerialPort`.
|
||||
* Use {@link TransportNodeSerial.create} for a convenient factory, or
|
||||
* `new TransportNodeSerial(port)` if you already have an open port.
|
||||
*/
|
||||
export class TransportNodeSerial implements Types.Transport {
|
||||
private readonly _toDevice: WritableStream<Uint8Array>;
|
||||
private readonly _fromDevice: ReadableStream<Types.DeviceOutput>;
|
||||
private fromDeviceController?: ReadableStreamDefaultController<Types.DeviceOutput>;
|
||||
private port: SerialPort | undefined;
|
||||
private pipePromise?: Promise<void>;
|
||||
private abortController: AbortController;
|
||||
private lastStatus: Types.DeviceStatusEnum =
|
||||
Types.DeviceStatusEnum.DeviceDisconnected;
|
||||
private closingByUser = false;
|
||||
|
||||
/**
|
||||
* Creates and connects a new TransportNode instance.
|
||||
* @param path - Path to the serial device
|
||||
* @param baudRate - Baud rate for the serial connection (default is 115200).
|
||||
* @returns A promise that resolves with a connected TransportNode instance.
|
||||
*/
|
||||
public static create(
|
||||
path: string,
|
||||
baudRate = 115200,
|
||||
): Promise<TransportNodeSerial> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const port = new SerialPort({
|
||||
path,
|
||||
baudRate,
|
||||
autoOpen: true,
|
||||
});
|
||||
|
||||
const onError = (err: Error) => {
|
||||
port.close();
|
||||
reject(err);
|
||||
};
|
||||
|
||||
port.once("error", onError);
|
||||
port.on("open", () => {
|
||||
port.removeListener("error", onError);
|
||||
resolve(new TransportNodeSerial(port));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new TransportNode.
|
||||
* @param port - An active Node.js SerialPort connection.
|
||||
*/
|
||||
constructor(port: SerialPort) {
|
||||
this.port = port;
|
||||
this.port.on("error", (err) => {
|
||||
console.error("Serial port connection error:", err);
|
||||
this.emitStatus(Types.DeviceStatusEnum.DeviceDisconnected, "port-error");
|
||||
});
|
||||
this.port.on("close", () => {
|
||||
if (this.closingByUser) {
|
||||
return;
|
||||
}
|
||||
this.emitStatus(Types.DeviceStatusEnum.DeviceDisconnected, "port-closed");
|
||||
});
|
||||
|
||||
const fromDeviceSource = Readable.toWeb(port) as ReadableStream<Uint8Array>;
|
||||
const transformed = fromDeviceSource.pipeThrough(Utils.fromDeviceStream());
|
||||
|
||||
this.abortController = new AbortController();
|
||||
const controller = this.abortController;
|
||||
|
||||
this._fromDevice = new ReadableStream<Types.DeviceOutput>({
|
||||
start: async (ctrl) => {
|
||||
this.fromDeviceController = ctrl;
|
||||
|
||||
this.emitStatus(Types.DeviceStatusEnum.DeviceConnecting);
|
||||
this.emitStatus(Types.DeviceStatusEnum.DeviceConnected);
|
||||
|
||||
const reader = transformed.getReader();
|
||||
try {
|
||||
while (true) {
|
||||
const { value, done } = await reader.read();
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
ctrl.enqueue(value);
|
||||
}
|
||||
ctrl.close();
|
||||
} catch (error) {
|
||||
if (this.closingByUser) {
|
||||
ctrl.close(); // graceful EOF on user
|
||||
} else {
|
||||
this.emitStatus(
|
||||
Types.DeviceStatusEnum.DeviceDisconnected,
|
||||
"read-error",
|
||||
);
|
||||
ctrl.error(
|
||||
error instanceof Error ? error : new Error(String(error)),
|
||||
);
|
||||
}
|
||||
try {
|
||||
await transformed.cancel();
|
||||
} catch {}
|
||||
} finally {
|
||||
reader.releaseLock();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Stream for data going FROM the application TO the Meshtastic device.
|
||||
const toDeviceTransform = Utils.toDeviceStream();
|
||||
this._toDevice = toDeviceTransform.writable;
|
||||
|
||||
this.pipePromise = toDeviceTransform.readable
|
||||
.pipeTo(Writable.toWeb(port) as WritableStream<Uint8Array>, {
|
||||
signal: controller.signal,
|
||||
})
|
||||
.catch((error) => {
|
||||
if (controller.signal.aborted || this.closingByUser) {
|
||||
return;
|
||||
}
|
||||
console.error("Error piping data to serial port:", error);
|
||||
this.emitStatus(
|
||||
Types.DeviceStatusEnum.DeviceDisconnected,
|
||||
"write-error",
|
||||
);
|
||||
try {
|
||||
this.port?.close();
|
||||
} catch {}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The WritableStream to send data to the Meshtastic device.
|
||||
*/
|
||||
public get toDevice(): WritableStream<Uint8Array> {
|
||||
return this._toDevice;
|
||||
}
|
||||
|
||||
/**
|
||||
* The ReadableStream to receive data from the Meshtastic device.
|
||||
*/
|
||||
public get fromDevice(): ReadableStream<Types.DeviceOutput> {
|
||||
return this._fromDevice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect from the serial port and emit `DeviceDisconnected("user")`.
|
||||
* Safe to call multiple times.
|
||||
*/
|
||||
async disconnect() {
|
||||
try {
|
||||
this.closingByUser = true;
|
||||
this.emitStatus(Types.DeviceStatusEnum.DeviceDisconnected, "user");
|
||||
|
||||
this.abortController?.abort();
|
||||
await this.pipePromise?.catch(() => {});
|
||||
|
||||
try {
|
||||
this.port?.close();
|
||||
} catch {}
|
||||
} finally {
|
||||
this.port = undefined;
|
||||
this.closingByUser = false;
|
||||
}
|
||||
}
|
||||
|
||||
private emitStatus(next: Types.DeviceStatusEnum, reason?: string): void {
|
||||
if (next === this.lastStatus) {
|
||||
return;
|
||||
}
|
||||
this.lastStatus = next;
|
||||
this.fromDeviceController?.enqueue({
|
||||
type: "status",
|
||||
data: { status: next, reason },
|
||||
});
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user