Initial import
This commit is contained in:
commit
65b8841c4c
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
/target
|
||||||
|
/result
|
||||||
|
/.direnv
|
1077
Cargo.lock
generated
Normal file
1077
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
12
Cargo.toml
Normal file
12
Cargo.toml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
[package]
|
||||||
|
name = "meshtastic-esp-ota"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tokio = { version = "1.42.0", features = ["full"] }
|
||||||
|
anyhow = "1.0.95"
|
||||||
|
btleplug = "0.11.7"
|
||||||
|
clap = { version = "4.5.23", features = ["derive"] }
|
||||||
|
uuid = "1.11.0"
|
||||||
|
futures-util = "0.3.31"
|
91
flake.lock
Normal file
91
flake.lock
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-parts": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs-lib": "nixpkgs-lib"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1733312601,
|
||||||
|
"narHash": "sha256-4pDvzqnegAfRkPwO3wmwBhVi/Sye1mzps0zHWYnP88c=",
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "flake-parts",
|
||||||
|
"rev": "205b12d8b7cd4802fbcb8e8ef6a0f1408781a4f9",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "flake-parts",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"naersk": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1733346208,
|
||||||
|
"narHash": "sha256-a4WZp1xQkrnA4BbnKrzJNr+dYoQr5Xneh2syJoddFyE=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "naersk",
|
||||||
|
"rev": "378614f37a6bee5a3f2ef4f825a73d948d3ae921",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "naersk",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1735268880,
|
||||||
|
"narHash": "sha256-7QEFnKkzD13SPxs+UFR5bUFN2fRw+GlL0am72ZjNre4=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "7cc0bff31a3a705d3ac4fdceb030a17239412210",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"id": "nixpkgs",
|
||||||
|
"type": "indirect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs-lib": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1733096140,
|
||||||
|
"narHash": "sha256-1qRH7uAUsyQI7R1Uwl4T+XvdNv778H0Nb5njNrqvylY=",
|
||||||
|
"type": "tarball",
|
||||||
|
"url": "https://github.com/NixOS/nixpkgs/archive/5487e69da40cbd611ab2cadee0b4637225f7cfae.tar.gz"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"type": "tarball",
|
||||||
|
"url": "https://github.com/NixOS/nixpkgs/archive/5487e69da40cbd611ab2cadee0b4637225f7cfae.tar.gz"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1735291276,
|
||||||
|
"narHash": "sha256-NYVcA06+blsLG6wpAbSPTCyLvxD/92Hy4vlY9WxFI1M=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "634fd46801442d760e09493a794c4f15db2d0cbb",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-parts": "flake-parts",
|
||||||
|
"naersk": "naersk",
|
||||||
|
"nixpkgs": "nixpkgs_2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
30
flake.nix
Normal file
30
flake.nix
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
description = "Meshtastic ble OTA client";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
flake-parts.url = "github:hercules-ci/flake-parts";
|
||||||
|
naersk.url = "github:nix-community/naersk";
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = inputs@{ flake-parts, naersk, self, ... }:
|
||||||
|
flake-parts.lib.mkFlake { inherit inputs; } {
|
||||||
|
systems = [ "x86_64-linux" "aarch64-linux" "aarch64-darwin" "x86_64-darwin" ];
|
||||||
|
perSystem = { pkgs, ... }:
|
||||||
|
let naersk' = pkgs.callPackage naersk { };
|
||||||
|
in rec {
|
||||||
|
packages.default = naersk'.buildPackage {
|
||||||
|
src = ./.;
|
||||||
|
nativeBuildInputs = with pkgs; [ rustPlatform.bindgenHook pkg-config ];
|
||||||
|
buildInputs = with pkgs; [ dbus ];
|
||||||
|
};
|
||||||
|
devShells.default = pkgs.mkShell {
|
||||||
|
inputsFrom = [ packages.default ];
|
||||||
|
buildInputs = with pkgs; [ dbus ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
flake = {
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
103
src/main.rs
Normal file
103
src/main.rs
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
use btleplug::api::{Central, Manager as _, Peripheral as _, ScanFilter};
|
||||||
|
use btleplug::platform::{Adapter, Manager, Peripheral};
|
||||||
|
use clap::Parser;
|
||||||
|
use futures_util::stream::StreamExt;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Read;
|
||||||
|
use std::time::Duration;
|
||||||
|
use tokio::time;
|
||||||
|
use uuid::{uuid, Uuid};
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
struct Args {
|
||||||
|
device_name: String,
|
||||||
|
ota: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
const CHARACTERISTIC_TX_UUID: Uuid = uuid!("62ec0272-3ec5-11eb-b378-0242ac130003");
|
||||||
|
const CHARACTERISTIC_OTA_UUID: Uuid = uuid!("62ec0272-3ec5-11eb-b378-0242ac130005");
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> anyhow::Result<()> {
|
||||||
|
let args = Args::parse();
|
||||||
|
|
||||||
|
let mut ota_file = Vec::new();
|
||||||
|
File::open(args.ota)?.read_to_end(&mut ota_file)?;
|
||||||
|
|
||||||
|
let manager = Manager::new().await.unwrap();
|
||||||
|
|
||||||
|
// get the first bluetooth adapter
|
||||||
|
let adapters = manager.adapters().await?;
|
||||||
|
let central = adapters.into_iter().next().unwrap();
|
||||||
|
|
||||||
|
// start scanning for devices
|
||||||
|
central.start_scan(ScanFilter::default()).await?;
|
||||||
|
|
||||||
|
// instead of waiting, you can use central.events() to get a stream which will
|
||||||
|
// notify you of new devices, for an example of that see examples/event_driven_discovery.rs
|
||||||
|
time::sleep(Duration::from_secs(2)).await;
|
||||||
|
|
||||||
|
// find the device we're interested in
|
||||||
|
let peripheral = find_peripheral(¢ral, &args.device_name).await.unwrap();
|
||||||
|
|
||||||
|
// connect to the device
|
||||||
|
peripheral.connect().await?;
|
||||||
|
|
||||||
|
// discover services and characteristics
|
||||||
|
peripheral.discover_services().await?;
|
||||||
|
|
||||||
|
// find the characteristic we want
|
||||||
|
let chars = peripheral.characteristics();
|
||||||
|
let write_char = chars
|
||||||
|
.iter()
|
||||||
|
.find(|c| c.uuid == CHARACTERISTIC_OTA_UUID)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let notify_char = chars
|
||||||
|
.iter()
|
||||||
|
.find(|c| c.uuid == CHARACTERISTIC_TX_UUID)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let data = format!("OTA_SIZE:{}", ota_file.len());
|
||||||
|
peripheral
|
||||||
|
.write(
|
||||||
|
write_char,
|
||||||
|
data.as_bytes(),
|
||||||
|
btleplug::api::WriteType::WithoutResponse,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
peripheral.subscribe(notify_char).await?;
|
||||||
|
|
||||||
|
let mut notification_stream = peripheral.notifications().await?;
|
||||||
|
|
||||||
|
for (id, chunk) in ota_file.chunks(512).enumerate() {
|
||||||
|
println!("writing chunk {} of {}", id, ota_file.len() / 512);
|
||||||
|
peripheral
|
||||||
|
.write(write_char, chunk, btleplug::api::WriteType::WithoutResponse)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
notification_stream.next().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn find_peripheral(central: &Adapter, device_name: &String) -> Option<Peripheral> {
|
||||||
|
println!("Looking for Peripheral {}...", device_name);
|
||||||
|
|
||||||
|
for p in central.peripherals().await.unwrap() {
|
||||||
|
let properties = p.properties().await.unwrap().unwrap();
|
||||||
|
|
||||||
|
let name = properties.local_name.unwrap_or("".into());
|
||||||
|
|
||||||
|
println!("Device detected: {}", name);
|
||||||
|
|
||||||
|
if name.contains(device_name) {
|
||||||
|
println!("Found {}!", device_name);
|
||||||
|
return Some(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
Loading…
Reference in a new issue