Skip to content

Commit

Permalink
resolve #2, resolve #3 manual mode selection, qrcodes w/multi lines
Browse files Browse the repository at this point in the history
  • Loading branch information
bmatcuk committed Oct 22, 2023
1 parent 36dc9f4 commit 202ca0b
Show file tree
Hide file tree
Showing 4 changed files with 364 additions and 97 deletions.
86 changes: 69 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,49 @@ Version: 0
Message: your content here
```

qrcode v2 supports a newer format as well (the old format still works for
backward compatibility, or, if you don't need the newer features, the app will
read version "0" files faster):

```
Filetype: QRCode
Version: 1
QRMode: B
QRVersion: 6
QRECC: L
Message: your content here
Message: multi-line content is possible
```

In a version "1" file, the `QRMode`, `QRVersion`, and `QRECC` are optional
(though, must be in that order if more than one are specified). The app will
attempt to use the specified mode, version, and/or ECC, if the content will
fit. Otherwise, it may select a different mode, version, and/or ECC. Keep
reading to learn about the meaning of `QRMode`, `QRVersion`, and `QRECC`.

Version "1" files also support multi-line content. Each line starting with
`Message:` will be concatenated together with newline characters.

My recommendation is to allow the app to select a mode, version, and ECC level
for you and, then, if you find that your qrcode reader prefers specific
settings, update the file appropriately.

### Message Format
qrcodes support 4 formats called "modes": numeric, alpha-numeric, binary, and
kanji. Because of the limited screen real-estate on the [Flipper Zero], you'll
want to pick the best mode for the data you are trying to display.

The app will automatically detect the best mode to use, so the only thing you
need to do is make sure the message in your file is formatted to use the best
mode. For example, if your message is entirely numeric, make sure you don't
include any extraneous punctuation in your file. If you're only encoding a
domain name, make sure it's uppercase to take advantage of alpha-numeric mode,
etc.
If unspecified in the `.qrcode` file, the app will automatically detect the
best mode to use based on the message content.

#### Numeric Mode
Consists of only numbers, nothing else. This mode can encode the most data.
#### Numeric Mode (QRMode: N)
Consists of only numbers, nothing else. This mode can encode the most data and
is useful for things like phone numbers. To use this mode, your message must
_not_ contain non-numeric characters. For example, a message content of "(xxx)
xxx-xxxx" can _not_ use numeric mode (it would require "binary" mode, in fact).
Instead, your message should just be "xxxxxxxxxx".

#### Alpha-Numeric Mode
#### Alpha-Numeric Mode (QRMode: A)
This mode can encode numbers, uppercase letters *only*, spaces, and the
following symbols: `$%*+-./:`. This format _may_ be appropriate for urls, as
long as you're only encoding the domain name and you remember to use uppercase
Expand All @@ -48,30 +75,56 @@ case-sensitive.

A qrcode in alpha-numeric mode can encode ~40% less data than numeric mode.

#### Binary Mode
#### Binary Mode (QRMode: B)
This mode is a little bit of a misnomer: binary mode simply means that the
message will be encoded as 8-bit bytes. The qrcode standard stipulates that
text will use ISO-8859-1 (also known as Latin-1) encoding, _not_ utf8 as would
be the standard these days. However, _some_ readers _may_ automatically detect
utf8. To be standard-compliant, that basically means you can only use Latin
letters, numbers, and symbols.

Multi-line messages will always be in binary mode, since the other modes cannot
encode a newline character.

A qrcode in binary mode can encode ~60% less data than numeric mode, and ~30%
less than alpha-numeric.

#### Kanji Mode
#### Kanji Mode (QRMode: K)
This mode is unsupported, so I won't go into detail. A limitation of the
underlying qrcode library that I'm using, unfortunately. If there's interest,
perhaps I'll hack in support sometime.

### QRVersion
A qrcode's version specifies how "big" it is. Higher versions contain more
"modules" (ie, the "pixels" that make up qrcodes) and, thus, can encode more
data. A version 1 qrcode contains 21x21 modules, whereas a version 11 code (the
largest the Flipper Zero can display) contains 61x61 modules. The modules of a
version 1 code will be 3x3 pixels on the Flipper Zero screen; version 2 and 3
qrcodes will each have 2x2 pixel modules; and version 4 through 11 qrcodes will
have single pixel modules.

If unspecified in the `.qrcode` file, the app will automatically select the
lowest version that can contain all of the message content, given the mode
selected in the previous step.

### QRECC
A qrcode's ECC level determines the qrcode's resilience to "damage". In the
case of the Flipper Zero, "damage" might be a dirty screen, dead pixels, or
even screen glare. Higher ECC modes are more resilient, but can contain less
data. The ECC modes are Low, Medium, Quartile, and High and can be specified in
the `.qrcode` file using the first letter (L, M, Q, and H).

qrcode readers may have an easier time reading qrcodes with higher ECC levels,
so, if unspecified in the `.qrcode` file, the app will select the highest ECC
level that can contain all of the message content, given the qrcode mode and
version selected in the previous steps.

## Using the App
The app is fairly straightforward. When it first starts, the file browser will
automatically open to the `qrcodes` directory and display any `.qrcode` files.
Select one using the arrow keys and the center button. The qrcode will display.
If you push the right arrow, some stats will display: the qrcode "Version" -
which corresponds to how big it is; the ECC level - which determines the
qrcode's resilience to damage, such as a dirty screen (Low, Medium, Quartile,
and High); and the qrcode Mode (Numeric, Alpha-Numeric, Binary, or Kanji).
If you push the right arrow, some stats will display: the qrcode "Version"; the
ECC level; and the qrcode Mode (Numeric, Alpha-Numeric, Binary, or Kanji).

While viewing the stats, you can select Version or ECC using the up and down
arrows and the center button. You can then increase or decrease the Version or
Expand Down Expand Up @@ -133,7 +186,6 @@ git clone git@github.com:bmatcuk/flipperzero-qrcode.git
Next, in the base of the [flipperzero-firmware] directory, run fbt:

```bash
cd ..
./fbt fap_qrcode
```

Expand All @@ -146,7 +198,7 @@ find the .fap, should it change in the future).
This application uses the [QRCode] library by ricmoo. This is the same library
that is in the lib directory of the flipper-firmware repo (which was originally
included for a [now-removed demo app]), but modified slightly to fix some
compiler errors.
compiler errors and allow the explicit selection of the qrcode mode.

[now-removed demo app]: https://github.com/flipperdevices/flipperzero-firmware/pull/160/files
[flipperzero-firmware]: https://github.com/flipperdevices/flipperzero-firmware
Expand Down
46 changes: 21 additions & 25 deletions qrcode.c
Original file line number Diff line number Diff line change
Expand Up @@ -119,21 +119,21 @@ static int8_t getAlphanumeric(char c) {
return -1;
}

static bool isAlphanumeric(const char *text, uint16_t length) {
while (length != 0) {
if (getAlphanumeric(text[--length]) == -1) { return false; }
}
return true;
}
/* static bool isAlphanumeric(const char *text, uint16_t length) { */
/* while (length != 0) { */
/* if (getAlphanumeric(text[--length]) == -1) { return false; } */
/* } */
/* return true; */
/* } */


static bool isNumeric(const char *text, uint16_t length) {
while (length != 0) {
char c = text[--length];
if (c < '0' || c > '9') { return false; }
}
return true;
}
/* static bool isNumeric(const char *text, uint16_t length) { */
/* while (length != 0) { */
/* char c = text[--length]; */
/* if (c < '0' || c > '9') { return false; } */
/* } */
/* return true; */
/* } */


// We store the following tightly packed (less 8) in modeInfo
Expand Down Expand Up @@ -614,11 +614,8 @@ static void rs_getRemainder(uint8_t degree, uint8_t *coeff, uint8_t *data, uint8



static int8_t encodeDataCodewords(BitBucket *dataCodewords, const uint8_t *text, uint16_t length, uint8_t version) {
int8_t mode = MODE_BYTE;

if (isNumeric((char*)text, length)) {
mode = MODE_NUMERIC;
static int8_t encodeDataCodewords(BitBucket *dataCodewords, const uint8_t *text, uint16_t length, int8_t mode, uint8_t version) {
if (mode == MODE_NUMERIC) {
bb_appendBits(dataCodewords, 1 << MODE_NUMERIC, 4);
bb_appendBits(dataCodewords, length, getModeBits(version, MODE_NUMERIC));

Expand All @@ -639,8 +636,7 @@ static int8_t encodeDataCodewords(BitBucket *dataCodewords, const uint8_t *text,
bb_appendBits(dataCodewords, accumData, accumCount * 3 + 1);
}

} else if (isAlphanumeric((char*)text, length)) {
mode = MODE_ALPHANUMERIC;
} else if (mode == MODE_ALPHANUMERIC) {
bb_appendBits(dataCodewords, 1 << MODE_ALPHANUMERIC, 4);
bb_appendBits(dataCodewords, length, getModeBits(version, MODE_ALPHANUMERIC));

Expand Down Expand Up @@ -758,7 +754,7 @@ uint16_t qrcode_getBufferSize(uint8_t version) {
}

// @TODO: Return error if data is too big.
int8_t qrcode_initBytes(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, uint8_t *data, uint16_t length) {
int8_t qrcode_initBytes(QRCode *qrcode, uint8_t *modules, int8_t mode, uint8_t version, uint8_t ecc, uint8_t *data, uint16_t length) {
uint8_t size = version * 4 + 17;
qrcode->version = version;
qrcode->size = size;
Expand All @@ -781,7 +777,7 @@ int8_t qrcode_initBytes(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8
bb_initBuffer(&codewords, codewordBytes, (int32_t)sizeof(codewordBytes));

// Place the data code words into the buffer
int8_t mode = encodeDataCodewords(&codewords, data, length, version);
mode = encodeDataCodewords(&codewords, data, length, mode, version);

if (mode < 0) { return -1; }
qrcode->mode = mode;
Expand Down Expand Up @@ -834,9 +830,9 @@ int8_t qrcode_initBytes(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8
return 0;
}

int8_t qrcode_initText(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, const char *data) {
return qrcode_initBytes(qrcode, modules, version, ecc, (uint8_t*)data, strlen(data));
}
/* int8_t qrcode_initText(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, const char *data) { */
/* return qrcode_initBytes(qrcode, modules, version, ecc, (uint8_t*)data, strlen(data)); */
/* } */

bool qrcode_getModule(QRCode *qrcode, uint8_t x, uint8_t y) {
if (x >= qrcode->size || y >= qrcode->size) {
Expand Down
4 changes: 2 additions & 2 deletions qrcode.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ extern "C"{

uint16_t qrcode_getBufferSize(uint8_t version);

int8_t qrcode_initText(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, const char *data);
int8_t qrcode_initBytes(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, uint8_t *data, uint16_t length);
/* int8_t qrcode_initText(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, const char *data); */
int8_t qrcode_initBytes(QRCode *qrcode, uint8_t *modules, int8_t mode, uint8_t version, uint8_t ecc, uint8_t *data, uint16_t length);

bool qrcode_getModule(QRCode *qrcode, uint8_t x, uint8_t y);

Expand Down
Loading

0 comments on commit 202ca0b

Please sign in to comment.