Skip to content

Commit 3f48e2d

Browse files
authored
Merge pull request #91 from ivmarkov/qr
no_std QR code rendering
2 parents 4c347c0 + b89539c commit 3f48e2d

File tree

3 files changed

+149
-30
lines changed

3 files changed

+149
-30
lines changed

rs-matter/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ license = "Apache-2.0"
1414
default = ["os", "mbedtls"]
1515
os = ["std", "backtrace", "env_logger", "nix", "critical-section/std", "embassy-sync/std", "embassy-time/std"]
1616
esp-idf = ["std", "rustcrypto", "esp-idf-sys"]
17-
std = ["alloc", "rand", "qrcode", "async-io", "esp-idf-sys?/std", "embassy-time/generic-queue-16"]
17+
std = ["alloc", "rand", "async-io", "esp-idf-sys?/std", "embassy-time/generic-queue-16"]
1818
backtrace = []
1919
alloc = []
2020
nightly = []
@@ -45,6 +45,7 @@ embassy-sync = "0.2"
4545
critical-section = "1.1.1"
4646
domain = { version = "0.7.2", default_features = false, features = ["heapless"] }
4747
portable-atomic = "1"
48+
qrcodegen-no-heap = "1.8"
4849

4950
# embassy-net dependencies
5051
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 }
5354

5455
# STD-only dependencies
5556
rand = { version = "0.8.5", optional = true }
56-
qrcode = { version = "0.12", default-features = false, optional = true } # Print QR code
5757
async-io = { version = "=1.12", optional = true } # =1.12 for compatibility with ESP IDF
5858

5959
# crypto

