Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
+42
-31
@@ -28,41 +28,52 @@ jobs:
|
|||||||
cache: pnpm
|
cache: pnpm
|
||||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||||
|
|
||||||
- name: Setup Deno
|
- name: Prewarm & Install (workspace)
|
||||||
uses: denoland/setup-deno@v2
|
run: |
|
||||||
with:
|
set -euo pipefail
|
||||||
deno-version: v2.x
|
pnpm fetch
|
||||||
|
pnpm install --frozen-lockfile --offline
|
||||||
|
|
||||||
- name: Cache Deno dependencies
|
- name: Build All Packages (with exclusions)
|
||||||
uses: actions/cache@v4
|
shell: bash
|
||||||
with:
|
|
||||||
path: ~/.cache/deno
|
|
||||||
key: ${{ runner.os }}-deno-${{ hashFiles('**/deno.lock', '**/package.json') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-deno-
|
|
||||||
|
|
||||||
- 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
|
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
for pkg_dir in packages/*/; do
|
# List packages to exclude (full paths under repo root)
|
||||||
pkg_dir=${pkg_dir%/} # Remove trailing slash
|
EXCLUDED_DIRS=("packages/protobufs" "packages/transport-deno")
|
||||||
echo "🔍 Inspecting $pkg_dir..."
|
|
||||||
|
|
||||||
if [[ -f "$pkg_dir/package.json" ]] && [[ "$pkg_dir" != "packages/web" ]]; then
|
is_excluded() {
|
||||||
echo "🔧 Building with pnpm: $pkg_dir"
|
local dir="$1"
|
||||||
(cd "$pkg_dir" && pnpm install --frozen-lockfile && pnpm run build:npm)
|
for ex in "${EXCLUDED_DIRS[@]}"; do
|
||||||
else
|
if [[ "$dir" == "$ex" ]]; then
|
||||||
echo "⚠️ Skipping $pkg_dir (web package or no package.json)"
|
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
|
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
|
done
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ jobs:
|
|||||||
path: packages/web/dist/build.tar
|
path: packages/web/dist/build.tar
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Determine nightly tag
|
- name: Compute tags and labels
|
||||||
id: meta
|
id: meta
|
||||||
shell: bash
|
shell: bash
|
||||||
env:
|
env:
|
||||||
@@ -61,20 +61,27 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
DATE="$(date -u +%Y%m%d)"
|
DATE="$(date -u +%Y%m%d)"
|
||||||
|
ISO_CREATED="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||||
SHORTSHA="$(git rev-parse --short=12 HEAD)"
|
SHORTSHA="$(git rev-parse --short=12 HEAD)"
|
||||||
|
|
||||||
# Try to use latest release tag if it exists; fallback to package version; else date
|
# 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)"
|
LATEST_TAG="$(gh release view --json tagName --jq .tagName 2>/dev/null || true)"
|
||||||
if [ -z "$LATEST_TAG" ] && [ -f packages/web/package.json ]; then
|
if [ -z "$LATEST_TAG" ] && [ -f packages/web/package.json ]; then
|
||||||
LATEST_TAG="v$(jq -r .version packages/web/package.json)"
|
LATEST_TAG="v$(jq -r .version packages/web/package.json)"
|
||||||
fi
|
fi
|
||||||
if [ -n "$LATEST_TAG" ] && [ "$LATEST_TAG" != "vnull" ]; then
|
|
||||||
TAG="nightly-${LATEST_TAG}-${SHORTSHA}"
|
if [ -n "${LATEST_TAG:-}" ] && [ "$LATEST_TAG" != "vnull" ]; then
|
||||||
|
IMMUTABLE="nightly-${LATEST_TAG}-${SHORTSHA}"
|
||||||
else
|
else
|
||||||
TAG="nightly-${DATE}-${SHORTSHA}"
|
IMMUTABLE="nightly-${DATE}-${SHORTSHA}"
|
||||||
fi
|
fi
|
||||||
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "tags=nightly, $TAG" >> "$GITHUB_OUTPUT"
|
# Outputs
|
||||||
echo "Resolved nightly tags: nightly, $TAG"
|
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
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
@@ -86,23 +93,28 @@ jobs:
|
|||||||
containerfiles: |
|
containerfiles: |
|
||||||
./packages/web/infra/Containerfile
|
./packages/web/infra/Containerfile
|
||||||
image: ${{ env.REGISTRY_IMAGE }}
|
image: ${{ env.REGISTRY_IMAGE }}
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: |
|
||||||
|
${{ steps.meta.outputs.moving_tag }}
|
||||||
|
${{ steps.meta.outputs.immutable_tag }}
|
||||||
oci: true
|
oci: true
|
||||||
platforms: linux/amd64, linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
labels: |
|
labels: |
|
||||||
org.opencontainers.image.source=${{ github.repository }}
|
org.opencontainers.image.source=${{ github.repository }}
|
||||||
org.opencontainers.image.revision=${{ github.sha }}
|
org.opencontainers.image.revision=${{ github.sha }}
|
||||||
org.opencontainers.image.created=${{ github.run_id }}
|
org.opencontainers.image.created=${{ steps.meta.outputs.created }}
|
||||||
|
|
||||||
- name: Push To GHCR
|
- name: Push To GHCR
|
||||||
id: push-to-registry
|
id: push-to-registry
|
||||||
uses: redhat-actions/push-to-registry@v2
|
uses: redhat-actions/push-to-registry@v2
|
||||||
with:
|
with:
|
||||||
image: ${{ steps.build-container.outputs.image }}
|
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
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Print image URL
|
- name: Print image URLs
|
||||||
run: echo "Image pushed to ${{ steps.push-to-registry.outputs.registry-paths }}"
|
run: |
|
||||||
|
echo "Moving tag: ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.moving_tag }}"
|
||||||
|
echo "Immutable tag: ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.immutable_tag }}"
|
||||||
|
|||||||
@@ -4,47 +4,47 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
packages:
|
packages:
|
||||||
description: 'Packages to release (comma-separated, or "all" for all packages)'
|
description: 'Packages to release (comma-separated, or "all")'
|
||||||
required: false
|
required: false
|
||||||
default: 'all'
|
default: 'all'
|
||||||
|
bump:
|
||||||
|
description: 'Semver bump (patch | minor | major)'
|
||||||
|
required: false
|
||||||
|
default: 'patch'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: write # we commit the bumped versions back
|
||||||
id-token: write # <-- required for JSR OIDC
|
id-token: write # required for JSR OIDC
|
||||||
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
# Node + pnpm (with cache)
|
|
||||||
- name: Setup Node
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 22
|
|
||||||
cache: pnpm
|
|
||||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
|
|
||||||
- name: Configure npm auth
|
- name: Setup Node.js
|
||||||
env:
|
uses: actions/setup-node@v4
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
with:
|
||||||
run: |
|
node-version: 22
|
||||||
pnpm config set //registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}
|
cache: pnpm
|
||||||
pnpm config set registry https://registry.npmjs.org/
|
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||||
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
|
||||||
- name: Install deps (root)
|
- name: Install dependencies
|
||||||
run: pnpm install
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Resolve package list
|
- name: Resolve package list
|
||||||
id: pkgs
|
id: pkgs
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
if [ "${{ github.event.inputs.packages }}" = "all" ] || [ -z "${{ github.event.inputs.packages }}" ]; then
|
if [ "${{ github.event.inputs.packages }}" = "all" ] || [ -z "${{ github.event.inputs.packages }}" ]; then
|
||||||
mapfile -t TARGETS < <(ls -d packages/* | grep -v 'packages/web')
|
mapfile -t TARGETS < <(ls -d packages/* | grep -v 'packages/web')
|
||||||
else
|
else
|
||||||
@@ -53,61 +53,96 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
printf '%s\n' "${TARGETS[@]}" | paste -sd, - > targets.txt
|
printf '%s\n' "${TARGETS[@]}" | paste -sd, - > targets.txt
|
||||||
echo "list=$(cat targets.txt)" >> "$GITHUB_OUTPUT"
|
echo "list=$(cat targets.txt)" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Targets: $(cat targets.txt)"
|
||||||
|
|
||||||
- name: Build selected packages (tsdown)
|
- name: Bump package.json versions (no git tag)
|
||||||
|
shell: bash
|
||||||
run: |
|
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
|
||||||
|
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 }}"
|
IFS=',' read -ra TARGETS <<< "${{ steps.pkgs.outputs.list }}"
|
||||||
for dir in "${TARGETS[@]}"; do
|
for dir in "${TARGETS[@]}"; do
|
||||||
echo "Building $dir"
|
echo "Building $dir"
|
||||||
pnpm --filter "./$dir" run build
|
pnpm --filter "./$dir" run build
|
||||||
done
|
done
|
||||||
|
|
||||||
- name: Sync jsr.json version from package.json
|
|
||||||
run: |
|
|
||||||
IFS=',' read -ra TARGETS <<< "${{ steps.pkgs.outputs.list }}"
|
|
||||||
for dir in "${TARGETS[@]}"; do
|
|
||||||
if [ -f "$dir/jsr.json" ] && [ -f "$dir/package.json" ]; then
|
|
||||||
PKG_VER=$(jq -r .version "$dir/package.json")
|
|
||||||
jq --arg v "$PKG_VER" '.version = $v' "$dir/jsr.json" > "$dir/jsr.json.tmp" && mv "$dir/jsr.json.tmp" "$dir/jsr.json"
|
|
||||||
echo "Updated $dir/jsr.json to version $PKG_VER"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
- name: Publish to JSR (OIDC)
|
- name: Publish to JSR (OIDC)
|
||||||
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
IFS=',' read -ra TARGETS <<< "${{ steps.pkgs.outputs.list }}"
|
IFS=',' read -ra TARGETS <<< "${{ steps.pkgs.outputs.list }}"
|
||||||
for dir in "${TARGETS[@]}"; do
|
for dir in "${TARGETS[@]}"; do
|
||||||
if [ -f "$dir/jsr.json" ]; then
|
if [ -f "$dir/jsr.json" ]; then
|
||||||
echo "Publishing $dir to JSR via OIDC…"
|
echo "Publishing $dir to JSR via OIDC…"
|
||||||
cd "$dir"
|
( cd "$dir"
|
||||||
[ -d dist ] || pnpm run build
|
[ -d dist ] || pnpm run build
|
||||||
if ! npx --yes jsr publish 2>&1 | tee jsr_publish.log; then
|
npx --yes jsr publish
|
||||||
echo "JSR publish failed for $dir. Error output:"
|
)
|
||||||
cat jsr_publish.log
|
|
||||||
fi
|
|
||||||
cd - >/dev/null
|
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
- name: Replace exports entry in package.json
|
- name: Configure npm auth
|
||||||
run: |
|
run: |
|
||||||
tmp=$(mktemp)
|
pnpm config set //registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}
|
||||||
jq '.exports["."] = "./dist/mod.mjs"' package.json > "$tmp" \
|
pnpm config set registry https://registry.npmjs.org/
|
||||||
&& mv "$tmp" package.json
|
|
||||||
|
|
||||||
- name: Publish to npm
|
- name: Publish to npm
|
||||||
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
IFS=',' read -ra TARGETS <<< "${{ steps.pkgs.outputs.list }}"
|
IFS=',' read -ra TARGETS <<< "${{ steps.pkgs.outputs.list }}"
|
||||||
for dir in "${TARGETS[@]}"; do
|
for dir in "${TARGETS[@]}"; do
|
||||||
if [ -f "$dir/package.json" ]; then
|
if [ -f "$dir/package.json" ]; then
|
||||||
echo "Publishing $dir to npm…"
|
echo "Publishing $dir to npm…"
|
||||||
cd "$dir"
|
( cd "$dir"
|
||||||
[ -d dist ] || pnpm run build
|
[ -d dist ] || pnpm run build
|
||||||
npm publish --access public || echo "npm publish failed for $dir"
|
npm publish --access public
|
||||||
cd - >/dev/null
|
)
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -30,38 +30,11 @@ jobs:
|
|||||||
release-web:
|
release-web:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
# For manual runs, allow building a chosen ref (branch/tag/SHA)
|
|
||||||
ref: ${{ inputs.ref != '' && inputs.ref || github.ref }}
|
ref: ${{ inputs.ref != '' && inputs.ref || github.ref }}
|
||||||
|
|
||||||
- name: Determine tag & latest flag
|
|
||||||
id: meta
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
# Determine TAG
|
|
||||||
if [ "${{ github.event_name }}" = "release" ]; then
|
|
||||||
TAG="${{ github.event.release.tag_name }}"
|
|
||||||
# Push "latest" only for full releases (not prereleases)
|
|
||||||
if [ "${{ github.event.release.prerelease }}" = "true" ]; then
|
|
||||||
PUSH_LATEST="false"
|
|
||||||
else
|
|
||||||
PUSH_LATEST="true"
|
|
||||||
fi
|
|
||||||
elif [ -n "${{ inputs.tag_name }}" ]; then
|
|
||||||
TAG="${{ inputs.tag_name }}"
|
|
||||||
PUSH_LATEST="false"
|
|
||||||
else
|
|
||||||
SHA="$(git rev-parse --short=12 HEAD)"
|
|
||||||
TAG="adhoc-${SHA}"
|
|
||||||
PUSH_LATEST="false"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "push_latest=$PUSH_LATEST" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "Resolved tag: $TAG (push_latest=$PUSH_LATEST)"
|
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
@@ -81,77 +54,107 @@ jobs:
|
|||||||
working-directory: packages/web
|
working-directory: packages/web
|
||||||
run: pnpm run build
|
run: pnpm run build
|
||||||
|
|
||||||
- name: Build web package
|
- name: Create release archive
|
||||||
working-directory: packages/web
|
|
||||||
run: pnpm run build
|
|
||||||
|
|
||||||
- name: Create Web App Release Archive
|
|
||||||
working-directory: packages/web
|
working-directory: packages/web
|
||||||
run: pnpm run package
|
run: pnpm run package
|
||||||
|
|
||||||
- name: Upload Web App Archive (artifact)
|
- name: Upload archive (artifact)
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: web-build-${{ steps.meta.outputs.tag }}
|
name: web-build
|
||||||
if-no-files-found: error
|
|
||||||
path: packages/web/dist/build.tar
|
path: packages/web/dist/build.tar
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Attach Web Archive to GitHub Release
|
- name: Attach archive to GitHub Release
|
||||||
if: ${{ github.event_name == 'release' || inputs.attach_to_release == true }}
|
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:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
# Compute created timestamp & short SHA for labels/tags
|
||||||
|
- name: Compute meta
|
||||||
|
id: gen
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
if [ "${{ github.event_name }}" = "release" ]; then
|
echo "created=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT"
|
||||||
TAG="${{ steps.meta.outputs.tag }}"
|
echo "shortsha=$(git rev-parse --short=12 HEAD)" >> "$GITHUB_OUTPUT"
|
||||||
else
|
|
||||||
if [ -z "${{ inputs.tag_name }}" ]; then
|
# Build/tag matrix:
|
||||||
echo "attach_to_release requested but no tag_name provided." >&2
|
# - For release events: vX.Y.Z, semver helpers, and latest (only if NOT prerelease)
|
||||||
exit 1
|
# - For prerelease events: vX.Y.Z and semver helpers (NO latest)
|
||||||
fi
|
# - For manual runs:
|
||||||
TAG="${{ inputs.tag_name }}"
|
# * if tag_name set: that raw tag
|
||||||
fi
|
# * else: adhoc-<shortsha>
|
||||||
gh release upload "$TAG" packages/web/dist/build.tar --clobber
|
- 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
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
- name: Compute image tags
|
- name: Set up Buildx
|
||||||
id: tags
|
uses: docker/setup-buildx-action@v3
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
if [ "${{ steps.meta.outputs.push_latest }}" = "true" ]; then
|
|
||||||
echo "list=latest, ${{ steps.meta.outputs.tag }}" >> "$GITHUB_OUTPUT"
|
|
||||||
else
|
|
||||||
echo "list=${{ steps.meta.outputs.tag }}" >> "$GITHUB_OUTPUT"
|
|
||||||
fi
|
|
||||||
TAGS="latest, ${{ steps.meta.outputs.tag }}"
|
|
||||||
else
|
|
||||||
TAGS="${{ steps.meta.outputs.tag }}"
|
|
||||||
fi
|
|
||||||
echo "list=$TAGS" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "Using image tags: $TAGS"
|
|
||||||
|
|
||||||
- name: Build Container Image
|
- name: Login to GHCR
|
||||||
id: build-container
|
uses: docker/login-action@v3
|
||||||
uses: redhat-actions/buildah-build@v2
|
|
||||||
with:
|
with:
|
||||||
containerfiles: |
|
|
||||||
./packages/web/infra/Containerfile
|
|
||||||
image: ${{ env.REGISTRY_IMAGE }}
|
|
||||||
tags: ${{ steps.tags.outputs.list }}
|
|
||||||
oci: true
|
|
||||||
platforms: linux/amd64, linux/arm64
|
|
||||||
|
|
||||||
- name: Push Container 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 }}
|
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Output Image URL
|
# Push images for:
|
||||||
run: echo "🖼️ Image pushed to ${{ steps.push-to-registry.outputs.registry-paths }}"
|
# - 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
|
||||||
|
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)."
|
||||||
|
|||||||
@@ -9,3 +9,5 @@ __screenshots__*
|
|||||||
npm/
|
npm/
|
||||||
.idea
|
.idea
|
||||||
**/LICENSE
|
**/LICENSE
|
||||||
|
|
||||||
|
packages/protobufs/packages/ts/dist
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "packages/packages/protobufs"]
|
||||||
|
path = packages/packages/protobufs
|
||||||
|
url = https://github.com/meshtastic/protobufs
|
||||||
Vendored
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"recommendations": ["bradlc.vscode-tailwindcss", "biomejs.biome"]
|
"recommendations": ["bradlc.vscode-tailwindcss", "biomejs.biome"]
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+3
-2
@@ -1,9 +1,10 @@
|
|||||||
{
|
{
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.biome": "explicit",
|
"source.fixAll.biome": "explicit"
|
||||||
},
|
},
|
||||||
"search.exclude": {
|
"search.exclude": {
|
||||||
"**/i18n/locales/*-*/**": true,
|
"**/i18n/locales/*-*/**": true
|
||||||
},
|
},
|
||||||
|
"vitest.workspaceConfig": "vitest.config.ts"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,10 +21,12 @@ All projects are located within the `packages/` directory:
|
|||||||
- **[Hosted version](https://client.meshtastic.org)**
|
- **[Hosted version](https://client.meshtastic.org)**
|
||||||
- **`packages/core`:** Core functionality for Meshtastic JS.
|
- **`packages/core`:** Core functionality for Meshtastic JS.
|
||||||
- **`packages/transport-node`:** TCP Transport for the NodeJS runtime.
|
- **`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-deno`:** TCP Transport for the Deno runtime.
|
||||||
- **`packages/transport-http`:** HTTP Transport.
|
- **`packages/transport-http`:** HTTP Transport.
|
||||||
- **`packages/transport-web-bluetooth`:** Web Bluetooth Transport.
|
- **`packages/transport-web-bluetooth`:** Web Bluetooth Transport.
|
||||||
- **`packages/transport-web-serial`:** Web Serial 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
|
All `Meshtastic JS` packages (core and transports) are published both to
|
||||||
[JSR](https://jsr.io/@meshtastic). [NPM](https://www.npmjs.com/org/meshtastic)
|
[JSR](https://jsr.io/@meshtastic). [NPM](https://www.npmjs.com/org/meshtastic)
|
||||||
|
|||||||
+68
-40
@@ -1,44 +1,72 @@
|
|||||||
{
|
{
|
||||||
"files": {
|
"files": {
|
||||||
"includes": ["**/*.ts", "**/*.tsx", "!**/*.test.ts", "!**/*.test.tsx", "!npm_modules/**", "!dist/**", "!npm/**"],
|
"includes": [
|
||||||
"ignoreUnknown": false
|
"**/*.ts",
|
||||||
},
|
"**/*.tsx",
|
||||||
"formatter": {
|
"!npm_modules",
|
||||||
"enabled": true,
|
"!dist",
|
||||||
"formatWithErrors": false,
|
"!npm",
|
||||||
"indentStyle": "space",
|
"**/*.json",
|
||||||
"indentWidth": 2,
|
"!**/locales/*-*/*.json"
|
||||||
"lineWidth": 80,
|
],
|
||||||
"attributePosition": "auto"
|
"ignoreUnknown": false
|
||||||
},
|
},
|
||||||
"linter": {
|
"formatter": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
"formatWithErrors": false,
|
||||||
|
"indentStyle": "space",
|
||||||
|
"indentWidth": 2,
|
||||||
|
"lineWidth": 80,
|
||||||
|
"attributePosition": "auto"
|
||||||
|
},
|
||||||
|
"linter": {
|
||||||
|
"enabled": true,
|
||||||
"includes": ["**", "!test/**"],
|
"includes": ["**", "!test/**"],
|
||||||
"rules": {
|
"rules": {
|
||||||
"recommended": true,
|
"recommended": true,
|
||||||
"suspicious": {
|
"suspicious": {
|
||||||
"noExplicitAny": "error",
|
"noExplicitAny": "error",
|
||||||
"noDebugger": "error"
|
"noDebugger": "error"
|
||||||
},
|
},
|
||||||
"style": {
|
"style": {
|
||||||
"useBlockStatements": "error",
|
"useBlockStatements": "error",
|
||||||
"useSingleVarDeclarator": "off"
|
"useSingleVarDeclarator": "off"
|
||||||
},
|
},
|
||||||
"correctness": {
|
"correctness": {
|
||||||
"noUnusedVariables": "error",
|
"noUnusedVariables": "error",
|
||||||
"noUnusedImports": "error"
|
"noUnusedImports": "error",
|
||||||
}
|
"useImportExtensions": "error"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"javascript": {
|
|
||||||
"formatter": {
|
|
||||||
"quoteStyle": "double",
|
|
||||||
"semicolons": "always"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"json": {
|
|
||||||
"formatter": {
|
|
||||||
"enabled": false
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+50
-50
@@ -1,52 +1,52 @@
|
|||||||
{
|
{
|
||||||
"name": "@meshtastic/web",
|
"name": "@meshtastic/web",
|
||||||
"version": "2.7.0-0",
|
"version": "2.7.0-0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Meshtastic web client monorepo",
|
"description": "Meshtastic web client monorepo",
|
||||||
"license": "GPL-3.0-only",
|
"license": "GPL-3.0-only",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/meshtastic/web.git"
|
"url": "git+https://github.com/meshtastic/web.git"
|
||||||
},
|
},
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/meshtastic/web/issues"
|
"url": "https://github.com/meshtastic/web/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://meshtastic.org",
|
"homepage": "https://meshtastic.org",
|
||||||
"simple-git-hooks": {
|
"simple-git-hooks": {
|
||||||
"pre-commit": "pnpm run check:fix"
|
"pre-commit": "pnpm run check:fix"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"preinstall": "npx only-allow pnpm",
|
"preinstall": "npx only-allow pnpm",
|
||||||
"lint": "biome lint",
|
"lint": "biome lint",
|
||||||
"lint:fix": "biome lint --write",
|
"lint:fix": "biome lint --write",
|
||||||
"format": "biome format",
|
"format": "biome format",
|
||||||
"format:fix": "biome format . --write",
|
"format:fix": "biome format . --write",
|
||||||
"check": "biome check",
|
"check": "biome check",
|
||||||
"check:fix": "biome check --write",
|
"check:fix": "biome check --write",
|
||||||
"build:all": "pnpm run --filter '*' build",
|
"build:all": "pnpm run --filter '*' build",
|
||||||
"clean:all": "pnpm run --filter '*' clean",
|
"clean:all": "pnpm run --filter '*' clean",
|
||||||
"publish:packages": "pnpm run build --filter 'packages/transport-*'",
|
"publish:packages": "pnpm run build --filter 'packages/transport-*'",
|
||||||
"test": "vitest"
|
"test": "vitest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bufbuild/protobuf": "^2.6.1",
|
"@bufbuild/protobuf": "^2.9.0",
|
||||||
"@meshtastic/protobufs": "npm:@jsr/meshtastic__protobufs@^2.7.0",
|
"@meshtastic/protobufs": "npm:@jsr/meshtastic__protobufs@^2.7.12-1",
|
||||||
"ste-simple-events": "^3.0.11",
|
"ste-simple-events": "^3.0.11",
|
||||||
"tslog": "^4.9.3"
|
"tslog": "^4.9.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.16.4",
|
"@types/node": "^24.3.1",
|
||||||
"biome": "^0.3.3",
|
"@biomejs/biome": "2.2.4",
|
||||||
"tsdown": "^0.13.4",
|
"tsdown": "^0.15.0",
|
||||||
"typescript": "^5.8.3",
|
"typescript": "^5.9.2",
|
||||||
"vitest": "^3.2.4"
|
"vitest": "^3.2.4"
|
||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"onlyBuiltDependencies": [
|
"onlyBuiltDependencies": [
|
||||||
"@tailwindcss/oxide",
|
"@tailwindcss/oxide",
|
||||||
"core-js",
|
"core-js",
|
||||||
"esbuild",
|
"esbuild",
|
||||||
"simple-git-hooks"
|
"simple-git-hooks"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@meshtastic/core",
|
|
||||||
"version": "2.6.6",
|
|
||||||
"exports": {
|
|
||||||
".": "./mod.ts"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +1,34 @@
|
|||||||
{
|
{
|
||||||
"name": "@meshtastic/core",
|
"name": "@meshtastic/core",
|
||||||
"version": "2.6.6-1",
|
"version": "2.6.7",
|
||||||
"description": "Core functionalities for Meshtastic web applications.",
|
"description": "Core functionalities for Meshtastic web applications.",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./mod.ts"
|
".": "./mod.ts"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/mod.mjs",
|
"main": "./dist/mod.js",
|
||||||
"module": "./dist/mod.mjs",
|
"module": "./dist/mod.js",
|
||||||
"types": "./dist/mod.d.mts",
|
"types": "./dist/mod.d.ts",
|
||||||
"license": "GPL-3.0-only",
|
"license": "GPL-3.0-only",
|
||||||
"tsdown": {
|
"tsdown": {
|
||||||
"entry": "mod.ts",
|
"entry": "mod.ts",
|
||||||
"dts": true,
|
"dts": true,
|
||||||
"format": ["esm"],
|
"format": [
|
||||||
|
"esm"
|
||||||
|
],
|
||||||
"splitting": false,
|
"splitting": false,
|
||||||
"clean": true
|
"clean": true
|
||||||
},
|
},
|
||||||
"files": [
|
"jsrInclude": [
|
||||||
|
"mod.ts",
|
||||||
|
"src",
|
||||||
|
"README.md",
|
||||||
|
"LICENSE"
|
||||||
|
],
|
||||||
|
"jsrExclude": [
|
||||||
|
"src/**/*.test.ts"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
"package.json",
|
"package.json",
|
||||||
"README.md",
|
"README.md",
|
||||||
"LICENSE",
|
"LICENSE",
|
||||||
|
|||||||
@@ -64,6 +64,11 @@ export class MeshDevice {
|
|||||||
this.isConfigured = true;
|
this.isConfigured = true;
|
||||||
} else if (status === DeviceStatusEnum.DeviceConfiguring) {
|
} else if (status === DeviceStatusEnum.DeviceConfiguring) {
|
||||||
this.isConfigured = false;
|
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
|
* Gets specified channel information from the radio
|
||||||
*/
|
*/
|
||||||
@@ -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);
|
clearInterval(this._heartbeatIntervalId);
|
||||||
}
|
}
|
||||||
this._heartbeatIntervalId = setInterval(() => {
|
this._heartbeatIntervalId = setInterval(() => {
|
||||||
this.heartbeat();
|
this.heartbeat().catch((err) => {
|
||||||
|
this.log.error(
|
||||||
|
Emitter[Emitter.Ping],
|
||||||
|
`⚠️ Unable to send heartbeat: ${err.message}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
}, interval);
|
}, interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,12 @@ interface DebugLog {
|
|||||||
data: string;
|
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 {
|
export interface Transport {
|
||||||
toDevice: WritableStream<Uint8Array>;
|
toDevice: WritableStream<Uint8Array>;
|
||||||
@@ -101,6 +106,7 @@ export enum Emitter {
|
|||||||
RemoveNodeByNum = 32,
|
RemoveNodeByNum = 32,
|
||||||
SetCannedMessages = 33,
|
SetCannedMessages = 33,
|
||||||
Disconnect = 34,
|
Disconnect = 34,
|
||||||
|
ConnectionStatus = 35,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LogEvent {
|
export interface LogEvent {
|
||||||
|
|||||||
@@ -117,6 +117,14 @@ export class Queue {
|
|||||||
await writer.write(item.data);
|
await writer.write(item.data);
|
||||||
item.sent = true;
|
item.sent = true;
|
||||||
} catch (error) {
|
} 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);
|
console.error(`Error sending packet ${item.id}`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,20 +7,51 @@ export const decodePacket = (device: MeshDevice) =>
|
|||||||
new WritableStream<DeviceOutput>({
|
new WritableStream<DeviceOutput>({
|
||||||
write(chunk) {
|
write(chunk) {
|
||||||
switch (chunk.type) {
|
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": {
|
case "debug": {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "packet": {
|
case "packet": {
|
||||||
const decodedMessage = fromBinary(
|
let decodedMessage: Protobuf.Mesh.FromRadio;
|
||||||
Protobuf.Mesh.FromRadioSchema,
|
try {
|
||||||
chunk.data,
|
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);
|
device.events.onFromRadio.dispatch(decodedMessage);
|
||||||
|
|
||||||
/** @todo Add map here when `all=true` gets fixed. */
|
/** @todo Add map here when `all=true` gets fixed. */
|
||||||
switch (decodedMessage.payloadVariant.case) {
|
switch (decodedMessage.payloadVariant.case) {
|
||||||
case "packet": {
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export const fromDeviceStream: () => TransformStream<Uint8Array, DeviceOutput> =
|
|||||||
byteBuffer = new Uint8Array([...byteBuffer, ...chunk]);
|
byteBuffer = new Uint8Array([...byteBuffer, ...chunk]);
|
||||||
let processingExhausted = false;
|
let processingExhausted = false;
|
||||||
while (byteBuffer.length !== 0 && !processingExhausted) {
|
while (byteBuffer.length !== 0 && !processingExhausted) {
|
||||||
const framingIndex = byteBuffer.findIndex((byte) => byte === 0x94);
|
const framingIndex = byteBuffer.indexOf(0x94);
|
||||||
const framingByte2 = byteBuffer[framingIndex + 1];
|
const framingByte2 = byteBuffer[framingIndex + 1];
|
||||||
if (framingByte2 === 0xc3) {
|
if (framingByte2 === 0xc3) {
|
||||||
if (byteBuffer.subarray(0, framingIndex).length) {
|
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 packet = byteBuffer.subarray(4, 4 + (msb << 8) + lsb);
|
||||||
|
|
||||||
const malformedDetectorIndex = packet.findIndex(
|
const malformedDetectorIndex = packet.indexOf(0x94);
|
||||||
(byte) => byte === 0x94,
|
|
||||||
);
|
|
||||||
if (
|
if (
|
||||||
malformedDetectorIndex !== -1 &&
|
malformedDetectorIndex !== -1 &&
|
||||||
packet[malformedDetectorIndex + 1] === 0xc3
|
packet[malformedDetectorIndex + 1] === 0xc3
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
/**
|
/**
|
||||||
* Pads packets with appropriate framing information before writing to the output stream.
|
* Pads packets with appropriate framing information before writing to the output stream.
|
||||||
*/
|
*/
|
||||||
export const toDeviceStream: TransformStream<Uint8Array, Uint8Array> =
|
export const toDeviceStream: () => TransformStream<Uint8Array, Uint8Array> =
|
||||||
new TransformStream<Uint8Array, Uint8Array>({
|
() => {
|
||||||
transform(chunk: Uint8Array, controller): void {
|
return new TransformStream<Uint8Array, Uint8Array>({
|
||||||
const bufLen = chunk.length;
|
transform(chunk: Uint8Array, controller): void {
|
||||||
const header = new Uint8Array([
|
const bufLen = chunk.length;
|
||||||
0x94,
|
const header = new Uint8Array([
|
||||||
0xc3,
|
0x94,
|
||||||
(bufLen >> 8) & 0xff,
|
0xc3,
|
||||||
bufLen & 0xff,
|
(bufLen >> 8) & 0xff,
|
||||||
]);
|
bufLen & 0xff,
|
||||||
controller.enqueue(new Uint8Array([...header, ...chunk]));
|
]);
|
||||||
},
|
controller.enqueue(new Uint8Array([...header, ...chunk]));
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"extends": "../../tsconfig.json",
|
"extends": "../../tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
@@ -7,12 +7,7 @@
|
|||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"emitDeclarationOnly": false,
|
"emitDeclarationOnly": false,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true
|
||||||
},
|
},
|
||||||
"include": ["src"]
|
"include": ["src"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Submodule
+1
Submodule packages/packages/protobufs added at a1b8c3d171
@@ -0,0 +1,3 @@
|
|||||||
|
* text=auto eol=lf
|
||||||
|
*.{cmd,[cC][mM][dD]} text eol=crlf
|
||||||
|
*.{bat,[bB][aA][tT]} text eol=crlf
|
||||||
@@ -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
@@ -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
|
||||||
@@ -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 }}
|
||||||
@@ -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 }}
|
||||||
@@ -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
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
.DS_Store
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
[submodule "packages/protobufs"]
|
||||||
|
branch = master
|
||||||
+3
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["pbkit.vscode-pbkit", "bufbuild.vscode-buf"]
|
||||||
|
}
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.defaultFormatter": "pbkit.vscode-pbkit"
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|
||||||
|

|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
version: v2
|
||||||
|
plugins:
|
||||||
|
- remote: buf.build/bufbuild/es
|
||||||
|
out: packages/ts/dist
|
||||||
|
opt: target=ts
|
||||||
@@ -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
|
||||||
@@ -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"]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
*ChannelSet.settings max_count:8
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
*CannedMessageModuleConfig.messages max_size:201
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
|||||||
|
*WifiConnectionStatus.ssid max_size:33
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
*InterdeviceMessage.nmea max_size:1024
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
*RTTTLConfig.ringtone max_size:231
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
*StoreAndForward.text max_size:233
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|
||||||
|

|
||||||
@@ -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"]
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+384
@@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,18 +1,20 @@
|
|||||||
{
|
{
|
||||||
"name": "@meshtastic/transport-deno",
|
"name": "@meshtastic/transport-deno",
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
"description": "Deno-specific transport layer for Meshtastic web applications.",
|
"description": "Deno-specific transport layer for Meshtastic web applications.",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./mod.ts"
|
".": "./mod.ts"
|
||||||
},
|
},
|
||||||
"main": "./dist/mod.mjs",
|
"main": "./dist/mod.js",
|
||||||
"module": "./dist/mod.mjs",
|
"module": "./dist/mod.js",
|
||||||
"types": "./dist/mod.d.mts",
|
"types": "./dist/mod.d.ts",
|
||||||
"license": "GPL-3.0-only",
|
"license": "GPL-3.0-only",
|
||||||
"tsdown": {
|
"tsdown": {
|
||||||
"entry": "mod.ts",
|
"entry": "mod.ts",
|
||||||
"dts": true,
|
"dts": true,
|
||||||
"format": ["esm"],
|
"format": [
|
||||||
|
"esm"
|
||||||
|
],
|
||||||
"splitting": false,
|
"splitting": false,
|
||||||
"clean": true
|
"clean": true
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ await build({
|
|||||||
// package.json properties
|
// package.json properties
|
||||||
name: "@meshtastic/transport-deno",
|
name: "@meshtastic/transport-deno",
|
||||||
version: Deno.args[0],
|
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",
|
license: "GPL-3.0-only",
|
||||||
repository: {
|
repository: {
|
||||||
type: "git",
|
type: "git",
|
||||||
|
|||||||
@@ -16,9 +16,10 @@ export class TransportDeno implements Types.Transport {
|
|||||||
|
|
||||||
constructor(connection: Deno.Conn) {
|
constructor(connection: Deno.Conn) {
|
||||||
this.connection = connection;
|
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(
|
this._fromDevice = this.connection.readable.pipeThrough(
|
||||||
Utils.fromDeviceStream(),
|
Utils.fromDeviceStream(),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,28 +1,41 @@
|
|||||||
{
|
{
|
||||||
"name": "@meshtastic/transport-http",
|
"name": "@meshtastic/transport-http",
|
||||||
"version": "0.2.3-2",
|
"version": "0.2.5",
|
||||||
"description": "A transport layer for Meshtastic applications using HTTP.",
|
"description": "A transport layer for Meshtastic applications using HTTP.",
|
||||||
"exports": {".": "./mod.ts"},
|
"exports": {
|
||||||
|
".": "./mod.ts"
|
||||||
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"files": [
|
"files": [
|
||||||
"package.json",
|
"package.json",
|
||||||
"README.md",
|
"README.md",
|
||||||
"LICENSE",
|
"LICENSE",
|
||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
"main": "./dist/mod.mjs",
|
"main": "./dist/mod.js",
|
||||||
"module": "./dist/mod.mjs",
|
"module": "./dist/mod.js",
|
||||||
"types": "./dist/mod.d.mts",
|
"types": "./dist/mod.d.ts",
|
||||||
"license": "GPL-3.0-only",
|
"license": "GPL-3.0-only",
|
||||||
"tsdown": {
|
"tsdown": {
|
||||||
"entry": "mod.ts",
|
"entry": "mod.ts",
|
||||||
"dts": true,
|
"dts": true,
|
||||||
"format": ["esm"],
|
"format": [
|
||||||
|
"esm"
|
||||||
|
],
|
||||||
"splitting": false,
|
"splitting": false,
|
||||||
"clean": true
|
"clean": true
|
||||||
},
|
},
|
||||||
|
"jsrInclude": [
|
||||||
|
"mod.ts",
|
||||||
|
"src",
|
||||||
|
"README.md",
|
||||||
|
"LICENSE"
|
||||||
|
],
|
||||||
|
"jsrExclude": [
|
||||||
|
"src/**/*.test.ts"
|
||||||
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"preinstall": "npx only-allow pnpm",
|
"preinstall": "npx only-allow pnpm",
|
||||||
"prepack": "cp ../../LICENSE ./LICENSE",
|
"prepack": "cp ../../LICENSE ./LICENSE",
|
||||||
"clean": "rm -rf dist LICENSE",
|
"clean": "rm -rf dist LICENSE",
|
||||||
"build:npm": "tsdown",
|
"build:npm": "tsdown",
|
||||||
|
|||||||
@@ -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 {
|
export class TransportHTTP implements Types.Transport {
|
||||||
private _toDevice: WritableStream<Uint8Array>;
|
private _toDevice: WritableStream<Uint8Array>;
|
||||||
private _fromDevice: ReadableStream<Types.DeviceOutput>;
|
private _fromDevice: ReadableStream<Types.DeviceOutput>;
|
||||||
|
private fromDeviceController?: ReadableStreamDefaultController<Types.DeviceOutput>;
|
||||||
|
|
||||||
private url: string;
|
private url: string;
|
||||||
private receiveBatchRequests: boolean;
|
private receiveBatchRequests: boolean;
|
||||||
private fetchInterval: number;
|
private fetchInterval: number;
|
||||||
private fetching: boolean;
|
private fetching: boolean;
|
||||||
private interval: ReturnType<typeof setInterval> | undefined;
|
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(
|
public static async create(
|
||||||
address: string,
|
address: string,
|
||||||
tls?: boolean,
|
tls?: boolean,
|
||||||
@@ -17,97 +52,194 @@ export class TransportHTTP implements Types.Transport {
|
|||||||
await fetch(`${connectionUrl}/api/v1/toradio`, {
|
await fetch(`${connectionUrl}/api/v1/toradio`, {
|
||||||
method: "OPTIONS",
|
method: "OPTIONS",
|
||||||
});
|
});
|
||||||
await Promise.resolve();
|
|
||||||
return new TransportHTTP(connectionUrl);
|
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) {
|
constructor(url: string) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.receiveBatchRequests = false;
|
this.receiveBatchRequests = false;
|
||||||
this.fetchInterval = 3000;
|
this.fetchInterval = FETCH_INTERVAL_MS;
|
||||||
this.fetching = false;
|
this.fetching = false;
|
||||||
|
|
||||||
this._toDevice = new WritableStream<Uint8Array>({
|
this._toDevice = new WritableStream<Uint8Array>({
|
||||||
write: async (chunk) => {
|
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>({
|
this._fromDevice = new ReadableStream<Types.DeviceOutput>({
|
||||||
start: (ctrl) => {
|
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(
|
/** Poll `/api/v1/fromradio` and enqueue incoming packets. */
|
||||||
controller: ReadableStreamDefaultController<Types.DeviceOutput>,
|
private async readFromRadio(): Promise<void> {
|
||||||
): Promise<void> {
|
|
||||||
let readBuffer = new ArrayBuffer(1);
|
let readBuffer = new ArrayBuffer(1);
|
||||||
|
|
||||||
while (readBuffer.byteLength > 0) {
|
while (readBuffer.byteLength > 0) {
|
||||||
const response = await fetch(
|
const inflight = new AbortController();
|
||||||
`${this.url}/api/v1/fromradio?all=${
|
this.inflightReadController = inflight;
|
||||||
this.receiveBatchRequests ? "true" : "false"
|
|
||||||
}`,
|
const signal = AbortSignal.any([
|
||||||
{
|
inflight.signal,
|
||||||
method: "GET",
|
AbortSignal.timeout(READ_TIMEOUT_MS),
|
||||||
headers: {
|
]);
|
||||||
Accept: "application/x-protobuf",
|
|
||||||
|
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) {
|
readBuffer = await response.arrayBuffer();
|
||||||
controller.enqueue({
|
|
||||||
type: "packet",
|
if (readBuffer.byteLength > 0) {
|
||||||
data: new Uint8Array(readBuffer),
|
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> {
|
private async writeToRadio(data: Uint8Array): Promise<void> {
|
||||||
await fetch(`${this.url}/api/v1/toradio`, {
|
try {
|
||||||
method: "PUT",
|
const response = await fetch(`${this.url}/api/v1/toradio`, {
|
||||||
headers: {
|
method: "PUT",
|
||||||
"Content-Type": "application/x-protobuf",
|
headers: { "Content-Type": "application/x-protobuf" },
|
||||||
},
|
body: toArrayBuffer(data),
|
||||||
body: 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> {
|
get toDevice(): WritableStream<Uint8Array> {
|
||||||
return this._toDevice;
|
return this._toDevice;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Readable stream of {@link Types.DeviceOutput} from the device. */
|
||||||
get fromDevice(): ReadableStream<Types.DeviceOutput> {
|
get fromDevice(): ReadableStream<Types.DeviceOutput> {
|
||||||
return this._fromDevice;
|
return this._fromDevice;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop polling and emit `DeviceDisconnected("user")`.
|
||||||
|
*/
|
||||||
disconnect(): Promise<void> {
|
disconnect(): Promise<void> {
|
||||||
this.fetching = false;
|
this.closingByUser = true;
|
||||||
|
|
||||||
if (this.interval) {
|
if (this.interval) {
|
||||||
clearInterval(this.interval);
|
clearInterval(this.interval);
|
||||||
}
|
}
|
||||||
this.interval = undefined;
|
this.interval = undefined;
|
||||||
|
this.fetching = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.inflightReadController?.abort();
|
||||||
|
} catch {}
|
||||||
|
this.inflightReadController = undefined;
|
||||||
|
|
||||||
|
this.emitStatus(Types.DeviceStatusEnum.DeviceDisconnected, "user");
|
||||||
|
|
||||||
return Promise.resolve();
|
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",
|
"outDir": "./dist",
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"emitDeclarationOnly": false,
|
"emitDeclarationOnly": false,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true
|
||||||
},
|
},
|
||||||
"include": ["src"]
|
"include": ["src"]
|
||||||
}
|
}
|
||||||
@@ -1,34 +1,45 @@
|
|||||||
{
|
{
|
||||||
"name": "@meshtastic/transport-node-serial",
|
"name": "@meshtastic/transport-node-serial",
|
||||||
"version": "0.0.1",
|
"version": "0.0.2",
|
||||||
"description": "NodeJS-specific serial transport layer for Meshtastic web applications.",
|
"description": "NodeJS-specific serial transport layer for Meshtastic web applications.",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./dist/mod.mjs"
|
".": "./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
|
|
||||||
},
|
},
|
||||||
|
"type": "module",
|
||||||
|
"main": "./dist/mod.js",
|
||||||
|
"module": "./dist/mod.js",
|
||||||
|
"types": "./dist/mod.d.ts",
|
||||||
"files": [
|
"files": [
|
||||||
"package.json",
|
"package.json",
|
||||||
"README.md",
|
"README.md",
|
||||||
"LICENSE",
|
"LICENSE",
|
||||||
"dist"
|
"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": {
|
"scripts": {
|
||||||
"preinstall": "npx only-allow pnpm",
|
"preinstall": "npx only-allow pnpm",
|
||||||
"prepack": "cp ../../LICENSE ./LICENSE",
|
"prepack": "cp ../../LICENSE ./LICENSE",
|
||||||
"clean": "rm -rf dist LICENSE",
|
"clean": "rm -rf dist LICENSE",
|
||||||
"build:npm": "tsdown",
|
"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",
|
"prepare:jsr": "rm -rf dist && pnpm dlx pkg-to-jsr",
|
||||||
"publish:jsr": "pnpm run prepack && pnpm prepare:jsr && deno publish --allow-dirty --no-check"
|
"publish:jsr": "pnpm run prepack && pnpm prepare:jsr && deno publish --allow-dirty --no-check"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,20 +1,35 @@
|
|||||||
import { Readable, Writable } from "node:stream";
|
import { Readable, Writable } from "node:stream";
|
||||||
import type { Types } from "@meshtastic/core";
|
import { Types, Utils } from "@meshtastic/core";
|
||||||
import { Utils } from "@meshtastic/core";
|
|
||||||
import { SerialPort } from "serialport";
|
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 {
|
export class TransportNodeSerial implements Types.Transport {
|
||||||
private readonly _toDevice: WritableStream<Uint8Array>;
|
private readonly _toDevice: WritableStream<Uint8Array>;
|
||||||
private readonly _fromDevice: ReadableStream<Types.DeviceOutput>;
|
private readonly _fromDevice: ReadableStream<Types.DeviceOutput>;
|
||||||
|
private fromDeviceController?: ReadableStreamDefaultController<Types.DeviceOutput>;
|
||||||
private port: SerialPort | undefined;
|
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.
|
* Creates and connects a new TransportNode instance.
|
||||||
* @param path - Path to the serial device
|
* @param path - Path to the serial device
|
||||||
* @param baudRate - The port number for the TCP connection (defaults to 4403).
|
* @param baudRate - Baud rate for the serial connection (default is 115200).
|
||||||
* @returns A promise that resolves with a connected TransportNode instance.
|
* @returns A promise that resolves with a connected TransportNode instance.
|
||||||
*/
|
*/
|
||||||
public static create(path: string, baudRate = 115200): Promise<TransportNodeSerial> {
|
public static create(
|
||||||
|
path: string,
|
||||||
|
baudRate = 115200,
|
||||||
|
): Promise<TransportNodeSerial> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const port = new SerialPort({
|
const port = new SerialPort({
|
||||||
path,
|
path,
|
||||||
@@ -43,25 +58,79 @@ export class TransportNodeSerial implements Types.Transport {
|
|||||||
this.port = port;
|
this.port = port;
|
||||||
this.port.on("error", (err) => {
|
this.port.on("error", (err) => {
|
||||||
console.error("Serial port connection 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(
|
const fromDeviceSource = Readable.toWeb(port) as ReadableStream<Uint8Array>;
|
||||||
port,
|
const transformed = fromDeviceSource.pipeThrough(Utils.fromDeviceStream());
|
||||||
) as ReadableStream<Uint8Array>;
|
|
||||||
this._fromDevice = 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.
|
// Stream for data going FROM the application TO the Meshtastic device.
|
||||||
const toDeviceTransform = Utils.toDeviceStream;
|
const toDeviceTransform = Utils.toDeviceStream();
|
||||||
this._toDevice = toDeviceTransform.writable;
|
this._toDevice = toDeviceTransform.writable;
|
||||||
|
|
||||||
// The readable end of the transform is then piped to the Node.js SerialPort connection.
|
this.pipePromise = toDeviceTransform.readable
|
||||||
// A similar assertion is needed here because `Writable.toWeb` also returns
|
.pipeTo(Writable.toWeb(port) as WritableStream<Uint8Array>, {
|
||||||
// a generically typed stream (`WritableStream<any>`).
|
signal: controller.signal,
|
||||||
toDeviceTransform.readable
|
})
|
||||||
.pipeTo(Writable.toWeb(port) as WritableStream<Uint8Array>)
|
.catch((error) => {
|
||||||
.catch((err) => {
|
if (controller.signal.aborted || this.closingByUser) {
|
||||||
console.error("Error piping data to serial port:", err);
|
return;
|
||||||
this.port.close(err as Error);
|
}
|
||||||
|
console.error("Error piping data to serial port:", error);
|
||||||
|
this.emitStatus(
|
||||||
|
Types.DeviceStatusEnum.DeviceDisconnected,
|
||||||
|
"write-error",
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
this.port?.close();
|
||||||
|
} catch {}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,9 +148,35 @@ export class TransportNodeSerial implements Types.Transport {
|
|||||||
return this._fromDevice;
|
return this._fromDevice;
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnect() {
|
/**
|
||||||
this.port.close();
|
* Disconnect from the serial port and emit `DeviceDisconnected("user")`.
|
||||||
this.port = undefined;
|
* Safe to call multiple times.
|
||||||
return Promise.resolve();
|
*/
|
||||||
|
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 },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"extends": "../../tsconfig.json",
|
"extends": "../../tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
@@ -7,12 +7,7 @@
|
|||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"emitDeclarationOnly": false,
|
"emitDeclarationOnly": false,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true
|
||||||
},
|
},
|
||||||
"include": ["src"]
|
"include": ["src"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +1,42 @@
|
|||||||
{
|
{
|
||||||
"name": "@meshtastic/transport-node",
|
"name": "@meshtastic/transport-node",
|
||||||
"version": "0.0.1-1",
|
"version": "0.0.2",
|
||||||
"description": "NodeJS-specific transport layer for Meshtastic web applications.",
|
"description": "NodeJS-specific transport layer for Meshtastic web applications.",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./mod.ts"
|
".": "./mod.ts"
|
||||||
},
|
|
||||||
"type": "module",
|
|
||||||
"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
|
|
||||||
},
|
},
|
||||||
|
"type": "module",
|
||||||
"files": [
|
"files": [
|
||||||
"package.json",
|
"package.json",
|
||||||
"README.md",
|
"README.md",
|
||||||
"LICENSE",
|
"LICENSE",
|
||||||
"dist"
|
"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": {
|
"scripts": {
|
||||||
"preinstall": "npx only-allow pnpm",
|
"preinstall": "npx only-allow pnpm",
|
||||||
"prepack": "cp ../../LICENSE ./LICENSE",
|
"prepack": "cp ../../LICENSE ./LICENSE",
|
||||||
"clean": "rm -rf dist LICENSE",
|
"clean": "rm -rf dist LICENSE",
|
||||||
"build:npm": "tsdown",
|
"build:npm": "tsdown",
|
||||||
|
|||||||
@@ -0,0 +1,185 @@
|
|||||||
|
import type { Socket } from "node:net";
|
||||||
|
import { Duplex } from "node:stream";
|
||||||
|
import { Types, Utils } from "@meshtastic/core";
|
||||||
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import { runTransportContract } from "../../../tests/utils/transportContract.ts";
|
||||||
|
import { TransportNode } from "./transport.ts";
|
||||||
|
|
||||||
|
function isStatusEvent(
|
||||||
|
out: Types.DeviceOutput | undefined,
|
||||||
|
): out is Extract<Types.DeviceOutput, { type: "status" }> {
|
||||||
|
return !!out && (out as any).type === "status";
|
||||||
|
}
|
||||||
|
|
||||||
|
class FakeSocket 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 error") {
|
||||||
|
this.emit("error", new Error(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
emitClose() {
|
||||||
|
this.emit("close");
|
||||||
|
}
|
||||||
|
|
||||||
|
override destroy(error?: Error) {
|
||||||
|
super.destroy(error);
|
||||||
|
this.emit("close");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
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("TransportNode (contract)", () => {
|
||||||
|
let transformsStub: { restore: () => void } | undefined;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
transformsStub = stubCoreTransforms();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
transformsStub?.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
runTransportContract({
|
||||||
|
name: "TransportNode",
|
||||||
|
setup: () => {},
|
||||||
|
teardown: () => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
},
|
||||||
|
create: async () => {
|
||||||
|
const fakeSocket = new FakeSocket();
|
||||||
|
const transport = new TransportNode(fakeSocket as unknown as Socket);
|
||||||
|
await Promise.resolve();
|
||||||
|
(globalThis as unknown as { __nodeSock: FakeSocket }).__nodeSock =
|
||||||
|
fakeSocket;
|
||||||
|
return transport;
|
||||||
|
},
|
||||||
|
pushIncoming: async (bytes) => {
|
||||||
|
(
|
||||||
|
globalThis as unknown as { __nodeSock: FakeSocket }
|
||||||
|
).__nodeSock.pushIncoming(bytes);
|
||||||
|
await Promise.resolve();
|
||||||
|
},
|
||||||
|
assertLastWritten: (bytes) => {
|
||||||
|
const sock = (globalThis as unknown as { __nodeSock: FakeSocket })
|
||||||
|
.__nodeSock;
|
||||||
|
expect(sock.lastWritten).toBeDefined();
|
||||||
|
expect(sock.lastWritten).toEqual(bytes);
|
||||||
|
},
|
||||||
|
triggerDisconnect: async () => {
|
||||||
|
(
|
||||||
|
globalThis as unknown as { __nodeSock: FakeSocket }
|
||||||
|
).__nodeSock.emitErrorOnce("test-disconnect");
|
||||||
|
await Promise.resolve();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("TransportNode (extras)", () => {
|
||||||
|
let transformsStub: { restore: () => void } | undefined;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
transformsStub = stubCoreTransforms();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
transformsStub?.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("emits DeviceDisconnected with reason 'socket-closed' on close event", async () => {
|
||||||
|
const fakeSocket = new FakeSocket();
|
||||||
|
const transport = new TransportNode(fakeSocket as unknown as Socket);
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fakeSocket.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 === "socket-closed") {
|
||||||
|
sawClosed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect(sawClosed).toBe(true);
|
||||||
|
|
||||||
|
reader.releaseLock();
|
||||||
|
await transport.disconnect();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,34 +1,55 @@
|
|||||||
import { Socket } from "node:net";
|
import { Socket } from "node:net";
|
||||||
import { Readable, Writable } from "node:stream";
|
import { Readable, Writable } from "node:stream";
|
||||||
import type { Types } from "@meshtastic/core";
|
import { Types, Utils } from "@meshtastic/core";
|
||||||
import { Utils } from "@meshtastic/core";
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Node.js TCP transport for Meshtastic.
|
||||||
|
*
|
||||||
|
* Implements {@link Types.Transport} on top of a Node `net.Socket`.
|
||||||
|
* Use {@link TransportNode.create} to open a new connection, or
|
||||||
|
* construct directly with an existing socket.
|
||||||
|
*/
|
||||||
export class TransportNode implements Types.Transport {
|
export class TransportNode implements Types.Transport {
|
||||||
private readonly _toDevice: WritableStream<Uint8Array>;
|
private readonly _toDevice: WritableStream<Uint8Array>;
|
||||||
private readonly _fromDevice: ReadableStream<Types.DeviceOutput>;
|
private readonly _fromDevice: ReadableStream<Types.DeviceOutput>;
|
||||||
|
private fromDeviceController?: ReadableStreamDefaultController<Types.DeviceOutput>;
|
||||||
private socket: Socket | undefined;
|
private socket: Socket | undefined;
|
||||||
|
private pipePromise?: Promise<void>;
|
||||||
|
private abortController: AbortController;
|
||||||
|
private lastStatus: Types.DeviceStatusEnum =
|
||||||
|
Types.DeviceStatusEnum.DeviceDisconnected;
|
||||||
|
|
||||||
|
private closingByUser = false;
|
||||||
|
private errored = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates and connects a new TransportNode instance.
|
* Creates and connects a new TransportNode instance.
|
||||||
* @param hostname - The IP address or hostname of the Meshtastic device.
|
* @param hostname - The IP address or hostname of the Meshtastic device.
|
||||||
* @param port - The port number for the TCP connection (defaults to 4403).
|
* @param port - The port number for the TCP connection (defaults to 4403).
|
||||||
|
* @param timeout - TCP socket timeout in milliseconds (defaults to 60000).
|
||||||
* @returns A promise that resolves with a connected TransportNode instance.
|
* @returns A promise that resolves with a connected TransportNode instance.
|
||||||
*/
|
*/
|
||||||
public static create(hostname: string, port = 4403): Promise<TransportNode> {
|
public static create(
|
||||||
|
hostname: string,
|
||||||
|
port = 4403,
|
||||||
|
timeout = 60000,
|
||||||
|
): Promise<TransportNode> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const socket = new Socket();
|
const socket = new Socket();
|
||||||
|
|
||||||
const onError = (err: Error) => {
|
const onError = (err: Error) => {
|
||||||
socket.destroy();
|
socket.destroy();
|
||||||
|
socket.removeAllListeners();
|
||||||
reject(err);
|
reject(err);
|
||||||
};
|
};
|
||||||
|
|
||||||
socket.once("error", onError);
|
socket.once("error", onError);
|
||||||
|
socket.once("ready", () => {
|
||||||
socket.connect(port, hostname, () => {
|
|
||||||
socket.removeListener("error", onError);
|
socket.removeListener("error", onError);
|
||||||
resolve(new TransportNode(socket));
|
resolve(new TransportNode(socket));
|
||||||
});
|
});
|
||||||
|
socket.setTimeout(timeout);
|
||||||
|
socket.connect(port, hostname);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,47 +59,154 @@ export class TransportNode implements Types.Transport {
|
|||||||
*/
|
*/
|
||||||
constructor(connection: Socket) {
|
constructor(connection: Socket) {
|
||||||
this.socket = connection;
|
this.socket = connection;
|
||||||
this.socket.on("error", (err) => {
|
|
||||||
console.error("Socket connection error:", err);
|
this.socket.on("error", () => {
|
||||||
|
this.errored = true;
|
||||||
|
this.socket?.removeAllListeners();
|
||||||
|
this.socket?.destroy();
|
||||||
|
if (!this.closingByUser) {
|
||||||
|
this.emitStatus(
|
||||||
|
Types.DeviceStatusEnum.DeviceDisconnected,
|
||||||
|
"socket-error",
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.socket.on("end", () => {
|
||||||
|
if (this.closingByUser) {
|
||||||
|
return; // suppress close-derived disconnect in user flow
|
||||||
|
}
|
||||||
|
this.emitStatus(Types.DeviceStatusEnum.DeviceDisconnected, "socket-end");
|
||||||
|
this.socket?.removeAllListeners();
|
||||||
|
this.socket?.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.socket.on("timeout", () => {
|
||||||
|
this.emitStatus(
|
||||||
|
Types.DeviceStatusEnum.DeviceDisconnected,
|
||||||
|
"socket-timeout",
|
||||||
|
);
|
||||||
|
this.socket?.removeAllListeners();
|
||||||
|
this.socket?.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.socket.on("close", () => {
|
||||||
|
if (this.closingByUser) {
|
||||||
|
return; // suppress close-derived disconnect in user flow
|
||||||
|
}
|
||||||
|
this.emitStatus(
|
||||||
|
Types.DeviceStatusEnum.DeviceDisconnected,
|
||||||
|
"socket-closed",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.abortController = new AbortController();
|
||||||
|
const abortController = this.abortController;
|
||||||
|
|
||||||
const fromDeviceSource = Readable.toWeb(
|
const fromDeviceSource = Readable.toWeb(
|
||||||
connection,
|
connection,
|
||||||
) as ReadableStream<Uint8Array>;
|
) as ReadableStream<Uint8Array>;
|
||||||
this._fromDevice = fromDeviceSource.pipeThrough(Utils.fromDeviceStream());
|
const transformed = fromDeviceSource.pipeThrough(Utils.fromDeviceStream());
|
||||||
|
|
||||||
|
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 || this.errored) {
|
||||||
|
ctrl.close();
|
||||||
|
} 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.
|
// Stream for data going FROM the application TO the Meshtastic device.
|
||||||
const toDeviceTransform = Utils.toDeviceStream;
|
const toDeviceTransform = Utils.toDeviceStream();
|
||||||
this._toDevice = toDeviceTransform.writable;
|
this._toDevice = toDeviceTransform.writable;
|
||||||
|
|
||||||
// The readable end of the transform is then piped to the Node.js socket.
|
this.pipePromise = toDeviceTransform.readable
|
||||||
// A similar assertion is needed here because `Writable.toWeb` also returns
|
.pipeTo(Writable.toWeb(connection) as WritableStream<Uint8Array>, {
|
||||||
// a generically typed stream (`WritableStream<any>`).
|
signal: abortController.signal,
|
||||||
toDeviceTransform.readable
|
})
|
||||||
.pipeTo(Writable.toWeb(connection) as WritableStream<Uint8Array>)
|
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error("Error piping data to socket:", err);
|
if (abortController.signal.aborted || this.socket?.destroyed) {
|
||||||
this.socket.destroy(err as Error);
|
return;
|
||||||
|
}
|
||||||
|
const error = err instanceof Error ? err : new Error(String(err));
|
||||||
|
this.socket?.destroy(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** WritableStream to send data to the device. */
|
||||||
* The WritableStream to send data to the Meshtastic device.
|
|
||||||
*/
|
|
||||||
public get toDevice(): WritableStream<Uint8Array> {
|
public get toDevice(): WritableStream<Uint8Array> {
|
||||||
return this._toDevice;
|
return this._toDevice;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** ReadableStream to receive data from the device. */
|
||||||
* The ReadableStream to receive data from the Meshtastic device.
|
|
||||||
*/
|
|
||||||
public get fromDevice(): ReadableStream<Types.DeviceOutput> {
|
public get fromDevice(): ReadableStream<Types.DeviceOutput> {
|
||||||
return this._fromDevice;
|
return this._fromDevice;
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnect() {
|
/**
|
||||||
this.socket.destroy();
|
* Disconnect from the TCP socket and emit `DeviceDisconnected("user")`.
|
||||||
this.socket = undefined;
|
* Safe to call multiple times.
|
||||||
return Promise.resolve();
|
*/
|
||||||
|
async disconnect(): Promise<void> {
|
||||||
|
try {
|
||||||
|
this.closingByUser = true;
|
||||||
|
this.emitStatus(Types.DeviceStatusEnum.DeviceDisconnected, "user");
|
||||||
|
|
||||||
|
this.abortController.abort();
|
||||||
|
if (this.pipePromise) {
|
||||||
|
await this.pipePromise;
|
||||||
|
}
|
||||||
|
this.socket?.destroy();
|
||||||
|
} finally {
|
||||||
|
this.socket = undefined;
|
||||||
|
this.closingByUser = false;
|
||||||
|
this.errored = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private emitStatus(next: Types.DeviceStatusEnum, reason?: string): void {
|
||||||
|
if (next === this.lastStatus) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.lastStatus = next;
|
||||||
|
try {
|
||||||
|
this.fromDeviceController?.enqueue({
|
||||||
|
type: "status",
|
||||||
|
data: { status: next, reason },
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Enqueue fail", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"extends": "../../tsconfig.json",
|
"extends": "../../tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
@@ -7,12 +7,7 @@
|
|||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"emitDeclarationOnly": false,
|
"emitDeclarationOnly": false,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true
|
||||||
},
|
},
|
||||||
"include": ["src"]
|
"include": ["src"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"name": "@meshtastic/transport-web-bluetooth",
|
"name": "@meshtastic/transport-web-bluetooth",
|
||||||
"version": "0.1.4-1",
|
"version": "0.1.5",
|
||||||
"description": "A transport layer for Meshtastic applications using Web Bluetooth.",
|
"description": "A transport layer for Meshtastic applications using Web Bluetooth.",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./mod.ts"
|
".": "./mod.ts"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/mod.mjs",
|
"main": "./dist/mod.js",
|
||||||
"module": "./dist/mod.mjs",
|
"module": "./dist/mod.js",
|
||||||
"types": "./dist/mod.d.mts",
|
"types": "./dist/mod.d.ts",
|
||||||
"files": [
|
"files": [
|
||||||
"package.json",
|
"package.json",
|
||||||
"README.md",
|
"README.md",
|
||||||
@@ -19,12 +19,23 @@
|
|||||||
"tsdown": {
|
"tsdown": {
|
||||||
"entry": "mod.ts",
|
"entry": "mod.ts",
|
||||||
"dts": true,
|
"dts": true,
|
||||||
"format": ["esm"],
|
"format": [
|
||||||
|
"esm"
|
||||||
|
],
|
||||||
"splitting": false,
|
"splitting": false,
|
||||||
"clean": true
|
"clean": true
|
||||||
},
|
},
|
||||||
|
"jsrInclude": [
|
||||||
|
"mod.ts",
|
||||||
|
"src",
|
||||||
|
"README.md",
|
||||||
|
"LICENSE"
|
||||||
|
],
|
||||||
|
"jsrExclude": [
|
||||||
|
"src/**/*.test.ts"
|
||||||
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"preinstall": "npx only-allow pnpm",
|
"preinstall": "npx only-allow pnpm",
|
||||||
"prepack": "cp ../../LICENSE ./LICENSE",
|
"prepack": "cp ../../LICENSE ./LICENSE",
|
||||||
"clean": "rm -rf dist LICENSE",
|
"clean": "rm -rf dist LICENSE",
|
||||||
"build:npm": "tsdown",
|
"build:npm": "tsdown",
|
||||||
|
|||||||
@@ -0,0 +1,201 @@
|
|||||||
|
import { describe, expect, vi } from "vitest";
|
||||||
|
import { runTransportContract } from "../../../tests/utils/transportContract.ts";
|
||||||
|
import { TransportWebBluetooth } from "./transport.ts";
|
||||||
|
|
||||||
|
class MiniEmitter {
|
||||||
|
private listeners = new Map<string, Set<(e: Event) => void>>();
|
||||||
|
addEventListener(type: string, listener: (e: Event) => void) {
|
||||||
|
if (!this.listeners.has(type)) {
|
||||||
|
this.listeners.set(type, new Set());
|
||||||
|
}
|
||||||
|
this.listeners.get(type)!.add(listener);
|
||||||
|
}
|
||||||
|
removeEventListener(type: string, listener: (e: Event) => void) {
|
||||||
|
this.listeners.get(type)?.delete(listener);
|
||||||
|
}
|
||||||
|
dispatchEvent(event: Event) {
|
||||||
|
this.listeners.get(event.type)?.forEach((l) => {
|
||||||
|
l(event);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function stubWebBluetooth() {
|
||||||
|
const incomingQueue: Uint8Array[] = [];
|
||||||
|
let lastWritten: Uint8Array | undefined;
|
||||||
|
|
||||||
|
// fromRadioCharacteristic: read bytes from queue, one buffer per read
|
||||||
|
const fromRadioCharacteristic: BluetoothRemoteGATTCharacteristic = {
|
||||||
|
async readValue() {
|
||||||
|
const next = incomingQueue.shift() ?? new Uint8Array();
|
||||||
|
return new DataView(
|
||||||
|
next.buffer,
|
||||||
|
next.byteOffset,
|
||||||
|
next.byteLength,
|
||||||
|
) as unknown as DataView;
|
||||||
|
},
|
||||||
|
addEventListener() {},
|
||||||
|
removeEventListener() {},
|
||||||
|
} as unknown as BluetoothRemoteGATTCharacteristic;
|
||||||
|
|
||||||
|
// characteristicvaluechanged event plumbing (fromNumCharacteristic)
|
||||||
|
const charEmitter = new MiniEmitter();
|
||||||
|
|
||||||
|
const fromNumCharacteristic: BluetoothRemoteGATTCharacteristic = {
|
||||||
|
async startNotifications() {
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
addEventListener(type: string, listener: (e: Event) => void) {
|
||||||
|
charEmitter.addEventListener(type, listener);
|
||||||
|
},
|
||||||
|
removeEventListener(type: string, listener: (e: Event) => void) {
|
||||||
|
charEmitter.removeEventListener(type, listener);
|
||||||
|
},
|
||||||
|
} as unknown as BluetoothRemoteGATTCharacteristic;
|
||||||
|
|
||||||
|
const toRadioCharacteristic: BluetoothRemoteGATTCharacteristic = {
|
||||||
|
async writeValue(bufferSource: BufferSource) {
|
||||||
|
const u8 =
|
||||||
|
bufferSource instanceof ArrayBuffer
|
||||||
|
? new Uint8Array(bufferSource)
|
||||||
|
: new Uint8Array(
|
||||||
|
bufferSource.buffer,
|
||||||
|
bufferSource.byteOffset,
|
||||||
|
bufferSource.byteLength,
|
||||||
|
);
|
||||||
|
lastWritten = new Uint8Array(u8);
|
||||||
|
},
|
||||||
|
} as unknown as BluetoothRemoteGATTCharacteristic;
|
||||||
|
|
||||||
|
// Primary service returns our three characteristics by UUID
|
||||||
|
const primaryService: BluetoothRemoteGATTService = {
|
||||||
|
async getCharacteristic(uuid: string) {
|
||||||
|
if (uuid === TransportWebBluetooth.ToRadioUuid) {
|
||||||
|
return toRadioCharacteristic;
|
||||||
|
}
|
||||||
|
if (uuid === TransportWebBluetooth.FromRadioUuid) {
|
||||||
|
return fromRadioCharacteristic;
|
||||||
|
}
|
||||||
|
if (uuid === TransportWebBluetooth.FromNumUuid) {
|
||||||
|
return fromNumCharacteristic;
|
||||||
|
}
|
||||||
|
throw new Error(`Unknown characteristic: ${uuid}`);
|
||||||
|
},
|
||||||
|
} as unknown as BluetoothRemoteGATTService;
|
||||||
|
|
||||||
|
// Device-level emitter to deliver gattserverdisconnected
|
||||||
|
const deviceEmitter = new MiniEmitter();
|
||||||
|
|
||||||
|
// GATT server with readonly connected
|
||||||
|
let isConnected = true;
|
||||||
|
const gattServer: BluetoothRemoteGATTServer = {
|
||||||
|
get connected() {
|
||||||
|
return isConnected;
|
||||||
|
},
|
||||||
|
async connect() {
|
||||||
|
isConnected = true;
|
||||||
|
return gattServer;
|
||||||
|
},
|
||||||
|
disconnect() {
|
||||||
|
isConnected = false;
|
||||||
|
deviceEmitter.dispatchEvent(new Event("gattserverdisconnected"));
|
||||||
|
},
|
||||||
|
async getPrimaryService() {
|
||||||
|
return primaryService;
|
||||||
|
},
|
||||||
|
device: {
|
||||||
|
addEventListener: (
|
||||||
|
...args: Parameters<EventTarget["addEventListener"]>
|
||||||
|
) =>
|
||||||
|
deviceEmitter.addEventListener(
|
||||||
|
args[0] as string,
|
||||||
|
args[1] as (e: Event) => void,
|
||||||
|
),
|
||||||
|
removeEventListener: (
|
||||||
|
...args: Parameters<EventTarget["removeEventListener"]>
|
||||||
|
) =>
|
||||||
|
deviceEmitter.removeEventListener(
|
||||||
|
args[0] as string,
|
||||||
|
args[1] as (e: Event) => void,
|
||||||
|
),
|
||||||
|
} as unknown as BluetoothDevice,
|
||||||
|
} as unknown as BluetoothRemoteGATTServer;
|
||||||
|
|
||||||
|
const fakeDevice: BluetoothDevice = {
|
||||||
|
async watchAdvertisements() {},
|
||||||
|
gatt: gattServer,
|
||||||
|
} as unknown as BluetoothDevice;
|
||||||
|
|
||||||
|
const fakeNavigator = {
|
||||||
|
bluetooth: {
|
||||||
|
async requestDevice() {
|
||||||
|
return fakeDevice;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
vi.stubGlobal(
|
||||||
|
"navigator",
|
||||||
|
Object.assign({}, globalThis.navigator, fakeNavigator),
|
||||||
|
);
|
||||||
|
|
||||||
|
// helper actions for tests/contract
|
||||||
|
return {
|
||||||
|
pushIncoming: (u8: Uint8Array) => {
|
||||||
|
incomingQueue.push(u8);
|
||||||
|
charEmitter.dispatchEvent(new Event("characteristicvaluechanged"));
|
||||||
|
},
|
||||||
|
assertLastWritten: (u8: Uint8Array) => {
|
||||||
|
expect(lastWritten).toBeDefined();
|
||||||
|
expect(lastWritten).toEqual(u8);
|
||||||
|
},
|
||||||
|
// simulate underlying link drop (OS-level disconnect)
|
||||||
|
triggerGattDisconnect: () => {
|
||||||
|
isConnected = false;
|
||||||
|
deviceEmitter.dispatchEvent(new Event("gattserverdisconnected"));
|
||||||
|
},
|
||||||
|
cleanup: () => {
|
||||||
|
vi.unstubAllGlobals();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("TransportWebBluetooth (contract)", () => {
|
||||||
|
runTransportContract({
|
||||||
|
name: "TransportWebBluetooth",
|
||||||
|
setup: () => {},
|
||||||
|
teardown: () => {
|
||||||
|
(
|
||||||
|
globalThis as unknown as { __ble?: ReturnType<typeof stubWebBluetooth> }
|
||||||
|
).__ble?.cleanup();
|
||||||
|
(
|
||||||
|
globalThis as unknown as { __ble?: ReturnType<typeof stubWebBluetooth> }
|
||||||
|
).__ble = undefined;
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
vi.unstubAllGlobals();
|
||||||
|
},
|
||||||
|
create: async () => {
|
||||||
|
(
|
||||||
|
globalThis as unknown as { __ble: ReturnType<typeof stubWebBluetooth> }
|
||||||
|
).__ble = stubWebBluetooth();
|
||||||
|
return await TransportWebBluetooth.create();
|
||||||
|
},
|
||||||
|
pushIncoming: async (bytes) => {
|
||||||
|
(
|
||||||
|
globalThis as unknown as { __ble: ReturnType<typeof stubWebBluetooth> }
|
||||||
|
).__ble.pushIncoming(bytes);
|
||||||
|
await Promise.resolve();
|
||||||
|
},
|
||||||
|
assertLastWritten: (bytes) => {
|
||||||
|
(
|
||||||
|
globalThis as unknown as { __ble: ReturnType<typeof stubWebBluetooth> }
|
||||||
|
).__ble.assertLastWritten(bytes);
|
||||||
|
},
|
||||||
|
triggerDisconnect: async () => {
|
||||||
|
(
|
||||||
|
globalThis as unknown as { __ble: ReturnType<typeof stubWebBluetooth> }
|
||||||
|
).__ble.triggerGattDisconnect();
|
||||||
|
await Promise.resolve();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user