Skip to content
This repository has been archived by the owner on Jul 28, 2024. It is now read-only.

Commit

Permalink
simple menu to change version, ecc
Browse files Browse the repository at this point in the history
  • Loading branch information
bmatcuk committed Jan 2, 2023
1 parent 6a009dd commit 0d6d173
Show file tree
Hide file tree
Showing 3 changed files with 158 additions and 26 deletions.
20 changes: 15 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,19 +71,29 @@ 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). You
can hide the stats by pressing the left arrow.
and High); 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
ECC using up and down and save your choice using the center buttton. This
feature was mostly added for my own amusement and testing, but, theoretically,
it may help a reader that's having trouble if the default ECC is less than the
highest value ("H"): you can increase the Version by 1 and then set the ECC to
"H". Whether or not this helps depends on the reader.

You can hide the stats by pressing the left arrow.

When you're done viewing the qrcode, press the back button to return to the
file browser. If you push the back button in the file browser, the app will
exit.

I will ask that you temper your expectations: the Flipper Zero screen is small
and many readers may have difficulty reading the qrcodes, especially if they
are encoding a lot of data. I have successfully got my iPhone to read qrcodes
encoding phone numbers and wifi info, both of which are relatively short.
are encoding a lot of data. However, I have successfully got my iPhone to read
qrcodes encoding phone numbers, wifi info, and a url, all the way up to a
version 11 qrcode (ie, the largest size the screen will fit).

