Files
TagTinker/protocol/tagtinker_proto.h
T
HoggorminoandClaude Opus 4.8 0447f12164 Add a custom-size option for unrecognized infrared tags
A tag whose type code is not in the profile table has no dimensions, so
after the unknown-profile guard its Set Text / Set Image / WiFi Plugins
actions are hidden. This adds a "Set Custom Size" action for those tags:
enter width, height and colour, and the graphics actions become usable.

The IR framing is identical to a table profile; only the screen geometry
differs, so no protocol is guessed. Custom size is applied only when the
barcode's type is not in the table, so a real profile is never overridden.

- New scene (VariableItemList): Width and Height in 8 px steps from 8 to
  800, Colour mono/red/yellow, and Save.
- tagtinker_target_refresh_profile() synthesizes a "Custom" profile from
  the stored size for unknown-type targets, so Show Tag Info, Set Text
  and Set Image all see real dimensions.
- targets.txt gains three optional fields (barcode|name|cw|ch|ccolor);
  older two-field lines still load, with no custom size.
- "Set Custom Size" appears in Target Actions only for unknown-type
  targets (tagtinker_type_is_known()).

Builds clean on release SDK 1.4.3 (API 87.1) and dev SDK; no new
clang-format drift.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-16 21:58:55 +02:00

206 lines
6.1 KiB
C

