Basic image drawing and clustring working (still slow, and still a lot of hardcoded stuff).
This commit is contained in:
commit
348eb6bedf
6 changed files with 1267 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/target
|
||||||
1020
Cargo.lock
generated
Normal file
1020
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
11
Cargo.toml
Normal file
11
Cargo.toml
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
[package]
|
||||||
|
name = "ipxl-rs"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bluer = { version = "0.17.4", features = ["bluetoothd"] }
|
||||||
|
futures-lite = "2.6.1"
|
||||||
|
rand = "0.9.2"
|
||||||
|
tokio = {version = "1.48.0", features = ["full"]}
|
||||||
|
|
||||||
BIN
my-file
Normal file
BIN
my-file
Normal file
Binary file not shown.
193
src/led.rs
Normal file
193
src/led.rs
Normal file
|
|
@ -0,0 +1,193 @@
|
||||||
|
use std::{collections::HashMap, io::Write, str::FromStr, time::Duration};
|
||||||
|
|
||||||
|
use bluer::{
|
||||||
|
Adapter, Address, Device, DiscoveryFilter, Uuid,
|
||||||
|
gatt::{
|
||||||
|
WriteOp,
|
||||||
|
remote::{Characteristic, CharacteristicWriteRequest},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use futures_lite::StreamExt;
|
||||||
|
use tokio::time::timeout;
|
||||||
|
|
||||||
|
const ADDRESS: &str = "B2:E5:7C:A7:A6:23";
|
||||||
|
const CHAR_UUID: &str = "0000fa02-0000-1000-8000-00805f9b34fb";
|
||||||
|
|
||||||
|
pub struct LEDDevice {
|
||||||
|
pub width: u8,
|
||||||
|
pub height: u8,
|
||||||
|
pub address: Address,
|
||||||
|
pub device: Device,
|
||||||
|
pub characterstic: Characteristic,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct Position(pub u8, pub u8);
|
||||||
|
#[derive(Eq, Hash, PartialEq, Copy, Clone)]
|
||||||
|
pub struct RGB(pub u8, pub u8, pub u8);
|
||||||
|
|
||||||
|
pub struct Pixel {
|
||||||
|
pub position: Position,
|
||||||
|
pub color: RGB,
|
||||||
|
}
|
||||||
|
pub struct PixelCluster {
|
||||||
|
pub positions: Vec<Position>,
|
||||||
|
pub color: RGB,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait PixelToByte {
|
||||||
|
type Output;
|
||||||
|
fn to_byte(&self) -> Self::Output;
|
||||||
|
}
|
||||||
|
impl PixelToByte for Pixel {
|
||||||
|
type Output = Vec<u8>;
|
||||||
|
|
||||||
|
fn to_byte(&self) -> Self::Output {
|
||||||
|
let rgb = &self.color;
|
||||||
|
let pos = &self.position;
|
||||||
|
vec![
|
||||||
|
0x0a, 0x00, 0x05, 0x01, 0x00, rgb.0, rgb.1, rgb.2, pos.0, pos.1,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl PixelToByte for PixelCluster {
|
||||||
|
type Output = Vec<Vec<u8>>;
|
||||||
|
fn to_byte(&self) -> Vec<Vec<u8>> {
|
||||||
|
let color = &self.color;
|
||||||
|
let chunks = self.positions.as_chunks::<120>();
|
||||||
|
|
||||||
|
let mut buf = Vec::with_capacity(chunks.0.len() + 1);
|
||||||
|
|
||||||
|
let mut push_chunk = |positions: &[Position]| {
|
||||||
|
if positions.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let payload_len = positions.len() * 2;
|
||||||
|
let total = 8 + payload_len;
|
||||||
|
let mut out = Vec::with_capacity(total);
|
||||||
|
|
||||||
|
out.extend_from_slice(&[total as u8, 0, 5, 1, 0, color.0, color.1, color.2]);
|
||||||
|
|
||||||
|
for p in positions {
|
||||||
|
out.push(p.0);
|
||||||
|
out.push(p.1);
|
||||||
|
}
|
||||||
|
buf.push(out);
|
||||||
|
};
|
||||||
|
|
||||||
|
for c in chunks.0 {
|
||||||
|
push_chunk(c);
|
||||||
|
}
|
||||||
|
push_chunk(chunks.1);
|
||||||
|
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait PixelsToCluster {
|
||||||
|
fn to_cluster(&self) -> Vec<PixelCluster>;
|
||||||
|
}
|
||||||
|
impl PixelsToCluster for Vec<Pixel> {
|
||||||
|
fn to_cluster(&self) -> Vec<PixelCluster> {
|
||||||
|
let mut map: HashMap<RGB, Vec<Position>> = HashMap::new();
|
||||||
|
for px in self {
|
||||||
|
map.entry(px.color).or_default().push(px.position);
|
||||||
|
}
|
||||||
|
map.into_iter()
|
||||||
|
.map(|(color, positions)| PixelCluster {
|
||||||
|
positions: positions,
|
||||||
|
color,
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl LEDDevice {
|
||||||
|
pub async fn new(
|
||||||
|
width: u8,
|
||||||
|
height: u8,
|
||||||
|
addr: Address,
|
||||||
|
adapter: Adapter,
|
||||||
|
) -> bluer::Result<LEDDevice> {
|
||||||
|
adapter.set_powered(true).await?;
|
||||||
|
|
||||||
|
adapter
|
||||||
|
.set_discovery_filter(DiscoveryFilter {
|
||||||
|
pattern: Some(addr.to_string()),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mut discover = adapter.discover_devices().await?;
|
||||||
|
|
||||||
|
let device = loop {
|
||||||
|
match timeout(Duration::from_secs(10), discover.next()).await {
|
||||||
|
Ok(Some(bluer::AdapterEvent::DeviceAdded(a))) if a == addr => {
|
||||||
|
break adapter.device(addr)?;
|
||||||
|
}
|
||||||
|
Ok(Some(_)) => continue,
|
||||||
|
Ok(None) | Err(_) => {
|
||||||
|
return Err(bluer::Error {
|
||||||
|
kind: bluer::ErrorKind::DoesNotExist,
|
||||||
|
message: "device not found".to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
device.connect().await?;
|
||||||
|
let uuid = Uuid::from_str(CHAR_UUID).unwrap();
|
||||||
|
|
||||||
|
let chr = 'outer: {
|
||||||
|
for svc in device.services().await? {
|
||||||
|
for ch in svc.characteristics().await? {
|
||||||
|
if ch.uuid().await? == uuid {
|
||||||
|
break 'outer ch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
panic!("characteristic {} not found on peripheral", uuid);
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(LEDDevice {
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
address: addr,
|
||||||
|
device: device,
|
||||||
|
characterstic: chr,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn write_data(&self, data: &[u8]) -> bluer::Result<()> {
|
||||||
|
let chr = &self.characterstic;
|
||||||
|
|
||||||
|
chr.write_ext(
|
||||||
|
data,
|
||||||
|
&CharacteristicWriteRequest {
|
||||||
|
op_type: WriteOp::Request,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub async fn write_pixel(&self, pixel: &Pixel) -> bluer::Result<()> {
|
||||||
|
self.write_data(&pixel.to_byte()).await
|
||||||
|
}
|
||||||
|
pub async fn write_pixelcluster(&self, pixelcluster: &PixelCluster) -> bluer::Result<()> {
|
||||||
|
for data in pixelcluster.to_byte() {
|
||||||
|
self.write_data(&data).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub async fn write_pixelclusters(&self, pixelclusters: &[PixelCluster]) -> bluer::Result<()> {
|
||||||
|
for cluster in pixelclusters {
|
||||||
|
self.write_pixelcluster(cluster).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
42
src/main.rs
Normal file
42
src/main.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
mod led;
|
||||||
|
|
||||||
|
use led::{LEDDevice, Pixel, PixelsToCluster, RGB};
|
||||||
|
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use bluer::Address;
|
||||||
|
|
||||||
|
const ADDRESS: &str = "B2:E5:7C:A7:A6:23";
|
||||||
|
|
||||||
|
#[tokio::main(flavor = "current_thread")]
|
||||||
|
async fn main() -> bluer::Result<()> {
|
||||||
|
let session = bluer::Session::new().await?;
|
||||||
|
let adapter = session.default_adapter().await?;
|
||||||
|
|
||||||
|
let addr = Address::from_str(ADDRESS)?;
|
||||||
|
let led = LEDDevice::new(64, 16, addr, adapter).await?;
|
||||||
|
|
||||||
|
let device = &led.device;
|
||||||
|
println!(
|
||||||
|
"{:?}: {:?}",
|
||||||
|
device.name().await?.unwrap(),
|
||||||
|
device.is_connected().await?
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut pixels: Vec<Pixel> = vec![];
|
||||||
|
for i in 0..(led.width as usize * led.height as usize) {
|
||||||
|
let rng: u8 = rand::random();
|
||||||
|
pixels.push(Pixel {
|
||||||
|
position: led::Position(
|
||||||
|
(i % led.width as usize) as u8,
|
||||||
|
(i / led.width as usize) as u8,
|
||||||
|
),
|
||||||
|
// color: RGB(255, 255, 255),
|
||||||
|
color: RGB(rng, rng % 128, rng / 2),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
led.write_pixelclusters(&pixels.to_cluster()).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue