mirror of
https://github.com/i12bp8/TagTinker.git
synced 2026-08-26 20:40:19 +00:00
TagTinker V1.3 Android Companion, Extra Settings, ESL Type Detection, Bug Fixes
This commit is contained in:
+21
-23
@@ -1,7 +1,10 @@
|
||||
/*
|
||||
* TagTinker - ESL protocol helpers (implementation)
|
||||
* ESL protocol helpers.
|
||||
*
|
||||
* Ported from furrtek/TagTinker tools_python/pr.py and img2dm.py
|
||||
* This file covers three jobs:
|
||||
* 1. Decode a barcode into the tag address and known display profile.
|
||||
* 2. Pack pixels into the tag's raw or RLE bitmap format.
|
||||
* 3. Wrap those bytes into the IR frames that the tag understands.
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
@@ -11,8 +14,6 @@
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* ── Helpers ────────────────────────────────────────────────── */
|
||||
|
||||
typedef struct {
|
||||
uint16_t type_code;
|
||||
uint16_t width;
|
||||
@@ -21,6 +22,7 @@ typedef struct {
|
||||
TagTinkerTagColor color;
|
||||
} TagTinkerProfileEntry;
|
||||
|
||||
/* Known type codes seen in ESL barcodes. */
|
||||
static const TagTinkerProfileEntry profile_table[] = {
|
||||
{1206, 0, 0, TagTinkerTagKindSegment, TagTinkerTagColorMono},
|
||||
{1207, 0, 0, TagTinkerTagKindSegment, TagTinkerTagColorMono},
|
||||
@@ -86,6 +88,7 @@ static size_t terminate(uint8_t* buf, size_t len) {
|
||||
|
||||
static size_t raw_frame(uint8_t* buf, uint8_t proto,
|
||||
const uint8_t plid[4], uint8_t cmd) {
|
||||
/* Every addressed frame starts with protocol byte, PLID, then command. */
|
||||
buf[0] = proto;
|
||||
buf[1] = plid[3]; buf[2] = plid[2];
|
||||
buf[3] = plid[1]; buf[4] = plid[0];
|
||||
@@ -94,6 +97,7 @@ static size_t raw_frame(uint8_t* buf, uint8_t proto,
|
||||
}
|
||||
|
||||
static size_t mcu_frame(uint8_t* buf, const uint8_t plid[4], uint8_t cmd) {
|
||||
/* Image upload is tunneled through command 0x34 with an inner MCU opcode. */
|
||||
size_t p = raw_frame(buf, TAGTINKER_PROTO_DM, plid, 0x34);
|
||||
buf[p++] = 0x00;
|
||||
buf[p++] = 0x00;
|
||||
@@ -102,8 +106,6 @@ static size_t mcu_frame(uint8_t* buf, const uint8_t plid[4], uint8_t cmd) {
|
||||
return p;
|
||||
}
|
||||
|
||||
/* ── CRC-16 (poly 0x8408, init 0x8408) ─────────────────────── */
|
||||
|
||||
uint16_t tagtinker_crc16(const uint8_t* data, size_t len) {
|
||||
uint16_t crc = 0x8408;
|
||||
for(size_t i = 0; i < len; i++) {
|
||||
@@ -114,8 +116,6 @@ uint16_t tagtinker_crc16(const uint8_t* data, size_t len) {
|
||||
return crc;
|
||||
}
|
||||
|
||||
/* ── Barcode → PLID ─────────────────────────────────────────── */
|
||||
|
||||
bool tagtinker_barcode_to_plid(const char* barcode, uint8_t plid[4]) {
|
||||
if(!barcode || strlen(barcode) != 17) return false;
|
||||
for(int i = 2; i < 12; i++)
|
||||
@@ -166,8 +166,6 @@ bool tagtinker_barcode_to_profile(const char* barcode, TagTinkerTagProfile* prof
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ── Frame builders ─────────────────────────────────────────── */
|
||||
|
||||
size_t tagtinker_build_broadcast_page_frame(
|
||||
uint8_t* buf, uint8_t page, bool forever, uint16_t duration) {
|
||||
|
||||
@@ -231,10 +229,9 @@ static void record_run(uint8_t* out, size_t* pos, size_t cap, uint32_t run_count
|
||||
for(int i = 0; i < n / 2; i++) {
|
||||
uint8_t t = bits[i]; bits[i] = bits[n - 1 - i]; bits[n - 1 - i] = t;
|
||||
}
|
||||
/* Prefix zeros (n-1 of them, skip leading 1) */
|
||||
/* Runs are unary-prefixed: zeros mark bit-length, then the count bits follow. */
|
||||
for(int i = 1; i < n; i++)
|
||||
if(*pos < cap) out[(*pos)++] = 0;
|
||||
/* The bits themselves */
|
||||
for(int i = 0; i < n; i++)
|
||||
if(*pos < cap) out[(*pos)++] = bits[i];
|
||||
}
|
||||
@@ -263,10 +260,10 @@ size_t tagtinker_rle_compress(
|
||||
if(run_count > 1) record_run(out, &pos, out_cap, run_count);
|
||||
|
||||
if(pos < count) {
|
||||
*comp_type = 2; /* RLE */
|
||||
*comp_type = 2;
|
||||
return pos;
|
||||
}
|
||||
/* Compression didn't help — use raw */
|
||||
|
||||
memcpy(out, pixels, count < out_cap ? count : out_cap);
|
||||
*comp_type = 0;
|
||||
return count < out_cap ? count : out_cap;
|
||||
@@ -411,6 +408,7 @@ bool tagtinker_encode_planes_payload(
|
||||
if(mode == TagTinkerCompressionRle) {
|
||||
use_compressed = true;
|
||||
} else if(mode == TagTinkerCompressionAuto) {
|
||||
/* Auto mode picks RLE only when it is smaller than the raw bitstream. */
|
||||
use_compressed = (comp_len > 0U) && (comp_len < total_pixels);
|
||||
}
|
||||
size_t src_len = use_compressed ? comp_len : total_pixels;
|
||||
@@ -474,6 +472,7 @@ size_t tagtinker_make_image_param_frame(
|
||||
uint16_t height,
|
||||
uint16_t pos_x,
|
||||
uint16_t pos_y) {
|
||||
/* Command 0x05 tells the tag how many bytes are coming and where to place them. */
|
||||
size_t p = mcu_frame(buf, plid, 0x05);
|
||||
append_word(buf, &p, byte_count);
|
||||
buf[p++] = 0x00;
|
||||
@@ -495,6 +494,7 @@ size_t tagtinker_make_image_data_frame(
|
||||
const uint8_t plid[4],
|
||||
uint16_t frame_index,
|
||||
const uint8_t data_bytes[20]) {
|
||||
/* Command 0x20 carries one fixed 20-byte image block. */
|
||||
size_t p = mcu_frame(buf, plid, 0x20);
|
||||
append_word(buf, &p, frame_index);
|
||||
memcpy(&buf[p], data_bytes, DATA_BYTES_PER_FRAME);
|
||||
@@ -502,8 +502,6 @@ size_t tagtinker_make_image_data_frame(
|
||||
return terminate(buf, p);
|
||||
}
|
||||
|
||||
/* ── Image upload sequence builder ──────────────────────────── */
|
||||
|
||||
void tagtinker_build_image_sequence(
|
||||
TagTinkerApp* app,
|
||||
const uint8_t plid[4],
|
||||
@@ -517,6 +515,8 @@ void tagtinker_build_image_sequence(
|
||||
if(!tagtinker_encode_image_payload(
|
||||
pixels, width, height, app->color_clear, app->compression_mode, &payload))
|
||||
return;
|
||||
|
||||
/* The tag expects 20 data bytes per frame, so pad the payload to that boundary. */
|
||||
size_t frame_count = payload.byte_count / DATA_BYTES_PER_FRAME;
|
||||
|
||||
FURI_LOG_I("TagTinker", "IMG %ux%u pg=%u comp=%u %zu->%zu frames=%zu",
|
||||
@@ -528,7 +528,6 @@ void tagtinker_build_image_sequence(
|
||||
payload.byte_count,
|
||||
frame_count);
|
||||
|
||||
/* Total: ping + params + N data + refresh */
|
||||
size_t total = 2 + frame_count + 1;
|
||||
|
||||
app->frame_seq_count = total;
|
||||
@@ -544,13 +543,13 @@ void tagtinker_build_image_sequence(
|
||||
|
||||
size_t idx = 0;
|
||||
|
||||
/* 1. Ping */
|
||||
/* Wake the tag before sending the upload. */
|
||||
app->frame_sequence[idx] = malloc(TAGTINKER_MAX_FRAME_SIZE);
|
||||
app->frame_lengths[idx] = tagtinker_make_ping_frame(app->frame_sequence[idx], plid);
|
||||
app->frame_repeats[idx] = wake_repeats;
|
||||
idx++;
|
||||
|
||||
/* 2. Parameters (cmd 0x05) */
|
||||
/* The parameter frame describes size, page, compression mode, and placement. */
|
||||
app->frame_sequence[idx] = malloc(TAGTINKER_MAX_FRAME_SIZE);
|
||||
app->frame_lengths[idx] = tagtinker_make_image_param_frame(
|
||||
app->frame_sequence[idx],
|
||||
@@ -565,22 +564,21 @@ void tagtinker_build_image_sequence(
|
||||
app->frame_repeats[idx] = 1;
|
||||
idx++;
|
||||
|
||||
/* 3..N+2. Data frames (cmd 0x20) */
|
||||
/* Data frames follow in order and carry the packed bitmap bytes. */
|
||||
for(size_t fi = 0; fi < frame_count; fi++) {
|
||||
app->frame_sequence[idx] = malloc(TAGTINKER_MAX_FRAME_SIZE);
|
||||
size_t start = fi * DATA_BYTES_PER_FRAME;
|
||||
app->frame_lengths[idx] = tagtinker_make_image_data_frame(
|
||||
app->frame_sequence[idx], plid, (uint16_t)fi, &payload.data[start]);
|
||||
app->frame_repeats[idx] = 3; /* 3 repeats per data frame for reliability */
|
||||
app->frame_repeats[idx] = 3;
|
||||
idx++;
|
||||
}
|
||||
|
||||
/* N+3. Refresh */
|
||||
/* Refresh asks the tag to display the uploaded image. */
|
||||
app->frame_sequence[idx] = malloc(TAGTINKER_MAX_FRAME_SIZE);
|
||||
app->frame_lengths[idx] = tagtinker_make_refresh_frame(app->frame_sequence[idx], plid);
|
||||
app->frame_repeats[idx] = 1;
|
||||
|
||||
/* Copy first data frame for display in TX scene */
|
||||
if(app->frame_seq_count > 1) {
|
||||
memcpy(app->frame_buf, app->frame_sequence[1],
|
||||
app->frame_lengths[1] < TAGTINKER_MAX_FRAME_SIZE
|
||||
|
||||
+12
-24
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* TagTinker - ESL protocol helpers
|
||||
* ESL protocol helpers.
|
||||
*
|
||||
* Frame construction, CRC, and encoding for the infrared
|
||||
* ESL protocol used by this project. Ported from furrtek/TagTinker.
|
||||
* This layer turns barcodes, pixels, and payload bytes into the frames
|
||||
* that the Flipper sends over IR to the tag.
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
@@ -13,19 +13,15 @@
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define TAGTINKER_PROTO_DM 0x85 /* Dot-matrix / graphic ESLs */
|
||||
#define TAGTINKER_PROTO_SEG 0x84 /* 7-segment ESLs */
|
||||
#define TAGTINKER_PROTO_DM 0x85
|
||||
#define TAGTINKER_PROTO_SEG 0x84
|
||||
#define TAGTINKER_MAX_FRAME_SIZE 96
|
||||
|
||||
/* Forward declaration for TagTinkerApp (avoids circular include) */
|
||||
typedef struct TagTinkerApp TagTinkerApp;
|
||||
|
||||
/* ── CRC ────────────────────────────────────────────────────── */
|
||||
|
||||
/* CRC used by the ESL wire format. */
|
||||
uint16_t tagtinker_crc16(const uint8_t* data, size_t len);
|
||||
|
||||
/* ── Barcode / PLID ─────────────────────────────────────────── */
|
||||
|
||||
typedef enum {
|
||||
TagTinkerTagKindUnknown = 0,
|
||||
TagTinkerTagKindDotMatrix,
|
||||
@@ -93,40 +89,32 @@ size_t tagtinker_make_image_data_frame(
|
||||
uint16_t frame_index,
|
||||
const uint8_t data_bytes[20]);
|
||||
|
||||
/* ── Frame builders ─────────────────────────────────────────── */
|
||||
|
||||
/* Broadcast page-change (no barcode needed). */
|
||||
/* Broadcast frames address every listening tag. */
|
||||
size_t tagtinker_build_broadcast_page_frame(
|
||||
uint8_t* buf, uint8_t page, bool forever, uint16_t duration);
|
||||
|
||||
/* Broadcast diagnostic page. */
|
||||
size_t tagtinker_build_broadcast_debug_frame(uint8_t* buf);
|
||||
|
||||
/* Addressed DM frame: wraps raw payload with protocol + PLID + CRC. */
|
||||
/* 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);
|
||||
|
||||
/* Wake-up ping (must be sent before most addressed commands). */
|
||||
/* Tags need a wake ping before most addressed commands. */
|
||||
size_t tagtinker_make_ping_frame(uint8_t* buf, const uint8_t plid[4]);
|
||||
|
||||
/* Display refresh request. */
|
||||
size_t tagtinker_make_refresh_frame(uint8_t* buf, const uint8_t plid[4]);
|
||||
|
||||
/* MCU-level frame (used for image upload protocol). */
|
||||
/* 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);
|
||||
|
||||
/* ── Image upload helpers ───────────────────────────────────── */
|
||||
|
||||
/* RLE-compress a pixel array (0/1 values).
|
||||
* Returns compressed bitstream length. comp_type is set to 0 (raw) or 2 (RLE). */
|
||||
/* 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);
|
||||
|
||||
/* Build a complete image-upload frame sequence and store it in app state.
|
||||
* Allocates memory that the transmit scene frees on exit. */
|
||||
/* Builds the full IR sequence: wake, image params, data chunks, refresh. */
|
||||
void tagtinker_build_image_sequence(
|
||||
TagTinkerApp* app,
|
||||
const uint8_t plid[4],
|
||||
|
||||
Reference in New Issue
Block a user