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