/*
* ESL protocol helpers.
*
* This layer turns barcodes, pixels, and payload bytes into the frames
* that the Flipper sends over IR to the tag.
*
* SPDX-License-Identifier: GPL-3.0-only
*/
#pragma once
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#define TAGTINKER_PROTO_DM 0x85
#define TAGTINKER_PROTO_SEG 0x84
#define TAGTINKER_MAX_FRAME_SIZE 96
#define TAGTINKER_IMAGE_DATA_BYTES_PER_FRAME 20U
/* Color 2.6 glass is landscape 296x152. Image header is 152x296 (short x long). */
#define TAGTINKER_TYPE_SMARTAG_COLOR_26 1626
#define TAGTINKER_COLOR26_WIRE_W 152U
#define TAGTINKER_COLOR26_WIRE_H 296U
#define TAGTINKER_COLOR26_GLASS_W 296U
#define TAGTINKER_COLOR26_GLASS_H 152U
static inline bool tagtinker_type_needs_wh_swap(uint16_t type_code) {
return type_code == TAGTINKER_TYPE_SMARTAG_COLOR_26;
}
static inline bool tagtinker_type_uses_ui_page(uint16_t type_code) {
return tagtinker_type_needs_wh_swap(type_code);
}
/* Store-used Color 2.6 tags keep the barcode on page 1. Page 2 is the image
* slot. Unspecified pages (0 or 1) map there. Explicit 2-7 are left alone. */
static inline uint8_t tagtinker_color26_resolve_page(uint8_t page) {
if(page <= 1U) return 2U;
if(page > 7U) return 7U;
return page;
}
/* Wire (px, py) on 152x296 -> glass (bx, by) 296x152. */
static inline void tagtinker_color26_proto_to_glass(
uint16_t proto_w,
uint16_t px,
uint16_t py,
uint16_t* bx,
uint16_t* by) {
*bx = py;
*by = (uint16_t)(proto_w - 1U - px);
}
typedef struct TagTinkerApp TagTinkerApp;
/* CRC used by the ESL wire format. */
uint16_t tagtinker_crc16(const uint8_t* data, size_t len);
typedef enum {
TagTinkerTagKindUnknown = 0,
TagTinkerTagKindDotMatrix,
TagTinkerTagKindSegment,
} TagTinkerTagKind;
typedef enum {
TagTinkerTagColorMono = 0,
TagTinkerTagColorRed,
TagTinkerTagColorYellow,
} TagTinkerTagColor;
typedef struct {
uint16_t type_code;
uint16_t width;
uint16_t height;
TagTinkerTagKind kind;
TagTinkerTagColor color;
const char* model_name;
uint8_t pl_bit_def;
bool known;
} TagTinkerTagProfile;
static inline bool tagtinker_profile_needs_wh_swap(const TagTinkerTagProfile* profile) {
return profile && tagtinker_type_needs_wh_swap(profile->type_code);
}
static inline bool tagtinker_profile_uses_ui_page(const TagTinkerTagProfile* profile) {
return profile && tagtinker_type_uses_ui_page(profile->type_code);
}
static inline void tagtinker_profile_glass_size(
const TagTinkerTagProfile* profile,
uint16_t* width,
uint16_t* height) {
if(!profile || !width || !height) return;
if(tagtinker_profile_needs_wh_swap(profile)) {
*width = TAGTINKER_COLOR26_GLASS_W;
*height = TAGTINKER_COLOR26_GLASS_H;
return;
}
*width = profile->width;
*height = profile->height;
}
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 {
uint8_t* data;
size_t byte_count;
uint8_t comp_type;
} TagTinkerImagePayload;
typedef enum {
TagTinkerCompressionAuto = 0,
TagTinkerCompressionRaw,
TagTinkerCompressionRle,
} TagTinkerCompressionMode;
bool tagtinker_encode_image_payload(
const uint8_t* pixels,
uint16_t width,
uint16_t height,
bool color_clear,
TagTinkerCompressionMode mode,
TagTinkerImagePayload* payload);
typedef uint8_t (*TagTinkerPixelAtFn)(size_t idx, void* ctx);
bool tagtinker_encode_fn_payload(
TagTinkerPixelAtFn pixel_at,
void* ctx,
size_t total_pixels,
TagTinkerCompressionMode mode,
TagTinkerImagePayload* payload);
bool tagtinker_encode_planes_payload(
const uint8_t* primary_pixels,
const uint8_t* secondary_pixels,
size_t pixel_count,
TagTinkerCompressionMode mode,
TagTinkerImagePayload* payload);
void tagtinker_free_image_payload(TagTinkerImagePayload* payload);
size_t tagtinker_make_image_param_frame(
uint8_t* buf,
const uint8_t plid[4],
uint16_t byte_count,
uint8_t comp_type,
uint8_t page,
uint16_t width,
uint16_t height,
uint16_t pos_x,
uint16_t pos_y);
size_t tagtinker_make_image_data_frame(
uint8_t* buf,
const uint8_t plid[4],
uint16_t frame_index,
const uint8_t data_bytes[TAGTINKER_IMAGE_DATA_BYTES_PER_FRAME]);
/* Broadcast frames address every listening tag. */
size_t tagtinker_build_broadcast_page_frame(
uint8_t* buf, uint8_t page, bool forever, uint16_t duration);
size_t tagtinker_build_broadcast_debug_frame(uint8_t* buf);
/* Addressed frames use the PLID decoded from the barcode. */
size_t tagtinker_make_addressed_frame(
uint8_t* buf, const uint8_t plid[4],
const uint8_t* payload, size_t payload_len);
/* Tags need a wake ping before most addressed commands. */
size_t tagtinker_make_ping_frame(uint8_t* buf, const uint8_t plid[4]);
size_t tagtinker_make_wake_frame(uint8_t* buf, const uint8_t plid[4]);
size_t tagtinker_make_refresh_frame(uint8_t* buf, const uint8_t plid[4]);
/* Image upload uses MCU frames: one parameter frame followed by data frames. */
size_t tagtinker_make_mcu_frame(
uint8_t* buf, const uint8_t plid[4], uint8_t cmd);
/* RLE is the tag's compact bitmap format. Raw mode keeps one bit per pixel. */
size_t tagtinker_rle_compress(
const uint8_t* pixels, size_t count,
uint8_t* out, size_t out_cap, uint8_t* comp_type);
/* Builds the full IR sequence: wake, image params, data chunks, refresh. */
void tagtinker_build_image_sequence(
TagTinkerApp* app,
const uint8_t plid[4],
const uint8_t* pixels,
uint16_t width, uint16_t height,
uint8_t page,
uint16_t pos_x, uint16_t pos_y,
uint16_t wake_repeats);