#include "app_mozen_handler.h" #include "mozen_tunnel.h" #include "mx_log.h" #include #include #include #include "app_calibration.h" #include "main.h" #include "cJSON.h" // Define logs if not using MX_LOG directly #ifndef MX_LOGD #define MX_LOGD MOZEN_LOGD #define MX_LOGI MOZEN_LOGI #define MX_LOGW MOZEN_LOGW #define MX_LOGE MOZEN_LOGE #endif #define MX_PTO_TAG "MOZEN_APP" volatile uint16_t g_sensor_frames_to_send = 0; volatile uint8_t g_sensor_data_type = SENSOR_BIT_16; static mozen_protocol_t *s_prot_instance = NULL; static mozen_tunnel_t s_tunnel_instance; // --- Tunnel Stream State --- static struct { bool is_streaming_write; uint8_t* write_ptr; uint8_t target_cmd; uint8_t target_sub_class; } s_stream_state; // --- Response Utilities --- #pragma pack(1) typedef struct { uint8_t target_id; uint8_t sub_class; uint8_t operation; } mozen_cmd_header_t; #pragma pack() typedef void (*cmd_sub_handler_t)(const mozen_cmd_header_t* header, const uint8_t* payload, uint16_t payload_len); typedef struct { uint8_t target_id; cmd_sub_handler_t handler; } cmd_handler_entry_t; // --- Forward Declarations --- static void param_handle_report_mode(const mozen_cmd_header_t* header, const uint8_t* payload, uint16_t payload_len); static void param_handle_device_info(const mozen_cmd_header_t* header, const uint8_t* payload, uint16_t payload_len); static void param_handle_cali_display(const mozen_cmd_header_t* header, const uint8_t* payload, uint16_t payload_len); static void prod_handle_sys_cmd(const mozen_cmd_header_t* header, const uint8_t* payload, uint16_t payload_len); static void prod_handle_map_cali_cmd(const mozen_cmd_header_t* header, const uint8_t* payload, uint16_t payload_len); // --- param setters --- static const cmd_handler_entry_t s_param_handlers[] = { {MOZEN_PARAM_TGT_REPORT_MODE, param_handle_report_mode}, // 0x01 {MOZEN_PARAM_TGT_DEVICE_INFO, param_handle_device_info}, // 0x02 {MOZEN_PARAM_TGT_CALI_DISPLAY, param_handle_cali_display}, // 0x03 }; // --- Production Test Targets --- static const cmd_handler_entry_t s_prod_handlers[] = { {MOZEN_PROD_TGT_SYS, prod_handle_sys_cmd}, {MOZEN_PROD_TGT_MAP_CALI, prod_handle_map_cali_cmd}, }; // --- Init --- mozen_protocol_t g_mozen_prot; // Global mozen protocol instance #define RX_BUFFER_SIZE 1024 // RX buffer size #define TX_BUFFER_SIZE 1024 // TX buffer size static uint8_t s_rx_buf[RX_BUFFER_SIZE]; // RX buffer static uint8_t s_tx_buf_main[TX_BUFFER_SIZE]; // TX buffer static uint8_t s_tx_buf_irq[TX_BUFFER_SIZE]; // TX buffer // --- Legacy Compatibility Functions --- static uint8_t is_legacy_config_payload(const uint8_t *data, uint16_t len) { if ((data == NULL) || (len < 3U)) return 0U; if ((data[0] != 0x3DU) && (data[0] != 0x3FU)) return 0U; if ((data[1] != '{') || (data[len - 1U] != '}')) return 0U; return 1U; } static int handle_old_protocol_device_status(uint8_t *data, uint16_t length) { if ((data == NULL) || (length < 2U)) return -1; if (data[0] == 0x3D) // "=" 设置操作 { cJSON *root = cJSON_Parse((char *)&data[1]); if (!root) { MX_LOGE(MX_PTO_TAG, "old protocol json parse failed"); return -1; } MX_LOGD(MX_PTO_TAG, "old protocol json parsed"); // 1. 从应用层获取当前状态副本进行更新 app_device_status_t new_status = *app_device_status_get(); // 2. 解析公共开关字段 cJSON *led_sw = cJSON_GetObjectItem(root, "led_sw"); if (led_sw) new_status.led_sw = (uint8_t)led_sw->valueint; cJSON *tx_sw = cJSON_GetObjectItem(root, "tx_sw"); if (tx_sw) { int tx_sw_val = tx_sw->valueint; if (new_status.sensor_tx_sw == 1 && tx_sw_val == 0) { new_status.tx_stopping = 1; } new_status.sensor_tx_sw = (uint8_t)tx_sw_val; } cJSON *pick_sw = cJSON_GetObjectItem(root, "pick_sw"); if (pick_sw) new_status.output_pick = (uint8_t)pick_sw->valueint; // printf("pick_sw: %d\n", new_status.output_pick); // 3. 解析数学标定参数 (math / math_full) cJSON *math_arr = cJSON_GetObjectItem(root, "math"); cJSON *extra_flag = cJSON_GetObjectItem(root, "math_full"); if (math_arr && cJSON_IsArray(math_arr)) { int size = cJSON_GetArraySize(math_arr); app_math_cali_t temp_params; if (app_math_get_temp_params(&temp_params)) { if (size >= 1) { temp_params.max_trigger_res_value = (uint16_t)cJSON_GetArrayItem(math_arr, 0)->valueint; } if (size >= 2) { temp_params.min_trigger_res_value = (uint16_t)cJSON_GetArrayItem(math_arr, 1)->valueint; } if (extra_flag && extra_flag->valueint && size >= 5) { temp_params.max_display_value = (uint16_t)cJSON_GetArrayItem(math_arr, 4)->valueint; } app_math_set_temp_params(&temp_params); } } // 4. 解析抗蠕变参数 cJSON *creep_strength = cJSON_GetObjectItem(root, "creep_strength"); cJSON *creep_level = cJSON_GetObjectItem(root, "creep_level"); if (creep_strength || creep_level) { app_creep_params temp_creep; app_creep_read_params(&temp_creep); if (creep_strength) { temp_creep.creep_strength = (uint8_t)creep_strength->valueint; } if (creep_level) { temp_creep.creep_level = (uint8_t)creep_level->valueint; } app_creep_write_temp_params(&temp_creep); } // 5. Flash 持久化处理 cJSON *flash_cmd = cJSON_GetObjectItem(root, "flash"); if(flash_cmd && cJSON_IsString(flash_cmd)) { if (strcmp(flash_cmd->valuestring, "creep_write") == 0) { app_creep_params temp_creep; app_creep_read_params(&temp_creep); app_creep_save_params(&temp_creep); } } // 6. 将更新后的 Legacy 状态回写到应用层 app_device_status_set(&new_status); cJSON_Delete(root); } return 0; } static bool app_mozen_raw_frame_handler(void *context, const uint8_t *raw_frame, uint16_t length) { mozen_frame_header_t header; if (length < sizeof(mozen_frame_header_t) + 2) return false; memcpy(&header, raw_frame, sizeof(mozen_frame_header_t)); if (header.command_id == MOZEN_CMD_ID_PARAM_CONFIG) // 0x02 { uint16_t full_frame_len = header.frame_length; uint16_t data_len = full_frame_len - sizeof(mozen_frame_header_t); const uint8_t *data_ptr = raw_frame + sizeof(mozen_frame_header_t); uint16_t received_crc; memcpy(&received_crc, raw_frame + full_frame_len, sizeof(uint16_t)); uint16_t calc_crc = mozen_protocol_checksum(raw_frame, full_frame_len); if ((calc_crc == received_crc) || is_legacy_config_payload(data_ptr, data_len)) { handle_old_protocol_device_status((uint8_t *)data_ptr, data_len); return true; // Handled } } return false; // Not handled, fallback to standard parsing } // --- Command Handlers --- /* * @brief handle sensor data * @param context: context * @param cmd_id: command id * @param data: data * @param length: length * @retval none */ static void handle_sensor_data(void *context, uint8_t cmd_id, const uint8_t *data, uint16_t length) { if (length < 3) { MX_LOGE(MX_PTO_TAG, "sensor command length invalid: %u", (unsigned int)length); return; } uint8_t new_data_type = data[0]; if (new_data_type != g_sensor_data_type) { g_sensor_data_type = new_data_type; app_math_cali_t temp_params; bool got_params = false; got_params = app_math_get_temp_params(&temp_params); if (got_params) { temp_params.max_display_value = 4095; temp_params.sensor_data_type = g_sensor_data_type; app_math_set_temp_params(&temp_params); } } g_sensor_frames_to_send = *(uint16_t*)(&data[1]); } // --- Response Utilities --- /* * @brief send mozen command response * @param cmd_id: command id * @param req_header: request header * @param payload: payload * @param payload_len: payload length * @retval none */ static void send_mozen_cmd_response(uint8_t cmd_id, const mozen_cmd_header_t* req_header, const uint8_t* payload, uint16_t payload_len) { static uint8_t response_buf[1024]; mozen_cmd_header_t* res_header = (mozen_cmd_header_t*)response_buf; res_header->target_id = req_header->target_id; res_header->sub_class = req_header->sub_class; res_header->operation = req_header->operation; if (payload && payload_len > 0) { memcpy(response_buf + sizeof(mozen_cmd_header_t), payload, payload_len); } mozen_protocol_send_frame(s_prot_instance, cmd_id, response_buf, sizeof(mozen_cmd_header_t) + payload_len, false); } /* * @brief send mozen command ok or ng * @param cmd_id: command id * @param req_header: request header * @param success: success or not * @retval none */ static inline void send_mozen_cmd_ok_ng(uint8_t cmd_id, const mozen_cmd_header_t* req_header, bool success) { uint8_t payload[2] = { success ? 'o' : 'N', success ? 'k' : 'G' }; send_mozen_cmd_response(cmd_id, req_header, payload, 2); } // --- Parameter Config Handlers (Cmd ID: 0x02) --- /* * @brief handle report mode * @param header: header * @param payload: payload * @param payload_len: payload length * @retval none */ static void param_handle_report_mode(const mozen_cmd_header_t* header, const uint8_t* payload, uint16_t payload_len) { if (header->sub_class == 0x01 && header->operation == 0x01 && payload_len >= 1) { if (payload[0] == 0x00) g_is_active_reporting = false; else if (payload[0] == 0x01) g_is_active_reporting = true; send_mozen_cmd_ok_ng(MOZEN_CMD_ID_PARAM_CONFIG, header, true); } } /* * @brief handle device info * @param header: header * @param payload: payload * @param payload_len: payload length * @retval none */ static void param_handle_device_info(const mozen_cmd_header_t* header, const uint8_t* payload, uint16_t payload_len) { if (header->operation == 0x00) { app_dev_info dev_info; char sensor_size_text[sizeof(dev_info.sensor_size) + 1U] = {0}; char protocol_ver_text[sizeof(dev_info.protocol_ver) + 1U] = {0}; if (app_device_get_info(&dev_info)) { memcpy(sensor_size_text, dev_info.sensor_size, sizeof(dev_info.sensor_size)); memcpy(protocol_ver_text, dev_info.protocol_ver, sizeof(dev_info.protocol_ver)); uint8_t resp_payload[256]; resp_payload[0] = 'o'; resp_payload[1] = 'k'; int json_len = snprintf((char *)(resp_payload + 2), sizeof(resp_payload) - 2, "{\"PN\":\"%s\",\"SN\":\"%s\",\"SV\":\"%s\",\"HVI\":\"%s\",\"XY\":\"%s\",\"Protocol\":\"%s\"}", dev_info.pn, dev_info.sn, dev_info.sw_ver, dev_info.hw_ver, sensor_size_text, protocol_ver_text); if (json_len > 0) { send_mozen_cmd_response(MOZEN_CMD_ID_PARAM_CONFIG, header, resp_payload, 2 + (uint16_t)json_len); } else { send_mozen_cmd_ok_ng(MOZEN_CMD_ID_PARAM_CONFIG, header, false); } } else { send_mozen_cmd_ok_ng(MOZEN_CMD_ID_PARAM_CONFIG, header, false); } } } /* * @brief handle calibration display * @param header: header * @param payload: payload * @param payload_len: payload length * @retval none */ static void param_handle_cali_display(const mozen_cmd_header_t* header, const uint8_t* payload, uint16_t payload_len) { if (header->operation == 0x00) { uint8_t success = 0; uint16_t data_len = 0; uint8_t resp_payload[256]; if (header->sub_class == 0x01) { success = app_math_read_temp_params(resp_payload + 2, &data_len); } else if (header->sub_class == 0x02) { success = app_math_read_solidified_params(resp_payload + 2, &data_len); } if (success) { resp_payload[0] = 'o'; resp_payload[1] = 'k'; send_mozen_cmd_response(MOZEN_CMD_ID_PARAM_CONFIG, header, resp_payload, 2 + data_len); } else { send_mozen_cmd_ok_ng(MOZEN_CMD_ID_PARAM_CONFIG, header, false); } } else if (header->operation == 0x01) { uint8_t success = 0; if (header->sub_class == 0x01) { success = app_math_write_temp_params(payload, payload_len); } else if (header->sub_class == 0x02) { success = app_math_save_params(payload, payload_len); } else if (header->sub_class == 0x03) { success = app_math_clear_params(); app_calibration_invalidate_runtime(); } send_mozen_cmd_ok_ng(MOZEN_CMD_ID_PARAM_CONFIG, header, success); } } /* * @brief handle param config * @param context: context * @param cmd_id: cmd id * @param data: data * @param length: length * @retval none */ static void handle_param_config(void *context, uint8_t cmd_id, const uint8_t *data, uint16_t length) { if (length < sizeof(mozen_cmd_header_t)) return; const mozen_cmd_header_t* cmd_header = (const mozen_cmd_header_t*)data; const uint8_t* cmd_payload = data + sizeof(mozen_cmd_header_t); uint16_t payload_len = length - sizeof(mozen_cmd_header_t); for (size_t i = 0; i < sizeof(s_param_handlers) / sizeof(s_param_handlers[0]); i++) { if (s_param_handlers[i].target_id == cmd_header->target_id) { s_param_handlers[i].handler(cmd_header, cmd_payload, payload_len); break; } } } // --- Production Test Handlers (Cmd ID: 0xAA) --- /* * @brief handle system command * @param header: header * @param payload: payload * @param payload_len: payload length * @retval none */ static void prod_handle_sys_cmd(const mozen_cmd_header_t* header, const uint8_t* payload, uint16_t payload_len) { if (header->operation == 0x01) { if (header->sub_class == 0x02) { if(payload_len > 0) { g_is_creep_enable = (payload[0] == 0x01); send_mozen_cmd_ok_ng(MOZEN_CMD_ID_PROD_TEST, header, true); } } else if (header->sub_class == 0x01) { app_dev_info dev_info; if (payload_len == 0U || payload_len >= sizeof(dev_info.sn)) { send_mozen_cmd_ok_ng(MOZEN_CMD_ID_PROD_TEST, header, false); return; } if (!app_device_get_info(&dev_info)) { send_mozen_cmd_ok_ng(MOZEN_CMD_ID_PROD_TEST, header, false); return; } memset(dev_info.sn, 0, sizeof(dev_info.sn)); memcpy(dev_info.sn, payload, payload_len); if (!app_device_set_info(&dev_info)) { send_mozen_cmd_ok_ng(MOZEN_CMD_ID_PROD_TEST, header, false); return; } send_mozen_cmd_ok_ng(MOZEN_CMD_ID_PROD_TEST, header, true); } } } /* * @brief handle map calibration * @param header: header * @param payload: payload * @param payload_len: payload length * @retval none */ static void prod_handle_map_cali_cmd(const mozen_cmd_header_t* header, const uint8_t* payload, uint16_t payload_len) { uint8_t resp_payload[260]; // First, handle map temp params (operation 0x01, sub_class 0x3F / 0x3D) which return no response // First, handle map temp params (operation 0x01, sub_class 0x3F / 0x3D) which return no response if (header->operation == 0x01) { if (header->sub_class == 0x3F) { app_map_write_temp_params(payload, payload_len); return; } else if (header->sub_class == 0x3D) { app_map_save_params(payload, payload_len); return; } } switch (header->sub_class) { case 0x4F: if (header->operation == 0x00) { uint16_t d_len; if(app_pressure_read_temp_params(resp_payload + 2, &d_len)) { resp_payload[0] = 'o'; resp_payload[1] = 'k'; send_mozen_cmd_response(MOZEN_CMD_ID_PROD_TEST, header, resp_payload, 2 + d_len); } else { send_mozen_cmd_ok_ng(MOZEN_CMD_ID_PROD_TEST, header, false); } } else if (header->operation == 0x01) { bool success = app_pressure_write_temp_params(payload, payload_len); send_mozen_cmd_ok_ng(MOZEN_CMD_ID_PROD_TEST, header, success); } break; case 0x4D: if (header->operation == 0x00) { uint16_t d_len; if(app_pressure_read_solidified_params(resp_payload + 2, &d_len)) { resp_payload[0] = 'o'; resp_payload[1] = 'k'; send_mozen_cmd_response(MOZEN_CMD_ID_PROD_TEST, header, resp_payload, 2 + d_len); } else { send_mozen_cmd_ok_ng(MOZEN_CMD_ID_PROD_TEST, header, false); } } else if (header->operation == 0x01) { bool success = app_pressure_save_params(payload, payload_len); send_mozen_cmd_ok_ng(MOZEN_CMD_ID_PROD_TEST, header, success); } break; case 0x3F: case 0x3D: if (header->operation == 0x00) { uint32_t total_data_len = app_map_data_size(); uint16_t total_packets = (total_data_len + 240 - 1) / 240; memcpy(resp_payload, &total_data_len, sizeof(uint32_t)); memcpy(resp_payload + 4, &total_packets, sizeof(uint16_t)); send_mozen_cmd_response(MOZEN_CMD_ID_PROD_TEST, header, resp_payload, 6); } else if (header->operation == 0x02) { if (payload_len < 2) { send_mozen_cmd_ok_ng(MOZEN_CMD_ID_PROD_TEST, header, false); break; } uint16_t packet_index; memcpy(&packet_index, payload, sizeof(uint16_t)); uint32_t total_data_len = app_map_data_size(); uint32_t offset = (packet_index - 1) * 240; if (offset >= total_data_len) { send_mozen_cmd_ok_ng(MOZEN_CMD_ID_PROD_TEST, header, false); break; } uint16_t chunk_len = 240; if (offset + chunk_len > total_data_len) chunk_len = total_data_len - offset; memcpy(resp_payload, &packet_index, sizeof(uint16_t)); memcpy(resp_payload + 2, app_map_data_ptr() + offset, chunk_len); send_mozen_cmd_response(MOZEN_CMD_ID_PROD_TEST, header, resp_payload, 2 + chunk_len); } break; case 0x00: if (header->operation == 0x01) { bool success = app_pressure_clear_params(); success = success && app_map_clear_params(); send_mozen_cmd_ok_ng(MOZEN_CMD_ID_PROD_TEST, header, success); app_calibration_invalidate_runtime(); } break; } } /* * This function handles the production test command. * It checks the target ID and calls the appropriate handler function. * @param context The context pointer. * @param cmd_id The command ID. * @param data The command data. * @param length The length of the command data. */ static void handle_production_test(void *context, uint8_t cmd_id, const uint8_t *data, uint16_t length) { if (length < sizeof(mozen_cmd_header_t)) return; const mozen_cmd_header_t* cmd_header = (const mozen_cmd_header_t*)data; const uint8_t* cmd_payload = data + sizeof(mozen_cmd_header_t); uint16_t payload_len = length - sizeof(mozen_cmd_header_t); for (size_t i = 0; i < sizeof(s_prod_handlers) / sizeof(s_prod_handlers[0]); i++) { if (s_prod_handlers[i].target_id == cmd_header->target_id) { s_prod_handlers[i].handler(cmd_header, cmd_payload, payload_len); break; } } } /* * This function handles the tunnel command. * It calls the tunnel handle command function with the command data. * @param context The context pointer. * @param cmd_id The command ID. * @param data The command data. * @param length The length of the command data. */ static void handle_tunnel_command(void *context, uint8_t cmd_id, const uint8_t *data, uint16_t length) { mozen_tunnel_handle_command(&s_tunnel_instance, data, length); } // --- Tunnel Callbacks --- /* * This function is called when a stream starts. * It checks if the target command is production test and the sub class is 0x3F or 0x3D. * If so, it sets the stream state to streaming write and initializes the write pointer. * @param context The context pointer. * @param target_cmd The target command. * @param first_chunk_data The first chunk of data. * @param chunk_len The length of the chunk. * @return true if the stream is started, false otherwise. */ static bool tunnel_on_stream_start(void *context, uint8_t target_cmd, const uint8_t *first_chunk_data, uint16_t chunk_len) { if (target_cmd == MOZEN_CMD_ID_PROD_TEST) { const mozen_cmd_header_t* pt_header = (const mozen_cmd_header_t*)first_chunk_data; if (chunk_len >= sizeof(mozen_cmd_header_t) && pt_header->target_id == MOZEN_PROD_TGT_MAP_CALI && pt_header->operation == 0x01) { if (pt_header->sub_class == 0x3F || pt_header->sub_class == 0x3D) { s_stream_state.is_streaming_write = true; s_stream_state.target_sub_class = pt_header->sub_class; s_stream_state.target_cmd = target_cmd; s_stream_state.write_ptr = app_map_data_ptr(); uint16_t data_offset = sizeof(mozen_cmd_header_t); uint16_t data_to_write = chunk_len - data_offset; uint8_t* end_ptr = (app_map_data_ptr() + app_map_data_size()); if (s_stream_state.write_ptr + data_to_write > end_ptr) { data_to_write = end_ptr - s_stream_state.write_ptr; } if (data_to_write > 0) { memcpy(s_stream_state.write_ptr, first_chunk_data + data_offset, data_to_write); s_stream_state.write_ptr += data_to_write; } return true; } } } return false; } /* * This function is called when data is received during a stream. * It checks if the stream is in streaming write mode and writes the data to the map data buffer. * @param context The context pointer. * @param data The data. * @param len The length of the data. * @return true if the data is handled, false otherwise. */ static bool tunnel_on_stream_data(void *context, const uint8_t *data, uint16_t len) { if (s_stream_state.is_streaming_write) { uint8_t* end_ptr = (app_map_data_ptr() + app_map_data_size()); uint16_t data_to_write = len; if (s_stream_state.write_ptr >= end_ptr) { data_to_write = 0; } else if (s_stream_state.write_ptr + data_to_write > end_ptr) { data_to_write = end_ptr - s_stream_state.write_ptr; } if (data_to_write > 0) { memcpy(s_stream_state.write_ptr, data, data_to_write); s_stream_state.write_ptr += data_to_write; } return true; } return false; } /* * This function is called when a stream finishes. * It checks if the stream is in streaming write mode and saves the map parameters if the target command is production test and the sub class is 0x3F or 0x3D. * @param context The context pointer. * @param success The success flag. * @return None. */ static void tunnel_on_stream_finish(void *context, bool success) { if (s_stream_state.is_streaming_write && success) { uint32_t expected_bytes = app_map_data_size(); uint32_t written_bytes = (uint32_t)(s_stream_state.write_ptr - app_map_data_ptr()); if (written_bytes >= expected_bytes) { if (s_stream_state.target_cmd == MOZEN_CMD_ID_PROD_TEST && s_stream_state.target_sub_class == 0x3D) { app_map_save_params(app_map_data_ptr(), (uint16_t)expected_bytes); app_calibration_request_reset_effect(); } } } s_stream_state.is_streaming_write = false; s_stream_state.write_ptr = NULL; } // --- Init --- /* * @brief: Initialize the mozen handler. * @param tx_fn: The function to send data. * @return None. */ void app_mozen_init(mozen_tx_fn_t tx_fn) { mozen_protocol_init(&g_mozen_prot, s_rx_buf, sizeof(s_rx_buf), s_tx_buf_main, s_tx_buf_irq, sizeof(s_tx_buf_main), tx_fn, NULL); app_mozen_handler_init(&g_mozen_prot); } /* * @brief: Handle raw frame from mozen protocol * @param: prot - Protocol instance * @return: None */ void app_mozen_handler_init(mozen_protocol_t *prot) { s_prot_instance = prot; // Register commands mozen_protocol_register_handler(prot, MOZEN_CMD_ID_SENSOR_DATA, handle_sensor_data); mozen_protocol_register_handler(prot, MOZEN_CMD_ID_PARAM_CONFIG, handle_param_config); mozen_protocol_register_handler(prot, MOZEN_CMD_ID_PROD_TEST, handle_production_test); mozen_protocol_register_handler(prot, MOZEN_TUNNEL_COMMAND_ID, handle_tunnel_command); // Legacy handler mozen_protocol_set_raw_handler(prot, app_mozen_raw_frame_handler); // Init tunnel mozen_tunnel_init(&s_tunnel_instance, prot, 240, NULL); mozen_tunnel_set_callbacks(&s_tunnel_instance, tunnel_on_stream_start, tunnel_on_stream_data, tunnel_on_stream_finish); }