Initial import

This commit is contained in:
Moritz Bitsch 2024-12-28 18:05:10 +01:00
commit 65b8841c4c
No known key found for this signature in database
7 changed files with 1317 additions and 0 deletions

1
.envrc Normal file
View file

@ -0,0 +1 @@
use flake

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
/target
/result
/.direnv

1077
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

12
Cargo.toml Normal file
View 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
View 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
View 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
View 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(&central, &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
}