## Wifi QRCodes
## Example: Wifi QRCodes
Most phones can automatically connect to wifi networks from a qrcode. If you
should like to encode your wifi's connection info into a qrcode, here's how
you'd do it:
Expand Down
2 changes: 1 addition & 1 deletion application.fam
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
App(
appid="qrcode",
name="qrcode",
fap_version=(1,0),
fap_version=(1,1),
fap_description="Display qrcodes",
fap_author="Bob Matcuk",
fap_weburl="https://github.com/bmatcuk/flipperzero-qrcode",
Expand Down
162 changes: 142 additions & 20 deletions qrcode_app.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,17 @@ typedef struct {
ViewPort* view_port;

FuriMutex** mutex;
FuriString* message;
QRCode* qrcode;
uint8_t min_version;
uint8_t max_ecc_at_min_version;
bool loading;
bool too_long;
bool show_stats;
uint8_t selected_idx;
bool edit;
uint8_t set_version;
uint8_t set_ecc;
} QRCodeApp;

/**
Expand Down Expand Up @@ -107,11 +114,13 @@ static void render_callback(Canvas* canvas, void* ctx) {
uint8_t font_height = canvas_current_font_height(canvas);
uint8_t width = canvas_width(canvas);
uint8_t height = canvas_height(canvas);
if (instance->qrcode) {
if (instance->loading) {
canvas_draw_str_aligned(canvas, width / 2, height / 2, AlignCenter, AlignCenter, "Loading...");
} else if (instance->qrcode) {
uint8_t size = instance->qrcode->size;
uint8_t pixel_size = height / size;
uint8_t top = (height - pixel_size * size) / 2;
uint8_t left = instance->show_stats ? top : (width - pixel_size * size) / 2;
uint8_t left = ((instance->show_stats ? 65 : width) - pixel_size * size) / 2;
for (uint8_t y = 0; y < size; y++) {
for (uint8_t x = 0; x < size; x++) {
if (qrcode_getModule(instance->qrcode, x, y)) {
Expand All @@ -125,24 +134,44 @@ static void render_callback(Canvas* canvas, void* ctx) {
}

if (instance->show_stats) {
if (top < 2) top = 2;
left += size * pixel_size + top;
top = 10;
left = 66;

FuriString* str = furi_string_alloc();

furi_string_printf(str, "Ver: %i", instance->qrcode->version);
canvas_draw_str(canvas, left, top + font_height, furi_string_get_cstr(str));
if (!instance->edit || instance->selected_idx == 0) {
furi_string_printf(str, "Ver: %i", instance->set_version);
canvas_draw_str(canvas, left + 5, top + font_height, furi_string_get_cstr(str));
if (instance->selected_idx == 0) {
canvas_draw_triangle(canvas, left, top + font_height / 2, font_height - 4, 4, CanvasDirectionLeftToRight);
}
if (instance->edit) {
uint8_t arrow_left = left + 5 + canvas_string_width(canvas, "Ver: 8") / 2;
canvas_draw_triangle(canvas, arrow_left, top, font_height - 4, 4, CanvasDirectionBottomToTop);
canvas_draw_triangle(canvas, arrow_left, top + font_height + 1, font_height - 4, 4, CanvasDirectionTopToBottom);
}
}

furi_string_printf(str, "ECC: %c", get_ecc_char(instance->qrcode->ecc));
canvas_draw_str(canvas, left, 2 * (top + font_height), furi_string_get_cstr(str));
if (!instance->edit || instance->selected_idx == 1) {
furi_string_printf(str, "ECC: %c", get_ecc_char(instance->set_ecc));
canvas_draw_str(canvas, left + 5, 2 * font_height + top + 2, furi_string_get_cstr(str));
if (instance->selected_idx == 1) {
canvas_draw_triangle(canvas, left, 3 * font_height / 2 + top + 2, font_height - 4, 4, CanvasDirectionLeftToRight);
}
if (instance->edit) {
uint8_t arrow_left = left + 5 + canvas_string_width(canvas, "ECC: H") / 2;
canvas_draw_triangle(canvas, arrow_left, font_height + top + 2, font_height - 4, 4, CanvasDirectionBottomToTop);
canvas_draw_triangle(canvas, arrow_left, 2 * font_height + top + 3, font_height - 4, 4, CanvasDirectionTopToBottom);
}
}

furi_string_printf(str, "Mod: %c", get_mode_char(instance->qrcode->mode));
canvas_draw_str(canvas, left, 3 * (top + font_height), furi_string_get_cstr(str));
if (!instance->edit) {
furi_string_printf(str, "Mod: %c", get_mode_char(instance->qrcode->mode));
canvas_draw_str(canvas, left + 5, 3 * font_height + top + 4, furi_string_get_cstr(str));
}

furi_string_free(str);
}
} else if (instance->loading) {
canvas_draw_str_aligned(canvas, width / 2, height / 2, AlignCenter, AlignCenter, "Loading...");
} else {
uint8_t margin = (height - font_height * 2) / 3;
canvas_draw_str_aligned(canvas, width / 2, margin, AlignCenter, AlignTop, "Could not load qrcode.");
Expand Down Expand Up @@ -230,6 +259,36 @@ static void qrcode_free(QRCode* qrcode) {
free(qrcode);
}

/**
* Rebuild the qrcode. Assumes that instance->message is the message to encode,
* that the mutex has been acquired, and the specified version/ecc will be
* sufficiently large enough to encode the full message. It is also assumed
* that the old qrcode will be free'd by the caller.
* @param instance The qrcode app instance
* @param version The qrcode version to use
* @param ecc The qrcode ECC level to use
* @returns true if the qrcode was successfully created
*/
static bool rebuild_qrcode(QRCodeApp* instance, uint8_t version, uint8_t ecc) {
furi_assert(instance);
furi_assert(instance->message);

const char* cstr = furi_string_get_cstr(instance->message);
uint16_t len = strlen(cstr);
instance->qrcode = qrcode_alloc(version);

int8_t res = qrcode_initBytes(instance->qrcode, instance->qrcode->modules, version, ecc, (uint8_t*)cstr, len);
if (res != 0) {
FURI_LOG_E(TAG, "Could not create qrcode");

qrcode_free(instance->qrcode);
instance->qrcode = NULL;

return false;
}
return true;
}

/**
* Load a qrcode from a string
* @param instance The qrcode app instance
Expand All @@ -241,18 +300,30 @@ static bool qrcode_load_string(QRCodeApp* instance, FuriString* str) {
furi_assert(str);

furi_check(furi_mutex_acquire(instance->mutex, FuriWaitForever) == FuriStatusOk);
if (instance->message) {
furi_string_free(instance->message);
instance->message = NULL;
}
if (instance->qrcode) {
qrcode_free(instance->qrcode);
instance->qrcode = NULL;
}
instance->too_long = false;
instance->show_stats = false;
instance->selected_idx = 0;
instance->edit = false;

bool result = false;
do {
const char* cstr = furi_string_get_cstr(str);
uint16_t len = strlen(cstr);

instance->message = furi_string_alloc_set(str);
if (!instance->message) {
FURI_LOG_E(TAG, "Could not allocate message");
break;
}

// figure out the qrcode "mode"
uint8_t mode = MODE_BYTE;
if (is_numeric(cstr, len)) mode = MODE_NUMERIC;
Expand Down Expand Up @@ -285,17 +356,14 @@ static bool qrcode_load_string(QRCodeApp* instance, FuriString* str) {
version++;

// Build the qrcode
instance->qrcode = qrcode_alloc(version);
int8_t res = qrcode_initBytes(instance->qrcode, instance->qrcode->modules, version, ecc, (uint8_t*)cstr, len);
if (res != 0) {
FURI_LOG_E(TAG, "Could not create qrcode");

qrcode_free(instance->qrcode);
instance->qrcode = NULL;

if (!rebuild_qrcode(instance, version, ecc)) {
furi_string_free(instance->message);
instance->message = NULL;
break;
}

instance->min_version = instance->set_version = version;
instance->max_ecc_at_min_version = instance->set_ecc = ecc;
result = true;
} while (false);

Expand Down Expand Up @@ -370,10 +438,13 @@ static QRCodeApp* qrcode_app_alloc() {

instance->mutex = furi_mutex_alloc(FuriMutexTypeNormal);

instance->message = NULL;
instance->qrcode = NULL;
instance->loading = true;
instance->too_long = false;
instance->show_stats = false;
instance->selected_idx = 0;
instance->edit = false;

return instance;
}
Expand All @@ -383,6 +454,7 @@ static QRCodeApp* qrcode_app_alloc() {
* @param qrcode_app The app to free
*/
static void qrcode_app_free(QRCodeApp* instance) {
if (instance->message) furi_string_free(instance->message);
if (instance->qrcode) qrcode_free(instance->qrcode);

gui_remove_view_port(instance->gui, instance->view_port);
Expand Down Expand Up @@ -433,17 +505,67 @@ int32_t qrcode_app(void* p) {
furi_check(furi_mutex_acquire(instance->mutex, FuriWaitForever) == FuriStatusOk);

if (input.key == InputKeyBack) {
if (instance->message) {
furi_string_free(instance->message);
instance->message = NULL;
}
if (instance->qrcode) {
qrcode_free(instance->qrcode);
instance->qrcode = NULL;
}
instance->loading = true;
instance->edit = false;
furi_mutex_release(instance->mutex);
break;
} else if (input.key == InputKeyRight) {
instance->show_stats = true;
} else if (input.key == InputKeyLeft) {
instance->show_stats = false;
} else if (instance->show_stats && !instance->loading && instance->qrcode) {
if (input.key == InputKeyUp) {
if (!instance->edit) {
instance->selected_idx = MAX(0, instance->selected_idx - 1);
} else {
if (instance->selected_idx == 0 && instance->set_version < MAX_QRCODE_VERSION) {
instance->set_version++;
} else if (instance->selected_idx == 1) {
uint8_t max_ecc = instance->set_version == instance->min_version ? instance->max_ecc_at_min_version : ECC_HIGH;
if (instance->set_ecc < max_ecc) {
instance->set_ecc++;
}
}
}
} else if (input.key == InputKeyDown) {
if (!instance->edit) {
instance->selected_idx = MIN(1, instance->selected_idx + 1);
} else {
if (instance->selected_idx == 0 && instance->set_version > instance->min_version) {
instance->set_version--;
if (instance->set_version == instance->min_version) {
instance->set_ecc = MAX(instance->set_ecc, instance->max_ecc_at_min_version);
}
} else if (instance->selected_idx == 1 && instance->set_ecc > 0) {
instance->set_ecc--;
}
}
} else if (input.key == InputKeyOk) {
if (instance->edit && (instance->set_version != instance->qrcode->version || instance->set_ecc != instance->qrcode->ecc)) {
QRCode* qrcode = instance->qrcode;
instance->loading = true;

if (rebuild_qrcode(instance, instance->set_version, instance->set_ecc)) {
qrcode_free(qrcode);
} else {
FURI_LOG_E(TAG, "Could not rebuild qrcode");
instance->qrcode = qrcode;
instance->set_version = qrcode->version;
instance->set_ecc = qrcode->ecc;
}

instance->loading = false;
}
instance->edit = !instance->edit;
}
}

furi_mutex_release(instance->mutex);
Expand Down

0 comments on commit 0d6d173

Please sign in to comment.