Merge pull request #62 from Hoggormino/custom-size

Add a custom-size option for unrecognized infrared tags
This commit is contained in:
I12BP8
2026-09-17 17:17:20 +02:00
committed by GitHub
9 changed files with 212 additions and 10 deletions
+1
View File
@@ -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",
+4
View File
@@ -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;
+8
View File
@@ -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 {
+3
View File
@@ -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) = {
@@ -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*) = {
@@ -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 = {
+5
View File
@@ -27,6 +27,7 @@ typedef enum {
TagTinkerSceneWifiPlugins,
TagTinkerSceneWifiSetup,
TagTinkerSceneWifiRun,
TagTinkerSceneCustomSize,
TagTinkerSceneCount,
} TagTinkerScene;
@@ -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);
+130
View File
@@ -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);
}
+12
View File
@@ -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;
@@ -98,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);
@@ -129,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];
+37 -10
View File
@@ -59,6 +59,17 @@ void tagtinker_target_refresh_profile(TagTinkerTarget* target) {
memset(&target->profile, 0, sizeof(target->profile));
tagtinker_barcode_to_profile(target->barcode, &target->profile);
/* If the barcode's type is not in the table but the user gave a size, build
* a profile from it. A real table profile is left untouched. */
if(!target->profile.known && target->custom_width > 0 && target->custom_height > 0) {
target->profile.width = target->custom_width;
target->profile.height = target->custom_height;
target->profile.color = (TagTinkerTagColor)target->custom_color;
target->profile.kind = TagTinkerTagKindDotMatrix;
target->profile.model_name = "Custom";
target->profile.known = true;
}
}
void tagtinker_target_set_default_name(TagTinkerApp* app, TagTinkerTarget* target) {
@@ -512,22 +523,35 @@ void tagtinker_targets_load(TagTinkerApp* app) {
if(nl) *nl = '\0';
if(*line) {
char* sep = strchr(line, '|');
if(sep) *sep = '\0';
/* Split into up to 5 fields: barcode|name|cw|ch|ccolor.
* Older files have only barcode|name; the rest default to 0. */
char* fields[5] = {line, NULL, NULL, NULL, NULL};
char* p = line;
for(uint8_t f = 1; f < 5; f++) {
char* sep = strchr(p, '|');
if(!sep) break;
*sep = '\0';
fields[f] = sep + 1;
p = sep + 1;
}
if(tagtinker_barcode_to_plid(line, app->targets[app->target_count].plid)) {
if(tagtinker_barcode_to_plid(fields[0], app->targets[app->target_count].plid)) {
TagTinkerTarget* target = &app->targets[app->target_count];
strncpy(target->barcode, line, TAGTINKER_BC_LEN);
memset(target, 0, sizeof(*target));
strncpy(target->barcode, fields[0], TAGTINKER_BC_LEN);
target->barcode[TAGTINKER_BC_LEN] = '\0';
memset(target->name, 0, sizeof(target->name));
if(sep && *(sep + 1)) {
strncpy(target->name, sep + 1, TAGTINKER_TARGET_NAME_LEN);
if(fields[1] && *fields[1]) {
strncpy(target->name, fields[1], TAGTINKER_TARGET_NAME_LEN);
target->name[TAGTINKER_TARGET_NAME_LEN] = '\0';
} else {
tagtinker_target_set_default_name(app, target);
}
if(fields[2]) target->custom_width = (uint16_t)atoi(fields[2]);
if(fields[3]) target->custom_height = (uint16_t)atoi(fields[3]);
if(fields[4]) target->custom_color = (uint8_t)atoi(fields[4]);
tagtinker_target_refresh_profile(target);
app->target_count++;
}
@@ -551,13 +575,16 @@ bool tagtinker_targets_save(const TagTinkerApp* app) {
if(storage_file_open(file, APP_DATA_PATH("targets.txt"), FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
ok = true;
for(uint8_t i = 0; i < app->target_count; i++) {
char line[64];
char line[96];
int len = snprintf(
line,
sizeof(line),
"%s|%s\n",
"%s|%s|%u|%u|%u\n",
app->targets[i].barcode,
app->targets[i].name);
app->targets[i].name,
app->targets[i].custom_width,
app->targets[i].custom_height,
app->targets[i].custom_color);
if(len <= 0 || !storage_file_write(file, line, (uint16_t)len)) {
ok = false;
+12
View File
@@ -105,6 +105,12 @@ typedef struct {
char barcode[TAGTINKER_BC_LEN + 1];
uint8_t plid[4];
TagTinkerTagProfile profile;
/* User-entered geometry for a tag whose type code is not in the profile
* table. 0x0 means unset. Applied by tagtinker_target_refresh_profile only
* when the barcode's type is unknown, so a real profile is never overridden. */
uint16_t custom_width;
uint16_t custom_height;
uint8_t custom_color; /* TagTinkerTagColor */
} TagTinkerTarget;
struct TagTinkerApp {
@@ -156,6 +162,11 @@ struct TagTinkerApp {
/* Saved targets */
TagTinkerTarget targets[TAGTINKER_MAX_TARGETS];
/* Custom-size scene working values. */
uint16_t custom_size_w;
uint16_t custom_size_h;
uint8_t custom_size_color;
uint8_t target_count;
/* Text to push */
@@ -294,6 +305,7 @@ typedef enum {
TagTinkerTargetDeleteSyncedImages,
TagTinkerTargetPingFlash,
TagTinkerTargetDeleteTag,
TagTinkerTargetCustomSize,
} TagTinkerTargetActionItem;
void tagtinker_target_refresh_profile(TagTinkerTarget* target);