no_std QR code rendering

This commit is contained in:
ivmarkov 2023-07-26 19:54:00 +00:00
parent 4c347c0c0b
commit b89539c8c6
3 changed files with 149 additions and 30 deletions

View file

@ -14,7 +14,7 @@ license = "Apache-2.0"
default = ["os", "mbedtls"] default = ["os", "mbedtls"]
os = ["std", "backtrace", "env_logger", "nix", "critical-section/std", "embassy-sync/std", "embassy-time/std"] os = ["std", "backtrace", "env_logger", "nix", "critical-section/std", "embassy-sync/std", "embassy-time/std"]
esp-idf = ["std", "rustcrypto", "esp-idf-sys"] esp-idf = ["std", "rustcrypto", "esp-idf-sys"]
std = ["alloc", "rand", "qrcode", "async-io", "esp-idf-sys?/std", "embassy-time/generic-queue-16"] std = ["alloc", "rand", "async-io", "esp-idf-sys?/std", "embassy-time/generic-queue-16"]
backtrace = [] backtrace = []
alloc = [] alloc = []
nightly = [] nightly = []
@ -45,6 +45,7 @@ embassy-sync = "0.2"
critical-section = "1.1.1" critical-section = "1.1.1"
domain = { version = "0.7.2", default_features = false, features = ["heapless"] } domain = { version = "0.7.2", default_features = false, features = ["heapless"] }
portable-atomic = "1" portable-atomic = "1"
qrcodegen-no-heap = "1.8"
# embassy-net dependencies # embassy-net dependencies
embassy-net = { version = "0.1", features = ["igmp", "proto-ipv6", "udp"], optional = true } embassy-net = { version = "0.1", features = ["igmp", "proto-ipv6", "udp"], optional = true }
@ -53,7 +54,6 @@ smoltcp = { version = "0.10", default-features = false, optional = true }
# STD-only dependencies # STD-only dependencies
rand = { version = "0.8.5", optional = true } rand = { version = "0.8.5", optional = true }
qrcode = { version = "0.12", default-features = false, optional = true } # Print QR code
async-io = { version = "=1.12", optional = true } # =1.12 for compatibility with ESP IDF async-io = { version = "=1.12", optional = true } # =1.12 for compatibility with ESP IDF
# crypto # crypto

View file

