Compare commits

..

No commits in common. "9e48a99318cc17caa06b07d10cf5bf9912ad8d73" and "b1ee0677890ffd8f7cc9cf4cc38ca8acfa638afb" have entirely different histories.

8 changed files with 302 additions and 667 deletions

243
Cargo.lock generated
View File

@ -133,19 +133,6 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "async-compat"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1ba85bc55464dcbf728b56d97e119d673f4cf9062be330a9a26f3acf504a590"
dependencies = [
"futures-core",
"futures-io",
"once_cell",
"pin-project-lite",
"tokio",
]
[[package]]
name = "async-executor"
version = "1.14.0"
@ -540,7 +527,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
dependencies = [
"generic-array",
"rand_core 0.6.4",
"rand_core",
"subtle",
"zeroize",
]
@ -580,18 +567,8 @@ version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858"
dependencies = [
"darling_core 0.10.2",
"darling_macro 0.10.2",
]
[[package]]
name = "darling"
version = "0.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0"
dependencies = [
"darling_core 0.21.3",
"darling_macro 0.21.3",
"darling_core",
"darling_macro",
]
[[package]]
@ -608,41 +585,17 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "darling_core"
version = "0.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"syn 2.0.117",
]
[[package]]
name = "darling_macro"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
dependencies = [
"darling_core 0.10.2",
"darling_core",
"quote",
"syn 1.0.109",
]
[[package]]
name = "darling_macro"
version = "0.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81"
dependencies = [
"darling_core 0.21.3",
"quote",
"syn 2.0.117",
]
[[package]]
name = "defmt"
version = "0.3.100"
@ -745,7 +698,7 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2658621297f2cf68762a6f7dc0bb7e1ff2cfd6583daef8ee0fed6f7ec468ec0"
dependencies = [
"darling 0.10.2",
"darling",
"derive_builder_core",
"proc-macro2",
"quote",
@ -758,7 +711,7 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2791ea3e372c8495c0bc2033991d76b512cd799d07491fbd6890124db9458bef"
dependencies = [
"darling 0.10.2",
"darling",
"proc-macro2",
"quote",
"syn 1.0.109",
@ -796,29 +749,6 @@ dependencies = [
"time",
]
[[package]]
name = "domain"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f7ff15f82df7d5086fb15dfc1c1e96598a6ded9829840a9bcfa1fa3ccd8d01d"
dependencies = [
"domain-macros",
"heapless 0.8.0",
"octseq",
"time",
]
[[package]]
name = "domain-macros"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d1a6796ad411f6812d691955066ad27450196bfb181bb91b66a643cc3e8f5b7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
]
[[package]]
name = "ecdsa"
version = "0.16.9"
@ -832,47 +762,6 @@ dependencies = [
"signature",
]
[[package]]
name = "edge-mdns"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73e6e268ceceab96a31eeefb7fc4afbb8f005313de23944a0ecb10a20f382193"
dependencies = [
"domain 0.11.1",
"edge-nal",
"embassy-futures",
"embassy-sync",
"embassy-time",
"heapless 0.9.3",
"log",
"rand_core 0.9.5",
]
[[package]]
name = "edge-nal"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3c7d7163586cb9d457a34561a644aa957ce870226729bf6c9c8beeaead7e0d8"
dependencies = [
"embassy-time",
"embedded-io-async 0.7.0",
]
[[package]]
name = "edge-nal-std"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4af3f481df81463ca9a1bdb5adda6ecb956b1e4efacb29dab1835194479cdbc8"
dependencies = [
"async-io",
"edge-nal",
"embedded-io-async 0.7.0",
"futures-lite",
"heapless 0.9.3",
"libc",
"log",
]
[[package]]
name = "either"
version = "1.15.0"
@ -892,7 +781,7 @@ dependencies = [
"generic-array",
"group",
"hkdf",
"rand_core 0.6.4",
"rand_core",
"sec1",
"subtle",
"zeroize",
@ -918,7 +807,7 @@ checksum = "73974a3edbd0bd286759b3d483540f0ebef705919a5f56f4fc7709066f71689b"
dependencies = [
"cfg-if",
"critical-section",
"embedded-io-async 0.6.1",
"embedded-io-async",
"futures-core",
"futures-sink",
"heapless 0.8.0",
@ -992,28 +881,13 @@ version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
[[package]]
name = "embedded-io"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9eb1aa714776b75c7e67e1da744b81a129b3ff919c8712b5e1b32252c1f07cc7"
[[package]]
name = "embedded-io-async"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f"
dependencies = [
"embedded-io 0.6.1",
]
[[package]]
name = "embedded-io-async"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2564b9f813c544241430e147d8bc454815ef9ac998878d30cc3055449f7fd4c0"
dependencies = [
"embedded-io 0.7.1",
"embedded-io",
]
[[package]]
@ -1043,27 +917,6 @@ dependencies = [
"syn 2.0.117",
]
[[package]]
name = "enumset"
version = "1.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "839c4174b41e75c8f7306110b2c51996a293b8d1d850edd529011841d9fede7d"
dependencies = [
"enumset_derive",
]
[[package]]
name = "enumset_derive"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bd536557b58c682b217b8fb199afdff47cd3eff260623f19e77074eb073d63a"
dependencies = [
"darling 0.21.3",
"proc-macro2",
"quote",
"syn 2.0.117",
]
[[package]]
name = "env_filter"
version = "1.0.1"
@ -1136,7 +989,7 @@ version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393"
dependencies = [
"rand_core 0.6.4",
"rand_core",
"subtle",
]
@ -1261,7 +1114,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
dependencies = [
"ff",
"rand_core 0.6.4",
"rand_core",
"subtle",
]
@ -2024,8 +1877,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
dependencies = [
"libc",
"rand_chacha 0.3.1",
"rand_core 0.6.4",
"rand_chacha",
"rand_core",
]
[[package]]
@ -2035,17 +1888,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core 0.6.4",
]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core 0.9.5",
"rand_core",
]
[[package]]
@ -2057,12 +1900,6 @@ dependencies = [
"getrandom 0.2.17",
]
[[package]]
name = "rand_core"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
[[package]]
name = "regex"
version = "1.12.3"
@ -2105,7 +1942,7 @@ dependencies = [
[[package]]
name = "rs-matter"
version = "0.1.1"
source = "git+https://git.fishmt.net/nakajima/rs-matter?branch=bluez-ios-reconnect#e44e9e3debaf74e18f827ca163daf6fe03c7410f"
source = "git+https://git.fishmt.net/nakajima/rs-matter?branch=bluez-ios-reconnect#fe15e0cd698e49969abf42329966a70ffdfb8576"
dependencies = [
"aes",
"async-channel",
@ -2119,7 +1956,7 @@ dependencies = [
"defmt 0.3.100",
"der",
"digest",
"domain 0.10.4",
"domain",
"ecdsa",
"either",
"elliptic-curve",
@ -2144,7 +1981,7 @@ dependencies = [
"primeorder",
"qrcodegen-no-heap",
"rand",
"rand_core 0.6.4",
"rand_core",
"rs-matter-codegen",
"rs-matter-macros",
"scopeguard",
@ -2167,7 +2004,7 @@ dependencies = [
[[package]]
name = "rs-matter-codegen"
version = "0.1.0"
source = "git+https://git.fishmt.net/nakajima/rs-matter?branch=bluez-ios-reconnect#e44e9e3debaf74e18f827ca163daf6fe03c7410f"
source = "git+https://git.fishmt.net/nakajima/rs-matter?branch=bluez-ios-reconnect#fe15e0cd698e49969abf42329966a70ffdfb8576"
dependencies = [
"convert_case",
"miette",
@ -2186,7 +2023,7 @@ dependencies = [
[[package]]
name = "rs-matter-macros"
version = "0.1.0"
source = "git+https://git.fishmt.net/nakajima/rs-matter?branch=bluez-ios-reconnect#e44e9e3debaf74e18f827ca163daf6fe03c7410f"
source = "git+https://git.fishmt.net/nakajima/rs-matter?branch=bluez-ios-reconnect#fe15e0cd698e49969abf42329966a70ffdfb8576"
dependencies = [
"proc-macro-crate",
"proc-macro2",
@ -2194,29 +2031,6 @@ dependencies = [
"syn 2.0.117",
]
[[package]]
name = "rs-matter-stack"
version = "0.1.4"
source = "git+https://git.fishmt.net/nakajima/rs-matter-stack?branch=master#0a022bb2ac686707c3f5a84a790b1c3430b500ce"
dependencies = [
"bitflags 2.11.1",
"cfg-if",
"edge-mdns",
"edge-nal",
"edge-nal-std",
"embassy-futures",
"embassy-sync",
"embassy-time",
"enumset",
"heapless 0.8.0",
"log",
"rand_chacha 0.9.0",
"rand_core 0.6.4",
"rand_core 0.9.5",
"rs-matter",
"scopeguard",
]
[[package]]
name = "rustc-demangle"
version = "0.1.27"
@ -2385,7 +2199,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
dependencies = [
"digest",
"rand_core 0.6.4",
"rand_core",
]
[[package]]
@ -2410,15 +2224,6 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "static_cell"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0530892bb4fa575ee0da4b86f86c667132a94b74bb72160f58ee5a4afec74c23"
dependencies = [
"portable-atomic",
]
[[package]]
name = "strsim"
version = "0.9.3"
@ -2589,17 +2394,17 @@ checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
[[package]]
name = "toes-matter"
version = "0.1.17"
version = "0.1.12"
dependencies = [
"async-compat",
"async-io",
"embassy-futures",
"embassy-time",
"embassy-time-queue-utils",
"env_logger",
"futures-lite",
"log",
"rand",
"rs-matter",
"rs-matter-stack",
"static_cell",
]
[[package]]

View File

@ -1,6 +1,6 @@
[package]
name = "toes-matter"
version = "0.1.17"
version = "0.1.12"
edition = "2021"
license = "MIT OR Apache-2.0"
readme = "README.md"
@ -20,13 +20,13 @@ required-features = ["device"]
[features]
default = ["device"]
device = [
"dep:async-compat",
"dep:async-io",
"dep:embassy-futures",
"dep:embassy-time",
"dep:embassy-time-queue-utils",
"dep:env_logger",
"dep:futures-lite",
"dep:log",
"dep:rs-matter-stack",
"dep:static_cell",
"rs-matter/log",
"rs-matter/os",
"rs-matter/rustcrypto",
@ -35,28 +35,17 @@ device = [
"rs-matter/max-groups-per-fabric-12",
"rs-matter/max-group-keys-per-fabric-2",
"rs-matter/max-group-endpoints-per-fabric-3",
"rs-matter-stack/log",
"rs-matter-stack/os",
"rs-matter-stack/std",
"rs-matter-stack/zbus",
"rs-matter-stack/max-sessions-32",
"rs-matter-stack/max-groups-per-fabric-12",
"rs-matter-stack/max-group-keys-per-fabric-2",
"rs-matter-stack/max-group-endpoints-per-fabric-3",
"zeroconf",
]
zeroconf = ["rs-matter/zeroconf", "rs-matter-stack/zeroconf"]
zeroconf = ["rs-matter/zeroconf"]
[dependencies]
async-compat = { version = "0.2", optional = true }
async-io = { version = "2", optional = true }
embassy-futures = { version = "0.1", optional = true }
embassy-time = { version = "0.5", features = ["std"], optional = true }
embassy-time-queue-utils = { version = "0.3", features = ["generic-queue-64"], optional = true }
env_logger = { version = "0.11", optional = true }
futures-lite = { version = "2", optional = true }
log = { version = "0.4", optional = true }
rand = { version = "0.8", features = ["std", "std_rng"] }
rs-matter = { version = "0.1", default-features = false }
rs-matter-stack = { git = "https://git.fishmt.net/nakajima/rs-matter-stack", branch = "master", default-features = false, optional = true }
static_cell = { version = "2.1", optional = true }
[patch.crates-io]
rs-matter = { git = "https://git.fishmt.net/nakajima/rs-matter", branch = "bluez-ios-reconnect" }
rs-matter = { git = "https://git.fishmt.net/nakajima/rs-matter", branch = "bluez-ios-reconnect", default-features = false }

View File

@ -1,24 +1,23 @@
# toes-matter
Tiny facade around `rs-matter-stack` for the Toes RGB light flow:
Tiny facade around `rs-matter` for the Toes RGB light flow:
```rust
#![recursion_limit = "256"]
use core::pin::pin;
fn main() -> toes_matter::Result<()> {
env_logger::init_from_env(
env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"),
);
let runtime = pin!(toes_matter::run());
futures_lite::future::block_on(async_compat::Compat::new(runtime))
futures_lite::future::block_on(async {
toes_matter::generate_credentials("./creds").await?;
toes_matter::provision().await?;
toes_matter::listen().await
})
}
```
`toes_matter::run()` handles both first-boot BLE/Wi-Fi commissioning and normal operational Matter traffic. The older `provision()` and `listen()` functions are kept as compatibility aliases for the same combined runtime.
## Runtime assumptions
- Linux + BlueZ on system D-Bus

View File

@ -1,14 +1,13 @@
#![recursion_limit = "256"]
use core::pin::pin;
fn main() -> toes_matter::Result<()> {
env_logger::init_from_env(
env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"),
);
futures_lite::future::block_on(toes_matter::generate_credentials("./creds"))?;
let runtime = pin!(toes_matter::run());
futures_lite::future::block_on(async_compat::Compat::new(runtime))
futures_lite::future::block_on(async {
toes_matter::generate_credentials("./creds").await?;
toes_matter::provision().await?;
toes_matter::listen().await
})
}

View File

@ -1,229 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
IMG="$HOME/Documents/toes-image-builder/toes-ubuntu-base.img"
USBBOOT="$HOME/apps/forks/usbboot"
PROVISION_SCRIPT="${PROVISION_SCRIPT:-$SCRIPT_DIR/scripts/provision-device.sh}"
PROVISION_DEVICE_ID="${PROVISION_DEVICE_ID:-${DEVICE_ID:-device-001}}"
DEFAULT_SERIAL_LOGIN="toes"
DEFAULT_SERIAL_PASSWORD="nicetrywiseguy"
REBOOT_SETTLE_SECS="${REBOOT_SETTLE_SECS:-45}"
SERIAL_BOOT_TIMEOUT_SECS="${SERIAL_BOOT_TIMEOUT_SECS:-180}"
list_external_disks() {
diskutil list external physical 2>/dev/null | awk '/^\/dev\/disk[0-9]+ / { sub("^/dev/", "", $1); print $1 }' | sort || true
}
disk_bytes() {
diskutil info "/dev/$1" | awk -F'[()]' '/Disk Size:/ { sub(/ Bytes.*/, "", $2); print $2; exit }'
}
detect_serial_port() {
local pattern port
local -a matches
matches=()
for pattern in /dev/cu.usbserial\* /dev/cu.usbmodem\*; do
for port in $pattern; do
[[ -e "$port" ]] || continue
matches+=("$port")
done
done
if (( ${#matches[@]} == 1 )); then
printf '%s\n' "${matches[0]}"
return 0
fi
if (( ${#matches[@]} > 1 )); then
echo "multiple serial ports detected; set SERIAL_PORT to one of:" >&2
printf ' %s\n' "${matches[@]}" >&2
return 2
fi
matches=()
for pattern in /dev/tty.usbserial\* /dev/tty.usbmodem\*; do
for port in $pattern; do
[[ -e "$port" ]] || continue
matches+=("$port")
done
done
if (( ${#matches[@]} == 1 )); then
printf '%s\n' "${matches[0]}"
return 0
fi
if (( ${#matches[@]} > 1 )); then
echo "multiple serial ports detected; set SERIAL_PORT to one of:" >&2
printf ' %s\n' "${matches[@]}" >&2
return 2
fi
return 1
}
resolve_serial_port() {
local port status
if [[ -n "${SERIAL_PORT:-}" ]]; then
[[ -e "$SERIAL_PORT" ]] || { echo "serial port does not exist: $SERIAL_PORT" >&2; return 1; }
printf '%s\n' "$SERIAL_PORT"
return 0
fi
if port="$(detect_serial_port)"; then
printf '%s\n' "$port"
return 0
fi
status=$?
if (( status == 1 )); then
echo "no serial port detected; set SERIAL_PORT=/dev/cu.*" >&2
fi
return 1
}
wait_for_serial_port() {
local timeout="$1"
local deadline=$((SECONDS + timeout))
local port status
if [[ -n "${SERIAL_PORT:-}" ]]; then
while (( SECONDS < deadline )); do
[[ -e "$SERIAL_PORT" ]] && { printf '%s\n' "$SERIAL_PORT"; return 0; }
sleep 1
done
echo "timed out waiting for serial port: $SERIAL_PORT" >&2
return 1
fi
while (( SECONDS < deadline )); do
if port="$(detect_serial_port 2>/dev/null)"; then
printf '%s\n' "$port"
return 0
fi
status=$?
if (( status == 2 )); then
detect_serial_port >/dev/null || true
return 1
fi
sleep 1
done
echo "timed out waiting for serial port; set SERIAL_PORT=/dev/cu.*" >&2
return 1
}
stop_rpiboot_mass_storage() {
pkill -f 'rpiboot.*mass-storage-gadget' 2>/dev/null || true
}
reboot_target_over_serial() {
local port="$1"
echo "Requesting target reboot over $port"
stty -f "$port" 115200 raw -echo clocal 2>/dev/null || true
{
sleep 0.2
printf '\r'
sleep 0.5
printf 'root\r'
sleep 0.5
printf '\r'
sleep 0.5
printf 'sync\r'
sleep 0.5
printf 'reboot -f\r'
} >"$port"
}
provision_device() {
local port="$1"
local provision_login="${SERIAL_LOGIN:-$DEFAULT_SERIAL_LOGIN}"
local provision_password="${SERIAL_PASSWORD:-$DEFAULT_SERIAL_PASSWORD}"
local provision_prompt="${SERIAL_PROMPT:-${provision_login}@}"
[[ -x "$PROVISION_SCRIPT" ]] || { echo "provision script is not executable: $PROVISION_SCRIPT" >&2; return 1; }
echo "Provisioning $PROVISION_DEVICE_ID over $port"
SERIAL_PORT="$port" \
SERIAL_LOGIN="$provision_login" \
SERIAL_PASSWORD="$provision_password" \
SERIAL_PROMPT="$provision_prompt" \
SERIAL_LOGIN_TIMEOUT_SECS="${SERIAL_LOGIN_TIMEOUT_SECS:-$SERIAL_BOOT_TIMEOUT_SECS}" \
"$PROVISION_SCRIPT" "$PROVISION_DEVICE_ID"
}
if [[ "${SKIP_BUILD:-0}" != 1 ]]; then
ssh toes-image-builder 'cd toes-image-builder && ./build.sh && cp build/toes-ubuntu-base.img /mnt/utm/toes-image-builder/'
fi
[[ -f "$IMG" ]] || { echo "missing $IMG" >&2; exit 1; }
target="${DISK:-}"
if [[ -n "$target" ]]; then
target="${target#/dev/}"
target="${target#r}"
else
before="$(mktemp)"
after="$(mktemp)"
trap 'rm -f "$before" "$after"' EXIT
list_external_disks >"$before"
sudo "$USBBOOT/rpiboot" -d "$USBBOOT/mass-storage-gadget64"
for _ in {1..30}; do
list_external_disks >"$after"
new="$(comm -13 "$before" "$after")"
[[ -n "$new" ]] && { sleep 2; list_external_disks >"$after"; new="$(comm -13 "$before" "$after")"; break; }
sleep 1
done
[[ -n "${new:-}" ]] || { echo "no new disks" >&2; exit 1; }
echo "New disks:"
target_size=0
for disk in $new; do
bytes="$(disk_bytes "$disk")"
echo " /dev/$disk ${bytes:-?} bytes"
[[ "$bytes" =~ ^[0-9]+$ ]] && (( bytes > target_size )) && { target="$disk"; target_size="$bytes"; }
done
fi
[[ "${target:-}" =~ ^disk[0-9]+$ ]] || { echo "bad disk: ${target:-}" >&2; exit 1; }
target_size="$(disk_bytes "$target")"
image_size="$(stat -f %z "$IMG")"
[[ "$target_size" =~ ^[0-9]+$ ]] || { echo "could not size /dev/$target" >&2; exit 1; }
(( target_size >= image_size )) || { echo "/dev/$target is smaller than $IMG" >&2; exit 1; }
echo "Selected /dev/$target"
echo "Image: $IMG"
diskutil info "/dev/$target"
printf 'Type FLASH to erase /dev/%s: ' "$target"
read -r answer
[[ "$answer" == FLASH ]] || { echo "aborted"; exit 1; }
diskutil unmountDisk "/dev/$target"
sudo dd if="$IMG" of="/dev/r$target" bs=16m status=progress conv=fsync
sync
diskutil eject "/dev/$target"
if [[ "${SKIP_REBOOT:-0}" != 1 ]]; then
reboot_port="$(resolve_serial_port)"
stop_rpiboot_mass_storage
reboot_target_over_serial "$reboot_port"
echo "Waiting ${REBOOT_SETTLE_SECS}s for target to start booting..."
sleep "$REBOOT_SETTLE_SECS"
else
echo "Skipping target reboot because SKIP_REBOOT=1"
fi
if [[ "${SKIP_PROVISION:-0}" != 1 ]]; then
provision_port="$(wait_for_serial_port "$SERIAL_BOOT_TIMEOUT_SECS")"
provision_device "$provision_port"
else
echo "Skipping provisioning because SKIP_PROVISION=1"
fi

View File

@ -110,10 +110,6 @@ impl Options {
while let Some(arg) = args.next() {
match arg.as_str() {
"-V" | "--version" => {
println!("toes-matter-credentials {}", env!("CARGO_PKG_VERSION"));
std::process::exit(0);
}
"-h" | "--help" => {
print_usage();
std::process::exit(0);
@ -195,7 +191,6 @@ fn print_usage() {
--password PASS Serial login password [env: SERIAL_PASSWORD]\n\
--prompt PROMPT Shell prompt marker; may repeat [env: SERIAL_PROMPT]\n\
--login-timeout-secs N Serial login timeout [default: 30, env: SERIAL_LOGIN_TIMEOUT_SECS]\n\
-V, --version Show version\n\
-h, --help Show this help"
);
}

View File

@ -1,20 +1,12 @@
#![recursion_limit = "256"]
use core::pin::pin;
fn main() -> toes_matter::Result<()> {
if std::env::args()
.skip(1)
.any(|arg| matches!(arg.as_str(), "-V" | "--version"))
{
println!("toes-matter {}", env!("CARGO_PKG_VERSION"));
return Ok(());
}
env_logger::init_from_env(
env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"),
);
let runtime = pin!(toes_matter::run());
futures_lite::future::block_on(async_compat::Compat::new(runtime))
futures_lite::future::block_on(async {
toes_matter::provision().await?;
toes_matter::listen().await
})
}

View File

@ -5,64 +5,70 @@
use core::cell::RefCell;
use core::pin::pin;
use std::net::UdpSocket;
use std::path::{Path, PathBuf};
use std::process::Command;
use embassy_futures::select::{select, select4};
use log::{info, warn};
use rand::RngCore;
use static_cell::StaticCell;
use rs_matter_stack::ble::GattPeripheral;
use rs_matter_stack::matter as rs_matter;
use rs_matter_stack::matter::crypto::{default_crypto, Crypto};
use rs_matter_stack::matter::dm::clusters::app::level_control::LevelControlHooks;
use rs_matter_stack::matter::dm::clusters::app::on_off::OnOffHooks;
use rs_matter_stack::matter::dm::clusters::app::{level_control, on_off};
use rs_matter_stack::matter::dm::clusters::decl::color_control;
use rs_matter_stack::matter::dm::clusters::decl::color_control::ClusterAsyncHandler as ColorControlHandler;
use rs_matter_stack::matter::dm::clusters::desc::{ClusterHandler as _, DescHandler};
use rs_matter_stack::matter::dm::clusters::dev_att::DeviceAttestation;
use rs_matter_stack::matter::dm::clusters::gen_diag::{NetifDiag, NetifInfo};
use rs_matter_stack::matter::dm::clusters::net_comm::{
NetCtl, NetCtlError, NetworkScanInfo, NetworkType, ThreadCapabilitiesBitmap, WiFiBandEnum,
WirelessCreds,
use rs_matter::crypto::{default_crypto, Crypto};
use rs_matter::dm::clusters::app::level_control::LevelControlHooks;
use rs_matter::dm::clusters::app::on_off::OnOffHooks;
use rs_matter::dm::clusters::app::{level_control, on_off};
use rs_matter::dm::clusters::decl::color_control;
use rs_matter::dm::clusters::decl::color_control::ClusterAsyncHandler as ColorControlHandler;
use rs_matter::dm::clusters::desc::{ClusterHandler as _, DescHandler};
use rs_matter::dm::clusters::dev_att::DeviceAttestation;
use rs_matter::dm::clusters::net_comm::{
NetCtl, NetCtlError, NetworkScanInfo, NetworkType, SharedNetworks, ThreadCapabilitiesBitmap,
WiFiBandEnum, WirelessCreds,
};
use rs_matter_stack::matter::dm::clusters::wifi_diag::{
use rs_matter::dm::clusters::wifi_diag::{
SecurityTypeEnum, WiFiVersionEnum, WifiDiag, WirelessDiag,
};
use rs_matter_stack::matter::dm::devices::test::{
DAC_PRIVKEY, TEST_DEV_ATT, TEST_DEV_COMM, TEST_DEV_DET,
use rs_matter::dm::devices::test::{DAC_PRIVKEY, TEST_DEV_ATT, TEST_DEV_COMM, TEST_DEV_DET};
use rs_matter::dm::endpoints;
use rs_matter::dm::events::Events;
use rs_matter::dm::networks::unix::UnixNetifs;
use rs_matter::dm::networks::wireless::{NetCtlState, NetCtlWithStatusImpl, WifiNetworks};
use rs_matter::dm::networks::NetChangeNotif;
use rs_matter::dm::subscriptions::Subscriptions;
use rs_matter::dm::{
Async, Cluster, DataModel, Dataver, EmptyHandler, Endpoint, EpClMatcher, Node,
};
use rs_matter_stack::matter::dm::networks::unix::UnixNetifs;
use rs_matter_stack::matter::dm::networks::NetChangeNotif;
use rs_matter_stack::matter::dm::{
Async, Cluster, Dataver, EmptyHandler, Endpoint, EpClMatcher, Node,
use rs_matter::error::{Error, ErrorCode};
use rs_matter::pairing::qr::{no_optional_data, CommFlowType, QrPayload, QrTextType};
use rs_matter::pairing::DiscoveryCapabilities;
use rs_matter::persist::{DirKvBlobStore, SharedKvBlobStore};
use rs_matter::respond::DefaultResponder;
use rs_matter::sc::pase::{
Spake2pVerifierPassword, Spake2pVerifierPasswordRef, MAX_COMM_WINDOW_TIMEOUT_SECS,
};
use rs_matter_stack::matter::error::{Error, ErrorCode};
use rs_matter_stack::matter::pairing::qr::{no_optional_data, CommFlowType, QrPayload};
use rs_matter_stack::matter::pairing::DiscoveryCapabilities;
use rs_matter_stack::matter::persist::DirKvBlobStore;
use rs_matter_stack::matter::sc::pase::{Spake2pVerifierPassword, Spake2pVerifierPasswordRef};
use rs_matter_stack::matter::tlv::Nullable;
use rs_matter_stack::matter::transport::network::btp::{AdvData, Btp};
use rs_matter_stack::matter::transport::network::mdns::zeroconf::ZeroconfMdnsResponder;
use rs_matter_stack::matter::transport::network::wifi::wpa_supp::unix::DhClientCtl;
use rs_matter_stack::matter::transport::network::wifi::wpa_supp::WpaSuppCtl;
use rs_matter_stack::matter::utils::init::InitMaybeUninit;
use rs_matter_stack::matter::utils::sync::blocking::Mutex;
use rs_matter_stack::matter::utils::sync::DynBase;
use rs_matter_stack::matter::utils::zbus::Connection;
use rs_matter_stack::matter::{clusters, devices, with, BasicCommData};
use rs_matter_stack::nal::std::Stack as StdNetStack;
use rs_matter_stack::wireless::{PreexistingWireless, WifiMatterStack};
use rs_matter::tlv::Nullable;
use rs_matter::transport::network::btp::bluez;
use rs_matter::transport::network::btp::{AdvData, Btp};
use rs_matter::transport::network::mdns::zeroconf::ZeroconfMdnsResponder;
use rs_matter::transport::network::wifi::wpa_supp::unix::DhClientCtl;
use rs_matter::transport::network::wifi::wpa_supp::WpaSuppCtl;
use rs_matter::transport::network::{Address, ChainedNetwork};
use rs_matter::transport::MATTER_SOCKET_BIND_ADDR;
use rs_matter::utils::select::Coalesce;
use rs_matter::utils::storage::pooled::PooledBuffers;
use rs_matter::utils::sync::blocking::Mutex;
use rs_matter::utils::sync::DynBase;
use rs_matter::utils::zbus::Connection;
use rs_matter::{clusters, devices, root_endpoint, with, BasicCommData, Matter, MATTER_PORT};
pub type Result<T> = core::result::Result<T, Error>;
const DEFAULT_CREDS_DIR: &str = "./creds";
const DEFAULT_WIFI_IFACE: &str = "wlan0";
const LIGHT_ENDPOINT_ID: u16 = 1;
const KV_BUF_SIZE: usize = 4096;
const BLE_SERVICE_NAME: &str = "TOES";
const BUMP_SIZE: usize = 48_000;
const COMMISSIONING_HANDOFF_SECS: u64 = 20;
const DEV_TYPE_EXTENDED_COLOR_LIGHT: rs_matter::dm::DeviceType = rs_matter::dm::DeviceType {
dtype: 0x010d,
@ -71,7 +77,7 @@ const DEV_TYPE_EXTENDED_COLOR_LIGHT: rs_matter::dm::DeviceType = rs_matter::dm::
const NODE: Node = Node {
endpoints: &[
WifiMatterStack::<0, ()>::root_endpoint(),
root_endpoint!(wifi),
Endpoint {
id: LIGHT_ENDPOINT_ID,
device_types: devices!(DEV_TYPE_EXTENDED_COLOR_LIGHT),
@ -85,9 +91,7 @@ const NODE: Node = Node {
],
};
static MATTER_STACK: StaticCell<WifiMatterStack<BUMP_SIZE, ()>> = StaticCell::new();
/// Runtime configuration used by [`run_with_config`].
/// Runtime configuration used by [`provision_with_config`] and [`listen_with_config`].
#[derive(Clone, Debug)]
pub struct Config {
pub creds_dir: PathBuf,
@ -242,58 +246,79 @@ fn valid_setup_passcode(passcode: u32) -> bool {
(1..=99_999_998).contains(&passcode) && !INVALID.contains(&passcode)
}
/// Run the combined rs-matter-stack runtime.
/// Provision this device over BLE Matter commissioning and Wi-Fi Network Commissioning.
///
/// This runtime handles both first-boot BLE/Wi-Fi commissioning and normal
/// operational Matter traffic. It runs until the process is stopped or an
/// unrecoverable transport error is returned.
pub async fn run() -> Result<()> {
run_with_config(Config::from_env()).await
}
pub async fn run_with_config(config: Config) -> Result<()> {
run_stack(config).await
}
/// Legacy entrypoint kept for callers that used the earlier split API.
///
/// With `rs-matter-stack`, provisioning and listening are one runtime, so this
/// does not return after commissioning; it continues serving Matter traffic.
/// Returns immediately if a fabric is already stored in the configured state dir.
/// Otherwise advertises over BlueZ, receives Wi-Fi credentials, connects the Wi-Fi
/// interface via wpa_supplicant, persists Matter state, then returns.
pub async fn provision() -> Result<()> {
info!("Starting combined Matter provisioning/listening runtime");
run().await
provision_with_config(Config::from_env()).await
}
pub async fn provision_with_config(config: Config) -> Result<()> {
info!("Starting combined Matter provisioning/listening runtime");
run_with_config(config).await
std::fs::create_dir_all(&config.state_dir)?;
let connection = Connection::system().await?;
let net_ctl = PersistingWifiCtl::new(
WpaSuppCtl::new(
&connection,
&config.wifi_iface,
DhClientCtl::new(&config.wifi_iface, true),
),
&config.wifi_iface,
);
run_provision(&connection, net_ctl, &config).await
}
/// Listen forever for Matter commands, opening BLE commissioning when needed.
/// Listen forever for Matter commands on the provisioned Wi-Fi/IP network.
///
/// RGB commands are applied by invoking `rgbled --order <RGBLED_ORDER|grb> <rrggbb>`.
pub async fn listen() -> Result<()> {
run().await
listen_with_config(Config::from_env()).await
}
pub async fn listen_with_config(config: Config) -> Result<()> {
run_with_config(config).await
}
async fn run_stack(config: Config) -> Result<()> {
std::fs::create_dir_all(&config.state_dir)?;
let comm = load_comm_data(&config)?;
let stack = MATTER_STACK
.uninit()
.init_with(WifiMatterStack::init_default(
&TEST_DEV_DET,
comm,
&TEST_DEV_ATT,
));
let connection = Connection::system().await?;
let net_ctl = PersistingWifiCtl::new(
WpaSuppCtl::new(
&connection,
&config.wifi_iface,
DhClientCtl::new(&config.wifi_iface, true),
),
&config.wifi_iface,
);
run_listen(net_ctl, &config).await
}
async fn run_provision<N>(connection: &Connection, net_ctl: N, config: &Config) -> Result<()>
where
N: NetCtl + WifiDiag + NetChangeNotif,
{
let comm = load_comm_data(config)?;
let mut matter = Matter::new_default(&TEST_DEV_DET, comm.clone(), &TEST_DEV_ATT, MATTER_PORT);
let mut networks = WifiNetworks::<3>::new();
let mut events: Events = Events::new_default();
let mut kv_buf = [0_u8; KV_BUF_SIZE];
let mut kv = DirKvBlobStore::new(config.state_dir.clone());
matter.load_persist(&mut kv, &mut kv_buf).await?;
networks.load_persist(&mut kv, &mut kv_buf).await?;
events.load_persist(&mut kv, &mut kv_buf).await?;
if matter.is_commissioned() {
info!("Matter fabric already exists; skipping BLE provisioning");
return Ok(());
}
let networks = SharedNetworks::new(networks);
let buffers = PooledBuffers::<10, _>::new(0);
let subscriptions: Subscriptions = Subscriptions::new();
let crypto = default_crypto(rand::thread_rng(), DAC_PRIVKEY);
let mut rand = crypto.weak_rand()?;
let mut rand = crypto.rand()?;
let rgb_light = RgbLight::new(Dataver::new_rand(&mut rand), config.rgbled_order.clone());
let on_off =
@ -308,43 +333,159 @@ async fn run_stack(config: Config) -> Result<()> {
on_off.init(Some(&level));
level.init(Some(&on_off));
let net_ctl_state = NetCtlState::new_with_mutex();
let net_ctl = NetCtlWithStatusImpl::new(&net_ctl_state, net_ctl);
let handler = rgb_handler(&mut rand, &rgb_light, &on_off, &level);
let mut kv = DirKvBlobStore::new(config.state_dir.clone());
stack.startup(&crypto, &mut kv).await?;
let kv = stack.create_shared_kv(kv)?;
let connection = Connection::system().await?;
let net_ctl = PersistingWifiCtl::new(
WpaSuppCtl::new(
&connection,
&config.wifi_iface,
DhClientCtl::new(&config.wifi_iface, true),
),
&config.wifi_iface,
);
info!(
"Matter Network Commissioning will manage Wi-Fi interface {}",
config.wifi_iface
);
info!("Running Matter via rs-matter-stack with BLE name {BLE_SERVICE_NAME}");
let matter = pin!(stack.run_coex(
PreexistingWireless::new(
StdNetStack::new(),
PreferredNetifs::new(&config.wifi_iface),
net_ctl,
ZeroconfMdnsResponder::new(),
ToesBluezGattPeripheral::new(&connection, None),
),
let dm = DataModel::new(
&matter,
&crypto,
(NODE, handler),
&kv,
(),
));
&buffers,
&subscriptions,
&events,
(
NODE,
endpoints::with_wifi_sys(&true, &(), &UnixNetifs, &net_ctl, &net_ctl, rand, handler),
),
SharedKvBlobStore::new(kv, kv_buf.as_mut_slice()),
&networks,
);
matter.await
let responder = DefaultResponder::new(&dm);
let mut respond = pin!(responder.run::<4, 4>());
let mut dm_job = pin!(dm.run());
matter.print_standard_qr_text(DiscoveryCapabilities::BLE)?;
matter.print_standard_qr_code(QrTextType::Unicode, DiscoveryCapabilities::BLE)?;
dm.open_basic_comm_window(MAX_COMM_WINDOW_TIMEOUT_SECS)?;
let btp = Btp::new();
let udp = async_io::Async::<UdpSocket>::bind(MATTER_SOCKET_BIND_ADDR)?;
let mut mdns_responder = ZeroconfMdnsResponder::new();
let mut mdns = pin!(mdns_responder.run(&matter));
let adv_data = AdvData::new(&TEST_DEV_DET, comm.discriminator);
let mut bluetooth = pin!(bluez::run_peripheral(
connection,
None,
BLE_SERVICE_NAME,
&adv_data,
&btp,
));
let mut transport = pin!(matter.run(
&crypto,
ChainedNetwork::new(Address::is_udp, &udp, &btp),
ChainedNetwork::new(Address::is_udp, &udp, &btp),
&udp,
));
info!("Running Matter over concurrent BLE commissioning and IP transport");
let mut commissioning_done_task = pin!(async {
NetCtlState::wait_prov_ready(&net_ctl_state, &btp).await;
info!("Wi-Fi credentials accepted; waiting for Matter fabric setup");
while !matter.is_commissioned() {
embassy_time::Timer::after_secs(1).await;
}
info!(
"Matter fabric added; keeping BLE/IP commissioning transports alive for {}s handoff",
COMMISSIONING_HANDOFF_SECS
);
embassy_time::Timer::after_secs(COMMISSIONING_HANDOFF_SECS).await;
Ok(())
});
let mut matter_tasks = pin!(select4(
&mut transport,
&mut bluetooth,
&mut mdns,
select(&mut respond, &mut dm_job).coalesce(),
)
.coalesce());
select(&mut matter_tasks, &mut commissioning_done_task)
.coalesce()
.await?;
matter.reset_transport()?;
info!("Matter provisioning completed; call toes_matter::listen().await next");
Ok(())
}
async fn run_listen<N>(net_ctl: N, config: &Config) -> Result<()>
where
N: NetCtl + WifiDiag + NetChangeNotif,
{
let comm = load_comm_data(config)?;
let mut matter = Matter::new_default(&TEST_DEV_DET, comm, &TEST_DEV_ATT, MATTER_PORT);
let mut networks = WifiNetworks::<3>::new();
let mut events: Events = Events::new_default();
let mut kv_buf = [0_u8; KV_BUF_SIZE];
let mut kv = DirKvBlobStore::new(config.state_dir.clone());
matter.load_persist(&mut kv, &mut kv_buf).await?;
networks.load_persist(&mut kv, &mut kv_buf).await?;
events.load_persist(&mut kv, &mut kv_buf).await?;
if !matter.is_commissioned() {
warn!("Matter state is not commissioned; run toes_matter::provision().await first");
return Err(ErrorCode::InvalidState.into());
}
let networks = SharedNetworks::new(networks);
let buffers = PooledBuffers::<10, _>::new(0);
let subscriptions: Subscriptions = Subscriptions::new();
let crypto = default_crypto(rand::thread_rng(), DAC_PRIVKEY);
let mut rand = crypto.rand()?;
let rgb_light = RgbLight::new(Dataver::new_rand(&mut rand), config.rgbled_order.clone());
let on_off =
on_off::OnOffHandler::new(Dataver::new_rand(&mut rand), LIGHT_ENDPOINT_ID, &rgb_light);
let level = level_control::LevelControlHandler::new(
Dataver::new_rand(&mut rand),
LIGHT_ENDPOINT_ID,
&rgb_light,
level_control::AttributeDefaults::default(),
);
on_off.init(Some(&level));
level.init(Some(&on_off));
let net_ctl_state = NetCtlState::new_with_mutex();
let net_ctl = NetCtlWithStatusImpl::new(&net_ctl_state, net_ctl);
let handler = rgb_handler(&mut rand, &rgb_light, &on_off, &level);
let dm = DataModel::new(
&matter,
&crypto,
&buffers,
&subscriptions,
&events,
(
NODE,
endpoints::with_wifi_sys(&false, &(), &UnixNetifs, &net_ctl, &net_ctl, rand, handler),
),
SharedKvBlobStore::new(kv, kv_buf.as_mut_slice()),
&networks,
);
let responder = DefaultResponder::new(&dm);
let mut respond = pin!(responder.run::<4, 4>());
let mut dm_job = pin!(dm.run());
let mut mdns_responder = ZeroconfMdnsResponder::new();
let mut mdns = pin!(mdns_responder.run(&matter));
let udp = async_io::Async::<UdpSocket>::bind(MATTER_SOCKET_BIND_ADDR)?;
let mut transport = pin!(matter.run(&crypto, &udp, &udp, &udp));
select4(&mut transport, &mut mdns, &mut respond, &mut dm_job)
.coalesce()
.await
}
fn rgb_handler<'a, OH, LH>(
@ -376,62 +517,6 @@ where
)
}
struct ToesBluezGattPeripheral<'a> {
connection: &'a Connection,
adapter_name: Option<&'a str>,
}
impl<'a> ToesBluezGattPeripheral<'a> {
const fn new(connection: &'a Connection, adapter_name: Option<&'a str>) -> Self {
Self {
connection,
adapter_name,
}
}
}
impl GattPeripheral for ToesBluezGattPeripheral<'_> {
async fn run(&mut self, btp: &Btp, _service_name: &str, service_adv: &AdvData) -> Result<()> {
rs_matter::transport::network::btp::bluez::run_peripheral(
self.connection,
self.adapter_name,
BLE_SERVICE_NAME,
service_adv,
btp,
)
.await
}
}
struct PreferredNetifs<'a> {
ifname: &'a str,
}
impl<'a> PreferredNetifs<'a> {
const fn new(ifname: &'a str) -> Self {
Self { ifname }
}
}
impl DynBase for PreferredNetifs<'_> {}
impl NetifDiag for PreferredNetifs<'_> {
fn netifs(&self, f: &mut dyn FnMut(&NetifInfo) -> Result<()>) -> Result<()> {
UnixNetifs.netifs(&mut |netif| {
if netif.name == self.ifname {
f(netif)?;
}
Ok(())
})
}
}
impl NetChangeNotif for PreferredNetifs<'_> {
async fn wait_changed(&self) {
UnixNetifs.wait_changed().await;
}
}
struct PersistingWifiCtl<'a, C> {
inner: C,
ifname: &'a str,