2026-03-15 09:01:02 +08:00

770 lines
26 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @file qdx_tcp_logic.c
* @brief TCP Network Logic Implementation for MCU
*/
#include "qdx_tcp_logic.h"
#include "qdx_port.h"
#include <stddef.h>
#include <stdio.h>
#include <string.h>
/* ============================================================
* Internal State & Configuration Cache
* ============================================================ */
#define RECV_BUF_SIZE 2048
#define MAX_FRAGMENT_PAYLOAD 1400
#define HEARTBEAT_INTERVAL_MS 2000
#define SERVER_TIMEOUT_MS 6000
#define RECONNECT_DELAY_MS 3000
typedef struct {
qdx_socket_t sock;
uint32_t last_activity_ms;
uint32_t last_heartbeat_ms;
uint32_t last_reconnect_ms;
uint16_t sequence;
uint8_t is_connected;
uint8_t is_running;
const char *label;
uint8_t recv_buffer[RECV_BUF_SIZE * 2];
uint32_t recv_len;
} TcpStreamCtx_t;
static struct {
uint8_t uuid[16];
uint8_t auth_token[16];
uint16_t dev_id;
int32_t pending_new_dev_id;
uint32_t frame_count;
TcpStreamCtx_t control_stream;
TcpStreamCtx_t data_stream;
qdx_mutex_t config_mutex;
uint8_t has_valid_config;
ConfigCommon_t cached_common;
Config2D_t cached_cfg2d;
Config1D_t cached_cfg1d;
ConfigUpdateCallback_t config_cb;
DetectionResultCallback_t detect_cb;
TempFrameRequestCallback_t temp_req_cb;
} g_TcpLogic;
/* Server endpoint prototype - user would configure these, but we map to demo
* defaults */
static const char *SERVER_IP = "192.168.7.50";
static const uint16_t CONTROL_PORT = 5511;
static const uint16_t DATA_PORT = 5512;
/* ============================================================
* Internal Helpers
* ============================================================ */
static void tcp_stream_init(TcpStreamCtx_t *ctx, const char *label) {
memset(ctx, 0, sizeof(TcpStreamCtx_t));
ctx->label = label;
}
static void tcp_stream_disconnect(TcpStreamCtx_t *ctx) {
DBG_NET("[%s] Disconnected\r\n", ctx->label);
ctx->is_connected = 0;
if (ctx->sock) {
qdx_port_tcp_close(ctx->sock);
ctx->sock = NULL;
}
}
static int8_t tcp_stream_connect(TcpStreamCtx_t *ctx, const char *ip,
uint16_t port) {
DBG_NET("[%s] connecting %s:%d...\r\n", ctx->label, ip, port);
ctx->sock = qdx_port_tcp_connect(ip, port);
if (ctx->sock == NULL) {
DBG_ERR("[%s] connect FAILED\r\n", ctx->label);
return -1;
}
ctx->is_connected = 1;
ctx->last_activity_ms = qdx_port_get_tick_ms();
ctx->last_heartbeat_ms = ctx->last_activity_ms;
ctx->recv_len = 0;
DBG_NET("[%s] Connected to %s:%d\r\n", ctx->label, ip, port);
return 0;
}
static int32_t tcp_send_frame(TcpStreamCtx_t *ctx, uint8_t msg_class,
uint8_t tlv_type, const uint8_t *payload,
uint16_t payload_len, uint8_t flags) {
if (!ctx->is_connected || ctx->sock == NULL)
return -1;
/* Max frame buffer for generic small control/heartbeat messages.
Note: Images use BuildAndSendTemperatureFrame instead of this. */
uint8_t buffer[1024];
if (HEADER_SIZE + TLV_HEADER_SIZE + payload_len + CRC_SIZE > sizeof(buffer)) {
return -1; /* Too large for generic send */
}
uint16_t seq = ctx->sequence++;
int frame_len =
qdx_build_frame(buffer, msg_class, tlv_type, payload, payload_len,
g_TcpLogic.dev_id, seq, qdx_port_get_tick_ms(), flags);
int32_t sent = qdx_port_tcp_send(ctx->sock, buffer, frame_len);
if (sent < 0) {
tcp_stream_disconnect(ctx);
return -1;
}
return sent;
}
static void tcp_send_handshake(TcpStreamCtx_t *ctx) {
DBG_NET("[%s] Handshake (DevID=%d)\r\n", ctx->label, (int)g_TcpLogic.dev_id);
uint8_t payload[54];
memset(payload, 0, sizeof(payload));
qdx_write_u16_le(payload + 0, 0x0200);
memcpy(payload + 2, g_TcpLogic.uuid, 16);
memcpy(payload + 18, g_TcpLogic.auth_token, 16);
/* Safe string copy without relying on strncpy platform behavior */
const char *hw = "V1.0";
const char *fw = "V2.0";
for (int i = 0; i < 8 && hw[i]; i++)
payload[34 + i] = hw[i];
for (int i = 0; i < 8 && fw[i]; i++)
payload[42 + i] = fw[i];
qdx_write_u32_le(payload + 50, 0x07);
tcp_send_frame(ctx, CLASS_SYSTEM, TYPE_HANDSHAKE, payload, sizeof(payload),
FLAG_ACK_REQ);
}
static void tcp_send_heartbeat(TcpStreamCtx_t *ctx) {
uint8_t payload[6];
qdx_write_u32_le(payload + 0, qdx_port_get_tick_ms());
payload[4] = 10; /* Placeholder CpuLoad */
payload[5] = 20; /* Placeholder MemUsage */
tcp_send_frame(ctx, CLASS_SYSTEM, TYPE_HEARTBEAT, payload, sizeof(payload),
0);
}
static void tcp_send_ack(TcpStreamCtx_t *ctx, uint16_t ack_seq, uint8_t status,
uint16_t error_code) {
uint8_t payload[5];
qdx_write_u16_le(payload + 0, ack_seq);
payload[2] = status;
qdx_write_u16_le(payload + 3, error_code);
tcp_send_frame(ctx, CLASS_RESPONSE, TYPE_ACK_PAYLOAD, payload,
sizeof(payload), 0);
}
/* ============================================================
* Receiving and Parsing
* ============================================================ */
static void qdx_deserialize_config_common(ConfigCommon_t *cfg,
const uint8_t *val) {
/* PipelineId: 16 字节字符数组,逐字节拷贝 */
for (int i = 0; i < 16; i++)
cfg->PipelineId[i] = (char)val[i];
cfg->PipelineType = val[16];
cfg->WorkMode = val[17];
cfg->ConfigTag = val[18];
cfg->StrictnessLevel = val[19];
cfg->IsCustomMode = val[20];
cfg->Reserved[0] = val[21];
cfg->Reserved[1] = val[22];
}
static void qdx_deserialize_config2d(Config2D_t *cfg, const uint8_t *val) {
cfg->Enabled = val[0];
cfg->IsLive = val[1];
cfg->DeviceId = qdx_read_u16_le(val + 2);
cfg->Width = qdx_read_u16_le(val + 4);
cfg->Height = qdx_read_u16_le(val + 6);
cfg->Fps = val[8];
cfg->Exposure = qdx_read_u32_le(val + 9);
cfg->AutoExposure = val[13];
cfg->MaskEnabled = val[14];
cfg->MaskThreshold = (int16_t)qdx_read_u16_le(val + 15);
cfg->MaskWidth = qdx_read_u16_le(val + 17);
cfg->MaskHeight = qdx_read_u16_le(val + 19);
cfg->Angle = (int16_t)qdx_read_u16_le(val + 21);
cfg->TargetWidth = qdx_read_u16_le(val + 23);
cfg->TargetHeight = qdx_read_u16_le(val + 25);
cfg->TriggerMode = val[27];
cfg->TriggerGpioLine = val[28];
cfg->TriggerDelayMs = qdx_read_u16_le(val + 29);
cfg->TriggerBurstCount = val[31];
cfg->TriggerInternalIntervalMs = qdx_read_u16_le(val + 32);
cfg->TriggerTemperatureThreshold = (int16_t)qdx_read_u16_le(val + 34);
cfg->TriggerDebounceIntervalMs = qdx_read_u16_le(val + 36);
cfg->TriggerCondition = val[38];
cfg->TriggerRoiX = qdx_read_u16_le(val + 39);
cfg->TriggerRoiY = qdx_read_u16_le(val + 41);
cfg->TriggerRoiW = qdx_read_u16_le(val + 43);
cfg->TriggerRoiH = qdx_read_u16_le(val + 45);
cfg->NGioDelay = qdx_read_u16_le(val + 47);
cfg->OutputGpioLine = val[49];
cfg->AlarmGpioLine = val[50];
cfg->AlarmHoldMs = qdx_read_u16_le(val + 51);
cfg->StoreNgImagesOnly = val[53];
cfg->TrainingEnabled = val[54];
cfg->TrainingSampleThreshold = qdx_read_u16_le(val + 55);
cfg->ProcessingTimeoutMs = qdx_read_u16_le(val + 57);
cfg->MaxProcessingQueueSize = val[59];
cfg->Reserved = val[60];
}
static void qdx_deserialize_config1d(Config1D_t *cfg, const uint8_t *val) {
cfg->Enabled = val[0];
cfg->RunMode = val[1];
cfg->TriggerType = val[2];
cfg->BufferSize = qdx_read_u16_le(val + 3);
cfg->TriggerTempLimit = (int16_t)qdx_read_u16_le(val + 5);
cfg->StartPointsToRemove = qdx_read_u16_le(val + 7);
cfg->ReferenceLength = qdx_read_u16_le(val + 9);
cfg->HighTimerLimit = qdx_read_u16_le(val + 11);
cfg->TimerCLimit = qdx_read_u16_le(val + 13);
cfg->NgCountLimit = val[15];
cfg->LSizeStart = qdx_read_u16_le(val + 16);
cfg->RSizeStart = qdx_read_u16_le(val + 18);
cfg->NGioDelay = qdx_read_u16_le(val + 20);
cfg->OutputGpioLine = val[22];
cfg->AlarmGpioLine = val[23];
cfg->AlarmHoldMs = qdx_read_u16_le(val + 24);
}
static void parse_and_dispatch_tlv(TcpStreamCtx_t *ctx, const uint8_t *packet,
uint16_t pkt_len) {
uint16_t hdr_seq = qdx_read_u16_le(packet + 5);
uint8_t hdr_flags = packet[15];
/* TLV Data starts after header (16 bytes) */
int offset = HEADER_SIZE;
int payload_len = pkt_len - HEADER_SIZE - CRC_SIZE;
int parsed_len = 0;
uint8_t cfg_updated = 0;
DBG_HB("[%s] TLV pkt: Seq=%d Len=%d\r\n", ctx->label,
hdr_seq, payload_len);
while (parsed_len <= payload_len - 3) {
uint8_t type = packet[offset];
uint16_t len = qdx_read_u16_le(packet + offset + 1);
DBG_HB("[%s] TLV: Type=0x%02X Len=%d\r\n", ctx->label, type, len);
if (parsed_len + 3 + len > payload_len) {
DBG_ERR("[%s] TLV truncated (need %d, have %d)\r\n",
ctx->label, len, payload_len - parsed_len - 3);
break; /* Malformed */
}
const uint8_t *value = packet + offset + 3;
switch (type) {
case TYPE_HEARTBEAT: {
/* Server heartbeat response — update activity timestamp */
ctx->last_activity_ms = qdx_port_get_tick_ms();
break;
}
case TYPE_ACK_PAYLOAD: {
if (len >= 3) {
uint16_t ack_seq = qdx_read_u16_le(value);
uint8_t status = value[2];
uint16_t err_code = (len >= 5) ? qdx_read_u16_le(value + 3) : 0;
DBG_CFG("<< ACK: seq=%d status=%d err=0x%04x\r\n",
(int)ack_seq, (int)status, (int)err_code);
}
break;
}
case TYPE_DEVID_ASSIGN: {
if (len >= sizeof(DevIDAssignment_t)) {
uint16_t new_id = qdx_read_u16_le(value);
DBG_CFG("<< DevID assigned: %d -> %d\r\n",
(int)g_TcpLogic.dev_id, (int)new_id);
g_TcpLogic.pending_new_dev_id = new_id;
tcp_send_ack(ctx, hdr_seq, 0, 0);
}
break;
}
case TYPE_CONFIG_COMMON: {
if (len >= sizeof(ConfigCommon_t)) {
qdx_port_mutex_lock(g_TcpLogic.config_mutex);
qdx_deserialize_config_common(&g_TcpLogic.cached_common, value);
g_TcpLogic.has_valid_config = 1;
cfg_updated = 1;
qdx_port_mutex_unlock(g_TcpLogic.config_mutex);
DBG_CFG("<< ConfigCommon: Mode=%d Tag=%d Strict=%d\r\n",
(int)g_TcpLogic.cached_common.WorkMode,
(int)g_TcpLogic.cached_common.ConfigTag,
(int)g_TcpLogic.cached_common.StrictnessLevel);
}
break;
}
case TYPE_CONFIG_2D: {
if (len >= sizeof(Config2D_t)) {
qdx_port_mutex_lock(g_TcpLogic.config_mutex);
qdx_deserialize_config2d(&g_TcpLogic.cached_cfg2d, value);
g_TcpLogic.has_valid_config = 1;
cfg_updated = 1;
qdx_port_mutex_unlock(g_TcpLogic.config_mutex);
DBG_CFG("<< Config2D: En=%d %dx%d Tgt=%dx%d Fps=%d "
"Trig=%d Burst=%d Intv=%d Thresh=%d "
"ROI(%d,%d,%d,%d) NGio=%d\r\n",
(int)g_TcpLogic.cached_cfg2d.Enabled,
(int)g_TcpLogic.cached_cfg2d.Width,
(int)g_TcpLogic.cached_cfg2d.Height,
(int)g_TcpLogic.cached_cfg2d.TargetWidth,
(int)g_TcpLogic.cached_cfg2d.TargetHeight,
(int)g_TcpLogic.cached_cfg2d.Fps,
(int)g_TcpLogic.cached_cfg2d.TriggerMode,
(int)g_TcpLogic.cached_cfg2d.TriggerBurstCount,
(int)g_TcpLogic.cached_cfg2d.TriggerInternalIntervalMs,
(int)g_TcpLogic.cached_cfg2d.TriggerTemperatureThreshold,
(int)g_TcpLogic.cached_cfg2d.TriggerRoiX,
(int)g_TcpLogic.cached_cfg2d.TriggerRoiY,
(int)g_TcpLogic.cached_cfg2d.TriggerRoiW,
(int)g_TcpLogic.cached_cfg2d.TriggerRoiH,
(int)g_TcpLogic.cached_cfg2d.NGioDelay);
} else {
DBG_ERR("<< Config2D bad len=%d (need %d)\r\n",
len, (int)sizeof(Config2D_t));
}
break;
}
case TYPE_CONFIG_1D: {
if (len >= sizeof(Config1D_t)) {
qdx_port_mutex_lock(g_TcpLogic.config_mutex);
qdx_deserialize_config1d(&g_TcpLogic.cached_cfg1d, value);
g_TcpLogic.has_valid_config = 1;
cfg_updated = 1;
qdx_port_mutex_unlock(g_TcpLogic.config_mutex);
DBG_CFG("<< Config1D: En=%d RunMode=%d TrigType=%d "
"BufSz=%d TempLim=%d NGio=%d\r\n",
(int)g_TcpLogic.cached_cfg1d.Enabled,
(int)g_TcpLogic.cached_cfg1d.RunMode,
(int)g_TcpLogic.cached_cfg1d.TriggerType,
(int)g_TcpLogic.cached_cfg1d.BufferSize,
(int)g_TcpLogic.cached_cfg1d.TriggerTempLimit,
(int)g_TcpLogic.cached_cfg1d.NGioDelay);
}
break;
}
case TYPE_TEMP_FRAME: {
DBG_CFG("<< TempFrame request (len=%d)\r\n", (int)len);
if (g_TcpLogic.temp_req_cb) {
uint8_t is2d = 0;
if (len >= 18) {
is2d = value[18];
}
DBG_CFG(" -> callback: is2D=%d\r\n", (int)is2d);
g_TcpLogic.temp_req_cb(is2d);
}
break;
}
case TYPE_DETECTION_RESULT: {
if (len >= sizeof(DetectionResult_t)) {
uint32_t frame_num = qdx_read_u32_le(value);
uint8_t result_status = value[4];
DBG_CFG("<< DetectionResult: frm=%d result=%d\r\n",
(int)frame_num, (int)result_status);
if (g_TcpLogic.detect_cb)
g_TcpLogic.detect_cb(frame_num, result_status);
}
break;
}
case TYPE_HANDSHAKE:
/* Server echoes handshake info — safe to ignore */
break;
default:
DBG_ERR("Unknown TLV type=0x%02x len=%d\r\n", (int)type, (int)len);
break;
}
offset += (3 + len);
parsed_len += (3 + len);
}
if (cfg_updated && g_TcpLogic.config_cb && g_TcpLogic.has_valid_config) {
DBG_CFG("Config updated -> notify app\r\n");
qdx_port_mutex_lock(g_TcpLogic.config_mutex);
g_TcpLogic.config_cb(&g_TcpLogic.cached_common, &g_TcpLogic.cached_cfg2d,
&g_TcpLogic.cached_cfg1d);
qdx_port_mutex_unlock(g_TcpLogic.config_mutex);
}
if (hdr_flags & FLAG_ACK_REQ) {
tcp_send_ack(ctx, hdr_seq, 0, 0);
}
}
static void tcp_process_rx_buffer(TcpStreamCtx_t *ctx) {
while (ctx->recv_len >= HEADER_SIZE) {
/* 1. Search for Magic 0x55AA */
int start_idx = -1;
for (uint32_t i = 0; i <= ctx->recv_len - 2; i++) {
if (ctx->recv_buffer[i] == 0xAA && ctx->recv_buffer[i + 1] == 0x55) {
start_idx = i;
break;
}
}
if (start_idx == -1) {
ctx->recv_buffer[0] = ctx->recv_buffer[ctx->recv_len - 1];
ctx->recv_len = 1;
break;
}
if (start_idx > 0) {
/* 缓冲区内部左移,源与目标重叠,必须使用 memmove */
memmove(ctx->recv_buffer, ctx->recv_buffer + start_idx,
ctx->recv_len - start_idx);
ctx->recv_len -= start_idx;
if (ctx->recv_len < HEADER_SIZE)
break;
}
uint8_t version = ctx->recv_buffer[2];
uint16_t length = qdx_read_u16_le(ctx->recv_buffer + 3);
if (version != PROTO_VERSION || length < HEADER_SIZE + CRC_SIZE) {
DBG_ERR("[%s] bad header: ver=0x%02X(exp 0x%02X) len=%d\r\n",
ctx->label, version, PROTO_VERSION, length);
memmove(ctx->recv_buffer, ctx->recv_buffer + 2, ctx->recv_len - 2);
ctx->recv_len -= 2;
continue;
}
if (length > sizeof(ctx->recv_buffer)) {
DBG_ERR("[%s] frame too large: %d > %d\r\n", ctx->label,
length, (int)sizeof(ctx->recv_buffer));
ctx->recv_len = 0;
break;
}
if (ctx->recv_len < length) {
break; /* Need more data */
}
/* 2. Validate CRC */
uint16_t received_crc = qdx_read_u16_le(ctx->recv_buffer + length - 2);
uint16_t calculated_crc = qdx_crc16_modbus(ctx->recv_buffer, length - 2);
if (received_crc == calculated_crc) {
/* 3. Dispatch */
parse_and_dispatch_tlv(ctx, ctx->recv_buffer, length);
} else {
DBG_ERR("[%s] CRC fail: calc=0x%04X recv=0x%04X len=%d\r\n",
ctx->label, calculated_crc, received_crc, length);
}
/* 4. 移除已处理帧(缓冲区内部左移,必须 memmove */
memmove(ctx->recv_buffer, ctx->recv_buffer + length,
ctx->recv_len - length);
ctx->recv_len -= length;
}
}
static void recv_thread_entry(void *arg) {
TcpStreamCtx_t *ctx = (TcpStreamCtx_t *)arg;
while (ctx->is_running) {
if (!ctx->is_connected) {
qdx_port_delay_ms(100);
continue;
}
/* Leave space for maximum TCP MTU read */
if (sizeof(ctx->recv_buffer) - ctx->recv_len > 0) {
int32_t bytes =
qdx_port_tcp_recv(ctx->sock, ctx->recv_buffer + ctx->recv_len,
sizeof(ctx->recv_buffer) - ctx->recv_len);
if (bytes > 0) {
ctx->recv_len += bytes;
ctx->last_activity_ms = qdx_port_get_tick_ms();
tcp_process_rx_buffer(ctx);
} else if (bytes < 0) {
/* Disconnected / Error */
tcp_stream_disconnect(ctx);
}
} else {
/* Buffer completely full but no valid packet found. Prevent overflow
* lock. */
ctx->recv_len = 0;
}
qdx_port_delay_ms(10);
}
}
/* ============================================================
* Main Background Manager
* ============================================================ */
static void manager_thread_entry(void *arg) {
while (1) {
uint32_t now = qdx_port_get_tick_ms();
/* DevID Reassignment Handling */
if (g_TcpLogic.pending_new_dev_id >= 0) {
g_TcpLogic.dev_id = (uint16_t)g_TcpLogic.pending_new_dev_id;
g_TcpLogic.pending_new_dev_id = -1;
tcp_stream_disconnect(&g_TcpLogic.control_stream);
tcp_stream_disconnect(&g_TcpLogic.data_stream);
g_TcpLogic.control_stream.last_reconnect_ms = 0;
g_TcpLogic.data_stream.last_reconnect_ms = 0;
qdx_port_delay_ms(500);
continue;
}
/* Connection Management: Control Stream */
if (!g_TcpLogic.control_stream.is_connected) {
if (now - g_TcpLogic.control_stream.last_reconnect_ms >
RECONNECT_DELAY_MS) {
if (tcp_stream_connect(&g_TcpLogic.control_stream, SERVER_IP,
CONTROL_PORT) == 0) {
tcp_send_handshake(&g_TcpLogic.control_stream);
}
g_TcpLogic.control_stream.last_reconnect_ms = qdx_port_get_tick_ms();
}
}
/* Connection Management: Data Stream */
if (!g_TcpLogic.data_stream.is_connected) {
if (now - g_TcpLogic.data_stream.last_reconnect_ms > RECONNECT_DELAY_MS) {
if (tcp_stream_connect(&g_TcpLogic.data_stream, SERVER_IP, DATA_PORT) ==
0) {
tcp_send_handshake(&g_TcpLogic.data_stream);
}
g_TcpLogic.data_stream.last_reconnect_ms = qdx_port_get_tick_ms();
}
}
/* Heartbeat & Timeout checks */
if (g_TcpLogic.control_stream.is_connected) {
if (now - g_TcpLogic.control_stream.last_heartbeat_ms >
HEARTBEAT_INTERVAL_MS) {
tcp_send_heartbeat(&g_TcpLogic.control_stream);
g_TcpLogic.control_stream.last_heartbeat_ms = now;
}
}
if (g_TcpLogic.data_stream.is_connected) {
if (now - g_TcpLogic.data_stream.last_heartbeat_ms >
HEARTBEAT_INTERVAL_MS) {
tcp_send_heartbeat(&g_TcpLogic.data_stream);
g_TcpLogic.data_stream.last_heartbeat_ms = now;
}
}
qdx_port_delay_ms(100);
}
}
/* ============================================================
* Public API Implementations
* ============================================================ */
int8_t TcpLogic_Init(const uint8_t *deviceUUID, const uint8_t *authToken) {
memset(&g_TcpLogic, 0, sizeof(g_TcpLogic));
if (deviceUUID)
memcpy(g_TcpLogic.uuid, deviceUUID, 16);
if (authToken)
memcpy(g_TcpLogic.auth_token, authToken, 16);
/* Default DevID = 101 */
g_TcpLogic.dev_id = 101;
g_TcpLogic.pending_new_dev_id = -1;
g_TcpLogic.config_mutex = qdx_port_mutex_create();
if (g_TcpLogic.config_mutex == NULL)
return -1;
tcp_stream_init(&g_TcpLogic.control_stream, "Control");
tcp_stream_init(&g_TcpLogic.data_stream, "Data");
return 0;
}
void TcpLogic_Start(void) {
g_TcpLogic.control_stream.is_running = 1;
g_TcpLogic.data_stream.is_running = 1;
qdx_port_thread_create("tcp_mgr", manager_thread_entry, NULL, 2048, 3);
qdx_port_thread_create("tcp_rx_c", recv_thread_entry,
&g_TcpLogic.control_stream, 2048, 4);
qdx_port_thread_create("tcp_rx_d", recv_thread_entry, &g_TcpLogic.data_stream,
2048, 4);
}
int8_t TcpLogic_GetLatestConfig(ConfigCommon_t *out_common,
Config2D_t *out_cfg2d, Config1D_t *out_cfg1d) {
if (!out_common || !out_cfg2d || !out_cfg1d)
return -2;
qdx_port_mutex_lock(g_TcpLogic.config_mutex);
if (!g_TcpLogic.has_valid_config) {
qdx_port_mutex_unlock(g_TcpLogic.config_mutex);
return -1;
}
memcpy(out_common, &g_TcpLogic.cached_common, sizeof(ConfigCommon_t));
memcpy(out_cfg2d, &g_TcpLogic.cached_cfg2d, sizeof(Config2D_t));
memcpy(out_cfg1d, &g_TcpLogic.cached_cfg1d, sizeof(Config1D_t));
qdx_port_mutex_unlock(g_TcpLogic.config_mutex);
return 0;
}
void TcpLogic_RegisterConfigCallback(ConfigUpdateCallback_t cb) {
g_TcpLogic.config_cb = cb;
}
void TcpLogic_RegisterDetectionCallback(DetectionResultCallback_t cb) {
g_TcpLogic.detect_cb = cb;
}
void TcpLogic_RegisterTempFrameRequestCallback(TempFrameRequestCallback_t cb) {
g_TcpLogic.temp_req_cb = cb;
}
void TcpLogic_InjectTestConfig(const ConfigCommon_t *common,
const Config2D_t *cfg2d,
const Config1D_t *cfg1d) {
qdx_port_mutex_lock(g_TcpLogic.config_mutex);
if (common) memcpy(&g_TcpLogic.cached_common, common, sizeof(*common));
if (cfg2d) memcpy(&g_TcpLogic.cached_cfg2d, cfg2d, sizeof(*cfg2d));
if (cfg1d) memcpy(&g_TcpLogic.cached_cfg1d, cfg1d, sizeof(*cfg1d));
g_TcpLogic.has_valid_config = 1;
qdx_port_mutex_unlock(g_TcpLogic.config_mutex);
if (g_TcpLogic.config_cb) {
g_TcpLogic.config_cb(&g_TcpLogic.cached_common,
&g_TcpLogic.cached_cfg2d,
&g_TcpLogic.cached_cfg1d);
}
}
/* ============================================================
* Zero-Copy Frame Building & Fragmentation for Temperature Data
* ============================================================ */
int8_t
TcpLogic_BuildAndSendTemperatureFrame(TcpTxBuffer_t *io_buffer,
const PreprocessResult_t *processMeta,
uint8_t frameType, uint8_t is2D) {
if (!g_TcpLogic.data_stream.is_connected || !io_buffer || !processMeta)
return -1;
if (io_buffer->ValidPayloadLen == 0)
return -2;
g_TcpLogic.frame_count++;
DBG_DATA(">> TempFrame #%d: %dx%d type=%d is2D=%d payload=%d\r\n",
(int)processMeta->FrameNumber,
(int)processMeta->ValidWidth, (int)processMeta->ValidHeight,
(int)frameType, (int)is2D, (int)io_buffer->ValidPayloadLen);
/* We need to prepend: TLV Header (3) + TemperatureFrameHeader_t (18) */
uint32_t tlv_wrapper_len = TLV_HEADER_SIZE + sizeof(TemperatureFrameHeader_t);
/* Ensure application left enough head room */
if (io_buffer->HeadOffset < HEADER_SIZE + tlv_wrapper_len) {
return -3; /* Not enough offset space allocated by user memory pool */
}
/* Start writing right before the application payload */
uint8_t *tlv_start =
io_buffer->pBuffer + io_buffer->HeadOffset - tlv_wrapper_len;
/* 1. Fill TLV Header manually via shift */
uint32_t tlv_value_len =
sizeof(TemperatureFrameHeader_t) + io_buffer->ValidPayloadLen;
tlv_start[0] = TYPE_TEMP_FRAME;
qdx_write_u16_le(tlv_start + 1, (uint16_t)tlv_value_len);
/* 2. Fill TemperatureFrameHeader manually via shift to avoid alignment
* faults
*/
uint8_t *temp_hdr = tlv_start + TLV_HEADER_SIZE;
qdx_write_u32_le(temp_hdr + 0, processMeta->FrameNumber);
qdx_write_u16_le(temp_hdr + 4, processMeta->ValidWidth);
qdx_write_u16_le(temp_hdr + 6, processMeta->ValidHeight);
qdx_write_u16_le(temp_hdr + 8, (uint16_t)processMeta->MinTemp);
qdx_write_u16_le(temp_hdr + 10, (uint16_t)processMeta->MaxTemp);
qdx_write_u16_le(temp_hdr + 12, (uint16_t)processMeta->AvgTemp);
qdx_write_u16_le(temp_hdr + 14, (uint16_t)processMeta->RoiTemp);
temp_hdr[16] = frameType;
temp_hdr[17] = processMeta->Status;
temp_hdr[18] = is2D;
temp_hdr[19] = 0; /* Reserved */
/* Total payload length is the entire TLV block */
uint32_t total_tlv_len = TLV_HEADER_SIZE + tlv_value_len;
/* 3. Handle Fragmentation if necessary */
if (total_tlv_len <= MAX_FRAGMENT_PAYLOAD) {
/* No fragmentation needed, build frame in place at the front */
uint8_t *frame_start = tlv_start - HEADER_SIZE;
uint16_t seq = g_TcpLogic.data_stream.sequence++;
int final_len = qdx_build_frame_inplace(
frame_start, CLASS_DATA, (uint16_t)total_tlv_len, g_TcpLogic.dev_id,
seq, qdx_port_get_tick_ms(), 0);
int32_t sent =
qdx_port_tcp_send(g_TcpLogic.data_stream.sock, frame_start, final_len);
return (sent >= 0) ? 0 : -1;
}
/* Fragmentation required.
Note: For zero-copy fragmentation, we send piece by piece.
We need an external buffer for each piece's frame header + CRC.
We can't easily prepend headers to later fragments inline. */
uint32_t offset = 0;
static uint8_t frag_buf[HEADER_SIZE + MAX_FRAGMENT_PAYLOAD + CRC_SIZE];
uint32_t frag_count =
(total_tlv_len + MAX_FRAGMENT_PAYLOAD - 1) / MAX_FRAGMENT_PAYLOAD;
DBG_DATA(">> Fragmented: %d frags, total=%d\r\n",
(int)frag_count, (int)total_tlv_len);
for (uint32_t i = 0; i < frag_count; i++) {
uint32_t chunk_len = total_tlv_len - offset;
if (chunk_len > MAX_FRAGMENT_PAYLOAD)
chunk_len = MAX_FRAGMENT_PAYLOAD;
uint8_t flags = (i == frag_count - 1) ? FLAG_LAST_FRAGMENT : 0;
uint16_t seq = g_TcpLogic.data_stream.sequence++;
/* We copy the chunk into frag_buf to append Header/CRC.
This involves ONE copy of the chunk, but it's small (1400 bytes at a
time), and ensures we don't need additional memory pools. */
int frame_len = qdx_build_fragment_frame(
frag_buf, CLASS_DATA, tlv_start + offset, (uint16_t)chunk_len,
g_TcpLogic.dev_id, seq, qdx_port_get_tick_ms(), flags);
int32_t sent =
qdx_port_tcp_send(g_TcpLogic.data_stream.sock, frag_buf, frame_len);
if (sent < 0) {
tcp_stream_disconnect(&g_TcpLogic.data_stream);
return -1;
}
offset += chunk_len;
}
return 0;
}