@ -31,7 +31,7 @@ use crate::{
use self::{ use self::{
code::{compute_pairing_code, pretty_print_pairing_code}, code::{compute_pairing_code, pretty_print_pairing_code},
qr::{compute_qr_code, print_qr_code}, qr::{compute_qr_code_text, print_qr_code},
}; };
pub struct DiscoveryCapabilities { pub struct DiscoveryCapabilities {
@ -88,10 +88,10 @@ pub fn print_pairing_code_and_qr(
buf: &mut [u8], buf: &mut [u8],
) -> Result<(), Error> { ) -> Result<(), Error> {
let pairing_code = compute_pairing_code(comm_data); let pairing_code = compute_pairing_code(comm_data);
let qr_code = compute_qr_code(dev_det, comm_data, discovery_capabilities, buf)?; let qr_code = compute_qr_code_text(dev_det, comm_data, discovery_capabilities, buf)?;
pretty_print_pairing_code(&pairing_code); pretty_print_pairing_code(&pairing_code);
print_qr_code(qr_code); print_qr_code(qr_code)?;
Ok(()) Ok(())
} }

View file

@ -15,6 +15,10 @@
* limitations under the License. * limitations under the License.
*/ */
use core::mem::MaybeUninit;
use qrcodegen_no_heap::{QrCode, QrCodeEcc, Version};
use crate::{ use crate::{
error::ErrorCode, error::ErrorCode,
tlv::{TLVWriter, TagType}, tlv::{TLVWriter, TagType},
@ -319,28 +323,153 @@ fn estimate_struct_overhead(first_field_size: usize) -> usize {
first_field_size + 4 + 2 first_field_size + 4 + 2
} }
pub(super) fn print_qr_code(qr_code: &str) { pub(crate) fn print_qr_code(qr_code_text: &str) -> Result<(), Error> {
info!("QR Code: {}", qr_code); info!("QR Code Text: {}", qr_code_text);
#[cfg(feature = "std")] let mut tmp_buf = MaybeUninit::<[u8; Version::MAX.buffer_len()]>::uninit();
{ let mut out_buf = MaybeUninit::<[u8; 7000]>::uninit();
use qrcode::{render::unicode, QrCode, Version};
let needed_version = compute_qr_version(qr_code); let tmp_buf = unsafe { tmp_buf.assume_init_mut() };
let code = let out_buf = unsafe { out_buf.assume_init_mut() };
QrCode::with_version(qr_code, Version::Normal(needed_version), qrcode::EcLevel::M)
.unwrap();
let image = code
.render::<unicode::Dense1x2>()
.dark_color(unicode::Dense1x2::Light)
.light_color(unicode::Dense1x2::Dark)
.build();
info!("\n{}", image); let qr_code = compute_qr_code(qr_code_text, out_buf, tmp_buf)?;
info!(
"\n{}",
TextImage::Unicode.render(&qr_code, 4, false, out_buf)?
);
Ok(())
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum TextImage {
Ascii,
Ansi,
Unicode,
}
impl TextImage {
pub fn render<'a>(
&self,
qr_code: &QrCode,
border: u8,
invert: bool,
out_buf: &'a mut [u8],
) -> Result<&'a str, Error> {
let mut offset = 0;
for c in self.render_iter(qr_code, border, invert) {
let mut dst = [0; 4];
let bytes = c.encode_utf8(&mut dst).as_bytes();
if offset + bytes.len() > out_buf.len() {
return Err(ErrorCode::BufferTooSmall)?;
} else {
out_buf[offset..offset + bytes.len()].copy_from_slice(bytes);
offset += bytes.len();
}
}
Ok(unsafe { core::str::from_utf8_unchecked(&out_buf[..offset]) })
}
pub fn render_iter<'a>(
&self,
qr_code: &'a QrCode<'a>,
border: u8,
invert: bool,
) -> impl Iterator<Item = char> + 'a {
let border: i32 = border as _;
let console_type = *self;
(-border..qr_code.size() + border)
.filter(move |y| console_type != Self::Unicode || (y - -border) % 2 == 0)
.flat_map(move |y| (-border..qr_code.size() + border + 1).map(move |x| (x, y)))
.map(move |(x, y)| {
if x < qr_code.size() + border {
let white = !qr_code.get_module(x, y) ^ invert;
match console_type {
Self::Ascii => {
if white {
"#"
} else {
" "
}
}
Self::Ansi => {
let prev_white = if x > -border {
Some(qr_code.get_module(x - 1, y))
} else {
None
}
.map(|prev_white| !prev_white ^ invert);
if prev_white != Some(white) {
if white {
"\x1b[47m "
} else {
"\x1b[40m "
}
} else {
" "
}
}
Self::Unicode => {
if white == !qr_code.get_module(x, y + 1) ^ invert {
if white {
"\u{2588}"
} else {
" "
}
} else if white {
"\u{2580}"
} else {
"\u{2584}"
}
}
}
} else {
"\x1b[0m\n"
}
})
.flat_map(str::chars)
} }
} }
pub fn compute_qr_code<'a>( pub fn compute_qr_code<'a>(
qr_code_text: &str,
tmp_buf: &mut [u8],
out_buf: &'a mut [u8],
) -> Result<QrCode<'a>, Error> {
let needed_version = compute_qr_code_version(qr_code_text);
let code = QrCode::encode_text(
qr_code_text,
tmp_buf,
out_buf,
QrCodeEcc::Medium,
Version::new(needed_version),
Version::new(needed_version),
None,
false,
)
.map_err(|_| ErrorCode::BufferTooSmall)?;
Ok(code)
}
pub fn compute_qr_code_version(qr_code_text: &str) -> u8 {
match qr_code_text.len() {
0..=38 => 2,
39..=61 => 3,
62..=90 => 4,
_ => 5,
}
}
pub fn compute_qr_code_text<'a>(
dev_det: &BasicInfoConfig, dev_det: &BasicInfoConfig,
comm_data: &CommissioningData, comm_data: &CommissioningData,
discovery_capabilities: DiscoveryCapabilities, discovery_capabilities: DiscoveryCapabilities,
@ -350,16 +479,6 @@ pub fn compute_qr_code<'a>(
payload_base38_representation(&qr_code_data, buf) payload_base38_representation(&qr_code_data, buf)
} }
#[cfg(feature = "std")]
fn compute_qr_version(qr_data: &str) -> i16 {
match qr_data.len() {
0..=38 => 2,
39..=61 => 3,
62..=90 => 4,
_ => 5,
}
}
fn populate_bits( fn populate_bits(
bits: &mut [u8], bits: &mut [u8],
offset: &mut usize, offset: &mut usize,