Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions application.fam
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ App(
"views/numlock_input.c",
"nfc/tagtinker_nfc.c",
"scenes/tagtinker_scene_nfc_scan.c",
"scenes/tagtinker_scene_custom_size.c",
"wifi/tagtinker_wifi.c",
"wifi/tagtinker_wifi_bmp.c",
"scenes/tagtinker_scene_wifi_plugins.c",
Expand Down
4 changes: 4 additions & 0 deletions protocol/tagtinker_proto.c
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ bool tagtinker_barcode_to_plid(const char* barcode, uint8_t plid[4]) {
return true;
}

bool tagtinker_type_is_known(uint16_t type_code) {
return find_profile_entry(type_code) != NULL;
}

bool tagtinker_barcode_to_type(const char* barcode, uint16_t* type_code) {
if(!barcode || strlen(barcode) != 17 || !type_code) return false;
uint16_t type = 0;
Expand Down
8 changes: 8 additions & 0 deletions protocol/tagtinker_proto.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,14 @@ static inline void tagtinker_profile_glass_size(
bool tagtinker_is_barcode_valid(const char* barcode);
bool tagtinker_barcode_to_plid(const char* barcode, uint8_t plid[4]);
bool tagtinker_barcode_to_type(const char* barcode, uint16_t* type_code);

/* True when this type code has an entry in the built-in profile table. */
bool tagtinker_type_is_known(uint16_t type_code);

/* Bounds for a user-entered custom tag size (pixels). Width is byte-aligned. */
#define TAGTINKER_CUSTOM_SIZE_MIN 8U
#define TAGTINKER_CUSTOM_SIZE_MAX 800U
#define TAGTINKER_CUSTOM_SIZE_STEP 8U
bool tagtinker_barcode_to_profile(const char* barcode, TagTinkerTagProfile* profile);

typedef struct {
Expand Down
3 changes: 3 additions & 0 deletions scenes/tagtinker_scene.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ void(*const tagtinker_scene_on_enter_handlers[])(void*) = {
tagtinker_scene_wifi_plugins_on_enter,
tagtinker_scene_wifi_setup_on_enter,
tagtinker_scene_wifi_run_on_enter,
tagtinker_scene_custom_size_on_enter,
};

bool(*const tagtinker_scene_on_event_handlers[])(void*, SceneManagerEvent) = {
Expand All @@ -48,6 +49,7 @@ bool(*const tagtinker_scene_on_event_handlers[])(void*, SceneManagerEvent) = {
tagtinker_scene_wifi_plugins_on_event,
tagtinker_scene_wifi_setup_on_event,
tagtinker_scene_wifi_run_on_event,
tagtinker_scene_custom_size_on_event,
};

void(*const tagtinker_scene_on_exit_handlers[])(void*) = {
Expand All @@ -71,6 +73,7 @@ void(*const tagtinker_scene_on_exit_handlers[])(void*) = {
tagtinker_scene_wifi_plugins_on_exit,
tagtinker_scene_wifi_setup_on_exit,
tagtinker_scene_wifi_run_on_exit,
tagtinker_scene_custom_size_on_exit,
};

const SceneManagerHandlers tagtinker_scene_handlers = {
Expand Down
5 changes: 5 additions & 0 deletions scenes/tagtinker_scene.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ typedef enum {
TagTinkerSceneWifiPlugins,
TagTinkerSceneWifiSetup,
TagTinkerSceneWifiRun,
TagTinkerSceneCustomSize,
TagTinkerSceneCount,
} TagTinkerScene;

Expand Down Expand Up @@ -109,3 +110,7 @@ void tagtinker_scene_wifi_setup_on_exit(void* ctx);
void tagtinker_scene_wifi_run_on_enter(void* ctx);
bool tagtinker_scene_wifi_run_on_event(void* ctx, SceneManagerEvent event);
void tagtinker_scene_wifi_run_on_exit(void* ctx);

void tagtinker_scene_custom_size_on_enter(void* ctx);
bool tagtinker_scene_custom_size_on_event(void* ctx, SceneManagerEvent event);
void tagtinker_scene_custom_size_on_exit(void* ctx);
27 changes: 23 additions & 4 deletions scenes/tagtinker_scene_barcode_input.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@

static void unsupported_tag_back_cb(GuiButtonType type, InputType input_type, void* ctx) {
UNUSED(type);
UNUSED(input_type);
/* Widget buttons report press, short and release alike; act once. */
if(input_type != InputTypeShort) return;
TagTinkerApp* app = ctx;
scene_manager_previous_scene(app->scene_manager);
}
Expand Down Expand Up @@ -60,12 +61,30 @@ bool tagtinker_scene_barcode_input_on_event(void* ctx, SceneManagerEvent event)



app->selected_target = tagtinker_ensure_target(app, app->barcode);
int8_t idx = tagtinker_ensure_target(app, app->barcode);

if(app->selected_target >= 0) {
tagtinker_select_target(app, (uint8_t)app->selected_target);
if(idx < 0) {
/* The barcode already passed validation, so the only way left to fail
* is a full target list. Do not open the target actions with index -1. */
widget_reset(app->widget);
widget_add_string_element(
app->widget, 64, 10, AlignCenter, AlignTop, FontPrimary, "Target List Full");
widget_add_string_multiline_element(
app->widget,
64,
30,
AlignCenter,
AlignTop,
FontSecondary,
"Delete a saved tag\nto add a new one.");
widget_add_button_element(
app->widget, GuiButtonTypeLeft, "Back", unsupported_tag_back_cb, app);
view_dispatcher_switch_to_view(app->view_dispatcher, TagTinkerViewWidget);
return true;
}

tagtinker_select_target(app, (uint8_t)idx);

uint32_t target_scene = scene_manager_get_scene_state(
app->scene_manager, TagTinkerSceneBarcodeInput);
scene_manager_next_scene(app->scene_manager, target_scene);
Expand Down
130 changes: 130 additions & 0 deletions scenes/tagtinker_scene_custom_size.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* Custom size scene.
*
* Lets the user give a width, height and colour to a target whose type code is
* not in the profile table, so Set Text and Set Image work for it. The IR
* framing is identical to a table profile; only the screen geometry differs.
* This does not guess any protocol and only applies to unknown-type targets.
*/

#include "../tagtinker_app.h"
#include "../protocol/tagtinker_proto.h"

enum {
CustomSizeWidth,
CustomSizeHeight,
CustomSizeColor,
CustomSizeSave,
};

/* index 0 -> MIN, then MIN + i*STEP, up to MAX. */
static uint32_t size_value_count(void) {
return (TAGTINKER_CUSTOM_SIZE_MAX - TAGTINKER_CUSTOM_SIZE_MIN) / TAGTINKER_CUSTOM_SIZE_STEP +
1U;
}

static uint16_t size_index_to_px(uint8_t index) {
return (uint16_t)(TAGTINKER_CUSTOM_SIZE_MIN + (uint32_t)index * TAGTINKER_CUSTOM_SIZE_STEP);
}

static uint8_t size_px_to_index(uint16_t px) {
if(px < TAGTINKER_CUSTOM_SIZE_MIN) return 0U;
uint16_t clamped = px > TAGTINKER_CUSTOM_SIZE_MAX ? TAGTINKER_CUSTOM_SIZE_MAX : px;
return (uint8_t)((clamped - TAGTINKER_CUSTOM_SIZE_MIN) / TAGTINKER_CUSTOM_SIZE_STEP);
}

static void width_changed(VariableItem* item) {
TagTinkerApp* app = variable_item_get_context(item);
app->custom_size_w = size_index_to_px(variable_item_get_current_value_index(item));
char buf[8];
snprintf(buf, sizeof(buf), "%u", app->custom_size_w);
variable_item_set_current_value_text(item, buf);
}

static void height_changed(VariableItem* item) {
TagTinkerApp* app = variable_item_get_context(item);
app->custom_size_h = size_index_to_px(variable_item_get_current_value_index(item));
char buf[8];
snprintf(buf, sizeof(buf), "%u", app->custom_size_h);
variable_item_set_current_value_text(item, buf);
}

static void color_changed(VariableItem* item) {
TagTinkerApp* app = variable_item_get_context(item);
app->custom_size_color = (uint8_t)variable_item_get_current_value_index(item);
variable_item_set_current_value_text(
item, tagtinker_profile_color_label((TagTinkerTagColor)app->custom_size_color));
}

static void custom_size_cb(void* ctx, uint32_t index) {
TagTinkerApp* app = ctx;
if(index != CustomSizeSave) return;
if(app->selected_target < 0 || app->selected_target >= app->target_count) return;

TagTinkerTarget* target = &app->targets[app->selected_target];
target->custom_width = app->custom_size_w;
target->custom_height = app->custom_size_h;
target->custom_color = app->custom_size_color;
tagtinker_target_refresh_profile(target);
tagtinker_targets_save(app);

/* Keep the app's working size in step with the target we just sized. */
app->esl_width = target->profile.width;
app->esl_height = target->profile.height;

scene_manager_previous_scene(app->scene_manager);
}

void tagtinker_scene_custom_size_on_enter(void* ctx) {
TagTinkerApp* app = ctx;

uint16_t w = TAGTINKER_CUSTOM_SIZE_MIN;
uint16_t h = TAGTINKER_CUSTOM_SIZE_MIN;
uint8_t color = 0U;
if(app->selected_target >= 0 && app->selected_target < app->target_count) {
const TagTinkerTarget* t = &app->targets[app->selected_target];
if(t->custom_width > 0) w = t->custom_width;
if(t->custom_height > 0) h = t->custom_height;
color = t->custom_color;
}
app->custom_size_w = w;
app->custom_size_h = h;
app->custom_size_color = color;

variable_item_list_reset(app->var_item_list);

char buf[8];
VariableItem* item_w = variable_item_list_add(
app->var_item_list, "Width", (uint8_t)size_value_count(), width_changed, app);
variable_item_set_current_value_index(item_w, size_px_to_index(w));
snprintf(buf, sizeof(buf), "%u", w);
variable_item_set_current_value_text(item_w, buf);

VariableItem* item_h = variable_item_list_add(
app->var_item_list, "Height", (uint8_t)size_value_count(), height_changed, app);
variable_item_set_current_value_index(item_h, size_px_to_index(h));
snprintf(buf, sizeof(buf), "%u", h);
variable_item_set_current_value_text(item_h, buf);

VariableItem* item_c =
variable_item_list_add(app->var_item_list, "Color", 3, color_changed, app);
variable_item_set_current_value_index(item_c, color);
variable_item_set_current_value_text(
item_c, tagtinker_profile_color_label((TagTinkerTagColor)color));

variable_item_list_add(app->var_item_list, ">> Save <<", 0, NULL, app);
variable_item_list_set_enter_callback(app->var_item_list, custom_size_cb, app);

view_dispatcher_switch_to_view(app->view_dispatcher, TagTinkerViewVarItemList);
}

bool tagtinker_scene_custom_size_on_event(void* ctx, SceneManagerEvent event) {
UNUSED(ctx);
UNUSED(event);
return false;
}

void tagtinker_scene_custom_size_on_exit(void* ctx) {
TagTinkerApp* app = ctx;
variable_item_list_reset(app->var_item_list);
}
33 changes: 33 additions & 0 deletions scenes/tagtinker_scene_target_actions.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/

#include "../tagtinker_app.h"
#include "../protocol/tagtinker_proto.h"

static void target_actions_cb(void* ctx, uint32_t index) {
TagTinkerApp* app = ctx;
Expand All @@ -29,6 +30,27 @@ static void show_target_details(TagTinkerApp* app, const TagTinkerTarget* target
text_box_set_focus(app->text_box, TextBoxFocusStart);

static char details_buf[256];

if(!target->profile.known) {
/* No table entry: size and colour are unknown, not 0x0 and Mono. */
snprintf(
details_buf,
sizeof(details_buf),
"--- Tag Info ---\n"
"Model: Unknown\n"
"Type: %u\n"
"Not in the profile table,\n"
"so Set Text, Set Image\n"
"and WiFi Plugins are\n"
"hidden for this tag.\n"
"Barcode:\n%s",
target->profile.type_code,
target->barcode);
text_box_set_text(app->text_box, details_buf);
scene_manager_next_scene(app->scene_manager, TagTinkerSceneTextBox);
return;
}

uint16_t size_w = target->profile.width;
uint16_t size_h = target->profile.height;
tagtinker_profile_glass_size(&target->profile, &size_w, &size_h);
Expand Down Expand Up @@ -77,6 +99,14 @@ void tagtinker_scene_target_actions_on_enter(void* ctx) {
submenu_add_item(app->submenu, "WiFi Plugins", TagTinkerTargetWifiPlugins, target_actions_cb, app);
}

/* An unknown type has no table profile; let the user give it a size so the
* graphics actions above become usable. Shown whether or not a size is set,
* so it can be changed later. */
if(target && !tagtinker_type_is_known(target->profile.type_code)) {
submenu_add_item(
app->submenu, "Set Custom Size", TagTinkerTargetCustomSize, target_actions_cb, app);
}

submenu_add_item(app->submenu, "LED Test", TagTinkerTargetPingFlash, target_actions_cb, app);
submenu_add_item(app->submenu, "Delete Tag", TagTinkerTargetDeleteTag, target_actions_cb, app);

Expand Down Expand Up @@ -108,6 +138,9 @@ bool tagtinker_scene_target_actions_on_event(void* ctx, SceneManagerEvent event)
if(!tagtinker_target_supports_graphics(&app->targets[app->selected_target])) return true;
scene_manager_next_scene(app->scene_manager, TagTinkerSceneWifiPlugins);
return true;
case TagTinkerTargetCustomSize:
scene_manager_next_scene(app->scene_manager, TagTinkerSceneCustomSize);
return true;
case TagTinkerTargetPingFlash:
{
TagTinkerTarget* target = &app->targets[app->selected_target];
Expand Down
5 changes: 5 additions & 0 deletions scenes/tagtinker_scene_transmit.c
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,11 @@ static bool tx_send_color26_text(TagTinkerApp* app) {
}

static bool tx_stream_text_image(TagTinkerApp* app) {
/* A target without a table profile has no dimensions. Refuse here: both
* text paths below would otherwise call malloc(0), which the firmware
* treats as a fatal error. */
if(app->image_tx_job.width == 0U || app->image_tx_job.height == 0U) return false;

if(tx_is_color26(app)) {
return tx_send_color26_text(app);
}
Expand Down
Loading