Displays either a static qr code or a stream of raptor-encoded ones.

Kinda needs a client now to really test.
This commit is contained in:
Joe Ardent 2023-08-03 13:40:10 -07:00
parent 4b3869466b
commit 7ecb07eec3
4 changed files with 136 additions and 57 deletions

29
Cargo.lock generated
View file

@ -901,6 +901,21 @@ dependencies = [
"typenum", "typenum",
] ]
[[package]]
name = "cuttle"
version = "0.1.0"
dependencies = [
"clap",
"eframe",
"egui_extras",
"env_logger",
"fast_qr",
"png",
"rand",
"raptorq",
"rkyv",
]
[[package]] [[package]]
name = "d3d12" name = "d3d12"
version = "0.6.0" version = "0.6.0"
@ -1443,20 +1458,6 @@ dependencies = [
"web-sys", "web-sys",
] ]
[[package]]
name = "godiva"
version = "0.1.0"
dependencies = [
"clap",
"eframe",
"egui_extras",
"env_logger",
"fast_qr",
"png",
"raptorq",
"rkyv",
]
[[package]] [[package]]
name = "gpu-alloc" name = "gpu-alloc"
version = "0.5.4" version = "0.5.4"

View file

@ -1,5 +1,5 @@
[package] [package]
name = "godiva" name = "cuttle"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
@ -11,7 +11,7 @@ eframe = { version = "0.22", default-features = false, features = ["default_font
egui_extras = { version = "0.22", default-features = false, features = ["chrono", "image"] } egui_extras = { version = "0.22", default-features = false, features = ["chrono", "image"] }
env_logger = "*" env_logger = "*"
fast_qr = { version = "0.9", default-features = false, features = ["image"] } fast_qr = { version = "0.9", default-features = false, features = ["image"] }
png = "0.17.6" # pinning this to resolve conflict between eframe and fast_qr with the image crate
rand = { version = "0.8", default-features = false, features = ["std"] }
raptorq = "1.7" raptorq = "1.7"
# pinning this to resolve conflict between eframe and fast_qr with the image crate
png = "0.17.6"
rkyv = { version = "0.7.42", features = ["validation"] } rkyv = { version = "0.7.42", features = ["validation"] }

View file

@ -1,11 +1,11 @@
# Godiva, a flasher with a message # Cuttle, a flasher with a message
Godiva is a desktop application for transfering data from one computer to another by displaying a [possibly Cuttle is a desktop application for transfering data from one computer to another by displaying a
endless] stream of [QR encoded](https://en.wikipedia.org/wiki/QR_code) [Raptor [possibly endless] stream of [QR encoded](https://en.wikipedia.org/wiki/QR_code) [Raptor
codes](https://en.wikipedia.org/wiki/Raptor_code), and then on the receiving side, a camera to read codes](https://en.wikipedia.org/wiki/Raptor_code), so that a camera-equipped different computer can
the QR codes and turn them back into the original content. read the QR codes and turn them back into the original content.
## what? ## Tell me more!
Raptor codes are a type of [fountain code](https://en.wikipedia.org/wiki/Fountain_code); fountain Raptor codes are a type of [fountain code](https://en.wikipedia.org/wiki/Fountain_code); fountain
codes are a way to encode data as a stream of error-correcting pieces of that data; as long as you codes are a way to encode data as a stream of error-correcting pieces of that data; as long as you
@ -16,21 +16,24 @@ and not care if anyone is receiving, and receivers don't have to worry about mis
needed; they're guaranteed to get everything they need as long as they listen long enough. I think needed; they're guaranteed to get everything they need as long as they listen long enough. I think
that's pretty cool! that's pretty cool!
So the idea here is to give it a file (or short string of text) you want transferred to a different So the idea here is to give it a file (or short string of text) that you want transferred to a
machine (probably a mobile phone running a little app I'll be writing), and it will start producing different computer (like a mobile phone running a little companion app), and it will start producing
raptor codes for that file. Each piece of raptor code then gets encoded as a QR code, and then Raptor codes for that file. Each piece of Raptor code then gets encoded as a QR code, and then
displayed for a period of time on the screen. The user then holds up the receiving computer's camera displayed for a period of time on the screen. The user then holds up the receiving computer's camera
in order to receive the QR encoded raptor codes, and, *voila*, the file has been transferred! in order to receive the QR encoded raptor codes, and, *voila*, the file has been transferred!
# status # Current status
I literally just started this today, and currently it will produce a single QR code for a given The desktop transmitting application is nearly done; already it can convert text input into QR codes
input string, and display that. Honestly, this is already genuinely useful to me; sometimes I want (either a single static one if the data is small enough, otherwise it will stream a never ending
to look at a url on my phone, and the camera app will let me look at a QR code and interpret text as loop of them), and the mobile app has been started. Even without the mobile app, it's still
a link. But I hope to have a quick and dirty mobile app soon that will let me test streaming codes. already useful for transmitting short text strings to the phone, since the Android camera app will
decode any detected QR codes.
The mobile app has been started, and I hope to have it decoding QR streams soon!
# about the name # about the name
[Lady Godiva](https://en.wikipedia.org/wiki/Lady_Godiva), in legend, rode naked through her town in [Cuttlefish](https://en.wikipedia.org/wiki/Cuttlefish) are small but very intelligent cephalopods
order to send a message to her husband about the plight of the downtrodden under his whose bodies are covered in a 15hz multi-color, multi-polarity, pixel-mapped display that they use
landlordship. to visually communicate with other cuttlefish, as well as to dazzle predators and prey.

View file

@ -1,16 +1,25 @@
use std::ffi::OsString; use std::{ffi::OsString, time::Duration};
use clap::Parser; use clap::Parser;
use eframe::egui; use eframe::egui;
use egui_extras::RetainedImage; use egui_extras::RetainedImage;
use fast_qr::convert::image::ImageBuilder; use fast_qr::convert::image::ImageBuilder;
use rand::seq::SliceRandom;
use raptorq::{Encoder, ObjectTransmissionInformation};
pub const MTU: u16 = 776;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[clap(author, version, trailing_var_arg = true)] #[clap(author, version, trailing_var_arg = true)]
struct Cli { struct Cli {
#[clap(long, short, help = "File to expose")] #[clap(long, short, help = "File to expose")]
pub file: Option<OsString>, pub file: Option<OsString>,
#[clap(help = "all remaining arguments treated as a string; ignored if `-f` is given")]
#[clap(long, short, help = "Frames per second", default_value_t = 10)]
pub rate: u64,
#[clap(
help = "all remaining arguments treated as a string; this string is the whole message if `-f` is not given, otherwise it's an optional description of the file"
)]
text: Vec<String>, text: Vec<String>,
} }
@ -25,46 +34,112 @@ fn main() -> Result<(), eframe::Error> {
let cli = Cli::parse(); let cli = Cli::parse();
let content = if let Some(file) = cli.file { let flasher = if let Some(ref file) = cli.file {
file.to_string_lossy().bytes().collect() let (tx, rx) = std::sync::mpsc::sync_channel(1);
let content = Content::Streaming(rx);
let heading = if cli.text.is_empty() {
file.to_string_lossy().to_string()
} else { } else {
cli.text().join(" ").bytes().collect() cli.text().join(" ")
}; };
//println!("content: {content}"); stream_file(file, tx, cli.rate);
Flasher {
heading,
content,
rate: cli.rate,
}
} else {
let heading = "text message".to_string();
let bytes: Vec<u8> = cli.text().join(" ").bytes().collect();
let bytes = bytes.leak();
let content = Content::Static(bytes);
Flasher {
heading,
content,
rate: cli.rate,
}
};
let options = eframe::NativeOptions { let options = eframe::NativeOptions {
initial_window_size: Some(egui::vec2(1200.0, 1200.0)), initial_window_size: Some(egui::vec2(1200.0, 1200.0)),
active: true,
vsync: true,
..Default::default() ..Default::default()
}; };
eframe::run_native( eframe::run_native(
"Show an image with eframe/egui", "Cuttle: optical insanity",
options, options,
Box::new(|_cc| Box::new(Flasher { content })), Box::new(move |_cc| Box::new(flasher)),
) )
} }
struct Flasher { struct Flasher {
pub content: Vec<u8>, pub heading: String,
pub content: Content,
pub rate: u64,
}
enum Content {
Static(&'static [u8]),
Streaming(std::sync::mpsc::Receiver<Vec<u8>>),
} }
impl eframe::App for Flasher { impl eframe::App for Flasher {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
let sleep = 1000.0 / self.rate as f64;
ctx.request_repaint_after(Duration::from_millis(sleep as u64));
egui::CentralPanel::default().show(ctx, |ui| { egui::CentralPanel::default().show(ctx, |ui| {
let heading_text = String::from_utf8(self.content.clone()).unwrap(); ui.heading(&self.heading);
ui.heading(heading_text); let img = match self.content {
let qr = fast_qr::QRBuilder::new(self.content.clone()) Content::Static(bytes) => mk_img(bytes),
.build() Content::Streaming(ref rx) => {
.unwrap(); if let Ok(bytes) = rx.try_recv() {
mk_img(&bytes)
} else {
return;
}
}
};
img.show(ui);
});
}
}
fn mk_img(bytes: &[u8]) -> RetainedImage {
let qr = fast_qr::QRBuilder::new(bytes).build().unwrap();
let bytes = ImageBuilder::default() let bytes = ImageBuilder::default()
.fit_width(1100) .fit_width(1100)
.to_pixmap(&qr) .to_pixmap(&qr)
.encode_png() .encode_png()
.unwrap(); .unwrap();
let img = RetainedImage::from_image_bytes("generated qr code", &bytes).unwrap(); RetainedImage::from_image_bytes("generated qr code", &bytes).unwrap()
img.show(ui); }
});
} fn stream_file(file: &OsString, tx: std::sync::mpsc::SyncSender<Vec<u8>>, rate: u64) {
let bytes = std::fs::read(file).unwrap_or_else(|e| panic!("tried to open {file:?}, got {e:?}"));
let sleep = (1000.0 / rate as f64) as u64;
std::thread::spawn(move || {
let rng = &mut rand::thread_rng();
let config = ObjectTransmissionInformation::with_defaults(bytes.len() as u64, MTU);
let encoder = Encoder::new(&bytes, config);
let mut packets = encoder
.get_encoded_packets(10)
.iter()
.map(|p| p.serialize())
.collect::<Vec<_>>();
packets.shuffle(rng);
dbg!(&packets.len());
for (_counter, packet) in packets.iter().cycle().enumerate() {
tx.send(packet.clone()).unwrap();
std::thread::sleep(Duration::from_millis(sleep));
}
});
} }