/** * @file qdx_tcp_logic.c * @brief TCP Network Logic Implementation for MCU */ #include "qdx_tcp_logic.h" #include "qdx_port.h" #include #include #include /* ============================================================ * 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 L=%d R=%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.LSizeStart, (int)g_TcpLogic.cached_cfg1d.RSizeStart, (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; }