Skip to content

Commit

Permalink
Merge pull request project-chip#91 from ivmarkov/qr
Browse files Browse the repository at this point in the history
no_std QR code rendering
  • Loading branch information
kedars authored Aug 24, 2023
2 parents 4c347c0 + b89539c commit 3f48e2d
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 30 deletions.
4 changes: 2 additions & 2 deletions rs-matter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ license = "Apache-2.0"
default = ["os", "mbedtls"]
os = ["std", "backtrace", "env_logger", "nix", "critical-section/std", "embassy-sync/std", "embassy-time/std"]
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 = []
alloc = []
nightly = []
Expand Down Expand Up @@ -45,6 +45,7 @@ embassy-sync = "0.2"
critical-section = "1.1.1"
domain = { version = "0.7.2", default_features = false, features = ["heapless"] }
portable-atomic = "1"
qrcodegen-no-heap = "1.8"

# embassy-net dependencies
embassy-net = { version = "0.1", features = ["igmp", "proto-ipv6", "udp"], optional = true }
Expand All @@ -53,7 +54,6 @@ smoltcp = { version = "0.10", default-features = false, optional = true }

# STD-only dependencies
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

# crypto
Expand Down
6 changes: 3 additions & 3 deletions rs-matter/src/pairing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use crate::{

use self::{
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 {
Expand Down Expand Up @@ -88,10 +88,10 @@ pub fn print_pairing_code_and_qr(
buf: &mut [u8],
) -> Result<(), Error> {
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);
print_qr_code(qr_code);
print_qr_code(qr_code)?;

Ok(())
}
Expand Down
169 changes: 144 additions & 25 deletions rs-matter/src/pairing/qr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
* limitations under the License.
*/

use core::mem::MaybeUninit;

use qrcodegen_no_heap::{QrCode, QrCodeEcc, Version};

use crate::{
error::ErrorCode,
tlv::{TLVWriter, TagType},
Expand Down Expand Up @@ -319,47 +323,162 @@ fn estimate_struct_overhead(first_field_size: usize) -> usize {
first_field_size + 4 + 2
}

pub(super) fn print_qr_code(qr_code: &str) {
info!("QR Code: {}", qr_code);
pub(crate) fn print_qr_code(qr_code_text: &str) -> Result<(), Error> {
info!("QR Code Text: {}", qr_code_text);

let mut tmp_buf = MaybeUninit::<[u8; Version::MAX.buffer_len()]>::uninit();
let mut out_buf = MaybeUninit::<[u8; 7000]>::uninit();

let tmp_buf = unsafe { tmp_buf.assume_init_mut() };
let out_buf = unsafe { out_buf.assume_init_mut() };

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

let needed_version = compute_qr_version(qr_code);
let code =
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{}",
TextImage::Unicode.render(&qr_code, 4, false, out_buf)?
);

info!("\n{}", image);
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>(
dev_det: &BasicInfoConfig,
comm_data: &CommissioningData,
discovery_capabilities: DiscoveryCapabilities,
buf: &'a mut [u8],
) -> Result<&'a str, Error> {
let qr_code_data = QrSetupPayload::new(dev_det, comm_data, discovery_capabilities);
payload_base38_representation(&qr_code_data, buf)
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)
}

#[cfg(feature = "std")]
fn compute_qr_version(qr_data: &str) -> i16 {
match qr_data.len() {
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,
comm_data: &CommissioningData,
discovery_capabilities: DiscoveryCapabilities,
buf: &'a mut [u8],
) -> Result<&'a str, Error> {
let qr_code_data = QrSetupPayload::new(dev_det, comm_data, discovery_capabilities);
payload_base38_representation(&qr_code_data, buf)
}

fn populate_bits(
bits: &mut [u8],
offset: &mut usize,
Expand Down

0 comments on commit 3f48e2d

Please sign in to comment.