rs-matter/src/pairing/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ use crate::{
3131

3232
use self::{
3333
code::{compute_pairing_code, pretty_print_pairing_code},
34-
qr::{compute_qr_code, print_qr_code},
34+
qr::{compute_qr_code_text, print_qr_code},
3535
};
3636

3737
pub struct DiscoveryCapabilities {
@@ -88,10 +88,10 @@ pub fn print_pairing_code_and_qr(
8888
buf: &mut [u8],
8989
) -> Result<(), Error> {
9090
let pairing_code = compute_pairing_code(comm_data);
91-
let qr_code = compute_qr_code(dev_det, comm_data, discovery_capabilities, buf)?;
91+
let qr_code = compute_qr_code_text(dev_det, comm_data, discovery_capabilities, buf)?;
9292

9393
pretty_print_pairing_code(&pairing_code);
94-
print_qr_code(qr_code);
94+
print_qr_code(qr_code)?;
9595

9696
Ok(())
9797
}

rs-matter/src/pairing/qr.rs

Lines changed: 144 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
* limitations under the License.
1616
*/
1717

18+
use core::mem::MaybeUninit;
19+
20+
use qrcodegen_no_heap::{QrCode, QrCodeEcc, Version};
21+
1822
use crate::{
1923
error::ErrorCode,
2024
tlv::{TLVWriter, TagType},
@@ -319,47 +323,162 @@ fn estimate_struct_overhead(first_field_size: usize) -> usize {
319323
first_field_size + 4 + 2
320324
}
321325

322-
pub(super) fn print_qr_code(qr_code: &str) {
323-
info!("QR Code: {}", qr_code);
326+
pub(crate) fn print_qr_code(qr_code_text: &str) -> Result<(), Error> {
327+
info!("QR Code Text: {}", qr_code_text);
328+
329+
let mut tmp_buf = MaybeUninit::<[u8; Version::MAX.buffer_len()]>::uninit();
330+
let mut out_buf = MaybeUninit::<[u8; 7000]>::uninit();
331+
332+
let tmp_buf = unsafe { tmp_buf.assume_init_mut() };
333+
let out_buf = unsafe { out_buf.assume_init_mut() };
324334

325-
#[cfg(feature = "std")]
326-
{
327-
use qrcode::{render::unicode, QrCode, Version};
335+
let qr_code = compute_qr_code(qr_code_text, out_buf, tmp_buf)?;
328336

329-
let needed_version = compute_qr_version(qr_code);
330-
let code =
331-
QrCode::with_version(qr_code, Version::Normal(needed_version), qrcode::EcLevel::M)
332-
.unwrap();
333-
let image = code
334-
.render::<unicode::Dense1x2>()
335-
.dark_color(unicode::Dense1x2::Light)
336-
.light_color(unicode::Dense1x2::Dark)
337-
.build();
337+
info!(
338+
"\n{}",
339+
TextImage::Unicode.render(&qr_code, 4, false, out_buf)?
340+
);
338341

339-
info!("\n{}", image);
342+
Ok(())
343+
}
344+
345+
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
346+
pub enum TextImage {
347+
Ascii,
348+
Ansi,
349+
Unicode,
350+
}
351+
352+
impl TextImage {
353+
pub fn render<'a>(
354+
&self,
355+
qr_code: &QrCode,
356+
border: u8,
357+
invert: bool,
358+
out_buf: &'a mut [u8],
359+
) -> Result<&'a str, Error> {
360+
let mut offset = 0;
361+
362+
for c in self.render_iter(qr_code, border, invert) {
363+
let mut dst = [0; 4];
364+
let bytes = c.encode_utf8(&mut dst).as_bytes();
365+
366+
if offset + bytes.len() > out_buf.len() {
367+
return Err(ErrorCode::BufferTooSmall)?;
368+
} else {
369+
out_buf[offset..offset + bytes.len()].copy_from_slice(bytes);
370+
offset += bytes.len();
371+
}
372+
}
373+
374+
Ok(unsafe { core::str::from_utf8_unchecked(&out_buf[..offset]) })
375+
}
376+
377+
pub fn render_iter<'a>(
378+
&self,
379+
qr_code: &'a QrCode<'a>,
380+
border: u8,
381+
invert: bool,
382+
) -> impl Iterator<Item = char> + 'a {
383+
let border: i32 = border as _;
384+
let console_type = *self;
385+
386+
(-border..qr_code.size() + border)
387+
.filter(move |y| console_type != Self::Unicode || (y - -border) % 2 == 0)
388+
.flat_map(move |y| (-border..qr_code.size() + border + 1).map(move |x| (x, y)))
389+
.map(move |(x, y)| {
390+
if x < qr_code.size() + border {
391+
let white = !qr_code.get_module(x, y) ^ invert;
392+
393+
match console_type {
394+
Self::Ascii => {
395+
if white {
396+
"#"
397+
} else {
398+
" "
399+
}
400+
}
401+
Self::Ansi => {
402+
let prev_white = if x > -border {
403+
Some(qr_code.get_module(x - 1, y))
404+
} else {
405+
None
406+
}
407+
.map(|prev_white| !prev_white ^ invert);
408+
409+
if prev_white != Some(white) {
410+
if white {
411+
"\x1b[47m "
412+
} else {
413+
"\x1b[40m "
414+
}
415+
} else {
416+
" "
417+
}
418+
}
419+
Self::Unicode => {
420+
if white == !qr_code.get_module(x, y + 1) ^ invert {
421+
if white {
422+
"\u{2588}"
423+
} else {
424+
" "
425+
}
426+
} else if white {
427+
"\u{2580}"
428+
} else {
429+
"\u{2584}"
430+
}
431+
}
432+
}
433+
} else {
434+
"\x1b[0m\n"
435+
}
436+
})
437+
.flat_map(str::chars)
340438
}
341439
}
342440

343441
pub fn compute_qr_code<'a>(
344-
dev_det: &BasicInfoConfig,
345-
comm_data: &CommissioningData,
346-
discovery_capabilities: DiscoveryCapabilities,
347-
buf: &'a mut [u8],
348-
) -> Result<&'a str, Error> {
349-
let qr_code_data = QrSetupPayload::new(dev_det, comm_data, discovery_capabilities);
350-
payload_base38_representation(&qr_code_data, buf)
442+
qr_code_text: &str,
443+
tmp_buf: &mut [u8],
444+
out_buf: &'a mut [u8],
445+
) -> Result<QrCode<'a>, Error> {
446+
let needed_version = compute_qr_code_version(qr_code_text);
447+
448+
let code = QrCode::encode_text(
449+
qr_code_text,
450+
tmp_buf,
451+
out_buf,
452+
QrCodeEcc::Medium,
453+
Version::new(needed_version),
454+
Version::new(needed_version),
455+
None,
456+
false,
457+
)
458+
.map_err(|_| ErrorCode::BufferTooSmall)?;
459+
460+
Ok(code)
351461
}
352462

353-
#[cfg(feature = "std")]
354-
fn compute_qr_version(qr_data: &str) -> i16 {
355-
match qr_data.len() {
463+
pub fn compute_qr_code_version(qr_code_text: &str) -> u8 {
464+
match qr_code_text.len() {
356465
0..=38 => 2,
357466
39..=61 => 3,
358467
62..=90 => 4,
359468
_ => 5,
360469
}
361470
}
362471

472+
pub fn compute_qr_code_text<'a>(
473+
dev_det: &BasicInfoConfig,
474+
comm_data: &CommissioningData,
475+
discovery_capabilities: DiscoveryCapabilities,
476+
buf: &'a mut [u8],
477+
) -> Result<&'a str, Error> {
478+
let qr_code_data = QrSetupPayload::new(dev_det, comm_data, discovery_capabilities);
479+
payload_base38_representation(&qr_code_data, buf)
480+
}
481+
363482
fn populate_bits(
364483
bits: &mut [u8],
365484
offset: &mut usize,

0 commit comments

Comments
 (0)